Reputation: 167972
Given an Oracle stored procedure:
CREATE TYPE stringlist AS TABLE OF VARCHAR2(100);
/
CREATE PROCEDURE test_proc(
list IN stringlist,
output OUT VARCHAR2
)
AS
BEGIN
IF list IS NULL OR list IS EMPTY THEN
RETURN;
END IF;
output := list(1);
FOR i IN 2 .. list.COUNT LOOP
output := output || ',' || list(i);
END LOOP;
END;
/
How can I call this from ColdFusion?
<cfscript>
arr = [ 'A', 'B', 'C' ];
sp = new StoredProc(
dataSource = "orcl",
procedure = "test_proc",
result = "NA",
parameters = [
{ cfsqltype = "CF_SQL_ARRAY", type="in", value = arr },
{ cfsqltype = "CF_SQL_VARCHAR", type="out", variable = "out" }
]
).execute();
// WriteDump( sp.getProcOutVariables().out );
</cfscript>
Fails with:
Error Executing Database Query
Fail to convert to internal representation: [A, B, C]
Upvotes: 3
Views: 745
Reputation: 12485
I was playing around with this and I found that once the Oracle array is properly created (which involves creating a connection, as above, and creating an Oracle array of the appropriate type (in this case, STRINGLIST
) from the original array, then one can use <cfstoredproc>
(and, I assume, <cfquery>
) with a parameter of type CF_SQL_ARRAY
in order to execute the stored procedure (or query):
<cfset the_datasource = "oratest" />
<cfset the_array = javaCast("string[]", ["A","B","C"]) />
<cfset return_value = "" />
<cftry>
<cfset the_connection = createObject("java", "coldfusion.server.ServiceFactory")
.getDataSourceService()
.getDataSource("#the_datasource#")
.getConnection()
.getPhysicalConnection()
/>
<!---
<cfset type_desc = createObject("java", "oracle.sql.ArrayDescriptor").createDescriptor("STRINGLIST", the_connection) />
<cfset oracle_array = createObject("java", "oracle.sql.ARRAY").init(type_desc, the_connection, the_array) />
--->
<!--- oracle.SQL.ARRAY is deprecated; use this instead: --->
<cfset oracle_array = the_connection.createOracleArray("STRINGLIST", the_array) />
<cfset the_connection.close() />
<cfstoredproc procedure="test_proc" datasource="#the_datasource#">
<cfprocparam cfsqltype="CF_SQL_ARRAY" type="in" value="#oracle_array#" />
<cfprocparam cfsqltype="CF_SQL_VARCHAR" type="out" variable="return_value" />
</cfstoredproc>
<cfcatch>
<cfdump var="#cfcatch#" />
</cfcatch>
</cftry>
<cfdump var="#return_value#" />
Note that in the above code I open a connection to the database only to create the Oracle array. I haven't figured out yet whether I can try to use an existing connection or re-use the connection in the call to <cfstoredproc>
.
Hope this helps.
EDIT:
To pass the array to a query, you can simply do something like the following:
<cfquery name="get_table" datasource="#the_datasource#">
SELECT * FROM TABLE( <cfqueryparam cfsqltype="CF_SQL_ARRAY" value="#oracle_array#" /> )
</cfquery>
Upvotes: 2
Reputation: 167972
Firstly, set up a data source that uses the Oracle JDBC drivers. Download the appropriate JAR file and place it in the coldfusion instance's lib
directory and then, through the CFIDE administration panel, you can set up a data source like this:
CF Data Source Name: orcl
JDBC URL: jdbc:oracle:thin:@localhost:1521:orcl
Driver Class: oracle.jdbc.OracleDriver
Driver Name: Other
(Note: the driver name is "Other" not "Oracle" - which would use adobe's Oracle driver not the specified Oracle driver.)
Then you can invoke the stored procedure by dropping down to the raw Java rather than using <cfstoredproc>
or new StoredProc()
.
<cfscript>
array = JavaCast( "string[]", [ 'A', 'B', 'C' ] );
try {
connection = createObject( 'java', 'coldfusion.server.ServiceFactory' )
.getDataSourceService()
.getDataSource( 'orcl' )
.getConnection()
.getPhysicalConnection();
description = createObject( 'java', 'oracle.sql.ArrayDescriptor' )
.createDescriptor( 'STRINGLIST', connection );
oracleArray = createObject( 'java', 'oracle.sql.ARRAY' )
.init( description, connection, array );
statement = connection.prepareCall( '{call test_proc( :input, :output )}' );
statement.setARRAYAtName( "input", oracleArray );
stringType = createObject( 'java', 'java.sql.Types' ).VARCHAR;
statement.registerOutParameter( "output", stringType );
statement.executeQuery();
returnValue = statement.getString( "output" );
}
finally
{
if ( isDefined( "statement" ) )
statement.close();
if ( isDefined( "connection" ) )
connection.close();
}
</cfscript>
As an aside, you can also pass an array to a query (and then get a result you can use in a <cfloop>
) like this:
try {
// set-up connection, etc. as above
statement = connection.prepareStatement( 'SELECT * FROM TABLE( :input )' );
statement.setARRAYAtName( "input", oracleArray );
resultSet = statement.executeQuery();
queryResult = createObject( 'java', 'coldfusion.sql.QueryTable' )
.init( resultSet )
.FirstTable();
}
finally
{
if ( isDefined( "resultSet" ) )
resultSet.close();
if ( isDefined( "statement" ) )
statement.close();
if ( isDefined( "connection" ) )
connection.close();
}
Upvotes: 3