Skip to content

Feauture/query nested fields by dot #3470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
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
31 changes: 31 additions & 0 deletions docs/diagnostics/QueryNestedFieldsByDot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Разыменование ссылочных полей запроса через точку (QueryNestedFieldsByDot)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Описание диагностики

Диагностика позволяет контролировать разыменование ссылочных полей через точку в тексте запроса 1С.
Задача данной диагностики - предотвратить излишние неявные соединения между таблицами
и как следствие - повысить производительность исполнения запроса к БД.

## Примеры
1. Базовое разыменование ссылочных полей в выборке (во временную таблицу или в результат запроса)
`ЗаказКлиентаТовары.Ссылка.Организация КАК Организация`
2. Разыменование ссылочных полей в соединениях таблиц
`ВТ_РасчетыСКлиентами КАК ВТ_РасчетыСКлиентами
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеЗаказовКлиента КАК ВТ_ДанныеЗаказовКлиента
ПО ВТ_РасчетыСКлиентами.АналитикаУчетаПоПартнерам.Партнер = ВТ_ДанныеЗаказовКлиента.Партнер`
3. Разыменование ссылочных полей в виртуальных таблицах
`РегистрНакопления.РасчетыСКлиентами.Обороты(
&НачалоПериода,
&КонецПериода,
,
(АналитикаУчетаПоПартнерам.Партнер) В ...`
4. Конструкция "ВЫРАЗИТЬ" с разыменованием получаемого поля
`ВЫРАЗИТЬ(ВТ_ПланОтгрузок.ДокументПлан КАК Документ.ЗаказКлиента).Валюта.Наценка`
5. Разыменование ссылочных полей в секции "ГДЕ"
`ГДЕ азКлиентаТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода`

<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

## Источники
Источник: [Разыменование ссылочных полей составного типа в языке запросов] (https://its.1c.ru/db/v8std/content/654/hdoc)
27 changes: 27 additions & 0 deletions docs/en/diagnostics/QueryNestedFieldsByDot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Getting objects nested fields data by dot in database query text (QueryNestedFieldsByDot)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Description
Diagnostics allows you to control the dereference of reference fields through a dot in the 1C query language.
The purpose of this diagnostic is to prevent unnecessary implicit joins between tables.
and as a result, improve the performance of executing a database query.

## Examples
1. Base dereference through a dot (in temp. db or in select query)
`ЗаказКлиентаТовары.Ссылка.Организация КАК Организация`
2. Dereference of fields in table join section
`ВТ_РасчетыСКлиентами КАК ВТ_РасчетыСКлиентами
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеЗаказовКлиента КАК ВТ_ДанныеЗаказовКлиента
ПО ВТ_РасчетыСКлиентами.АналитикаУчетаПоПартнерам.Партнер = ВТ_ДанныеЗаказовКлиента.Партнер`
3. Dereference of fields in virtual tables
`РегистрНакопления.РасчетыСКлиентами.Обороты(
&НачалоПериода,
&КонецПериода,
,
(АналитикаУчетаПоПартнерам.Партнер) В ...`
4. Dereference in cast function result fields
`ВЫРАЗИТЬ(ВТ_ПланОтгрузок.ДокументПлан КАК Документ.ЗаказКлиента).Валюта.Наценка`
5. Dereference of fields in WHERE section
`ГДЕ азКлиентаТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода`
## Sources
Source: [Dereference of composite type reference fields in the query language (RU)] (https://its.1c.ru/db/v8std/content/654/hdoc)
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2025
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
import com.github._1c_syntax.bsl.parser.SDBLParser;

@DiagnosticMetadata(
type = DiagnosticType.CODE_SMELL,
severity = DiagnosticSeverity.INFO,
minutesToFix = 1,
tags = {
DiagnosticTag.SQL,
DiagnosticTag.PERFORMANCE,
DiagnosticTag.DESIGN
}

)
public class QueryNestedFieldsByDotDiagnostic extends AbstractSDBLListenerDiagnostic {

//Флаг обработки параметров виртуальной таблицы
public boolean isVirtualTable = false;

@Override
public void enterQuery(SDBLParser.QueryContext ctx) {
isVirtualTable = false; //Сбрасываем флаг при начале обработки запроса
super.enterQuery(ctx);
}

@Override
public void exitVirtualTableParameter(SDBLParser.VirtualTableParameterContext ctx) {
isVirtualTable = true; //Взводим флаг при начале обработки параметров виртуальной таблицы
super.exitVirtualTableParameter(ctx);
}

@Override
public void enterFunctionCall(SDBLParser.FunctionCallContext ctx) {
//Контролируем разыменование в функциях (ВЫРАЗИТЬ, ЕСТЬNULL и т.д.)
if(ctx.identifier != null && ctx.columnNames.size() > 1){
diagnosticStorage.addDiagnostic(ctx);
}
super.enterFunctionCall(ctx);
}
Comment on lines +59 to +65
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Отсутствует защита от NullPointerException.

В методе enterFunctionCall условие ctx.identifier != null && ctx.columnNames.size() > 1 может вызвать NullPointerException, если ctx.columnNames равно null. Рекомендуется добавить дополнительную проверку.

-    if(ctx.identifier != null && ctx.columnNames.size() > 1){
+    if(ctx.identifier != null && ctx.columnNames != null && ctx.columnNames.size() > 1){
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void enterFunctionCall(SDBLParser.FunctionCallContext ctx) {
//Контролируем разыменование в функциях (ВЫРАЗИТЬ, ЕСТЬNULL и т.д.)
if(ctx.identifier != null && ctx.columnNames.size() > 1){
diagnosticStorage.addDiagnostic(ctx);
}
super.enterFunctionCall(ctx);
}
public void enterFunctionCall(SDBLParser.FunctionCallContext ctx) {
//Контролируем разыменование в функциях (ВЫРАЗИТЬ, ЕСТЬNULL и т.д.)
if (ctx.identifier != null && ctx.columnNames != null && ctx.columnNames.size() > 1) {
diagnosticStorage.addDiagnostic(ctx);
}
super.enterFunctionCall(ctx);
}


@Override
public void enterColumn(SDBLParser.ColumnContext ctx) {
/*Если взведен флаг обработки виртуальной таблицы
и определен контекст метаданных, то проверяем заполненность контекста имен колонок.
В противном случае считаем что работаем со стандартным полем выборки или соединения
и выводим ошибку когда список имен колонки содержит более одного идентификатора
*/
if((isVirtualTable && ctx.mdoName != null && !ctx.columnNames.isEmpty()) || ctx.columnNames.size() > 1){

diagnosticStorage.addDiagnostic(ctx);
}
super.enterColumn(ctx);
}
Comment on lines +67 to +79
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Отсутствует защита от NullPointerException.

В методе enterColumn условие (isVirtualTable && ctx.mdoName != null && !ctx.columnNames.isEmpty()) || ctx.columnNames.size() > 1 также может вызвать NullPointerException, если ctx.columnNames равно null. Рекомендуется добавить дополнительную проверку.

-    if((isVirtualTable && ctx.mdoName != null && !ctx.columnNames.isEmpty()) || ctx.columnNames.size() > 1){
+    if((isVirtualTable && ctx.mdoName != null && ctx.columnNames != null && !ctx.columnNames.isEmpty()) || 
+       (ctx.columnNames != null && ctx.columnNames.size() > 1)){
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void enterColumn(SDBLParser.ColumnContext ctx) {
/*Если взведен флаг обработки виртуальной таблицы
и определен контекст метаданных, то проверяем заполненность контекста имен колонок.
В противном случае считаем что работаем со стандартным полем выборки или соединения
и выводим ошибку когда список имен колонки содержит более одного идентификатора
*/
if((isVirtualTable && ctx.mdoName != null && !ctx.columnNames.isEmpty()) || ctx.columnNames.size() > 1){
diagnosticStorage.addDiagnostic(ctx);
}
super.enterColumn(ctx);
}
@Override
public void enterColumn(SDBLParser.ColumnContext ctx) {
/*Если взведен флаг обработки виртуальной таблицы
и определен контекст метаданных, то проверяем заполненность контекста имен колонок.
В противном случае считаем что работаем со стандартным полем выборки или соединения
и выводим ошибку когда список имен колонки содержит более одного идентификатора
*/
if ((isVirtualTable
&& ctx.mdoName != null
&& ctx.columnNames != null
&& !ctx.columnNames.isEmpty())
|| (ctx.columnNames != null
&& ctx.columnNames.size() > 1)) {
diagnosticStorage.addDiagnostic(ctx);
}
super.enterColumn(ctx);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,16 @@
},
"$id": "#/definitions/PublicMethodsDescription"
},
"QueryNestedFieldsByDot": {
"description": "Getting objects nested fields data by dot in database query text",
"default": true,
"type": [
"boolean",
"object"
],
"title": "Getting objects nested fields data by dot in database query text",
"$id": "#/definitions/QueryNestedFieldsByDot"
},
"QueryParseError": {
"description": "Query text parsing error",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@
"PublicMethodsDescription": {
"$ref": "parameters-schema.json#/definitions/PublicMethodsDescription"
},
"QueryNestedFieldsByDot": {
"$ref": "parameters-schema.json#/definitions/QueryNestedFieldsByDot"
},
"QueryParseError": {
"$ref": "parameters-schema.json#/definitions/QueryParseError"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diagnosticMessage=Detected access to reference data nested field by dot in database query text
diagnosticName=Getting objects nested fields data by dot in database query text
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diagnosticMessage=Обнаружено разыменование ссылочного поля
diagnosticName=Разыменование ссылочных полей запроса через точку
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2025
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat;

class QueryNestedFieldsByDotDiagnosticTest extends AbstractDiagnosticTest<QueryNestedFieldsByDotDiagnostic> {
QueryNestedFieldsByDotDiagnosticTest() {
super(QueryNestedFieldsByDotDiagnostic.class);
}

@Test
void test() {

List<Diagnostic> diagnostics = getDiagnostics();

assertThat(diagnostics).hasSize(12);
assertThat(diagnostics, true)
.hasRange(21, 3, 21, 40) //Ошибка №1
.hasRange(22, 3, 22, 39) //Ошибка №1
.hasRange(23, 3, 23, 36) //Ошибка №1
.hasRange(24, 3, 24, 43) //Ошибка №1
.hasRange(29, 3, 29, 33) //Ошибка №7
.hasRange(53, 6, 53, 39) //Ошибка №3
.hasRange(53, 41, 53, 77) //Ошибка №3
.hasRange(53, 79, 53, 116) //Ошибка №3
.hasRange(101, 7, 101, 61) //Ошибка №2
.hasRange(102, 7, 102, 64) //Ошибка №2
.hasRange(103, 7, 103, 65) //Ошибка №2
.hasRange(115, 3, 115, 82); //Ошибка №6
}
}
Loading