Skip to content
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

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 <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> 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 <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> 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