Необъектная SQL-таблица: полный набор и настоящий составной ключ

Учебные задачи

Самостоятельный будущий опыт на своей новой VM, SQL базе, Windows readonly login, ODBC DSN и пустой файловой CF. Все фабрики настоящие; код только читает SQL, изменяя копию и черновик лишь в памяти.

Где и как запускать

Собственный Windows x64 толстый обычный клиент1С8.5.1.1343/новая файловая CF; native SQL вызовы здесь только authored, runtime0.

Подготовка

  1. Все действия ниже — будущая ручная подготовка отдельного собственного стенда. Здесь только авторство BSL/SQL и статические проверки: VM, SQL Server, ODBC, CF, права, соединения и 1С не создавались и не запускались. Не используйте рабочую базу или подключение предприятия.

  2. Создайте свою изолированную Windows VM GUIDE85SQLVM без рабочих баз и внешних маршрутов. SQL Server — новый default instance на localhost. Собственная база Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c и Windows login GUIDE85SQLVM\Guide85Read должны отсутствовать; существующие объекты никогда не заменяются.

  3. Создайте собственный обычный Windows аккаунт Guide85Read, не локального/SQL администратора. Администратор подготовки — другой собственный аккаунт. В SSMS именно своего нового localhost instance будущий администратор вручную выполняет полный SQL-подготовительный блок ниже один раз: два CREATE TABLE, собственные NEWID, три строки каждой таблицы, login/user и readonly права. Частично выполненный блок не откатывается автоматически; после ошибки не запускайте его заново и не удаляйте ничего без отдельного решения.

  4. Под Windows GUIDE85SQLVM\Guide85Read вручную выполните отдельный readonly SQL-блок. Нужны DB_NAME собственной базы, SYSTEM_USER своего login, SELECT=1 и INSERT/UPDATE/DELETE=0 у обеих таблиц, отсутствие sysadmin/db_owner/db_datawriter/db_ddladmin. Обе таблицы: COUNT3, SUM1500, A500/B750/C250. Сохраните действительный own_run_id из seed SELECT; не вводите UUID чужого источника. Здесь эти проверки не выполнялись.

  5. Нужен совместимый 64-bit ODBC драйвер SQL Server для x64 процесса 1С. В 64-bit ODBC Data Sources создайте новый System DSN Guide85ReadLocal: localhost, default instance, default database Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c, Windows authentication. Не меняйте существующий DSN и не отключайте общую TLS проверку. Сертификат и настройки выбранного драйвера проверяет человек внутри своей VM; имя DSN не доказывает реальный адрес, базу или права.

  6. Создайте новую пустую файловую базу строго в C:\1c-labs\OwnExternalReadonly15_2871b8c9-bf2c-44f7-92ff-6d32793e0e4c\IB, CF УчебноеЧтениеSQL85, встроенный язык Русский, РежимСовместимости=НеИспользовать, ОсновнойРежимЗапуска=ОбычноеПриложение. UUID каталога — метка стенда, не UUID информационной базы. Отдельно Сервис → Параметры → Общие → Редактирование конфигурации: Оба, чтобы видеть обычные клиентские флаги; это настройка редактора, не свойство SupportedRunModes.

  7. Создайте единственную собственную роль и пользователя Уч85ЧтениеSQL с Администрированием и полными правами только этой пустой CF. Native GetUsers и изменение параметров сеанса требуют административных прав 1С. Это не дает Windows login прав записи SQL. Запускайте только x64 толстый обычный клиент8.5.1.1343 из Windows Guide85Read; серверной службы, другой сессии и клиент-серверной ИБ нет.

  8. ВнешниеИсточникиДанных → УчебныйSQLЧтение85 → Данные → Таблицы → Добавить. В конструкторе выберите Вручную → Готово. У Лоты85: ВидТаблицы=Таблица, ИмяВИсточникеДанных=dbo.object_lots; Код=item_id Строка36, Метка=run_id Строка36, Название=title Строка40, Сумма=amount Число9.0. Все поля одиночного типа, конечная длина строк, Null запрещён, ТолькоЧтение=Нет. Родитель и версия не назначаются.

  9. В редакторе Лоты85 → ПоляКлюча → кнопка выбора перенесите ровно Код вправо. Одно ключевое поле дает тип ОбъектныеДанные; ПолеПредставления=Название. У Строки85: имя dbo.row_lots, Метка=run_id Строка36, Номер=line_no Число9.0, Название=title Строка40, Сумма=amount Число9.0. ПоляКлюча: Метка, затем Номер — два поля дают НеобъектныеДанные. Тип таблицы и порядок PK должны точно совпасть с SQL.

  10. У обеих таблиц ТолькоЧтение=Нет для допустимого memory-edit объекта, но внешняя запись запрещена собственному SQL login и отсутствует в BSL. Не путайте флаг CF с проверкой прав СУБД. Модули менеджера, объекта и набора оставьте пустыми, без подписок, обменов, функций, кубов, фоновых заданий и предопределенных изменений.

  11. Создайте два общих модуля УчебныеSQLСсылки85 и УчебныеSQLКлючи85: Клиент (обычное приложение)=Да, Клиент (управляемое приложение)=Нет, Сервер=Нет, ВызовСервера=Нет, ВнешнееСоединение=Нет, Глобальный=Нет, Привилегированный=Нет, ПовторноеИспользованиеВозвращаемыхЗначений=НеИспользовать. MAIN текущей карточки и supporting другой карточки — полные модули, вставляйте каждый один раз, не внутрь процедуры.

  12. Создайте обычную общую форму ПроверкаSQL85. Реквизиты: МеткаСтенда85=Строка36, ПодготовкаПодтверждена85=Булево, ВнешнийИтог85/ПредварительныйИтог85/ВыбраннаяСумма85=Число9.0, Статус85=Строка неограниченной длины. ПолеВвода МеткаСтенда85 связывается с одноименным реквизитом. Флажок ПодготовкаПодтверждена85 — с одноименным Булево. Четыре поля результата связаны с одноименными реквизитами, ТолькоПросмотр=Да; Статус85 многострочный.

  13. На своей форме добавьте Кнопка Ссылки85 и Кнопка Ключи85. Нажатие свяжите ровно с Ссылки85(Элемент) и Ключи85(Элемент); native Button.Click0 дополняется первым Элемент обычной формы по event104. ПриОткрытии — одноименная процедура без параметров. Вставьте полный supporting модуль формы и модуль обычного приложения; его ПриНачалеРаботыСистемы0 только открывает свою панель. Обработчики вручную не вызываются.

  14. После ручной проверки Windows/SQL/DSN/CF обновите только свою базу и запустите обычный толстый клиент. Панель открывается без SQL-соединения. Введите свой run_id, подтвердите флажком исключительно собственный readonly стенд и нажмите Ссылки или Ключи. Только это действие запустит будущий native Connect. Код устанавливает только параметры собственного сеанса; пароль не читается, не хранится и не выводится.

  15. Ссылки показывают внешний итог1500 и предварительный1600: копия собственного A меняется500→600 в памяти, B750/C250 не меняются. Отдельный новый черновикD200 не записан и не входит в прогноз1600. Ключи показывают исходный1500 и actualB750 по composite PK; D отсутствует и дает native пустой ключ без чтения несуществующей DB-строки.

  16. После ошибки sticky STOP не сбрасывается повторной инициализацией или другой кнопкой. Старые числа не объявляются успешным новым результатом. Сохраняется первичная диагностика; закрытие предпринимается не более одного раза. Если эта попытка также бросила ошибку, обе диагностики явно сообщаются и состояние своего соединения считается неизвестным. Проверьте только свое соединение/настройки вручную, не повторяйте автоматически. В этом уроке нет записи/удаления, транзакционного отката, runtime-компиляции или проверки реальной доставки GUI.

Процедура ПроверитьСтенд85() Экспорт
#Если ТолстыйКлиентОбычноеПриложение Тогда
    Если СтрокаСоединенияИнформационнойБазы() <> "File=""C:\1c-labs\OwnExternalReadonly15_2871b8c9-bf2c-44f7-92ff-6d32793e0e4c\IB"";" Тогда
        ВызватьИсключение "Нужна отдельная собственная файловая база учебного SQL-стенда";
    КонецЕсли;
    Если Метаданные.РежимСовместимости <> Метаданные.СвойстваОбъектов.РежимСовместимости.НеИспользовать Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 1 Или Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Роли.Количество() <> 1 Тогда
        ВызватьИсключение "Подготовьте пустую собственную CF с единственным учебным источником";
    КонецЕсли;
    Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
    Если Пользователь = Неопределено Тогда
        ВызватьИсключение "Войдите собственным явно созданным пользователем Уч85ЧтениеSQL";
    КонецЕсли;
    Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();
    Если Пользователь.Имя <> "Уч85ЧтениеSQL" Или Пользователи.Количество() <> 1 Тогда
        ВызватьИсключение "Разрешен только единственный собственный учебный пользователь";
    КонецЕсли;
    Для Каждого СвойПользователь Из Пользователи Цикл
        Если СвойПользователь.УникальныйИдентификатор <> Пользователь.УникальныйИдентификатор Тогда
            ВызватьИсключение "Не совпал собственный пользователь стенда";
        КонецЕсли;
    КонецЦикла;
    МетаИсточник = Метаданные.ВнешниеИсточникиДанных.УчебныйSQLЧтение85;
    Если МетаИсточник.Таблицы.Количество() <> 2 Или МетаИсточник.Кубы.Количество() <> 0
        Или МетаИсточник.Функции.Количество() <> 0 Тогда
        ВызватьИсключение "Нужны только две собственные таблицы без функций и кубов";
    КонецЕсли;
    ПроверитьМетаТаблицу85(МетаИсточник.Таблицы.Лоты85, Истина);
    ПроверитьМетаТаблицу85(МетаИсточник.Таблицы.Строки85, Ложь);
#Иначе
    ВызватьИсключение "Запустите собственный толстый обычный клиент, не сервер или тонкий клиент";
#КонецЕсли
КонецПроцедуры

Процедура ПроверитьUUID85(Значение)
    Если ТипЗнч(Значение) <> Тип("Строка") Или СтрДлина(Значение) <> 36
        Или Значение <> СокрЛП(Значение) Или Значение = "00000000-0000-0000-0000-000000000000" Тогда
        ВызватьИсключение "Скопируйте действительный собственный UUID из полного seed SELECT";
    КонецЕсли;
    Идентификатор = Новый УникальныйИдентификатор(Значение);
    Если НРег(Строка(Идентификатор)) <> НРег(Значение) Тогда
        ВызватьИсключение "Нужна каноническая строка собственного UUID";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьМетаТаблицу85(МетаТаблица, Объектные)
    Если Объектные Тогда
        ИмяТаблицы = "dbo.object_lots";
        ИмяКлюча = "Код";
        ИмяКлючаSQL = "item_id";
        ТипТаблицы = Метаданные.СвойстваОбъектов.ТипДанныхТаблицыВнешнегоИсточникаДанных.ОбъектныеДанные;
        ЧислоКлючей = 1;
    Иначе
        ИмяТаблицы = "dbo.row_lots";
        ИмяКлюча = "Номер";
        ИмяКлючаSQL = "line_no";
        ТипТаблицы = Метаданные.СвойстваОбъектов.ТипДанныхТаблицыВнешнегоИсточникаДанных.НеобъектныеДанные;
        ЧислоКлючей = 2;
    КонецЕсли;
    Если МетаТаблица.ИмяВИсточникеДанных <> ИмяТаблицы Или МетаТаблица.ТипДанныхТаблицы <> ТипТаблицы
        Или МетаТаблица.ТолькоЧтение Или МетаТаблица.Поля.Количество() <> 4
        Или МетаТаблица.ПоляКлюча.Количество() <> ЧислоКлючей Тогда
        ВызватьИсключение "Описание собственной таблицы не совпало с подготовленным SQL";
    КонецЕсли;
    Имена = Новый Массив;
    Для Каждого Поле Из МетаТаблица.Поля Цикл
        Если Поле.ТолькоЧтение Или Поле.РазрешитьNull Или Поле.Тип.Типы().Количество() <> 1 Тогда
            ВызватьИсключение "Учебные поля имеют один явный тип, Null запрещен";
        КонецЕсли;
        Если Поле.Имя = ИмяКлюча Тогда
            ИмяSQL = ИмяКлючаSQL;
            Длина = 36;
            Числовое = Не Объектные;
        ИначеЕсли Поле.Имя = "Метка" Тогда
            ИмяSQL = "run_id";
            Длина = 36;
            Числовое = Ложь;
        ИначеЕсли Поле.Имя = "Название" Тогда
            ИмяSQL = "title";
            Длина = 40;
            Числовое = Ложь;
        ИначеЕсли Поле.Имя = "Сумма" Тогда
            ИмяSQL = "amount";
            Длина = 0;
            Числовое = Истина;
        Иначе
            ВызватьИсключение "Обнаружено неизвестное поле учебной таблицы";
        КонецЕсли;
        Если Поле.ИмяВИсточникеДанных <> ИмяSQL Или Имена.Найти(Поле.Имя) <> Неопределено Тогда
            ВызватьИсключение "Не совпало SQL имя или поле повторилось";
        КонецЕсли;
        Если Числовое Тогда
            Если Не Поле.Тип.СодержитТип(Тип("Число")) Или Поле.Тип.КвалификаторыЧисла.Разрядность <> 9
                Или Поле.Тип.КвалификаторыЧисла.РазрядностьДробнойЧасти <> 0 Тогда
                ВызватьИсключение "Нужен одиночный числовой тип9.0";
            КонецЕсли;
        Иначе
            Если Не Поле.Тип.СодержитТип(Тип("Строка")) Или Поле.Тип.КвалификаторыСтроки.Длина <> Длина Тогда
                ВызватьИсключение "Не совпал одиночный строковый тип и его конечная длина";
            КонецЕсли;
        КонецЕсли;
        Имена.Добавить(Поле.Имя);
    КонецЦикла;
    Позиция = 0;
    Для Каждого ПолеКлюча Из МетаТаблица.ПоляКлюча Цикл
        Если Объектные Тогда
            ОжидаемоеИмя = "Код";
        ИначеЕсли Позиция = 0 Тогда
            ОжидаемоеИмя = "Метка";
        Иначе
            ОжидаемоеИмя = "Номер";
        КонецЕсли;
        Если ТипЗнч(ПолеКлюча) <> Тип("Поле") Или ПолеКлюча.Имя <> ОжидаемоеИмя Тогда
            ВызватьИсключение "Не совпали действительные поля и порядок SQL PK";
        КонецЕсли;
        Позиция = Позиция + 1;
    КонецЦикла;
КонецПроцедуры

Функция НачатьСвоёЧтение85(Метка)
    ПроверитьСтенд85();
    ПроверитьUUID85(Метка);
    Источник = ВнешниеИсточникиДанных.УчебныйSQLЧтение85;
    Если ТипЗнч(Источник) <> Тип("ВнешнийИсточникДанныхМенеджер.УчебныйSQLЧтение85")
        Или Источник.ПолучитьСостояние() <> СостояниеВнешнегоИсточникаДанных.Отключен Тогда
        ВызватьИсключение "Нужен свой источник без уже существующего соединения";
    КонецЕсли;
    Параметры = Источник.ПолучитьПараметрыСоединенияСеанса();
    Если Параметры.СтрокаСоединения <> Неопределено И Параметры.СтрокаСоединения <> ""
        И Параметры.СтрокаСоединения <> "DSN=Guide85ReadLocal;" Тогда
        ВызватьИсключение "Не разрешено заменять неизвестные параметры чужого DSN";
    КонецЕсли;
    Параметры.СУБД = "MSSQLServer";
    Параметры.СтрокаСоединения = "DSN=Guide85ReadLocal;";
    Параметры.АутентификацияОС = Истина;
    Параметры.АутентификацияСтандартная = Ложь;
    Источник.УстановитьПараметрыСоединенияСеанса(Параметры);
    Фактические = Источник.ПолучитьПараметрыСоединенияСеанса();
    Если Фактические.СУБД <> "MSSQLServer" Или Фактические.СтрокаСоединения <> "DSN=Guide85ReadLocal;"
        Или Фактические.АутентификацияОС <> Истина Или Фактические.АутентификацияСтандартная <> Ложь Тогда
        ВызватьИсключение "Не подтверждены собственные параметры Windows-аутентификации";
    КонецЕсли;
    Возврат Источник;
КонецФункции

Функция СнятьСнимок85(Данные, Метка, Объектные)
    Если ТипЗнч(Данные) <> Тип("ТаблицаЗначений") Или Данные.Количество() <> 3 Или Данные.Колонки.Количество() <> 4 Тогда
        ВызватьИсключение "Нужен полный результат из трёх строк и четырёх полей";
    КонецЕсли;
    Для Каждого ИмяКолонки Из СтрРазделить("Ключ,Метка,Название,Сумма", ",") Цикл
        Если Данные.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
            ВызватьИсключение "Отсутствует поле полного результата";
        КонецЕсли;
    КонецЦикла;
    Строки = Новый Массив;
    Ключи = Новый Массив;
    Названия = Новый Массив;
    Итог = 0;
    Для Каждого Лот Из Данные Цикл
        Если ТипЗнч(Лот.Метка) <> Тип("Строка") Или Лот.Метка <> Метка
            Или ТипЗнч(Лот.Название) <> Тип("Строка") Или ТипЗнч(Лот.Сумма) <> Тип("Число")
            Или Ключи.Найти(Лот.Ключ) <> Неопределено Или Названия.Найти(Лот.Название) <> Неопределено Тогда
            ВызватьИсключение "Нужны только три собственные разные подготовленные строки";
        КонецЕсли;
        Если Объектные Тогда
            ПроверитьUUID85(Лот.Ключ);
        Иначе
            Если ТипЗнч(Лот.Ключ) <> Тип("Число") Тогда
                ВызватьИсключение "Номер необъектной записи должен оставаться числом";
            КонецЕсли;
        КонецЕсли;
        Если Лот.Название = "A" Тогда
            ОжидаемаяСумма = 500;
            ОжидаемыйНомер = 1;
        ИначеЕсли Лот.Название = "B" Тогда
            ОжидаемаяСумма = 750;
            ОжидаемыйНомер = 2;
        ИначеЕсли Лот.Название = "C" Тогда
            ОжидаемаяСумма = 250;
            ОжидаемыйНомер = 3;
        Иначе
            ВызватьИсключение "Обнаружена строка вне собственного seed A/B/C";
        КонецЕсли;
        Если Лот.Сумма <> ОжидаемаяСумма Или (Не Объектные И Лот.Ключ <> ОжидаемыйНомер) Тогда
            ВызватьИсключение "Исходная строка не совпала с собственным fixture";
        КонецЕсли;
        Ключи.Добавить(Лот.Ключ);
        Названия.Добавить(Лот.Название);
        Итог = Итог + Лот.Сумма;
        Строки.Добавить(Новый ФиксированнаяСтруктура("Ключ,Метка,Название,Сумма", Лот.Ключ, Лот.Метка, Лот.Название, Лот.Сумма));
    КонецЦикла;
    Если Итог <> 1500 Тогда
        ВызватьИсключение "Независимый итог полного источника должен быть1500";
    КонецЕсли;
    Возврат Новый ФиксированныйМассив(Строки);
КонецФункции

Функция ПрочитатьЛоты85(Метка, Объектные)
    ПроверитьСтенд85();
    Если Объектные Тогда
        ТекстЗапроса = "ВЫБРАТЬ Т.Код КАК Ключ, Т.Метка, Т.Название, Т.Сумма
        |ИЗ ВнешнийИсточникДанных.УчебныйSQLЧтение85.Таблица.Лоты85 КАК Т";
    Иначе
        ТекстЗапроса = "ВЫБРАТЬ Т.Номер КАК Ключ, Т.Метка, Т.Название, Т.Сумма
        |ИЗ ВнешнийИсточникДанных.УчебныйSQLЧтение85.Таблица.Строки85 КАК Т";
    КонецЕсли;
    Запрос = Новый Запрос(ТекстЗапроса);
    Данные = Запрос.Выполнить().Выгрузить();
    Возврат СнятьСнимок85(Данные, Метка, Объектные);
КонецФункции

Функция НайтиПоНазванию85(Снимок, Название)
    Найденная = Неопределено;
    Для Каждого СтрокаЛота Из Снимок Цикл
        Если СтрокаЛота.Название = Название Тогда
            Если Найденная <> Неопределено Тогда
                ВызватьИсключение "Название повторилось в собственном полном снимке";
            КонецЕсли;
            Найденная = СтрокаЛота;
        КонецЕсли;
    КонецЦикла;
    Возврат Найденная;
КонецФункции

Процедура СравнитьСнимки85(До, После)
    Если ТипЗнч(До) <> Тип("ФиксированныйМассив") Или ТипЗнч(После) <> Тип("ФиксированныйМассив")
        Или До.Количество() <> 3 Или После.Количество() <> До.Количество() Тогда
        ВызватьИсключение "Изменился тип или полный размер собственного источника";
    КонецЕсли;
    Для Каждого Исходная Из До Цикл
        Текущая = НайтиПоНазванию85(После, Исходная.Название);
        Если Текущая = Неопределено Тогда
            ВызватьИсключение "Исчезла собственная исходная строка";
        КонецЕсли;
        Если ТипЗнч(Текущая.Ключ) <> ТипЗнч(Исходная.Ключ) Или Текущая.Ключ <> Исходная.Ключ
            Или ТипЗнч(Текущая.Метка) <> ТипЗнч(Исходная.Метка) Или Текущая.Метка <> Исходная.Метка
            Или ТипЗнч(Текущая.Название) <> ТипЗнч(Исходная.Название) Или Текущая.Название <> Исходная.Название
            Или ТипЗнч(Текущая.Сумма) <> ТипЗнч(Исходная.Сумма) Или Текущая.Сумма <> Исходная.Сумма Тогда
            ВызватьИсключение "Полные типизированные поля источника изменились";
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Функция НайтиКлюч85(Менеджер, Снимок, Название)
    СтрокаЛота = НайтиПоНазванию85(Снимок, Название);
    Если СтрокаЛота = Неопределено Тогда
        Возврат Менеджер.ПустойКлюч();
    КонецЕсли;
    ПоляКлюча = Новый Структура("Метка,Номер", СтрокаЛота.Метка, СтрокаЛота.Ключ);
    Возврат Менеджер.СоздатьКлючЗаписи(ПоляКлюча);
КонецФункции

Функция ВыполнитьКлючи85(Метка) Экспорт
    Источник = НачатьСвоёЧтение85(Метка);
    ЗакрытиеНачато = Ложь;
    Попытка
        Источник.УстановитьСоединение();
        Если Источник.ПолучитьСостояние() <> СостояниеВнешнегоИсточникаДанных.Подключен Тогда
            ВызватьИсключение "Не установлено собственное readonly соединение";
        КонецЕсли;
        Менеджер = Источник.Таблицы.Строки85;
        Если ТипЗнч(Менеджер) <> Тип("ВнешнийИсточникДанныхТаблицаМенеджер.УчебныйSQLЧтение85.Строки85") Тогда
            ВызватьИсключение "Не получен менеджер своей необъектной таблицы";
        КонецЕсли;
        До = ПрочитатьЛоты85(Метка, Ложь);
        Набор = Менеджер.СоздатьНаборЗаписей();
        Если ТипЗнч(Набор) <> Тип("ВнешнийИсточникДанныхТаблицаНаборЗаписей.УчебныйSQLЧтение85.Строки85") Или Набор.Количество() <> 0 Тогда
            ВызватьИсключение "Фабрика должна дать собственный пустой native набор";
        КонецЕсли;
        Набор.Прочитать();
        Если Набор.Количество() <> 3 Тогда
            ВызватьИсключение "Набор обязан прочитать все три собственные строки";
        КонецЕсли;
        СуммаНабора = 0;
        ПрочитанныеНазвания = Новый Массив;
        Для Каждого Запись Из Набор Цикл
            Если ТипЗнч(Запись) <> Тип("ВнешнийИсточникДанныхТаблицаЗапись.УчебныйSQLЧтение85.Строки85") Тогда
                ВызватьИсключение "Обход должен дать настоящую запись собственного набора";
            КонецЕсли;
            Исходная = НайтиПоНазванию85(До, Запись.Название);
            Если Исходная = Неопределено Или ПрочитанныеНазвания.Найти(Запись.Название) <> Неопределено Тогда
                ВызватьИсключение "Набор содержит неизвестную или повторенную строку";
            КонецЕсли;
            Если ТипЗнч(Запись.Номер) <> ТипЗнч(Исходная.Ключ) Или Запись.Номер <> Исходная.Ключ
                Или ТипЗнч(Запись.Метка) <> ТипЗнч(Исходная.Метка) Или Запись.Метка <> Исходная.Метка
                Или ТипЗнч(Запись.Название) <> ТипЗнч(Исходная.Название) Или Запись.Название <> Исходная.Название
                Или ТипЗнч(Запись.Сумма) <> ТипЗнч(Исходная.Сумма) Или Запись.Сумма <> Исходная.Сумма Тогда
                ВызватьИсключение "Каждое actual поле набора должно совпасть с независимым полным запросом";
            КонецЕсли;
            ПрочитанныеНазвания.Добавить(Запись.Название);
            СуммаНабора = СуммаНабора + Запись.Сумма;
        КонецЦикла;
        Если СуммаНабора <> 1500 Тогда
            ВызватьИсключение "Независимый итог прочитанного набора должен дать1500";
        КонецЕсли;
        КлючB = НайтиКлюч85(Менеджер, До, "B");
        ИсходныйB = НайтиПоНазванию85(До, "B");
        Если ТипЗнч(КлючB) <> Тип("ВнешнийИсточникДанныхТаблицаКлючЗаписи.УчебныйSQLЧтение85.Строки85") Или КлючB.Пустой()
            Или ТипЗнч(КлючB.Метка) <> ТипЗнч(ИсходныйB.Метка) Или КлючB.Метка <> ИсходныйB.Метка
            Или ТипЗнч(КлючB.Номер) <> ТипЗнч(ИсходныйB.Ключ) Или КлючB.Номер <> ИсходныйB.Ключ Тогда
            ВызватьИсключение "Фактический composite PK не совпал с настоящим ключом собственного B";
        КонецЕсли;
        ВыбраннаяСумма = Неопределено;
        Для Каждого Запись Из Набор Цикл
            Если Запись.Метка = КлючB.Метка И Запись.Номер = КлючB.Номер Тогда
                Если ВыбраннаяСумма <> Неопределено Тогда
                    ВызватьИсключение "Ключ matched более одной своей строки";
                КонецЕсли;
                ВыбраннаяСумма = Запись.Сумма;
            КонецЕсли;
        КонецЦикла;
        Если ВыбраннаяСумма <> ИсходныйB.Сумма Или ВыбраннаяСумма <> 750 Тогда
            ВызватьИсключение "Сумма действительной выбранной строки B должна дать750";
        КонецЕсли;
        ОтсутствующийКлюч = НайтиКлюч85(Менеджер, До, "D");
        Если ТипЗнч(ОтсутствующийКлюч) <> ТипЗнч(КлючB) Или Не ОтсутствующийКлюч.Пустой() Тогда
            ВызватьИсключение "Отсутствие D должно дать настоящий пустой ключ без чтения несуществующей записи";
        КонецЕсли;
        После = ПрочитатьЛоты85(Метка, Ложь);
        СравнитьСнимки85(До, После);
        ЗакрытиеНачато = Истина;
        Источник.РазорватьСоединение();
        Если Источник.ПолучитьСостояние() <> СостояниеВнешнегоИсточникаДанных.Отключен Тогда
            ВызватьИсключение "Проверьте закрытие только собственного учебного соединения";
        КонецЕсли;
        Возврат Новый ФиксированнаяСтруктура("До,Прогноз,После,Выбранная", СуммаНабора, СуммаНабора, СуммаНабора, ВыбраннаяСумма);
    Исключение
        ПервичнаяОшибка = ОписаниеОшибки();
        ОшибкаЗакрытия = "";
        Если Не ЗакрытиеНачато Тогда
            ЗакрытиеНачато = Истина;
            Попытка
                Источник.РазорватьСоединение();
            Исключение
                ОшибкаЗакрытия = ОписаниеОшибки();
            КонецПопытки;
        КонецЕсли;
        Если ОшибкаЗакрытия <> "" Тогда
            ВызватьИсключение "Первичная ошибка: " + ПервичнаяОшибка + Символы.ПС
                + "Отдельная ошибка закрытия: " + ОшибкаЗакрытия + Символы.ПС
                + "Состояние своего соединения неизвестно. STOP, без автоматического повтора.";
        КонецЕсли;
        ВызватьИсключение ПервичнаяОшибка;
    КонецПопытки;
КонецФункции
Второй полный общий модуль этого же учебного стенда

Своя CF → Общие → ОбщиеМодули → УчебныеSQLСсылки85 → Модуль целиком

Клиент (обычное приложение)Да/СерверНет/ВызовСервераНет, та же собственная CF.

Процедура ПроверитьСтенд85() Экспорт
#Если ТолстыйКлиентОбычноеПриложение Тогда
    Если СтрокаСоединенияИнформационнойБазы() <> "File=""C:\1c-labs\OwnExternalReadonly15_2871b8c9-bf2c-44f7-92ff-6d32793e0e4c\IB"";" Тогда
        ВызватьИсключение "Нужна отдельная собственная файловая база учебного SQL-стенда";
    КонецЕсли;
    Если Метаданные.РежимСовместимости <> Метаданные.СвойстваОбъектов.РежимСовместимости.НеИспользовать Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 1 Или Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Роли.Количество() <> 1 Тогда
        ВызватьИсключение "Подготовьте пустую собственную CF с единственным учебным источником";
    КонецЕсли;
    Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
    Если Пользователь = Неопределено Тогда
        ВызватьИсключение "Войдите собственным явно созданным пользователем Уч85ЧтениеSQL";
    КонецЕсли;
    Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();
    Если Пользователь.Имя <> "Уч85ЧтениеSQL" Или Пользователи.Количество() <> 1 Тогда
        ВызватьИсключение "Разрешен только единственный собственный учебный пользователь";
    КонецЕсли;
    Для Каждого СвойПользователь Из Пользователи Цикл
        Если СвойПользователь.УникальныйИдентификатор <> Пользователь.УникальныйИдентификатор Тогда
            ВызватьИсключение "Не совпал собственный пользователь стенда";
        КонецЕсли;
    КонецЦикла;
    МетаИсточник = Метаданные.ВнешниеИсточникиДанных.УчебныйSQLЧтение85;
    Если МетаИсточник.Таблицы.Количество() <> 2 Или МетаИсточник.Кубы.Количество() <> 0
        Или МетаИсточник.Функции.Количество() <> 0 Тогда
        ВызватьИсключение "Нужны только две собственные таблицы без функций и кубов";
    КонецЕсли;
    ПроверитьМетаТаблицу85(МетаИсточник.Таблицы.Лоты85, Истина);
    ПроверитьМетаТаблицу85(МетаИсточник.Таблицы.Строки85, Ложь);
#Иначе
    ВызватьИсключение "Запустите собственный толстый обычный клиент, не сервер или тонкий клиент";
#КонецЕсли
КонецПроцедуры

Процедура ПроверитьUUID85(Значение)
    Если ТипЗнч(Значение) <> Тип("Строка") Или СтрДлина(Значение) <> 36
        Или Значение <> СокрЛП(Значение) Или Значение = "00000000-0000-0000-0000-000000000000" Тогда
        ВызватьИсключение "Скопируйте действительный собственный UUID из полного seed SELECT";
    КонецЕсли;
    Идентификатор = Новый УникальныйИдентификатор(Значение);
    Если НРег(Строка(Идентификатор)) <> НРег(Значение) Тогда
        ВызватьИсключение "Нужна каноническая строка собственного UUID";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьМетаТаблицу85(МетаТаблица, Объектные)
    Если Объектные Тогда
        ИмяТаблицы = "dbo.object_lots";
        ИмяКлюча = "Код";
        ИмяКлючаSQL = "item_id";
        ТипТаблицы = Метаданные.СвойстваОбъектов.ТипДанныхТаблицыВнешнегоИсточникаДанных.ОбъектныеДанные;
        ЧислоКлючей = 1;
    Иначе
        ИмяТаблицы = "dbo.row_lots";
        ИмяКлюча = "Номер";
        ИмяКлючаSQL = "line_no";
        ТипТаблицы = Метаданные.СвойстваОбъектов.ТипДанныхТаблицыВнешнегоИсточникаДанных.НеобъектныеДанные;
        ЧислоКлючей = 2;
    КонецЕсли;
    Если МетаТаблица.ИмяВИсточникеДанных <> ИмяТаблицы Или МетаТаблица.ТипДанныхТаблицы <> ТипТаблицы
        Или МетаТаблица.ТолькоЧтение Или МетаТаблица.Поля.Количество() <> 4
        Или МетаТаблица.ПоляКлюча.Количество() <> ЧислоКлючей Тогда
        ВызватьИсключение "Описание собственной таблицы не совпало с подготовленным SQL";
    КонецЕсли;
    Имена = Новый Массив;
    Для Каждого Поле Из МетаТаблица.Поля Цикл
        Если Поле.ТолькоЧтение Или Поле.РазрешитьNull Или Поле.Тип.Типы().Количество() <> 1 Тогда
            ВызватьИсключение "Учебные поля имеют один явный тип, Null запрещен";
        КонецЕсли;
        Если Поле.Имя = ИмяКлюча Тогда
            ИмяSQL = ИмяКлючаSQL;
            Длина = 36;
            Числовое = Не Объектные;
        ИначеЕсли Поле.Имя = "Метка" Тогда
            ИмяSQL = "run_id";
            Длина = 36;
            Числовое = Ложь;
        ИначеЕсли Поле.Имя = "Название" Тогда
            ИмяSQL = "title";
            Длина = 40;
            Числовое = Ложь;
        ИначеЕсли Поле.Имя = "Сумма" Тогда
            ИмяSQL = "amount";
            Длина = 0;
            Числовое = Истина;
        Иначе
            ВызватьИсключение "Обнаружено неизвестное поле учебной таблицы";
        КонецЕсли;
        Если Поле.ИмяВИсточникеДанных <> ИмяSQL Или Имена.Найти(Поле.Имя) <> Неопределено Тогда
            ВызватьИсключение "Не совпало SQL имя или поле повторилось";
        КонецЕсли;
        Если Числовое Тогда
            Если Не Поле.Тип.СодержитТип(Тип("Число")) Или Поле.Тип.КвалификаторыЧисла.Разрядность <> 9
                Или Поле.Тип.КвалификаторыЧисла.РазрядностьДробнойЧасти <> 0 Тогда
                ВызватьИсключение "Нужен одиночный числовой тип9.0";
            КонецЕсли;
        Иначе
            Если Не Поле.Тип.СодержитТип(Тип("Строка")) Или Поле.Тип.КвалификаторыСтроки.Длина <> Длина Тогда
                ВызватьИсключение "Не совпал одиночный строковый тип и его конечная длина";
            КонецЕсли;
        КонецЕсли;
        Имена.Добавить(Поле.Имя);
    КонецЦикла;
    Позиция = 0;
    Для Каждого ПолеКлюча Из МетаТаблица.ПоляКлюча Цикл
        Если Объектные Тогда
            ОжидаемоеИмя = "Код";
        ИначеЕсли Позиция = 0 Тогда
            ОжидаемоеИмя = "Метка";
        Иначе
            ОжидаемоеИмя = "Номер";
        КонецЕсли;
        Если ТипЗнч(ПолеКлюча) <> Тип("Поле") Или ПолеКлюча.Имя <> ОжидаемоеИмя Тогда
            ВызватьИсключение "Не совпали действительные поля и порядок SQL PK";
        КонецЕсли;
        Позиция = Позиция + 1;
    КонецЦикла;
КонецПроцедуры

Функция НачатьСвоёЧтение85(Метка)
    ПроверитьСтенд85();
    ПроверитьUUID85(Метка);
    Источник = ВнешниеИсточникиДанных.УчебныйSQLЧтение85;
    Если ТипЗнч(Источник) <> Тип("ВнешнийИсточникДанныхМенеджер.УчебныйSQLЧтение85")
        Или Источник.ПолучитьСостояние() <> СостояниеВнешнегоИсточникаДанных.Отключен Тогда
        ВызватьИсключение "Нужен свой источник без уже существующего соединения";
    КонецЕсли;
    Параметры = Источник.ПолучитьПараметрыСоединенияСеанса();
    Если Параметры.СтрокаСоединения <> Неопределено И Параметры.СтрокаСоединения <> ""
        И Параметры.СтрокаСоединения <> "DSN=Guide85ReadLocal;" Тогда
        ВызватьИсключение "Не разрешено заменять неизвестные параметры чужого DSN";
    КонецЕсли;
    Параметры.СУБД = "MSSQLServer";
    Параметры.СтрокаСоединения = "DSN=Guide85ReadLocal;";
    Параметры.АутентификацияОС = Истина;
    Параметры.АутентификацияСтандартная = Ложь;
    Источник.УстановитьПараметрыСоединенияСеанса(Параметры);
    Фактические = Источник.ПолучитьПараметрыСоединенияСеанса();
    Если Фактические.СУБД <> "MSSQLServer" Или Фактические.СтрокаСоединения <> "DSN=Guide85ReadLocal;"
        Или Фактические.АутентификацияОС <> Истина Или Фактические.АутентификацияСтандартная <> Ложь Тогда
        ВызватьИсключение "Не подтверждены собственные параметры Windows-аутентификации";
    КонецЕсли;
    Возврат Источник;
КонецФункции

Функция СнятьСнимок85(Данные, Метка, Объектные)
    Если ТипЗнч(Данные) <> Тип("ТаблицаЗначений") Или Данные.Количество() <> 3 Или Данные.Колонки.Количество() <> 4 Тогда
        ВызватьИсключение "Нужен полный результат из трёх строк и четырёх полей";
    КонецЕсли;
    Для Каждого ИмяКолонки Из СтрРазделить("Ключ,Метка,Название,Сумма", ",") Цикл
        Если Данные.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
            ВызватьИсключение "Отсутствует поле полного результата";
        КонецЕсли;
    КонецЦикла;
    Строки = Новый Массив;
    Ключи = Новый Массив;
    Названия = Новый Массив;
    Итог = 0;
    Для Каждого Лот Из Данные Цикл
        Если ТипЗнч(Лот.Метка) <> Тип("Строка") Или Лот.Метка <> Метка
            Или ТипЗнч(Лот.Название) <> Тип("Строка") Или ТипЗнч(Лот.Сумма) <> Тип("Число")
            Или Ключи.Найти(Лот.Ключ) <> Неопределено Или Названия.Найти(Лот.Название) <> Неопределено Тогда
            ВызватьИсключение "Нужны только три собственные разные подготовленные строки";
        КонецЕсли;
        Если Объектные Тогда
            ПроверитьUUID85(Лот.Ключ);
        Иначе
            Если ТипЗнч(Лот.Ключ) <> Тип("Число") Тогда
                ВызватьИсключение "Номер необъектной записи должен оставаться числом";
            КонецЕсли;
        КонецЕсли;
        Если Лот.Название = "A" Тогда
            ОжидаемаяСумма = 500;
            ОжидаемыйНомер = 1;
        ИначеЕсли Лот.Название = "B" Тогда
            ОжидаемаяСумма = 750;
            ОжидаемыйНомер = 2;
        ИначеЕсли Лот.Название = "C" Тогда
            ОжидаемаяСумма = 250;
            ОжидаемыйНомер = 3;
        Иначе
            ВызватьИсключение "Обнаружена строка вне собственного seed A/B/C";
        КонецЕсли;
        Если Лот.Сумма <> ОжидаемаяСумма Или (Не Объектные И Лот.Ключ <> ОжидаемыйНомер) Тогда
            ВызватьИсключение "Исходная строка не совпала с собственным fixture";
        КонецЕсли;
        Ключи.Добавить(Лот.Ключ);
        Названия.Добавить(Лот.Название);
        Итог = Итог + Лот.Сумма;
        Строки.Добавить(Новый ФиксированнаяСтруктура("Ключ,Метка,Название,Сумма", Лот.Ключ, Лот.Метка, Лот.Название, Лот.Сумма));
    КонецЦикла;
    Если Итог <> 1500 Тогда
        ВызватьИсключение "Независимый итог полного источника должен быть1500";
    КонецЕсли;
    Возврат Новый ФиксированныйМассив(Строки);
КонецФункции

Функция ПрочитатьЛоты85(Метка, Объектные)
    ПроверитьСтенд85();
    Если Объектные Тогда
        ТекстЗапроса = "ВЫБРАТЬ Т.Код КАК Ключ, Т.Метка, Т.Название, Т.Сумма
        |ИЗ ВнешнийИсточникДанных.УчебныйSQLЧтение85.Таблица.Лоты85 КАК Т";
    Иначе
        ТекстЗапроса = "ВЫБРАТЬ Т.Номер КАК Ключ, Т.Метка, Т.Название, Т.Сумма
        |ИЗ ВнешнийИсточникДанных.УчебныйSQLЧтение85.Таблица.Строки85 КАК Т";
    КонецЕсли;
    Запрос = Новый Запрос(ТекстЗапроса);
    Данные = Запрос.Выполнить().Выгрузить();
    Возврат СнятьСнимок85(Данные, Метка, Объектные);
КонецФункции

Функция НайтиПоНазванию85(Снимок, Название)
    Найденная = Неопределено;
    Для Каждого СтрокаЛота Из Снимок Цикл
        Если СтрокаЛота.Название = Название Тогда
            Если Найденная <> Неопределено Тогда
                ВызватьИсключение "Название повторилось в собственном полном снимке";
            КонецЕсли;
            Найденная = СтрокаЛота;
        КонецЕсли;
    КонецЦикла;
    Возврат Найденная;
КонецФункции

Процедура СравнитьСнимки85(До, После)
    Если ТипЗнч(До) <> Тип("ФиксированныйМассив") Или ТипЗнч(После) <> Тип("ФиксированныйМассив")
        Или До.Количество() <> 3 Или После.Количество() <> До.Количество() Тогда
        ВызватьИсключение "Изменился тип или полный размер собственного источника";
    КонецЕсли;
    Для Каждого Исходная Из До Цикл
        Текущая = НайтиПоНазванию85(После, Исходная.Название);
        Если Текущая = Неопределено Тогда
            ВызватьИсключение "Исчезла собственная исходная строка";
        КонецЕсли;
        Если ТипЗнч(Текущая.Ключ) <> ТипЗнч(Исходная.Ключ) Или Текущая.Ключ <> Исходная.Ключ
            Или ТипЗнч(Текущая.Метка) <> ТипЗнч(Исходная.Метка) Или Текущая.Метка <> Исходная.Метка
            Или ТипЗнч(Текущая.Название) <> ТипЗнч(Исходная.Название) Или Текущая.Название <> Исходная.Название
            Или ТипЗнч(Текущая.Сумма) <> ТипЗнч(Исходная.Сумма) Или Текущая.Сумма <> Исходная.Сумма Тогда
            ВызватьИсключение "Полные типизированные поля источника изменились";
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Функция ВыполнитьСсылки85(Метка) Экспорт
    Источник = НачатьСвоёЧтение85(Метка);
    ЗакрытиеНачато = Ложь;
    Попытка
        Источник.УстановитьСоединение();
        Если Источник.ПолучитьСостояние() <> СостояниеВнешнегоИсточникаДанных.Подключен Тогда
            ВызватьИсключение "Не установлено собственное readonly соединение";
        КонецЕсли;
        Менеджер = Источник.Таблицы.Лоты85;
        Если ТипЗнч(Менеджер) <> Тип("ВнешнийИсточникДанныхТаблицаМенеджер.УчебныйSQLЧтение85.Лоты85") Тогда
            ВызватьИсключение "Не получен менеджер своей объектной таблицы";
        КонецЕсли;
        До = ПрочитатьЛоты85(Метка, Истина);
        ЛотA = НайтиПоНазванию85(До, "A");
        ЛотB = НайтиПоНазванию85(До, "B");
        ЛотC = НайтиПоНазванию85(До, "C");
        СсылкаЛота = Менеджер.НайтиПоПолю("Код", ЛотA.Ключ);
        Если ТипЗнч(СсылкаЛота) <> Тип("ВнешнийИсточникДанныхТаблицаСсылка.УчебныйSQLЧтение85.Лоты85") Или СсылкаЛота.Пустая()
            Или СсылкаЛота.Метаданные() <> Метаданные.ВнешниеИсточникиДанных.УчебныйSQLЧтение85.Таблицы.Лоты85 Тогда
            ВызватьИсключение "Не найдена настоящая ссылка собственного A";
        КонецЕсли;
        Если ТипЗнч(СсылкаЛота.Код) <> ТипЗнч(ЛотA.Ключ) Или СсылкаЛота.Код <> ЛотA.Ключ
            Или ТипЗнч(СсылкаЛота.Метка) <> ТипЗнч(ЛотA.Метка) Или СсылкаЛота.Метка <> ЛотA.Метка
            Или ТипЗнч(СсылкаЛота.Название) <> ТипЗнч(ЛотA.Название) Или СсылкаЛота.Название <> ЛотA.Название
            Или ТипЗнч(СсылкаЛота.Сумма) <> ТипЗнч(ЛотA.Сумма) Или СсылкаЛота.Сумма <> ЛотA.Сумма Тогда
            ВызватьИсключение "Ссылка не соответствует всем фактическим полям исходного A";
        КонецЕсли;
        ЗначениеСсылки = СсылкаЛота.Значение();
        Восстановленная = Менеджер.ПолучитьСсылку(ЗначениеСсылки);
        Если ТипЗнч(Восстановленная) <> ТипЗнч(СсылкаЛота) Или Восстановленная <> СсылкаЛота Тогда
            ВызватьИсключение "Не прошел roundtrip фактического непрозрачного значения ссылки";
        КонецЕсли;
        ПустаяСсылка = Менеджер.ПустаяСсылка();
        Отсутствующая = Менеджер.НайтиПоПолю("Код", "00000000-0000-0000-0000-000000000000");
        Если ТипЗнч(ПустаяСсылка) <> ТипЗнч(СсылкаЛота) Или Не ПустаяСсылка.Пустая()
            Или ТипЗнч(Отсутствующая) <> ТипЗнч(СсылкаЛота) Или Не Отсутствующая.Пустая() Тогда
            ВызватьИсключение "Ветка отсутствующего собственного элемента должна дать пустые ссылки";
        КонецЕсли;
        Копия = СсылкаЛота.Скопировать();
        Если ТипЗнч(Копия) <> Тип("ВнешнийИсточникДанныхТаблицаОбъект.УчебныйSQLЧтение85.Лоты85") Или Не Копия.ЭтоНовый()
            Или Копия.Метаданные() <> СсылкаЛота.Метаданные()
            Или ТипЗнч(Копия.Метка) <> Тип("Строка") Или Копия.Метка <> ЛотA.Метка
            Или ТипЗнч(Копия.Название) <> Тип("Строка") Или Копия.Название <> ЛотA.Название
            Или ТипЗнч(Копия.Сумма) <> Тип("Число") Или Копия.Сумма <> ЛотA.Сумма Тогда
            ВызватьИсключение "Проверьте фактическую несохраненную копию своего A";
        КонецЕсли;
        Копия.Сумма = 600;
        Если Копия.Сумма <> 600 Или Не Копия.Модифицированность() Тогда
            ВызватьИсключение "Не подтверждено собственное изменение только памяти копии";
        КонецЕсли;
        ПредварительныйИтог = Копия.Сумма + ЛотB.Сумма + ЛотC.Сумма;
        Если ПредварительныйИтог <> 1600 Тогда
            ВызватьИсключение "Прогноз копии600 и исходных750+250 должен дать1600";
        КонецЕсли;
        Черновик = Менеджер.СоздатьОбъект();
        Если ТипЗнч(Черновик) <> ТипЗнч(Копия) Или Не Черновик.ЭтоНовый()
            Или Черновик.Метаданные() <> Копия.Метаданные() Тогда
            ВызватьИсключение "Не создан новый собственный объект таблицы только в памяти";
        КонецЕсли;
        Черновик.Метка = Метка;
        Черновик.Название = "D";
        Черновик.Сумма = 200;
        Если Черновик.Метка <> Метка Или Черновик.Название <> "D" Или Черновик.Сумма <> 200 Тогда
            ВызватьИсключение "Поля отдельного несохраненного черновика не совпали";
        КонецЕсли;
        После = ПрочитатьЛоты85(Метка, Истина);
        СравнитьСнимки85(До, После);
        ЗакрытиеНачато = Истина;
        Источник.РазорватьСоединение();
        Если Источник.ПолучитьСостояние() <> СостояниеВнешнегоИсточникаДанных.Отключен Тогда
            ВызватьИсключение "Проверьте закрытие только собственного учебного соединения";
        КонецЕсли;
        Возврат Новый ФиксированнаяСтруктура("До,Прогноз,После,Выбранная", 1500, ПредварительныйИтог, 1500, Черновик.Сумма);
    Исключение
        ПервичнаяОшибка = ОписаниеОшибки();
        ОшибкаЗакрытия = "";
        Если Не ЗакрытиеНачато Тогда
            ЗакрытиеНачато = Истина;
            Попытка
                Источник.РазорватьСоединение();
            Исключение
                ОшибкаЗакрытия = ОписаниеОшибки();
            КонецПопытки;
        КонецЕсли;
        Если ОшибкаЗакрытия <> "" Тогда
            ВызватьИсключение "Первичная ошибка: " + ПервичнаяОшибка + Символы.ПС
                + "Отдельная ошибка закрытия: " + ОшибкаЗакрытия + Символы.ПС
                + "Состояние своего соединения неизвестно. STOP, без автоматического повтора.";
        КонецЕсли;
        ВызватьИсключение ПервичнаяОшибка;
    КонецПопытки;
КонецФункции
Своя ordinary панель: UUID, явное подтверждение, две реальные кнопки

Своя CF → Общие → ОбщиеФормы → ПроверкаSQL85 → Модуль целиком

Только толстый обычный клиент; настоящий Button0+generic Element104.

Перем Остановка85;

Процедура ПриОткрытии()
    Если Остановка85 <> Неопределено Тогда
        ВызватьИсключение "Повторная инициализация не должна снимать прежний STOP";
    КонецЕсли;
    Остановка85 = Истина;
    Попытка
        УчебныеSQLСсылки85.ПроверитьСтенд85();
        Если ЭтаФорма.ВладелецФормы <> Неопределено Тогда
            ВызватьИсключение "Нужна самостоятельная учебная SQL-панель";
        КонецЕсли;
        Для Каждого ИмяПоля Из СтрРазделить("ВнешнийИтог85,ПредварительныйИтог85,ВыбраннаяСумма85,Статус85", ",") Цикл
            Поле = ЭлементыФормы[ИмяПоля];
            Если ТипЗнч(Поле) <> Тип("ПолеВвода") Или Поле.Данные <> ИмяПоля Тогда
                ВызватьИсключение "Подготовьте все собственные связанные поля результата";
            КонецЕсли;
            Поле.ТолькоПросмотр = Истина;
            Если Не Поле.ТолькоПросмотр Тогда
                ВызватьИсключение "Результат должен оставаться только для просмотра";
            КонецЕсли;
        КонецЦикла;
        Если ТипЗнч(ЭлементыФормы.МеткаСтенда85) <> Тип("ПолеВвода") Или ЭлементыФормы.МеткаСтенда85.Данные <> "МеткаСтенда85"
            Или ТипЗнч(ЭлементыФормы.ПодготовкаПодтверждена85) <> Тип("Флажок") Или ЭлементыФормы.ПодготовкаПодтверждена85.Данные <> "ПодготовкаПодтверждена85" Тогда
            ВызватьИсключение "Подготовьте собственное поле UUID и флажок подтверждения стенда";
        КонецЕсли;
        ПодготовкаПодтверждена85 = Ложь;
        Статус85 = "Соединения нет. Введите seed run_id, подтвердите свой readonly стенд и нажмите одну кнопку.";
        Остановка85 = Ложь;
    Исключение
        Остановка85 = Истина;
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

Процедура ПринятьРезультат85(Кандидат)
    Если ТипЗнч(Кандидат) <> Тип("ФиксированнаяСтруктура")
        Или ТипЗнч(Кандидат.До) <> Тип("Число") Или Кандидат.До <> 1500
        Или ТипЗнч(Кандидат.После) <> Тип("Число") Или Кандидат.После <> 1500
        Или ТипЗнч(Кандидат.Прогноз) <> Тип("Число") Или ТипЗнч(Кандидат.Выбранная) <> Тип("Число")
        Или Не ((Кандидат.Прогноз = 1600 И Кандидат.Выбранная = 200) Или (Кандидат.Прогноз = 1500 И Кандидат.Выбранная = 750)) Тогда
        ВызватьИсключение "Не получен полный собственный проверенный результат";
    КонецЕсли;
    ВнешнийИтог85 = Кандидат.После;
    ПредварительныйИтог85 = Кандидат.Прогноз;
    ВыбраннаяСумма85 = Кандидат.Выбранная;
    Статус85 = "Внешний источник1500 не изменился. Прогноз только в памяти: " + Строка(Кандидат.Прогноз);
    Для Каждого ИмяПоля Из СтрРазделить("ВнешнийИтог85,ПредварительныйИтог85,ВыбраннаяСумма85,Статус85", ",") Цикл
        Поле = ЭлементыФормы[ИмяПоля];
        Если Не Поле.ТолькоПросмотр Тогда
            ВызватьИсключение "Нарушен readonly режим собственного вывода";
        КонецЕсли;
    КонецЦикла;
    Если ЭлементыФормы.ВнешнийИтог85.Значение <> ВнешнийИтог85
        Или ЭлементыФормы.ПредварительныйИтог85.Значение <> ПредварительныйИтог85
        Или ЭлементыФормы.ВыбраннаяСумма85.Значение <> ВыбраннаяСумма85 Или ЭлементыФормы.Статус85.Значение <> Статус85 Тогда
        ВызватьИсключение "Фактические связанные поля формы не совпали с результатом";
    КонецЕсли;
КонецПроцедуры

Процедура Ссылки85(Элемент)
    Попытка
        Если Остановка85 <> Ложь Или ТипЗнч(Элемент) <> Тип("Кнопка") Или Элемент <> ЭлементыФормы.Ссылки85
            Или ПодготовкаПодтверждена85 <> Истина Или ЭлементыФормы.ПодготовкаПодтверждена85.Значение <> Истина Тогда
            ВызватьИсключение "Нажмите свою кнопку только после ручного подтверждения собственного readonly стенда";
        КонецЕсли;
        Кандидат = УчебныеSQLСсылки85.ВыполнитьСсылки85(МеткаСтенда85);
        ПринятьРезультат85(Кандидат);
    Исключение
        Остановка85 = Истина;
        Статус85 = "СТОП. Проверка не завершена; старые числа не являются новым успешным результатом. Не повторяйте автоматически.";
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

Процедура Ключи85(Элемент)
    Попытка
        Если Остановка85 <> Ложь Или ТипЗнч(Элемент) <> Тип("Кнопка") Или Элемент <> ЭлементыФормы.Ключи85
            Или ПодготовкаПодтверждена85 <> Истина Или ЭлементыФормы.ПодготовкаПодтверждена85.Значение <> Истина Тогда
            ВызватьИсключение "Нажмите свою кнопку только после ручного подтверждения собственного readonly стенда";
        КонецЕсли;
        Кандидат = УчебныеSQLКлючи85.ВыполнитьКлючи85(МеткаСтенда85);
        ПринятьРезультат85(Кандидат);
    Исключение
        Остановка85 = Истина;
        Статус85 = "СТОП. Проверка не завершена; старые числа не являются новым успешным результатом. Не повторяйте автоматически.";
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры
Модуль обычного приложения: только открыть панель без SQL

Своя CF → Модуль обычного приложения целиком

Только толстый обычный клиент собственной файловой CF.

Процедура ПриНачалеРаботыСистемы()
#Если ТолстыйКлиентОбычноеПриложение Тогда
    УчебныеSQLСсылки85.ПроверитьСтенд85();
    Форма = ПолучитьОбщуюФорму("ПроверкаSQL85");
    Если Форма = Неопределено Или Форма.ВладелецФормы <> Неопределено Тогда
        ВызватьИсключение "Не получена самостоятельная собственная SQL-панель";
    КонецЕсли;
    Форма.Открыть();
#Иначе
    ВызватьИсключение "Нужен собственный толстый обычный клиент";
#КонецЕсли
КонецПроцедуры
SQL только для ручной подготовки нового собственного стенда

Будущий собственный SSMS/admin, localhost GUIDE85SQLVM default instance, master→новая база

Не BSL; отдельный ручной setup, здесь не выполнялся.

USE [master];
IF DB_NAME() <> N'master' THROW 51000, N'Only master on the own new local instance', 1;
IF ISNULL(CONVERT(nvarchar(128), SERVERPROPERTY('MachineName')), N'') <> N'GUIDE85SQLVM'
    OR SERVERPROPERTY('InstanceName') IS NOT NULL THROW 51001, N'Only the own VM default instance', 1;
IF DB_ID(N'Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c') IS NOT NULL THROW 51002, N'Existing database is not replaced', 1;
IF SUSER_ID(N'GUIDE85SQLVM\Guide85Read') IS NOT NULL THROW 51003, N'Existing login is not changed', 1;
CREATE DATABASE [Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c];
EXEC(N'
USE [Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c];
IF DB_NAME() <> N''Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c'' THROW 51004, N''Only the own new database'', 1;
CREATE TABLE dbo.object_lots (
    item_id nvarchar(36) NOT NULL PRIMARY KEY,
    run_id nvarchar(36) NOT NULL,
    title nvarchar(40) NOT NULL,
    amount numeric(9,0) NOT NULL CHECK (amount >= 0)
);
CREATE TABLE dbo.row_lots (
    run_id nvarchar(36) NOT NULL,
    line_no numeric(9,0) NOT NULL CHECK (line_no > 0),
    title nvarchar(40) NOT NULL,
    amount numeric(9,0) NOT NULL CHECK (amount >= 0),
    PRIMARY KEY (run_id, line_no)
);
DECLARE @run nvarchar(36) = LOWER(CONVERT(nvarchar(36), NEWID()));
INSERT INTO dbo.object_lots (item_id, run_id, title, amount) VALUES
    (LOWER(CONVERT(nvarchar(36), NEWID())), @run, N''A'', 500),
    (LOWER(CONVERT(nvarchar(36), NEWID())), @run, N''B'', 750),
    (LOWER(CONVERT(nvarchar(36), NEWID())), @run, N''C'', 250);
INSERT INTO dbo.row_lots (run_id, line_no, title, amount) VALUES
    (@run, 1, N''A'', 500), (@run, 2, N''B'', 750), (@run, 3, N''C'', 250);
CREATE LOGIN [GUIDE85SQLVM\Guide85Read] FROM WINDOWS
    WITH DEFAULT_DATABASE = [Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c];
CREATE USER [Guide85Read] FOR LOGIN [GUIDE85SQLVM\Guide85Read];
GRANT CONNECT TO [Guide85Read];
GRANT SELECT ON OBJECT::dbo.object_lots TO [Guide85Read];
GRANT SELECT ON OBJECT::dbo.row_lots TO [Guide85Read];
DENY INSERT, UPDATE, DELETE ON OBJECT::dbo.object_lots TO [Guide85Read];
DENY INSERT, UPDATE, DELETE ON OBJECT::dbo.row_lots TO [Guide85Read];
SELECT DB_NAME() AS own_database, @run AS own_run_id;
SELECT item_id, run_id, title, amount FROM dbo.object_lots;
SELECT run_id, line_no, title, amount FROM dbo.row_lots;
');
SQL только чтение: вручную под Windows Guide85Read

Будущий собственный SSMS под GUIDE85SQLVM\Guide85Read → только учебная новая база

Не BSL; проверка настоящих прав и1500, здесь не выполнялась.

USE [Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c];
IF DB_NAME() <> N'Guide85Read_2871b8c9bf2c44f792ff6d32793e0e4c'
    OR SYSTEM_USER <> N'GUIDE85SQLVM\Guide85Read' THROW 51100, N'Only the own readonly Windows login', 1;
IF ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 1) <> 0
    OR ISNULL(IS_MEMBER(N'db_owner'), 1) <> 0
    OR ISNULL(IS_MEMBER(N'db_datawriter'), 1) <> 0
    OR ISNULL(IS_MEMBER(N'db_ddladmin'), 1) <> 0 THROW 51101, N'No admin or writer role for readonly login', 1;
IF ISNULL(HAS_PERMS_BY_NAME(N'dbo.object_lots', N'OBJECT', N'SELECT'), 0) <> 1
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.row_lots', N'OBJECT', N'SELECT'), 0) <> 1
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.object_lots', N'OBJECT', N'INSERT'), 1) <> 0
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.object_lots', N'OBJECT', N'UPDATE'), 1) <> 0
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.object_lots', N'OBJECT', N'DELETE'), 1) <> 0
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.row_lots', N'OBJECT', N'INSERT'), 1) <> 0
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.row_lots', N'OBJECT', N'UPDATE'), 1) <> 0
    OR ISNULL(HAS_PERMS_BY_NAME(N'dbo.row_lots', N'OBJECT', N'DELETE'), 1) <> 0 THROW 51102, N'Own table permissions must be readonly', 1;
SELECT DB_NAME() AS own_database, SYSTEM_USER AS own_windows_login;
SELECT item_id, run_id, title, amount FROM dbo.object_lots;
SELECT run_id, line_no, title, amount FROM dbo.row_lots;
SELECT COUNT(*) AS own_count, SUM(amount) AS own_total FROM dbo.object_lots;
SELECT COUNT(*) AS own_count, SUM(amount) AS own_total FROM dbo.row_lots;

Разбор по шагам

  1. Native СоздатьНаборЗаписей возвращает настоящий пустой набор собственной необъектной таблицы. Прочитать получает все три строки; прямого конструктора/unknown source нет.

  2. Для Каждого получает настоящую запись набора. Все четыре readonly-поля сравниваются с независимым полным запросом; итог отдельно суммируется1500.

  3. Метка и Номер собственного B берутся из фактического источника. Структура содержит ровно эти имена и реальные типы; СоздатьКлючЗаписи формирует настоящий key собственной таблицы.

  4. Пустой() отличает настоящий непустой ключ. Его actual поля сопоставляются с конкретной записью набора и дают выбранную сумму750, не просто равенство абстрактных ключей.

  5. Поиск D возвращает native ПустойКлюч. Его Пустой()=Истина завершает ветвь; чтения несуществующей DB-строки нет.

  6. После выбора и отрицательной ветви повторный полный запрос подтверждает каждое исходное поле и неизменный1500. Все записи набора только читаются.

Как проверить результат

  1. На собственном будущем стенде: обе SQL таблицы имеют три строки и сумму1500 до и после, полные поля и PK совпадают; SSMS readonly-проверка прав выполняется отдельно.

  2. Для Ссылки ожидается memory-прогноз1600 и отдельный draft200. Для Ключи actual выбранный B750, отсутствующий D не читается и не создается.

  3. В отдельном собственном учебном варианте измените поле/число строк/DSN/права или снимите подтверждение: процедура должна отказать без Write/Delete. Не проверяйте отказ на рабочих данных.

  4. Настоящая доставка кнопки, открытие формы, SQL драйвер и runtime здесь не проверялись; callbacks вручную не вызывайте.

Ожидаемый результат

Полный источник остается1500. Новичок различает объектную ссылку, составной ключ необъектной записи и несохраненный объект памяти.

На что обратить внимание

  1. Только документация будущего самостоятельного запуска; реальные VM/SQL/ODBC/CF/1С/сеть/изменения прав здесь не выполнялись. Static native проверки не являются компиляцией или runtime.

  2. Непустая ссылка не гарантирует существование произвольной строки. FindByField при дублях возвращает только один объект; уникальность собственного PK сначала проверяется полным источником.

  3. Имя DSN, UUID каталога и run_id не доказывают адрес, авторизацию или владение чужой базой. Изоляцию обеспечивает новая собственная VM/SQL/CF, readonly login и ручная проверка настоящих параметров/прав.

  4. Copy не записывает объект по native контракту. Значения key/Ссылка новой копии и draft намеренно не угадываются; SQL Write/Delete и RecordManager здесь отсутствуют.

  5. МенеджерЗаписи исключен: его generic поля в installed prop9217 readonly, допустимый путь задания key для Read не подтвержден. GetObject4301 с неполным объявленным returnType не используется; это не утверждение runtime-дефекта.

  6. Снимки не блокируют параллельные изменения и не доказывают атомарность. Только один собственный пользователь/сеанс, никаких чужих процессов и автоматических повторов после ошибки.

  7. Последовательный подготовительный SQL script не обещает общего rollback CREATE DATABASE/login/seed. После частичного отказа не повторяйте и не удаляйте автоматически.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.