Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('getChangedQueryExecutionSettings', () => {
limitRows: DEFAULT_QUERY_SETTINGS.limitRows,
statisticsMode: STATISTICS_MODES.basic,
tracingLevel: TRACING_LEVELS.basic,
pragmas: 'PRAGMA TestPragma;',
};
const result = getChangedQueryExecutionSettings(currentSettings, DEFAULT_QUERY_SETTINGS);
expect(result).toEqual([
Expand All @@ -43,6 +44,7 @@ describe('getChangedQueryExecutionSettings', () => {
'timeout',
'statisticsMode',
'tracingLevel',
'pragmas',
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('getChangedQueryExecutionSettingsDescription', () => {
limitRows: DEFAULT_QUERY_SETTINGS.limitRows,
statisticsMode: STATISTICS_MODES.profile,
tracingLevel: TRACING_LEVELS.diagnostic,
pragmas: 'PRAGMA TestPragma;',
};

const result = getChangedQueryExecutionSettingsDescription({
Expand All @@ -71,6 +72,7 @@ describe('getChangedQueryExecutionSettingsDescription', () => {
[QUERY_SETTINGS_FIELD_SETTINGS.timeout.title]: '120',
[QUERY_SETTINGS_FIELD_SETTINGS.statisticsMode.title]: STATISTICS_MODES_TITLES.profile,
[QUERY_SETTINGS_FIELD_SETTINGS.tracingLevel.title]: TRACING_LEVELS_TITLES.diagnostic,
[QUERY_SETTINGS_FIELD_SETTINGS.pragmas.title]: 'PRAGMA TestPragma;',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
margin-right: var(--g-spacing-2);
}

&__pragmas {
width: 100%;
}

&__postfix {
margin-right: var(--g-spacing-2);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import {Button, Dialog, Flex, TextInput, Tooltip} from '@gravity-ui/uikit';
import {Button, Dialog, Flex, TextArea, TextInput, Tooltip} from '@gravity-ui/uikit';
import {zodResolver} from '@hookform/resolvers/zod';
import {Controller, useForm} from 'react-hook-form';

Expand Down Expand Up @@ -220,6 +220,29 @@ function QuerySettingsForm({initialValues, onSubmit, onClose}: QuerySettingsForm
/>
</div>
</Flex>
<Flex direction="row" alignItems="flex-start" className={b('dialog-row')}>
<label htmlFor="pragmas" className={b('field-title')}>
{QUERY_SETTINGS_FIELD_SETTINGS.pragmas.title}
</label>
<div className={b('control-wrapper')}>
<Controller
name="pragmas"
control={control}
render={({field}) => (
<TextArea
id="pragmas"
{...field}
className={b('pragmas')}
placeholder="PRAGMA OrderedColumns;"
rows={3}
validationState={errors.pragmas ? 'invalid' : undefined}
errorMessage={errors.pragmas?.message}
errorPlacement="inside"
/>
)}
/>
</div>
</Flex>
<Flex direction="row" alignItems="flex-start" className={b('dialog-row')}>
<Controller
name="timeout"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,7 @@ export const QUERY_SETTINGS_FIELD_SETTINGS = {
limitRows: {
title: formI18n('form.limit-rows'),
},
pragmas: {
title: formI18n('form.pragmas'),
},
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"form.statistics-mode": "Statistics collection mode",
"form.tracing-level": "Tracing level",
"form.limit-rows": "Limit rows",
"form.pragmas": "Pragmas",
"button-done": "Save",
"tooltip_plan-to-svg-statistics": "Statistics option is set to \"Full\" due to the enabled \"Execution plan\" experiment.\n To disable it, go to the \"Experiments\" section in the user settings.",
"button-cancel": "Cancel",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"form.statistics-mode": "Режим сбора статистики",
"form.tracing-level": "Tracing level",
"form.limit-rows": "Лимит строк",
"form.pragmas": "Прагмы",
"tooltip_plan-to-svg-statistics": "Опция статистики установлена в значение \"Full\" из-за включенного эксперимента \"Execution plan\".\n Чтобы отключить его, перейдите в раздел \"Experiments\" в настройках пользователя.",
"button-done": "Готово",
"button-cancel": "Отменить",
Expand Down
69 changes: 69 additions & 0 deletions src/store/reducers/query/__test__/pragmasIntegration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Integration test for pragmas functionality
describe('Pragmas Integration Tests', () => {
test('should correctly prepend pragmas to query for execution', () => {
// Test the actual function logic that would be used in query execution
const testQuery = 'SELECT * FROM users WHERE id = 1;';
const testPragmas = 'PRAGMA OrderedColumns;';

// This is the logic from our prepareQueryWithPragmas function
const prepareQuery = (query: string, pragmas?: string): string => {
if (!pragmas || !pragmas.trim()) {
return query;
}

const trimmedPragmas = pragmas.trim();
const separator = trimmedPragmas.endsWith(';') ? '\n\n' : ';\n\n';

return `${trimmedPragmas}${separator}${query}`;
};

const result = prepareQuery(testQuery, testPragmas);

expect(result).toBe('PRAGMA OrderedColumns;\n\nSELECT * FROM users WHERE id = 1;');
});

test('should handle multiple pragmas correctly', () => {
const query = 'SELECT COUNT(*) FROM orders;';
const pragmas = `PRAGMA OrderedColumns;
PRAGMA AnsiOptionalAS;
PRAGMA DisableAnsiRankForNullableKeys;`;

const prepareQuery = (query: string, pragmas?: string): string => {
if (!pragmas || !pragmas.trim()) {
return query;
}

const trimmedPragmas = pragmas.trim();
const separator = trimmedPragmas.endsWith(';') ? '\n\n' : ';\n\n';

return `${trimmedPragmas}${separator}${query}`;
};

const result = prepareQuery(query, pragmas);

expect(result).toContain('PRAGMA OrderedColumns;');
expect(result).toContain('PRAGMA AnsiOptionalAS;');
expect(result).toContain('PRAGMA DisableAnsiRankForNullableKeys;');
expect(result).toContain('SELECT COUNT(*) FROM orders;');
});

test('should preserve query when pragmas are empty', () => {
const query = 'EXPLAIN SELECT * FROM table;';
const pragmas = '';

const prepareQuery = (query: string, pragmas?: string): string => {
if (!pragmas || !pragmas.trim()) {
return query;
}

const trimmedPragmas = pragmas.trim();
const separator = trimmedPragmas.endsWith(';') ? '\n\n' : ';\n\n';

return `${trimmedPragmas}${separator}${query}`;
};

const result = prepareQuery(query, pragmas);

expect(result).toBe(query);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {prepareQueryWithPragmas} from '../utils';

describe('prepareQueryWithPragmas', () => {
test('Should prepend pragmas correctly', () => {
// This tests the behavior through the actual query API
const pragma = 'PRAGMA OrderedColumns;';
const query = 'SELECT * FROM table;';
const expectedResult = prepareQueryWithPragmas(query, pragma);

// The actual test would be integration test with the query API
expect(expectedResult).toBe('PRAGMA OrderedColumns;\n\nSELECT * FROM table;');
});

test('Should handle empty pragmas', () => {
const query = 'SELECT * FROM table;';
const pragma = '';
const expectedResult = prepareQueryWithPragmas(query, pragma);

// When pragma is empty, query should remain unchanged
expect(expectedResult).toBe('SELECT * FROM table;');
});

test('Should handle pragmas without semicolon', () => {
const pragma = 'PRAGMA OrderedColumns';
const query = 'SELECT * FROM table;';
const expectedResult = prepareQueryWithPragmas(query, pragma);

expect(expectedResult).toBe('PRAGMA OrderedColumns;\n\nSELECT * FROM table;');
});
});
10 changes: 7 additions & 3 deletions src/store/reducers/query/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
setStreamSession as setStreamSessionReducer,
} from './streamingReducers';
import type {QueryResult, QueryState} from './types';
import {getActionAndSyntaxFromQueryMode, getQueryInHistory} from './utils';
import {getActionAndSyntaxFromQueryMode, getQueryInHistory, prepareQueryWithPragmas} from './utils';

const MAXIMUM_QUERIES_IN_HISTORY = 20;

Expand Down Expand Up @@ -231,6 +231,8 @@ export const queryApi = api.injectEndpoints({
querySettings?.queryMode,
);

const finalQuery = prepareQueryWithPragmas(query, querySettings.pragmas);

try {
let streamDataChunkBatch: StreamDataChunk[] = [];
let batchTimeout: number | null = null;
Expand All @@ -245,7 +247,7 @@ export const queryApi = api.injectEndpoints({

await window.api.streaming.streamQuery(
{
query,
query: finalQuery,
database,
action,
syntax,
Expand Down Expand Up @@ -342,11 +344,13 @@ export const queryApi = api.injectEndpoints({
querySettings?.queryMode,
);

const finalQuery = prepareQueryWithPragmas(query, querySettings.pragmas);

try {
const timeStart = Date.now();
const response = await window.api.viewer.sendQuery(
{
query,
query: finalQuery,
database,
action,
syntax,
Expand Down
12 changes: 12 additions & 0 deletions src/store/reducers/query/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,15 @@ export function isQueryResponseChunk(content: StreamingChunk): content is QueryR
export function isKeepAliveChunk(content: StreamingChunk): content is SessionChunk {
return content?.meta?.event === 'KeepAlive';
}

export const prepareQueryWithPragmas = (query: string, pragmas?: string): string => {
if (!pragmas || !pragmas.trim()) {
return query;
}

// Add pragmas at the beginning with proper line separation
const trimmedPragmas = pragmas.trim();
const separator = trimmedPragmas.endsWith(';') ? '\n\n' : ';\n\n';

return `${trimmedPragmas}${separator}${query}`;
};
5 changes: 5 additions & 0 deletions src/utils/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,16 @@ export const parseQueryErrorToString = (error: unknown) => {
return parsedError?.error?.message;
};

export const defaultPragma = 'PRAGMA OrderedColumns;';

export const DEFAULT_QUERY_SETTINGS = {
queryMode: QUERY_MODES.query,
transactionMode: TRANSACTION_MODES.implicit,
timeout: null,
limitRows: 10000,
statisticsMode: STATISTICS_MODES.none,
tracingLevel: TRACING_LEVELS.off,
pragmas: defaultPragma,
};

export const queryModeSchema = z.nativeEnum(QUERY_MODES);
Expand All @@ -327,6 +330,7 @@ export const querySettingsValidationSchema = z.object({
transactionMode: transactionModeSchema,
statisticsMode: statisticsModeSchema,
tracingLevel: tracingLevelSchema,
pragmas: z.string(),
});

export const querySettingsRestoreSchema = z
Expand All @@ -343,5 +347,6 @@ export const querySettingsRestoreSchema = z
transactionMode: transactionModeSchema.catch(DEFAULT_QUERY_SETTINGS.transactionMode),
statisticsMode: statisticsModeSchema.catch(DEFAULT_QUERY_SETTINGS.statisticsMode),
tracingLevel: tracingLevelSchema.catch(DEFAULT_QUERY_SETTINGS.tracingLevel),
pragmas: z.string().catch(DEFAULT_QUERY_SETTINGS.pragmas),
})
.catch(DEFAULT_QUERY_SETTINGS);
5 changes: 4 additions & 1 deletion tests/suites/tenant/diagnostics/tabs/queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {expect, test} from '@playwright/test';

import {prepareQueryWithPragmas} from '../../../../../src/store/reducers/query/utils';
import {defaultPragma} from '../../../../../src/utils/query';
import {tenantName} from '../../../../utils/constants';
import {NavigationTabs, TenantPage} from '../../TenantPage';
import {longRunningQuery, longRunningStreamQuery} from '../../constants';
Expand Down Expand Up @@ -51,8 +53,9 @@ test.describe('Diagnostics Queries tab', async () => {
const diagnostics = new Diagnostics(page);
await diagnostics.clickTab(DiagnosticsTab.Queries);
await diagnostics.clickRadioSwitch(QueriesSwitch.Running);
const finalQueryText = prepareQueryWithPragmas(longRunningQuery, defaultPragma);
expect(
await diagnostics.table.waitForCellValueByHeader(1, 'Query text', longRunningQuery),
await diagnostics.table.waitForCellValueByHeader(1, 'Query text', finalQueryText),
).toBe(true);
});

Expand Down
Loading