- PVSM.RU - https://www.pvsm.ru -

Oracle Data Integrator. SubstitutionAPI: Порядок выполнения подстановок. Часть 1

Для кого эта статья

Статья предназначена для опытных разработчиков ODI (Oracle Data Integrator). Здесь рассмотрены плохо документированные аспекты связанные с порядком выполнения BeanShell-подстановок.

Подстановки %?$@

Спокойно! «%?$@» — это не эвфемизм, а спецсимволы, используемые для BeanShell-подстановок разных типов. Хотя именно в таким образом мог бы сказать о них разработчик, решивший использовать их все вместе, и не разобравшись в ряде тонкостей. А между тем — это чрезвычайно мощный инструмент, обеспечивающий гибкое формирование кода, выполняемого в сценариях.

Когда и где выполняются подстановки? Повторяются ли они в Target-коде для каждой source-строки? Можно ли вкладывать подстановки разных уровней? А одинаковых? Если я объявил Java-переменную, то где я всё еще могу её использовать? Почему иногда не работают функции Substitution API, а иногда работают (недокументировано в каком уровне подстановок какие функции применимы)? И так далее. Этим слабо документированным тонкостям и посвящается серия статей. И это первая из них.

Общим для любых подстановок является то, что внутри подстановки выполняется код на языке BeanShell Script [1]. Обработка подстановок напоминает парсинг JSP или PHP. Аналогично различают два синтаксиса подстановок: сокращённый и полный. В сокращённом подстановка содержит только выражение, результат которого замещает эту подстановку при выполнении.

<%=odiRef.getSysDate("yyyy-MM-DD HH:mm:ss.SSS")%>

Полный синтаксис содержит полноценный BeanShell-код, который может что-то «напечатать» с помощью out.print(), или не делать этого вовсе, а делать другую полезную работу втихаря. В последнем случае после выполнения подстановки она просто изымается из кода не оставляя там никаких следов.

<%out.print(odiRef.getSysDate("yyyy-MM-DD HH:mm:ss.SSS"));%>

Уровни подстановок

Уровень %

Подстановки вида <%…%> выполняются сразу же при старте сессии еще до того, причём физически эта подстановка выполняется на том хосте, который осуществляет запуск. То есть если вы запускаете ODI-сессию из дизайнера, то выполнение %-подстановок происходит на вашей рабочей станции, и если скрипт подстановки решит обратиться, например, к файловой системе (а в beanshell помимо всех возможностей Java есть еще и свои нативные функции для этого), то он увидит ваш локальный диск. Когда же запускается уже собранный ODI-сценарий — вызовом операции InvokeStartScen [2] или это запуск из планировщика, то клиентом, инициирующим запуск, является сам ODI-агент: физическое место выполнения %-подстановки — сервер ODI-агента.

Если во время выполнения %-подстановки возникает исключение, то падения не происходит. Весь стек ошибок вываливается в output, становится результатом выполнения подстановки. Этот результат становится исходным кодом (уже невалидным), который попытается быть интерпретированным на следующем уровне, и падение сессии произойдёт уже там.

Эта подстановка не работает внутри шага Set Variable и Evaluate Variable. То есть она там воспринимается как обычный текст и никак не обрабатывается.

Из этого уровня нельзя подключиться ни к source- ни к target-соединению, а только к work-репозиторию. Т.е. odiRef.getJDBCConnection(«WORKREP») уже доступно, но ни эта функция с другими аргументами, ни getJDBCConnectionFromLSchema() не работают. Потому что никаких коннектов еще не существует: работа сессии еще не начата.

Уровень ?

Итак, сценарий уже находится в агенте и начал выполняться. Сессия создана. Для очередного шага (step) происходит финальное формирование кода: появляется запись в SNP_STEP_LOG, и формируется финальный код всех задач (tasks) этого шага. Вот тут и выполняются подстановки <?…?>. После успешного выполнения (или при отсутствии подстановок) создаются записи в SNP_SESS_TASK_LOG, куда помещается результат — финальный код. Если при интерпретации возникает ошибка, то запись в SNP_SESS_TASK_LOG не создаётся, а сообщение об ошибке надо искать выше — в SNP_STEP_LOG. Когда весь шаг подготовлен, он тут же выполняется.

?-подстановка также игнорируется в шагах Set Variable и Evaluate Variable. Пользоваться source- и target-соединениями из этой подстановки уже можно.

Уровень $

Это особый уровень, который выполняется непосредственно перед выполнением задачи (task), и результат этой подстановки используется для апдейта записи в SNP_SESS_TASK_LOG. Причём если $-подстановка что-то печатает, а внутри подстановки используются ODI-переменные, то в логах вы увидите значения переменных, а не имена как обычно. То есть эту подстановку можно эффективно использовать, чтобы в логах оператора увидеть значение переменной.

Кроме того, $-подстановка это единственное место, где можно вызвать функцию odiRef.setTaskName(), которая ничего не печатает, но изменяет название задачи в логах. По сути это единственные две области применения, где $-подстановки полезны.

Эта подстановка аналогично не работает в шагах Set Variable и Evaluate Variable.

Уровень @

Не таким уж и финальным является финальный код задачи, как оказалось. Интерпретация и выполнение финального кода ведётся на языке, соответствующем технологии, но в код любой технологии можно можно включить @-подстановку с кодом на Java BeanShell. Естественно, подстановка выполняется в первую очередь. Если она что-то печатает, то она дополнительно модифицирует «финальный» код задачи перед выполнением.

@-подстановка может быть применена в шаге Set Variable. Таким образом ODI-переменной можно легко присвоить результат java-выражения. В Evaluate Variable по прежнему ничего нельзя. Если при выполнении подстановки происходит исключение, то сессия падает на текущем шаге.

Source или Target, кто первый?

Любая сессия в ODI состоит из шагов, в каждом шаге один или несколько тасков, а в каждом таске всегда по 2 плеча — source и targer. Даже, например, refresh или set ODI-переменной это такие же таски, просто с пустым телом source. Так как подстановки могут быть на обоих плечах, то интересно, а иногда и важно знать какая выполнится раньше. Потому что важно сначала объявить и присвоить java-переменную, а потом использовать, а не наоборот.

Удивительно, но подстановки %,? и $ выполняются сначала для target плеча, а потом для source. А вот @-подстановка — наоборот.

Зная это свойство вы можете корректно инициализировать переменные, функции, скриптовые объекты и также и обычные java-классы, и потом правильно их использовать.

Повторение @-подстановок

С другими уровнями всё просто. Сначала интерпретируется Target, а потом Source. C уровнем @ всё иначе, и зависит от многих факторов.

Допустим у нас и на sorce- и на target-плечах выбрана технология Orace, которая поддерживает prepareStatement. А также на обоих плечах есть @-подстановки. В такой ситуации действует ограничение: на source может использоваться только select. Нельзя и там и там выполнить, например, pl/sql-блок.

Если на source-плече есть непустой код, то он выполняется первым. Соответственно до выполнения source-кода происходит выполнение подстановок в нём один раз.

Source даёт нам ResultSet, который ODI обязан профетчить, и на каждый fetch попытаться выполнить target-плечо. Обращаться к Source-полям можно по-разному. Можно использовать запись : ИМЯ_ПОЛЯ или #ИМЯ_ПОЛЯ. Если драйвер target-технологии поддерживает операцию prepareStatement (для всех JDBC-драйверов это характерно) и при этом использована только нотация : ИМЯ_ПОЛЯ для полей, то ODI один раз выполняет @-подстановку и prepareStatement. А потом на каждый fetch выполняет только связывание переменных и выполнение оператора.

Если же хоть раз для поля использовано обращение через # (что означает простую текстовую подстановку), или драйвер не поддерживает prepareStatement, то формирование target-оператора будет на каждой итерации особенным (индивидуальным), по этому и @-подстановки будут выполняться многажды.

Рассмотрим пример:

Source:


<@long i=0L;@>
select f1, f2 from table /*даёт 10 строк*/

Target (вариант 1):


begin
   stored_Procedure(<@=++i@>, :f1, :f2);
   /* первый аргумент всегда будет = 1, потому что приращение 
      произойдёт только 1 раз при prepareStatement */
end;

Target (вариант 2):


begin
   stored_Procedure(<@=++i@>, #f1, #f2);
   /* первый аргумент будет = 1, 2, 3.....
  но надо учесть, что каждый раз базой данных
  будет компилироваться, новый текст оператора */
end;

В первом варианте @-подстановки выполняются 1 раз, когда происходит prepareStatement. А во втором каждый fetch рождает приводит к формированию нового текста оператора и новому выполнению @-подстановки.

Если target-технология не предусматривает возможности делать prepareStatement, (например это OdiTools) то обращение к source-полям через двоеточие либо невозможно, либо не отличается от #.

В будущих статьях мы рассмотрим другие сложности связанные с подстановками и использованием Substitution API. В частности в следующий раз мы расскажем про вложенную, в том числе рекурсивную интерпретацию подстановок одинакового и разных уровней. Вас ждёт много неожиданных открытий и вы еще не раз воскликнете: «Ах, вот же почему это так работает!»

Автор: nmaqsudov

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/oracle/259954

Ссылки в тексте:

[1] BeanShell Script: http://www.beanshell.org/manual/bshmanual.html

[2] InvokeStartScen: https://docs.oracle.com/cd/E17904_01/integrate.1111/e12643/running_executions.htm#ODIDG1114

[3] Источник: https://habrahabr.ru/post/332682/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox