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() {
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) {
const groupColumns = this.groupColumns;
if (groupColumns && groupColumns.length > 0) {
select.groupBy = groupColumns.map((col) => ({
// @ts-ignore
select.groupBy = groupColumns.map((col) => {
const colExpr: Expression = {
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)) {
for (let i = 0; i < select.columns.length; i++) {
const uniqueName = select.columns[i].alias;
if (groupColumns && groupColumns.includes(uniqueName)) continue;
// if (groupColumns && groupColumns.includes(uniqueName)) continue;
const grouping = this.getGrouping(uniqueName);
if (grouping == 'NULL') {
if (grouping == 'GROUP') {
continue;
} else if (grouping == '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 {
let func = 'MAX';
let argsPrefix = '';

View File

@ -76,6 +76,9 @@ class SqlDumper {
case 'v':
this.putValue(value);
break;
case 'c':
value(this);
break;
}
}
putFormattedList(c, collection) {
@ -255,6 +258,11 @@ class SqlDumper {
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 allow {boolean}

View File

@ -13,7 +13,41 @@ class MsSqlDumper extends SqlDumper {
}
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 function getFilterValueExpression(value) {
export function getFilterValueExpression(value, dataType) {
if (value == null) return 'NULL';
if (isTypeDateTime(dataType)) return value;
return `="${value}"`;
}

View File

@ -1,6 +1,7 @@
import P from 'parsimmon';
import { FilterType } from './types';
import { Condition } from '@dbgate/sqltree';
import { TransformType } from '@dbgate/types';
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 langDef = {
string1: () =>
@ -123,6 +168,10 @@ const createParser = (filterType: FilterType) => {
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])),
valueTestEq: (r) => r.value.map(binaryCondition('=')),
valueTestStr: (r) => r.value.map(likeCondition('like', '%#VALUE#%')),
@ -173,6 +222,7 @@ const createParser = (filterType: FilterType) => {
'containsNot'
);
if (filterType == 'logical') allowedElements.push('true', 'false', 'trueNum', 'falseNum');
if (filterType == 'datetime') allowedElements.push('yearMonthDayNum', 'yearMonthNum', 'yearNum');
// must be last
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.put(')');
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 {
// }
@ -130,7 +130,19 @@ export interface CallExpression {
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 ResultField = Expression & { alias?: string };

View File

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

View File

@ -5,6 +5,7 @@ import DropDownButton from '../widgets/DropDownButton';
import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu';
import { useSplitterDrag } from '../widgets/Splitter';
import { FontIcon } from '../icons';
import { isTypeDateTime } from '@dbgate/tools';
const HeaderDiv = styled.div`
display: flex;
@ -69,6 +70,16 @@ export default function ColumnHeaderControl({ column, setSort, onResize, order,
<DropDownMenuItem onClick={() => setGrouping('AVG')}>AVG</DropDownMenuItem>
<DropDownMenuItem onClick={() => setGrouping('COUNT')}>COUNT</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>
)}
<ResizeHandle className="resizeHandleControl" onMouseDown={onResizeDown} />

View File

@ -484,14 +484,13 @@ export default function DataGridCore(props) {
React.useEffect(() => {
if (display.groupColumns) {
console.log('SET REFERENCE');
props.onReferenceClick({
schemaName: display.baseTable.schemaName,
pureName: display.baseTable.pureName,
columns: display.groupColumns.map((col) => ({
baseName: 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(
reference.columns.map((col) => [
col.refName,
selectedRows.map((x) => getFilterValueExpression(x[col.baseName])).join(', '),
selectedRows.map((x) => getFilterValueExpression(x[col.baseName], col.dataType)).join(', '),
])
),
};