Странная ошибка "Ora-01001 недопустимый курсор" в процедуре

Вчера я работал над странной ошибкой в нашей производственной процедуре. Не удалось выполнить инструкцию

if v_cursor%isopen then
  close v_cursor; -- here was an error 
end if;

после некоторого копания я обнаружил, что проблема была в подпрограмму, которая открыла этот курсор. Я исправил ошибку, добавив выходной параметр sys_refcursor в подпрограмму. Для прояснения ситуации рассмотрим следующий тестовый код:

procedure nested_test(test  number,
                        p_cur out sys_refcursor)
  is  
    procedure nested_procedure_fail is
    begin      
      open p_cur for
        select 1, 2, 3, 4
          from dual
         where 1 = 0;
    end;

    procedure nested_procedure_success(p_cur out sys_refcursor) is
    begin
      open p_cur for
        select 1, 2, 3, 4
          from dual
         where 1 = 0;
    end;

  begin
    if test = 1 then
      nested_procedure_fail;
    else
      if test = 2 then
        nested_procedure_success(p_cur => p_cur);
      else
        open p_cur for
          select 6, 7, 8, 9
            from dual
           where 1 = 1;
      end if;
    end if;
  end;

  procedure test_fail is
    v_cur sys_refcursor;
  begin
    nested_test(test => 1, p_cur => v_cur);
    if v_cur%isopen then
      close v_cur;
    end if;
  end;

  procedure test_success is
    v_cur sys_refcursor;
  begin
    nested_test(test => 2, p_cur => v_cur);
    if v_cur%isopen then
      close v_cur;
    end if;
  end;

если я попытаюсь бежать test_success все в порядке, но на test_fail Я получаю сообщение

в Ora-01001: Неверный курсор

Я не могу найти никакой информации об этом. Может кто-нибудь объяснить, почему этот код не выполняется?

Oracle версии:

Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE    11.2.0.3.0  Production
TNS for Solaris: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production

2 ответов


это похоже на ошибку 7174888 или, по крайней мере, что-то тесно связанное с ней. Описание для этого- "ORA-6504, поднятый, когда sys_refcursor передан другой процедуре", но я могу сделать это, если я изменю test_fail сделать выборку:

  procedure test_fail is
    v_cur sys_refcursor;
    a number;
    b number;
    c number;
    d number;
  begin
    nested_test(test => 1, p_cur => v_cur);
    if v_cur%isopen then
      fetch v_cur into a,b,c,d;
      close v_cur;
    end if;
  end;

Я ORA-06504: PL/SQL: Return types of Result Set variables or query do not match.

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

инициализируйте курсор ref до ненулевого значения на самом высоком уровне на что это будет быть доступны

  begin
    /* Dummy open to avoid bug 7174888 */
    open v_cur for 'select 1 from dual';
    nested_test(test => 1, p_cur => v_cur);
    if v_cur%isopen then
      fetch v_cur into a,b,c,d;
      close v_cur;
    end if;
  end;

интересный вопрос! Просто хотел кое-что добавить.

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

для например, нельзя использовать переменные курсора в удаленных вызовах процедур (через dblinks). Этот же пример, даже используя обходной путь Алекса, потерпит неудачу, если open был вызван на 1 экземпляре db, а fetch на другом (если nested_test, любая версия, была определена на db_A, а затем вызвана из db_B). Однако тест для ISOPEN все равно вернет TRUE, но тогда попытка использовать курсор (выборка) завершится неудачей.

INVALID_CURSOR может быть поднят по другим причинам (например, выход за пределы max open курсоры, или иногда открытие курсора и ожидание некоторое время, прежде чем пытаться его использовать).

все, что сказано, нет теста" ISVALID", о котором я знаю. Лучшим подходом imo является открытие, извлечение и закрытие курсоров в рамках одной программы или подпрограммы. Создание процедуры, ответственность которой заключается в том, чтобы просто открыть курсор, немного странно для меня (но я уверен, что была какая-то причина), и может вызвать трудности с объяснением проблем (например, этот). Если вам нужна другая программа, откройте курсор, затем вы можете заключить код, который извлекает и в конечном итоге закрывает курсор в анонимном блоке, и поймать исключение INVALID_CURSOR.

просто мой бред ;-)