package org.exist.xquery.modules.sql; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.mock; import static org.easymock.EasyMock.replay; import static org.junit.Assert.assertEquals; import org.exist.EXistException; import org.exist.dom.QName; import org.exist.xquery.FunctionSignature; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.value.*; import org.junit.Test; import org.w3c.dom.Node; import java.sql.*; /** * Unit Tests for sql:execute */ public class ExecuteFunctionTest { // the function that will be tested final static QName functionName = new QName( "execute", SQLModule.NAMESPACE_URI, SQLModule.PREFIX ); @Test public void testStringEncoding() throws ClassNotFoundException, SQLException, EXistException, XPathException { // mocks a simple SQL query returning a single string and checks the result XQueryContext context = new XQueryContextStub(); ExecuteFunction execute = new ExecuteFunction(context, signatureByArity(ExecuteFunction.signatures, functionName, 3)); final String sql = "SELECT NAME FROM BLA"; final String testValue = "<&>"; // create mock objects Connection connection = mock(Connection.class); Statement stmt = mock(Statement.class); ResultSet rs = mock(ResultSet.class); ResultSetMetaData rsmd = mock(ResultSetMetaData.class); // mock behavior expect(connection.createStatement()).andReturn(stmt); expect(stmt.execute(sql)).andReturn(true); expect(stmt.getResultSet()).andReturn(rs); stmt.close(); expect(rs.getMetaData()).andReturn(rsmd); expect(rs.next()).andReturn(true).andReturn(false); expect(rs.getRow()).andReturn(1); expect(rs.getString(1)).andReturn(testValue); expect(rs.wasNull()).andReturn(false); rs.close(); expect(rsmd.getColumnCount()).andStubReturn(1); expect(rsmd.getColumnLabel(1)).andStubReturn("NAME"); expect(rsmd.getColumnTypeName(1)).andStubReturn("VARCHAR(100)"); expect(rsmd.getColumnType(1)).andStubReturn(Types.VARCHAR); replay(connection, stmt, rs, rsmd); // register mocked connection final long connId = SQLModule.storeConnection( context, connection ); // execute function Sequence res = execute.eval(new Sequence[] { new IntegerValue(connId), new StringValue(sql), new BooleanValue(false) }, Sequence.EMPTY_SEQUENCE); // assert expectations assertEquals(1, res.getItemCount()); assertEquals(Type.ELEMENT, res.getItemType()); Node root = ((NodeValue) res.itemAt(0)).getNode(); assertEquals("sql:result", root.getNodeName()); Node row = root.getFirstChild(); assertEquals("sql:row", row.getNodeName()); Node col = row.getFirstChild(); assertEquals("sql:field", col.getNodeName()); assertEquals(testValue, col.getNodeValue()); } @Test public void testEncodingInErrorMessage() throws SQLException, XPathException { // mocks a failing SQL query returning a single string and // checks the resulting error report XQueryContext context = new XQueryContextStub(); ExecuteFunction execute = new ExecuteFunction(context, signatureByArity(ExecuteFunction.signatures, functionName, 3)); final String query = "SELECT '<NAME>' FROM BLA"; final String testMessage = "Some <&> error occurred!"; // create mock objects Connection connection = mock(Connection.class); Statement stmt = mock(Statement.class); // mock behavior expect(connection.createStatement()).andReturn(stmt); expect(stmt.execute(query)).andStubThrow(new SQLException(testMessage)); stmt.close(); replay(connection, stmt); // register mocked connection final long connId = SQLModule.storeConnection( context, connection ); // execute function Sequence res = execute.eval(new Sequence[] { new IntegerValue(connId), new StringValue(query), new BooleanValue(false) }, Sequence.EMPTY_SEQUENCE); // assert expectations // <sql:exception><sql:state/><sql:message/><sql:stack-trace/><sql:sql/></sql:exception> assertEquals(1, res.getItemCount()); assertEquals(Type.ELEMENT, res.getItemType()); Node root = ((NodeValue) res.itemAt(0)).getNode(); assertEquals("sql:exception", root.getNodeName()); Node state = root.getFirstChild(); assertEquals("sql:state", state.getNodeName()); Node msg = state.getNextSibling(); assertEquals("sql:message", msg.getNodeName()); assertEquals(testMessage, msg.getNodeValue()); Node stacktrace = msg.getNextSibling(); assertEquals("sql:stack-trace", stacktrace.getNodeName()); Node sql = stacktrace.getNextSibling(); assertEquals("sql:sql", sql.getNodeName()); assertEquals(query, sql.getNodeValue()); Node xquery = sql.getNextSibling(); assertEquals("sql:xquery", xquery.getNodeName()); } public static class XQueryContextStub extends XQueryContext { public XQueryContextStub() { super(); } public String getCacheClass() { return org.exist.util.io.FileFilterInputStreamCache.class.getName(); } } static FunctionSignature signatureByArity(FunctionSignature[] signatures, QName functionName, int arity) { for (FunctionSignature signature : signatures) { if (signature.getName().equals(functionName) && signature.getArgumentCount() == arity) { return signature; } } return null; } }