Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {NavigationTree} from 'ydb-ui-components';

import {useCreateDirectoryFeatureAvailable} from '../../../../store/reducers/capabilities/hooks';
import {schemaApi} from '../../../../store/reducers/schema/schema';
import type {GetTableSchemaDataParams} from '../../../../store/reducers/tableSchemaData';
import {useGetTableSchemaDataMutation} from '../../../../store/reducers/tableSchemaData';
import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
import {useQueryExecutionSettings, useTypedDispatch} from '../../../../utils/hooks';
import {getSchemaControls} from '../../utils/controls';
Expand All @@ -26,6 +28,19 @@ export function SchemaTree(props: SchemaTreeProps) {
const createDirectoryFeatureAvailable = useCreateDirectoryFeatureAvailable();
const {rootPath, rootName, rootType, currentPath, onActivePathUpdate} = props;
const dispatch = useTypedDispatch();
const [getTableSchemaDataMutation] = useGetTableSchemaDataMutation();

const getTableSchemaDataPromise = React.useCallback(
async (args: GetTableSchemaDataParams) => {
try {
const result = await getTableSchemaDataMutation(args).unwrap();
return result;
} catch (e) {
return undefined;
}
},
[getTableSchemaDataMutation],
);

const [querySettings, setQueryExecutionSettings] = useQueryExecutionSettings();
const [createDirectoryOpen, setCreateDirectoryOpen] = React.useState(false);
Expand Down Expand Up @@ -119,6 +134,7 @@ export function SchemaTree(props: SchemaTreeProps) {
showCreateDirectoryDialog: createDirectoryFeatureAvailable
? handleOpenCreateDirectoryDialog
: undefined,
getTableSchemaDataPromise,
},
rootPath,
)}
Expand Down
8 changes: 8 additions & 0 deletions src/containers/Tenant/utils/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ const pathTypeToNodeType: Record<EPathType, NavigationTreeNodeType | undefined>
[EPathType.EPathTypeReplication]: 'async_replication',
};

export const nodeTableTypeToPathType: Partial<Record<NavigationTreeNodeType, EPathType>> = {
table: EPathType.EPathTypeTable,
index: EPathType.EPathTypeTableIndex,
column_table: EPathType.EPathTypeColumnTable,
external_table: EPathType.EPathTypeExternalTable,
view: EPathType.EPathTypeView,
};

export const mapPathTypeToNavigationTreeType = (
type: EPathType = EPathType.EPathTypeDir,
subType?: EPathSubType,
Expand Down
63 changes: 50 additions & 13 deletions src/containers/Tenant/utils/schemaActions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import copy from 'copy-to-clipboard';
import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components';

import type {AppDispatch} from '../../../store';
import {changeUserInput} from '../../../store/reducers/executeQuery';
import type {GetTableSchemaDataParams} from '../../../store/reducers/tableSchemaData';
import {TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants';
import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
import type {QueryMode, QuerySettings} from '../../../types/store/query';
import createToast from '../../../utils/createToast';
import {transformPath} from '../ObjectSummary/transformPath';
import type {SchemaData} from '../Schema/SchemaViewer/types';
import i18n from '../i18n';

import type {SchemaQueryParams} from './schemaQueryTemplates';
import {nodeTableTypeToPathType} from './schema';
import type {TemplateFn} from './schemaQueryTemplates';
import {
addTableIndex,
alterAsyncReplicationTemplate,
Expand All @@ -34,31 +38,60 @@ interface ActionsAdditionalEffects {
updateQueryExecutionSettings: (settings?: Partial<QuerySettings>) => void;
setActivePath: (path: string) => void;
showCreateDirectoryDialog?: (path: string) => void;
getTableSchemaDataPromise?: (
params: GetTableSchemaDataParams,
) => Promise<SchemaData[] | undefined>;
}

interface BindActionParams {
tenantName: string;
type: NavigationTreeNodeType;
path: string;
relativePath: string;
}

const bindActions = (
schemaQueryParams: SchemaQueryParams,
dispatch: React.Dispatch<any>,
params: BindActionParams,
dispatch: AppDispatch,
additionalEffects: ActionsAdditionalEffects,
) => {
const {setActivePath, updateQueryExecutionSettings, showCreateDirectoryDialog} =
additionalEffects;

const inputQuery = (tmpl: (params?: SchemaQueryParams) => string, mode?: QueryMode) => () => {
const {
setActivePath,
updateQueryExecutionSettings,
showCreateDirectoryDialog,
getTableSchemaDataPromise,
} = additionalEffects;

const inputQuery = (tmpl: TemplateFn, mode?: QueryMode) => () => {
if (mode) {
updateQueryExecutionSettings({queryMode: mode});
}

dispatch(changeUserInput({input: tmpl(schemaQueryParams)}));
const pathType = nodeTableTypeToPathType[params.type];
const withTableData = [selectQueryTemplate, upsertQueryTemplate].includes(tmpl);

const userInputDataPromise =
withTableData && pathType && getTableSchemaDataPromise
? getTableSchemaDataPromise({
path: params.path,
tenantName: params.tenantName,
type: pathType,
})
: Promise.resolve(undefined);

userInputDataPromise.then((tableData) => {
dispatch(changeUserInput({input: tmpl({...params, tableData})}));
});

dispatch(setTenantPage(TENANT_PAGES_IDS.query));
dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
setActivePath(schemaQueryParams.path);
setActivePath(params.path);
};

return {
createDirectory: showCreateDirectoryDialog
? () => {
showCreateDirectoryDialog(schemaQueryParams.path);
showCreateDirectoryDialog(params.path);
}
: undefined,
createTable: inputQuery(createTableTemplate, 'script'),
Expand All @@ -81,7 +114,7 @@ const bindActions = (
addTableIndex: inputQuery(addTableIndex, 'script'),
copyPath: () => {
try {
copy(schemaQueryParams.relativePath);
copy(params.relativePath);
createToast({
name: 'Copied',
title: i18n('actions.copied'),
Expand All @@ -101,10 +134,14 @@ const bindActions = (
type ActionsSet = ReturnType<Required<NavigationTreeProps>['getActions']>;

export const getActions =
(dispatch: React.Dispatch<any>, additionalEffects: ActionsAdditionalEffects, rootPath = '') =>
(dispatch: AppDispatch, additionalEffects: ActionsAdditionalEffects, rootPath = '') =>
(path: string, type: NavigationTreeNodeType) => {
const relativePath = transformPath(path, rootPath);
const actions = bindActions({path, relativePath}, dispatch, additionalEffects);
const actions = bindActions(
{path, relativePath, tenantName: rootPath, type},
dispatch,
additionalEffects,
);
const copyItem = {text: i18n('actions.copyPath'), action: actions.copyPath};

const DIR_SET: ActionsSet = [
Expand Down
14 changes: 12 additions & 2 deletions src/containers/Tenant/utils/schemaQueryTemplates.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type {SchemaData} from '../Schema/SchemaViewer/types';

export interface SchemaQueryParams {
path: string;
relativePath: string;
tableData?: SchemaData[];
}

export type TemplateFn = (params?: SchemaQueryParams) => string;

export const createTableTemplate = (params?: SchemaQueryParams) => {
return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/create_table
CREATE TABLE \`${params?.relativePath || '$path'}/ydb_row_table\` (
Expand Down Expand Up @@ -67,13 +72,18 @@ export const alterTableTemplate = (params?: SchemaQueryParams) => {
ADD COLUMN numeric_column Int32;`;
};
export const selectQueryTemplate = (params?: SchemaQueryParams) => {
return `SELECT *
const columns = params?.tableData?.map((column) => '`' + column.name + '`').join(', ') || '*';

return `SELECT ${columns}
FROM \`${params?.relativePath || '$path'}\`
LIMIT 10;`;
};
export const upsertQueryTemplate = (params?: SchemaQueryParams) => {
const columns =
params?.tableData?.map((column) => `\`${column.name}\``).join(', ') || `\`id\`, \`name\``;

return `UPSERT INTO \`${params?.relativePath || '$path'}\`
( \`id\`, \`name\` )
( ${columns} )
VALUES ( );`;
};

Expand Down
4 changes: 2 additions & 2 deletions src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
);
}
getDescribe(
{path, database}: {path: string; database: string},
{path, database, timeout}: {path: string; database: string; timeout?: Timeout},
{concurrentId, signal}: AxiosOptions = {},
) {
return this.get<Nullable<TEvDescribeSchemeResult>>(
Expand All @@ -386,7 +386,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
partition_stats: true,
subs: 0,
},
{concurrentId: concurrentId || `getDescribe|${path}`, requestConfig: {signal}},
{concurrentId: concurrentId || `getDescribe|${path}`, requestConfig: {signal}, timeout},
);
}
getSchemaAcl(
Expand Down
14 changes: 12 additions & 2 deletions src/store/reducers/overview/overview.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import type {Timeout} from '../../../types/api/query';
import {api} from '../api';

export const overviewApi = api.injectEndpoints({
endpoints: (build) => ({
getOverview: build.query({
queryFn: async ({paths, database}: {paths: string[]; database: string}, {signal}) => {
queryFn: async (
{
paths,
database,
timeout,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should exclude timeout (and probably concurrentId) in serializeQueryArgs, otherwise your getTableSchemaData will always initiate a new request and will never reuse data from other request to overviewApi

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In current versions there is always a new request, even if entity overview have already been loaded

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed timeout and concurrentId
now queries use same api and timeout moved to front with Promise.race and wait

concurrentId,
}: {paths: string[]; database: string; timeout?: Timeout; concurrentId?: string},
{signal},
) => {
try {
const [data, ...additionalData] = await Promise.all(
paths.map((p) =>
window.api.getDescribe(
{
path: p,
database,
timeout,
},
{signal},
{signal, concurrentId},
),
),
);
Expand Down
67 changes: 67 additions & 0 deletions src/store/reducers/tableSchemaData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
prepareSchemaData,
prepareViewSchema,
} from '../../containers/Tenant/Schema/SchemaViewer/prepareData';
import type {SchemaData} from '../../containers/Tenant/Schema/SchemaViewer/types';
import {isViewType} from '../../containers/Tenant/utils/schema';
import type {EPathType} from '../../types/api/schema';
import {isQueryErrorResponse} from '../../utils/query';

import {api} from './api';
import {overviewApi} from './overview/overview';
import {viewSchemaApi} from './viewSchema/viewSchema';

export interface GetTableSchemaDataParams {
path: string;
tenantName: string;
type: EPathType;
}

const TABLE_SCHEMA_TIMEOUT = 1000;

const getTableSchemaDataConcurrentId = 'getTableSchemaData';

export const tableSchemeDataApi = api.injectEndpoints({
endpoints: (build) => ({
getTableSchemaData: build.mutation<SchemaData[], GetTableSchemaDataParams>({
queryFn: async ({path, tenantName, type}, {dispatch}) => {
try {
const schemaData = await dispatch(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need this request for view? Probably, you should check if it's a table here

if (isViewType(type)){
  // do request for a view
} else {
  // do request for a table
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

overviewApi.endpoints.getOverview.initiate({
paths: [path],
database: tenantName,
timeout: TABLE_SCHEMA_TIMEOUT,
concurrentId: getTableSchemaDataConcurrentId + 'getOverview',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need concurrentId here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

}),
);

if (isViewType(type)) {
const response = await dispatch(
viewSchemaApi.endpoints.getViewSchema.initiate({
database: tenantName,
path,
timeout: TABLE_SCHEMA_TIMEOUT,
concurrentId: getTableSchemaDataConcurrentId + 'getViewSchema',
}),
);

if (isQueryErrorResponse(response)) {
return {error: response};
}

const result = prepareViewSchema(response.data);
return {data: result};
}

const result = prepareSchemaData(type, schemaData.data?.data);

return {data: result};
} catch (error) {
return {error};
}
},
}),
}),
});

export const {useGetTableSchemaDataMutation} = tableSchemeDataApi;
18 changes: 15 additions & 3 deletions src/store/reducers/viewSchema/viewSchema.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import type {Timeout} from '../../../types/api/query';
import {isQueryErrorResponse} from '../../../utils/query';
import {api} from '../api';

function createViewSchemaQuery(path: string) {
export function createViewSchemaQuery(path: string) {
return `SELECT * FROM \`${path}\` LIMIT 0`;
}

export const viewSchemaApi = api.injectEndpoints({
endpoints: (build) => ({
getViewSchema: build.query({
queryFn: async ({database, path}: {database: string; path: string}) => {
queryFn: async ({
database,
path,
timeout,
concurrentId,
}: {
database: string;
path: string;
timeout?: Timeout;
concurrentId?: string;
}) => {
try {
const response = await window.api.sendQuery(
{
query: createViewSchemaQuery(path),
database,
action: 'execute-scan',
timeout,
},
{withRetries: true},
{withRetries: true, concurrentId},
);

if (isQueryErrorResponse(response)) {
Expand Down
Loading