Skip to content

ORA-22922 in async LOB reading from cursor #500

Open
@klymenkosergiy

Description

@klymenkosergiy
  1. What versions are you using?
    Python 3.9.13 (Windows, Oracle Linux 8)
    Database: Oracle 19c
    oracledb: 3.1.0, 3.1.1

  2. Is it an error or a hang or a crash?
    DatabaseError with ORA-22922

  3. What error(s) or behavior you are seeing?
    I have observed the error "ORA-22922: nonexistent LOB value" after reading around 100 LOB objects. If lobAutoload is configured (refCursor.outputtypehandler = _connection_lobautoload_handler) I fetched the async cursor well.

I noticed that the issue is present only for the async version in oracledb module, sync version works well, my old code based on cx_Oracle + cx_Oracle_async also works well with this case.

  1. Does your application call init_oracle_client()?
    no

  2. Include a runnable Python script that shows the problem.

create or replace package pkg_test1 is

function encode(p_prefix nvarchar2, p_idx number) return blob;

function test return sys_refcursor;

end pkg_test1;
/
create or replace package body pkg_test1 is

gServerCodepage v$nls_parameters.value%type;

function encode(p_prefix nvarchar2, p_idx number) return blob
as
l_value nvarchar2(2000);
l_blob blob;
l_raw long raw;
begin
  l_value := p_prefix || '-' || to_char(p_idx);
  
  dbms_lob.createtemporary(l_blob, true, dbms_lob.call);
  
  if gServerCodepage != 'AL32UTF8' then
    l_raw := utl_raw.cast_to_raw(convert(l_value, 'AL32UTF8', gServerCodepage));
  else
    l_raw := utl_raw.cast_to_raw(l_value);
  end if;
  
  dbms_lob.writeappend(l_blob, utl_raw.length(l_raw), l_raw);
  return l_blob;
end encode;  

function test return sys_refcursor
as
l_cur sys_refcursor;
begin
  open l_cur for
    SELECT rownum                        id,
           pkg_test1.encode('col1', rownum) blob1,
           pkg_test1.encode('col2', rownum) blob2,
           pkg_test1.encode('col3', rownum) blob3,
           pkg_test1.encode('col4', rownum) blob4,
           pkg_test1.encode('col5', rownum) blob5,
           pkg_test1.encode('col6', rownum) blob6,
           pkg_test1.encode('col7', rownum) blob7,
           pkg_test1.encode('col8', rownum) blob8,
           pkg_test1.encode('col9', rownum) blob9,
           pkg_test1.encode('col10', rownum) blob10,
           pkg_test1.encode('col11', rownum) blob11,
           pkg_test1.encode('col12', rownum) blob12                                                                                        
    FROM dual
    --CONNECT BY LEVEL <= 1000;    
    CONNECT BY LEVEL <= 25;    
 return l_cur;   
end test;

begin
  select 
   (select p.value from v$nls_parameters p where p.PARAMETER = 'NLS_NCHAR_CHARACTERSET')
   into gServerCodepage
  from dual; 
end pkg_test1;
/


import oracledb
import asyncio

def _connection_lobautoload_handler(cursor, metadata):
	""" Handler to configure cursor types in connection """
	if metadata.type_code is oracledb.DB_TYPE_CLOB:
		return cursor.var(oracledb.DB_TYPE_LONG, arraysize=max(cursor.arraysize, 4096))
	if metadata.type_code is oracledb.DB_TYPE_BLOB:
		return cursor.var(oracledb.DB_TYPE_LONG_RAW, arraysize=max(cursor.arraysize, 4096))
	if metadata.type_code is oracledb.DB_TYPE_NCLOB:
		return cursor.var(oracledb.DB_TYPE_LONG_NVARCHAR, arraysize=max(cursor.arraysize, 4096))

def main():
	pool = oracledb.create_pool(
			user = "user",
			password ="password",
			dsn = "connection-string"
			)
	con = pool.acquire()
	try:
		with con.cursor() as cur, con.cursor() as refCursor:
			cur.execute(
					""" begin :cur := pkg_test1.test; end;""",
					cur = refCursor)

			rowCount = 0
			while True:
				row = refCursor.fetchone()
				if row == None: break
				for idx in range(0, len(refCursor.description)):
					if refCursor.description[idx][0].startswith('BLOB'):
						value = (row[idx].read()).decode('utf-8') if row[idx] != None and isinstance(row[idx], oracledb.LOB) else row[idx]
						print(f'{rowCount}: {refCursor.description[idx][0]}={value}')
				rowCount += 1
	finally:
		con.close()

async def mainAsync():
	pool = oracledb.create_pool_async(
			user = "user",
			password ="password",
			dsn = "connection-string"
			)
	con = await pool.acquire()
	try:
		async with con.cursor() as cur, con.cursor() as refCursor:
			#refCursor.outputtypehandler = _connection_lobautoload_handler
			await cur.execute(
					""" begin :cur := pkg_test1.test; end;""",
					cur = refCursor)

			rowCount = 0
			while True:
				row = await refCursor.fetchone()
				if row == None: break
				for idx in range(0, len(refCursor.description)):
					if refCursor.description[idx][0].startswith('BLOB'):
						value = (await row[idx].read()).decode('utf-8') if row[idx] != None and isinstance(row[idx], oracledb.AsyncLOB) else row[idx]
						print(f'{rowCount}: {refCursor.description[idx][0]}={value}')
				rowCount += 1
	finally:
		await con.close()

if __name__ == '__main__':
	print ('Test case#1: oracledb (sync mode).')
	main()
	print ('Test case#2: oracledb (async mode).')
	asyncio.run(mainAsync())

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions