Oracle что такое контекст

Обновлено: 07.07.2024

Было несколько статей на тему миграции баз и версионность. Статьи бесспорно хороши, но только когда у вас несколько десятков табличек. У нас же 200к строк PL/SQL кода (число других объектов сопоставимо с этим) и команда из 10 человек.
Сначала пытались использовать для контроля всего связку RequesitePro + ClearQuest + ClearCase, но после непродолжительного времени от СС отказались и заменили на git с локальным хранилищем на gitorious.
Разные части приложения разделили на разные репозитории. Создали отдельные репозитории для модификаций для отдельных клиентов.
Подход к файлам следующий: есть baseline и есть патчи к нему, со временем и то и другое обновляется.

Написание кода

  1. CREATE OR REPLACE PROCEDURE upd_for_dept (
  2. dept_in IN employee.department_id%TYPE
  3. ,newsal_in IN employee.salary%TYPE)
  4. IS
  5. CURSOR emp_cur IS
  6. SELECT employee_id,salary,hire_date
  7. FROM employee WHERE department_id = dept_in;
  8. BEGIN
  9. FOR rec IN emp_cur LOOP
  10. UPDATE employee SET salary = NVL(newsal_in, 1000)
  11. WHERE employee_id = rec.employee_id;
  12. END LOOP;
  13. END upd_for_dept;


Здесь стоит обратить еще внимание на %TYPE. Всегда когда тип переменной это тип какой то колонки или поля, то следует использовать %TYPE.
В цикле FOR выполняется множество операторов UPDATE (и да всю процедуру можно запихать в один UPDATE). В Oracle DB pl/sql и sql код выполняют разные движки, и соответственно будет множество переключений контекстов между этими движками, что занимает какое то время.
Поэтому перепишем нашу процедуру следующим образом:

  1. CREATE OR REPLACE PROCEDURE upd_for_dept (
  2. dept_in IN employee.department_id%TYPE
  3. ,newsal_in IN employee.salary%TYPE)
  4. IS
  5. deptlist id_list;
  6. BEGIN
  7. SELECT employee_id
  8. BULK COLLECT INTO deptlist
  9. FROM employee WHERE department_id = dept_in;
  10. FORALL indx IN deptlist. FIRST ..deptlist. LAST
  11. UPDATE employee
  12. SET salary = NVL(newsal_in, 1000)
  13. WHERE employee_id = deptlist(indx);
  14. END upd_for_dept;


Оператор FORALL не вызовет переключений контекстов. Но что здесь еще плохо. Правильно это NVL. NVL — это функция PL/SQL, следовательно снова будет переключение кон текстов. Поэтому ее следует заменить на COALESCE или оператор CASE.
Тут естественным образом вырисовывается правило, об использовании функций встроенных в SQL движок.
При использовании коллекций естественным образом меняется дизайн процедур и функций. Конечным бы вариантом стала бы такая процедура:

Илья Дергунов

Контексты приложений в PL/SQL: SYS_CONTEXT, SET_CONTEXT

При обсуждении безопасности уровня строк в Oracle было сделано очень важное допущение: предикат (то есть условие, ограничивающее набор видимых строк таблицы) оставался неизменным. В рассмотренных примерах он базировался на коде отдела пользователя. Допустим, в системе вводится новое требование: теперь пользо­ватели просматривают записи работников на основании не кодов отделов, а специально ведущихся для этой цели списков привилегий. В таблице EMP_ACCESS хранится инфор­мация о том, кому из пользователей разрешено работать с теми или иными данными.

Пользователь Martin может просматривать данные отделов с кодами 10 и 20, а пользо­ватель King — данные отделов 10, 20, 30 и 40. Если имя пользователя не указано в таб­лице, он не может просматривать записи. Новое правило требует, чтобы предикаты генерировались динамически в функции политики.

Кроме того, должна быть предусмотрена возможность динамического изменения при­вилегий пользователей посредством обновления таблицы EMP_ACCESS , и пользователя при этом не следует заставлять заново подключаться к базе данных. Следовательно, триггер LOGON нам не поможет.

Одно из возможных решений заключается в создании пакета с переменной, в которой хранится предикат; пользователю предоставляется возможность выполнения сегмента кода PL/SQL , присваивающего значение переменной. Внутри функции политики значе­ние пакетной переменной используется в качестве предиката. Насколько приемлемо это решение? Подумайте хорошенько: если пользователь может присвоить другое значение пакетной переменной, что помешает ему присвоить привилегированное значение? Поль­зователь подключается к базе данных, присваивает переменной значение, открывающее доступ ко всем данным, после чего выполняет выборку и видит все записи. Отсутствие безопасности делает этот вариант неприемлемым. Собственно, этот сценарий наглядно демонстрирует, почему код задания значений переменных следует размещать в триггере LOGON , где пользователь не сможет внести изменения.

Использование контекстов приложений

Вероятность того, что пользователь может динамически изменить пакетную переменную, заставляет переосмыслить стратегию. Нам необходим механизм задания глобальной переменной неким безопасным механизмом, исключающим несанкционированные из­менения. К счастью, Oracle предоставляет такую возможность. Контекст приложения аналогичен глобальной пакетной переменной; после задания значения он остается до­ступным на протяжении всего сеанса. Впрочем, на этом сходство кончается. Важнейшее различие заключается в том, что в отличие от пакетной переменной, контекст прило­жения не задается простым присваиванием; для изменения значения необходим вызов процедуры — и это обстоятельство делает этот вариант более безопасным.

Как и структуры языка C или записи PL/SQL , контекст приложения обладает атрибу­тами, которым присваиваются значения. Однако в отличие от аналогов из C и PL/SQL , имена атрибутов не фиксируются при создании контекста; это происходит во время выполнения. Контексты приложений по умолчанию хранятся в области PGA , если только они не определены как глобальные. Так как область PGA содержит приватные данные сеанса, хранящиеся в ней значения остаются невидимыми для других сеансов. В следующем примере команда CREATE CONTEXT используется для определения нового контекста с именем dept_ctx :

Секция USING set_dept_ctx означает, что существует процедура с именем set_dept_ctx , и только эта процедура может изменять атрибуты контекста dept_ctx . Никаким другим способом атрибуты изменяться не могут. У созданного контекста еще нет атрибутов — пока мы просто определили общий контекст (имя и безопасный механизм изменения). На следующем шаге необходимо создать процедуру, в которой атрибутам контекста будут присваиваться значения с использованием функции SET_CONTEXT встроенного пакета DBMS_SESSI0N , как показано в следующем примере:

Чтобы присвоить атрибуту DEPTNO значение 10, мы выполняем следующую команду:

Для получения текущего значения атрибута вызывается функция SYS_CONTEXT , которая получает два параметра: имя контекста и имя атрибута. Пример:

Функция также может использоваться для получения некоторых предопределенных контекстов, например IP-адресов и терминалов клиентов:

В этом фрагменте используется предопределенный контекст USERENV , обладающий такими атрибутами, как TERMINAL , IP_ADDRESS , OS_USER и т. д. Значения этих атрибутов присваиваются автоматически, и изменить их в приложении невозможно — допускается только чтение данных.

Безопасность в контекстах

В сущности, работа процедуры set_dept_ctx сводится к вызову готовой программы DBMS_SESSION . SET_CONTEXT с соответствующими параметрами. Зачем определять для этого процедуру? Почему бы не вызвать встроенную функцию напрямую? Давайте посмотрим, что произойдет, если пользователь попытается в том же сегменте кода при­своить значение атрибуту DEPTNO :

Ошибка ORA-01031 выглядит довольно странно: пользователь Martin обладает приви­легией EXECUTE для DBMS_SESSION , так что проблема, очевидно, не в нехватке привилегий. Вы можете убедиться в этом, заново предоставив привилегию EXECUTE для пакета и по­вторно выполнив тот же сегмент кода; вы получите ту же ошибку.

Дело в том, что контексты приложений не могут изменяться прямыми обращениями к встроенному пакету. Все модификации должны осуществляться программным блоком, связанным с контекстом при его создании. Такой блок называется доверенной програм­мой контекста приложения.

При создании контекста приложения необходимо указать его доверенную про­грамму. Только доверенная программа может задавать значения в этом контексте, но не в других контекстах.

Контексты как предикаты в RLS

Итак, для изменения значений атрибутов контекста должна использоваться процедура. Не приводит ли это к бессмысленному увеличению сложности программы?

Нет, потому что доверенная процедура — единственный механизм изменения контекста — играет роль «стража» для работы с контекстом. В ней могут выполняться сколь угодно сложные действия по аутентификации и проверке данных, гарантирующие действитель­ность присваивания. Мы даже можем полностью отказаться от передачи параметров и задавать их на основании предопределенных значений без получения данных от пользователя. Например, из поставленных требований известно, что контексту должна быть присвоена строка с кодами отделов, прочитанными из таблицы EMP_ACCESS (а не передаваемыми пользователем!). Затем контекст приложения используется в функции политики. Давайте посмотрим, как реализовать это требование.

Сначала необходимо внести изменения в функцию политики:

Функция политики предполагает, что коды отделов будут передаваться через атрибут DEPTN0_LIST контекста dept_ctx (строка 14). Чтобы задать значение атрибута, необходимо внести изменения в доверенную процедуру контекста:

Пора протестировать функцию. Сначала пользователь Martin подключается к базе данных и подсчитывает количество работников. Перед выдачей запроса он должен установить контекст:

В соответствии с таблицей EMP_ACCESS ему видны только данные работников отделов 10 и 20. Допустим, в данных Martin номер отдела меняется на 30. Администратор вносит соответствующие изменения в таблицу:

Теперь при выполнении тех же запросов Martin получит другой результат:

Изменения вступают в силу автоматически. Поскольку Martin не задает атрибуты кон­текста вручную, такое решение по своей природе более безопасно, чем задание глобаль­ной переменной. Кроме того, контекстная политика RLS в Oracle10g и более поздних версиях также способствует повышению производительности. Функция политики выполняется только при изменении контекста, а между изменениями используются кэшированные значения. Тем самым обеспечивается более высокая скорость работы этой политики по сравнению с используемой по умолчанию динамической политикой. Чтобы определить политику как контекстную, следует передать процедуре DBMS_RLS. add_policy дополнительный параметр:

Итак, чем же этот метод отличается от создания динамически сгенерированной функ­ции политики для таблицы emp_access ? В случае функции политики необходимо ее выполнение для получения значения предиката (списка отделов в нашей ситуации). Предположим, имеется таблица, к которой обращены миллионы запросов; функция политики будет выполняться многократно, при этом каждый раз будет происходить обращение к таблице emp_access — с печальными последствиями для производитель­ности. Политику можно определить как статическую, чтобы функция не выполнялась так часто, но тогда при изменении записей emp_access функция политики не будет учитывать изменения и выдаст неправильный результат. Определение контекстной политики в контексте приложения решает обе проблемы — функция политики будет выполняться заново при изменении контекстного значения. Контекстные значения хранятся в памяти, поэтому обращения к ним происходят очень быстро.

В Oracle Database 12c контекстная политика предоставляет еще больше преимуществ. Кроме параметра policy_type , можно передать еще два параметра — namespace (простран­ство имен, а проще говоря — имя) и attribute (атрибут контекста, изменение которого должно приводить к повторному выполнению функции политики). В приведенном выше примере я использовал контекст DEPT_CTX , а конкретнее — атрибут с именем DEPTN0_LIST , для вывода списка номеров отделов, которые разрешено видеть пользователю. Функцию политики можно определить так, чтобы она повторно выполнялась только при изменении списка отделов. Для этого в политику добавляются зависимости от контекста и атрибута:

Пример полного вызова процедуры add_policy :

Идентификация сторонних пользователей

Полезность контекстов приложений выходит далеко за рамки ситуаций, описанных выше. Самое важное применение контекстов приложения — возможность различать пользователей, которые не могут быть идентифицированы на уровне уникального се­анса. Этот сценарий типичен для веб-приложений, использующих пул подключений с одним пользователем (например, C0NNP00L ). Веб-пользователи подключаются к сер­веру приложения, который в свою очередь использует одно из подключений пула для обращения к базе данных (рис. 1).

Пользователи приложения и RLS

Рис. 1. Пользователи приложения и RLS

В этой схеме пользователи Martin и King не являются пользователями базы данных; это веб-пользователи, и базе данных о них ничего не известно. Пул подключений связы­вается с базой данных с идентификатором CONNPOOL , который является пользователем базы данных. Когда Martin запрашивает информацию из базы данных, пул должен решить использовать для выборки подключение 1. После завершения запроса под­ключение освобождается. Если в этот момент пользователь King запросит данные, пул может решить использовать то же подключение (1). Соответственно, с точки зрения базы данных сеанс (а на самом деле подключение из пула) принадлежит пользователю CONNPOOL . По этой причине в приведенных ранее примерах (в которых использовалась функция USER ) идентификация пользователя работать не будет. Функция USER всегда возвращает CONNPOOL , потому что к базе данных подключен именно этот пользователь. На помощь приходят контексты приложения. Предположим, имеется контекст с именем WEB_CTX и атрибутом WEBUSER . Клиент присваивает ему имя фактического пользователя (например, MARTIN) при отправке запроса к пулу подключений:

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

Обратите внимание на строку 17. В исходной версии кода она выглядела так:

А теперь выглядит так:

Это условие получает имя веб-пользователя и сравнивает его со столбцом ENAME .

Создаются контексты SQL-предложением CREATE CONTEXT. Из-за этого далее вместо «пространства имен» предпочтение отдается термину «контекст». Параметры контекста («атрибуты») устанавливаются процедурой DBMS_SESSION.SET_CONTEXT, а вот вычитываются в программу стандартной функцией SYS_CONTEXT. Пакет DBMS_SESSION содержит ряд других подпрограмм для работы с контекстами.

Здесь рассматривается лишь формальная сторона контекста сеанса, а способ его применения разработчик может определить сам или почерпнуть из описаний избирательного доступа к данным (FGAC или Label Security) и сервера приложений Oracle.

Готовый справочный контекст сеанса USERENV

Один контекст, с названием USERENV, создавать явным образом не требуется. Он доступен любому сеансу связи с СУБД Oracle в виде готового набора значений, разрешающего только прочтение, но не правку. Он позволяет узнать всевозможные сведения о сеансе, полезные для прикладного программирования. Ранее в Oracle существовала одноименная функция, но сейчас она поддерживается ради старых программ.

Пример информации, которую можно получить из контекста USERENV в программу:

Полный список атрибутов контекста USERENV можно узнать из документации (в справочнике по SQL, в разделе, посвященному функции SYS_CONTEXT). Вот пример того, как сведения из этого контекста помогают различить разные условия употребления конкретной программы:

Перетранслируем процедуру для работы с правами запускающего:

Примеры поясняют отличие атрибутов CURRENT_SCHEMA и CURRENT_USER контекста USERENV друг от друга и от системной переменной USER.

Готовый изменяемый контекст сеанса CLIENTCONTEXT

  • либо через библиотеку OCI с помощью специального вызова OCIAppCtxSet
  • либо из программы на Java с помощью методов класса oracle.jdbc.internal.OracleConnection.

Тем самым контекст CLIENTCONTEXT способен при открытии сеанса передать информацию, дополнительную к традиционному имени пользователя и к ограниченному кругу сведений (адрес IP клиента, имя компьютера и пр.), доступному из контекста USERENV.

Значения переданных в сеанс атрибутов контекста CLIENTCONTEXT можно читать как обычно функцией SYS_CONTEXT, и изменять, но можно заводить и новые атрибуты:

Если бы атрибут A был установлен клиентской программой на C или на Java перед установлением соединения, значение B мы бы увидели сразу.

Создаются контексты SQL-предложением CREATE CONTEXT. Из-за этого далее вместо «пространства имен» предпочтение отдается термину «контекст». Параметры контекста («атрибуты») устанавливаются процедурой DBMS_SESSION.SET_CONTEXT, а вот вычитываются в программу стандартной функцией SYS_CONTEXT. Пакет DBMS_SESSION содержит ряд других подпрограмм для работы с контекстами.

Здесь рассматривается лишь формальная сторона контекста сеанса, а способ его применения разработчик может определить сам или почерпнуть из описаний избирательного доступа к данным (FGAC или Label Security) и сервера приложений Oracle.

Готовый справочный контекст сеанса USERENV

Один контекст, с названием USERENV, создавать явным образом не требуется. Он доступен любому сеансу связи с СУБД Oracle в виде готового набора значений, разрешающего только прочтение, но не правку. Он позволяет узнать всевозможные сведения о сеансе, полезные для прикладного программирования. Ранее в Oracle существовала одноименная функция, но сейчас она поддерживается ради старых программ.

Пример информации, которую можно получить из контекста USERENV в программу:

Полный список атрибутов контекста USERENV можно узнать из документации (в справочнике по SQL, в разделе, посвященному функции SYS_CONTEXT). Вот пример того, как сведения из этого контекста помогают различить разные условия употребления конкретной программы:

Перетранслируем процедуру для работы с правами запускающего:

Примеры поясняют отличие атрибутов CURRENT_SCHEMA и CURRENT_USER контекста USERENV друг от друга и от системной переменной USER.

Готовый изменяемый контекст сеанса CLIENTCONTEXT

  • либо через библиотеку OCI с помощью специального вызова OCIAppCtxSet
  • либо из программы на Java с помощью методов класса oracle.jdbc.internal.OracleConnection.

Тем самым контекст CLIENTCONTEXT способен при открытии сеанса передать информацию, дополнительную к традиционному имени пользователя и к ограниченному кругу сведений (адрес IP клиента, имя компьютера и пр.), доступному из контекста USERENV.

Значения переданных в сеанс атрибутов контекста CLIENTCONTEXT можно читать как обычно функцией SYS_CONTEXT, и изменять, но можно заводить и новые атрибуты:

Если бы атрибут A был установлен клиентской программой на C или на Java перед установлением соединения, значение B мы бы увидели сразу.

Читайте также: