2 мая 2015 г.

Новые возможности в динамическом SQL в Oracle 12c (передача переменных PL/SQL типов)

И, наконец, последняя новая возможность в динамическом SQL, которая появилась в Oracle Database 12c.

Последняя, не по значимости, а по порядку изложения!
Стоит отметить, что она имеет очень большую практическую ценность, - и, по большому счету, ее очень не хватало.

Существенным ограничением до Oracle Database 12c являлась невозможность передачи в динамический код переменных, которые имеют PL/SQL-типы.
Если рассмотреть нижеприведенный анонимный блок, выполненный в среде Oracle Database 11g (11.2.0.4):
[oracle@localhost]$ sqlplus rscott/rtiger

SQL*Plus: Release 11.2.0.4.0 Production on Sun May 3 00:07:27 2015

Copyright (c) 1982, 2013, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> DECLARE
  b BOOLEAN;
BEGIN
  EXECUTE IMMEDIATE
    'begin :x := true; end;'
  USING
    OUT b;
END;
/
    OUT b;
        *
ERROR at line 7:
ORA-06550: line 7, column 9:
PLS-00457: expressions have to be of SQL types
ORA-06550: line 4, column 3:
PL/SQL: Statement ignored

SQL> 
можно увидеть, что ошибка при передаче в динамический блок кода возникает даже когда переменная типа BOOLEAN передается именно в PL/SQL-блок.

Очевидно, что исполняющая среда PL/SQL предполагает, что исполняться будет именно SQL-выражение.

Для того, чтобы обойти это ограничение, приходилось применять вот примерно такой дорогостоящий workaround, связанный с преобразованиями значения из PL/SQL-типа в SQL-тип, и затем обратно:

SQL> create function CharToBoolean(v_pValue in char) return boolean is
begin
  if v_pValue = 'Y' then
    return true;
  end if;

  return false;
end;
/

Function created.

SQL> create function BooleanToChar(v_pValue in boolean) return char is
begin
  if v_pValue then
    return 'Y';
  end if;

  return 'N';
end;
/

Function created.

SQL> DECLARE
  b BOOLEAN;
  s char(1);
BEGIN
  EXECUTE IMMEDIATE
    'begin :x := BooleanToChar(true); end;'
  USING
    OUT s;

  b := CharToBoolean(s);
END;
/
PL/SQL procedure successfully completed.

SQL> 
Конечно-же, это усложняло понимание кода и дальнейшее его сопровождение.

Наконец-то, это ограничение снято в Oracle Database 12c, и приведеный выше первоначальный код теперь выполняется без ошибок:

[oracle@localhost Desktop]$ sqlplus / as sysdba

SQL*Plus: Release 12.1.0.2.0 Production on Sun May 3 00:10:28 2015

Copyright (c) 1982, 2014, Oracle.  All rights reserved.

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options

SQL> DECLARE
  b BOOLEAN;
BEGIN
  EXECUTE IMMEDIATE
    'begin :x := true; end;'
  USING
    OUT b;
END;
/

PL/SQL procedure successfully completed.

SQL> 
Причем теперь, стало возможным передавать в динамический блок кода не только переменные типа BOOLEAN, но также PL/SQL-записи и PL/SQL-массивы:
SQL> create or replace package Account_service is
  type
    TArrayOfVarchar2 is table of Varchar2(32) index by pls_integer;

end;
/
Package created.

set serveroutput on
DECLARE
  v_xArray Account_service.TArrayOfVarchar2;
BEGIN
  v_xArray(1) := 'First'; v_xArray(2) := 'Second';

  EXECUTE IMMEDIATE
    'begin
       for v_xIndex in 1..:v_xCount
       loop
         dbms_output.put_line(:v_pArray(v_xIndex));
       end loop;
     end;'
  USING
    IN v_xArray.Count,v_xArray;
END;
/

First
Second

PL/SQL procedure successfully completed.
 
SQL>
Также поддерживается ситуация, когда передача параметра, имеющего PL/SQL, производится в SQL-выражение:
SQL> create or replace function printArray(v_pArray in Account_service.TArrayOfVarchar2) return number is
begin
  for v_xIndex in 1..v_pArray.count
  loop
    dbms_output.put_line(v_pArray(v_xIndex));
  end loop;

  return 1;
end;
/

Function created.

SQL>DECLARE
  v_xArray Account_service.TArrayOfVarchar2;
  n        number; 
BEGIN
  v_xArray(1) := 'First'; v_xArray(2) := 'Second';

  EXECUTE IMMEDIATE
    'SELECT printArray(:v_pArray) FROM dual'
  INTO 
    n
  USING
    IN v_xArray;
END;
/

First
Second

PL/SQL procedure successfully completed.

SQL>
К сожалению, хэш-массивы (PL/SQL-массивы c INDEX BY VARCHAR2) пока не поддерживаются.

И необходимо помнить, что динамический блок кода не является замыканием (closure), то есть используемый тип должен быть определен на уровне всей схемы - в спецификации пакета.

Поддержка передачи (и возвращения тоже!) в динамический SQL переменных имеющих PL/SQL-типы - это новая впечатляющая возможность Oracle Database 12c, которая позволяет упростить Ваш код, тем самым сделать его более читабельным и легким в сопровождении.