Подстановки %?$@
Спокойно! «%?$@» — это не эвфемизм, а спецсимволы, используемые для BeanShell-подстановок разных типов. Хотя именно в таким образом мог бы сказать о них разработчик, решивший использовать их все вместе, и не разобравшись в ряде тонкостей. А между тем — это чрезвычайно мощный инструмент, обеспечивающий гибкое формирование кода, выполняемого в сценариях.
Когда и где выполняются подстановки? Повторяются ли они в Target-коде для каждой source-строки? Можно ли вкладывать подстановки разных уровней? А одинаковых? Если я объявил Java-переменную, то где я всё еще могу её использовать? Почему иногда не работают функции Substitution API, а иногда работают (недокументировано в каком уровне подстановок какие функции применимы)? И так далее. Этим слабо документированным тонкостям и посвящается серия статей. И это первая из них.
Общим для любых подстановок является то, что внутри подстановки выполняется код на языке BeanShell Script. Обработка подстановок напоминает парсинг 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 или это запуск из планировщика, то клиентом, инициирующим запуск, является сам 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