INT-2865: Stored Procedure SqlReturnType support

JIRA: https://jira.springsource.org/browse/INT-2865
This commit is contained in:
Artem Bilan
2013-05-12 21:00:39 +03:00
committed by Gunnar Hillert
parent 3154173d23
commit 7d96dc7969
14 changed files with 364 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import org.w3c.dom.Element;
/**
* @author Gunnar Hillert
* @author Artem Bilan
* @since 2.1
*/
public final class StoredProcParserUtils {
@@ -68,6 +69,18 @@ public final class StoredProcParserUtils {
String sqlType = childElement.getAttribute("type");
String direction = childElement.getAttribute("direction");
String scale = childElement.getAttribute("scale");
String typeName = childElement.getAttribute("type-name");
String returnType = childElement.getAttribute("return-type");
if (StringUtils.hasText(typeName) && StringUtils.hasText(scale)) {
parserContext.getReaderContext().error("'type-name' and 'scale' attributes are mutually exclusive " +
"for 'sql-parameter-definition' element.", storedProcComponent);
}
if (StringUtils.hasText(returnType) && StringUtils.hasText(scale)) {
parserContext.getReaderContext().error("'returnType' and 'scale' attributes are mutually exclusive " +
"for 'sql-parameter-definition' element.", storedProcComponent);
}
final BeanDefinitionBuilder parameterBuilder;
@@ -79,6 +92,10 @@ public final class StoredProcParserUtils {
}
else {
parameterBuilder = BeanDefinitionBuilder.genericBeanDefinition(SqlParameter.class);
if (StringUtils.hasText(returnType)) {
parserContext.getReaderContext().error("'return-type' attribute can't be provided " +
"for IN 'sql-parameter-definition' element.", storedProcComponent);
}
}
if (StringUtils.hasText(name)) {
@@ -105,9 +122,19 @@ public final class StoredProcParserUtils {
parameterBuilder.addConstructorArgValue(Types.VARCHAR);
}
if (StringUtils.hasText(scale)) {
if (StringUtils.hasText(typeName)) {
parameterBuilder.addConstructorArgValue(typeName);
}
else if (StringUtils.hasText(scale)) {
parameterBuilder.addConstructorArgValue(new TypedStringValue(scale, Integer.class));
}
else {
parameterBuilder.addConstructorArgValue(null);
}
if (StringUtils.hasText(returnType)) {
parameterBuilder.addConstructorArgReference(returnType);
}
sqlParameterList.add(parameterBuilder.getBeanDefinition());
}
@@ -188,16 +215,6 @@ public final class StoredProcParserUtils {
String name = childElement.getAttribute("name");
String rowMapperAsString = childElement.getAttribute("row-mapper");
if (!StringUtils.hasText(name)) {
parserContext.getReaderContext().error(
"The 'name' attribute must be set for the 'returning-resultset' element.", storedProcComponent);
}
if (!StringUtils.hasText(rowMapperAsString)) {
parserContext.getReaderContext().error(
"The 'row-mapper' attribute must be set for the 'returning-resultset' element.", storedProcComponent);
}
BeanDefinitionBuilder rowMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(rowMapperAsString);
returningResultsetMap.put(name, rowMapperBuilder.getBeanDefinition());

View File

@@ -1296,12 +1296,32 @@
<xsd:union memberTypes="sqlType xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="scale" type="xsd:integer" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The scale of the Sql parameter. Only used for numeric and decimal
parameters.
]]></xsd:documentation>
<xsd:attribute name="scale" type="xsd:integer" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The scale of the Sql parameter. Only used for numeric and decimal
parameters.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="type-name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Used for types that are user-named like:
STRUCT, DISTINCT, JAVA_OBJECT, named array types.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="return-type" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Reference to a custom value handler for complex type.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.jdbc.core.SqlReturnType" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>

View File

@@ -38,8 +38,31 @@
<int:channel id="outputChannel"/>
<int:service-activator id="consumerEndpoint" input-channel="outputChannel" ref="consumer" />
<bean id="consumer" class="org.springframework.integration.jdbc.StoredProcOutboundGatewayWithSpelIntegrationTests$Consumer"/>
<int:logging-channel-adapter channel="errorChannel" log-full-message="true"/>
<int:channel id="output2Channel">
<int:queue/>
</int:channel>
<int-jdbc:stored-proc-outbound-gateway request-channel="getMessageChannel"
data-source="dataSource"
stored-procedure-name="GET_MESSAGE"
ignore-column-meta-data="true"
expect-single-result="true"
reply-channel="output2Channel">
<int-jdbc:sql-parameter-definition name="message_id"/>
<int-jdbc:sql-parameter-definition name="message_json" type="CLOB" direction="OUT" type-name="" return-type="clobSqlReturnType"/>
<int-jdbc:parameter name="message_id" expression="payload"/>
</int-jdbc:stored-proc-outbound-gateway>
<bean id="clobSqlReturnType" class="org.mockito.Mockito" factory-method="spy">
<constructor-arg>
<bean class="org.springframework.integration.jdbc.storedproc.ClobSqlReturnType"/>
</constructor-arg>
</bean>
</beans>

View File

@@ -16,9 +16,11 @@
package org.springframework.integration.jdbc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.sql.CallableStatement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -31,6 +33,7 @@ import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.AbstractApplicationContext;
@@ -38,14 +41,23 @@ import org.springframework.integration.Message;
import org.springframework.integration.MessageHandlingException;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.jdbc.config.JdbcTypesEnum;
import org.springframework.integration.jdbc.storedproc.User;
import org.springframework.integration.json.JsonInboundMessageMapper;
import org.springframework.integration.json.JsonOutboundMessageMapper;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SqlReturnType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Gunnar Hillert
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@@ -61,6 +73,18 @@ public class StoredProcOutboundGatewayWithSpelIntegrationTests {
@Qualifier("startChannel")
DirectChannel channel;
@Autowired
DirectChannel getMessageChannel;
@Autowired
PollableChannel output2Channel;
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
SqlReturnType clobSqlReturnType;
@Test
@DirtiesContext
public void executeStoredProcedureWithMessageHeader() throws Exception {
@@ -121,6 +145,27 @@ public class StoredProcOutboundGatewayWithSpelIntegrationTests {
}
@Test
@Transactional
public void testInt2865SqlReturnType() throws Exception {
Message<String> testMessage = MessageBuilder.withPayload("TEST").setHeader("FOO", "BAR").build();
String messageId = testMessage.getHeaders().getId().toString();
String jsonMessage = new JsonOutboundMessageMapper().fromMessage(testMessage);
this.jdbcTemplate.update("INSERT INTO json_message VALUES (?,?)", messageId, jsonMessage);
this.getMessageChannel.send(new GenericMessage<String>(messageId));
Message<?> resultMessage = this.output2Channel.receive(1000);
assertNotNull(resultMessage);
Object resultPayload = resultMessage.getPayload();
assertTrue(resultPayload instanceof String);
Message<?> message = new JsonInboundMessageMapper(String.class).toMessage((String) resultPayload);
assertEquals(testMessage.getPayload(), message.getPayload());
assertEquals(testMessage.getHeaders().get("FOO"), message.getHeaders().get("FOO"));
Mockito.verify(clobSqlReturnType).getTypeValue(Mockito.any(CallableStatement.class),
Mockito.eq(2), Mockito.eq(JdbcTypesEnum.CLOB.getCode()), Mockito.eq((String) null));
}
static class Counter {
private final AtomicInteger count = new AtomicInteger();

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.integration.jdbc.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.sql.Types;
import java.util.List;
import java.util.Properties;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.expression.Expression;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jdbc.storedproc.ProcedureParameter;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jdbc.core.SqlInOutParameter;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
/**
* @author Artem Bilan
* @since 3.0
*/
public class StoredProcInvalidConfigsTests {
@Test
public void testProcedureNameAndExpressionExclusivity() throws Exception {
try {
this.bootStrap("nameAndExpressionExclusivity");
fail("Expected a BeanDefinitionParsingException to be thrown.");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().contains("Exactly one of 'stored-procedure-name' or 'stored-procedure-name-expression' is required"));
}
}
@Test
public void testReturnTypeForInParameter() throws Exception {
try {
this.bootStrap("returnTypeForInParameter");
fail("Expected a BeanDefinitionParsingException to be thrown.");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().contains("'return-type' attribute can't be provided for IN 'sql-parameter-definition' element."));
}
}
@Test
public void testTypeNameAndScaleExclusivity() throws Exception {
try {
this.bootStrap("typeNameAndScaleExclusivity");
fail("Expected a BeanDefinitionParsingException to be thrown.");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().contains("'type-name' and 'scale' attributes are mutually exclusive " +
"for 'sql-parameter-definition' element."));
}
}
@Test
public void testReturnTypeAndScaleExclusivity() throws Exception {
try {
this.bootStrap("returnTypeAndScaleExclusivity");
fail("Expected a BeanDefinitionParsingException to be thrown.");
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().contains("'returnType' and 'scale' attributes are mutually exclusive " +
"for 'sql-parameter-definition' element."));
}
}
private ApplicationContext bootStrap(String configProperty) throws Exception {
PropertiesFactoryBean pfb = new PropertiesFactoryBean();
pfb.setLocation(new ClassPathResource("org/springframework/integration/jdbc/config/stored-proc-invalid-configs.properties"));
pfb.afterPropertiesSet();
Properties prop = pfb.getObject();
StringBuilder buffer = new StringBuilder();
buffer.append(prop.getProperty("xmlheaders")).append(prop.getProperty(configProperty)).append(prop.getProperty("xmlfooter"));
ByteArrayInputStream stream = new ByteArrayInputStream(buffer.toString().getBytes());
GenericApplicationContext ac = new GenericApplicationContext();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ac);
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
reader.loadBeanDefinitions(new InputStreamResource(stream));
ac.refresh();
return ac;
}
}

View File

@@ -0,0 +1,26 @@
xmlheaders=\
<?xml version="1.0" encoding="UTF-8"?> \
<beans xmlns="http://www.springframework.org/schema/beans" \
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc" \
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd \
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
xmlfooter= </beans>
nameAndExpressionExclusivity=<int-jdbc:stored-proc-outbound-gateway request-channel="requestChannel" data-source="dataSource" \
stored-procedure-name="FOO" stored-procedure-name-expression="'FOO'" />
returnTypeForInParameter=<int-jdbc:stored-proc-outbound-gateway request-channel="requestChannel" data-source="dataSource" \
stored-procedure-name="FOO" >\
<int-jdbc:sql-parameter-definition name="foo" return-type="fooReturnType"/>\
</int-jdbc:stored-proc-outbound-gateway>
typeNameAndScaleExclusivity=<int-jdbc:stored-proc-outbound-gateway request-channel="requestChannel" data-source="dataSource" \
stored-procedure-name="FOO" >\
<int-jdbc:sql-parameter-definition name="foo" type-name="FOO" scale="5"/>\
</int-jdbc:stored-proc-outbound-gateway>
returnTypeAndScaleExclusivity=<int-jdbc:stored-proc-outbound-gateway request-channel="requestChannel" data-source="dataSource" \
stored-procedure-name="FOO" >\
<int-jdbc:sql-parameter-definition name="foo" scale="5" return-type="fooReturnType" />\
</int-jdbc:stored-proc-outbound-gateway>

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.jdbc.storedproc;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.SQLException;
import org.springframework.jdbc.core.SqlReturnType;
/**
* @author Artem Bilan
* @since 3.0
*/
public class ClobSqlReturnType implements SqlReturnType {
@Override
public Object getTypeValue(CallableStatement cs, int paramIndex, int sqlType, String typeName) throws SQLException {
Clob clob = cs.getClob(paramIndex);
return clob != null ? clob.getSubString(1, (int) clob.length()) : null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.integration.jdbc.storedproc.derby;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@@ -26,6 +27,7 @@ import org.springframework.jdbc.support.JdbcUtils;
/**
*
* @author Gunnar Hillert
* @author Artem Bilan
*
*/
public final class DerbyStoredProcedures {
@@ -74,4 +76,16 @@ public final class DerbyStoredProcedures {
}
public static void getMessage(String messageId, Clob[] returnedData) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:default:connection");
PreparedStatement stmt = conn.prepareStatement("select MESSAGE_JSON from JSON_MESSAGE where MESSAGE_ID = ?");
stmt.setString( 1, messageId);
ResultSet results = stmt.executeQuery();
if (results.next()) {
returnedData[0] = results.getClob(1);
}
}
}

View File

@@ -2,3 +2,5 @@ DROP FUNCTION CONVERT_STRING_TO_UPPER_CASE;
DROP TABLE USERS;
DROP PROCEDURE CREATE_USER;
DROP PROCEDURE CREATE_USER_RETURN_ALL;
DROP TABLE JSON_MESSAGE;
DROP PROCEDURE GET_MESSAGE;

View File

@@ -1,4 +1,6 @@
CREATE FUNCTION CONVERT_STRING_TO_UPPER_CASE (invalue VARCHAR(50)) RETURNS VARCHAR(50) PARAMETER STYLE JAVA LANGUAGE JAVA EXTERNAL NAME 'org.springframework.integration.jdbc.storedproc.derby.DerbyFunctions.convertStringToUpperCase';
create table USERS(USERNAME varchar(100),PASSWORD varchar(100), EMAIL varchar(100));
CREATE PROCEDURE CREATE_USER( IN username VARCHAR(100), IN password VARCHAR(100), IN email VARCHAR(100) ) PARAMETER STYLE JAVA LANGUAGE JAVA EXTERNAL NAME 'org.springframework.integration.jdbc.storedproc.derby.DerbyStoredProcedures.createUser';
CREATE PROCEDURE CREATE_USER_RETURN_ALL(IN username VARCHAR(100), IN password VARCHAR(100), IN email VARCHAR(100)) PARAMETER STYLE JAVA LANGUAGE JAVA MODIFIES SQL DATA DYNAMIC RESULT SETS 1 EXTERNAL NAME 'org.springframework.integration.jdbc.storedproc.derby.DerbyStoredProcedures.createUserAndReturnAll';
CREATE PROCEDURE CREATE_USER_RETURN_ALL(IN username VARCHAR(100), IN password VARCHAR(100), IN email VARCHAR(100)) PARAMETER STYLE JAVA LANGUAGE JAVA MODIFIES SQL DATA DYNAMIC RESULT SETS 1 EXTERNAL NAME 'org.springframework.integration.jdbc.storedproc.derby.DerbyStoredProcedures.createUserAndReturnAll';
create table JSON_MESSAGE(MESSAGE_ID CHAR(36), MESSAGE_JSON CLOB);
CREATE PROCEDURE GET_MESSAGE(IN MESSAGE_ID CHAR(36), OUT MESSAGE_JSON CLOB) PARAMETER STYLE JAVA LANGUAGE JAVA EXTERNAL NAME 'org.springframework.integration.jdbc.storedproc.derby.DerbyStoredProcedures.getMessage';

View File

@@ -1,4 +0,0 @@
DROP FUNCTION CONVERT_STRING_TO_UPPER_CASE;
DROP TABLE USERS;
DROP PROCEDURE CREATE_USER;
DROP PROCEDURE CREATE_USER_RETURN_ALL;

View File

@@ -810,10 +810,13 @@
attribute.
</para>
<programlisting language="xml"><![CDATA[<int-jdbc:sql-parameter-definition name="" ]]><co id="sp-parameter-definition-xml01-co" linkends="sp-parameter-definition-xml01" /><![CDATA[
direction="IN" ]]><co id="sp-parameter-definition-xml02-co" linkends="sp-parameter-definition-xml02" /><![CDATA[
type="STRING" ]]><co id="sp-parameter-definition-xml03-co" linkends="sp-parameter-definition-xml03" /><![CDATA[
scale=""/> ]]><co id="sp-parameter-definition-xml04-co" linkends="sp-parameter-definition-xml04" /></programlisting>
<programlisting language="xml"><![CDATA[<int-jdbc:sql-parameter-definition
name="" ]]><co id="sp-parameter-definition-xml01-co" linkends="sp-parameter-definition-xml01" /><![CDATA[
direction="IN" ]]><co id="sp-parameter-definition-xml02-co" linkends="sp-parameter-definition-xml02" /><![CDATA[
type="STRING" ]]><co id="sp-parameter-definition-xml03-co" linkends="sp-parameter-definition-xml03" /><![CDATA[
scale="5" ]]><co id="sp-parameter-definition-xml04-co" linkends="sp-parameter-definition-xml04" /><![CDATA[
type-name="FOO_STRUCT" ]]><co id="sp-parameter-definition-xml05-co" linkends="sp-parameter-definition-xml05" /><![CDATA[
return-type="fooSqlReturnType"/> ]]><co id="sp-parameter-definition-xml06-co" linkends="sp-parameter-definition-xml06" /></programlisting>
<para>
<calloutlist>
<callout arearefs="sp-parameter-definition-xml01-co" id="sp-parameter-definition-xml01">
@@ -850,6 +853,23 @@
<emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="sp-parameter-definition-xml05-co" id="sp-parameter-definition-xml05">
<para>
The typeName for types that are user-named like: STRUCT, DISTINCT, JAVA_OBJECT, named array types.
This attribute is mutually exclusive with the <emphasis>scale</emphasis> attribute.
<emphasis>Optional</emphasis>.
</para>
</callout>
<callout arearefs="sp-parameter-definition-xml06-co" id="sp-parameter-definition-xml06">
<para>
The reference to a custom value handler for complex types. An implementation of
<ulink url="http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/jdbc/core/SqlReturnType.html"
>SqlReturnType</ulink>.
This attribute is mutually exclusive with the <emphasis>scale</emphasis> attribute
and is applicable for OUT(INOUT)-parameters only.
<emphasis>Optional</emphasis>.
</para>
</callout>
</calloutlist>
</para>

View File

@@ -191,6 +191,21 @@
For more information see <xref linkend="chain"/>.
</para>
</section>
<section id="3.0-stored-proc-sql-return-type">
<title>SqlReturnType support for Stored Procedure components</title>
<para>
For more complex database-specific types, not supported by the standard
<code>CallableStatement.getObject</code> method, 2 new additional
attributes were introduced to the <code>&lt;sql-parameter-definition/&gt;</code>
element with OUT-direction:
</para>
<itemizedlist>
<listitem><emphasis>type-name</emphasis></listitem>
<listitem><emphasis>return-type</emphasis></listitem>
</itemizedlist>
<para>
For more information see <xref linkend="stored-procedures"/>.
</para>
</section>
</section>
</chapter>