mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
grouping - work with datetimes
This commit is contained in:
parent
9cd2e68f0b
commit
72d38e4b8c
@ -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) => ({
|
||||
exprType: 'column',
|
||||
columnName: displayedColumnInfo[col].columnName,
|
||||
source: { alias: displayedColumnInfo[col].sourceAlias },
|
||||
}));
|
||||
// @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 = '';
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}"`;
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
|
3
packages/types/dumper.d.ts
vendored
3
packages/types/dumper.d.ts
vendored
@ -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);
|
||||
|
@ -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} />
|
||||
|
@ -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'),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
@ -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(', '),
|
||||
])
|
||||
),
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user