Основы InterSystems Caché ObjectScript

Курс знакомит с языком Caché ObjectScript и экосистемой InterSystems: синтаксисом, переменными, глобалями, классами и методами. Вы научитесь работать с данными, обрабатывать ошибки, отлаживать код и использовать базовые практики производительности и безопасности.

1. Введение в Caché: платформа, Studio/VS Code, namespaces

Введение в Caché: платформа, Studio/VS Code, namespaces

InterSystems Caché — это платформа, которая совмещает СУБД и сервер приложений для выполнения кода на ObjectScript. В этом курсе мы будем писать и запускать код, поэтому в первой статье разберёмся:

  • из каких частей состоит Caché как платформа;
  • чем отличаются среды разработки Studio и VS Code;
  • что такое namespace (пространство имён) и зачем оно нужно.
  • Что такое InterSystems Caché как платформа

    Caché можно воспринимать как готовую среду выполнения для серверных приложений:

  • хранит данные;
  • исполняет ObjectScript-код;
  • предоставляет сетевые интерфейсы для приложений и администрирования.
  • Ключевые элементы Caché

    Чтобы уверенно начать работать, достаточно понимать несколько базовых понятий.

  • Инстанс (instance) — установленный и запущенный экземпляр Caché на сервере. У инстанса есть настройки, пользователи, базы данных, namespaces и т.д.
  • Процесс (process) — отдельный серверный процесс Caché, который выполняет ваш код. Когда вы запускаете команду в терминале или выполняете метод, это делает конкретный процесс.
  • Глобалы (globals) — основной низкоуровневый способ хранения данных в Caché: персистентные структуры вида ^Имя(...). Они живут на диске и доступны между процессами.
  • Рутины (routines) — исходный код и скомпилированный код (включая процедуры, классы, методы), который исполняет Caché.
  • Namespace — логическая рабочая область, которая говорит системе, в каких физических базах данных искать глобалы и рутины по умолчанию.
  • Важно: в Caché физическое хранение (файлы баз данных) отделено от логической организации (namespaces). Это одна из причин, почему namespaces — центральное понятие.

    Инструменты разработки: Studio и VS Code

    ObjectScript-код обычно пишут локально на компьютере, а выполняется он на сервере Caché. Поэтому любая IDE решает две задачи:

  • редактировать исходники;
  • отправлять их на сервер для компиляции и запуска.
  • Studio

    InterSystems Studio — классическая IDE для Caché (исторически самая распространённая в экосистеме Caché).

    Сильные стороны Studio:

  • привычный для Caché-мира интерфейс и терминология;
  • встроенная работа с серверными сущностями (классы, рутины, глобалы);
  • быстрый старт в традиционной инфраструктуре Caché.
  • Ограничения, которые важно знать заранее:

  • Studio в основном ориентирована на Windows;
  • хуже вписывается в современные практики вроде Git-ориентированной разработки, удобных диффов и расширяемости редактора.
  • VS Code

    Visual Studio Code — универсальный редактор, который становится IDE за счёт расширений. Для ObjectScript обычно используют расширение от InterSystems.

  • Расширение: InterSystems ObjectScript (VS Code extension)
  • Что VS Code обычно даёт в ObjectScript-проектах:

  • редактирование классов и рутин как обычных файлов в репозитории;
  • загрузку на сервер и компиляцию из редактора;
  • работу с несколькими окружениями (dev/test/prod) через разные подключения;
  • удобную интеграцию с Git.
  • #### Как выглядит подключение в VS Code

    Конкретная настройка зависит от инфраструктуры, но типовой подход — хранить параметры подключения в настройках проекта.

    Пример (идея, а не универсальный шаблон):

    Пояснения к полям:

  • host и port — адрес и порт инстанса Caché;
  • ns — namespace, в который по умолчанию будут загружаться и компилироваться исходники;
  • username и password — учётные данные пользователя Caché.
  • На практике эти значения часто выносят в локальные настройки (чтобы не коммитить пароли), а в репозитории оставляют пример.

    Namespaces: логическая организация кода и данных

    Что такое namespace

    Namespace — это именованная конфигурация, которая определяет:

  • где хранятся и откуда читаются глобалы по умолчанию;
  • где хранятся и откуда читаются рутины по умолчанию;
  • какие пакеты классов или отдельные элементы могут быть смапплены (подключены) из других баз.
  • Проще говоря:

  • база данных — это физический контейнер (файлы на диске);
  • namespace — это логическая точка входа для разработчика и приложения.
  • Один и тот же физический файл базы данных может использоваться разными namespaces. И наоборот: один namespace может собирать данные и код из разных баз за счёт настроек и маппингов.

    !Диаграмма связи namespace с физическими базами данных для globals и routines и пример маппинга

    Типичные namespaces, которые встречаются в Caché

    Названия могут отличаться в зависимости от установки, но часто вы увидите:

  • %SYS — системный namespace для администрирования и системных данных. В нём работают системные утилиты и часть служебных классов.
  • USER — общий пользовательский namespace, который часто используют для разработки примеров и первых приложений.
  • SAMPLES — namespace с демонстрационными данными и примерами (если установлен).
  • Практическое правило для новичка:

  • не начинайте разработку в %SYS;
  • для учебных задач используйте USER или отдельный namespace, созданный специально под проект.
  • Зачем namespaces нужны разработчику

    Namespaces решают сразу несколько задач.

  • Изоляция проектов
  • - Разные приложения могут иметь одинаковые имена глобалов или классов, но быть разнесены по разным namespaces.
  • Разделение кода и данных
  • - Можно хранить данные в одной базе, а код — в другой.
  • Переиспользование
  • - Общие библиотеки можно подключать через маппинги, не копируя код.
  • Окружения dev/test/prod
  • - Часто делают одинаковые по структуре namespaces, но с разными физическими базами данных.

    Как переключиться в другой namespace

    Переключение важно, потому что компиляция и запуск кода происходят в контексте текущего namespace.

    В ObjectScript есть команда переключения namespace:

    Идея простая:

  • до ZN вы находитесь в одном namespace;
  • после ZN тот же процесс работает уже в другом, и поиск глобалов/рутин идёт по его настройкам.
  • Если вы используете IDE (Studio или VS Code), там обычно задают namespace подключения, чтобы не переключать его вручную каждый раз.

    Где настраиваются namespaces

    Администрирование Caché обычно делают через Management Portal (веб-интерфейс).

    Там можно:

  • создавать новый namespace;
  • назначать базы данных для globals и routines;
  • задавать маппинги.
  • Если вы учитесь на готовой установке (например, на учебном сервере), часто достаточно знать, как называется ваш namespace и какие базы ему назначены.

    Документация по концепции namespaces (на примере актуальной платформы InterSystems, концепция совпадает с Caché по смыслу):

  • InterSystems Documentation: Namespaces
  • Минимальный рабочий процесс после этой статьи

    После того как вы понимаете роль платформы, IDE и namespace, типовой цикл разработки выглядит так:

  • Вы выбираете namespace проекта (например, USER или отдельный).
  • Пишете код в Studio или VS Code.
  • Загружаете код на сервер Caché.
  • Компилируете (обычно это делает IDE или команда компиляции).
  • Запускаете код в контексте выбранного namespace.
  • В следующих материалах мы начнём писать первые элементы ObjectScript-кода и разберём, как компиляция, выполнение и хранение данных связаны между собой через namespace.

    2. Синтаксис ObjectScript: команды, выражения, переменные, типы

    Синтаксис ObjectScript: команды, выражения, переменные, типы

    В прошлой статье мы разобрали, что код ObjectScript выполняется внутри инстанса Caché и всегда в контексте namespace. Теперь перейдём к практическому фундаменту: как выглядит язык на уровне строк кода.

    В этой статье разберём:

  • что такое команды ObjectScript и как читается строка программы;
  • что такое выражения и как выполняются операции;
  • какие бывают переменные и чем локальные отличаются от глобалов;
  • какие типы данных реально встречаются в ObjectScript и как работает неявное преобразование.
  • Как «читается» ObjectScript-код

    ObjectScript исторически ориентирован на построчное выполнение: обычно одна строка содержит команду и её аргументы. При этом:

  • пробел отделяет команду от аргумента;
  • несколько команд можно написать в одной строке, разделяя их пробелами;
  • многие команды имеют сокращения.
  • Пример одной строки с двумя командами:

    Эта строка читается так:

  • SET x=10 — присвоить локальной переменной x значение 10;
  • WRITE x,! — вывести x, затем символ перевода строки !.
  • Важно: выполнение происходит в текущем процессе Caché и текущем namespace. Если вы переключили namespace командой ZN "USER", то дальнейшие обращения к глобалам и рутинным элементам будут интерпретироваться по правилам этого namespace.

    Команды ObjectScript

    Что такое команда

    Команда — это ключевое слово, которое описывает действие интерпретатора или скомпилированного кода. Команда может принимать аргумент (или несколько), а некоторые команды могут использоваться без аргументов.

    Примеры распространённых команд:

  • SET — присваивание.
  • WRITE — вывод.
  • READ — ввод.
  • IF — условное выполнение.
  • FOR — цикл.
  • QUIT — выход из текущего контекста (например, из функции).
  • DO — вызов процедуры или метода, когда результат не нужен.
  • Сокращения команд

    В ObjectScript у многих команд есть стандартные сокращения.

    Таблица (самые частые в учебных примерах):

    | Полная команда | Сокращение | Смысл | |---|---:|---| | SET | S | присвоить значение | | WRITE | W | вывести данные | | READ | R | прочитать данные | | KILL | K | удалить переменную/дерево переменных | | NEW | N | ограничить область видимости переменных | | DO | D | выполнить вызов без использования результата | | QUIT | Q | завершить выполнение текущего блока |

    В учебных целях полезно первое время писать полные команды: так код легче читать.

    Постусловие (выполнение «если истинно»)

    Особенность ObjectScript — постусловие: команду можно снабдить условием через двоеточие.

    Пример:

    Идея:

  • WRITE:x>0 ... выполнится только если выражение x>0 истинно.
  • x'>0 — пример отрицания условия (читается как «x не больше 0»).
  • Постусловия встречаются в существующем коде довольно часто, поэтому важно уметь их распознавать.

    Команда IF и блоки кода

    Базовый вариант:

    Если команд больше одной, используют блочную форму с фигурными скобками:

    Блоки делают код предсказуемее и обычно читаются проще, чем длинные строки с множеством постусловий.

    Выражения

    Выражение — это то, что вычисляется в значение: число, строку, логический результат, ссылку на объект.

    Примеры выражений:

  • 2+2
  • "Hello"_" world"
  • x>10
  • `.
  • Несколько базовых, которые постоянно встречаются:

  • EXTRACT(str,from,to) — подстрока.
  • HOROLOG — текущие дата/время в системном формате.
  • Пример:

    objectscript SET x=1 SET y=x+41 WRITE y,! objectscript SET x="outer" DO Demo() WRITE "after: ",x,! QUIT

    Demo() NEW x SET x="inner" WRITE "inside: ",x,! QUIT objectscript SET a=123 KILL a objectscript SET ^Demo("users",1)="Alice" SET ^Demo("users",2)="Bob" WRITE ^Demo("users",2),! objectscript SET a="10" WRITE a+5,! objectscript SET a="10" WRITE a_5,! `

    Это будет конкатенация и даст строку "105".

    Практический вывод: выбирайте оператор осознанно.

    Минимальные правила «хорошего синтаксиса» для старта

  • Старайтесь не писать много команд в одной строке, пока не привыкнете к чтению.
  • Для сложных условий используйте блоки IF { ... }.
  • Перед использованием переменных в процедурах защищайте контекст через NEW.
  • Разделяйте в голове локальные переменные и глобалы: x живёт в процессе, ^x` живёт в базе и зависит от namespace.
  • Куда смотреть справку

    Если хочется сверяться с формальными определениями команд, операторов и встроенных функций, используйте справочник языка (концепции совпадают для Caché и InterSystems IRIS):

  • ObjectScript Reference
  • В следующих материалах мы начнём собирать эти элементы в небольшие программы: ввод/вывод, ветвления, циклы и первые процедуры/методы.

    3. Управляющие конструкции и подпрограммы: IF, FOR, DO, QUIT

    Управляющие конструкции и подпрограммы: IF, FOR, DO, QUIT

    В прошлой статье мы разобрали базовый синтаксис ObjectScript: команды, выражения, переменные и типы. Теперь соберём это в управление потоком выполнения: как программа выбирает ветку, повторяет действия и вызывает подпрограммы.

    В этой статье разберём четыре ключевые команды:

  • IF — условное выполнение
  • FOR — циклы
  • DO — вызов подпрограмм и методов, когда результат не нужен
  • QUIT — выход из подпрограммы, функции или блока
  • > Важно: любой пример выполняется в текущем процессе и в текущем namespace. Если вы пишете в USER, то и глобалы, и рутины будут искаться по правилам USER.

    !Общая схема: условие (IF), повторение (FOR) и выход (QUIT) в типовой логике программы

    Команда IF

    IF в одной строке

    Самая короткая форма — одна команда после условия:

    Здесь WRITE выполнится только если x>5 истинно (обычно это 1).

    IF с блоком

    Если нужно выполнить несколько команд, используйте блок в фигурных скобках:

    Практическое правило: как только внутри ветки больше одной команды, переходите на блоки — так код проще читать и поддерживать.

    IF ... ELSE

    ELSE выполняется, если условие ложно:

    Вложенные условия

    Вложенность делается обычными блоками:

    Здесь x#2 — остаток от деления на 2.

    Постусловие как короткая альтернатива

    В ObjectScript у команд есть постусловие через :. Это удобно для коротких действий:

    Но для сложной логики (особенно с ELSE) чаще читаются блоки IF { ... }.

    Команда FOR

    Цикл FOR повторяет выполнение команд. В ObjectScript вы часто встретите два основных стиля.

    Счётный цикл FOR

    Классическая форма с шагом:

    Читается так:

  • i=1 — начальное значение
  • :1: — шаг (увеличивать на 1)
  • 5 — конечное значение (включительно)
  • Можно использовать другой шаг:

    Цикл FOR без параметров (пока не выйдем)

    Вторая важная форма — FOR { ... } без счётчика. Такой цикл продолжается бесконечно, пока вы явно не выйдете.

    Типовой паттерн — выход через QUIT:

    Обратите внимание на QUIT:n=3 — это постусловие: выйти из цикла, только когда n=3.

    Перебор данных через ORDER, которая возвращает следующий ключ.

    Пример с глобалом:

    `objectscript KILL ^Demo SET ^Demo("users",1)="Alice" SET ^Demo("users",2)="Bob"

    NEW id SET id="" FOR { SET id=ORDER(^Demo("users",id)) возвращает следующий существующий подскрипт

  • когда элементы заканчиваются, ORDER + FOR { ... } + QUIT.
  • Разделяйте процедуры и функции:
  • - процедура вызывается через DO и делает действия - функция вызывается через $$ и возвращает значение через QUIT <value>
  • В подпрограммах защищайте переменные от конфликтов через NEW (это особенно важно, когда вы пишете библиотечный код, который вызывается из разных мест).
  • Где посмотреть официальную справку

    Если хотите свериться с формальными вариантами синтаксиса, смотрите справочник команд ObjectScript:

  • InterSystems Documentation: IF command
  • InterSystems Documentation: FOR command
  • InterSystems Documentation: DO command
  • InterSystems Documentation: QUIT command
  • В следующем материале обычно удобно закреплять эти конструкции на практике: ввод/вывод, разбор строк, простые процедуры и обход данных в глобалах.

    4. Работа с глобалями и массивами: структура, $ORDER, $DATA

    Работа с глобалями и массивами: структура, DATA

    В прошлых статьях мы разобрали синтаксис ObjectScript и управляющие конструкции IF, FOR, DO, QUIT. Теперь добавим ключевую практическую тему Caché: иерархическое хранение данных в глобалях и многомерных массивах (локальных массивах), а также научимся безопасно обходить эти структуры с помощью DATA.

    Глобали и локальные массивы: одна структура, разное время жизни

    В ObjectScript у локальных массивов и глобалей одинаковая модель: разреженное многомерное дерево, в узлах которого могут быть:

  • значение (строка/число);
  • дочерние узлы;
  • и то, и другое.
  • Разница в том, где живут данные:

  • Локальный массив живёт в памяти текущего процесса и исчезает, когда процесс заканчивается.
  • - Синтаксис: arr("a",1)
  • Глобаль хранится в базе данных и доступна всем процессам (с учётом прав и текущего namespace).
  • - Синтаксис: ^arr("a",1)

    > Важно: глобаль всегда привязана к текущему namespace (из первой статьи). Один и тот же код в разных namespaces может работать с разными физическими данными.

    !Схема показывает древовидную структуру узлов и различие между локальным массивом и глобалью

    Структура узлов: что такое подскрипты и “узел”

    Запись вида ^Demo("users",1) читается так:

  • ^Demo — имя глобали (корень дерева).
  • ("users",1) — список подскриптов (ключей), которые ведут к конкретному узлу.
  • Узел может хранить значение:
  • Узел может иметь потомков даже без собственного значения

    Это частая особенность: узел может быть “папкой”, а не “файлом”.

    Здесь узел ^Demo("users",1) может не иметь собственного значения, но иметь потомков "name" и "age".

    Безопасное чтение: глобаль против локальной переменной

    Важное практическое различие:

  • чтение несуществующего узла глобали обычно возвращает пустую строку "";
  • чтение несуществующей локальной переменной/узла локального массива может привести к ошибке <UNDEFINED>.
  • Поэтому в реальном коде либо проверяют наличие узла через GET (встроенная функция, которая возвращает значение или значение по умолчанию).

    В этой статье фокус на DATA: есть ли значение и/или потомки

    DATA

    | DATA(^D("a")),! ; 0

    SET ^D("a")="x" WRITE DATA(^D("a")),! ; 10 (у ^D("a") нет значения, но есть потомок 1)

    SET ^D("a")="root" WRITE DATA(arr("x")) { WRITE "arr('x')=",arr("x"),! }

    IF 'ORDER: как обходить разреженные ключи

    ORDER не вернёт "" снова.

    Обход одного уровня глобали

    objectscript NEW arr,key SET arr("b")=2 SET arr("a")=1

    SET key="" FOR { SET key=ORDER поддерживает направление обхода вторым параметром. Часто используют -1 для движения назад.

    objectscript KILL ^Demo SET ^Demo("users",1,"name")="Alice" SET ^Demo("users",1,"age")=30 SET ^Demo("users",2,"name")="Bob"

    NEW id,field SET id="" FOR { SET id=ORDER(^Demo("users",id,field)) QUIT:field="" WRITE " ",field,"=",^Demo("users",id,field),! } }

    Минимальные практические правила

  • Для обучения и экспериментов начинайте блок с KILL ^SomeGlobal, чтобы не смешивать результаты разных запусков.
  • Если обходите узлы через DATA, чтобы не получить <UNDEFINED>.
  • Помните про контекст namespace: глобаль ^Demo в USER и ^Demo` в другом namespace могут быть физически разными данными.
  • Официальная справка

  • InterSystems Documentation: DATA
  • InterSystems Documentation: ObjectScript Reference
  • 5. ООП в Caché: классы, методы, свойства, наследование, %Status

    ООП в Caché: классы, методы, свойства, наследование, %Status

    В прошлых материалах мы писали код в процедурном стиле: команды, условия, циклы, подпрограммы (DO/Func().

  • В ООП вы чаще вызываете методы: DO obj.Method() или SET x=obj.Method().
  • Раньше данные часто хранились в глобалях ^Data(...).
  • В ООП вы обычно работаете с объектами, а запись/чтение из глобалей прячете в методах (или используете системные механизмы персистентности, если до них дойдёте позже).
  • Важно помнить контекст из первой статьи: код компилируется и выполняется в текущем namespace. Один и тот же класс, скомпилированный в USER, не появится автоматически в другом namespace.

    Класс, объект и oref

    Класс

    Класс — это описание структуры и поведения:

  • какие данные у сущности есть (это свойства);
  • какие действия она умеет делать (это методы).
  • Класс объявляется ключевым словом Class и обычно имеет полное имя с пакетом (пространством имён внутри кода), например MyApp.Person.

    Объект и ссылка (oref)

    Объект создаётся из класса и живёт в памяти процесса.

  • Переменная, в которой хранится объект, на самом деле хранит oref (object reference) — ссылку на объект.
  • Oref ведёт себя как значение, которое можно присваивать, передавать в методы, сравнивать на пустоту.
  • Создание объекта обычно выглядит так:

    p — это oref.

    Минимальный пример класса

    Ниже пример класса без привязки к хранению в базе (чтобы сосредоточиться на синтаксисе ООП). Базовый суперкласс %RegisteredObject даёт стандартное объектное поведение.

    Что важно заметить:

  • Property объявляет свойство (поле объекта).
  • Method объявляет метод.
  • ..Name — обращение к свойству текущего объекта (аналог this.Name в других языках).
  • QUIT внутри метода возвращает результат (как вы уже видели в ERROR(OK
  • } } `

    Здесь используются стандартные макросы (они доступны в типовых окружениях Caché):

  • ISOK(sc) {
  • WRITE "Ошибка: ",SYSTEM.Status.GetErrorText(sc).

    Как связать ООП и глобали в учебных примерах

    На раннем этапе удобно хранить данные просто в глобалях, а объект использовать как обёртку над логикой.

    Типовая идея:

  • Save() пишет свойства объекта в ^Demo("person",id,...);
  • Load(id) читает из глобали и заполняет свойства.
  • Так вы используете знания из статьи про DATA, но постепенно переносите структуру программы в классы и методы.

    Рекомендации по стилю для старта

  • Для методов, которые могут завершиться ошибкой, возвращайте %Status.
  • Не смешивайте вывод (WRITE) и бизнес-логику внутри одной и той же модели без необходимости: лучше пусть метод возвращает данные, а вывод делает вызывающий код.
  • Внутри методов активно используйте NEW для локальных переменных, если пишете нетривиальную логику, чтобы не «протечь» в контекст вызывающего кода.
  • Документация

  • ObjectScript Reference
  • Class Definition Reference
  • В следующей практике обычно переходят к маленьким задачам: написать 1–2 класса, сделать валидацию через %Status, сохранить данные в глобаль и обойти сохранённые записи через $ORDER`.

    6. Доступ к данным и SQL: Persistent классы, запросы, embedded SQL

    Доступ к данным и SQL: Persistent классы, запросы, embedded SQL

    До этого мы работали с данными напрямую через глобали ^... и обходили их через DATA. Это полезно для понимания устройства Caché, но в прикладной разработке чаще используют более высокоуровневый доступ к данным:

  • Persistent классы: объектная модель, которая автоматически сохраняется в базе
  • SQL: выборки, фильтрация, сортировка, агрегаты
  • embedded SQL: SQL прямо внутри ObjectScript-кода
  • Ключевая идея: в Caché объекты, глобали и SQL — это разные способы работать с одними и теми же данными, если вы используете persistent-классы.

    !Связь между persistent-классом, SQL-проекцией и физическим хранением в глобалях

    Persistent класс как основа объектного доступа к данным

    Что такое persistent-класс

    Persistent класс — это класс, объекты которого можно сохранять в базу данных. В Caché это делается наследованием от системного класса %Persistent.

    Минимальный пример:

    Что это даёт:

  • Caché создаёт для класса механизм хранения в базе (физически это глобали)
  • класс получает стандартные методы для работы с записью: создание, сохранение, открытие, удаление
  • класс автоматически получает SQL-проекцию и становится доступен через SQL как таблица
  • Важно про namespace

    Как и в прошлых статьях, всё работает в контексте текущего namespace:

  • класс компилируется в текущем namespace
  • SQL-таблица этого класса существует в этом же namespace
  • данные сохраняются в глобали, привязанные к базам данных namespace
  • Одинаковое имя класса в разных namespaces может означать разные данные.

    Базовые операции с persistent-объектами

    Создание и сохранение: %New() и %Save()

    Как persistent-класс становится таблицей SQL

    Имя таблицы и колонки

    По умолчанию класс MyApp.Person проецируется в SQL-таблицу (часто вида) MyApp_Person.

  • %Id() соответствует SQL-колонке ID
  • каждое Property обычно становится SQL-колонкой
  • Если нужно явно задать имя таблицы, используют параметр SqlTableName:

    Индексы

    Чтобы SQL-запросы работали быстро, обычно добавляют индексы.

    Смысл индекса:

  • ускоряет WHERE Name=... или WHERE Age>...
  • физически индекс хранится в отдельных глобалях
  • Запросы к данным через SQL из ObjectScript

    В Caché есть два популярных подхода:

  • Dynamic SQL: SQL как строка, подготовка и выполнение через классы %SQL.*
  • embedded SQL: SQL-вставка прямо в код через &sql(...)
  • Оба подхода полезны, выбор зависит от задачи.

    Dynamic SQL: подготовка, выполнение, чтение результатов

    Dynamic SQL удобен, когда:

  • запрос собирается динамически
  • нужно универсально обрабатывать результаты
  • нужен код, который похож на привычный API выполнения SQL
  • Пример: получить всех людей старше заданного возраста.

    %ROWCOUNT — системная переменная embedded SQL, показывающая число затронутых строк последней SQL-операцией.

    Class Query: запрос как часть класса

    Кроме «обычного SQL», в Caché есть механизм Query внутри класса.

    Смысл:

  • вы объявляете запрос как часть класса
  • запрос можно вызывать из ObjectScript через API запросов
  • часто такой Query также виден из SQL как вызываемый объект (в зависимости от настроек и платформы)
  • Пример: Query по минимальному возрасту.

    Вызов из ObjectScript:

    На практике Class Query удобны как «контракт»: запрос находится рядом с моделью данных, а вызывающий код использует единый интерфейс.

    Когда выбирать объекты, когда SQL, и как сочетать с глобалями

    Практический выбор

  • Persistent-объекты
  • - хорошо подходят для CRUD-операций, валидации через %Status, инкапсуляции логики в методах
  • SQL
  • - лучше для выборок, отчётов, агрегаций, сложных фильтров и сортировок
  • Глобали напрямую
  • - полезны для низкоуровневых структур, кешей и специальных оптимизационных сценариев

    Важное правило

    Если данные класса хранятся как persistent, старайтесь не менять его storage-глобали вручную через SET ^....

    Причина простая: SQL-проекция, индексы и объектная модель ожидают согласованность данных. Ручная запись в глобали может нарушить индексы и привести к «странным» результатам в SQL.

    Минимальные рекомендации по стилю

  • Почти все операции сохранения и многие операции SQL возвращают или подразумевают ошибки: используйте %Status и проверяйте его сразу
  • Для embedded SQL всегда проверяйте SQLCODE, а для изменений — дополнительно %ROWCOUNT
  • Если запрос становится сложным или должен переиспользоваться, рассмотрите Class Query или Dynamic SQL вместо «больших» embedded SQL вставок
  • Документация

  • ObjectScript Reference
  • Class Definition Reference
  • InterSystems SQL Reference
  • 7. Отладка, обработка ошибок и оптимизация: TRY/CATCH, логирование, индексы

    Отладка, обработка ошибок и оптимизация: TRY/CATCH, логирование, индексы

    В предыдущих статьях мы уже используем процедурный код, классы, %Status и SQL. В реальном проекте этого недостаточно: код должен быть наблюдаемым (чтобы быстро понять, что произошло), устойчивым к ошибкам (чтобы не падать «молча») и достаточно быстрым (чтобы не упираться в базу).

    В этой статье разберём три практические темы, которые обычно идут рядом:

  • Отладка: как быстро понять, где программа ведёт себя не так.
  • Обработка ошибок: как сочетать %Status и TRY/CATCH.
  • Оптимизация: как индексы ускоряют SQL-запросы и какие у них побочные эффекты.
  • !Визуальная схема, как связаны %Status, ThrowStatus и TRY/CATCH

    Отладка: минимальный набор инструментов

    Отладка в Caché обычно происходит в контексте:

  • текущего namespace (помните ZN "USER" из первой статьи);
  • текущего процесса (системная переменная ORDER и деревьями данных.
  • Обработка ошибок: %Status и TRY/CATCH

    В экосистеме InterSystems встречаются два основных «способа сообщить об ошибке»:

  • возврат %Status (OK
  • } CATCH ex { QUIT ex.AsStatus() } }

    Смысл примера:

  • ORDER так же, как в статье про глобали.
  • Практический совет по стилю

  • Не делайте WRITE основным способом диагностики в серверном коде.
  • Логируйте ошибки там, где вы их обрабатываете (обычно в CATCH или сразу после проверки %Status).
  • Старайтесь писать лог так, чтобы его можно было фильтровать: уровень, namespace, job.
  • Оптимизация SQL: индексы и их цена

    В прошлой статье про SQL мы добавляли индексы в persistent-класс, но теперь разберём зачем и какие последствия.

    Что такое индекс в контексте persistent-класса

    Индекс — это дополнительная структура (физически тоже хранится в глобалях), которая позволяет SQL быстро находить строки по условию.

    Простой пример:

    Смысл:

  • запрос WHERE Name = ? может использовать NameIdx вместо полного перебора таблицы;
  • запрос WHERE Age > ? может использовать AgeIdx для ускорения отбора.
  • Составной индекс

    Если часто фильтруете по двум полям одновременно, полезен составной индекс:

    Обычно он помогает запросам вида:

  • WHERE Name=? AND Age>?
  • Уникальный индекс

    Если значение должно быть уникальным (например, табельный номер), используйте уникальный индекс. Это одновременно:

  • ускорение поиска;
  • защита данных от дублей.
  • Пример (общая идея):

    Как понять, что индекс реально используется

    На практике это делают через просмотр плана запроса в SQL-инструментах платформы (в том числе в Management Portal) или через команды показа плана в SQL-среде.

    Прикладной критерий для разработчика:

  • если запрос по условию на поле без индекса «внезапно» стал медленным на больших данных, первым делом проверьте наличие подходящего индекса.
  • Официальная справка по SQL в платформе:

  • InterSystems SQL Reference
  • Цена индексов

    Индексы почти всегда ускоряют чтение, но имеют стоимость:

  • замедляют вставку и обновление, потому что индекс тоже нужно обновлять;
  • занимают место в базе;
  • усложняют схему, если индексов слишком много.
  • Практический вывод: индекс добавляют не «на всякий случай», а под конкретные запросы.

    Итоговые практические паттерны

  • Обрабатывайте ошибки единообразно: либо возвращайте %Status, либо в верхнем уровне делайте TRY/CATCH и возвращайте ex.AsStatus().
  • Если вы используете %Status, проверяйте его сразу после вызова.
  • Логируйте ошибки в CATCH вместе с контекстом (NAMESPACE) и текстом статуса.
  • Добавляйте индексы под реальные WHERE/ORDER BY` ваших запросов и помните, что индексы замедляют записи.