grouping - work with datetimes

This commit is contained in:
Jan Prochazka
2020-06-21 21:44:54 +02:00
parent 9cd2e68f0b
commit 72d38e4b8c
11 changed files with 164 additions and 15 deletions

View File

@@ -189,25 +189,50 @@ export abstract class GridDisplay {
} }
get groupColumns() { get groupColumns() {
return this.isGrouped ? _.keys(_.pickBy(this.config.grouping, (v) => v == 'GROUP')) : null; return this.isGrouped
? _.keys(_.pickBy(this.config.grouping, (v) => v == 'GROUP' || v.startsWith('GROUP:')))
: null;
} }
applyGroupOnSelect(select: Select, displayedColumnInfo: DisplayedColumnInfo) { applyGroupOnSelect(select: Select, displayedColumnInfo: DisplayedColumnInfo) {
const groupColumns = this.groupColumns; const groupColumns = this.groupColumns;
if (groupColumns && groupColumns.length > 0) { if (groupColumns && groupColumns.length > 0) {
select.groupBy = groupColumns.map((col) => ({ // @ts-ignore
exprType: 'column', select.groupBy = groupColumns.map((col) => {
columnName: displayedColumnInfo[col].columnName, const colExpr: Expression = {
source: { alias: displayedColumnInfo[col].sourceAlias }, exprType: 'column',
})); columnName: displayedColumnInfo[col].columnName,
source: { alias: displayedColumnInfo[col].sourceAlias },
};
const grouping = this.config.grouping[col];
if (grouping.startsWith('GROUP:')) {
return {
exprType: 'transform',
transform: grouping,
expr: colExpr,
};
} else {
return colExpr;
}
});
} }
if (!_.isEmpty(this.config.grouping)) { if (!_.isEmpty(this.config.grouping)) {
for (let i = 0; i < select.columns.length; i++) { for (let i = 0; i < select.columns.length; i++) {
const uniqueName = select.columns[i].alias; const uniqueName = select.columns[i].alias;
if (groupColumns && groupColumns.includes(uniqueName)) continue; // if (groupColumns && groupColumns.includes(uniqueName)) continue;
const grouping = this.getGrouping(uniqueName); const grouping = this.getGrouping(uniqueName);
if (grouping == 'NULL') { if (grouping == 'GROUP') {
continue;
} else if (grouping == 'NULL') {
select.columns[i].alias = null; select.columns[i].alias = null;
} else if (grouping && grouping.startsWith('GROUP:')) {
select.columns[i] = {
exprType: 'transform',
// @ts-ignore
transform: grouping,
expr: select.columns[i],
alias: select.columns[i].alias,
};
} else { } else {
let func = 'MAX'; let func = 'MAX';
let argsPrefix = ''; let argsPrefix = '';

View File

@@ -76,6 +76,9 @@ class SqlDumper {
case 'v': case 'v':
this.putValue(value); this.putValue(value);
break; break;
case 'c':
value(this);
break;
} }
} }
putFormattedList(c, collection) { putFormattedList(c, collection) {
@@ -255,6 +258,11 @@ class SqlDumper {
if (fk.updateAction) this.put(' ^on ^update %k', fk.updateAction); if (fk.updateAction) this.put(' ^on ^update %k', fk.updateAction);
} }
/** @param type {import('@dbgate/types').TransformType} */
transform(type, dumpExpr) {
dumpExpr();
}
/** /**
* @param table {import('@dbgate/types').NamedObjectInfo} * @param table {import('@dbgate/types').NamedObjectInfo}
* @param allow {boolean} * @param allow {boolean}

View File

@@ -13,7 +13,41 @@ class MsSqlDumper extends SqlDumper {
} }
allowIdentityInsert(table, allow) { allowIdentityInsert(table, allow) {
this.putCmd("^set ^identity_insert %f %k;&n", table, allow ? "on" : "off"); this.putCmd('^set ^identity_insert %f %k;&n', table, allow ? 'on' : 'off');
}
/** @param type {import('@dbgate/types').TransformType} */
transform(type, dumpExpr) {
switch (type) {
case 'GROUP:YEAR':
case 'YEAR':
this.put('^datepart(^year, %c)', dumpExpr);
break;
case 'MONTH':
this.put('^datepart(^month, %c)', dumpExpr);
break;
case 'DAY':
this.put('^datepart(^day, %c)', dumpExpr);
break;
case 'GROUP:MONTH':
this.put(
"^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^convert(^varchar(100), ^datepart(^month, %c))",
dumpExpr,
dumpExpr
);
break;
case 'GROUP:DAY':
this.put(
"^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^convert(^varchar(100), ^datepart(^month, %c))+'-' + ^convert(^varchar(100), ^datepart(^day, %c))",
dumpExpr,
dumpExpr,
dumpExpr
);
break;
default:
dumpExpr();
break;
}
} }
} }

View File

@@ -1,7 +1,10 @@
import { isTypeDateTime } from '@dbgate/tools';
export type FilterMultipleValuesMode = 'is' | 'is_not' | 'contains' | 'begins' | 'ends'; export type FilterMultipleValuesMode = 'is' | 'is_not' | 'contains' | 'begins' | 'ends';
export function getFilterValueExpression(value) { export function getFilterValueExpression(value, dataType) {
if (value == null) return 'NULL'; if (value == null) return 'NULL';
if (isTypeDateTime(dataType)) return value;
return `="${value}"`; return `="${value}"`;
} }

View File

@@ -1,6 +1,7 @@
import P from 'parsimmon'; import P from 'parsimmon';
import { FilterType } from './types'; import { FilterType } from './types';
import { Condition } from '@dbgate/sqltree'; import { Condition } from '@dbgate/sqltree';
import { TransformType } from '@dbgate/types';
const whitespace = P.regexp(/\s*/m); const whitespace = P.regexp(/\s*/m);
@@ -94,6 +95,50 @@ const negateCondition = (condition) => {
}; };
}; };
function getTransformCondition(transform: TransformType, value) {
return {
conditionType: 'binary',
operator: '=',
left: {
exprType: 'transform',
transform,
expr: {
exprType: 'placeholder',
},
},
right: {
exprType: 'value',
value,
},
};
}
const yearCondition = () => (value) => {
return getTransformCondition('YEAR', value);
};
const yearMonthCondition = () => (value) => {
const m = value.match(/(\d\d\d\d)-(\d\d?)/);
return {
conditionType: 'and',
conditions: [getTransformCondition('YEAR', m[1]), getTransformCondition('MONTH', m[2])],
};
};
const yearMonthDayCondition = () => (value) => {
const m = value.match(/(\d\d\d\d)-(\d\d?)-(\d\d?)/);
return {
conditionType: 'and',
conditions: [
getTransformCondition('YEAR', m[1]),
getTransformCondition('MONTH', m[2]),
getTransformCondition('DAY', m[3]),
],
};
};
const createParser = (filterType: FilterType) => { const createParser = (filterType: FilterType) => {
const langDef = { const langDef = {
string1: () => string1: () =>
@@ -123,6 +168,10 @@ const createParser = (filterType: FilterType) => {
noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'), noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'),
yearNum: () => P.regexp(/\d\d\d\d/).map(yearCondition()),
yearMonthNum: () => P.regexp(/\d\d\d\d-\d\d?/).map(yearMonthCondition()),
yearMonthDayNum: () => P.regexp(/\d\d\d\d-\d\d?-\d\d?/).map(yearMonthDayCondition()),
value: (r) => P.alt(...allowedValues.map((x) => r[x])), value: (r) => P.alt(...allowedValues.map((x) => r[x])),
valueTestEq: (r) => r.value.map(binaryCondition('=')), valueTestEq: (r) => r.value.map(binaryCondition('=')),
valueTestStr: (r) => r.value.map(likeCondition('like', '%#VALUE#%')), valueTestStr: (r) => r.value.map(likeCondition('like', '%#VALUE#%')),
@@ -173,6 +222,7 @@ const createParser = (filterType: FilterType) => {
'containsNot' 'containsNot'
); );
if (filterType == 'logical') allowedElements.push('true', 'false', 'trueNum', 'falseNum'); if (filterType == 'logical') allowedElements.push('true', 'false', 'trueNum', 'falseNum');
if (filterType == 'datetime') allowedElements.push('yearMonthDayNum', 'yearMonthNum', 'yearNum');
// must be last // must be last
if (filterType == 'string') allowedElements.push('valueTestStr'); if (filterType == 'string') allowedElements.push('valueTestStr');

View File

@@ -33,5 +33,9 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
dmp.putCollection(',', expr.args, (x) => dumpSqlExpression(dmp, x)); dmp.putCollection(',', expr.args, (x) => dumpSqlExpression(dmp, x));
dmp.put(')'); dmp.put(')');
break; break;
case 'transform':
dmp.transform(expr.transform, () => dumpSqlExpression(dmp, expr.expr));
break;
} }
} }

View File

@@ -1,4 +1,4 @@
import { NamedObjectInfo, RangeDefinition } from '@dbgate/types'; import { NamedObjectInfo, RangeDefinition, TransformType } from '@dbgate/types';
// export interface Command { // export interface Command {
// } // }
@@ -130,7 +130,19 @@ export interface CallExpression {
argsPrefix?: string; // DISTINCT in case of COUNT DISTINCT argsPrefix?: string; // DISTINCT in case of COUNT DISTINCT
} }
export type Expression = ColumnRefExpression | ValueExpression | PlaceholderExpression | RawExpression | CallExpression; export interface TranformExpression {
exprType: 'transform';
expr: Expression;
transform: TransformType;
}
export type Expression =
| ColumnRefExpression
| ValueExpression
| PlaceholderExpression
| RawExpression
| CallExpression
| TranformExpression;
export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' }; export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' };
export type ResultField = Expression & { alias?: string }; export type ResultField = Expression & { alias?: string };

View File

@@ -1,6 +1,8 @@
import { TableInfo } from './dbinfo'; import { TableInfo } from './dbinfo';
import { SqlDialect } from './dialect'; import { SqlDialect } from './dialect';
export type TransformType = 'GROUP:YEAR' | 'GROUP:MONTH' | 'GROUP:DAY' | 'YEAR' | 'MONTH' | 'DAY'; // | 'GROUP:HOUR' | 'GROUP:MINUTE';
export interface SqlDumper { export interface SqlDumper {
s: string; s: string;
dialect: SqlDialect; dialect: SqlDialect;
@@ -10,6 +12,7 @@ export interface SqlDumper {
putCmd(format: string, ...args); putCmd(format: string, ...args);
putValue(value: string | number | Date); putValue(value: string | number | Date);
putCollection<T>(delimiter: string, collection: T[], lambda: (item: T) => void); putCollection<T>(delimiter: string, collection: T[], lambda: (item: T) => void);
transform(type: TransformType, dumpExpr: () => void);
endCommand(); endCommand();
createTable(table: TableInfo); createTable(table: TableInfo);

View File

@@ -5,6 +5,7 @@ import DropDownButton from '../widgets/DropDownButton';
import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu'; import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu';
import { useSplitterDrag } from '../widgets/Splitter'; import { useSplitterDrag } from '../widgets/Splitter';
import { FontIcon } from '../icons'; import { FontIcon } from '../icons';
import { isTypeDateTime } from '@dbgate/tools';
const HeaderDiv = styled.div` const HeaderDiv = styled.div`
display: flex; display: flex;
@@ -69,6 +70,16 @@ export default function ColumnHeaderControl({ column, setSort, onResize, order,
<DropDownMenuItem onClick={() => setGrouping('AVG')}>AVG</DropDownMenuItem> <DropDownMenuItem onClick={() => setGrouping('AVG')}>AVG</DropDownMenuItem>
<DropDownMenuItem onClick={() => setGrouping('COUNT')}>COUNT</DropDownMenuItem> <DropDownMenuItem onClick={() => setGrouping('COUNT')}>COUNT</DropDownMenuItem>
<DropDownMenuItem onClick={() => setGrouping('COUNT DISTINCT')}>COUNT DISTINCT</DropDownMenuItem> <DropDownMenuItem onClick={() => setGrouping('COUNT DISTINCT')}>COUNT DISTINCT</DropDownMenuItem>
{isTypeDateTime(column.dataType) && (
<>
<DropDownMenuDivider />
<DropDownMenuItem onClick={() => setGrouping('GROUP:YEAR')}>Group by YEAR</DropDownMenuItem>
<DropDownMenuItem onClick={() => setGrouping('GROUP:MONTH')}>Group by MONTH</DropDownMenuItem>
<DropDownMenuItem onClick={() => setGrouping('GROUP:DAY')}>Group by DAY</DropDownMenuItem>
{/* <DropDownMenuItem onClick={() => setGrouping('GROUP:HOUR')}>Group by HOUR</DropDownMenuItem>
<DropDownMenuItem onClick={() => setGrouping('GROUP:MINUTE')}>Group by MINUTE</DropDownMenuItem> */}
</>
)}
</DropDownButton> </DropDownButton>
)} )}
<ResizeHandle className="resizeHandleControl" onMouseDown={onResizeDown} /> <ResizeHandle className="resizeHandleControl" onMouseDown={onResizeDown} />

View File

@@ -484,14 +484,13 @@ export default function DataGridCore(props) {
React.useEffect(() => { React.useEffect(() => {
if (display.groupColumns) { if (display.groupColumns) {
console.log('SET REFERENCE');
props.onReferenceClick({ props.onReferenceClick({
schemaName: display.baseTable.schemaName, schemaName: display.baseTable.schemaName,
pureName: display.baseTable.pureName, pureName: display.baseTable.pureName,
columns: display.groupColumns.map((col) => ({ columns: display.groupColumns.map((col) => ({
baseName: col, baseName: col,
refName: col, refName: col,
dataType: _.get(display.baseTable && display.baseTable.columns.find((x) => x.columnName == col), 'dataType'),
})), })),
}); });
} }

View File

@@ -106,7 +106,7 @@ export default function TableDataGrid({
..._.fromPairs( ..._.fromPairs(
reference.columns.map((col) => [ reference.columns.map((col) => [
col.refName, col.refName,
selectedRows.map((x) => getFilterValueExpression(x[col.baseName])).join(', '), selectedRows.map((x) => getFilterValueExpression(x[col.baseName], col.dataType)).join(', '),
]) ])
), ),
}; };