From: <mro...@us...> - 2012-12-07 20:11:15
|
Revision: 57460 http://firebird.svn.sourceforge.net/firebird/?rev=57460&view=rev Author: mrotteveel Date: 2012-12-07 20:11:04 +0000 (Fri, 07 Dec 2012) Log Message: ----------- Misc changes Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdConnectionProperties.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBConnectionTimeout.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdConnectionProperties.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdConnectionProperties.java 2012-12-07 19:56:57 UTC (rev 57459) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdConnectionProperties.java 2012-12-07 20:11:04 UTC (rev 57460) @@ -404,7 +404,7 @@ /** * Set the connect timeout. * - * @param connectTimout Connect timeout in seconds (0 is 'infinite', or better: OS specific timeout) + * @param connectTimeout Connect timeout in seconds (0 is 'infinite', or better: OS specific timeout) */ void setConnectTimeout(int connectTimeout); } Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBConnectionTimeout.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBConnectionTimeout.java 2012-12-07 19:56:57 UTC (rev 57459) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBConnectionTimeout.java 2012-12-07 20:11:04 UTC (rev 57460) @@ -61,7 +61,7 @@ public static void verifyTestType() { // Test won't work for embedded assumeTrue(!FBTestProperties.getGdsType().toString().equals(EmbeddedGDSImpl.EMBEDDED_TYPE_NAME)); - // Test won't for for native + // Test won't work for for native assumeTrue(!FBTestProperties.getGdsType().toString().equals(NativeGDSImpl.NATIVE_TYPE_NAME)); } @@ -78,7 +78,7 @@ DriverManager.setLoginTimeout(0); long startTime = System.currentTimeMillis(); try { - DriverManager.getConnection("jdbc:firebirdsql://" + NON_EXISTENT_IP + "/db", "sysdba", "masterkey"); + DriverManager.getConnection(buildTestURL(), "sysdba", "masterkey"); } catch (SQLException e) { long endTime = System.currentTimeMillis(); long difference = endTime - startTime; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2012-12-24 13:39:06
|
Revision: 57543 http://sourceforge.net/p/firebird/code/57543 Author: mrotteveel Date: 2012-12-24 13:39:03 +0000 (Mon, 24 Dec 2012) Log Message: ----------- JDBC-223 Fix for JDBC LIKE Escape character escape + test Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java Added Paths: ----------- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java 2012-12-24 12:49:36 UTC (rev 57542) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java 2012-12-24 13:39:03 UTC (rev 57543) @@ -394,7 +394,7 @@ * @return converted code. */ protected String convertEscapeString(final String escapeString) { - return escapeString; + return "ESCAPE " + escapeString; } /** Added: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java (rev 0) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java 2012-12-24 13:39:03 UTC (rev 57543) @@ -0,0 +1,177 @@ +/* + * Firebird Open Source J2ee connector - jdbc driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import static org.junit.Assert.*; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.firebirdsql.common.FBJUnit4TestBase; +import org.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.JdbcResourceHelper; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for support of the <code>LIKE</code> escape character escape as defined + * in section 13.4.5 of the JDBC 4.1 specification. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + */ +public class TestLikeEscape extends FBJUnit4TestBase { + + private static final String[] TEST_DATA = { "abcdef", "abc_ef", "abc%ef" }; + + @Before + public void setupTestData() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); +//@formatter:off + stmt.execute( + "CREATE TABLE TAB1 (" + + " ID INT CONSTRAINT PK_TAB1 PRIMARY KEY," + + " VAL VARCHAR(30)" + + ")"); +//@formatter:on + stmt.close(); + + con.setAutoCommit(false); + PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID, VAL) VALUES (?, ?)"); + for (int idx = 0; idx < TEST_DATA.length; idx++) { + pstmt.setInt(1, idx + 1); + pstmt.setString(2, TEST_DATA[idx]); + pstmt.addBatch(); + } + pstmt.executeBatch(); + con.commit(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test for LIKE with an alternate escape defined, with % wildcard, but no + * escaped values. + */ + @Test + public void testSimpleLike_percent() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc%' {escape '&'}"); + Set<String> expectedStrings = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(TEST_DATA))); + + assertEquals("Unexpected result for LIKE 'abc%' {escape '&'}", expectedStrings, getStrings(rs, 1)); + + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test for LIKE with an alternate escape defined, with _ wildcard, but no + * escaped values. + */ + @Test + public void testSimpleLike_underscore() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc_ef' {escape '&'}"); + Set<String> expectedStrings = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(TEST_DATA))); + + assertEquals("Unexpected result for LIKE 'abc_ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); + + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test for LIKE with an alternate escape defined, with escaped % character. + */ + @Test + public void testEscapedLike_percent() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc&%ef' {escape '&'}"); + Set<String> expectedStrings = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList("abc%ef"))); + + assertEquals("Unexpected result for LIKE 'abc&%ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); + + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test for LIKE with an alternate escape defined, with escaped _ character. + */ + @Test + public void testEscapedLike_underscore() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc&_ef' {escape '&'}"); + Set<String> expectedStrings = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList("abc_ef"))); + + assertEquals("Unexpected result for LIKE 'abc&_ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); + + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Helper method to sequentially process a ResultSet and add all strings in + * columnIndex into a Set. + * + * @param rs + * ResultSet to process + * @param columnIndex + * Index of the column + * @return Set of strings in columnIndex. + * @throws SQLException + */ + private Set<String> getStrings(ResultSet rs, int columnIndex) throws SQLException { + Set<String> strings = new HashSet<String>(); + while (rs.next()) { + strings.add(rs.getString(columnIndex)); + } + return strings; + } +} Property changes on: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2012-12-24 16:02:39
|
Revision: 57547 http://sourceforge.net/p/firebird/code/57547 Author: mrotteveel Date: 2012-12-24 16:02:36 +0000 (Mon, 24 Dec 2012) Log Message: ----------- JDBC-223 Implementation of JDBC Limiting Returned Rows Escape (section 13.4.6) Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBEscapedCallParser.java Added Paths: ----------- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java 2012-12-24 15:56:07 UTC (rev 57546) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java 2012-12-24 16:02:36 UTC (rev 57547) @@ -19,6 +19,7 @@ package org.firebirdsql.jdbc; import java.text.BreakIterator; +import java.text.MessageFormat; /** * The class <code>FBEscapedParser</code> parses the SQL and converts escaped @@ -76,7 +77,8 @@ public static final String ESCAPE_TIMESTAMP_KEYWORD = "ts"; public static final String ESCAPE_FUNCTION_KEYWORD = "fn"; public static final String ESCAPE_ESCAPE_KEYWORD = "escape"; - public static final String ESCAPE_OUTERJOIN_KEYWORS = "oj"; + public static final String ESCAPE_OUTERJOIN_KEYWORD = "oj"; + public static final String ESCAPE_LIMIT_KEYWORD = "limit"; /* * These constants are necessary to speed up checking the @@ -91,6 +93,9 @@ protected static final String CHECK_FUNCTION = "{fn"; protected static final String CHECK_ESCAPE = "{escape"; protected static final String CHECK_OUTERJOIN = "{oj"; + protected static final String CHECK_LIMIT = "{limit"; + + protected static final String LIMIT_OFFSET_CLAUSE = " offset "; private int state = NORMAL_STATE; private int lastState = NORMAL_STATE; @@ -222,7 +227,8 @@ sql.indexOf(CHECK_FUNCTION) != -1 || sql.indexOf(CHECK_OUTERJOIN) != -1 || sql.indexOf(CHECK_TIME) != -1 || - sql.indexOf(CHECK_TIMESTAMP) != -1; + sql.indexOf(CHECK_TIMESTAMP) != -1 || + sql.indexOf(CHECK_LIMIT) != -1; //@formatter:on } @@ -312,12 +318,14 @@ return convertEscapeString(payload.toString().trim()); else if (keywordStr.equalsIgnoreCase(ESCAPE_FUNCTION_KEYWORD)) return convertEscapedFunction(payload.toString().trim()); - else if (keywordStr.equalsIgnoreCase(ESCAPE_OUTERJOIN_KEYWORS)) + else if (keywordStr.equalsIgnoreCase(ESCAPE_OUTERJOIN_KEYWORD)) return convertOuterJoin(payload.toString().trim()); else if (keywordStr.equalsIgnoreCase(ESCAPE_TIME_KEYWORD)) return toTimeString(payload.toString().trim()); else if (keywordStr.equalsIgnoreCase(ESCAPE_TIMESTAMP_KEYWORD)) return toTimestampString(payload.toString().trim()); + else if (keywordStr.equalsIgnoreCase(ESCAPE_LIMIT_KEYWORD)) + return convertLimitString(payload.toString().trim()); else throw new FBSQLParseException("Unknown keyword " + keywordStr + " for escaped syntax."); } @@ -398,6 +406,37 @@ } /** + * Convert the <code>"{limit <rows> [offset <rows_offset>]}"</code> call into the corresponding rows + * clause for Firebird. + * <p> + * NOTE: We assume that the {limit ...} escape occurs in the right place to + * work for a + * <code><a href="http://www.firebirdsql.org/file/documentation/reference_manuals/reference_material/html/langrefupd25-select.html#langrefupd25-select-rows">ROWS</a></code> + * clause in Firebird. + * </p> + * <p> + * This implementation supports a parameter for the value of <rows>, but not for <rows_offset>. + * </p> + * + * @param limitClause + * Limit clause + * @return converted code + */ + protected String convertLimitString(final String limitClause) throws FBSQLParseException { + final int offsetStart = limitClause.toLowerCase().indexOf(LIMIT_OFFSET_CLAUSE.toLowerCase()); + if (offsetStart == -1) { + return "ROWS " + limitClause; + } else { + final String rows = limitClause.substring(0, offsetStart).trim(); + final String offset = limitClause.substring(offsetStart + LIMIT_OFFSET_CLAUSE.length()).trim(); + if (offset.indexOf('?') != -1) { + throw new FBSQLParseException("Extended limit escape ({limit <rows> offset <offset_rows>} does not support parameters for <offset_rows>"); + } + return MessageFormat.format("ROWS {0} TO {0} + {1}", offset, rows); + } + } + + /** * This method converts escaped function to a server function call. Actually * we do not change anything here, we hope that all UDF are defined. * Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBEscapedCallParser.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBEscapedCallParser.java 2012-12-24 15:56:07 UTC (rev 57546) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBEscapedCallParser.java 2012-12-24 16:02:36 UTC (rev 57547) @@ -16,16 +16,13 @@ * * All rights reserved. */ - package org.firebirdsql.jdbc; import java.sql.SQLException; import java.sql.Types; - import junit.framework.TestCase; - /** * Describe class <code>TestFBEscapedCallParser</code> here. * Added: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java (rev 0) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java 2012-12-24 16:02:36 UTC (rev 57547) @@ -0,0 +1,196 @@ +/* + * Firebird Open Source J2ee connector - jdbc driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import static org.junit.Assert.*; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; + +import org.firebirdsql.common.FBJUnit4TestBase; +import org.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.JdbcResourceHelper; +import org.firebirdsql.jdbc.FBSQLParseException; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests for support of the Limiting Returned Rows Escape as defined in section + * 13.4.6 of the JDBC 4.1 specification. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + */ +public class TestLimitEscape extends FBJUnit4TestBase { + + private static final int ROW_COUNT = 25; + + @Before + public void setupTestData() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + stmt.execute("CREATE TABLE TAB1 (" + " ID INT CONSTRAINT PK_TAB1 PRIMARY KEY" + ")"); + stmt.close(); + + con.setAutoCommit(false); + PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID) VALUES (?)"); + for (int id = 1; id <= ROW_COUNT; id++) { + pstmt.setInt(1, id); + pstmt.addBatch(); + } + pstmt.executeBatch(); + pstmt.close(); + con.commit(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test of limit escape with only the rows parameter with a literal value + */ + @Test + public void testLimitLiteralWithoutOffset() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT ID FROM TAB1 ORDER BY ID {limit 10}"); + + int id = 0; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 10, id); + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test of limit escape with only the rows parameter with a parametrized value + */ + @Test + public void testLimitParametrizedWithoutOffset() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + PreparedStatement stmt = con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ?}"); + stmt.setInt(1, 10); + ResultSet rs = stmt.executeQuery(); + + int id = 0; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 10, id); + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test of limit escape with the rows and row_offset parameter with a literal value for both + */ + @Test + public void testLimitLiteralWithOffset() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT ID FROM TAB1 ORDER BY ID {limit 10 offset 5}"); + + int id = 4; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 15, id); + rs.close(); + stmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test of limit escape with the rows and row_offset parameter with a literal value for rows and a parameter for offset_rows. + * <p> + * Expects exception, as parameter for offset_rows is not supported by driver implementation. + * </p> + */ + @Test(expected = FBSQLParseException.class) + public void testLimitOffsetParameter() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit 10 offset ?}"); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test of limit escape with the rows and row_offset parameter with a parameter for rows and a literal value for offset_rows. + */ + @Test + public void testLimitRowsParameter() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + PreparedStatement pstmt = con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ? offset 10}"); + pstmt.setInt(1, 8); + ResultSet rs = pstmt.executeQuery(); + + int id = 9; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 18, id); + rs.close(); + pstmt.close(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test of limit escape with the rows and row_offset parameter with a parameter for rows and offset_rows. + * <p> + * Expects exception, as parameter for offset_rows is not supported by driver implementation. + * </p> + */ + @Test(expected = FBSQLParseException.class) + public void testLimitRowsAndOffsetParameter() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ? offset ?}"); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } +} Property changes on: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2012-12-28 08:16:59
|
Revision: 57557 http://sourceforge.net/p/firebird/code/57557 Author: mrotteveel Date: 2012-12-28 08:16:53 +0000 (Fri, 28 Dec 2012) Log Message: ----------- Move escape parsing to package org.firebirdsql.jdbc.escape Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/ client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedCallParser.java client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBSQLParseException.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java Removed Paths: ------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedCallParser.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedFunctionHelper.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBSQLParseException.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBEscapedCallParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBEscapedFunctionHelper.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java 2012-12-28 03:37:44 UTC (rev 57556) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java 2012-12-28 08:16:53 UTC (rev 57557) @@ -32,6 +32,8 @@ import org.firebirdsql.gds.GDSException; import org.firebirdsql.gds.impl.DatabaseParameterBufferExtension; import org.firebirdsql.gds.impl.GDSHelper; +import org.firebirdsql.jdbc.escape.FBEscapedCallParser; +import org.firebirdsql.jdbc.escape.FBEscapedParser; import org.firebirdsql.jdbc.field.FBField; import org.firebirdsql.jdbc.field.TypeConversionException; Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java 2012-12-28 03:37:44 UTC (rev 57556) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java 2012-12-28 08:16:53 UTC (rev 57557) @@ -60,6 +60,7 @@ import org.firebirdsql.jca.FBLocalTransaction; import org.firebirdsql.jca.FBManagedConnection; import org.firebirdsql.jca.FirebirdLocalTransaction; +import org.firebirdsql.jdbc.escape.FBEscapedParser; import org.firebirdsql.util.SQLExceptionChainBuilder; /** Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2012-12-28 03:37:44 UTC (rev 57556) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2012-12-28 08:16:53 UTC (rev 57557) @@ -29,6 +29,7 @@ import org.firebirdsql.gds.impl.AbstractGDS; import org.firebirdsql.gds.impl.GDSFactory; import org.firebirdsql.gds.impl.GDSHelper; +import org.firebirdsql.jdbc.escape.FBEscapedFunctionHelper; import org.firebirdsql.logging.Logger; import org.firebirdsql.logging.LoggerFactory; Deleted: client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedCallParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedCallParser.java 2012-12-28 03:37:44 UTC (rev 57556) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedCallParser.java 2012-12-28 08:16:53 UTC (rev 57557) @@ -1,438 +0,0 @@ -/* - * Firebird Open Source J2ee connector - jdbc driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ - -package org.firebirdsql.jdbc; - - -/** - * Parser for escaped procedure call. - */ -public class FBEscapedCallParser { - - private static final int NORMAL_STATE = 1; - private static final int LITERAL_STATE = 2; - private static final int BRACE_STATE = 4; - private static final int CURLY_BRACE_STATE = 8; - private static final int SPACE_STATE = 16; - private static final int COMMA_STATE = 32; - - - private int state = NORMAL_STATE; - - private int paramPosition; - private int paramCount; - - private boolean isFirstOutParam; - private boolean isNameProcessed; - private boolean isExecuteWordProcessed; - private boolean isProcedureWordProcessed; - private boolean isCallWordProcessed; - - private int openBraceCount; - - private FBProcedureCall procedureCall; - private FBEscapedParser escapedParser; - - public FBEscapedCallParser(int mode) { - this.escapedParser = new FBEscapedParser(mode); - } - - /** - * Returns the current state. - */ - protected int getState() { return state; } - - /** - * Sets the current state. - * @param state to enter. - * @throws <code>java.lang.IllegalStateException</code> if the system - * cannot enter the desired state. - */ - protected void setState(int state) { - this.state = state; - } - - /** - * Returns if the system is in state <code>state</code>. - * @param state we're testing - * @return <code>true</code> if the system is in state <code>state</code>. - */ - protected boolean isInState(int state) { return this.state == state; } - - /** - * Test the character to be the state switching character and switches - * the state if necessary. - * @param testChar character to test - */ - protected void switchState(char testChar) throws FBSQLParseException { - - switch (testChar) { - case '\'' : - if (isInState(NORMAL_STATE)) - setState(LITERAL_STATE); - else - if (isInState(LITERAL_STATE)) - setState(NORMAL_STATE); - - break; - - case ' ' : - case '\t' : - case '\r' : - case '\n' : - case '\f' : - if (!isInState(LITERAL_STATE)) - setState(SPACE_STATE); - - break; - - case ',' : - if (!isInState(LITERAL_STATE) && !isInState(BRACE_STATE)) - setState(COMMA_STATE); - - break; - - case '(' : - case ')' : - if (isInState(LITERAL_STATE)) - break; - - setState(BRACE_STATE); - - break; - - case '{' : - case '}' : - if (!isInState(LITERAL_STATE)) - setState(CURLY_BRACE_STATE); - - break; - - default : - if (!isInState(LITERAL_STATE) && !isInState(BRACE_STATE)) - setState(NORMAL_STATE); - } - } - - - /** - * Clean the SQL statement. This method removes leading and trailing spaces - * and removes leading and trailing curly braces if any. - * - * @param sql SQL statement to clean up. - * - * @return cleaned up statement. - * - * @throws FBSQLParseException if cleanup resulted in empty statement. - */ - private String cleanUpCall(String sql) throws FBSQLParseException { - // TODO Consider replacing with processing into buffer or simply String.trim(), as deleting is not very efficient - StringBuilder cleanupBuffer = new StringBuilder(sql); - - // remove spaces at the beginning - while(cleanupBuffer.length() > 0 && - Character.isSpaceChar(cleanupBuffer.charAt(0))) - cleanupBuffer.deleteCharAt(0); - - // remove spaces at the end - while(cleanupBuffer.length() > 0 && - Character.isSpaceChar(cleanupBuffer.charAt(cleanupBuffer.length() - 1))) - cleanupBuffer.deleteCharAt(cleanupBuffer.length() - 1); - - if (cleanupBuffer.length() == 0) - throw new FBSQLParseException( - "Escaped call statement was empty."); - - if (cleanupBuffer.charAt(0) == '{') - cleanupBuffer.deleteCharAt(0); - - if (cleanupBuffer.charAt(cleanupBuffer.length() - 1) == '}') - cleanupBuffer.deleteCharAt(cleanupBuffer.length() - 1); - - return cleanupBuffer.toString(); - } - - /** - * Check if either "call" keyword or "EXECUTE PROCEDURE" keyword processed. - * - * @return <code>true</code> if either one or another keyword were processed. - */ - private boolean isCallKeywordProcessed() { - return isCallWordProcessed || - (isExecuteWordProcessed && isProcedureWordProcessed); - } - - /** - * Converts escaped parts in the passed SQL to native representation. - * @param sql to parse - * - * @return native form of the <code>sql</code>. - */ - public FBProcedureCall parseCall(String sql) throws FBSQLException { - - sql = cleanUpCall(sql); - - procedureCall = new FBProcedureCall(); - - isExecuteWordProcessed = false; - isProcedureWordProcessed = false; - isCallWordProcessed = false; - isNameProcessed = false; - - isFirstOutParam = false; - - paramCount = 0; - paramPosition = 0; - - setState(NORMAL_STATE); - - final char[] sqlbuff = sql.toCharArray(); - final StringBuilder buffer = new StringBuilder(); - - for(int i = 0; i < sqlbuff.length; i++) { - switchState(sqlbuff[i]); - - if (isInState(NORMAL_STATE)) { - - // if we have an equal sign, most likely {? = call ...} - // syntax is used (there's hardly any place for this sign - // in procedure parameters). but to be sure, we check if - // no brace is open and if buffer contains only '?' - if (sqlbuff[i] == '=') { - - if (openBraceCount <= 0) { - - String token = buffer.toString().trim(); - - if ("?".equals(token) && !isFirstOutParam && !isNameProcessed) { - - FBProcedureParam param = - procedureCall.addParam(paramPosition, token); - - paramCount++; - param.setIndex(paramCount); - - isFirstOutParam = true; - paramPosition++; - - buffer.setLength(0); - continue; - } - } - } - - buffer.append(sqlbuff[i]); - - } else - if (isInState(SPACE_STATE)) { - - if (buffer.length() == 0) { - setState(NORMAL_STATE); - continue; - } - - if (openBraceCount > 0) { - buffer.append(sqlbuff[i]); - setState(NORMAL_STATE); - continue; - } - - String token = buffer.toString().trim(); - - // if procedure name was not yet processed, process - // the token; we look for the sequence EXECUTE PROCEDURE <name> - // otherwise go into normal state to enable next transitions. - if (!isNameProcessed) { - boolean tokenProcessed = processToken(token); - if (tokenProcessed) { - buffer.setLength(0); - setState(NORMAL_STATE); - if (isNameProcessed){ - // If we just found a name, fast-forward to the - // opening parenthesis, if there is one - int j = i; - while (j < sqlbuff.length - 1 - && Character.isWhitespace(sqlbuff[j])) j++; - if (sqlbuff[j] == '(') - i = j; - } - - } - } else { - buffer.append(sqlbuff[i]); - setState(NORMAL_STATE); - } - - } else - if (isInState(BRACE_STATE)) { - - // if we have an opening brace and we already processed - // EXECUTE PROCEDURE words, but still do not have procedure - // name set, we can be sure that buffer contains procedure - // name. - boolean isProcedureName = - sqlbuff[i] == '(' && - isCallKeywordProcessed() && - !isNameProcessed; - - if (isProcedureName) { - String token = buffer.toString().trim(); - - if ("".equals(token)) - throw new FBSQLParseException( - "Procedure name is empty."); - - procedureCall.setName(token); - isNameProcessed = true; - - buffer.setLength(0); - - } else { - buffer.append(sqlbuff[i]); - - if (sqlbuff[i] == '(') - openBraceCount++; - else - openBraceCount--; - } - - setState(NORMAL_STATE); - - } else - if (isInState(CURLY_BRACE_STATE)) { - - buffer.append(sqlbuff[i]); - setState(NORMAL_STATE); - - } else - if (isInState(COMMA_STATE)) { - - if (openBraceCount > 0) { - buffer.append(sqlbuff[i]); - continue; - } - - String param = processParam(buffer.toString()); - buffer.setLength(0); - - FBProcedureParam callParam = - procedureCall.addParam(paramPosition, param); - - if (callParam.isParam()) { - paramCount++; - callParam.setIndex(paramCount); - } - - paramPosition++; - - setState(NORMAL_STATE); - - } else - if (isInState(LITERAL_STATE)) - buffer.append(sqlbuff[i]); - } - - if (buffer.length() == 0) - return procedureCall; - - // remove spaces at the beginning and the end - while(Character.isSpaceChar(buffer.charAt(0))) - buffer.deleteCharAt(0); - - while(Character.isSpaceChar(buffer.charAt(buffer.length() - 1))) - buffer.deleteCharAt(buffer.length() - 1); - - // if buffer starts with '(', remove it, - // we do not want this thing to bother us - if (buffer.charAt(0) == '(') - buffer.deleteCharAt(0); - - - // if buffer ends with ')', remove it - // it should match an opening brace right after the procedure - // name, and we assume that all syntax check was already done. - if (buffer.charAt(buffer.length() - 1) == ')') - buffer.deleteCharAt(buffer.length() - 1); - - // if there's something in the buffer, treat it as last param - if(null == procedureCall.getName() && !isNameProcessed) { - procedureCall.setName(buffer.toString()); - } else { - FBProcedureParam callParam = - procedureCall.addParam(paramPosition, buffer.toString()); - - if (callParam.isParam()) { - paramCount++; - callParam.setIndex(paramCount); - } - } - - return procedureCall; - } - - /** - * Process token. This method detects procedure call keywords and sets - * appropriate flags. Also it detects procedure name and sets appropriate - * filed in the procedure call object. - * - * @param token token to process. - * - * @return <code>true</code> if token was understood and processed. - */ - protected boolean processToken(String token) { - if ("EXECUTE".equalsIgnoreCase(token) && - !isExecuteWordProcessed && !isProcedureWordProcessed && !isNameProcessed) { - isExecuteWordProcessed = true; - return true; - } - - if ("PROCEDURE".equalsIgnoreCase(token) && - isExecuteWordProcessed && !isProcedureWordProcessed && !isNameProcessed) { - isProcedureWordProcessed = true; - return true; - } - - if ("call".equalsIgnoreCase(token) && !isCallWordProcessed && !isNameProcessed) { - isCallWordProcessed = true; - return true; - } - - if ((isCallWordProcessed || (isExecuteWordProcessed && isProcedureWordProcessed)) && !isNameProcessed) { - procedureCall.setName(token); - isNameProcessed = true; - return true; - } - - return false; - } - - /** - * Pre-process parameter. This method checks if there is escaped call inside - * and converts it to the native one. - * - * @param param parameter to process. - * - * @return processed parameter. - * - * @throws FBSQLParseException if parameter cannot be correctly parsed. - */ - protected String processParam(String param) throws FBSQLException { - return escapedParser.parse(param); - } -} Deleted: client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedFunctionHelper.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedFunctionHelper.java 2012-12-28 03:37:44 UTC (rev 57556) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedFunctionHelper.java 2012-12-28 08:16:53 UTC (rev 57557) @@ -1,793 +0,0 @@ -/* - * Firebird Open Source J2ee connector - jdbc driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ -package org.firebirdsql.jdbc; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.text.MessageFormat; -import java.util.*; - -/** - * Helper class for escaped functions. - * - * @author <a href="mailto:rro...@us...">Roman Rokytskyy</a> - */ -public class FBEscapedFunctionHelper { - - /** - * This map contains mapping between JDBC function names and Firebird ones. - * Mapping to null means: execute as is (might fail if there is no built-in or UDF) - */ - private static final Map<String, String> FUNCTION_MAP; - - /** - * Supported numeric functions - */ - private static final Set<String> SUPPORTED_NUMERIC_FUNCTIONS; - - /** - * Supported string functions - */ - private static final Set<String> SUPPORTED_STRING_FUNCTIONS; - - /** - * Supported time and date functions - */ - private static final Set<String> SUPPORTED_TIME_DATE_FUNCTIONS; - - /** - * Supported system functions - */ - private static final Set<String> SUPPORTED_SYSTEM_FUNCTIONS; - - static { - Map<String, String> functionMap = new HashMap<String, String>(); - /* Numeric Functions */ - functionMap.put("ABS", null); - functionMap.put("ACOS", null); - functionMap.put("ASIN", null); - functionMap.put("ATAN", null); - functionMap.put("ATAN2", null); - functionMap.put("CEILING", null); - functionMap.put("COS", null); - functionMap.put("COT", null); - functionMap.put("EXP", null); - functionMap.put("FLOOR", null); - functionMap.put("LOG", "LN({0})"); - functionMap.put("LOG10", null); - functionMap.put("MOD", null); - functionMap.put("PI", null); - functionMap.put("POWER", null); - functionMap.put("ROUND", null); - functionMap.put("SIGN", null); - functionMap.put("SIN", null); - functionMap.put("SQRT", null); - functionMap.put("TAN", null); - functionMap.put("TRUNCATE", "TRUNC({0},{1})"); - - SUPPORTED_NUMERIC_FUNCTIONS = Collections.unmodifiableSet(new HashSet<String>(functionMap.keySet())); - - /* String Functions */ - functionMap.put("ASCII", "ASCII_VAL({0})"); - functionMap.put("CHAR", "ASCII_CHAR({0})"); - // TODO support difference between CHARACTER and OCTETS optional param - functionMap.put("CHAR_LENGTH", "CHAR_LENGTH({0})"); - functionMap.put("CHARACTER_LENGTH", "CHAR_LENGTH({0})"); - functionMap.put("CONCAT", "{0}||{1}"); - functionMap.put("INSERT", "OVERLAY({0} PLACING {3} FROM {1} FOR {2})"); - functionMap.put("LCASE", "LOWER({0})"); - functionMap.put("LEFT", null); - // TODO support difference between CHARACTER and OCTETS optional param - functionMap.put("LENGTH", "CHAR_LENGTH(TRIM(TRAILING FROM {0}))"); - // TODO Support variant without start position - functionMap.put("LOCATE", "POSITION({0},{1},{2})"); - functionMap.put("LTRIM", "TRIM(LEADING FROM {0})"); - functionMap.put("OCTET_LENGTH", null); - functionMap.put("POSITION", null); - functionMap.put("REPEAT", "RPAD('''',{1},{0})"); - functionMap.put("REPLACE", null); - functionMap.put("RIGHT", null); - functionMap.put("RTRIM", "TRIM(TRAILING FROM {0})"); - functionMap.put("SPACE", "RPAD('''',{0})"); - functionMap.put("SUBSTRING", "SUBSTRING({0} FROM {1} FOR {2})"); - functionMap.put("UCASE", "UPPER({0})"); - - Set<String> supportedStringFunctions = new HashSet<String>(functionMap.keySet()); - supportedStringFunctions.removeAll(SUPPORTED_NUMERIC_FUNCTIONS); - SUPPORTED_STRING_FUNCTIONS = Collections.unmodifiableSet(new HashSet<String>(supportedStringFunctions)); - - /* Time and Date Functions */ - functionMap.put("CURRENT_DATE", "CURRENT_DATE"); - functionMap.put("CURRENT_TIME", "CURRENT_TIME"); - functionMap.put("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"); - functionMap.put("CURDATE", "CURRENT_DATE"); - functionMap.put("CURTIME", "CURRENT_TIME"); - functionMap.put("DAYOFMONTH", "EXTRACT(DAY FROM {0})"); - functionMap.put("DAYOFWEEK", "EXTRACT(WEEKDAY FROM {0})+1"); - functionMap.put("DAYOFYEAR", "EXTRACT(YEARDAY FROM {0})+1"); - functionMap.put("EXTRACT", null); - functionMap.put("HOUR", "EXTRACT(HOUR FROM {0})"); - functionMap.put("MINUTE", "EXTRACT(MINUTE FROM {0})"); - functionMap.put("MONTH", "EXTRACT(MONTH FROM {0})"); - functionMap.put("NOW", "CURRENT_TIMESTAMP"); - functionMap.put("SECOND", "EXTRACT(SECOND FROM {0})"); - functionMap.put("TIMESTAMPADD", null); - functionMap.put("TIMESTAMPDIFF", null); - functionMap.put("WEEK", "EXTRACT(WEEK FROM {0})"); - functionMap.put("YEAR", "EXTRACT(YEAR FROM {0})"); - - Set<String> supportedTimeDateFunctions = new HashSet<String>(functionMap.keySet()); - supportedTimeDateFunctions.removeAll(SUPPORTED_NUMERIC_FUNCTIONS); - supportedTimeDateFunctions.removeAll(SUPPORTED_STRING_FUNCTIONS); - SUPPORTED_TIME_DATE_FUNCTIONS = Collections.unmodifiableSet(new HashSet<String>(supportedTimeDateFunctions)); - - /* System Functions */ - functionMap.put("IFNULL", "COALESCE({0}, {1})"); - functionMap.put("USER", "USER"); - - Set<String> supportedSystemFunctions = new HashSet<String>(functionMap.keySet()); - supportedSystemFunctions.removeAll(SUPPORTED_NUMERIC_FUNCTIONS); - supportedSystemFunctions.removeAll(SUPPORTED_STRING_FUNCTIONS); - supportedSystemFunctions.removeAll(SUPPORTED_TIME_DATE_FUNCTIONS); - SUPPORTED_SYSTEM_FUNCTIONS = Collections.unmodifiableSet(new HashSet<String>(supportedSystemFunctions)); - - /* Conversion Functions */ - // TODO This does not support the conversion with SQL_ prefix in appendix D - // TODO Should work without specifying size on CHAR and VARCHAR - functionMap.put("CONVERT", "CAST({0} AS {1})"); - - // Unsupported functions defined in appendix D that might accidentally work due to UDFs - // Numerics - functionMap.put("DEGREES", null); - functionMap.put("RADIANS", null); - functionMap.put("RAND", null); - - // String - functionMap.put("DIFFERENCE", null); - functionMap.put("SOUNDEX", null); - - // Time and date - functionMap.put("DAYNAME", null); - functionMap.put("MONTHNAME", null); - functionMap.put("QUARTER", null); - - // System - functionMap.put("DATABASE", null); - - FUNCTION_MAP = Collections.unmodifiableMap(functionMap); - } - - /** - * Simple syntax check if function is specified in form "name(...)". - * - * @param functionCall string representing function call. - * - * @throws FBSQLParseException if simple syntax check failed. - */ - private static void checkSyntax(String functionCall) throws FBSQLParseException { - // NOTE: Some function calls don't require parenthesis eg CURRENT_TIMESTAMP - int parenthesisStart = functionCall.indexOf('('); - if (parenthesisStart != -1 && functionCall.charAt(functionCall.length() - 1) != ')') - throw new FBSQLParseException("No closing parenthesis found, not a function call."); - } - - /** - * Extract function name from the function call. - * - * @param functionCall escaped function call. - * - * @return name of the function. - * - * @throws FBSQLParseException if parse error occurs. - */ - public static String parseFunction(String functionCall) throws FBSQLParseException { - functionCall = functionCall.trim(); - checkSyntax(functionCall); - int parenthesisStart = functionCall.indexOf('('); - - return parenthesisStart != -1 ? functionCall.substring(0, parenthesisStart) : functionCall; - } - - /** - * Extract function arguments from the function call. This method parses - * escaped function call string and extracts function parameters from it. - * - * @param functionCall escaped function call. - * - * @return list of parameters of the function. - * - * @throws FBSQLParseException if parse error occurs. - */ - public static List<String> parseArguments(String functionCall) throws FBSQLParseException { - functionCall = functionCall.trim(); - checkSyntax(functionCall); - - int parenthesisStart = functionCall.indexOf('('); - if (parenthesisStart == -1) { - return Collections.emptyList(); - } - - String paramsString = functionCall.substring( - parenthesisStart + 1, functionCall.length() - 1); - - List<String> params = new ArrayList<String>(); - StringBuilder sb = new StringBuilder(); - boolean inQuotes = false; - boolean inDoubleQuotes = false; - - char[] chars = paramsString.toCharArray(); - - for(int i = 0; i < chars.length ; i++) { - switch(chars[i]) { - case '\'' : - sb.append(chars[i]); - if (!inDoubleQuotes) - inQuotes = !inQuotes; - break; - - case '"' : - sb.append(chars[i]); - if (!inQuotes) - inDoubleQuotes = !inDoubleQuotes; - break; - - // we ignore spaces, tabs and new lines if - // we are not in the string literal - // TODO Ignoring spaces can break some nested calls (eg CAST(... AS type) - case ' ' : - case '\t' : - case '\n' : - case '\r' : - if (inQuotes || inDoubleQuotes) - sb.append(chars[i]); - - break; - - // comma is considered parameter separator - // if it is not within the string literal - case ',' : - if (inQuotes || inDoubleQuotes) - sb.append(chars[i]); - else { - params.add(sb.toString()); - sb.setLength(0); - } - break; - - // by default we add chars to the buffer - default : - sb.append(chars[i]); - } - } - - // add last parameter if present - if (sb.length() > 0) - params.add(sb.toString()); - - // after processing all parameters all string literals should be closed - if (inQuotes || inDoubleQuotes) - throw new FBSQLParseException("String literal is not properly closed."); - - return params; - } - - /** - * Convert escaped function call using function template. - * - * @param functionCall escaped function call. - * - * @return server-side representation of the function call or <code>null</code> - * if no template found. - * - * @throws FBSQLParseException if escaped function call has incorrect syntax. - */ - public static String convertTemplate(final String functionCall, final int mode) throws FBSQLParseException { - final String functionName = parseFunction(functionCall).toUpperCase(); - final String[] params = parseArguments(functionCall).toArray(new String[0]); - - if (!FUNCTION_MAP.containsKey(functionName)) { - /* See 13.4.1 of JDBC 4.1 spec: - * "The escape syntax for scalar functions must only be used to invoke the scalar - * functions defined in Appendix D \x93Scalar Functions". The escape syntax is not - * intended to be used to invoke user-defined or vendor specific scalar functions." - */ - // TODO Consider throwing SQLFeatureNotSupported or a different SQLException - throw new FBSQLParseException("Unsupported JDBC function escape: " + functionName); - } - - final String firebirdTemplate = FUNCTION_MAP.get(functionName); - - if (firebirdTemplate != null) - return MessageFormat.format(firebirdTemplate, (Object[]) params); - - if (mode == FBEscapedParser.USE_STANDARD_UDF) - return convertUsingStandardUDF(functionName, params); - - return null; - } - - /* - * Functions below are conversion routines of the escaped function calls - * into the standard UDF library functions. The conversion function must - * have the name equal to the function name in the escaped syntax in the - * lower case and should take array of strings as parameter and it may throw - * the FBSQLParseException and must be declared as static and have public - * visibility. It should return a string of the converted function call. - */ - - private static String convertUsingStandardUDF(String name, String[] params) throws FBSQLParseException { - - try { - - name = name.toLowerCase(); - - // workaround for the {fn char()} function, since we cannot use - // "char" as name of the function - it is reserved word. - if ("char".equals(name)) - name = "_char"; - - Method method = FBEscapedFunctionHelper.class.getMethod( - name.toLowerCase(), new Class[] { String[].class}); - - return (String)method.invoke(null, new Object[]{params}); - - } catch(NoSuchMethodException ex) { - return null; - } catch (IllegalArgumentException ex) { - throw new FBSQLParseException("Error when converting function " - + name + ". Error " + ex.getClass().getName() + - " : " + ex.getMessage()); - } catch (IllegalAccessException ex) { - throw new FBSQLParseException("Error when converting function " - + name + ". Error " + ex.getClass().getName() + - " : " + ex.getMessage()); - } catch (InvocationTargetException ex) { - throw new FBSQLParseException("Error when converting function " - + name + ". Error " + ex.getClass().getName() + - " : " + ex.getMessage()); - } - - } - - - /* - * Mathematical functions - */ - - /** - * Produce a function call for the <code>abs</code> UDF function. - * The syntax of the <code>abs</code> function is - * <code>{fn abs(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String abs(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function abs : " + params.length); - - return "abs(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>acos</code> UDF function. - * The syntax of the <code>acos</code> function is - * <code>{fn acos(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String acos(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function acos : " + params.length); - - return "acos(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>asin</code> UDF function. - * The syntax of the <code>asin</code> function is - * <code>{fn asin(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String asin(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function asin : " + params.length); - - return "asin(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>atan</code> UDF function. - * The syntax of the <code>atan</code> function is - * <code>{fn atan(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String atan(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function atan : " + params.length); - - return "atan(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>atan2</code> UDF function. - * The syntax of the <code>atan2</code> function is - * <code>{fn atan2(float1, float2)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String atan2(String[] params) throws FBSQLParseException { - if (params.length != 2) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function atan2 : " + params.length); - - return "atan2(" + params[0] + ", " + params[1] + ")"; - } - - /** - * Produce a function call for the <code>ceiling</code> UDF function. - * The syntax of the <code>ceiling</code> function is - * <code>{fn ceiling(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String ceiling(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function ceiling : " + params.length); - - return "ceiling(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>cos</code> UDF function. - * The syntax of the <code>cos</code> function is - * <code>{fn cos(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String cos(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function cos : " + params.length); - - return "cos(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>cot</code> UDF function. - * The syntax of the <code>cot</code> function is - * <code>{fn cot(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String cot(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function cot : " + params.length); - - return "cot(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>floor</code> UDF function. - * The syntax of the <code>floor</code> function is - * <code>{fn floor(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String floor(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function floor : " + params.length); - - return "floor(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>log</code> UDF function. - * The syntax of the <code>log</code> function is - * <code>{fn log(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String log(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function log : " + params.length); - - return "ln(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>log10</code> UDF function. - * The syntax of the <code>log10</code> function is - * <code>{fn log10(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String log10(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function log10 : " + params.length); - - return "log10(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>mod</code> UDF function. - * The syntax of the <code>mod</code> function is - * <code>{fn mod(integer1, integer2)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String mod(String[] params) throws FBSQLParseException { - if (params.length != 2) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function mod : " + params.length); - - return "mod(" + params[0] + ", " + params[1] + ")"; - } - - /** - * Produce a function call for the <code>pi</code> UDF function. - * The syntax of the <code>pi</code> function is <code>{fn pi()}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String pi(String[] params) throws FBSQLParseException { - if (params.length != 0) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function pi : " + params.length); - - return "pi()"; - } - - /** - * Produce a function call for the <code>rand</code> UDF function. - * The syntax for the <code>rand</code> function is - * <code>{fn rand()}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String rand(String[] params) throws FBSQLParseException { - if (params.length != 0) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function rand : " + params.length); - - return "rand()"; - } - - /** - * Produce a function call for the <code>sign</code> UDF function. - * The syntax for the <code>sign</code> function is - * <code>{fn sign(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String sign(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function sign : " + params.length); - - return "sign(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>sin</code> UDF function. - * The syntax for the <code>sin</code> function is - * <code>{fn sin(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String sin(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function sin : " + params.length); - - return "sin(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>sqrt</code> UDF function. - * The syntax for the <code>sqrt</code> function is - * <code>{fn sqrt(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String sqrt(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function sqrt : " + params.length); - - return "sqrt(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>tan</tan> UDF function. - * The syntax for the <code>tan</code> function is - * <code>{fn tan(float)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String tan(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function tan : " + params.length); - - return "tan(" + params[0] + ")"; - } - - - /* - * String functions. - */ - - - /** - * Produce a function call for the <code>ascii</code> UDF function. - * The syntax of the <code>ascii</code> function is - * <code>{fn ascii(string)}</code> - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String ascii(String[] params) throws FBSQLParseException { - if (params.length != 1 ) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function ascii : " + params.length); - - if (params[0] == null || params[0].length() < 1) - throw new FBSQLParseException("Parameter must not be " + - "empty or null"); - - return "ascii_val(" + params[0].charAt(0) + ")"; - } - - /** - * Produce a function call for the <code>char</code> UDF function. - * The syntax of the <code>char</code> function is - * <code>{fn char(integer)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String _char(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function char : " + params.length); - - return "char(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>lcase</code> UDF function. - * The syntax of the <code>lcase</code> function is - * <code>{fn lcase(string)}</code> - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String lcase(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function lcase : " + params.length); - - return "lower(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>length</code> UDF function. - * The syntax of the <code>length</code> function is - * <code>{fn length(string)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String length(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function length : " + params.length); - - return "strlen(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>ltrim</code> UDF function. - * The syntax of the <code>ltrim</code> function is - * <code>{fn ltrim(string)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String ltrim(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function ltrim : " + params.length); - - return "ltrim(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>rtrim</code> UDF function. - * The syntax of the <code>rtrim</code> function is - * <code>{fn rtrim(string)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String rtrim(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function rtrim : " + params.length); - - return "rtrim(" + params[0] + ")"; - } - - /** - * @return Set of JDBC numeric functions supported (as defined in appendix D.1 of JDBC 4.1) - */ - public static Set<String> getSupportedNumericFunctions() { - return SUPPORTED_NUMERIC_FUNCTIONS; - } - - /** - * @return Set of JDBC string functions supported (as defined in appendix D.2 of JDBC 4.1) - */ - public static Set<String> getSupportedStringFunctions() { - return SUPPORTED_STRING_FUNCTIONS; - } - - /** - * @return Set of JDBC time and date functions supported (as defined in appendix D.3 of JDBC 4.1) - */ - public static Set<String> getSupportedTimeDateFunctions() { - return SUPPORTED_TIME_DATE_FUNCTIONS; - } - - /** - * @return Set of JDBC system functions supported (as defined in appendix D.4 of JDBC 4.1) - */ - public static Set<String> getSupportedSystemFunctions() { - return SUPPORTED_SYSTEM_FUNCTIONS; - } -} Deleted: client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java 2012-12-28 03:37:44 UTC (rev 57556) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBEscapedParser.java 2012-12-28 08:16:53 UTC (rev 57557) @@ -1,413 +0,0 @@ -/* - * Firebird Open Source J2ee connector - jdbc driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ -package org.firebirdsql.jdbc; - -import java.text.BreakIterator; -import java.text.MessageFormat; -import java.util.regex.Pattern; - -/** - * The class <code>FBEscapedParser</code> parses the SQL and converts escaped - * syntax to native form. - * - * @author <a href="mailto:rro...@us...">Roman Rokytskyy</a> - * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> - * @version 1.0 - */ -public final class FBEscapedParser { - - /** - * Use built-in functions if available. - * <p> - * This may still result in a UDF function beign used if the UDF matches - * naming and arguments of the function (or function template) - * </p> - */ - public static final int USE_BUILT_IN = 0; - - /** - * Attempt to use UDF if there is no explicit function template defined. - * <p> - * This may still result in a built-in function being used if there is an - * explicit function template, and/or if there is no UDF with the name, but - * there is a built-in with the same name and parameter order. - * </p> - */ - public static final int USE_STANDARD_UDF = 1; - - /* - Stored procedure calls support both following syntax: - {call procedure_name[(arg1, arg2, ...)]} - or - {?= call procedure_name[(arg1, arg2, ...)]} - */ - private static final String ESCAPE_CALL_KEYWORD = "call"; - private static final String ESCAPE_CALL_KEYWORD3 = "?"; - private static final String ESCAPE_DATE_KEYWORD = "d"; - private static final String ESCAPE_TIME_KEYWORD = "t"; - private static final String ESCAPE_TIMESTAMP_KEYWORD = "ts"; - private static final String ESCAPE_FUNCTION_KEYWORD = "fn"; - private static final String ESCAPE_ESCAPE_KEYWORD = "escape"; - private static final String ESCAPE_OUTERJOIN_KEYWORD = "oj"; - private static final String ESCAPE_LIMIT_KEYWORD = "limit"; - - /** - * Regular expression to check for existence of JDBC escapes, is used to - * stop processing the entire SQL statement if it does not contain any of - * the substrings. - */ - private static final Pattern CHECK_ESCAPE_PATTERN = Pattern.compile( - "\\{(?:(?:\\?\\s*=\\s*)?call|d|ts?|escape|fn|oj|limit)\\s", - Pattern.CASE_INSENSITIVE); - - private static final String LIMIT_OFFSET_CLAUSE = " offset "; - - private final int mode; - - /** - * Creates a parser for JDBC escaped strings. - * - * @param mode - * One of {@link FBEscapedParser#USE_BUILT_IN} or - * {@link FBEscapedParser#USE_STANDARD_UDF} - */ - public FBEscapedParser(int mode) { - assert (mode == USE_BUILT_IN || mode == USE_STANDARD_UDF); - this.mode = mode; - } - - /** - * Check if the target SQL contains at least one of the escaped syntax - * commands. This method performs simple substring matching, so it may - * report that SQL contains escaped syntax when the <code>"{"</code> is - * followed by the escaped syntax command in regular string constants that - * are passed as parameters. In this case {@link #parse(String)} will - * perform complete SQL parsing. - * - * @param sql - * to test - * @return <code>true</code> if the <code>sql</code> is suspected to contain - * escaped syntax. - */ - private boolean checkForEscapes(String sql) { - return CHECK_ESCAPE_PATTERN.matcher(sql).find(); - } - - /** - * Converts escaped parts in the passed SQL to native representation. - * - * @param sql - * to parse - * @return native form of the <code>sql</code>. - */ - public String parse(final String sql) throws FBSQLException { - ParserState state = ParserState.NORMAL_STATE; - int nestedEscaped = 0; - - if (!checkForEscapes(sql)) return sql; - - final StringBuilder buffer = new StringBuilder(); - final StringBuilder escape = new StringBuilder(); - - for (int i = 0, n = sql.length(); i < n; i++) { - char currentChar = sql.charAt(i); - state = state.nextState(currentChar); - - switch (state) { - case NORMAL_STATE: - case LITERAL_STATE: - buffer.append(currentChar); - break; - case ESCAPE_ENTER_STATE: - nestedEscaped++; - break; - case ESCAPE_STATE: - escape.append(currentChar); - break; - case ESCAPE_EXIT_STATE: - nestedEscaped--; - if (nestedEscaped == 0) { - buffer.append(escapeToNative(escape.toString())); - escape.setLength(0); - state = ParserState.NORMAL_STATE; - } else { - state = ParserState.ESCAPE_STATE; - } - break; - default: - throw new IllegalStateException("Unexpected parser state " + state); - } - } - return buffer.toString(); - } - - private void processEscaped(final String escaped, final StringBuilder keyword, final StringBuilder payload) { - if (keyword.length() != 0) keyword.setLength(0); - if (payload.length() != 0) payload.setLength(0); - - // Extract the keyword from the escaped syntax. - final BreakIterator iterator = BreakIterator.getWordInstance(); - iterator.setText(escaped); - final int keyStart = iterator.first(); - final int keyEnd = iterator.next(); - keyword.append(escaped.substring(keyStart, keyEnd)); - payload.append(escaped.substring(keyEnd, escaped.length())); - } - - /** - * This method checks the passed parameter to conform the escaped syntax, - * checks for the unknown keywords and re-formats result according to the - * Firebird SQL syntax. - * - * @param escaped - * the part of escaped SQL between the '{' and '}'. - * @return the native representation of the SQL code. - */ - private String escapeToNative(final String escaped) throws FBSQLException { - final StringBuilder keyword = new StringBuilder(); - final StringBuilder payload = new StringBuilder(); - - processEscaped(escaped, keyword, payload); - - //Handle keywords. - final String keywordStr = keyword.toString().toLowerCase(); - // NOTE: We assume here that all KEYWORD constants are lowercase! - if (keywordStr.equals(ESCAPE_CALL_KEYWORD)) { - StringBuilder call = new StringBuilder(); - call.append('{') - .append(keyword) - .append(' ') - .append(payload) - .append('}'); - return convertProcedureCall(call.toString()); - } else if (keywordStr.equals(ESCAPE_CALL_KEYWORD3)) { - StringBuilder call = new Strin... [truncated message content] |
From: <mro...@us...> - 2012-12-31 10:51:17
|
Revision: 57570 http://sourceforge.net/p/firebird/code/57570 Author: mrotteveel Date: 2012-12-31 10:51:13 +0000 (Mon, 31 Dec 2012) Log Message: ----------- JDBC-292 : Parse nested JDBC escapes, correctly parse arguments when the parameter to a jdbc function escape or call escape has parameters of its own. + Replace integer parser mode with enum, additional cleanup (removal of 'UDF' function escape methods that will never be used as there is an explicit template) + Additional tests for parser Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedCallParser.java client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java Added Paths: ----------- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBCallableStatement.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -33,7 +33,7 @@ import org.firebirdsql.gds.impl.DatabaseParameterBufferExtension; import org.firebirdsql.gds.impl.GDSHelper; import org.firebirdsql.jdbc.escape.FBEscapedCallParser; -import org.firebirdsql.jdbc.escape.FBEscapedParser; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; import org.firebirdsql.jdbc.field.FBField; import org.firebirdsql.jdbc.field.TypeConversionException; @@ -103,10 +103,10 @@ DatabaseParameterBuffer dpb = c.getDatabaseParameterBuffer(); - int mode = FBEscapedParser.USE_BUILT_IN; + EscapeParserMode mode = EscapeParserMode.USE_BUILT_IN; if (dpb.hasArgument(DatabaseParameterBufferExtension.USE_STANDARD_UDF)) - mode = FBEscapedParser.USE_STANDARD_UDF; + mode = EscapeParserMode.USE_STANDARD_UDF; FBEscapedCallParser parser = new FBEscapedCallParser(mode); @@ -114,6 +114,7 @@ // and second time in parser.parseCall(...)... not nice, maybe // in the future should be fixed by calling FBEscapedParser for // each parameter in FBEscapedCallParser class + // TODO Might be unnecessary now FBEscapedParser processes nested escapes procedureCall = parser.parseCall(nativeSQL(sql)); if (storedProcMetaData.canGetSelectableInformation()) { Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnection.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -36,6 +36,7 @@ import java.sql.Statement; import java.sql.Struct; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -61,6 +62,7 @@ import org.firebirdsql.jca.FBManagedConnection; import org.firebirdsql.jca.FirebirdLocalTransaction; import org.firebirdsql.jdbc.escape.FBEscapedParser; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; import org.firebirdsql.util.SQLExceptionChainBuilder; /** @@ -90,19 +92,20 @@ private FBLocalTransaction localTransaction; private FBDatabaseMetaData metaData; - protected InternalTransactionCoordinator txCoordinator; + protected final InternalTransactionCoordinator txCoordinator; private SQLWarning firstWarning; // This set contains all allocated but not closed statements // It is used to close them before the connection is closed - protected Set<Statement> activeStatements = new HashSet<Statement>(); + protected final Set<Statement> activeStatements = Collections.synchronizedSet(new HashSet<Statement>()); private int resultSetHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT; private boolean autoCommit; private StoredProcedureMetaData storedProcedureMetaData; + private FBEscapedParser escapedParser; /** * Create a new AbstractConnection instance based on a @@ -403,17 +406,24 @@ * @return the native form of this statement * @exception SQLException if a database access error occurs */ - public synchronized String nativeSQL(String sql) throws SQLException { - - DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); - - int mode = FBEscapedParser.USE_BUILT_IN; - - if (dpb.hasArgument(DatabaseParameterBufferExtension.USE_STANDARD_UDF)) - mode = FBEscapedParser.USE_STANDARD_UDF; - - return new FBEscapedParser(mode).parse(sql); + public String nativeSQL(String sql) throws SQLException { + return getEscapedParser().parse(sql); } + + /** + * Returns the FBEscapedParser instance for this connection. + * + * @return Instance of FBEscapedParser + */ + protected FBEscapedParser getEscapedParser() { + if (escapedParser == null) { + DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); + EscapeParserMode mode = dpb.hasArgument(DatabaseParameterBufferExtension.USE_STANDARD_UDF) ? EscapeParserMode.USE_STANDARD_UDF + : EscapeParserMode.USE_BUILT_IN; + escapedParser = new FBEscapedParser(mode); + } + return escapedParser; + } /** * Sets this connection's auto-commit mode. Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -27,6 +27,7 @@ import org.firebirdsql.gds.*; import org.firebirdsql.gds.impl.*; import org.firebirdsql.jdbc.escape.FBEscapedParser; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; /** * <P>The object used for executing a static SQL statement @@ -47,8 +48,8 @@ */ public class FBStatement implements FirebirdStatement, Synchronizable { - protected GDSHelper gdsHelper; - protected FBObjectListener.StatementListener statementListener; + protected final GDSHelper gdsHelper; + protected final FBObjectListener.StatementListener statementListener; protected AbstractIscStmtHandle fixedStmt; @@ -72,14 +73,13 @@ private int queryTimeout; private String cursorName; - private int rsConcurrency; - private int rsType; - private int rsHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT; + private final int rsConcurrency; + private final int rsType; + private final int rsHoldability; - private FBObjectListener.ResultSetListener resultSetListener = new RSListener(); + private final FBObjectListener.ResultSetListener resultSetListener = new RSListener(); + private final FBConnection connection; - private FBConnection connection; - /** * Listener for the result sets. */ @@ -102,7 +102,6 @@ } } - /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.ResultSetListener#allRowsFetched(java.sql.ResultSet) */ @@ -121,11 +120,10 @@ // generate the "resultSetClosed" event, that in turn generates // the "statementCompleted" event - if (statementListener.getConnection().getAutoCommit()) + if (connection != null && connection.getAutoCommit()) rs.close(); } - /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.ResultSetListener#executionCompleted(org.firebirdsql.jdbc.FirebirdRowUpdater, boolean) */ @@ -133,15 +131,12 @@ notifyStatementCompleted(success); } - /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.ResultSetListener#executionStarted(org.firebirdsql.jdbc.FirebirdRowUpdater) */ public void executionStarted(FirebirdRowUpdater updater) throws SQLException { notifyStatementStarted(false); } - - } protected FBStatement(GDSHelper c, int rsType, int rsConcurrency, int rsHoldability, FBObjectListener.StatementListener statementListener) throws SQLException { @@ -153,6 +148,7 @@ this.statementListener = statementListener; + // TODO Find out if connection is actually ever null, because some parts of the code expect it not to be null this.connection = statementListener != null ? statementListener.getConnection() : null; @@ -1443,18 +1439,16 @@ } protected String nativeSQL(String sql) throws SQLException { - DatabaseParameterBuffer dpb = gdsHelper.getDatabaseParameterBuffer(); - - int mode = FBEscapedParser.USE_BUILT_IN; - - if (dpb.hasArgument(DatabaseParameterBufferExtension.USE_STANDARD_UDF)) - mode = FBEscapedParser.USE_STANDARD_UDF; - - return new FBEscapedParser(mode).parse(sql); - + if (connection != null) { + return connection.nativeSQL(sql); + } else { + DatabaseParameterBuffer dpb = gdsHelper.getDatabaseParameterBuffer(); + EscapeParserMode mode = dpb.hasArgument(DatabaseParameterBufferExtension.USE_STANDARD_UDF) ? EscapeParserMode.USE_STANDARD_UDF + : EscapeParserMode.USE_BUILT_IN; + return new FBEscapedParser(mode).parse(sql); + } } - /** * Get the execution plan of this PreparedStatement * Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedCallParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedCallParser.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedCallParser.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -16,14 +16,14 @@ * * All rights reserved. */ - package org.firebirdsql.jdbc.escape; +import java.sql.SQLException; + import org.firebirdsql.jdbc.FBProcedureCall; import org.firebirdsql.jdbc.FBProcedureParam; -import org.firebirdsql.jdbc.FBSQLException; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; - /** * Parser for escaped procedure call. */ @@ -36,7 +36,6 @@ private static final int SPACE_STATE = 16; private static final int COMMA_STATE = 32; - private int state = NORMAL_STATE; private int paramPosition; @@ -53,7 +52,7 @@ private FBProcedureCall procedureCall; private FBEscapedParser escapedParser; - public FBEscapedCallParser(int mode) { + public FBEscapedCallParser(EscapeParserMode mode) { this.escapedParser = new FBEscapedParser(mode); } @@ -188,7 +187,7 @@ * * @return native form of the <code>sql</code>. */ - public FBProcedureCall parseCall(String sql) throws FBSQLException { + public FBProcedureCall parseCall(String sql) throws SQLException { sql = cleanUpCall(sql); @@ -436,7 +435,7 @@ * * @throws FBSQLParseException if parameter cannot be correctly parsed. */ - protected String processParam(String param) throws FBSQLException { + protected String processParam(String param) throws SQLException { return escapedParser.parse(param); } } Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -18,11 +18,12 @@ */ package org.firebirdsql.jdbc.escape; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.*; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; + /** * Helper class for escaped functions. * @@ -230,49 +231,72 @@ StringBuilder sb = new StringBuilder(); boolean inQuotes = false; boolean inDoubleQuotes = false; + // ignore initial whitespace + boolean coalesceSpace = true; + int nestedParentheses = 0; - char[] chars = paramsString.toCharArray(); - - for(int i = 0; i < chars.length ; i++) { - switch(chars[i]) { - case '\'' : - sb.append(chars[i]); - if (!inDoubleQuotes) - inQuotes = !inQuotes; - break; - - case '"' : - sb.append(chars[i]); - if (!inQuotes) - inDoubleQuotes = !inDoubleQuotes; - break; - - // we ignore spaces, tabs and new lines if - // we are not in the string literal - // TODO Ignoring spaces can break some nested calls (eg CAST(... AS type) - case ' ' : - case '\t' : - case '\n' : - case '\r' : - if (inQuotes || inDoubleQuotes) - sb.append(chars[i]); - - break; - - // comma is considered parameter separator - // if it is not within the string literal - case ',' : - if (inQuotes || inDoubleQuotes) - sb.append(chars[i]); - else { - params.add(sb.toString()); - sb.setLength(0); + for(int i = 0, n = paramsString.length(); i < n; i++) { + char currentChar = paramsString.charAt(i); + switch(currentChar) { + case '\'' : + sb.append(currentChar); + if (!inDoubleQuotes) + inQuotes = !inQuotes; + coalesceSpace = false; + break; + case '"' : + sb.append(currentChar); + if (!inQuotes) + inDoubleQuotes = !inDoubleQuotes; + coalesceSpace = false; + break; + case '(': + if (!(inQuotes || inDoubleQuotes)) { + nestedParentheses++; + } + sb.append('('); + coalesceSpace = false; + break; + case ')': + if (!(inQuotes || inDoubleQuotes)) { + nestedParentheses--; + if (nestedParentheses < 0) { + throw new FBSQLParseException("Unbalanced parentheses in parameters at position " + i); } - break; - - // by default we add chars to the buffer - default : - sb.append(chars[i]); + } + sb.append(')'); + coalesceSpace = false; + break; + // we coalesce spaces, tabs and new lines into a single space if + // we are not in a string literal + case ' ' : + case '\t' : + case '\n' : + case '\r' : + if (inQuotes || inDoubleQuotes) { + sb.append(currentChar); + } else if (!coalesceSpace) { + sb.append(' '); + coalesceSpace = true; + } + break; + // comma is considered parameter separator + // if it is not within the string literal or within parentheses + case ',' : + if (inQuotes || inDoubleQuotes || nestedParentheses > 0) { + sb.append(currentChar); + } else { + params.add(sb.toString()); + sb.setLength(0); + // Ignore whitespace after parameter + coalesceSpace = true; + } + break; + + // by default we add chars to the buffer + default : + sb.append(currentChar); + coalesceSpace = false; } } @@ -281,8 +305,12 @@ params.add(sb.toString()); // after processing all parameters all string literals should be closed - if (inQuotes || inDoubleQuotes) + if (inQuotes || inDoubleQuotes) { throw new FBSQLParseException("String literal is not properly closed."); + } + if (nestedParentheses != 0) { + throw new FBSQLParseException("Unbalanced parentheses in parameters."); + } return params; } @@ -297,7 +325,7 @@ * * @throws FBSQLParseException if escaped function call has incorrect syntax. */ - public static String convertTemplate(final String functionCall, final int mode) throws FBSQLParseException { + public static String convertTemplate(final String functionCall, final EscapeParserMode mode) throws FBSQLParseException { final String functionName = parseFunction(functionCall).toUpperCase(); final String[] params = parseArguments(functionCall).toArray(new String[0]); @@ -316,7 +344,7 @@ if (firebirdTemplate != null) return MessageFormat.format(firebirdTemplate, (Object[]) params); - if (mode == FBEscapedParser.USE_STANDARD_UDF) + if (mode == EscapeParserMode.USE_STANDARD_UDF) return convertUsingStandardUDF(functionName, params); return null; @@ -332,40 +360,19 @@ */ private static String convertUsingStandardUDF(String name, String[] params) throws FBSQLParseException { - try { - name = name.toLowerCase(); - - // workaround for the {fn char()} function, since we cannot use - // "char" as name of the function - it is reserved word. - if ("char".equals(name)) - name = "_char"; - - Method method = FBEscapedFunctionHelper.class.getMethod( - name.toLowerCase(), new Class[] { String[].class}); - + Method method = FBEscapedFunctionHelper.class.getMethod(name, new Class[] { String[].class}); return (String)method.invoke(null, new Object[]{params}); - } catch(NoSuchMethodException ex) { return null; - } catch (IllegalArgumentException ex) { + } catch (Exception ex) { throw new FBSQLParseException("Error when converting function " + name + ". Error " + ex.getClass().getName() + " : " + ex.getMessage()); - } catch (IllegalAccessException ex) { - throw new FBSQLParseException("Error when converting function " - + name + ". Error " + ex.getClass().getName() + - " : " + ex.getMessage()); - } catch (InvocationTargetException ex) { - throw new FBSQLParseException("Error when converting function " - + name + ". Error " + ex.getClass().getName() + - " : " + ex.getMessage()); } - } - /* * Mathematical functions */ @@ -513,22 +520,6 @@ return "floor(" + params[0] + ")"; } - - /** - * Produce a function call for the <code>log</code> UDF function. - * The syntax of the <code>log</code> function is - * <code>{fn log(number)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String log(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function log : " + params.length); - - return "ln(" + params[0] + ")"; - } /** * Produce a function call for the <code>log10</code> UDF function. @@ -657,113 +648,7 @@ return "tan(" + params[0] + ")"; } - - /* - * String functions. - */ - - /** - * Produce a function call for the <code>ascii</code> UDF function. - * The syntax of the <code>ascii</code> function is - * <code>{fn ascii(string)}</code> - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String ascii(String[] params) throws FBSQLParseException { - if (params.length != 1 ) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function ascii : " + params.length); - - if (params[0] == null || params[0].length() < 1) - throw new FBSQLParseException("Parameter must not be " + - "empty or null"); - - return "ascii_val(" + params[0].charAt(0) + ")"; - } - - /** - * Produce a function call for the <code>char</code> UDF function. - * The syntax of the <code>char</code> function is - * <code>{fn char(integer)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String _char(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function char : " + params.length); - - return "char(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>lcase</code> UDF function. - * The syntax of the <code>lcase</code> function is - * <code>{fn lcase(string)}</code> - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String lcase(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function lcase : " + params.length); - - return "lower(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>length</code> UDF function. - * The syntax of the <code>length</code> function is - * <code>{fn length(string)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String length(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function length : " + params.length); - - return "strlen(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>ltrim</code> UDF function. - * The syntax of the <code>ltrim</code> function is - * <code>{fn ltrim(string)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String ltrim(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function ltrim : " + params.length); - - return "ltrim(" + params[0] + ")"; - } - - /** - * Produce a function call for the <code>rtrim</code> UDF function. - * The syntax of the <code>rtrim</code> function is - * <code>{fn rtrim(string)}</code>. - * - * @param params The parameters to be used in the call - * @throws FBSQLParseException if there is an error with the parameters - */ - public static String rtrim(String[] params) throws FBSQLParseException { - if (params.length != 1) - throw new FBSQLParseException("Incorrect number of " + - "parameters of function rtrim : " + params.length); - - return "rtrim(" + params[0] + ")"; - } - - /** * @return Set of JDBC numeric functions supported (as defined in appendix D.1 of JDBC 4.1) */ public static Set<String> getSupportedNumericFunctions() { Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -18,12 +18,13 @@ */ package org.firebirdsql.jdbc.escape; +import java.sql.SQLException; import java.text.BreakIterator; -import java.text.MessageFormat; +import java.util.Deque; +import java.util.LinkedList; import java.util.regex.Pattern; import org.firebirdsql.jdbc.FBProcedureCall; -import org.firebirdsql.jdbc.FBSQLException; /** * The class <code>FBEscapedParser</code> parses the SQL and converts escaped @@ -35,25 +36,6 @@ */ public final class FBEscapedParser { - /** - * Use built-in functions if available. - * <p> - * This may still result in a UDF function beign used if the UDF matches - * naming and arguments of the function (or function template) - * </p> - */ - public static final int USE_BUILT_IN = 0; - - /** - * Attempt to use UDF if there is no explicit function template defined. - * <p> - * This may still result in a built-in function being used if there is an - * explicit function template, and/or if there is no UDF with the name, but - * there is a built-in with the same name and parameter order. - * </p> - */ - public static final int USE_STANDARD_UDF = 1; - /* Stored procedure calls support both following syntax: {call procedure_name[(arg1, arg2, ...)]} @@ -76,12 +58,12 @@ * the substrings. */ private static final Pattern CHECK_ESCAPE_PATTERN = Pattern.compile( - "\\{(?:(?:\\?\\s*=\\s*)?call|d|ts?|escape|fn|oj|limit)\\s", + "\\{(?:(?:\\?\\s*=\\s*)?call|d|ts?|escape|fn|oj|limit)\\s", Pattern.CASE_INSENSITIVE); private static final String LIMIT_OFFSET_CLAUSE = " offset "; - private final int mode; + private final EscapeParserMode mode; /** * Creates a parser for JDBC escaped strings. @@ -90,14 +72,13 @@ * One of {@link FBEscapedParser#USE_BUILT_IN} or * {@link FBEscapedParser#USE_STANDARD_UDF} */ - public FBEscapedParser(int mode) { - assert (mode == USE_BUILT_IN || mode == USE_STANDARD_UDF); + public FBEscapedParser(EscapeParserMode mode) { this.mode = mode; } /** * Check if the target SQL contains at least one of the escaped syntax - * commands. This method performs simple substring matching, so it may + * commands. This method performs a simple regex match, so it may * report that SQL contains escaped syntax when the <code>"{"</code> is * followed by the escaped syntax command in regular string constants that * are passed as parameters. In this case {@link #parse(String)} will @@ -119,58 +100,64 @@ * to parse * @return native form of the <code>sql</code>. */ - public String parse(final String sql) throws FBSQLException { - ParserState state = ParserState.NORMAL_STATE; - int nestedEscaped = 0; - + public String parse(final String sql) throws SQLException { if (!checkForEscapes(sql)) return sql; - final StringBuilder buffer = new StringBuilder(); - final StringBuilder escape = new StringBuilder(); + ParserState state = ParserState.NORMAL_STATE; + final Deque<StringBuilder> bufferStack = new LinkedList<StringBuilder>(); + StringBuilder buffer = new StringBuilder(); for (int i = 0, n = sql.length(); i < n; i++) { char currentChar = sql.charAt(i); state = state.nextState(currentChar); - switch (state) { case NORMAL_STATE: case LITERAL_STATE: buffer.append(currentChar); break; case ESCAPE_ENTER_STATE: - nestedEscaped++; + bufferStack.push(buffer); + buffer = new StringBuilder(); break; - case ESCAPE_STATE: - escape.append(currentChar); - break; case ESCAPE_EXIT_STATE: - nestedEscaped--; - if (nestedEscaped == 0) { - buffer.append(escapeToNative(escape.toString())); - escape.setLength(0); - state = ParserState.NORMAL_STATE; - } else { - state = ParserState.ESCAPE_STATE; + if (bufferStack.isEmpty()) { + throw new FBSQLParseException("Unbalanced JDBC escape, too many '}'"); } + escapeToNative(bufferStack.peek(), buffer.toString()); + buffer = bufferStack.pop(); break; default: - throw new IllegalStateException("Unexpected parser state " + state); + throw new FBSQLParseException("Unexpected parser state " + state); } } - return buffer.toString(); + if (bufferStack.isEmpty()) { + return buffer.toString(); + } else { + throw new FBSQLParseException("Unbalanced JDBC escape, too many '{'"); + } } private void processEscaped(final String escaped, final StringBuilder keyword, final StringBuilder payload) { - if (keyword.length() != 0) keyword.setLength(0); - if (payload.length() != 0) payload.setLength(0); + assert (keyword.length() == 0 && payload.length() == 0) : "StringBuilders keyword and payload should be empty"; // Extract the keyword from the escaped syntax. final BreakIterator iterator = BreakIterator.getWordInstance(); iterator.setText(escaped); final int keyStart = iterator.first(); final int keyEnd = iterator.next(); - keyword.append(escaped.substring(keyStart, keyEnd)); - payload.append(escaped.substring(keyEnd, escaped.length())); + keyword.append(escaped, keyStart, keyEnd); + + int payloadStart = keyEnd; + // Remove whitespace before payload + while (payloadStart < escaped.length() - 1 && escaped.charAt(payloadStart) <= ' ') { + payloadStart++; + } + int payloadEnd = escaped.length(); + // Remove whitespace after payload + while (payloadEnd > payloadStart && escaped.charAt(payloadEnd - 1) <= ' ') { + payloadEnd--; + } + payload.append(escaped, payloadStart, payloadEnd); } /** @@ -178,11 +165,13 @@ * checks for the unknown keywords and re-formats result according to the * Firebird SQL syntax. * + * @param target + * Target StringBuilder to append to. * @param escaped * the part of escaped SQL between the '{' and '}'. * @return the native representation of the SQL code. */ - private String escapeToNative(final String escaped) throws FBSQLException { + private void escapeToNative(final StringBuilder target, final String escaped) throws SQLException { final StringBuilder keyword = new StringBuilder(); final StringBuilder payload = new StringBuilder(); @@ -198,28 +187,28 @@ .append(' ') .append(payload) .append('}'); - return convertProcedureCall(call.toString()); + convertProcedureCall(target, call.toString()); } else if (keywordStr.equals(ESCAPE_CALL_KEYWORD3)) { StringBuilder call = new StringBuilder(); call.append('{') .append(ESCAPE_CALL_KEYWORD3) .append(payload) .append('}'); - return convertProcedureCall(call.toString()); + convertProcedureCall(target, call.toString()); } else if (keywordStr.equals(ESCAPE_DATE_KEYWORD)) - return toDateString(payload.toString()); + toDateString(target, payload); else if (keywordStr.equals(ESCAPE_ESCAPE_KEYWORD)) - return convertEscapeString(payload.toString()); + convertEscapeString(target, payload); else if (keywordStr.equals(ESCAPE_FUNCTION_KEYWORD)) - return convertEscapedFunction(payload.toString()); + convertEscapedFunction(target, payload); else if (keywordStr.equals(ESCAPE_OUTERJOIN_KEYWORD)) - return convertOuterJoin(payload.toString()); + convertOuterJoin(target, payload); else if (keywordStr.equals(ESCAPE_TIME_KEYWORD)) - return toTimeString(payload.toString()); + toTimeString(target, payload); else if (keywordStr.equals(ESCAPE_TIMESTAMP_KEYWORD)) - return toTimestampString(payload.toString()); + toTimestampString(target, payload); else if (keywordStr.equals(ESCAPE_LIMIT_KEYWORD)) - return convertLimitString(payload.toString()); + convertLimitString(target, payload); else throw new FBSQLParseException("Unknown keyword " + keywordStr + " for escaped syntax."); } @@ -228,63 +217,77 @@ * This method converts the 'yyyy-mm-dd' date format into the Firebird * understandable format. * + * @param target + * Target StringBuilder to append to. * @param dateStr * the date in the 'yyyy-mm-dd' format. * @return Firebird understandable date format. */ - private String toDateString(final String dateStr) throws FBSQLParseException { + private void toDateString(final StringBuilder target, final CharSequence dateStr) throws FBSQLParseException { // use shorthand date cast (using just the string will not work in all contexts) - return "DATE " + dateStr; + target.append("DATE ").append(dateStr); } /** * This method converts the 'hh:mm:ss' time format into the Firebird * understandable format. * + * @param target + * Target StringBuilder to append to. * @param timeStr * the date in the 'hh:mm:ss' format. * @return Firebird understandable date format. */ - private String toTimeString(final String timeStr) throws FBSQLParseException { + private void toTimeString(final StringBuilder target, final CharSequence timeStr) throws FBSQLParseException { // use shorthand time cast (using just the string will not work in all contexts) - return "TIME " + timeStr; + target.append("TIME ").append(timeStr); } /** * This method converts the 'yyyy-mm-dd hh:mm:ss' timestamp format into the * Firebird understandable format. * + * @param target + * Target StringBuilder to append to. * @param timestampStr * the date in the 'yyyy-mm-dd hh:mm:ss' format. * @return Firebird understandable date format. */ - private String toTimestampString(final String timestampStr) throws FBSQLParseException { + private void toTimestampString(final StringBuilder target, final CharSequence timestampStr) + throws FBSQLParseException { // use shorthand timestamp cast (using just the string will not work in all contexts) - return "TIMESTAMP " + timestampStr; + target.append("TIMESTAMP ").append(timestampStr); } /** * This methods converts the escaped procedure call syntax into the native * procedure call. * + * @param target + * Target StringBuilder to append native procedure call to. * @param procedureCall * part of {call proc_name(...)} without curly braces and "call" * word. * @return native procedure call. */ - private String convertProcedureCall(final String procedureCall) throws FBSQLException { + private void convertProcedureCall(final StringBuilder target, final String procedureCall) throws SQLException { FBEscapedCallParser tempParser = new FBEscapedCallParser(mode); FBProcedureCall call = tempParser.parseCall(procedureCall); - return call.getSQL(false); + target.append(call.getSQL(false)); } /** * This method converts the escaped outer join call syntax into the native * outer join. Actually, we do not change anything here, since Firebird's * syntax is the same. + * + * @param target + * Target StringBuilder to append to. + * @param outerJoin + * Outer join text */ - private String convertOuterJoin(final String outerJoin) throws FBSQLParseException { - return outerJoin; + private void convertOuterJoin(final StringBuilder target, final CharSequence outerJoin) throws FBSQLParseException { + target.append(outerJoin); } /** @@ -295,8 +298,8 @@ * escape string to convert * @return converted code. */ - private String convertEscapeString(final String escapeString) { - return "ESCAPE " + escapeString; + private void convertEscapeString(final StringBuilder target, final CharSequence escapeString) { + target.append("ESCAPE ").append(escapeString); } /** @@ -318,17 +321,19 @@ * Limit clause * @return converted code */ - private String convertLimitString(final String limitClause) throws FBSQLParseException { - final int offsetStart = limitClause.toLowerCase().indexOf(LIMIT_OFFSET_CLAUSE); + private void convertLimitString(final StringBuilder target, final CharSequence limitClause) + throws FBSQLParseException { + final String limitEscape = limitClause.toString().toLowerCase(); + final int offsetStart = limitEscape.indexOf(LIMIT_OFFSET_CLAUSE); if (offsetStart == -1) { - return "ROWS " + limitClause; + target.append("ROWS ").append(limitEscape); } else { - final String rows = limitClause.substring(0, offsetStart).trim(); - final String offset = limitClause.substring(offsetStart + LIMIT_OFFSET_CLAUSE.length()).trim(); + final String rows = limitEscape.substring(0, offsetStart).trim(); + final String offset = limitEscape.substring(offsetStart + LIMIT_OFFSET_CLAUSE.length()).trim(); if (offset.indexOf('?') != -1) { throw new FBSQLParseException("Extended limit escape ({limit <rows> offset <offset_rows>} does not support parameters for <offset_rows>"); } - return MessageFormat.format("ROWS {0} TO {0} + {1}", offset, rows); + target.append("ROWS ").append(offset).append(" TO ").append(offset).append("+").append(rows); } } @@ -336,15 +341,18 @@ * This method converts escaped function to a server function call. Actually * we do not change anything here, we hope that all UDF are defined. * + * @param target + * Target StringBuilder to append to. * @param escapedFunction * escaped function call * @return server-side function call. * @throws FBSQLParseException * if something was wrong. */ - private String convertEscapedFunction(final String escapedFunction) throws FBSQLParseException { - final String templateResult = FBEscapedFunctionHelper.convertTemplate(escapedFunction, mode); - return templateResult != null ? templateResult : escapedFunction; + private void convertEscapedFunction(final StringBuilder target, final CharSequence escapedFunction) + throws FBSQLParseException { + final String templateResult = FBEscapedFunctionHelper.convertTemplate(escapedFunction.toString(), mode); + target.append(templateResult != null ? templateResult : escapedFunction); } private enum ParserState { @@ -359,6 +367,8 @@ return LITERAL_STATE; case '{': return ESCAPE_ENTER_STATE; + case '}': + return ESCAPE_EXIT_STATE; default: return NORMAL_STATE; } @@ -378,29 +388,20 @@ */ ESCAPE_ENTER_STATE { @Override - protected ParserState nextState(char inputChar) { + protected ParserState nextState(char inputChar) throws FBSQLParseException { if (inputChar == '}') { - throw new IllegalStateException("Did not expect end of JDBC escape at this point"); + throw new FBSQLParseException("Did not expect end of JDBC escape at this point"); } - return ESCAPE_STATE; + return NORMAL_STATE; } }, /** - * Inside JDBC escape - */ - ESCAPE_STATE { - @Override - protected ParserState nextState(char inputChar) { - return (inputChar == '}') ? ESCAPE_EXIT_STATE : ESCAPE_STATE; - } - }, - /** * End of JDBC escape (} character encountered) */ ESCAPE_EXIT_STATE { @Override - protected ParserState nextState(char inputChar) { - throw new IllegalStateException("nextState(char) should never be called on ESCAPE_EXIT_STATE"); + protected ParserState nextState(char inputChar) throws FBSQLParseException { + return (inputChar == '}') ? ESCAPE_EXIT_STATE : NORMAL_STATE; } }; @@ -410,7 +411,29 @@ * @param inputChar * Input character * @return Next state + * @throws FBSQLParseException + * For incorrect states during parsing */ - protected abstract ParserState nextState(char inputChar); + protected abstract ParserState nextState(char inputChar) throws FBSQLParseException; } + + public enum EscapeParserMode { + /** + * Use built-in functions if available. + * <p> + * This may still result in a UDF function beign used if the UDF matches + * naming and arguments of the function (or function template) + * </p> + */ + USE_BUILT_IN, + /** + * Attempt to use UDF if there is no explicit function template defined. + * <p> + * This may still result in a built-in function being used if there is + * an explicit function template, and/or if there is no UDF with the + * name, but there is a built-in with the same name and parameter order. + * </p> + */ + USE_STANDARD_UDF; + } } Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -24,6 +24,7 @@ import org.firebirdsql.jdbc.FBProcedureCall; import org.firebirdsql.jdbc.FBProcedureParam; import org.firebirdsql.jdbc.FBSQLException; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; import junit.framework.TestCase; @@ -93,7 +94,7 @@ } public void testProcessEscapedCall() throws Exception { - FBEscapedCallParser parser = new FBEscapedCallParser(FBEscapedParser.USE_BUILT_IN); + FBEscapedCallParser parser = new FBEscapedCallParser(EscapeParserMode.USE_BUILT_IN); FBProcedureCall procedureCall = parser.parseCall(CALL_TEST_5); procedureCall.registerOutParam(1, Types.INTEGER); Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java 2012-12-30 03:35:20 UTC (rev 57569) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -22,6 +22,8 @@ import java.util.ArrayList; import java.util.List; +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; + import junit.framework.TestCase; /** @@ -75,7 +77,7 @@ public static final String UCASE_FUNCTION_TEST = "UPPER(some_identifier)"; public void testEscapedFunctionCall() throws SQLException { - FBEscapedParser parser = new FBEscapedParser(FBEscapedParser.USE_BUILT_IN); + FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); String ucaseTest = parser.parse(UCASE_FUNCTION_CALL); assertEquals("ucase function parsing should be correct", @@ -83,7 +85,7 @@ } public void testUseStandardUdf() throws SQLException { - FBEscapedParser parser = new FBEscapedParser(FBEscapedParser.USE_STANDARD_UDF); + FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_STANDARD_UDF); String lcaseTest = parser.parse(LCASE_FUNCTION_CALL); assertEquals("lcase function parsing should be correct", Added: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java (rev 0) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java 2012-12-31 10:51:13 UTC (rev 57570) @@ -0,0 +1,283 @@ +/* + * Firebird Open Source J2ee connector - jdbc driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import static org.junit.Assert.*; + +import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; +import org.junit.Test; + +/** + * Tests for {@link FBEscapedParser}. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + */ +public class TestFBEscapedParser { + + @Test + public void testStringWithoutEscapes() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x = 'xyz'"; + + String parseResult = parser.parse(input); + assertEquals("Expected output identical to input for string without escapes", input, parseResult); + } + + @Test + public void testEscapeEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' {escape '&'}"; + final String expectedOutput = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' ESCAPE '&'"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {escape ..}", expectedOutput, parseResult); + } + + @Test + public void testFunctionEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE {fn abs(x)} = ?"; + final String expectedOutput = "SELECT * FROM some_table WHERE abs(x) = ?"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {fn ..}", expectedOutput, parseResult); + } + + @Test + public void testDateEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x = {d '2012-12-28'}"; + final String expectedOutput = "SELECT * FROM some_table WHERE x = DATE '2012-12-28'"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {d ..}", expectedOutput, parseResult); + } + + @Test + public void testTimeEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x = {t '22:15:28'}"; + final String expectedOutput = "SELECT * FROM some_table WHERE x = TIME '22:15:28'"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {t ..}", expectedOutput, parseResult); + } + + @Test + public void testTimestampEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x = {ts '2012-12-28 22:15:28'}"; + final String expectedOutput = "SELECT * FROM some_table WHERE x = TIMESTAMP '2012-12-28 22:15:28'"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {ts ..}", expectedOutput, parseResult); + } + + @Test + public void testOuterjoinEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM {oj some_table FULL OUTER JOIN some_other_table ON some_table.x = some_other_table.x}"; + final String expectedOutput = "SELECT * FROM some_table FULL OUTER JOIN some_other_table ON some_table.x = some_other_table.x"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {oj ..}", expectedOutput, parseResult); + } + + @Test + public void testSimpleLimitEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table {limit 10}"; + final String expectedOutput = "SELECT * FROM some_table ROWS 10"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); + } + + @Test + public void testExtendedLimitEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table {limit 10 offset 15}"; + final String expectedOutput = "SELECT * FROM some_table ROWS 15 TO 15+10"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); + } + + @Test + public void testSimpleLimitEscapeWithParameter() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table {limit ?}"; + final String expectedOutput = "SELECT * FROM some_table ROWS ?"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); + } + + @Test + public void testExtendedLimitEscapeRowsParameter() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table {limit ? offset 15}"; + final String expectedOutput = "SELECT * FROM some_table ROWS 15 TO 15+?"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); + } + + /** + * Test of limit escape with the rows and row_offset parameter with a literal value for rows and a parameter for offset_rows. + * <p> + * Expects exception, as parameter for offset_rows is not supported by driver implementation. + * </p> + */ + @Test(expected=FBSQLParseException.class) + public void testExtendedLimitEscapeOffsetParameter() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table {limit 10 offset ?}"; + + parser.parse(input); + fail("Expected parse of limit escape with parameter for offset to fail"); + } + + /** + * Test of limit escape with the rows and row_offset parameter with a parameter for rows and offset_rows. + * <p> + * Expects exception, as parameter for offset_rows is not supported by driver implementation. + * </p> + */ + @Test(expected=FBSQLParseException.class) + public void testExtendedLimitEscapeRowsAndOffsetParameter() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table {limit ? offset ?}"; + + parser.parse(input); + fail("Expected parse of limit escape with parameter for offset to fail"); + } + + @Test + public void testCallEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{call FUNCTION(?,?)}"; + final String expectedOutput = "EXECUTE PROCEDURE FUNCTION(?, ?)"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {call ..}", expectedOutput, parseResult); + } + + @Test + public void testQuestionmarkCallEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{?=call FUNCTION(?,?)}"; + final String expectedOutput = "EXECUTE PROCEDURE FUNCTION(?, ?, ?)"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {call ..}", expectedOutput, parseResult); + } + + @Test + public void testQuestionmarkCallEscapeExtraWhitespace() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{? = call FUNCTION(?,?)}"; + final String expectedOutput = "EXECUTE PROCEDURE FUNCTION(?, ?, ?)"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {call ..}", expectedOutput, parseResult); + } + + @Test(expected=FBSQLParseException.class) + public void testUnsupportedKeyword() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + // NOTE: need to include an existent keyword, otherwise string isn't parsed at all + final String input = "{fn ABS(?)} {doesnotexist xyz}"; + + parser.parse(input); + fail("Non existent keyword should result in an FBSQLParseException"); + } + + @Test(expected=FBSQLParseException.class) + public void testTooManyCurlyBraceOpen() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{escape '&'"; + + parser.parse(input); + fail("Too many open curly braces should result in an FBSQLParseException"); + } + + @Test(expected=FBSQLParseException.class) + public void testTooManyCurlyBraceClose() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{escape '&'}}"; + + parser.parse(input); + fail("Too many close curly braces should result in an FBSQLParseException"); + } + + @Test(expected=FBSQLParseException.class) + public void testCurlyBraceOpenClose() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + // NOTE: need to include an existent keyword, otherwise string isn't parsed at all + final String input = "{escape '&'} {}"; + + parser.parse(input); + fail("Too many close curly braces should result in an FBSQLParseException"); + } + + @Test + public void testAdditionalWhitespaceBetweenEscapeAndParameter() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' {escape '&'}"; + final String expectedOutput = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' ESCAPE '&'"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {escape ..}", expectedOutput, parseResult); + } + + @Test + public void testAdditionalWhitespaceAfterParameter() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' {escape '&' }"; + final String expectedOutput = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' ESCAPE '&'"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for {escape ..}", expectedOutput, parseResult); + } + + @Test + public void testNestedEscapes() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{fn LTRIM({fn RTRIM(' abc ')})}"; + final String expectedOutput = "TRIM(LEADING FROM TRIM(TRAILING FROM ' abc '))"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); + } + + /** + * Tests if the parser preserves whitespace inside parameters (implementation coalesces multiple whitespace characters into one space) + * @throws Exception + */ + @Test + public void testWhitespaceInParameter() throws Exception { + final FBEscapedP... [truncated message content] |
From: <mro...@us...> - 2013-01-04 12:23:23
|
Revision: 57579 http://sourceforge.net/p/firebird/code/57579 Author: mrotteveel Date: 2013-01-04 12:23:18 +0000 (Fri, 04 Jan 2013) Log Message: ----------- Fix stream copying bug + add testcase and cleanup testcases Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java 2013-01-04 10:44:00 UTC (rev 57578) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java 2013-01-04 12:23:18 UTC (rev 57579) @@ -567,7 +567,7 @@ byte[] buffer = new byte[Math.min(bufferlength, length)]; int chunk; try { - while (length > 0 && (chunk = inputStream.read(buffer, 0, Math.min(buffer.length, length))) != 1) { + while (length > 0 && (chunk = inputStream.read(buffer, 0, Math.min(buffer.length, length))) != -1) { os.write(buffer, 0, chunk); length -= chunk; } @@ -628,8 +628,7 @@ length -= chunk; } osw.flush(); - os.flush(); - os.close(); + osw.close(); } catch (IOException ioe) { throw new FBSQLException(ioe); Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java 2013-01-04 10:44:00 UTC (rev 57578) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java 2013-01-04 12:23:18 UTC (rev 57579) @@ -29,6 +29,7 @@ import java.util.Random; import static org.firebirdsql.common.FBTestProperties.*; +import static org.junit.Assert.assertArrayEquals; /** * Describe class <code>TestFBBlobAccess</code> here. @@ -37,6 +38,9 @@ * @version 1.0 */ public class TestFBBlobStream extends FBTestBase { + + private static final Random rnd = new Random(); + public static final String CREATE_TABLE = "CREATE TABLE test_blob(" + " id INTEGER, " + @@ -55,8 +59,6 @@ private Connection connection; - private byte[][] testData; - public TestFBBlobStream(String testName) { super(testName); } @@ -64,8 +66,6 @@ protected void setUp() throws Exception { super.setUp(); - Class.forName(FBDriver.class.getName()); - Properties props = getDefaultPropertiesForConnection(); props.put("isc_dpb_use_stream_blobs", ""); @@ -76,17 +76,6 @@ stmt.executeUpdate(CREATE_TABLE); stmt.execute(CREATE_PROCEDURE); stmt.close(); - - java.util.Random rnd = new java.util.Random(); - - testData = new byte[TEST_ROW_COUNT][0]; - - for (int i = 0; i < testData.length; i++) { - int testLength = rnd.nextInt(100 * 1024) + 128; - testData[i] = new byte[testLength]; - rnd.nextBytes(testData[i]); - } - } protected void tearDown() throws Exception { @@ -104,14 +93,13 @@ PreparedStatement ps = connection.prepareStatement( "INSERT INTO test_blob(id, bin_data) VALUES (?, ?)"); + + int size = generateRandomLength(); + byte[] data = createRandomBytes(size); try { - long start = System.currentTimeMillis(); - - long size = testData[0].length; - ps.setInt(1, 1); - ps.setBytes(2, testData[0]); + ps.setBytes(2, data); ps.executeUpdate(); ps.close(); @@ -129,12 +117,9 @@ FBBlob blob = (FBBlob)rs.getBlob(1); - start = System.currentTimeMillis(); + // Do it repeatedly (TODO: does this make sense) for(int i = 0; i < 1000; i++) - assertTrue("Reported length should be correct.", blob.length() == size); - System.out.println("Getting info took " + - (System.currentTimeMillis() - start)); - + assertEquals("Reported length should be correct.", size, blob.length()); rs.close(); } finally { ps.close(); @@ -152,29 +137,25 @@ PreparedStatement ps = connection.prepareStatement( "INSERT INTO test_blob(id, bin_data) VALUES (?, ?)"); + byte[] data = createRandomBytes(generateRandomLength()); try { ps.setInt(1, 1); - ps.setBytes(2, testData[0]); + ps.setBytes(2, data); ps.executeUpdate(); - } finally { ps.close(); } - connection.commit(); + connection.commit(); try { - ps = connection.prepareStatement( - "SELECT bin_data FROM test_blob WHERE id = ?"); - + ps = connection.prepareStatement("SELECT bin_data FROM test_blob WHERE id = ?"); ps.setInt(1, 1); - ResultSet rs = ps.executeQuery(); assertTrue("Should select at least one row", rs.next()); - FBBlobInputStream in = - (FBBlobInputStream)rs.getBinaryStream(1); + FBBlobInputStream in = (FBBlobInputStream)rs.getBinaryStream(1); int blobSize = (int)in.length(); byte[] fullBlob = new byte[blobSize]; @@ -193,8 +174,8 @@ System.arraycopy(fullBlob, 10, testBlob, 0, blobSize - 10); assertTrue("Full and original blobs must be equal.", - Arrays.equals(testData[0], fullBlob)); - + Arrays.equals(data, fullBlob)); + assertTrue("Truncated and testing blobs must be equal.", Arrays.equals(testBlob, truncatedBlob)); @@ -203,23 +184,26 @@ ps.close(); } } - - - + /** * Test if byte[] are correctly stored and retrieved from database * * @throws Exception if something went wrong. */ public void testFieldTypes() throws Exception { - connection.setAutoCommit(false); PreparedStatement ps = connection.prepareStatement( "INSERT INTO test_blob(id, bin_data) VALUES (?, ?)"); + + byte[][] testData; + testData = new byte[TEST_ROW_COUNT][0]; + for (int i = 0; i < testData.length; i++) { + testData[i] = new byte[generateRandomLength()]; + rnd.nextBytes(testData[i]); + } long start = System.currentTimeMillis(); - long size = 0; for(int i = 0; i < TEST_ROW_COUNT; i++) { @@ -243,42 +227,37 @@ Statement stmt = connection.createStatement(); for (int i = 0; i < 10; i++) { - ResultSet rs = stmt.executeQuery("SELECT id, bin_data FROM test_blob"); - start = System.currentTimeMillis(); - - size = 0; - - try { - int counter = 0; - - while(rs.next()) { - - int id = rs.getInt("id"); - byte[] data = rs.getBytes("bin_data"); - - size += data.length; - - assertTrue( - "Data read from database for id " + id + - " should be equal to generated one.", - java.util.Arrays.equals(testData[id], data)); - - counter++; + ResultSet rs = stmt.executeQuery("SELECT id, bin_data FROM test_blob"); + start = System.currentTimeMillis(); + + size = 0; + + try { + int counter = 0; + while(rs.next()) { + int id = rs.getInt("id"); + byte[] data = rs.getBytes("bin_data"); + + size += data.length; + + assertTrue( + "Data read from database for id " + id + + " should be equal to generated one.", + Arrays.equals(testData[id], data)); + + counter++; + } + + assertEquals("Unexpected number of rows read", TEST_ROW_COUNT, counter); + + duration = System.currentTimeMillis() - start; + + System.out.println("Read " + size + " bytes in " + duration + " ms, " + + "speed " + ((size * 1000 * 1000 / duration / 1024 / 1024) / 1000.0) + " MB/s"); + } finally { + rs.close(); } - - assertTrue( - "Should read " + TEST_ROW_COUNT + - " rows, read " + counter, TEST_ROW_COUNT == counter); - - duration = System.currentTimeMillis() - start; - - System.out.println("Read " + size + " bytes in " + duration + " ms, " + - "speed " + ((size * 1000 * 1000 / duration / 1024 / 1024) / 1000.0) + " MB/s"); - } finally { - rs.close(); -// stmt.close(); } - } stmt.close(); } @@ -291,49 +270,92 @@ public void testEndlessLoop() throws Exception { connection.setAutoCommit(false); - PreparedStatement ps = connection.prepareStatement( - "INSERT INTO test_blob(id, bin_data) VALUES (?, ?)"); + PreparedStatement ps = connection.prepareStatement("INSERT INTO test_blob(id, bin_data) VALUES (?, ?)"); try { ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]); ps.setInt(1, 1); ps.setBinaryStream(2, in, 10); + long startTime = System.currentTimeMillis(); ps.executeUpdate(); + long endTime = System.currentTimeMillis(); + + if (endTime - startTime > 5000) { + fail("Executing update with empty binarystream took longer than 5 seconds"); + } + ps.close(); + connection.commit(); + ps = connection.prepareStatement("SELECT bin_data FROM test_blob WHERE id = ?"); + ps.setInt(1, 1); + + ResultSet rs = ps.executeQuery(); + + assertTrue("Should select at least one row", rs.next()); + byte[] blob = rs.getBytes(1); + + assertTrue("Reported length should be correct.", blob.length == 0); + + rs.close(); + } finally { ps.close(); + } + } + /** + * Check if using only 1 byte from the stream works + * + * @throws Exception if something went wrong. + */ + public void testSingleByteRead() throws Exception { + connection.setAutoCommit(false); + + PreparedStatement ps = connection.prepareStatement("INSERT INTO test_blob(id, bin_data) VALUES (?, ?)"); + + final byte[] testData = new byte[] { 56, 54, 52 }; + try { + ByteArrayInputStream in = new ByteArrayInputStream(testData); + + ps.setInt(1, 1); + ps.setBinaryStream(2, in, 1); + long startTime = System.currentTimeMillis(); + ps.executeUpdate(); + long endTime = System.currentTimeMillis(); + + if (endTime - startTime > 5000) { + fail("Executing update with reading 1 byte from binarystream took longer than 5 seconds"); + } + ps.close(); connection.commit(); - ps = connection.prepareStatement( - "SELECT bin_data FROM test_blob WHERE id = ?"); - + ps = connection.prepareStatement("SELECT bin_data FROM test_blob WHERE id = ?"); ps.setInt(1, 1); ResultSet rs = ps.executeQuery(); assertTrue("Should select at least one row", rs.next()); - byte[] blob = rs.getBytes(1); - assertTrue("Reported length should be correct.", blob.length == 0); + assertEquals("Reported length should be correct.", 1, blob.length); + assertEquals("Unexpected value for first byte", 56, blob[0]); rs.close(); } finally { ps.close(); } - } public void testStreamForLongVarChar() throws Exception { - PreparedStatement ps = connection.prepareCall("{call test_procedure(?, ?)}"); try { - ByteArrayInputStream in = new ByteArrayInputStream(testData[0]); + byte[] data = createRandomBytes(generateRandomLength()); + ByteArrayInputStream in = new ByteArrayInputStream(data); + ps.setInt(1, 1); - ps.setBinaryStream(2, in, testData[0].length); + ps.setBinaryStream(2, in, data.length); ps.execute(); @@ -343,21 +365,18 @@ "SELECT id, char_data FROM test_blob WHERE id = 1"); assertTrue("Should select data from table", rs.next()); - assertTrue("Value should be correct", Arrays.equals(rs.getBytes(2), testData[0])); + assertTrue("Value should be correct", Arrays.equals(rs.getBytes(2), data)); assertTrue("Should not have more rows.", !rs.next()); } finally { stmt.close(); } - } finally { ps.close(); } } public void testWriteBytes() throws Exception { - byte[] data = new byte[75 * 1024]; // should be more than 64k - Random rnd = new Random(); - rnd.nextBytes(data); + final byte[] data = createRandomBytes(75 * 1024); // should be more than 64k FirebirdConnection fbConnection = (FirebirdConnection)connection; fbConnection.setAutoCommit(false); @@ -390,6 +409,25 @@ } finally { stmt.close(); } - } + + /** + * Creates a byte array with random bytes with the specified length. + * + * @param length Requested length + * @return Byte array of length filled with random bytes + */ + private static byte[] createRandomBytes(int length) { + byte[] randomBytes = new byte[length]; + rnd.nextBytes(randomBytes); + return randomBytes; + } + + /** + * Generates a random length betwen 128 and 102528 + * @return generated length + */ + private static int generateRandomLength() { + return rnd.nextInt(100 * 1024) + 128; + } } \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-01-04 13:40:22
|
Revision: 57580 http://sourceforge.net/p/firebird/code/57580 Author: mrotteveel Date: 2013-01-04 13:40:17 +0000 (Fri, 04 Jan 2013) Log Message: ----------- Do not process JDBC escapes inside line or block comments Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java 2013-01-04 12:23:18 UTC (rev 57579) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java 2013-01-04 13:40:17 UTC (rev 57580) @@ -113,6 +113,11 @@ switch (state) { case NORMAL_STATE: case LITERAL_STATE: + case START_LINE_COMMENT: + case LINE_COMMENT: + case START_BLOCK_COMMENT: + case BLOCK_COMMENT: + case END_BLOCK_COMMENT: buffer.append(currentChar); break; case ESCAPE_ENTER_STATE: @@ -123,8 +128,9 @@ if (bufferStack.isEmpty()) { throw new FBSQLParseException("Unbalanced JDBC escape, too many '}'"); } - escapeToNative(bufferStack.peek(), buffer.toString()); + String escapeText = buffer.toString(); buffer = bufferStack.pop(); + escapeToNative(buffer, escapeText); break; default: throw new FBSQLParseException("Unexpected parser state " + state); @@ -371,6 +377,10 @@ return ESCAPE_ENTER_STATE; case '}': return ESCAPE_EXIT_STATE; + case '-': + return START_LINE_COMMENT; + case '/': + return START_BLOCK_COMMENT; default: return NORMAL_STATE; } @@ -391,10 +401,18 @@ ESCAPE_ENTER_STATE { @Override protected ParserState nextState(char inputChar) throws FBSQLParseException { - if (inputChar == '}') { - throw new FBSQLParseException("Did not expect end of JDBC escape at this point"); + switch (inputChar) { + case '?': // start of {?= call ...} + case 'c': // start of {call ...} + case 't': // start of {t ...} or {ts ...} + case 'e': // start of {escape ...} + case 'f': // start of {fn ...} + case 'o': // start of {oj ...} + case 'l': // start of {limit ...} + return NORMAL_STATE; + default: + throw new FBSQLParseException("Unexpected first character inside JDBC escape: " + inputChar); } - return NORMAL_STATE; } }, /** @@ -403,8 +421,62 @@ ESCAPE_EXIT_STATE { @Override protected ParserState nextState(char inputChar) throws FBSQLParseException { - return (inputChar == '}') ? ESCAPE_EXIT_STATE : NORMAL_STATE; + switch (inputChar) { + case '}': + return ESCAPE_EXIT_STATE; + case '-': + return START_LINE_COMMENT; + case '/': + return START_BLOCK_COMMENT; + default: + return NORMAL_STATE; + } } + }, + /** + * Potential start of line comment + */ + START_LINE_COMMENT { + @Override + protected ParserState nextState(char inputChar) throws FBSQLParseException { + return (inputChar == '-') ? LINE_COMMENT : NORMAL_STATE; + } + }, + /** + * Line comment + */ + LINE_COMMENT { + @Override + protected ParserState nextState(char inputChar) throws FBSQLParseException { + return (inputChar == '\n') ? NORMAL_STATE : LINE_COMMENT; + } + }, + /** + * Potential start of block comment + */ + START_BLOCK_COMMENT { + @Override + protected ParserState nextState(char inputChar) throws FBSQLParseException { + return (inputChar == '*') ? BLOCK_COMMENT : NORMAL_STATE; + } + }, + /** + * Block comment + */ + BLOCK_COMMENT { + @Override + protected ParserState nextState(char inputChar) throws FBSQLParseException { + return (inputChar == '*') ? END_BLOCK_COMMENT : BLOCK_COMMENT; + } + }, + /** + * Potential block comment end + */ + END_BLOCK_COMMENT { + @Override + protected ParserState nextState(char inputChar) throws FBSQLParseException { + return (inputChar == '/') ? NORMAL_STATE : BLOCK_COMMENT; + } }; /** @@ -414,7 +486,7 @@ * Input character * @return Next state * @throws FBSQLParseException - * For incorrect states during parsing + * For incorrect character for current state during parsing */ protected abstract ParserState nextState(char inputChar) throws FBSQLParseException; } Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java 2013-01-04 12:23:18 UTC (rev 57579) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBBlobStream.java 2013-01-04 13:40:17 UTC (rev 57580) @@ -29,7 +29,6 @@ import java.util.Random; import static org.firebirdsql.common.FBTestProperties.*; -import static org.junit.Assert.assertArrayEquals; /** * Describe class <code>TestFBBlobAccess</code> here. Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java 2013-01-04 12:23:18 UTC (rev 57579) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java 2013-01-04 13:40:17 UTC (rev 57580) @@ -269,7 +269,6 @@ /** * Tests if the parser preserves whitespace inside parameters (implementation coalesces multiple whitespace characters into one space) - * @throws Exception */ @Test public void testWhitespaceInParameter() throws Exception { @@ -280,4 +279,30 @@ String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } + + /** + * Tests if the parser does not process JDBC escapes inside a line comment + */ + @Test + public void testEscapeInLineComment() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{fn LTRIM(CAST( ?\tAS VARCHAR(10)))} --{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\n{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}"; + final String expectedOutput = "TRIM(LEADING FROM CAST( ? AS VARCHAR(10))) --{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\nTRIM(LEADING FROM CAST( ? AS VARCHAR(10)))"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); + } + + /** + * Tests if the parser does not process JDBC escapes inside a block comment + */ + @Test + public void testEscapeInBlockComment() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "{fn LTRIM(CAST( ?\tAS VARCHAR(10)))} /*{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\n*/{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}"; + final String expectedOutput = "TRIM(LEADING FROM CAST( ? AS VARCHAR(10))) /*{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\n*/TRIM(LEADING FROM CAST( ? AS VARCHAR(10)))"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); + } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-01-04 14:31:21
|
Revision: 57581 http://sourceforge.net/p/firebird/code/57581 Author: mrotteveel Date: 2013-01-04 14:31:19 +0000 (Fri, 04 Jan 2013) Log Message: ----------- Make sure parser does not ignore escapes and comments directly after a potential line comment, block comment or escape exit. Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java 2013-01-04 13:40:17 UTC (rev 57580) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java 2013-01-04 14:31:19 UTC (rev 57581) @@ -404,6 +404,7 @@ switch (inputChar) { case '?': // start of {?= call ...} case 'c': // start of {call ...} + case 'd': // start of {d ...} case 't': // start of {t ...} or {ts ...} case 'e': // start of {escape ...} case 'f': // start of {fn ...} @@ -421,16 +422,7 @@ ESCAPE_EXIT_STATE { @Override protected ParserState nextState(char inputChar) throws FBSQLParseException { - switch (inputChar) { - case '}': - return ESCAPE_EXIT_STATE; - case '-': - return START_LINE_COMMENT; - case '/': - return START_BLOCK_COMMENT; - default: - return NORMAL_STATE; - } + return (inputChar == '}') ? ESCAPE_EXIT_STATE : NORMAL_STATE.nextState(inputChar); } }, /** @@ -439,7 +431,7 @@ START_LINE_COMMENT { @Override protected ParserState nextState(char inputChar) throws FBSQLParseException { - return (inputChar == '-') ? LINE_COMMENT : NORMAL_STATE; + return (inputChar == '-') ? LINE_COMMENT : NORMAL_STATE.nextState(inputChar); } }, /** @@ -457,7 +449,7 @@ START_BLOCK_COMMENT { @Override protected ParserState nextState(char inputChar) throws FBSQLParseException { - return (inputChar == '*') ? BLOCK_COMMENT : NORMAL_STATE; + return (inputChar == '*') ? BLOCK_COMMENT : NORMAL_STATE.nextState(inputChar); } }, /** Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java 2013-01-04 13:40:17 UTC (rev 57580) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java 2013-01-04 14:31:19 UTC (rev 57581) @@ -305,4 +305,30 @@ String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } + + /** + * Tests if the parser correctly processes an escape that is directly after a '-' (potential start of line comment). + */ + @Test + public void testLineCommentStartFollowedByEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "6-{fn EXP(2)}"; + final String expectedOutput = "6-EXP(2)"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); + } + + /** + * Tests if the parser correctly processes an escape that is directly after a '/' (potential start of block comment). + */ + @Test + public void testBlockCommentStartFollowedByEscape() throws Exception { + final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); + final String input = "6/{fn EXP(2)}"; + final String expectedOutput = "6/EXP(2)"; + + String parseResult = parser.parse(input); + assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); + } } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-03-16 09:15:54
|
Revision: 57782 http://sourceforge.net/p/firebird/code/57782 Author: mrotteveel Date: 2013-03-16 09:15:51 +0000 (Sat, 16 Mar 2013) Log Message: ----------- * Make GDSServerVersion fully immutable * Add test for serialization * Add test for parse failure * Add license to test identical to license of GDSServerVersion Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java Added Paths: ----------- client-java/trunk/src/test/org/firebirdsql/gds/impl/TestGDSServerVersion.java Removed Paths: ------------- client-java/trunk/src/test/org/firebirdsql/gds/TestGDSServerVersion.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java 2013-03-16 01:15:41 UTC (rev 57781) +++ client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java 2013-03-16 09:15:51 UTC (rev 57782) @@ -33,18 +33,21 @@ * in response to the <code>isc_info_firebird_version</code> information call. * Expected version format is: * <p> - * <platform>-<type><majorVersion>.<minorVersion>.<variant>.<buildNum> <serverName>[,<extended server info>] + * <code><platform>-<type><majorVersion>.<minorVersion>.<variant>.<buildNum> <serverName>[,<extended server info>]</code> * </p> + * <p> * where <code>platform</code> is a two-character platform identification string, * Windows for example is "WI", <code>type</code> is one of the three characters: * "V" - production version, "T" - beta version, "X" - development version. - * + * </p> */ public class GDSServerVersion implements Serializable { + + // TODO Document why this class is serializable (or remove it) + + private static final long serialVersionUID = -3401092369588765195L; - private static final long serialVersionUID = -153657557318248541L; - - public static final String TYPE_PRODUCTION = "V"; + public static final String TYPE_PRODUCTION = "V"; public static final String TYPE_BETA = "T"; public static final String TYPE_DEVELOPMENT = "X"; @@ -61,21 +64,32 @@ private static final int SERVER_NAME_IDX = 8; private static final int EXTENDED_INFO_IDX = 9; - private String rawStr; + private final String rawStr; - private String platform; - private String type; + private final String platform; + private final String type; - private String fullVersion; - private int majorVersion; - private int minorVersion; - private int variant; - private int buildNumber; + private final String fullVersion; + private final int majorVersion; + private final int minorVersion; + private final int variant; + private final int buildNumber; - private String serverName; - private String extendedServerName; + private final String serverName; + private final String extendedServerName; - private GDSServerVersion() { + private GDSServerVersion(String rawStr, String platform, String type, String fullVersion, int majorVersion, + int minorVersion, int variant, int buildNumber, String serverName, String extendedServerName) { + this.rawStr = rawStr; + this.platform = platform; + this.type = type; + this.fullVersion = fullVersion; + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.variant = variant; + this.buildNumber = buildNumber; + this.serverName = serverName; + this.extendedServerName = extendedServerName; } public int getBuildNumber() { @@ -140,26 +154,24 @@ * @throws GDSServerVersionException if versionString does not match expected pattern */ public static GDSServerVersion parseRawVersion(String versionString) throws GDSServerVersionException { - Matcher matcher = VERSION_PATTERN.matcher(versionString); - if (!matcher.matches()) { - throw new GDSServerVersionException("Version string does not match expected format"); - } - - GDSServerVersion version = new GDSServerVersion(); - - version.rawStr = versionString; - - version.serverName = matcher.group(SERVER_NAME_IDX); - version.extendedServerName = matcher.group(EXTENDED_INFO_IDX); - version.platform = matcher.group(PLATFORM_IDX); - version.type = matcher.group(TYPE_IDX); + Matcher matcher = VERSION_PATTERN.matcher(versionString); + if (!matcher.matches()) { + throw new GDSServerVersionException(String.format("Version string \"%s\" does not match expected format \"%s\"", + versionString, VERSION_PATTERN)); + } - version.fullVersion = matcher.group(FULL_VERSION_IDX); - version.majorVersion = Integer.parseInt(matcher.group(MAJOR_IDX)); - version.minorVersion = Integer.parseInt(matcher.group(MINOR_IDX)); - version.variant = Integer.parseInt(matcher.group(VARIANT_IDX)); - version.buildNumber = Integer.parseInt(matcher.group(BUILD_IDX)); - - return version; + GDSServerVersion version = new GDSServerVersion( + versionString, + matcher.group(PLATFORM_IDX), + matcher.group(TYPE_IDX), + matcher.group(FULL_VERSION_IDX), + Integer.parseInt(matcher.group(MAJOR_IDX)), + Integer.parseInt(matcher.group(MINOR_IDX)), + Integer.parseInt(matcher.group(VARIANT_IDX)), + Integer.parseInt(matcher.group(BUILD_IDX)), + matcher.group(SERVER_NAME_IDX), + matcher.group(EXTENDED_INFO_IDX)); + + return version; } } Deleted: client-java/trunk/src/test/org/firebirdsql/gds/TestGDSServerVersion.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/TestGDSServerVersion.java 2013-03-16 01:15:41 UTC (rev 57781) +++ client-java/trunk/src/test/org/firebirdsql/gds/TestGDSServerVersion.java 2013-03-16 09:15:51 UTC (rev 57782) @@ -1,66 +0,0 @@ -package org.firebirdsql.gds; - -import org.firebirdsql.gds.impl.GDSServerVersion; - -import junit.framework.TestCase; - -public class TestGDSServerVersion extends TestCase { - - private static final String TEST_VERSION_15 = - "WI-V1.5.2.4731 Firebird 1.5,WI-V1.5.2.4731 Firebird 1.5/tcp (PCRORO)/P10"; - - private static final String TEST_VERSION_21 = - "WI-V2.1.3.18185 Firebird 2.1-WI-V2.1.3.18185 Firebird 2.1/tcp (Ramona)/P10"; - - private static final String TEST_NO_EXTENDED_INFO = - "WI-V2.1.3.18185 Firebird 2.1"; - - public TestGDSServerVersion(String arg0) { - super(arg0); - } - - public void testParse15() throws Exception { - GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_VERSION_15); - - assertEquals("WI", version.getPlatform()); - assertEquals("V", version.getType()); - assertEquals(1, version.getMajorVersion()); - assertEquals(5, version.getMinorVersion()); - assertEquals(2, version.getVariant()); - assertEquals(4731, version.getBuildNumber()); - assertEquals("Firebird 1.5", version.getServerName()); - assertEquals("WI-V1.5.2.4731 Firebird 1.5/tcp (PCRORO)/P10", - version.getExtendedServerName()); - assertEquals("WI-V1.5.2.4731", version.getFullVersion()); - } - - public void testParse21() throws Exception { - GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_VERSION_21); - - assertEquals("WI", version.getPlatform()); - assertEquals("V", version.getType()); - assertEquals(2, version.getMajorVersion()); - assertEquals(1, version.getMinorVersion()); - assertEquals(3, version.getVariant()); - assertEquals(18185, version.getBuildNumber()); - assertEquals("Firebird 2.1", version.getServerName()); - assertEquals("WI-V2.1.3.18185 Firebird 2.1/tcp (Ramona)/P10", - version.getExtendedServerName()); - assertEquals("WI-V2.1.3.18185", version.getFullVersion()); - } - - public void testParseNoExtendedInfo() throws Exception { - GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_NO_EXTENDED_INFO); - - assertEquals("WI", version.getPlatform()); - assertEquals("V", version.getType()); - assertEquals(2, version.getMajorVersion()); - assertEquals(1, version.getMinorVersion()); - assertEquals(3, version.getVariant()); - assertEquals(18185, version.getBuildNumber()); - assertEquals("Firebird 2.1", version.getServerName()); - assertEquals(null, - version.getExtendedServerName()); - assertEquals("WI-V2.1.3.18185", version.getFullVersion()); - } -} Copied: client-java/trunk/src/test/org/firebirdsql/gds/impl/TestGDSServerVersion.java (from rev 57643, client-java/trunk/src/test/org/firebirdsql/gds/TestGDSServerVersion.java) =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/impl/TestGDSServerVersion.java (rev 0) +++ client-java/trunk/src/test/org/firebirdsql/gds/impl/TestGDSServerVersion.java 2013-03-16 09:15:51 UTC (rev 57782) @@ -0,0 +1,120 @@ +/* + * Public Firebird Java API. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.firebirdsql.gds.impl; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.junit.Test; + +public class TestGDSServerVersion { + + private static final String TEST_VERSION_15 = + "WI-V1.5.2.4731 Firebird 1.5,WI-V1.5.2.4731 Firebird 1.5/tcp (PCRORO)/P10"; + + private static final String TEST_VERSION_21 = + "WI-V2.1.3.18185 Firebird 2.1-WI-V2.1.3.18185 Firebird 2.1/tcp (Ramona)/P10"; + + private static final String TEST_NO_EXTENDED_INFO = + "WI-V2.1.3.18185 Firebird 2.1"; + + private static final String TEST_INCORRECT_FORMAT = + "WI-V2.5.2a.26540 Firebird 2.5"; + + @Test + public void testParse15() throws Exception { + GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_VERSION_15); + + assertEquals("WI", version.getPlatform()); + assertEquals("V", version.getType()); + assertEquals(1, version.getMajorVersion()); + assertEquals(5, version.getMinorVersion()); + assertEquals(2, version.getVariant()); + assertEquals(4731, version.getBuildNumber()); + assertEquals("Firebird 1.5", version.getServerName()); + assertEquals("WI-V1.5.2.4731 Firebird 1.5/tcp (PCRORO)/P10", + version.getExtendedServerName()); + assertEquals("WI-V1.5.2.4731", version.getFullVersion()); + } + + @Test + public void testParse21() throws Exception { + GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_VERSION_21); + + assertEquals("WI", version.getPlatform()); + assertEquals("V", version.getType()); + assertEquals(2, version.getMajorVersion()); + assertEquals(1, version.getMinorVersion()); + assertEquals(3, version.getVariant()); + assertEquals(18185, version.getBuildNumber()); + assertEquals("Firebird 2.1", version.getServerName()); + assertEquals("WI-V2.1.3.18185 Firebird 2.1/tcp (Ramona)/P10", + version.getExtendedServerName()); + assertEquals("WI-V2.1.3.18185", version.getFullVersion()); + } + + @Test + public void testParseNoExtendedInfo() throws Exception { + GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_NO_EXTENDED_INFO); + + assertEquals("WI", version.getPlatform()); + assertEquals("V", version.getType()); + assertEquals(2, version.getMajorVersion()); + assertEquals(1, version.getMinorVersion()); + assertEquals(3, version.getVariant()); + assertEquals(18185, version.getBuildNumber()); + assertEquals("Firebird 2.1", version.getServerName()); + assertEquals(null, + version.getExtendedServerName()); + assertEquals("WI-V2.1.3.18185", version.getFullVersion()); + } + + @Test(expected = GDSServerVersionException.class) + public void testIncorrectFormat() throws Exception { + GDSServerVersion.parseRawVersion(TEST_INCORRECT_FORMAT); + } + + @Test + public void testSerializable() throws Exception { + final GDSServerVersion version = GDSServerVersion.parseRawVersion(TEST_VERSION_21); + + // Serialize object + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objectOut = new ObjectOutputStream(out); + objectOut.writeObject(version); + + // Deserialize object + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + ObjectInputStream objectIn = new ObjectInputStream(in); + + GDSServerVersion serializedVersion = (GDSServerVersion) objectIn.readObject(); + + assertEquals("Serialized version should be equal to original", version, serializedVersion); + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-03-30 14:35:19
|
Revision: 57877 http://sourceforge.net/p/firebird/code/57877 Author: mrotteveel Date: 2013-03-30 14:35:16 +0000 (Sat, 30 Mar 2013) Log Message: ----------- Misc cleanup Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdResultSet.java client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdStatement.java client-java/trunk/src/test/org/firebirdsql/jca/TestFBConnection.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java 2013-03-30 14:34:50 UTC (rev 57876) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java 2013-03-30 14:35:16 UTC (rev 57877) @@ -39,7 +39,7 @@ * @author <a href="mailto:rro...@us...">Roman Rokytskyy</a> * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> */ -public class FBResultSet implements FirebirdResultSet, Synchronizable, FBObjectListener.FetcherListener { +public class FBResultSet implements ResultSet, FirebirdResultSet, Synchronizable, FBObjectListener.FetcherListener { private FBStatement fbStatement; private FBFetcher fbFetcher; @@ -47,9 +47,9 @@ protected GDSHelper gdsHelper; - public XSQLVAR[] xsqlvars; + protected XSQLVAR[] xsqlvars; - public byte[][] row; + protected byte[][] row; private int maxRows; Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java 2013-03-30 14:34:50 UTC (rev 57876) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java 2013-03-30 14:35:16 UTC (rev 57877) @@ -186,7 +186,7 @@ protected void finalize() throws Throwable { if (!closed) - close(true); + close(); } public void completeStatement() throws SQLException { Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdResultSet.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdResultSet.java 2013-03-30 14:34:50 UTC (rev 57876) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdResultSet.java 2013-03-30 14:35:16 UTC (rev 57877) @@ -1,4 +1,6 @@ /* + * $Id$ + * * Firebird Open Source J2ee connector - jdbc driver, public Firebird-specific * JDBC extensions. * Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdStatement.java 2013-03-30 14:34:50 UTC (rev 57876) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FirebirdStatement.java 2013-03-30 14:35:16 UTC (rev 57877) @@ -1,4 +1,6 @@ /* + * $Id$ + * * Firebird Open Source J2ee connector - jdbc driver, public Firebird-specific * JDBC extensions. * Modified: client-java/trunk/src/test/org/firebirdsql/jca/TestFBConnection.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jca/TestFBConnection.java 2013-03-30 14:34:50 UTC (rev 57876) +++ client-java/trunk/src/test/org/firebirdsql/jca/TestFBConnection.java 2013-03-30 14:35:16 UTC (rev 57877) @@ -1,4 +1,6 @@ - /* +/* + * $Id$ + * * Firebird Open Source J2ee connector - jdbc driver * * Distributable under LGPL license. @@ -42,12 +44,9 @@ } public static Test suite() { - return new TestSuite(TestFBConnection.class); } - - public void testCreateC() throws Exception { if (log != null) log.info("testCreateC"); FBManagedConnectionFactory mcf = initMcf(); @@ -112,9 +111,5 @@ if (ex != null) { throw ex; } - } - - - } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-03-30 15:33:50
|
Revision: 57879 http://sourceforge.net/p/firebird/code/57879 Author: mrotteveel Date: 2013-03-30 15:33:46 +0000 (Sat, 30 Mar 2013) Log Message: ----------- (partial) fix for JDBC-304 and JDBC-305 Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java client-java/trunk/src/main/org/firebirdsql/jdbc/InternalTransactionCoordinator.java client-java/trunk/src/openoffice/org/firebirdsql/jdbc/oo/OOStatement.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java 2013-03-30 15:14:44 UTC (rev 57878) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSet.java 2013-03-30 15:33:46 UTC (rev 57879) @@ -31,6 +31,7 @@ import org.firebirdsql.gds.impl.AbstractIscStmtHandle; import org.firebirdsql.gds.impl.GDSHelper; import org.firebirdsql.jdbc.field.*; +import org.firebirdsql.util.SQLExceptionChainBuilder; /** * Implementation of {@link ResultSet} interface. @@ -215,7 +216,7 @@ * @throws SQLException if statement is closed. */ protected void checkCursorMove() throws SQLException { - if (closed && rsHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) + if (isClosed()) throw new FBSQLException("The result set is closed"); closeFields(); @@ -229,9 +230,19 @@ protected void closeFields() throws SQLException { wasNullValid = false; + SQLExceptionChainBuilder<SQLException> chain = new SQLExceptionChainBuilder<SQLException>(); // close current fields, so that resources are freed. - for(int i = 0; i < fields.length; i++) - fields[i].close(); + for(int i = 0; i < fields.length; i++) { + try { + fields[i].close(); + } catch (SQLException ex) { + chain.append(ex); + } + } + + if (chain.hasException()) { + throw chain.getException(); + } } /* (non-Javadoc) @@ -291,32 +302,48 @@ } void close(boolean notifyListener) throws SQLException { - wasNullValid = false; + if (isClosed()) return; closed = true; + SQLExceptionChainBuilder<SQLException> chain = new SQLExceptionChainBuilder<SQLException>(); try { - - for(int i = 0; i < fields.length; i++) - fields[i].close(); - + closeFields(); + } catch (SQLException ex) { + chain.append(ex); } finally { + try { + if (fbFetcher != null) { + try { + fbFetcher.close(); + } catch (SQLException ex) { + chain.append(ex); + } + } - if (fbFetcher != null) { - fbFetcher.close(); + if (rowUpdater != null) { + try { + rowUpdater.close(); + } catch (SQLException ex) { + chain.append(ex); + } + } - if (rowUpdater != null) - rowUpdater.close(); - - if (notifyListener) { - if (listener != null) + if (notifyListener && listener != null) { + try { listener.resultSetClosed(this); + } catch (SQLException ex) { + chain.append(ex); + } } - + } finally { + fbFetcher = null; + rowUpdater = null; } - - if (rsHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) - fbFetcher = null; } + + if (chain.hasException()) { + throw chain.getException(); + } } /** @@ -631,7 +658,7 @@ * Factory method for the field access objects */ public FBField getField(int columnIndex, boolean checkRowPosition) throws SQLException { - if (closed && rsHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) + if (isClosed()) throw new FBSQLException("The resultSet is closed"); if (checkRowPosition && row == null && rowUpdater == null) @@ -657,7 +684,7 @@ * @throws SQLException if the field cannot be retrieved */ public FBField getField(String columnName) throws SQLException { - if (closed && rsHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) + if (isClosed()) throw new FBSQLException("The resultSet is closed"); if (row == null && rowUpdater == null) Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java 2013-03-30 15:14:44 UTC (rev 57878) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBStatement.java 2013-03-30 15:33:46 UTC (rev 57879) @@ -190,7 +190,13 @@ } public void completeStatement() throws SQLException { - closeResultSet(false); + completeStatement(CompletionReason.OTHER); + } + + public void completeStatement(CompletionReason reason) throws SQLException { + if (currentRs != null && (reason != CompletionReason.COMMIT || currentRs.getHoldability() == ResultSet.CLOSE_CURSORS_AT_COMMIT)) { + closeResultSet(false); + } if (!completed) notifyStatementCompleted(); @@ -231,14 +237,13 @@ } protected void notifyStatementStarted(boolean closeResultSet) throws SQLException { - if (closeResultSet) closeResultSet(false); // notify listener that statement execution is about to start statementListener.executionStarted(this); - this.completed = false; + completed = false; } protected void notifyStatementCompleted() throws SQLException { @@ -246,7 +251,7 @@ } protected void notifyStatementCompleted(boolean success) throws SQLException { - this.completed = true; + completed = true; statementListener.statementCompleted(this, success); } @@ -582,8 +587,7 @@ } void close(boolean ignoreAlreadyClosed) throws SQLException { - - if (closed) { + if (isClosed()) { if (ignoreAlreadyClosed) return; @@ -597,7 +601,6 @@ try { try { closeResultSet(false); - } finally { //may need ensureTransaction? if (fixedStmt.isValid()) @@ -1014,18 +1017,13 @@ } public boolean getMoreResults(int mode) throws SQLException { - hasMoreResults = false; boolean closeResultSet = mode == Statement.CLOSE_ALL_RESULTS || mode == Statement.CLOSE_CURRENT_RESULT; if (closeResultSet && currentRs != null) { - try { - currentRs.close(); - } finally { - currentRs = null; - } + closeResultSet(true); } return hasMoreResults; @@ -1315,7 +1313,7 @@ */ public Connection getConnection() throws SQLException { checkValidity(); - return statementListener.getConnection(); + return connection; } //package level @@ -1323,13 +1321,18 @@ void closeResultSet(boolean notifyListener) throws SQLException { boolean wasCompleted = completed; - if (currentRs != null) { - currentRs.close(notifyListener); - currentRs = null; + try { + if (currentRs != null) { + try { + currentRs.close(notifyListener); + } finally { + currentRs = null; + } + } + } finally { + if (notifyListener && !wasCompleted) + statementListener.statementCompleted(this); } - - if (notifyListener && !wasCompleted) - statementListener.statementCompleted(this); } public void forgetResultSet() { //yuck should be package @@ -1482,7 +1485,6 @@ return fixedStmt.getStatementType(); } - private void populateStatementInfo() throws FBSQLException { if (fixedStmt.getExecutionPlan() == null){ try { @@ -1508,4 +1510,17 @@ public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new FBDriverNotCapableException(); } + + /** + * Reasons for statement completion. This is intended for the {@link InternalTransactionCoordinator} to + * notify the statement on why it should complete. + * <p> + * TODO: This is a bit of kludge to fix <a href="http://tracker.firebirdsql.org/browse/JDBC-304">JDBC-304</a> in 2.2.x, might need some more polish for 2.3 + * </p> + * @since 2.2.3 + */ + protected enum CompletionReason { + COMMIT, + OTHER; + } } Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/InternalTransactionCoordinator.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/InternalTransactionCoordinator.java 2013-03-30 15:14:44 UTC (rev 57878) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/InternalTransactionCoordinator.java 2013-03-30 15:33:46 UTC (rev 57879) @@ -27,6 +27,7 @@ import javax.resource.ResourceException; import org.firebirdsql.jca.FirebirdLocalTransaction; +import org.firebirdsql.jdbc.FBStatement.CompletionReason; import org.firebirdsql.util.SQLExceptionChainBuilder; /** @@ -61,13 +62,15 @@ public FBConnection getConnection() throws SQLException { return coordinator.getConnection(); } + public void statementClosed(FBStatement stmt) throws SQLException { - coordinator.statementClosed(stmt); } + public void statementCompleted(FBStatement stmt) throws SQLException { statementCompleted(stmt, true); } + public void statementCompleted(FBStatement stmt, boolean success) throws SQLException { Object syncObject = getSynchronizationObject(); synchronized (syncObject) { @@ -81,18 +84,21 @@ coordinator.executionCompleted(blob); } } + public void executionStarted(FirebirdBlob blob) throws SQLException { Object syncObject = getSynchronizationObject(); synchronized (syncObject) { coordinator.executionStarted(blob); } } + public void ensureTransaction() throws SQLException { Object syncObject = getSynchronizationObject(); synchronized (syncObject) { coordinator.ensureTransaction(); } } + public void commit() throws SQLException { Object syncObject = getSynchronizationObject(); synchronized (syncObject) { @@ -111,10 +117,9 @@ Object syncObject = getSynchronizationObject(); synchronized (syncObject) { if (this.coordinator != null) { - this.coordinator.completeStatements(); + this.coordinator.completeStatements(CompletionReason.COMMIT); coordinator.setStatements(this.coordinator.getStatements()); } - this.coordinator = coordinator; } } @@ -146,7 +151,8 @@ protected void setStatements(Collection<FBStatement> statements) { this.statements.addAll(statements); } - protected void completeStatements() throws SQLException { + + protected void completeStatements(CompletionReason reason) throws SQLException { SQLExceptionChainBuilder<SQLException> chain = new SQLExceptionChainBuilder<SQLException>(); // we have to loop through the array, since the @@ -159,7 +165,7 @@ (FBStatement)statementsToComplete[i]; try { - statement.completeStatement(); + statement.completeStatement(reason); } catch(SQLException ex) { chain.append(ex); } @@ -202,8 +208,8 @@ * @see org.firebirdsql.jdbc.FBObjectListener.StatementListener#executionStarted(java.sql.Statement) */ public void executionStarted(FBStatement stmt) throws SQLException { - List<FBStatement> tempList = new ArrayList<FBStatement>(statements); + SQLExceptionChainBuilder<SQLException> chain = new SQLExceptionChainBuilder<SQLException>(); // complete all open statements for the connection // (there should be only one anyway) @@ -215,15 +221,19 @@ iter.remove(); continue; } - - tempStatement.completeStatement(); + // Autocommit, so reason of completion is COMMIT + try { + tempStatement.completeStatement(CompletionReason.COMMIT); + } catch (SQLException e) { + chain.append(e); + } } statements.removeAll(tempList); + if (chain.hasException()) { + throw chain.getException(); + } - if (statements.contains(stmt)) - return; - if (!statements.contains(stmt)) statements.add(stmt); @@ -285,9 +295,7 @@ * @see org.firebirdsql.jdbc.FBObjectListener.BlobListener#executionCompleted(org.firebirdsql.jdbc.FirebirdBlob) */ public void executionCompleted(FirebirdBlob blob) throws SQLException { - // do nothing, next line exists only for breakpoint - @SuppressWarnings("unused") - int i = 0; + } /* (non-Javadoc) @@ -316,7 +324,6 @@ public static class LocalTransactionCoordinator extends AbstractTransactionCoordinator { - /** * @param connection * @param localTransaction @@ -325,6 +332,7 @@ FirebirdLocalTransaction localTransaction) { super(connection, localTransaction); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.InternalTransactionCoordinator.AbstractTransactionCoordinator#ensureTransaction() */ @@ -336,6 +344,7 @@ throw new FBSQLException(ex); } } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.InternalTransactionCoordinator#commit() */ @@ -347,6 +356,7 @@ throw new FBSQLException(ex); } } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.InternalTransactionCoordinator#rollback() */ @@ -358,12 +368,14 @@ throw new FBSQLException(ex); } } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.StatementListener#executionStarted(java.sql.Statement) */ public void executionStarted(FBStatement stmt) throws SQLException { ensureTransaction(); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.StatementListener#statementClosed(java.sql.Statement) */ @@ -371,6 +383,7 @@ stmt.completeStatement(); connection.notifyStatementClosed(stmt); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.StatementListener#statementCompleted(java.sql.Statement) */ @@ -386,8 +399,9 @@ * @see org.firebirdsql.jdbc.FBObjectListener.BlobListener#executionCompleted(org.firebirdsql.jdbc.FirebirdBlob) */ public void executionCompleted(FirebirdBlob blob) throws SQLException { - // empty + } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.BlobListener#executionStarted(org.firebirdsql.jdbc.FirebirdBlob) */ @@ -398,7 +412,6 @@ public static class ManagedTransactionCoordinator extends LocalTransactionCoordinator { - /** * Create instance of this class for the specified connection. * @@ -469,7 +482,7 @@ * @see org.firebirdsql.jdbc.FBObjectListener.BlobListener#executionStarted(org.firebirdsql.jdbc.FirebirdBlob) */ public void executionStarted(FirebirdBlob blob) throws SQLException { - // empty + } } @@ -489,34 +502,38 @@ public MetaDataTransactionCoordinator() { super(null, null); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.InternalTransactionCoordinator.AbstractTransactionCoordinator#ensureTransaction() */ public void ensureTransaction() throws SQLException { throw new UnsupportedOperationException(); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.InternalTransactionCoordinator#commit() */ public void commit() throws SQLException { throw new UnsupportedOperationException(); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.InternalTransactionCoordinator#rollback() */ public void rollback() throws SQLException { throw new UnsupportedOperationException(); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.StatementListener#executionStarted(java.sql.Statement) */ public void executionStarted(FBStatement stmt) throws SQLException { - if (tc == null) return; tc.ensureTransaction(); } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.StatementListener#statementClosed(java.sql.Statement) */ @@ -527,6 +544,7 @@ stmt.completeStatement(); tc.coordinator.connection.notifyStatementClosed(stmt); } + public void statementCompleted(FBStatement stmt) throws SQLException { statementCompleted(stmt, true); } @@ -541,7 +559,6 @@ if (!connection.getAutoCommit()) return; - // commit in case of auto-commit mode to end the transaction that we started try { if (!localTransaction.inTransaction()) @@ -564,12 +581,14 @@ throw new FBSQLException(ex); } } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.BlobListener#executionCompleted(org.firebirdsql.jdbc.FirebirdBlob) */ public void executionCompleted(FirebirdBlob blob) throws SQLException { } + /* (non-Javadoc) * @see org.firebirdsql.jdbc.FBObjectListener.BlobListener#executionStarted(org.firebirdsql.jdbc.FirebirdBlob) */ Modified: client-java/trunk/src/openoffice/org/firebirdsql/jdbc/oo/OOStatement.java =================================================================== --- client-java/trunk/src/openoffice/org/firebirdsql/jdbc/oo/OOStatement.java 2013-03-30 15:14:44 UTC (rev 57878) +++ client-java/trunk/src/openoffice/org/firebirdsql/jdbc/oo/OOStatement.java 2013-03-30 15:33:46 UTC (rev 57879) @@ -16,6 +16,12 @@ public void completeStatement() throws SQLException { // workaround - do not close the result set, OpenOffice gets crazy + // TODO Test if this is still necessary after the changes of JDBC-304 if (!completed) notifyStatementCompleted(); } + + public void completeStatement(CompletionReason reason) throws SQLException { + // TODO Test if this is still necessary after the changes of JDBC-304 + if (!completed) notifyStatementCompleted(); + } } Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java 2013-03-30 15:14:44 UTC (rev 57878) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java 2013-03-30 15:33:46 UTC (rev 57879) @@ -54,6 +54,9 @@ + " \"CamelStr\" VARCHAR(255)" + ")" ; + + public static final String SELECT_TEST_TABLE = + "SELECT id, str FROM test_table"; public static final String CREATE_TABLE_STATEMENT2 = "" + "CREATE TABLE test_table2(" @@ -944,26 +947,43 @@ } public void testHoldability() throws Exception { - ((FirebirdConnection)connection).setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT); + final int recordCount = 10; + PreparedStatement ps = + connection.prepareStatement(INSERT_INTO_TABLE_STATEMENT); + + try { + for(int i = 0; i < recordCount; i++) { + ps.setInt(1, i); + ps.setInt(2, i); + ps.executeUpdate(); + } + } finally { + ps.close(); + } + + Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + Statement stmt2 = connection.createStatement(); - Statement stmt = connection.createStatement( - ResultSet.TYPE_SCROLL_INSENSITIVE, - ResultSet.CONCUR_READ_ONLY); - try { // execute first query - ResultSet rs = stmt.executeQuery("SELECT * FROM rdb$database"); + FirebirdResultSet rs = (FirebirdResultSet) stmt.executeQuery(SELECT_TEST_TABLE); // now execute another query, causes commit in auto-commit mode - stmt.executeQuery("SELECT * FROM rdb$database"); + stmt2.executeQuery("SELECT * FROM rdb$database"); // now let's access the result set + int actualCount = 0; + assertEquals("Unexpected holdability", ResultSet.HOLD_CURSORS_OVER_COMMIT, rs.getHoldability()); while(rs.next()) { rs.getString(1); + actualCount++; } + assertEquals("Unexpected number of reads from holdable resultset", recordCount, actualCount); } finally { - stmt.close(); + JdbcResourceHelper.closeQuietly(stmt); + JdbcResourceHelper.closeQuietly(stmt2); } } @@ -1117,7 +1137,6 @@ } public void testRelAlias() throws Exception { - Statement stmt = connection.createStatement(); try { @@ -1138,7 +1157,6 @@ } public void testUpdatableHoldableResultSet() throws Exception { - connection.setAutoCommit(true); int recordCount = 10; @@ -1181,6 +1199,22 @@ stmt.close(); } } + + // TODO Ignored, see JDBC-307 http://tracker.firebirdsql.org/browse/JDBC-307 + public void _testClosedOnCommit() throws Exception { + connection.setAutoCommit(false); + Statement stmt = connection.createStatement(); + try { + FirebirdResultSet rs = (FirebirdResultSet) stmt.executeQuery("SELECT * FROM RDB$DATABASE"); + assertEquals("Unexpected holdability", ResultSet.CLOSE_CURSORS_AT_COMMIT, rs.getHoldability()); + assertFalse("Expected resultset to be open", rs.isClosed()); + + connection.commit(); + assertTrue("Expected resultset to be closed", rs.isClosed()); + } finally { + stmt.close(); + } + } public static void main(String[] args) { TestRunner.run(new TestFBResultSet("testMemoryGrowth")); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-03-30 16:15:39
|
Revision: 57881 http://sourceforge.net/p/firebird/code/57881 Author: mrotteveel Date: 2013-03-30 16:15:36 +0000 (Sat, 30 Mar 2013) Log Message: ----------- JDBC-304 Additional fix for PreparedStatement Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBPreparedStatement.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBPreparedStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBPreparedStatement.java 2013-03-30 16:15:18 UTC (rev 57880) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBPreparedStatement.java 2013-03-30 16:15:36 UTC (rev 57881) @@ -149,12 +149,13 @@ } } - public void completeStatement() throws SQLException { - if (!metaDataQuery) - closeResultSet(false); - - if (!completed) + @Override + public void completeStatement(CompletionReason reason) throws SQLException { + if (!metaDataQuery) { + super.completeStatement(reason); + } else if (!completed) { notifyStatementCompleted(); + } } protected void notifyStatementCompleted(boolean success) Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java 2013-03-30 16:15:18 UTC (rev 57880) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBResultSet.java 2013-03-30 16:15:36 UTC (rev 57881) @@ -946,7 +946,7 @@ "", rs.getExecutionPlan()); } - public void testHoldability() throws Exception { + public void testHoldabilityStatement() throws Exception { final int recordCount = 10; PreparedStatement ps = @@ -986,7 +986,48 @@ JdbcResourceHelper.closeQuietly(stmt2); } } + + public void testHoldabilityPreparedStatement() throws Exception { + final int recordCount = 10; + + PreparedStatement ps = + connection.prepareStatement(INSERT_INTO_TABLE_STATEMENT); + try { + for(int i = 0; i < recordCount; i++) { + ps.setInt(1, i); + ps.setInt(2, i); + ps.executeUpdate(); + } + } finally { + ps.close(); + } + + PreparedStatement stmt = connection.prepareStatement(SELECT_TEST_TABLE, ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); + Statement stmt2 = connection.createStatement(); + + try { + // execute first query + FirebirdResultSet rs = (FirebirdResultSet) stmt.executeQuery(); + + // now execute another query, causes commit in auto-commit mode + stmt2.executeQuery("SELECT * FROM rdb$database"); + + // now let's access the result set + int actualCount = 0; + assertEquals("Unexpected holdability", ResultSet.HOLD_CURSORS_OVER_COMMIT, rs.getHoldability()); + while(rs.next()) { + rs.getString(1); + actualCount++; + } + assertEquals("Unexpected number of reads from holdable resultset", recordCount, actualCount); + } finally { + JdbcResourceHelper.closeQuietly(stmt); + JdbcResourceHelper.closeQuietly(stmt2); + } + } + public void testFetchSize() throws Exception { final int FETCH_SIZE = 3; Statement stmt = connection.createStatement(); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-05-01 09:13:54
|
Revision: 58019 http://sourceforge.net/p/firebird/code/58019 Author: mrotteveel Date: 2013-05-01 09:13:50 +0000 (Wed, 01 May 2013) Log Message: ----------- Simplify metadata + 'implement' (empty) metadata for unsupported functionality Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBStringField.java client-java/trunk/src/openoffice/org/firebirdsql/jdbc/oo/OODatabaseMetaData.java client-java/trunk/src/test/org/firebirdsql/jca/TestFBDatabaseMetaData.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java 2013-05-01 09:07:00 UTC (rev 58018) +++ client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java 2013-05-01 09:13:50 UTC (rev 58019) @@ -147,6 +147,20 @@ } /** + * Encode a <code>short</code> value as a <code>byte</code> array in network-order(big-endian) representation. + * <p> + * NOTE: Implementation is identical to {@link #intToBytes(int)} + * </p> + * + * @param value The value to be encoded + * @return The value of <code>value</code> encoded as a + * <code>byte</code> array + */ + public static byte[] shortToBytes(short value) { + return intToBytes(value); + } + + /** * Decode a <code>byte</code> array into a <code>short</code> value. * * @param byte_int The <code>byte</code> array to be decoded @@ -165,11 +179,22 @@ * <code>byte</code> array */ public byte[] encodeInt(int value){ + return intToBytes(value); + } + + /** + * Encode an <code>int</code> value as a <code>byte</code> array in network-order(big-endian) representation. + * + * @param value The value to be encoded + * @return The value of <code>value</code> encoded as a + * <code>byte</code> array + */ + public static byte[] intToBytes(int value) { byte ret[] = new byte[4]; ret[0] = (byte) ((value >>> 24) & 0xff); ret[1] = (byte) ((value >>> 16) & 0xff); ret[2] = (byte) ((value >>> 8) & 0xff); - ret[3] = (byte) ((value >>> 0) & 0xff); + ret[3] = (byte) ((value) & 0xff); return ret; } @@ -185,7 +210,7 @@ int b2 = byte_int[1]&0xFF; int b3 = byte_int[2]&0xFF; int b4 = byte_int[3]&0xFF; - return ((b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0)); + return ((b1 << 24) + (b2 << 16) + (b3 << 8) + b4); } /** @@ -195,7 +220,18 @@ * @return The value of <code>value</code> encoded as a * <code>byte</code> array */ - public byte[] encodeLong(long value){ + public byte[] encodeLong(long value){ + return longToBytes(value); + } + + /** + * Encode a <code>long</code> value as a <code>byte</code> array in network-order(big-endian) representation. + * + * @param value The value to be encoded + * @return The value of <code>value</code> encoded as a + * <code>byte</code> array + */ + public static byte[] longToBytes(long value) { byte[] ret = new byte[8]; ret[0] = (byte) (value >>> 56 & 0xFF); ret[1] = (byte) (value >>> 48 & 0xFF); @@ -204,7 +240,7 @@ ret[4] = (byte) (value >>> 24 & 0xFF); ret[5] = (byte) (value >>> 16 & 0xFF); ret[6] = (byte) (value >>> 8 & 0xFF); - ret[7] = (byte) (value >>> 0 & 0xFF); + ret[7] = (byte) (value & 0xFF); return ret; } @@ -215,7 +251,7 @@ * @return The <code>long</code> value of the decoded * <code>byte</code> array */ - public long decodeLong(byte[] byte_int){ + public long decodeLong(byte[] byte_int){ long b1 = byte_int[0]&0xFF; long b2 = byte_int[1]&0xFF; long b3 = byte_int[2]&0xFF; @@ -225,7 +261,7 @@ long b7 = byte_int[6]&0xFF; long b8 = byte_int[7]&0xFF; return ((b1 << 56) + (b2 << 48) + (b3 << 40) + (b4 << 32) - + (b5 << 24) + (b6 << 16) + (b7 << 8) + (b8 << 0)); + + (b5 << 24) + (b6 << 16) + (b7 << 8) + b8); } /** Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2013-05-01 09:07:00 UTC (rev 58018) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2013-05-01 09:13:50 UTC (rev 58019) @@ -62,6 +62,60 @@ private final static Logger log = LoggerFactory.getLogger(FBDatabaseMetaData.class,false); protected static final String SPACES = " ";//31 spaces + private static final int SUBTYPE_NUMERIC = 1; + private static final int SUBTYPE_DECIMAL = 2; + + private static final byte[] TRUE_BYTES = getBytes("T"); + private static final byte[] FALSE_BYTES = getBytes("F"); + private static final byte[] YES_BYTES = getBytes("YES"); + private static final byte[] NO_BYTES = getBytes("NO"); + private static final byte[] EMPTY_STRING_BYTES = getBytes(""); + private static final byte[] CASESENSITIVE = TRUE_BYTES; + private static final byte[] CASEINSENSITIVE = FALSE_BYTES; + private static final byte[] UNSIGNED = TRUE_BYTES; + private static final byte[] SIGNED = FALSE_BYTES; + private static final byte[] FIXEDSCALE = TRUE_BYTES; + private static final byte[] VARIABLESCALE = FALSE_BYTES; + private static final byte[] NOTAUTOINC = FALSE_BYTES; + // TODO in implementation short and int are encoded identical, remove distinction? + private static final byte[] INT_ZERO = createInt(0); + private static final byte[] SHORT_ZERO = createShort(0); + private static final byte[] SHORT_ONE = createShort(1); + private static final byte[] RADIX_BINARY = createInt(2); + private static final byte[] RADIX_TEN = createInt(10); + // TODO in implementation short and int are encoded identical, remove distinction? + private static final byte[] RADIX_TEN_SHORT = createShort(10); + private static final byte[] TYPE_PRED_NONE = createShort(DatabaseMetaData.typePredNone); + private static final byte[] TYPE_PRED_BASIC = createShort(DatabaseMetaData.typePredBasic); + private static final byte[] TYPE_SEARCHABLE = createShort(DatabaseMetaData.typeSearchable); + private static final byte[] TYPE_NULLABLE = createShort(DatabaseMetaData.typeNullable); + private static final byte[] PROCEDURE_NO_RESULT = createShort(DatabaseMetaData.procedureNoResult); + private static final byte[] PROCEDURE_RETURNS_RESULT = createShort(DatabaseMetaData.procedureReturnsResult); + private static final byte[] PROCEDURE_NO_NULLS = createShort(DatabaseMetaData.procedureNoNulls); + private static final byte[] PROCEDURE_NULLABLE = createShort(DatabaseMetaData.procedureNullable); + private static final byte[] PROCEDURE_COLUMN_IN = createShort(DatabaseMetaData.procedureColumnIn); + private static final byte[] PROCEDURE_COLUMN_OUT = createShort(DatabaseMetaData.procedureColumnOut); + private static final byte[] FLOAT_PRECISION = createInt(7); + private static final byte[] DOUBLE_PRECISION = createInt(15); + private static final byte[] BIGINT_PRECISION = createInt(19); + private static final byte[] INTEGER_PRECISION = createInt(10); + private static final byte[] SMALLINT_PRECISION = createInt(5); + private static final byte[] DATE_PRECISION = createInt(10); + private static final byte[] TIME_PRECISION = createInt(8); + private static final byte[] TIMESTAMP_PRECISION = createInt(19); + private static final byte[] NUMERIC_PRECISION = createInt(18); + private static final byte[] DECIMAL_PRECISION = createInt(18); + private static final byte[] COLUMN_NO_NULLS = createInt(DatabaseMetaData.columnNoNulls); + private static final byte[] COLUMN_NULLABLE = createInt(DatabaseMetaData.columnNullable); + private static final byte[] IMPORTED_KEY_NO_ACTION = createShort(DatabaseMetaData.importedKeyNoAction); + private static final byte[] IMPORTED_KEY_CASCADE = createShort(DatabaseMetaData.importedKeyCascade); + private static final byte[] IMPORTED_KEY_SET_NULL = createShort(DatabaseMetaData.importedKeySetNull); + private static final byte[] IMPORTED_KEY_SET_DEFAULT = createShort(DatabaseMetaData.importedKeySetDefault); + private static final byte[] IMPORTED_KEY_NOT_DEFERRABLE = createShort(DatabaseMetaData.importedKeyNotDeferrable); + private static final byte[] TABLE_INDEX_OTHER = createShort(DatabaseMetaData.tableIndexOther); + private static final byte[] ASC_BYTES = getBytes("A"); + private static final byte[] DESC_BYTES = getBytes("D"); + private GDSHelper gdsHelper; private FBConnection connection; @@ -659,7 +713,6 @@ * </p> */ public String getNumericFunctions() throws SQLException { - // TODO cache result? return collectionToCommaSeperatedList(FBEscapedFunctionHelper.getSupportedNumericFunctions()); } @@ -681,7 +734,6 @@ * </p> */ public String getStringFunctions() throws SQLException { - // TODO cache result? return collectionToCommaSeperatedList(FBEscapedFunctionHelper.getSupportedStringFunctions()); } @@ -693,7 +745,6 @@ * </p> */ public String getSystemFunctions() throws SQLException { - // TODO cache result? return collectionToCommaSeperatedList(FBEscapedFunctionHelper.getSupportedSystemFunctions()); } @@ -705,7 +756,6 @@ * </p> */ public String getTimeDateFunctions() throws SQLException { - // TODO cache result? return collectionToCommaSeperatedList(FBEscapedFunctionHelper.getSupportedTimeDateFunctions()); } @@ -794,6 +844,7 @@ * @exception SQLException if a database access error occurs */ public boolean supportsConvert() throws SQLException { + // TODO: Set true after JDBC-294 has been done return false; // Support is broken right now } @@ -807,6 +858,7 @@ * @see Types */ public boolean supportsConvert(int fromType, int toType) throws SQLException { + // TODO: implement actual mapping with JDBC-294 return false; // Support is broken right now } @@ -1722,7 +1774,8 @@ public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException { checkCatalogAndSchema(catalog, schemaPattern); - + + // TODO null or "" are not according to spec if (procedureNamePattern == null || procedureNamePattern.equals("")) { procedureNamePattern = "%"; } @@ -1752,10 +1805,11 @@ } ResultSet rs = doQuery(sql, params); - ArrayList<byte[][]> rows = new ArrayList<byte[][]>(); + // if nothing found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!procedureClause.getCondition().equals("")) { params.add(procedureClause.getValue()); @@ -1764,10 +1818,13 @@ rs = doQuery(sql, params); // if nothing found, return an empty result set - if (!rs.next()) - return new FBResultSet(xsqlvars, rows); + if (!rs.next()) { + rs.close(); + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + } } - + + ArrayList<byte[][]> rows = new ArrayList<byte[][]>(); do { byte[][] row = new byte[9][]; row[0] = null; @@ -1781,11 +1838,11 @@ if (remarks != null && remarks.length() > xsqlvars[6].sqllen) xsqlvars[6].sqllen = remarks.length(); short procedureType = rs.getShort("PROCEDURE_TYPE"); - row[7] = (procedureType == 0) ? xsqlvars[0].encodeShort((short)procedureNoResult) : xsqlvars[0].encodeShort((short)procedureReturnsResult); + row[7] = procedureType == 0 ? PROCEDURE_NO_RESULT : PROCEDURE_RETURNS_RESULT; row[8] = row[2]; rows.add(row); } while (rs.next()); - + rs.close(); return new FBResultSet(xsqlvars, rows); } @@ -1955,6 +2012,7 @@ // if nothing found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!procedureClause.getCondition().equals("")) { params.add(procedureClause.getValue()); @@ -1966,8 +2024,10 @@ rs = doQuery(sql, params); // if nothing found, return an empty result set - if (!rs.next()) + if (!rs.next()) { + rs.close(); return new FBResultSet(xsqlvars, rows); + } } do { @@ -1980,17 +2040,17 @@ short columnType = rs.getShort("COLUMN_TYPE"); // TODO: Unsure if procedureColumnOut is correct, maybe procedureColumnResult, or need ODS dependent use of RDB$PROCEDURE_TYPE to decide on selectable or executable? // TODO: ResultSet columns should not be first according to JDBC 4.1 description - row[4] = (columnType == 0) ? xsqlvars[0].encodeShort((short)procedureColumnIn) : xsqlvars[0].encodeShort((short)procedureColumnOut); + row[4] = columnType == 0 ? PROCEDURE_COLUMN_IN : PROCEDURE_COLUMN_OUT; short fieldType = rs.getShort("FIELD_TYPE"); short fieldSubType = rs.getShort("FIELD_SUB_TYPE"); short fieldScale = rs.getShort("FIELD_SCALE"); int dataType = getDataType(fieldType, fieldSubType, fieldScale); - row[5] = xsqlvars[0].encodeInt(dataType); + row[5] = createInt(dataType); row[6] = getBytes(getDataTypeName(fieldType, fieldSubType, fieldScale)); - row[8] = xsqlvars[0].encodeInt(rs.getShort("FIELD_LENGTH")); + row[8] = createInt(rs.getShort("FIELD_LENGTH")); // Defaults: some are overridden in the switch row[7] = null; @@ -2000,51 +2060,51 @@ switch (dataType){ case Types.DECIMAL: case Types.NUMERIC: - row[7] = xsqlvars[0].encodeInt(rs.getShort("FIELD_PRECISION")); - row[9] = xsqlvars[0].encodeShort((short)(fieldScale * (-1))); - row[10] = xsqlvars[0].encodeShort((short)10); + row[7] = createInt(rs.getShort("FIELD_PRECISION")); + row[9] = createShort(-1 * fieldScale); + row[10] = RADIX_TEN_SHORT; break; case Types.CHAR: case Types.VARCHAR: short charLen = rs.getShort("CHAR_LEN"); if (!rs.wasNull()) { - row[7] = xsqlvars[0].encodeInt(charLen); + row[7] = createInt(charLen); } else { row[7] = row[8]; } row[16] = row[8]; break; case Types.FLOAT: - row[7] = xsqlvars[0].encodeInt(7); - row[10] = xsqlvars[0].encodeShort((short)10); + row[7] = FLOAT_PRECISION; + row[10] = RADIX_TEN_SHORT; break; case Types.DOUBLE: - row[7] = xsqlvars[0].encodeInt(15); - row[10] = xsqlvars[0].encodeShort((short)10); + row[7] = DOUBLE_PRECISION; + row[10] = RADIX_TEN_SHORT; break; case Types.BIGINT: - row[7] = xsqlvars[0].encodeInt(19); - row[9] = xsqlvars[0].encodeShort((short)0); - row[10] = xsqlvars[0].encodeShort((short)10); + row[7] = BIGINT_PRECISION; + row[9] = SHORT_ZERO; + row[10] = RADIX_TEN_SHORT; break; case Types.INTEGER: - row[7] = xsqlvars[0].encodeInt(10); - row[9] = xsqlvars[0].encodeShort((short)0); - row[10] = xsqlvars[0].encodeShort((short)10); + row[7] = INTEGER_PRECISION; + row[9] = SHORT_ZERO; + row[10] = RADIX_TEN_SHORT; break; case Types.SMALLINT: - row[7] = xsqlvars[0].encodeInt(5); - row[9] = xsqlvars[0].encodeShort((short)0); - row[10] = xsqlvars[0].encodeShort((short)10); + row[7] = SMALLINT_PRECISION; + row[9] = SHORT_ZERO; + row[10] = RADIX_TEN_SHORT; break; case Types.DATE: - row[7] = xsqlvars[0].encodeInt(10); + row[7] = DATE_PRECISION; break; case Types.TIME: - row[7] = xsqlvars[0].encodeInt(8); + row[7] = TIME_PRECISION; break; case Types.TIMESTAMP: - row[7] = xsqlvars[0].encodeInt(19); + row[7] = TIMESTAMP_PRECISION; break; default: row[7] = null; @@ -2052,8 +2112,7 @@ // TODO: Find out what the difference is with NULL_FLAG in RDB$PROCEDURE_PARAMETERS (might be ODS dependent) short nullFlag = rs.getShort("NULL_FLAG"); - row[11] = (nullFlag == 1) ? xsqlvars[0].encodeShort((short)procedureNoNulls) : - xsqlvars[0].encodeShort((short)procedureNullable); + row[11] = nullFlag == 1 ? PROCEDURE_NO_NULLS : PROCEDURE_NULLABLE; String remarks = rs.getString("REMARKS"); row[12] = getBytes(remarks); @@ -2064,13 +2123,14 @@ row[14] = null; row[15] = null; // TODO: Find correct value for ORDINAL_POSITION (+ order of columns and intent, see JDBC-229) - row[17] = xsqlvars[0].encodeInt(rs.getInt("PARAMETER_NUMBER")); + row[17] = createInt(rs.getInt("PARAMETER_NUMBER")); // TODO: Find out if there is a conceptual difference with NULLABLE (idx 11) - row[18] = (nullFlag == 1) ? getBytes("NO") : getBytes("YES"); + row[18] = (nullFlag == 1) ? NO_BYTES : YES_BYTES; row[19] = row[2]; rows.add(row); } while (rs.next()); + rs.close(); return new FBResultSet(xsqlvars, rows); } @@ -2191,6 +2251,7 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException { + // TODO null or "" are invalid according to JDBC spec if (tableNamePattern == null || "".equals(tableNamePattern)) tableNamePattern = "%"; @@ -2470,6 +2531,7 @@ // if no direct match happened, check the uppercased match if (!rs.next()) { + rs.close(); params.clear(); if (!tableClause.getCondition().equals("")) { params.add(tableClause.getValue()); @@ -2483,8 +2545,10 @@ // if no rows are available, we have to exit now, otherwise the // following do/while loop will throw SQLException that the // result set is not positioned on a row - if (!rs.next()) + if (!rs.next()) { + rs.close(); return new FBResultSet(xsqlvars, rows); + } } do { @@ -2499,7 +2563,7 @@ short fieldScale = rs.getShort("FIELD_SCALE"); int dataType = getDataType(fieldType, fieldSubType, fieldScale); - row[4] = xsqlvars[0].encodeInt(dataType); + row[4] = createInt(dataType); row[5] = getBytes(getDataTypeName(fieldType, fieldSubType, fieldScale)); row[7] = null; @@ -2511,51 +2575,51 @@ switch (dataType){ case Types.DECIMAL: case Types.NUMERIC: - row[6] = xsqlvars[0].encodeInt(rs.getShort("FIELD_PRECISION")); - row[8] = xsqlvars[0].encodeInt(fieldScale * (-1)); - row[9] = xsqlvars[0].encodeInt(10); + row[6] = createInt(rs.getShort("FIELD_PRECISION")); + row[8] = createInt(fieldScale * (-1)); + row[9] = RADIX_TEN; break; case Types.CHAR: case Types.VARCHAR: - row[15] = xsqlvars[0].encodeInt(rs.getShort("FIELD_LENGTH")); + row[15] = createInt(rs.getShort("FIELD_LENGTH")); short charLen = rs.getShort("CHAR_LEN"); if (!rs.wasNull()) { - row[6] = xsqlvars[0].encodeInt(charLen); + row[6] = createInt(charLen); } else { row[6] = row[15]; } break; case Types.FLOAT: - row[6] = xsqlvars[0].encodeInt(7); - row[9] = xsqlvars[0].encodeInt(10); + row[6] = FLOAT_PRECISION; + row[9] = RADIX_TEN; break; case Types.DOUBLE: - row[6] = xsqlvars[0].encodeInt(15); - row[9] = xsqlvars[0].encodeInt(10); + row[6] = DOUBLE_PRECISION; + row[9] = RADIX_TEN; break; case Types.BIGINT: - row[6] = xsqlvars[0].encodeInt(19); - row[8] = xsqlvars[0].encodeInt(0); - row[9] = xsqlvars[0].encodeInt(10); + row[6] = BIGINT_PRECISION; + row[8] = INT_ZERO; + row[9] = RADIX_TEN; break; case Types.INTEGER: - row[6] = xsqlvars[0].encodeInt(10); - row[8] = xsqlvars[0].encodeInt(0); - row[9] = xsqlvars[0].encodeInt(10); + row[6] = INTEGER_PRECISION; + row[8] = INT_ZERO; + row[9] = RADIX_TEN; break; case Types.SMALLINT: - row[6] = xsqlvars[0].encodeInt(5); - row[8] = xsqlvars[0].encodeInt(0); - row[9] = xsqlvars[0].encodeInt(10); + row[6] = SMALLINT_PRECISION; + row[8] = INT_ZERO; + row[9] = RADIX_TEN; break; case Types.DATE: - row[6] = xsqlvars[0].encodeInt(10); + row[6] = DATE_PRECISION; break; case Types.TIME: - row[6] = xsqlvars[0].encodeInt(8); + row[6] = TIME_PRECISION; break; case Types.TIMESTAMP: - row[6] = xsqlvars[0].encodeInt(19); + row[6] = TIMESTAMP_PRECISION; break; default: row[6] = null; @@ -2563,9 +2627,9 @@ short nullFlag = rs.getShort("NULL_FLAG"); short sourceNullFlag = rs.getShort("SOURCE_NULL_FLAG"); - row[10] = (nullFlag == 1 || sourceNullFlag == 1) ? - xsqlvars[0].encodeInt(columnNoNulls) : - xsqlvars[0].encodeInt(columnNullable); + row[10] = (nullFlag == 1 || sourceNullFlag == 1) ? + COLUMN_NO_NULLS : + COLUMN_NULLABLE; String remarks = rs.getString("REMARKS"); row[11] = getBytes(remarks); @@ -2584,9 +2648,8 @@ row[13] = null; row[14] = null; - row[16] = xsqlvars[0].encodeInt(rs.getInt("FIELD_POSITION")); - row[17] = (nullFlag == 1 || sourceNullFlag == 1) ? - getBytes("NO") : getBytes("YES"); + row[16] = createInt(rs.getInt("FIELD_POSITION")); + row[17] = (nullFlag == 1 || sourceNullFlag == 1) ? NO_BYTES : YES_BYTES; row[18] = null; row[19] = null; row[20] = null; @@ -2597,59 +2660,60 @@ case Types.BIGINT: case Types.SMALLINT: // Could be autoincrement, but we simply don't know - row[22] = getBytes(""); + row[22] = EMPTY_STRING_BYTES; break; case Types.NUMERIC: case Types.DECIMAL: if (fieldScale == 0) { // Could be autoincrement, but we simply don't know - row[22] = getBytes(""); + row[22] = EMPTY_STRING_BYTES; } else { // Scaled NUMERIC/DECIMAL: definitely not autoincrement - row[22] = getBytes("NO"); + row[22] = NO_BYTES; } break; default: // All other types are never autoincrement - row[22] = getBytes("NO"); + row[22] = NO_BYTES; } + // Retrieving COMPUTED_BLR to check if it was NULL or not rs.getString("COMPUTED_BLR"); - row[23] = getBytes(rs.wasNull() ? "NO" : "YES"); + row[23] = rs.wasNull() ? NO_BYTES : YES_BYTES; rows.add(row); } while (rs.next()); - + rs.close(); return new FBResultSet(xsqlvars, rows); } - private static final short smallint_type = 7; - private static final short integer_type = 8; - private static final short quad_type = 9; - private static final short float_type = 10; - private static final short d_float_type = 11; - private static final short date_type = 12; - private static final short time_type = 13; - private static final short char_type = 14; - private static final short int64_type = 16; - private static final short double_type = 27; - private static final short timestamp_type = 35; - private static final short varchar_type = 37; -// private static final short cstring_type = 40; - private static final short blob_type = 261; + private static final int smallint_type = 7; + private static final int integer_type = 8; + private static final int quad_type = 9; + private static final int float_type = 10; + private static final int d_float_type = 11; + private static final int date_type = 12; + private static final int time_type = 13; + private static final int char_type = 14; + private static final int int64_type = 16; + private static final int double_type = 27; + private static final int timestamp_type = 35; + private static final int varchar_type = 37; +// private static final int cstring_type = 40; + private static final int blob_type = 261; - private static int getDataType (short fieldType, short fieldSubType, short fieldScale) { + private static int getDataType(int fieldType, int fieldSubType, int fieldScale) { switch (fieldType) { case smallint_type: - if (fieldSubType == 1 || (fieldSubType == 0 && fieldScale < 0)) + if (fieldSubType == SUBTYPE_NUMERIC || (fieldSubType == 0 && fieldScale < 0)) return Types.NUMERIC; - else if (fieldSubType == 2) + else if (fieldSubType == SUBTYPE_DECIMAL) return Types.DECIMAL; else return Types.SMALLINT; case integer_type: - if (fieldSubType == 1 || (fieldSubType == 0 && fieldScale < 0)) + if (fieldSubType == SUBTYPE_NUMERIC || (fieldSubType == 0 && fieldScale < 0)) return Types.NUMERIC; - else if (fieldSubType == 2) + else if (fieldSubType == SUBTYPE_DECIMAL) return Types.DECIMAL; else return Types.INTEGER; @@ -2669,9 +2733,9 @@ case date_type: return Types.DATE; case int64_type: - if (fieldSubType == 1 || (fieldSubType == 0 && fieldScale < 0)) + if (fieldSubType == SUBTYPE_NUMERIC || (fieldSubType == 0 && fieldScale < 0)) return Types.NUMERIC; - else if (fieldSubType == 2) + else if (fieldSubType == SUBTYPE_DECIMAL) return Types.DECIMAL; else return Types.BIGINT; @@ -2691,19 +2755,19 @@ } } - private static String getDataTypeName(short sqltype, short sqlsubtype, short sqlscale) { + private static String getDataTypeName(int sqltype, int sqlsubtype, int sqlscale) { switch (sqltype) { case smallint_type: - if (sqlsubtype == 1 || (sqlsubtype == 0 && sqlscale < 0)) + if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0)) return "NUMERIC"; - else if (sqlsubtype == 2) + else if (sqlsubtype == SUBTYPE_DECIMAL) return "DECIMAL"; else return "SMALLINT"; case integer_type: - if (sqlsubtype == 1 || (sqlsubtype == 0 && sqlscale < 0)) + if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0)) return "NUMERIC"; - else if (sqlsubtype == 2) + else if (sqlsubtype == SUBTYPE_DECIMAL) return "DECIMAL"; else return "INTEGER"; @@ -2723,14 +2787,15 @@ case date_type: return "DATE"; case int64_type: - if (sqlsubtype == 1 || (sqlsubtype == 0 && sqlscale < 0)) + if (sqlsubtype == SUBTYPE_NUMERIC || (sqlsubtype == 0 && sqlscale < 0)) return "NUMERIC"; - else if (sqlsubtype == 2) + else if (sqlsubtype == SUBTYPE_DECIMAL) return "DECIMAL"; else return "BIGINT"; case blob_type: if (sqlsubtype < 0) + // TODO Include actual subtype? return "BLOB SUB_TYPE <0"; else if (sqlsubtype == 0) return "BLOB SUB_TYPE 0"; @@ -2768,7 +2833,30 @@ + "(RF.RDB$FIELD_NAME is null and UP.RDB$OBJECT_TYPE = 0)) " + "order by 2,5 "; + private static final Map<String, byte[]> PRIVILEGE_MAPPING; + static { + Map<String, byte[]> tempMapping = new HashMap<String, byte[]>(7); + tempMapping.put("A", getBytes("ALL")); + tempMapping.put("S", getBytes("SELECT")); + tempMapping.put("D", getBytes("DELETE")); + tempMapping.put("I", getBytes("INSERT")); + tempMapping.put("U", getBytes("UPDATE")); + tempMapping.put("R", getBytes("REFERENCE")); // TODO: JDBC apidoc specifies REFRENCES (yes: typo and + S) + tempMapping.put("M", getBytes("MEMBEROF")); + PRIVILEGE_MAPPING = Collections.unmodifiableMap(tempMapping); + } + /** + * Maps the (one character) Firebird privilege to the equivalent JDBC privilege. + * + * @param firebirdPrivilege Firebird privilege + * @return JDBC privilege encoded as byte array + */ + private static byte[] mapPrivilege(String firebirdPrivilege) { + return PRIVILEGE_MAPPING.get(firebirdPrivilege); + } + + /** * Gets a description of the access rights for a table's columns. * * <P>Only privileges matching the column name criteria are @@ -2827,11 +2915,12 @@ params.add(columnClause.getOriginalCaseValue()); } - List<byte[][]> rows = new ArrayList<byte[][]>(); + ResultSet rs = doQuery(sql, params); // if nothing was found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!columnClause.getCondition().equals("")) { params.add(stripQuotes(stripEscape(table), true)); @@ -2841,10 +2930,13 @@ rs = doQuery(sql, params); // return empty result set - if (!rs.next()) - return new FBResultSet(xsqlvars, rows); + if (!rs.next()) { + rs.close(); + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + } } + List<byte[][]> rows = new ArrayList<byte[][]>(); do { byte[][] row = new byte[8][]; row[0] = null; @@ -2853,30 +2945,13 @@ row[3] = getBytes(rs.getString("COLUMN_NAME")); row[4] = getBytes(rs.getString("GRANTOR")); row[5] = getBytes(rs.getString("GRANTEE")); - String privilege = rs.getString("PRIVILEGE"); - if (privilege.equals("A")) - row[6] = getBytes("ALL"); - else if (privilege.equals("S")) - row[6] = getBytes("SELECT"); - else if (privilege.equals("D")) - row[6] = getBytes("DELETE"); - else if (privilege.equals("I")) - row[6] = getBytes("INSERT"); - else if (privilege.equals("U")) - row[6] = getBytes("UPDATE"); - else if (privilege.equals("R")) - row[6] = getBytes("REFERENCE"); - else if (privilege.equals("M")) - row[6] = getBytes("MEMBEROF"); + row[6] = mapPrivilege(rs.getString("PRIVILEGE")); int isGrantable = rs.getShort("IS_GRANTABLE"); - if (isGrantable==0) - row[7] = getBytes("NO"); - else - row[7] = getBytes("YES"); + row[7] = isGrantable == 0 ? NO_BYTES : YES_BYTES; rows.add(row); } while(rs.next()); - + rs.close(); return new FBResultSet(xsqlvars, rows); } @@ -2951,6 +3026,7 @@ // if nothing found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!tableClause.getCondition().equals("")) { params.add(tableClause.getValue()); @@ -2959,8 +3035,10 @@ rs = doQuery(sql, params); // if nothing found, return an empty result set - if (!rs.next()) - return new FBResultSet(xsqlvars, new ArrayList<byte[][]>()); + if (!rs.next()) { + rs.close(); + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + } } return processTablePrivileges(xsqlvars, rs); @@ -2990,30 +3068,13 @@ row[2] = getBytes(fbTablePrivileges.getString("TABLE_NAME")); row[3] = getBytes(fbTablePrivileges.getString("GRANTOR")); row[4] = getBytes(fbTablePrivileges.getString("GRANTEE")); - String privilege = fbTablePrivileges.getString("PRIVILEGE"); - if (privilege.equals("A")) - row[5] = getBytes("ALL"); - else if (privilege.equals("S")) - row[5] = getBytes("SELECT"); - else if (privilege.equals("D")) - row[5] = getBytes("DELETE"); - else if (privilege.equals("I")) - row[5] = getBytes("INSERT"); - else if (privilege.equals("U")) - row[5] = getBytes("UPDATE"); - else if (privilege.equals("R")) - row[5] = getBytes("REFERENCE"); // TODO: JDBC spec specifies REFRENCES (yes: typo and + S) - else if (privilege.equals("M")) - row[5] = getBytes("MEMBEROF"); + row[5] = mapPrivilege(fbTablePrivileges.getString("PRIVILEGE")); int isGrantable = fbTablePrivileges.getShort("IS_GRANTABLE"); - if (isGrantable==0) - row[6] = getBytes("NO"); - else - row[6] = getBytes("YES"); + row[6] = isGrantable == 0 ? NO_BYTES : YES_BYTES; rows.add(row); } while (fbTablePrivileges.next()); - + fbTablePrivileges.close(); return new FBResultSet(xsqlvars, rows); } @@ -3092,24 +3153,28 @@ xsqlvars[6] = new XSQLVAR(ISCConstants.SQL_SHORT, 0, "DECIMAL_DIGITS", "ROWIDENTIFIER"); xsqlvars[7] = new XSQLVAR(ISCConstants.SQL_SHORT, 0, "PSEUDO_COLUMN", "ROWIDENTIFIER"); - ResultSet tables = getTables(catalog, schema, table, null); - - if (!tables.next()) - return new FBResultSet(xsqlvars, new ArrayList<byte[][]>()); - - List<byte[][]> rows = getPrimaryKeyIdentifier(tables.getString(3), scope, xsqlvars); + ResultSet tables = null; + List<byte[][]> rows = null; + try { + tables = getTables(catalog, schema, table, null); + if (!tables.next()) + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + rows = getPrimaryKeyIdentifier(tables.getString(3), scope, xsqlvars); + } finally { + if (tables != null) tables.close(); + } // if no primary key exists, add RDB$DB_KEY as pseudo-column if (rows.size() == 0) { byte[][] row = new byte[8][]; - row[0] = xsqlvars[0].encodeShort((short)scope); + row[0] = createShort(scope); row[1] = getBytes("RDB$DB_KEY"); - row[2] = xsqlvars[0].encodeShort((short)getDataType(char_type, (short)0, (short)0)); - row[3] = getBytes(getDataTypeName(char_type, (short)0, (short)0)); - row[4] = xsqlvars[0].encodeInt(0); + row[2] = createShort(getDataType(char_type, 0, 0)); + row[3] = getBytes(getDataTypeName(char_type, 0, 0)); + row[4] = createInt(0); row[5] = null; - row[6] = xsqlvars[0].encodeShort((short)0); - row[7] = xsqlvars[0].encodeShort((short)bestRowPseudo); + row[6] = createShort(0); + row[7] = createShort(bestRowPseudo); rows.add(row); } @@ -3140,19 +3205,21 @@ while (rs.next()) { byte[][] row = new byte[8][]; - row[0] = xsqlvars[0].encodeShort((short)scope); + row[0] = createShort(scope); row[1] = getBytes(rs.getString("COLUMN_NAME")); - row[2] = xsqlvars[0].encodeShort((short)getDataType(rs.getShort("FIELD_TYPE"), - rs.getShort("FIELD_SUB_TYPE"), rs.getShort("FIELD_SCALE"))); - row[3] = getBytes(getDataTypeName(rs.getShort("FIELD_TYPE"), - rs.getShort("FIELD_SUB_TYPE"), rs.getShort("FIELD_SCALE"))); - row[4] = xsqlvars[0].encodeInt(rs.getInt("FIELD_PRECISION")); + short fieldType = rs.getShort("FIELD_TYPE"); + short fieldSubType = rs.getShort("FIELD_SUB_TYPE"); + short fieldScale = rs.getShort("FIELD_SCALE"); + row[2] = createShort(getDataType(fieldType, fieldSubType, fieldScale)); + row[3] = getBytes(getDataTypeName(fieldType, fieldSubType, fieldScale)); + row[4] = createInt(rs.getInt("FIELD_PRECISION")); row[5] = null; - row[6] = xsqlvars[0].encodeShort(rs.getShort("FIELD_SCALE")); - row[7] = xsqlvars[0].encodeShort((short)bestRowNotPseudo); + row[6] = createShort(fieldScale); + row[7] = createShort(bestRowNotPseudo); rows.add(row); } + rs.close(); return rows; } @@ -3268,11 +3335,11 @@ params.add(tableClause.getOriginalCaseValue()); } - List<byte[][]> rows = new ArrayList<byte[][]>(); ResultSet rs = doQuery(sql, params); // if nothing found, check the uppercased identifier if (!rs.next()) { + rs.close(); params.clear(); if (!tableClause.getCondition().equals("")) { params.add(tableClause.getValue()); @@ -3281,22 +3348,25 @@ rs = doQuery(sql, params); // if nothing found, return empty result set - if (!rs.next()) - return new FBResultSet(xsqlvars, rows); + if (!rs.next()) { + rs.close(); + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + } } - + + List<byte[][]> rows = new ArrayList<byte[][]>(); do { byte[][] row = new byte[6][]; row[0] = null; row[1] = null; row[2] = getBytes(rs.getString("TABLE_NAME")); row[3] = getBytes(rs.getString("COLUMN_NAME")); - row[4] = xsqlvars[0].encodeShort(rs.getShort("KEY_SEQ")); + row[4] = createShort(rs.getShort("KEY_SEQ")); row[5] = getBytes(rs.getString("PK_NAME")); rows.add(row); } while(rs.next()); - + rs.close(); return new FBResultSet(xsqlvars, rows); } @@ -3331,7 +3401,28 @@ +"and ISP.RDB$FIELD_POSITION = ISF.RDB$FIELD_POSITION " +"order by 1, 5 "; + private static final Map<String, byte[]> ACTION_MAPPING; + static { + Map<String, byte[]> tempMap = new HashMap<String, byte[]>(); + tempMap.put("NO ACTION", IMPORTED_KEY_NO_ACTION); + tempMap.put("RESTRICT", IMPORTED_KEY_NO_ACTION); + tempMap.put("CASCADE", IMPORTED_KEY_CASCADE); + tempMap.put("SET NULL", IMPORTED_KEY_SET_NULL); + tempMap.put("SET DEFAULT", IMPORTED_KEY_SET_DEFAULT); + ACTION_MAPPING = Collections.unmodifiableMap(tempMap); + } + /** + * Maps the Firebird action name to the equivalent JDBC action. + * + * @param fbAction Firebird action + * @return JDBC action encoded as byte array + */ + private static byte[] mapAction(String fbAction) { + return ACTION_MAPPING.get(fbAction); + } + + /** * Gets a description of the primary key columns that are * referenced by a table's foreign key columns (the primary keys * imported by a table). They are ordered by PKTABLE_CAT, @@ -3432,11 +3523,11 @@ params.add(tableClause.getOriginalCaseValue()); } - List<byte[][]> rows = new ArrayList<byte[][]>(); ResultSet rs = doQuery(sql, params); // if nothing found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!tableClause.getCondition().equals("")) { params.add(tableClause.getValue()); @@ -3445,10 +3536,13 @@ rs = doQuery(sql, params); // if nothing found, return an empty result set - if (!rs.next()) - return new FBResultSet(xsqlvars, rows); + if (!rs.next()) { + rs.close(); + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + } } - + + List<byte[][]> rows = new ArrayList<byte[][]>(); do { byte[][] row = new byte[14][]; row[0] = null; @@ -3459,31 +3553,17 @@ row[5] = null; row[6] = getBytes(rs.getString("FKTABLE_NAME")); row[7] = getBytes(rs.getString("FKCOLUMN_NAME")); - row[8] = xsqlvars[0].encodeShort(rs.getShort("KEY_SEQ")); + row[8] = createShort(rs.getShort("KEY_SEQ")); String updateRule = rs.getString("UPDATE_RULE"); - if (updateRule.equals("NO ACTION") || updateRule.equals("RESTRICT")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNoAction); - else if (updateRule.equals("CASCADE")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyCascade); - else if (updateRule.equals("SET NULL")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetNull); - else if (updateRule.equals("SET DEFAULT")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetDefault); + row[9] = mapAction(updateRule); String deleteRule = rs.getString("DELETE_RULE"); - if (deleteRule.equals("NO ACTION") || deleteRule.equals("RESTRICT")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNoAction); - else if (deleteRule.equals("CASCADE")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyCascade); - else if (deleteRule.equals("SET NULL")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetNull); - else if (deleteRule.equals("SET DEFAULT")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetDefault); + row[10] = mapAction(deleteRule); row[11] = getBytes(rs.getString("FK_NAME")); row[12] = getBytes(rs.getString("PK_NAME")); - row[13] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNotDeferrable); + row[13] = IMPORTED_KEY_NOT_DEFERRABLE; rows.add(row); } while (rs.next()); - + rs.close(); return new FBResultSet(xsqlvars, rows); } @@ -3624,6 +3704,7 @@ // if nothing found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!tableClause.getCondition().equals("")) { params.add(tableClause.getValue()); @@ -3632,8 +3713,10 @@ rs = doQuery(sql, params); // if nothing found, return an empty result set - if (!rs.next()) + if (!rs.next()) { + rs.close(); return new FBResultSet(xsqlvars, rows); + } } do { @@ -3646,33 +3729,18 @@ row[5] = null; row[6] = getBytes(rs.getString("FKTABLE_NAME")); row[7] = getBytes(rs.getString("FKCOLUMN_NAME")); - row[8] = xsqlvars[0].encodeShort(rs.getShort("KEY_SEQ")); + row[8] = createShort(rs.getShort("KEY_SEQ")); String updateRule = rs.getString("UPDATE_RULE"); - if (updateRule.equals("NO ACTION") || updateRule.equals("RESTRICT")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNoAction); - else if (updateRule.equals("CASCADE")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyCascade); - else if (updateRule.equals("SET NULL")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetNull); - else if (updateRule.equals("SET DEFAULT")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetDefault); + row[9] = mapAction(updateRule); String deleteRule = rs.getString("DELETE_RULE"); - if (deleteRule.equals("NO ACTION") || deleteRule.equals("RESTRICT")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNoAction); - else if (deleteRule.equals("CASCADE")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyCascade); - else if (deleteRule.equals("SET NULL")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetNull); - else if (deleteRule.equals("SET DEFAULT")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetDefault); - + row[10] = mapAction(deleteRule); row[11] = getBytes(rs.getString("FK_NAME")); row[12] = getBytes(rs.getString("PK_NAME")); - row[13] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNotDeferrable); + row[13] = IMPORTED_KEY_NOT_DEFERRABLE; rows.add(row); } while(rs.next()); - + rs.close(); return new FBResultSet(xsqlvars, rows); } @@ -3825,11 +3893,11 @@ params.add(foreignTableClause.getOriginalCaseValue()); } - List<byte[][]> rows = new ArrayList<byte[][]>(); ResultSet rs = doQuery(sql, params); // if nothing found, check the uppercased identifiers if (!rs.next()) { + rs.close(); params.clear(); if (!primaryTableClause.getCondition().equals("")) { params.add(primaryTableClause.getValue()); @@ -3841,10 +3909,13 @@ rs = doQuery(sql, params); // return empty result set if nothing found - if (!rs.next()) - return new FBResultSet(xsqlvars, rows); + if (!rs.next()) { + rs.close(); + return new FBResultSet(xsqlvars, Collections.<byte[][]>emptyList()); + } } - + + List<byte[][]> rows = new ArrayList<byte[][]>(); do { byte[][] row = new byte[14][]; row[0] = null; @@ -3855,50 +3926,43 @@ row[5] = null; row[6] = getBytes(rs.getString("FKTABLE_NAME")); row[7] = getBytes(rs.getString("FKCOLUMN_NAME")); - row[8] = xsqlvars[0].encodeShort(rs.getShort("KEY_SEQ")); + row[8] = createShort(rs.getShort("KEY_SEQ")); String updateRule = rs.getString("UPDATE_RULE"); - if (updateRule.equals("NO ACTION") || updateRule.equals("RESTRICT")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNoAction); - else if (updateRule.equals("CASCADE")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyCascade); - else if (updateRule.equals("SET NULL")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetNull); - else if (updateRule.equals("SET DEFAULT")) - row[9] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetDefault); + row[9] = mapAction(updateRule); String deleteRule = rs.getString("DELETE_RULE"); - if (deleteRule.equals("NO ACTION") || deleteRule.equals("RESTRICT")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNoAction); - else if (deleteRule.equals("CASCADE")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyCascade); - else if (deleteRule.equals("SET NULL")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetNull); - else if (deleteRule.equals("SET DEFAULT")) - row[10] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeySetDefault); + row[10] = mapAction(deleteRule); row[11] = getBytes(rs.getString("FK_NAME")); row[12] = getBytes(rs.getString("PK_NAME")); - row[13] = xsqlvars[0].encodeShort((short) DatabaseMetaData.importedKeyNotDeferrable); + row[13] = IMPORTED_KEY_NOT_DEFERRABLE; rows.add(row); } while(rs.next()); + rs.close(); return new FBResultSet(xsqlvars, rows); } /** - * Simple convertor function to convert integer values to Short objects. - * Used in {@link #getTypeInfo()} for values of {@link java.sql.Types} class. + * Function to convert integer values to encoded byte arrays for shorts. * * @param value integer value to convert - * @return instance of java.lang.Short representing the value + * @return encoded byte array representing the value */ - private byte[] createShort(int value) throws SQLException { - if (value > Short.MAX_VALUE) - throw new FBSQLException("Cannot convert integer to short.", - FBSQLException.SQL_STATE_INVALID_ARG_VALUE); - - return new XSQLVAR().encodeShort((short)value); + private static byte[] createShort(int value) { + assert (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) : String.format("Value \"%d\" outside range of short", value); + return XSQLVAR.shortToBytes((short) value); } /** + * Function to convert integer values to encoded byte arrays for integers. + * + * @param value integer value to convert + * @return encoded byte array representing the value + */ + private static byte[] createInt(int value) { + return XSQLVAR.intToBytes(value); + } + + /** * Gets a description of all the standard SQL types supported by * this database. They are ordered by DATA_TYPE and then by how * closely the data type maps to the corresponding JDBC SQL type. @@ -3945,24 +4009,6 @@ * @exception SQLException if a database access error occurs */ public ResultSet getTypeInfo() throws SQLException { - - final XSQLVAR anXSQLVAR = new XSQLVAR(); - - byte[] shortZero = anXSQLVAR.encodeShort((short)0); - byte[] CASESENSITIVE = getBytes("T"); - byte[] CASEINSENSITIVE = getBytes("F"); - byte[] UNSIGNED = getBytes("T"); - byte[] SIGNED = getBytes("F"); - byte[] FIXEDSCALE = getBytes("T"); - byte[] VARIABLESCALE = getBytes("F"); - byte[] NOTAUTOINC = getBytes("F"); - byte[] BINARY = anXSQLVAR.encodeInt(2); - byte[] PREDNONE = anXSQLVAR.encodeShort((short) DatabaseMetaData.typePredNone); - // TODO Find out why unused - byte[] PREDBASIC = anXSQLVAR.encodeShort((short) DatabaseMetaData.typePredBasic); - byte[] SEARCHABLE = anXSQLVAR.encodeShort((short) DatabaseMetaData.typeSearchable); - byte[] NULLABLE = anXSQLVAR.encodeShort((short) DatabaseMetaData.typeNullable); - //need to construct xsqlvar[] for ResultSetMetaData. XSQLVAR[] xsqlvars = new XSQLVAR[18]; xsqlvars[0] = new XSQLVAR(ISCConstants.SQL_VARYING, 31, "TYPE_NAME", "TYPEINFO"); @@ -3988,100 +4034,86 @@ List<byte[][]> rows = new ArrayList<byte[][]>(); //BIGINT=-5 - rows.add(new byte[][] {getBytes("BIGINT"), createShort(Types.BIGINT) - , anXSQLVAR.encodeInt(64), null, null, null, - NULLABLE, CASEINSENSITIVE, SEARCHABLE, SIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, shortZero, anXSQLVAR.encodeInt(ISCConstants.SQL_INT64), null, BINARY}); - + rows.add(new byte[][]{ getBytes("BIGINT"), createShort(Types.BIGINT), BIGINT_PRECISION, null, null, null, + TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, + SHORT_ZERO, createInt(ISCConstants.SQL_INT64), null, RADIX_TEN }); + //LONGVARBINARY=-4 - rows.add(new byte[][] {getBytes("BLOB SUB_TYPE 0"), createShort(Types.LONGVARBINARY) - , anXSQLVAR.encodeInt(0), null, null, null, - NULLABLE, CASESENSITIVE, PREDNONE, UNSIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, shortZero, anXSQLVAR.encodeInt(ISCConstants.SQL_BLOB), null, BINARY}); + rows.add(new byte[][]{ getBytes("BLOB SUB_TYPE 0"), createShort(Types.LONGVARBINARY), INT_ZERO, null, null, + null, TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, + SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_BINARY }); //LONGVARCHAR=-1 - rows.add(new byte[][] {getBytes("BLOB SUB_TYPE 1"), createShort(Types.LONGVARCHAR) - , anXSQLVAR.encodeInt(0), null, null, null, - NULLABLE, CASESENSITIVE, PREDNONE, UNSIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, shortZero, anXSQLVAR.encodeInt(ISCConstants.SQL_BLOB), null, BINARY}); + rows.add(new byte[][]{ getBytes("BLOB SUB_TYPE 1"), createShort(Types.LONGVARCHAR), INT_ZERO, null, null, + null, TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, + SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_BINARY }); //CHAR=1 - rows.add(new byte[][] {getBytes("CHAR"), createShort(Types.CHAR) - , anXSQLVAR.encodeInt(32664), getBytes("'"), getBytes("'"), getBytes("length"), - NULLABLE, CASESENSITIVE, SEARCHABLE, UNSIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, shortZero, anXSQLVAR.encodeInt(ISCConstants.SQL_TEXT), null, BINARY}); + rows.add(new byte[][]{ getBytes("CHAR"), createShort(Types.CHAR), createInt(32664), getBytes("'"), + getBytes("'"), getBytes("length"), TYPE_NULLABLE, CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED, + FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_TEXT), null, + RADIX_BINARY }); //NUMERIC=2 - rows.add(new byte[][] {getBytes("NUMERIC"), createShort(Types.NUMERIC) - , anXSQLVAR.encodeInt(18), null, null, getBytes("precision,scale"), - NULLABLE, CASEINSENSITIVE, SEARCHABLE, SIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, createShort(18), anXSQLVAR.encodeInt(ISCConstants.SQL_INT64), null, BINARY}); + rows.add(new byte[][]{ getBytes("NUMERIC"), createShort(Types.NUMERIC), NUMERIC_PRECISION, null, null, + getBytes("precision,scale"), TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, + NOTAUTOINC, null, SHORT_ZERO, NUMERIC_PRECISION, createInt(ISCConstants.SQL_INT64), null, RADIX_TEN }); //DECIMAL=3 - rows.add(new byte[][] {getBytes("DECIMAL"), createShort(Types.DECIMAL) - , anXSQLVAR.encodeInt(18), null, null, getBytes("precision,scale"), - NULLABLE, CASEINSENSITIVE, SEARCHABLE, SIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, createShort(18), anXSQLVAR.encodeInt(ISCConstants.SQL_INT64), null, BINARY}); + rows.add(new byte[][]{ getBytes("DECIMAL"), createShort(Types.DECIMAL), DECIMAL_PRECISION, null, null, + getBytes("precision,scale"), TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, + NOTAUTOINC, null, SHORT_ZERO, DECIMAL_PRECISION, createInt(ISCConstants.SQL_INT64), null, RADIX_TEN }); //INTEGER=4 - rows.add(new byte[][] {getBytes("INTEGER"), createShort(Types.INTEGER) - , anXSQLVAR.encodeInt(32), null, null, null, - NULLABLE, CASEINSENSITIVE, SEARCHABLE, SIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, shortZero, anXSQLVAR.encodeInt(ISCConstants.SQL_LONG), null, BINARY}); + rows.add(new byte[][]{ getBytes("INTEGER"), createShort(Types.INTEGER), INTEGER_PRECISION, null, null, null, + TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, + SHORT_ZERO, createInt(ISCConstants.SQL_LONG), null, RADIX_TEN }); //SMALLINT=5 - rows.add(new byte[][] {getBytes("SMALLINT"), createShort(Types.SMALLINT) - , anXSQLVAR.encodeInt(16), null, null, null, - NULLABLE, CASEINSENSITIVE, SEARCHABLE, SIGNED, FIXEDSCALE, - NOTAUTOINC, null, shortZero, shortZero, anXSQLVAR.encodeInt(ISCConstants.SQL_SHORT), null, BINARY}); + rows.add(new byte[][]{ getBytes("SMALLINT"), createShort(Types.SMALLINT), SMALLINT_PRECISION, null, null, + null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, ... [truncated message content] |
From: <mro...@us...> - 2013-05-28 20:54:28
|
Revision: 58109 http://sourceforge.net/p/firebird/code/58109 Author: mrotteveel Date: 2013-05-28 20:54:26 +0000 (Tue, 28 May 2013) Log Message: ----------- JDBC-312 Fix for repeated batch execution with binary stream inserts empty blob in all batches after the first Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlobInputStream.java client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBlobField.java client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBCachedBlobField.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBPreparedStatement.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java 2013-05-28 18:48:18 UTC (rev 58108) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlob.java 2013-05-28 20:54:26 UTC (rev 58109) @@ -50,9 +50,9 @@ private FBBlobOutputStream blobOut = null; private FBBlob(GDSHelper c, boolean isNew, FBObjectListener.BlobListener blobListener) { - this.gdsHelper = c; + gdsHelper = c; this.isNew = isNew; - this.bufferlength = c.getBlobBufferLength(); + bufferlength = c.getBlobBufferLength(); this.blobListener = blobListener; } @@ -61,7 +61,7 @@ * writing to the Blob is allowed. * * @param c connection that will be used to write data to blob - * @param blobListener BlobListener + * @param blobListener Blob listener instance */ public FBBlob(GDSHelper c, FBObjectListener.BlobListener blobListener) { this(c, true, blobListener); @@ -82,24 +82,23 @@ * * @param c connection that will be used to access Blob. * @param blob_id ID of the Blob. - * @param blobListener BlobListener + * @param blobListener Blob listener instance */ public FBBlob(GDSHelper c, long blob_id, FBObjectListener.BlobListener blobListener) { this(c, false, blobListener); this.blob_id = blob_id; } - + + /** + * Create instance of this class to access existing Blob. + * + * @param c connection that will be used to access Blob. + * @param blob_id ID of the Blob. + */ public FBBlob(GDSHelper c, long blob_id) { this(c, blob_id, null); } - - /** - * Get synchronization object that will be used to synchronize multithreaded - * access to the database. - * - * @return object that will be used for synchronization. - */ public Object getSynchronizationObject() { return gdsHelper; } @@ -111,9 +110,7 @@ * when closed. */ public void close() throws IOException { - Object syncObject = getSynchronizationObject(); - synchronized(syncObject) { - + synchronized(getSynchronizationObject()) { IOException error = null; for (FBBlobInputStream blobIS : inputStreams) { @@ -188,10 +185,7 @@ * @throws SQLException if something went wrong. */ public byte[] getInfo(byte[] items, int buffer_length) throws SQLException { - - Object syncObject = getSynchronizationObject(); - - synchronized(syncObject) { + synchronized(getSynchronizationObject()) { try { if (blobListener != null) blobListener.executionStarted(this); @@ -200,6 +194,7 @@ try { return gdsHelper.getBlobInfo(blob, items, buffer_length); } finally { + // TODO Does it make sense to close blob here? gdsHelper.closeBlob(blob); } @@ -240,7 +235,6 @@ * @throws SQLException if length cannot be interpreted. */ public static long interpretLength(GDSHelper gdsHelper, byte[] info, int position) throws SQLException { - if (info[position] != ISCConstants.isc_info_blob_total_length) throw new FBSQLException("Length is not available."); @@ -331,9 +325,8 @@ throw new FBSQLException("Blob position is limited to 2^31 - 1 " + "due to isc_seek_blob limitations.", FBSQLException.SQL_STATE_INVALID_ARG_VALUE); - - Object syncObject = getSynchronizationObject(); - synchronized(syncObject) { + + synchronized(getSynchronizationObject()) { if (blobListener != null) blobListener.executionStarted(this); @@ -359,8 +352,7 @@ } public InputStream getBinaryStream () throws SQLException { - Object syncObject = getSynchronizationObject(); - synchronized(syncObject) { + synchronized(getSynchronizationObject()) { FBBlobInputStream blobstream = new FBBlobInputStream(this); inputStreams.add(blobstream); return blobstream; Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlobInputStream.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlobInputStream.java 2013-05-28 18:48:18 UTC (rev 58108) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBBlobInputStream.java 2013-05-28 20:54:26 UTC (rev 58109) @@ -9,9 +9,12 @@ * An input stream for reading directly from a FBBlob instance. */ public class FBBlobInputStream extends InputStream implements FirebirdBlob.BlobInputStream { - - private static final int READ_FULLY_BUFFER_SIZE = 16 * 1024; + /** + * Maximum blob segment size, see IB 6 Data Definition Guide, page 78 ("BLOB segment length") + */ + private static final int READ_FULLY_BUFFER_SIZE = 32 * 1024; + private byte[] buffer = null; private IscBlobHandle blobHandle; private int pos = 0; Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBlobField.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBlobField.java 2013-05-28 18:48:18 UTC (rev 58108) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBlobField.java 2013-05-28 20:54:26 UTC (rev 58109) @@ -1,4 +1,6 @@ /* + * $Id$ + * * Firebird Open Source J2ee connector - jdbc driver * * Distributable under LGPL license. @@ -16,17 +18,24 @@ * * All rights reserved. */ - + package org.firebirdsql.jdbc.field; -import java.io.*; +import org.firebirdsql.gds.GDSException; +import org.firebirdsql.gds.IscBlobHandle; +import org.firebirdsql.gds.XSQLVAR; +import org.firebirdsql.jdbc.FBBlob; +import org.firebirdsql.jdbc.FBClob; +import org.firebirdsql.jdbc.FBSQLException; +import org.firebirdsql.jdbc.Synchronizable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; -import org.firebirdsql.gds.*; -import org.firebirdsql.jdbc.*; - /** * Describe class <code>FBBlobField</code> here. * @@ -36,60 +45,49 @@ public class FBBlobField extends FBField implements FBFlushableField { private FBBlob blob; - // Rather then hold cached data in the XSQLDAVar we will hold it in here. - private int length; - private InputStream binaryStream; + // Rather then hold cached data in the XSQLDAVar we will hold it in here. + private int length; + private InputStream binaryStream; private Reader characterStream; private byte[] bytes; - FBBlobField(XSQLVAR field, FieldDataProvider dataProvider, int requiredType) - throws SQLException - { + FBBlobField(XSQLVAR field, FieldDataProvider dataProvider, int requiredType) throws SQLException { super(field, dataProvider, requiredType); } - + public void close() throws SQLException { try { - if (blob != null) - blob.close(); - } catch(IOException ioex) { + if (blob != null) blob.close(); + } catch (IOException ioex) { throw new FBSQLException(ioex); - } finally { + } finally { // forget this blob instance, resource waste // but simplifies our life. BLOB handle will be // released by a server automatically later - + blob = null; - this.bytes = null; - this.binaryStream = null; - this.characterStream = null; - this.length = 0; + bytes = null; + binaryStream = null; + characterStream = null; + length = 0; } } public Blob getBlob() throws SQLException { - if (blob != null) - return blob; - + if (blob != null) return blob; final byte[] bytes = getFieldData(); + if (bytes == null) return BLOB_NULL_VALUE; - if (bytes == null) - return BLOB_NULL_VALUE; - /*@todo convert this into a method of FirebirdConnection */ - blob = new FBBlob(gdsHelper, field.decodeLong( bytes )); + blob = new FBBlob(gdsHelper, field.decodeLong(bytes)); return blob; } - + public Clob getClob() throws SQLException { - FBBlob blob = (FBBlob) getBlob(); - - if (blob == BLOB_NULL_VALUE){ - return CLOB_NULL_VALUE; - } - - return new FBClob(blob); + FBBlob blob = (FBBlob) getBlob(); + if (blob == BLOB_NULL_VALUE) return CLOB_NULL_VALUE; + return new FBClob(blob); } public InputStream getAsciiStream() throws SQLException { @@ -100,12 +98,10 @@ // getBinaryStream() is not defined for BLOB types, only for BINARY if (field.sqlsubtype < 0) throw new TypeConversionException(BINARY_STREAM_CONVERSION_ERROR); - + Blob blob = getBlob(); + if (blob == BLOB_NULL_VALUE) return STREAM_NULL_VALUE; - if (blob == BLOB_NULL_VALUE) - return STREAM_NULL_VALUE; - return blob.getBinaryStream(); } @@ -117,81 +113,69 @@ return getBytesInternal(); } - + public byte[] getBytesInternal() throws SQLException { + final byte[] blobIdBuffer = getFieldData(); + if (blobIdBuffer == null) return BYTES_NULL_VALUE; - final byte[] blobIdBuffer = getFieldData(); - - if (blobIdBuffer == null) - return BYTES_NULL_VALUE; - final long blobId = field.decodeLong(blobIdBuffer); - - Object syncObject = ((Synchronizable)getBlob()).getSynchronizationObject(); - synchronized (syncObject) { + synchronized (((Synchronizable) getBlob()).getSynchronizationObject()) { try { - final IscBlobHandle blobHandle = - gdsHelper.openBlob(blobId, FBBlob.SEGMENTED); - + final IscBlobHandle blobHandle = gdsHelper.openBlob(blobId, FBBlob.SEGMENTED); + try { final int blobLength = gdsHelper.getBlobLength(blobHandle); - final int bufferLength = gdsHelper.getBlobBufferLength(); final byte[] resultBuffer = new byte[blobLength]; - + int offset = 0; - + while (offset < blobLength) { - final byte[] segementBuffer = - gdsHelper.getBlobSegment(blobHandle, bufferLength); - - if (segementBuffer.length == 0) { + final byte[] segmentBuffer = gdsHelper.getBlobSegment(blobHandle, bufferLength); + + if (segmentBuffer.length == 0) { // unexpected EOF throw new TypeConversionException(BYTES_CONVERSION_ERROR); } - - System.arraycopy(segementBuffer, 0, resultBuffer, offset, segementBuffer.length); - - offset += segementBuffer.length; + + System.arraycopy(segmentBuffer, 0, resultBuffer, offset, segmentBuffer.length); + offset += segmentBuffer.length; } - + return resultBuffer; - + } finally { gdsHelper.closeBlob(blobHandle); } - } catch (GDSException e) { throw new FBSQLException(e); } } - } public byte[] getCachedData() throws SQLException { - if (getFieldData() == null) { - - if (bytes != null) - return bytes; - else - return BYTES_NULL_VALUE; + if (getFieldData() != null) { + return getBytesInternal(); + } else if (bytes != null) { + return bytes; + } else { + return BYTES_NULL_VALUE; } + } - return getBytesInternal(); - } - public FBFlushableField.CachedObject getCachedObject() throws SQLException { - if (getFieldData() == null) + if (getFieldData() == null) return new FBFlushableField.CachedObject(bytes, binaryStream, characterStream, length); - return new CachedObject(getBytesInternal(), null, null, 0); } - + public void setCachedObject(FBFlushableField.CachedObject cachedObject) throws SQLException { - this.bytes = cachedObject.bytes; - this.binaryStream = cachedObject.binaryStream; - this.characterStream = cachedObject.characterStream; - this.length = cachedObject.length; + // setNull() to reset field to empty state + setNull(); + bytes = cachedObject.bytes; + binaryStream = cachedObject.binaryStream; + characterStream = cachedObject.characterStream; + length = cachedObject.length; } public String getString() throws SQLException { @@ -201,8 +185,7 @@ Blob blob = getBlob(); - if (blob == BLOB_NULL_VALUE) - return STRING_NULL_VALUE; + if (blob == BLOB_NULL_VALUE) return STRING_NULL_VALUE; return field.decodeString(getBytes(), javaEncoding, mappingPath); } @@ -213,87 +196,69 @@ //--- setXXX methods - - public void setAsciiStream(InputStream in, int length) throws SQLException { setBinaryStream(in, length); } public void setCharacterStream(Reader in, int length) throws SQLException { - - if (in == READER_NULL_VALUE) { - setNull(); - return; + // setNull() to reset field to empty state + setNull(); + if (in != READER_NULL_VALUE) { + characterStream = in; + this.length = length; } - - this.binaryStream = null; - this.characterStream = in; - this.bytes = null; - this.length = length; } public void setBinaryStream(InputStream in, int length) throws SQLException { - - if (in == STREAM_NULL_VALUE) { - setNull(); - return; + // setNull() to reset field to empty state + setNull(); + if (in != STREAM_NULL_VALUE) { + binaryStream = in; + this.length = length; } - - this.binaryStream = in; - this.characterStream = null; - this.bytes = null; - this.length = length; } - + public void flushCachedData() throws SQLException { if (binaryStream != null) - copyBinaryStream(this.binaryStream, this.length); - else - if (characterStream != null) + copyBinaryStream(binaryStream, length); + else if (characterStream != null) copyCharacterStream(characterStream, length, javaEncoding); - else - if (bytes != null) + else if (bytes != null) copyBytes(bytes, length); - else - if (blob == null) + else if (blob == null) setNull(); - + this.characterStream = null; this.binaryStream = null; this.bytes = null; this.length = 0; } - + private void copyBinaryStream(InputStream in, int length) throws SQLException { - - FBBlob blob = new FBBlob(gdsHelper); + FBBlob blob = new FBBlob(gdsHelper); blob.copyStream(in, length); setFieldData(field.encodeLong(blob.getBlobId())); } private void copyCharacterStream(Reader in, int length, String encoding) throws SQLException { - FBBlob blob = new FBBlob(gdsHelper); + FBBlob blob = new FBBlob(gdsHelper); blob.copyCharacterStream(in, length, encoding); setFieldData(field.encodeLong(blob.getBlobId())); } - + private void copyBytes(byte[] bytes, int length) throws SQLException { FBBlob blob = new FBBlob(gdsHelper); blob.copyBytes(bytes, 0, length); setFieldData(field.encodeLong(blob.getBlobId())); } - + public void setBytes(byte[] value) throws SQLException { - - if (value == BYTES_NULL_VALUE) { - setNull(); - return; + // setNull() to reset field to empty state + setNull(); + if (value != BYTES_NULL_VALUE) { + bytes = value; + length = value.length; } - - this.binaryStream = null; - this.characterStream = null; - this.bytes = value; - this.length = value.length; } public void setString(String value) throws SQLException { @@ -301,8 +266,8 @@ setNull(); return; } - - setBytes(field.encodeString(value,javaEncoding, mappingPath)); + + setBytes(field.encodeString(value, javaEncoding, mappingPath)); } public void setUnicodeStream(InputStream in, int length) throws SQLException { @@ -310,21 +275,29 @@ } public void setBlob(FBBlob blob) throws SQLException { + // setNull() to reset field to empty state + setNull(); setFieldData(field.encodeLong(blob.getBlobId())); this.blob = blob; } - + public void setClob(FBClob clob) throws SQLException { - FBBlob blob = clob.getWrappedBlob(); - setBlob(blob); + FBBlob blob = clob.getWrappedBlob(); + setBlob(blob); } public void setNull() { super.setNull(); - - this.binaryStream = null; - this.characterStream = null; - this.bytes = null; - this.length = 0; + try { + if (blob != null) blob.close(); + } catch (IOException e) { + //ignore + } + + blob = null; + binaryStream = null; + characterStream = null; + bytes = null; + length = 0; } } Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBCachedBlobField.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBCachedBlobField.java 2013-05-28 18:48:18 UTC (rev 58108) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBCachedBlobField.java 2013-05-28 20:54:26 UTC (rev 58109) @@ -55,6 +55,7 @@ } public byte[] getBytes() throws SQLException { + // TODO Looks suspicious compared to the implementation in FBBlobField return getFieldData(); } Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBPreparedStatement.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBPreparedStatement.java 2013-05-28 18:48:18 UTC (rev 58108) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBPreparedStatement.java 2013-05-28 20:54:26 UTC (rev 58109) @@ -1,4 +1,6 @@ /* + * $Id$ + * * Firebird Open Source J2ee connector - jdbc driver * * Distributable under LGPL license. @@ -18,39 +20,47 @@ */ package org.firebirdsql.jdbc; -import org.firebirdsql.common.FBTestBase; +import org.firebirdsql.common.FBJUnit4TestBase; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.math.BigDecimal; import java.sql.*; -import java.util.Calendar; -import java.util.Properties; -import java.util.TimeZone; +import java.util.*; -import static org.firebirdsql.common.DdlHelper.*; -import static org.firebirdsql.common.JdbcResourceHelper.*; +import static org.firebirdsql.common.DdlHelper.executeCreateTable; +import static org.firebirdsql.common.DdlHelper.executeDDL; import static org.firebirdsql.common.FBTestProperties.*; +import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; +import static org.junit.Assert.*; /** * Describe class <code>TestFBPreparedStatement</code> here. - * + * * @author <a href="mailto:rro...@us...">Roman Rokytskyy</a> * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @version 1.0 */ -public class TestFBPreparedStatement extends FBTestBase { +public class TestFBPreparedStatement extends FBJUnit4TestBase { + private static final Random rnd = new Random(); + public static final String CREATE_GENERATOR = "CREATE GENERATOR test_generator"; - public static final String CREATE_TEST_BLOB_TABLE = - "CREATE TABLE test_blob (" + public static final String CREATE_TEST_BLOB_TABLE = + "CREATE TABLE test_blob (" + " ID INTEGER, " + " OBJ_DATA BLOB, " + " TS_FIELD TIMESTAMP, " + " T_FIELD TIME " + ")"; - public static final String CREATE_TEST_CHARS_TABLE = - "CREATE TABLE TESTTAB (" + public static final String CREATE_TEST_CHARS_TABLE = + "CREATE TABLE TESTTAB (" + "ID INTEGER, " + "FIELD1 VARCHAR(10) NOT NULL PRIMARY KEY," + "FIELD2 VARCHAR(30)," @@ -62,9 +72,9 @@ + "num_field numeric(9,2)" + ")"; - public static final String CREATE_UNRECOGNIZED_TR_TABLE = - "CREATE TABLE t1(" - + " c1 CHAR(2) CHARACTER SET ASCII NOT NULL, " + public static final String CREATE_UNRECOGNIZED_TR_TABLE = + "CREATE TABLE t1(" + + " c1 CHAR(2) CHARACTER SET ASCII NOT NULL, " + " c2 BLOB SUB_TYPE TEXT CHARACTER SET ASCII NOT NULL " + ")"; @@ -80,15 +90,11 @@ private static final String INSERT_DATA = "INSERT INTO test(col1) VALUES(?)"; private static final String SELECT_DATA = "SELECT col1 FROM test ORDER BY col1"; - public TestFBPreparedStatement(String testName) { - super(testName); - } - private Connection con; - protected void setUp() throws Exception { - super.setUp(); - con = this.getConnectionViaDriverManager(); + @Before + public void setUp() throws Exception { + con = getConnectionViaDriverManager(); Statement stmt = con.createStatement(); try { executeCreateTable(con, CREATE_TEST_BLOB_TABLE); @@ -104,16 +110,16 @@ } } - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { closeQuietly(con); - super.tearDown(); } + @Test public void testModifyBlob() throws Exception { int id = 1; - PreparedStatement insertPs = con - .prepareStatement("INSERT INTO test_blob (id, obj_data) VALUES (?,?);"); + PreparedStatement insertPs = con.prepareStatement("INSERT INTO test_blob (id, obj_data) VALUES (?,?)"); try { insertPs.setInt(1, id); insertPs.setBytes(2, TEST_STRING.getBytes()); @@ -129,7 +135,7 @@ // Update item PreparedStatement updatePs = con - .prepareStatement("UPDATE test_blob SET obj_data=? WHERE id=?;"); + .prepareStatement("UPDATE test_blob SET obj_data=? WHERE id=?"); try { updatePs.setBytes(1, ANOTHER_TEST_STRING.getBytes()); updatePs.setInt(2, id); @@ -150,10 +156,11 @@ closeQuietly(updatePs); } } - + /** * The method {@link java.sql.Statement#executeQuery(String)} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecuteQuery_String() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { @@ -165,10 +172,11 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#executeUpdate(String)} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecuteUpdate_String() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { @@ -180,10 +188,11 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#execute(String)} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecute_String() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { @@ -195,10 +204,11 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#addBatch(String)} should not work on PreparedStatement. */ + @Test public void testUnsupportedAddBatch_String() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { @@ -210,10 +220,11 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#executeUpdate(String, int)} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecuteUpdate_String_int() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { @@ -225,14 +236,15 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#execute(String, int[])} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecuteUpdate_String_intArr() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { - ps.executeUpdate("SELECT * FROM test_blob", new int[] { 1 }); + ps.executeUpdate("SELECT * FROM test_blob", new int[]{ 1 }); fail("Expected SQLException when executing executeUpdate(String, int[]) on PreparedStatement"); } catch (SQLException ex) { assertStatementOnlyException(ex); @@ -240,14 +252,15 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#executeUpdate(String, String[])} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecuteUpdate_String_StringArr() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { - ps.executeUpdate("SELECT * FROM test_blob", new String[] { "col" }); + ps.executeUpdate("SELECT * FROM test_blob", new String[]{ "col" }); fail("Expected SQLException when executing executeUpdate(String, String[]) on PreparedStatement"); } catch (SQLException ex) { assertStatementOnlyException(ex); @@ -255,10 +268,11 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#execute(String, int)} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecute_String_int() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { @@ -270,14 +284,15 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#execute(String, int[])} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecute_String_intArr() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { - ps.execute("SELECT * FROM test_blob", new int[] { 1 }); + ps.execute("SELECT * FROM test_blob", new int[]{ 1 }); fail("Expected SQLException when executing execute(String, int[]) on PreparedStatement"); } catch (SQLException ex) { assertStatementOnlyException(ex); @@ -285,14 +300,15 @@ closeQuietly(ps); } } - + /** * The method {@link java.sql.Statement#execute(String, String[])} should not work on PreparedStatement. */ + @Test public void testUnsupportedExecute_String_StringArr() throws Exception { PreparedStatement ps = con.prepareStatement("SELECT 1 FROM RDB$DATABASE"); try { - ps.execute("SELECT * FROM test_blob", new String[] { "col" }); + ps.execute("SELECT * FROM test_blob", new String[]{ "col" }); fail("Expected SQLException when executing execute(String, String[]) on PreparedStatement"); } catch (SQLException ex) { assertStatementOnlyException(ex); @@ -302,15 +318,15 @@ } private void assertStatementOnlyException(SQLException ex) { - assertEquals("Unexpected SQLState for statement only method called on FBPreparedStatement", + assertEquals("Unexpected SQLState for statement only method called on FBPreparedStatement", FBSQLException.SQL_STATE_GENERAL_ERROR, ex.getSQLState()); - assertEquals("Unexpected exception message for statement only method called on FBPreparedStatement", + assertEquals("Unexpected exception message for statement only method called on FBPreparedStatement", FBPreparedStatement.METHOD_NOT_SUPPORTED, ex.getMessage()); } void checkSelectString(String stringToTest, int id) throws Exception { PreparedStatement selectPs = con - .prepareStatement("SELECT obj_data FROM test_blob WHERE id = ?"); + .prepareStatement("SELECT obj_data FROM test_blob WHERE id = ?"); try { selectPs.setInt(1, id); ResultSet rs = selectPs.executeQuery(); @@ -329,18 +345,19 @@ } } + @Test public void testGenerator() throws Exception { PreparedStatement ps = con - .prepareStatement("SELECT gen_id(test_generator, 1) as new_value FROM rdb$database"); + .prepareStatement("SELECT gen_id(test_generator, 1) as new_value FROM rdb$database"); try { ResultSet rs = ps.executeQuery(); - + assertTrue("Should get at least one row", rs.next()); - + rs.getLong("new_value"); - + assertFalse("should have only one row", rs.next()); - + rs.close(); } finally { closeQuietly(ps); @@ -353,14 +370,15 @@ * problem (@see org.firebirdsql.jdbc.field.FBWorkaroundStringField) this * test case is no longer relevant. In order to make it execute correctly * one has to remove this workaround. - * - * @throws Exception - * if something went wrong. + * + * @throws Exception if something went wrong. */ - public void _testOpCancelled() throws Exception { + @Test + @Ignore(value="Broken due to FBWorkaroundStringField") + public void testOpCancelled() throws Exception { PreparedStatement prep = con - .prepareStatement("INSERT INTO TESTTAB (FIELD1, FIELD3, FIELD4, FIELD5 ) " - + "VALUES ( ?, ?, ?, ? )"); + .prepareStatement("INSERT INTO TESTTAB (FIELD1, FIELD3, FIELD4, FIELD5 ) " + + "VALUES ( ?, ?, ?, ? )"); try { for (int i = 0; i < 5; i++) { if (i == 0) { @@ -402,10 +420,10 @@ /** * Test if parameters are correctly checked for their length. - * - * @throws Exception - * if something went wrong. + * + * @throws Exception if something went wrong. */ + @Test public void testLongParameter() throws Exception { Statement stmt = con.createStatement(); try { @@ -417,7 +435,7 @@ con.setAutoCommit(false); PreparedStatement ps = con - .prepareStatement("UPDATE testtab SET field6=? WHERE id = 1"); + .prepareStatement("UPDATE testtab SET field6=? WHERE id = 1"); try { try { ps.setString(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); @@ -437,23 +455,23 @@ /** * Test if batch execution works correctly. - * - * @throws Exception - * if something went wrong. + * + * @throws Exception if something went wrong. */ + @Test public void testBatch() throws Exception { Statement s = con.createStatement(); try { s.executeUpdate("CREATE TABLE foo (" + "bar varchar(64) NOT NULL, " - + "baz varchar(8) NOT NULL, " + + "baz varchar(8) NOT NULL, " + "CONSTRAINT pk_foo PRIMARY KEY (bar, baz))"); } finally { closeQuietly(s); } PreparedStatement ps = con - .prepareStatement("Insert into foo values (?, ?)"); + .prepareStatement("Insert into foo values (?, ?)"); try { ps.setString(1, "one"); ps.setString(2, "two"); @@ -470,6 +488,7 @@ } } + @Test public void testTimestampWithCalendar() throws Exception { Properties props = new Properties(); props.putAll(getDefaultPropertiesForConnection()); @@ -478,13 +497,13 @@ Connection connection = DriverManager.getConnection(getUrl(), props); try { PreparedStatement stmt = connection - .prepareStatement("INSERT INTO test_blob(id, ts_field) VALUES (?, ?)"); + .prepareStatement("INSERT INTO test_blob(id, ts_field) VALUES (?, ?)"); try { Calendar calendar = Calendar.getInstance(TimeZone - .getTimeZone("GMT+01")); + .getTimeZone("GMT+01")); Calendar utcCalendar = Calendar.getInstance(TimeZone - .getTimeZone("UTC")); + .getTimeZone("UTC")); Timestamp ts = new Timestamp(calendar.getTime().getTime()); @@ -516,17 +535,17 @@ while (rs.next()) { switch (rs.getInt(1)) { - case 2: - ts2 = rs.getTimestamp(3); - ts2AsStr = rs.getString(2); - ts2AsStr = ts2AsStr.substring(0, Math.min(ts2AsStr.length(), maxLength)); - break; + case 2: + ts2 = rs.getTimestamp(3); + ts2AsStr = rs.getString(2); + ts2AsStr = ts2AsStr.substring(0, Math.min(ts2AsStr.length(), maxLength)); + break; - case 3: - ts3 = rs.getTimestamp(3); - ts3AsStr = rs.getString(2); - ts3AsStr = ts3AsStr.substring(0, Math.min(ts3AsStr.length(), maxLength)); - break; + case 3: + ts3 = rs.getTimestamp(3); + ts3AsStr = rs.getString(2); + ts3AsStr = ts3AsStr.substring(0, Math.min(ts3AsStr.length(), maxLength)); + break; } } @@ -546,6 +565,7 @@ } } + @Test public void testTimeWithCalendar() throws Exception { Properties props = new Properties(); props.putAll(getDefaultPropertiesForConnection()); @@ -554,13 +574,13 @@ Connection connection = DriverManager.getConnection(getUrl(), props); try { PreparedStatement stmt = connection - .prepareStatement("INSERT INTO test_blob(id, t_field) VALUES (?, ?)"); + .prepareStatement("INSERT INTO test_blob(id, t_field) VALUES (?, ?)"); try { Calendar calendar = Calendar.getInstance(TimeZone - .getTimeZone("GMT+01")); + .getTimeZone("GMT+01")); Calendar utcCalendar = Calendar.getInstance(TimeZone - .getTimeZone("UTC")); + .getTimeZone("UTC")); Time t = new Time(calendar.getTime().getTime()); @@ -590,15 +610,15 @@ while (rs.next()) { switch (rs.getInt(1)) { - case 2: - t2 = rs.getTime(3); - t2Str = rs.getString(2); - break; + case 2: + t2 = rs.getTime(3); + t2Str = rs.getString(2); + break; - case 3: - t3 = rs.getTime(3); - t3Str = rs.getString(2); - break; + case 3: + t3 = rs.getTime(3); + t3Str = rs.getString(2); + break; } } @@ -618,18 +638,19 @@ /** * Test if failure in setting the parameter leaves the driver in correct * state (i.e. "not all params were set"). - * + * * @throws Exception */ + @Test public void testBindParameter() throws Exception { con.setAutoCommit(false); PreparedStatement ps = con - .prepareStatement("UPDATE testtab SET field1 = ? WHERE id = ?"); + .prepareStatement("UPDATE testtab SET field1 = ? WHERE id = ?"); try { try { ps.setString(1, - "veeeeeeeeeeeeeeeeeeeeery looooooooooooooooooooooong striiiiiiiiiiiiiiiiiiing"); + "veeeeeeeeeeeeeeeeeeeeery looooooooooooooooooooooong striiiiiiiiiiiiiiiiiiing"); } catch (DataTruncation ex) { // ignore } @@ -659,14 +680,15 @@ /** * Test if failure in setting the parameter leaves the driver in correct * state (i.e. "not all params were set"). - * + * * @throws Exception */ + @Test public void testLikeParameter() throws Exception { con.setAutoCommit(false); PreparedStatement ps = con - .prepareStatement("SELECT * FROM testtab WHERE field7 = ?"); + .prepareStatement("SELECT * FROM testtab WHERE field7 = ?"); try { try { ps.setString(1, "%a%"); @@ -703,18 +725,19 @@ } } + @Test public void testGetExecutionPlan() throws SQLException { - FBPreparedStatement stmt = (FBPreparedStatement)con + FBPreparedStatement stmt = (FBPreparedStatement) con .prepareStatement("SELECT * FROM TESTTAB WHERE ID = 2"); try { String executionPlan = stmt.getExecutionPlan(); assertTrue("Ensure that a valid execution plan is retrieved", - executionPlan.indexOf("TESTTAB") >= 0); + executionPlan.indexOf("TESTTAB") >= 0); } finally { closeQuietly(stmt); } } - + protected void checkStatementType(String query, int expectedStatementType, String assertionMessage) throws SQLException { FBPreparedStatement stmt = (FBPreparedStatement) con .prepareStatement(query); @@ -726,33 +749,40 @@ closeQuietly(stmt); } } - + + @Test public void testGetStatementType_Select() throws SQLException { - checkStatementType("SELECT * FROM TESTTAB", FirebirdPreparedStatement.TYPE_SELECT, + checkStatementType("SELECT * FROM TESTTAB", FirebirdPreparedStatement.TYPE_SELECT, "TYPE_SELECT should be returned for a SELECT statement"); } - + + @Test public void testGetStatementType_Insert() throws SQLException { - checkStatementType("INSERT INTO testtab(id, field1, field6) VALUES(?, ?, ?)", FirebirdPreparedStatement.TYPE_INSERT, + checkStatementType("INSERT INTO testtab(id, field1, field6) VALUES(?, ?, ?)", FirebirdPreparedStatement.TYPE_INSERT, "TYPE_INSERT should be returned for an INSERT statement"); } - + + @Test public void testGetStatementType_Delete() throws SQLException { - checkStatementType("DELETE FROM TESTTAB WHERE ID = ?", FirebirdPreparedStatement.TYPE_DELETE, + checkStatementType("DELETE FROM TESTTAB WHERE ID = ?", FirebirdPreparedStatement.TYPE_DELETE, "TYPE_DELETE should be returned for a DELETE statement"); } - + + @Test public void testGetStatementType_Update() throws SQLException { - checkStatementType("UPDATE TESTTAB SET FIELD1 = ? WHERE ID = ?", FirebirdPreparedStatement.TYPE_UPDATE, + checkStatementType("UPDATE TESTTAB SET FIELD1 = ? WHERE ID = ?", FirebirdPreparedStatement.TYPE_UPDATE, "TYPE_UPDATE should be returned for an UPDATE statement"); } - + + @Test public void testGetStatementType_InsertReturning() throws SQLException { - checkStatementType("INSERT INTO testtab(field1) VALUES(?) RETURNING id", FirebirdPreparedStatement.TYPE_EXEC_PROCEDURE, + checkStatementType("INSERT INTO testtab(field1) VALUES(?) RETURNING id", FirebirdPreparedStatement.TYPE_EXEC_PROCEDURE, "TYPE_EXEC_PROCEDURE should be returned for an INSERT ... RETURNING statement"); } - public void _testLikeFullLength() throws Exception { + @Test + @Ignore + public void testLikeFullLength() throws Exception { Statement stmt = con.createStatement(); try { stmt.execute("INSERT INTO testtab(field1) VALUES('abcdefghij')"); @@ -761,7 +791,7 @@ } PreparedStatement ps = con - .prepareStatement("SELECT field1 FROM testtab WHERE field1 LIKE ?"); + .prepareStatement("SELECT field1 FROM testtab WHERE field1 LIKE ?"); try { ps.setString(1, "%abcdefghi%"); @@ -774,10 +804,10 @@ /** * Test if parameters are correctly checked for their length. - * - * @throws Exception - * if something went wrong. + * + * @throws Exception if something went wrong. */ + @Test public void testNumeric15_2() throws Exception { Properties props = getDefaultPropertiesForConnection(); props.setProperty("sqlDialect", "1"); @@ -818,19 +848,20 @@ } } + @Test public void testInsertReturning() throws Exception { FirebirdPreparedStatement stmt = (FirebirdPreparedStatement) con - .prepareStatement("INSERT INTO testtab(id, field1) VALUES(gen_id(test_generator, 1), 'a') RETURNING id");; + .prepareStatement("INSERT INTO testtab(id, field1) VALUES(gen_id(test_generator, 1), 'a') RETURNING id"); try { assertEquals( - "TYPE_EXEC_PROCEDURE should be returned for an INSERT...RETURNING statement", + "TYPE_EXEC_PROCEDURE should be returned for an INSERT...RETURNING statement", FirebirdPreparedStatement.TYPE_EXEC_PROCEDURE, stmt.getStatementType()); ResultSet rs = stmt.executeQuery(); assertTrue("Should return at least 1 row", rs.next()); assertTrue("Generator value should be > 0 (actual value is " - + rs.getInt(1) + ")", rs.getInt(1) > 0); + + rs.getInt(1) + ")", rs.getInt(1) > 0); assertFalse("Should return exactly one row", rs.next()); } finally { @@ -838,20 +869,21 @@ } } - private static final String dummySelect = - "execute block returns(a integer) " - + " as" - + " declare variable i integer;" - + " begin" - + " i = 1;" - + " while(i < 10000) do begin" - + " EXECUTE STATEMENT 'SELECT ' || :i || ' FROM rdb$database' INTO :a;" - + " i = i + 1;" - + " suspend;" - + " end" - + " end"; + private static final String dummySelect = + "execute block returns(a integer) " + + " as" + + " declare variable i integer;" + + " begin" + + " i = 1;" + + " while(i < 10000) do begin" + + " EXECUTE STATEMENT 'SELECT ' || :i || ' FROM rdb$database' INTO :a;" + + " i = i + 1;" + + " suspend;" + + " end" + + " end"; // TODO: This test intermittently fails + @Test public void testCancelStatement() throws Exception { final Statement stmt = con.createStatement(); try { @@ -873,7 +905,7 @@ }, "cancel-thread"); cancelThread.start(); - + int i = 0; try { while (hasRecord) { @@ -894,10 +926,11 @@ closeQuietly(stmt); } } - + /** * Tests NULL parameter when using {@link PreparedStatement#setNull(int, int)} */ + @Test public void testParameterIsNullQuerySetNull() throws Throwable { createIsNullTestData(); @@ -925,11 +958,12 @@ /** * Tests NULL parameter when using actual (non-null) value in {@link PreparedStatement#setString(int, String)} */ + @Test public void testParameterIsNullQueryWithValues() throws Throwable { createIsNullTestData(); PreparedStatement ps = con.prepareStatement( - "SELECT id FROM testtab WHERE field2 = ? OR ? IS NULL ORDER BY 1"); + "SELECT id FROM testtab WHERE field2 = ? OR ? IS NULL ORDER BY 1"); ResultSet rs; try { ps.setString(1, "a"); @@ -947,10 +981,11 @@ closeQuietly(ps); } } - + /** * Tests NULL parameter when using null value in {@link PreparedStatement#setString(int, String)} */ + @Test public void testParameterIsNullQueryWithNull() throws Throwable { createIsNullTestData(); @@ -987,9 +1022,10 @@ /** * Closing a statement twice should not result in an Exception. - * + * * @throws SQLException */ + @Test public void testDoubleClose() throws SQLException { PreparedStatement stmt = con.prepareStatement("SELECT 1, 2 FROM RDB$DATABASE"); stmt.close(); @@ -1002,9 +1038,10 @@ * <p> * JDBC 4.1 feature * </p> - * + * * @throws SQLException */ + @Test public void testCloseOnCompletion_StatementClosed_afterImplicitResultSetClose() throws SQLException { FBPreparedStatement stmt = (FBPreparedStatement) con.prepareStatement(SELECT_DATA); try { @@ -1026,15 +1063,16 @@ stmt.close(); } } - + /** * Tests insertion of a single character into a single character field on a UTF8 connection. * <p> * See JDBC-234 for rationale of this test. * </p> - * + * * @throws Exception */ + @Test public void testInsertSingleCharOnUTF8() throws Exception { Properties props = getDefaultPropertiesForConnection(); props.setProperty("lc_ctype", "UTF8"); @@ -1047,15 +1085,16 @@ closeQuietly(connection); } } - + /** * Tests if a parameter with a CAST around it will correctly be NULL when set * <p> * See JDBC-271 for rationale of this test. * </p> - * + * * @throws Exception */ + @Test public void testNullParameterWithCast() throws Exception { PreparedStatement stmt = con.prepareStatement("SELECT CAST(? AS VARCHAR(1)) FROM RDB$DATABASE"); try { @@ -1069,6 +1108,60 @@ } } + /** + * Tests multiple batch executions in a row when using blobs created from a stream + * <p> + * See <a href="http://tracker.firebirdsql.org/browse/JDBC-312">JDBC-312</a> + * </p> + */ + @Test + public void testRepeatedBatchExecutionWithBlobFromStream() throws Exception { + con.setAutoCommit(false); + List<byte[]> expectedData = new ArrayList<byte[]>(); + try { + PreparedStatement insert = con.prepareStatement("INSERT INTO test_blob (id, obj_data) VALUES (?,?)"); + // Execute two separate batches inserting a random blob + try { + for (int i = 0; i < 2; i++) { + byte[] testData = new byte[50]; + rnd.nextBytes(testData); + expectedData.add(testData.clone()); + insert.setInt(1, i); + InputStream in = new ByteArrayInputStream(testData); + insert.setBinaryStream(2, in, testData.length); + insert.addBatch(); + insert.executeBatch(); + } + } finally { + closeQuietly(insert); + } + + // Check if the stored data matches the retrieved data + Statement select = con.createStatement(); + try { + ResultSet rs = select.executeQuery("SELECT id, obj_data FROM test_blob ORDER BY id"); + try { + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt(1); + byte[] data = rs.getBytes(2); + + // TODO Change to JUnit 4 assertArrayEquals + assertArrayEquals(String.format("Unexpected blob data for id %d", id), expectedData.get(id), data); + } + assertEquals("Unexpected number of blobs in table", 2, count); + } finally { + closeQuietly(rs); + } + } finally { + closeQuietly(select); + } + } finally { + con.setAutoCommit(true); + } + } + // Other closeOnCompletion behavior considered to be sufficiently tested in TestFBStatement private void prepareTestData() throws SQLException { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-07-27 09:43:55
|
Revision: 58417 http://sourceforge.net/p/firebird/code/58417 Author: mrotteveel Date: 2013-07-27 09:43:52 +0000 (Sat, 27 Jul 2013) Log Message: ----------- JDBC-317 Fix incorrect values for NUM_PREC_RADIX in getColumns and getTypeInfo Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataColumns.java Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2013-07-27 00:24:23 UTC (rev 58416) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2013-07-27 09:43:52 UTC (rev 58417) @@ -2570,14 +2570,13 @@ // Defaults: some are overridden in the switch row[8] = null; - row[9] = null; + row[9] = RADIX_TEN; row[15] = null; switch (dataType){ case Types.DECIMAL: case Types.NUMERIC: row[6] = createInt(rs.getShort("FIELD_PRECISION")); row[8] = createInt(fieldScale * (-1)); - row[9] = RADIX_TEN; break; case Types.CHAR: case Types.VARCHAR: @@ -2591,26 +2590,21 @@ break; case Types.FLOAT: row[6] = FLOAT_PRECISION; - row[9] = RADIX_TEN; break; case Types.DOUBLE: row[6] = DOUBLE_PRECISION; - row[9] = RADIX_TEN; break; case Types.BIGINT: row[6] = BIGINT_PRECISION; row[8] = INT_ZERO; - row[9] = RADIX_TEN; break; case Types.INTEGER: row[6] = INTEGER_PRECISION; row[8] = INT_ZERO; - row[9] = RADIX_TEN; break; case Types.SMALLINT: row[6] = SMALLINT_PRECISION; row[8] = INT_ZERO; - row[9] = RADIX_TEN; break; case Types.DATE: row[6] = DATE_PRECISION; @@ -4041,18 +4035,18 @@ //LONGVARBINARY=-4 rows.add(new byte[][]{ getBytes("BLOB SUB_TYPE 0"), createShort(Types.LONGVARBINARY), INT_ZERO, null, null, null, TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, - SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_BINARY }); + SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_TEN }); //LONGVARCHAR=-1 rows.add(new byte[][]{ getBytes("BLOB SUB_TYPE 1"), createShort(Types.LONGVARCHAR), INT_ZERO, null, null, null, TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, - SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_BINARY }); + SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_TEN }); //CHAR=1 rows.add(new byte[][]{ getBytes("CHAR"), createShort(Types.CHAR), createInt(32664), getBytes("'"), getBytes("'"), getBytes("length"), TYPE_NULLABLE, CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_TEXT), null, - RADIX_BINARY }); + RADIX_TEN }); //NUMERIC=2 rows.add(new byte[][]{ getBytes("NUMERIC"), createShort(Types.NUMERIC), NUMERIC_PRECISION, null, null, @@ -4077,43 +4071,43 @@ //FLOAT=6 rows.add(new byte[][]{ getBytes("FLOAT"), createShort(Types.FLOAT), FLOAT_PRECISION, null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, VARIABLESCALE, NOTAUTOINC, null, SHORT_ZERO, - SHORT_ZERO, createInt(ISCConstants.SQL_FLOAT), null, RADIX_BINARY }); + SHORT_ZERO, createInt(ISCConstants.SQL_FLOAT), null, RADIX_TEN }); //DOUBLE=8 rows.add(new byte[][]{ getBytes("DOUBLE PRECISION"), createShort(Types.DOUBLE), DOUBLE_PRECISION, null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, SIGNED, VARIABLESCALE, NOTAUTOINC, null, - SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_DOUBLE), null, RADIX_BINARY }); + SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_DOUBLE), null, RADIX_TEN }); //VARCHAR=12 rows.add(new byte[][]{ getBytes("VARCHAR"), createShort(Types.VARCHAR), createInt(32664), getBytes("'"), getBytes("'"), getBytes("length"), TYPE_NULLABLE, CASESENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_VARYING), null, - RADIX_BINARY }); + RADIX_TEN }); //DATE=91 rows.add(new byte[][]{ getBytes("DATE"), createShort(Types.DATE), DATE_PRECISION, null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, - SHORT_ZERO, createInt(ISCConstants.SQL_TYPE_DATE), null, RADIX_BINARY }); + SHORT_ZERO, createInt(ISCConstants.SQL_TYPE_DATE), null, RADIX_TEN }); //TIME=92 rows.add(new byte[][]{ getBytes("TIME"), createShort(Types.TIME), TIME_PRECISION, null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, - SHORT_ZERO, createInt(ISCConstants.SQL_TYPE_TIME), null, RADIX_BINARY }); + SHORT_ZERO, createInt(ISCConstants.SQL_TYPE_TIME), null, RADIX_TEN }); //TIMESTAMP=93 rows.add(new byte[][]{ getBytes("TIMESTAMP"), createShort(Types.TIMESTAMP), TIMESTAMP_PRECISION, null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_SEARCHABLE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, - SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_TIMESTAMP), null, RADIX_BINARY }); + SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_TIMESTAMP), null, RADIX_TEN }); //OTHER=1111 rows.add(new byte[][]{ getBytes("ARRAY"), createShort(Types.OTHER), INT_ZERO, null, null, null, TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, - createInt(ISCConstants.SQL_ARRAY), null, RADIX_BINARY }); + createInt(ISCConstants.SQL_ARRAY), null, RADIX_TEN }); //BLOB=2004 rows.add(new byte[][]{ getBytes("BLOB SUB_TYPE <0 "), createShort(Types.BLOB), INT_ZERO, null, null, null, TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, - SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_BINARY }); + SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_TEN }); return new FBResultSet(xsqlvars, rows); } Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataColumns.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataColumns.java 2013-07-27 00:24:23 UTC (rev 58416) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataColumns.java 2013-07-27 09:43:52 UTC (rev 58417) @@ -119,7 +119,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "INTEGER"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 10); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); // Explicit comment: validationRules.put(ColumnMetaData.REMARKS, "Some comment"); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 1); @@ -138,7 +137,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "INTEGER"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 10); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 32); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); validationRules.put(ColumnMetaData.COLUMN_DEF, "NULL"); @@ -155,7 +153,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "INTEGER"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 10); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 33); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); validationRules.put(ColumnMetaData.COLUMN_DEF, "999"); @@ -173,7 +170,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "INTEGER"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 10); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 30); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); validationRules.put(ColumnMetaData.NULLABLE, DatabaseMetaData.columnNoNulls); @@ -192,7 +188,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "BIGINT"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 19); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 2); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -209,7 +204,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "SMALLINT"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 5); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 3); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -225,7 +219,6 @@ validationRules.put(ColumnMetaData.DATA_TYPE, Types.DOUBLE); validationRules.put(ColumnMetaData.TYPE_NAME, "DOUBLE PRECISION"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 15); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 4); validate("test_column_metadata", "col_double", validationRules); @@ -240,7 +233,6 @@ validationRules.put(ColumnMetaData.DATA_TYPE, Types.FLOAT); validationRules.put(ColumnMetaData.TYPE_NAME, "FLOAT"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 7); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 5); validate("test_column_metadata", "col_float", validationRules); @@ -256,7 +248,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "DECIMAL"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 18); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 2); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 6); validate("test_column_metadata", "col_dec18_2", validationRules); @@ -275,7 +266,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "DECIMAL"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 18); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 7); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -292,7 +282,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "DECIMAL"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 7); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 3); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 8); validate("test_column_metadata", "col_dec7_3", validationRules); @@ -311,7 +300,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "DECIMAL"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 7); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 9); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -328,7 +316,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "DECIMAL"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 4); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 3); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 10); validate("test_column_metadata", "col_dec4_3", validationRules); @@ -347,7 +334,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "DECIMAL"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 4); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 11); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -364,7 +350,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "NUMERIC"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 18); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 2); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 12); validate("test_column_metadata", "col_num18_2", validationRules); @@ -383,7 +368,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "NUMERIC"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 18); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 13); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -400,7 +384,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "NUMERIC"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 7); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 3); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 14); validate("test_column_metadata", "col_num7_3", validationRules); @@ -419,7 +402,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "NUMERIC"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 7); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 15); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -436,7 +418,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "NUMERIC"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 4); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 3); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 16); validate("test_column_metadata", "col_num4_3", validationRules); @@ -455,7 +436,6 @@ validationRules.put(ColumnMetaData.TYPE_NAME, "NUMERIC"); validationRules.put(ColumnMetaData.COLUMN_SIZE, 4); validationRules.put(ColumnMetaData.DECIMAL_DIGITS, 0); - validationRules.put(ColumnMetaData.NUM_PREC_RADIX, 10); validationRules.put(ColumnMetaData.ORDINAL_POSITION, 17); validationRules.put(ColumnMetaData.IS_AUTOINCREMENT, ""); @@ -751,7 +731,7 @@ defaults.put(ColumnMetaData.TABLE_SCHEM, null); defaults.put(ColumnMetaData.BUFFER_LENGTH, null); defaults.put(ColumnMetaData.DECIMAL_DIGITS, null); - defaults.put(ColumnMetaData.NUM_PREC_RADIX, null); + defaults.put(ColumnMetaData.NUM_PREC_RADIX, 10); defaults.put(ColumnMetaData.NULLABLE, DatabaseMetaData.columnNullable); defaults.put(ColumnMetaData.REMARKS, null); defaults.put(ColumnMetaData.COLUMN_DEF, null); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-08-05 12:16:27
|
Revision: 58464 http://sourceforge.net/p/firebird/code/58464 Author: mrotteveel Date: 2013-08-05 12:16:24 +0000 (Mon, 05 Aug 2013) Log Message: ----------- JDBC-197 Fix broken tests + add TODOs regarding resolving to NONE if nothing is specified Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/encodings/ConnectionEncodingFactory.java client-java/trunk/src/main/org/firebirdsql/encodings/EncodingFactory.java client-java/trunk/src/main/org/firebirdsql/encodings/IEncodingFactory.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java client-java/trunk/src/test/org/firebirdsql/gds/ng/TestFbConnectionProperties.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/TestWireConnection.java Modified: client-java/trunk/src/main/org/firebirdsql/encodings/ConnectionEncodingFactory.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/encodings/ConnectionEncodingFactory.java 2013-08-05 11:59:28 UTC (rev 58463) +++ client-java/trunk/src/main/org/firebirdsql/encodings/ConnectionEncodingFactory.java 2013-08-05 12:16:24 UTC (rev 58464) @@ -104,6 +104,12 @@ @Override public EncodingDefinition getEncodingDefinition(final String firebirdEncodingName, final String javaCharsetAlias) { + // TODO Consider returning getDefaultEncodingDefinition() if null return factory.getEncodingDefinition(firebirdEncodingName, javaCharsetAlias); } + + @Override + public IEncodingFactory withDefaultEncodingDefinition(EncodingDefinition encodingDefinition) { + return factory.withDefaultEncodingDefinition(encodingDefinition != null ? encodingDefinition : getDefaultEncodingDefinition()); + } } Modified: client-java/trunk/src/main/org/firebirdsql/encodings/EncodingFactory.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/encodings/EncodingFactory.java 2013-08-05 11:59:28 UTC (rev 58463) +++ client-java/trunk/src/main/org/firebirdsql/encodings/EncodingFactory.java 2013-08-05 12:16:24 UTC (rev 58464) @@ -242,6 +242,7 @@ try { EncodingDefinition encodingDefinition = null; Charset charset = null; + // TODO Consider returning getDefaultEncodingDefinition() if both firebirdEncodingName and javaCharsetAlias are NULL if (firebirdEncodingName != null) { encodingDefinition = getEncodingDefinitionByFirebirdName(firebirdEncodingName); if (javaCharsetAlias != null) { @@ -257,18 +258,25 @@ } if (encodingDefinition == null) { + // TODO Consider throwing exception if no EncodingDefinition is found return null; } if (!encodingDefinition.isInformationOnly() && (charset == null || encodingDefinition.getJavaCharset().equals(charset))) { + // Normal encoding definition return encodingDefinition; } if (charset != null) { + /* Construct non-standard combination of Firebird encoding + Java character set + * This allows for special purpose combinations like Firebird ISO8859_3 with Java ISO-8859-1 + * But is mostly intended for using Firebird NONE with a specific java character set + */ return new DefaultEncodingDefinition(encodingDefinition.getFirebirdEncodingName(), charset, encodingDefinition.getMaxBytesPerChar(), encodingDefinition.getFirebirdCharacterSetId(), false); } - if (firebirdEncodingName != null && firebirdEncodingName.equalsIgnoreCase("NONE")) { + if ("NONE".equalsIgnoreCase(firebirdEncodingName)) { return getDefaultEncodingDefinition(); } + // TODO Consider throwing exception if no EncodingDefinition is found return null; } catch (Exception e) { log.debug(String.format("Exception looking up encoding definition for firebirdEncodingName %s, javaCharsetAlias %s", firebirdEncodingName, javaCharsetAlias), e); @@ -276,13 +284,7 @@ } } - /** - * Returns an {@link IEncodingFactory} that uses <code>encodingDefinition</code> as the default. - * - * @param encodingDefinition - * The default encoding to use (or <code>null</code> for the value of {@link #getDefaultEncoding()} - * @return IEncoding instance with the specified default. - */ + @Override public IEncodingFactory withDefaultEncodingDefinition(EncodingDefinition encodingDefinition) { return new ConnectionEncodingFactory(this, encodingDefinition != null ? encodingDefinition : getDefaultEncodingDefinition()); } Modified: client-java/trunk/src/main/org/firebirdsql/encodings/IEncodingFactory.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/encodings/IEncodingFactory.java 2013-08-05 11:59:28 UTC (rev 58463) +++ client-java/trunk/src/main/org/firebirdsql/encodings/IEncodingFactory.java 2013-08-05 12:16:24 UTC (rev 58464) @@ -183,4 +183,13 @@ * @return An EncodingDefinition or null if both parameters are null, no encoding was found or if an exception occurred. */ EncodingDefinition getEncodingDefinition(String firebirdEncodingName, String javaCharsetAlias); + + /** + * Returns an {@link org.firebirdsql.encodings.IEncodingFactory} that uses <code>encodingDefinition</code> as the default. + * + * @param encodingDefinition + * The default encoding to use (or <code>null</code> for the value of {@link #getDefaultEncoding()} + * @return IEncoding instance with the specified default. + */ + IEncodingFactory withDefaultEncodingDefinition(EncodingDefinition encodingDefinition); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java 2013-08-05 11:59:28 UTC (rev 58463) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java 2013-08-05 12:16:24 UTC (rev 58464) @@ -110,12 +110,13 @@ public WireConnection(IConnectionProperties connectionProperties, IEncodingFactory encodingFactory, ProtocolCollection protocols) throws SQLException { this.connectionProperties = new FbConnectionProperties(connectionProperties); this.protocols = protocols; - this.encodingFactory = encodingFactory; encodingDefinition = encodingFactory.getEncodingDefinition(connectionProperties.getEncoding(), connectionProperties.getCharSet()); if (encodingDefinition == null || encodingDefinition.isInformationOnly()) { + // TODO Don't throw exception if encoding/charSet is null (see also TODO inside EncodingFactory.getEncodingDefinition) throw new SQLNonTransientConnectionException(String.format("No valid encoding definition for Firebird encoding %s and/or Java charset %s", connectionProperties.getEncoding(), connectionProperties.getCharSet()), FBSQLException.SQL_STATE_CONNECTION_ERROR); } + this.encodingFactory = encodingFactory.withDefaultEncodingDefinition(encodingDefinition); } public boolean isConnected() { Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/TestFbConnectionProperties.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/TestFbConnectionProperties.java 2013-08-05 11:59:28 UTC (rev 58463) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/TestFbConnectionProperties.java 2013-08-05 12:16:24 UTC (rev 58464) @@ -188,6 +188,9 @@ } else if (parameterType == String.class) { method.invoke(info, method.getName()); testValues.put(descriptor.getName(), method.getName()); + } else if (parameterType == boolean.class) { + method.invoke(info, true); + testValues.put(descriptor.getName(), true); } else { throw new IllegalStateException("Unexpected setter type: " + parameterType); } Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/TestWireConnection.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/TestWireConnection.java 2013-08-05 11:59:28 UTC (rev 58463) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/TestWireConnection.java 2013-08-05 12:16:24 UTC (rev 58464) @@ -58,6 +58,8 @@ connectionInfo.setServerName(FBTestProperties.DB_SERVER_URL); connectionInfo.setPortNumber(FBTestProperties.DB_SERVER_PORT); connectionInfo.setDatabaseName(FBTestProperties.getDatabasePath()); + // TODO consider keeping NONE the default in WireConnection if not specified + connectionInfo.setEncoding("NONE"); } @BeforeClass This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-08-11 12:07:06
|
Revision: 58486 http://sourceforge.net/p/firebird/code/58486 Author: mrotteveel Date: 2013-08-11 12:07:02 +0000 (Sun, 11 Aug 2013) Log Message: ----------- JDBC-321 implement BOOLEAN support Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/ISCConstants.java client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBParameterMetaData.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSetMetaData.java client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBField.java client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBStringField.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataProcedureColumns.java client-java/trunk/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBooleanField.java client-java/trunk/src/test/org/firebirdsql/jdbc/TestBooleanSupport.java client-java/trunk/src/test/org/firebirdsql/jdbc/field/TestFBBooleanField.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/ISCConstants.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ISCConstants.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/gds/ISCConstants.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -1942,7 +1942,7 @@ int SQL_TYPE_TIME = 560; int SQL_TYPE_DATE = 570; int SQL_INT64 = 580; - + int SQL_BOOLEAN = 32764; int SQL_NULL = 32766; /* Historical alias for pre V6 applications */ @@ -1952,6 +1952,6 @@ /* Other stuff */ /*******************/ int CS_NONE = 0; /* No Character Set */ - int CS_BINARY = 1; /* BINARY BYTES */ + int CS_BINARY = 1; /* BINARY BYTES */ int CS_dynamic = 127; // Pseudo number for runtime charset (see intl\charsets.h and references to it in Firebird) } Modified: client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/gds/XSQLVAR.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -647,6 +647,26 @@ } /** + * Decode boolean from supplied data. + * + * @param data (expected) 1 bytes + * @return <code>false</code> when 0, <code>true</code> for all other values + */ + public boolean decodeBoolean(byte[] data) { + return data[0] != 0; + } + + /** + * Encodes boolean to 1 byte data. + * + * @param value Boolean value to encode + * @return <code>true</code> as 1, <code>false</code> as 0. + */ + public byte[] encodeBoolean(boolean value) { + return new byte[] { (byte) (value ? 1 : 0) }; + } + + /** * Helper Class to encode/decode times/dates */ private class datetime{ Modified: client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -1221,6 +1221,9 @@ case ISCConstants.SQL_NULL: xsqlda.ioLength[i] = 0; break; + case ISCConstants.SQL_BOOLEAN: + xsqlda.ioLength[i] = 1 + 1; + break; } } } @@ -1982,27 +1985,31 @@ blr_len = 8; int par_count = 0; - for (int i = 0; i < xsqlda.sqld; i++) { - int dtype = xsqlda.sqlvar[i].sqltype & ~1; - if (dtype == ISCConstants.SQL_VARYING - || dtype == ISCConstants.SQL_TEXT - || dtype == ISCConstants.SQL_NULL) { - blr_len += 3; - } else if (dtype == ISCConstants.SQL_SHORT - || dtype == ISCConstants.SQL_LONG - || dtype == ISCConstants.SQL_INT64 - || dtype == ISCConstants.SQL_QUAD - || dtype == ISCConstants.SQL_BLOB - || dtype == ISCConstants.SQL_ARRAY) { - blr_len += 2; - } else { - blr_len++; - } - blr_len += 2; - par_count += 2; - } + for (int i = 0; i < xsqlda.sqld; i++) { + final int dtype = xsqlda.sqlvar[i].sqltype & ~1; + switch (dtype) { + case ISCConstants.SQL_VARYING: + case ISCConstants.SQL_TEXT: + case ISCConstants.SQL_NULL: + blr_len += 3; + break; + case ISCConstants.SQL_SHORT: + case ISCConstants.SQL_LONG: + case ISCConstants.SQL_INT64: + case ISCConstants.SQL_QUAD: + case ISCConstants.SQL_BLOB: + case ISCConstants.SQL_ARRAY: + blr_len += 2; + break; + default: + blr_len++; + break; + } + blr_len += 2; + par_count += 2; + } - byte[] blr = new byte[blr_len]; + final byte[] blr = new byte[blr_len]; int n = 0; blr[n++] = 5; // blr_version5 @@ -2014,53 +2021,72 @@ blr[n++] = (byte) (par_count >> 8); for (int i = 0; i < xsqlda.sqld; i++) { - int dtype = xsqlda.sqlvar[i].sqltype & ~1; - int len = xsqlda.sqlvar[i].sqllen; - if (dtype == ISCConstants.SQL_VARYING) { - blr[n++] = 37; // blr_varying - blr[n++] = (byte) (len & 255); - blr[n++] = (byte) (len >> 8); - } else if (dtype == ISCConstants.SQL_TEXT) { - blr[n++] = 14; // blr_text - blr[n++] = (byte) (len & 255); - blr[n++] = (byte) (len >> 8); - } else if (dtype == ISCConstants.SQL_NULL) { + final int dtype = xsqlda.sqlvar[i].sqltype & ~1; + final int len = xsqlda.sqlvar[i].sqllen; + switch (dtype) { + case ISCConstants.SQL_VARYING: + blr[n++] = 37; // blr_varying + blr[n++] = (byte) (len & 255); + blr[n++] = (byte) (len >> 8); + break; + case ISCConstants.SQL_TEXT: blr[n++] = 14; // blr_text + blr[n++] = (byte) (len & 255); + blr[n++] = (byte) (len >> 8); + break; + case ISCConstants.SQL_NULL: + blr[n++] = 14; // blr_text blr[n++] = 0; blr[n++] = 0; - } else if (dtype == ISCConstants.SQL_DOUBLE) { - blr[n++] = 27; // blr_double - } else if (dtype == ISCConstants.SQL_FLOAT) { - blr[n++] = 10; // blr_float - } else if (dtype == ISCConstants.SQL_D_FLOAT) { - blr[n++] = 11; // blr_d_float - } else if (dtype == ISCConstants.SQL_TYPE_DATE) { - blr[n++] = 12; // blr_sql_date - } else if (dtype == ISCConstants.SQL_TYPE_TIME) { - blr[n++] = 13; // blr_sql_time - } else if (dtype == ISCConstants.SQL_TIMESTAMP) { - blr[n++] = 35; // blr_timestamp - } else if (dtype == ISCConstants.SQL_BLOB) { - blr[n++] = 9; // blr_quad - blr[n++] = 0; - } else if (dtype == ISCConstants.SQL_ARRAY) { - blr[n++] = 9; // blr_quad - blr[n++] = 0; - } else if (dtype == ISCConstants.SQL_LONG) { - blr[n++] = 8; // blr_long - blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; - } else if (dtype == ISCConstants.SQL_SHORT) { - blr[n++] = 7; // blr_short - blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; - } else if (dtype == ISCConstants.SQL_INT64) { - blr[n++] = 16; // blr_int64 - blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; - } else if (dtype == ISCConstants.SQL_QUAD) { - blr[n++] = 9; // blr_quad - blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; - } else { - // return error_dsql_804 (gds__dsql_sqlda_value_err); - } + break; + case ISCConstants.SQL_DOUBLE: + blr[n++] = 27; // blr_double + break; + case ISCConstants.SQL_FLOAT: + blr[n++] = 10; // blr_float + break; + case ISCConstants.SQL_D_FLOAT: + blr[n++] = 11; // blr_d_float + break; + case ISCConstants.SQL_TYPE_DATE: + blr[n++] = 12; // blr_sql_date + break; + case ISCConstants.SQL_TYPE_TIME: + blr[n++] = 13; // blr_sql_time + break; + case ISCConstants.SQL_TIMESTAMP: + blr[n++] = 35; // blr_timestamp + break; + case ISCConstants.SQL_BLOB: + blr[n++] = 9; // blr_quad + blr[n++] = 0; + break; + case ISCConstants.SQL_ARRAY: + blr[n++] = 9; // blr_quad + blr[n++] = 0; + break; + case ISCConstants.SQL_LONG: + blr[n++] = 8; // blr_long + blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; + break; + case ISCConstants.SQL_SHORT: + blr[n++] = 7; // blr_short + blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; + break; + case ISCConstants.SQL_INT64: + blr[n++] = 16; // blr_int64 + blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; + break; + case ISCConstants.SQL_QUAD: + blr[n++] = 9; // blr_quad + blr[n++] = (byte) xsqlda.sqlvar[i].sqlscale; + break; + case ISCConstants.SQL_BOOLEAN: + blr[n++] = 23; // blr_bool + break; + default: + throw new GDSException(ISCConstants.isc_dsql_sqlda_value_err); + } blr[n++] = 7; // blr_short blr[n++] = 0; Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -85,6 +85,7 @@ private static final byte[] RADIX_TEN = createInt(10); // TODO in implementation short and int are encoded identical, remove distinction? private static final byte[] RADIX_TEN_SHORT = createShort(10); + private static final byte[] RADIX_BINARY_SHORT = createShort(2); private static final byte[] TYPE_PRED_NONE = createShort(DatabaseMetaData.typePredNone); private static final byte[] TYPE_PRED_BASIC = createShort(DatabaseMetaData.typePredBasic); private static final byte[] TYPE_SEARCHABLE = createShort(DatabaseMetaData.typeSearchable); @@ -105,6 +106,7 @@ private static final byte[] TIMESTAMP_PRECISION = createInt(19); private static final byte[] NUMERIC_PRECISION = createInt(18); private static final byte[] DECIMAL_PRECISION = createInt(18); + private static final byte[] BOOLEAN_PRECISION = createInt(1); private static final byte[] COLUMN_NO_NULLS = createInt(DatabaseMetaData.columnNoNulls); private static final byte[] COLUMN_NULLABLE = createInt(DatabaseMetaData.columnNullable); private static final byte[] IMPORTED_KEY_NO_ACTION = createShort(DatabaseMetaData.importedKeyNoAction); @@ -2055,14 +2057,13 @@ // Defaults: some are overridden in the switch row[7] = null; row[9] = null; - row[10] = null; + row[10] = RADIX_TEN_SHORT; row[16] = null; switch (dataType){ case Types.DECIMAL: case Types.NUMERIC: row[7] = createInt(rs.getShort("FIELD_PRECISION")); row[9] = createShort(-1 * fieldScale); - row[10] = RADIX_TEN_SHORT; break; case Types.CHAR: case Types.VARCHAR: @@ -2076,26 +2077,21 @@ break; case Types.FLOAT: row[7] = FLOAT_PRECISION; - row[10] = RADIX_TEN_SHORT; break; case Types.DOUBLE: row[7] = DOUBLE_PRECISION; - row[10] = RADIX_TEN_SHORT; break; case Types.BIGINT: row[7] = BIGINT_PRECISION; row[9] = SHORT_ZERO; - row[10] = RADIX_TEN_SHORT; break; case Types.INTEGER: row[7] = INTEGER_PRECISION; row[9] = SHORT_ZERO; - row[10] = RADIX_TEN_SHORT; break; case Types.SMALLINT: row[7] = SMALLINT_PRECISION; row[9] = SHORT_ZERO; - row[10] = RADIX_TEN_SHORT; break; case Types.DATE: row[7] = DATE_PRECISION; @@ -2106,6 +2102,9 @@ case Types.TIMESTAMP: row[7] = TIMESTAMP_PRECISION; break; + case Types.BOOLEAN: + row[7] = BOOLEAN_PRECISION; + row[10] = RADIX_BINARY_SHORT; default: row[7] = null; } @@ -2615,6 +2614,10 @@ case Types.TIMESTAMP: row[6] = TIMESTAMP_PRECISION; break; + case Types.BOOLEAN: + row[6] = BOOLEAN_PRECISION; + row[9] = RADIX_BINARY; + break; default: row[6] = null; } @@ -2694,6 +2697,7 @@ private static final int varchar_type = 37; // private static final int cstring_type = 40; private static final int blob_type = 261; + private static final short boolean_type = 23; private static int getDataType(int fieldType, int fieldSubType, int fieldScale) { switch (fieldType) { @@ -2744,6 +2748,8 @@ return Types.OTHER; case quad_type: return Types.OTHER; + case boolean_type: + return Types.BOOLEAN; default: return Types.NULL; } @@ -2799,6 +2805,8 @@ return "BLOB SUB_TYPE " + sqlsubtype; case quad_type: return "ARRAY"; + case boolean_type: + return "BOOLEAN"; default: return "NULL"; } @@ -4109,6 +4117,13 @@ TYPE_NULLABLE, CASESENSITIVE, TYPE_PRED_NONE, UNSIGNED, FIXEDSCALE, NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BLOB), null, RADIX_TEN }); + //BOOLEAN=16 + if (getDatabaseMajorVersion() >= 3) { + rows.add(new byte[][] {getBytes("BOOLEAN"), createShort(Types.BOOLEAN), BOOLEAN_PRECISION, + null, null, null, TYPE_NULLABLE, CASEINSENSITIVE, TYPE_PRED_BASIC, UNSIGNED, FIXEDSCALE, + NOTAUTOINC, null, SHORT_ZERO, SHORT_ZERO, createInt(ISCConstants.SQL_BOOLEAN), null, RADIX_BINARY}); + } + return new FBResultSet(xsqlvars, rows); } Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBParameterMetaData.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBParameterMetaData.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBParameterMetaData.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -155,6 +155,8 @@ return 7; case Types.DOUBLE: return 15; + case Types.BIGINT: + return 19; case Types.INTEGER: return 10; case Types.SMALLINT: @@ -165,6 +167,8 @@ return 8; case Types.TIMESTAMP: return 19; + case Types.BOOLEAN: + return 1; default: return 0; } @@ -252,6 +256,8 @@ return Types.OTHER; case ISCConstants.SQL_QUAD: return Types.OTHER; + case ISCConstants.SQL_BOOLEAN: + return Types.BOOLEAN; default: return Types.NULL; } @@ -326,6 +332,8 @@ return "BLOB SUB_TYPE " + sqlsubtype; case ISCConstants.SQL_QUAD: return "ARRAY"; + case ISCConstants.SQL_BOOLEAN: + return "BOOLEAN"; default: return "NULL"; } @@ -395,9 +403,12 @@ else { return BigDecimal.class.getName(); } + + case ISCConstants.SQL_BOOLEAN: + return Boolean.class.getName(); default: - throw new SQLException("Unkown SQL type", + throw new SQLException("Unknown SQL type", FBSQLException.SQL_STATE_INVALID_PARAM_TYPE); } } Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSetMetaData.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSetMetaData.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBResultSetMetaData.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -226,9 +226,10 @@ return 8; case Types.TIMESTAMP: return 19; + case Types.BOOLEAN: + return 1; default: - - return 0; + return 0; } } @@ -311,7 +312,7 @@ case Types.INTEGER: return 10; case Types.BIGINT: - return 20; + return 19; case Types.SMALLINT: return 5; case Types.DATE: @@ -320,6 +321,8 @@ return 8; case Types.TIMESTAMP: return 19; + case Types.BOOLEAN: + return 1; default: return 0; } @@ -437,6 +440,8 @@ return Types.OTHER; case ISCConstants.SQL_QUAD: return Types.OTHER; + case ISCConstants.SQL_BOOLEAN: + return Types.BOOLEAN; default: return Types.NULL; } @@ -509,6 +514,8 @@ return "BLOB SUB_TYPE " + sqlsubtype; case ISCConstants.SQL_QUAD: return "ARRAY"; + case ISCConstants.SQL_BOOLEAN: + return "BOOLEAN"; default: return "NULL"; } @@ -614,8 +621,12 @@ return Long.class.getName(); } return BigDecimal.class.getName(); + + case ISCConstants.SQL_BOOLEAN: + return Boolean.class.getName(); + default: - throw new FBSQLException("Unkown SQL type.", + throw new FBSQLException("Unknown SQL type.", FBSQLException.SQL_STATE_INVALID_PARAM_TYPE); } } Added: client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBooleanField.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBooleanField.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBooleanField.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -0,0 +1,157 @@ +/* + * $Id$ + * + * Firebird Open Source J2ee connector - jdbc driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.field; + +import org.firebirdsql.gds.XSQLVAR; + +import java.math.BigDecimal; +import java.sql.SQLException; + +/** + * {@link FBField} implementation for {@link java.sql.Types#BOOLEAN} (Firebird type {@link org.firebirdsql.gds.ISCConstants#SQL_BOOLEAN}). + * <p> + * This field type is only supported on Firebird 3.0 + * </p> + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.2.4 + */ +public class FBBooleanField extends FBField { + // TODO Evaluate current choices for truth values for non-boolean types (especially if number types != 0 are all true) + + FBBooleanField(XSQLVAR field, FieldDataProvider dataProvider, int requiredType) throws SQLException { + super(field, dataProvider, requiredType); + } + + public byte getByte() throws SQLException { + if (isNull()) { + return BYTE_NULL_VALUE; + } + return (byte) (getBoolean() ? 1 : 0); + } + + public short getShort() throws SQLException { + return getByte(); + } + + public int getInt() throws SQLException { + if (isNull()) { + return INT_NULL_VALUE; + } + return getBoolean() ? 1 : 0; + } + + public long getLong() throws SQLException { + return getInt(); + } + + public float getFloat() throws SQLException { + return getInt(); + } + + public double getDouble() throws SQLException { + return getInt(); + } + + public BigDecimal getBigDecimal() throws SQLException { + if (isNull()) { + return BIGDECIMAL_NULL_VALUE; + } + return getBoolean() ? BigDecimal.ONE : BigDecimal.ZERO; + } + + public String getString() throws SQLException { + if (isNull()) { + return STRING_NULL_VALUE; + } + return getBoolean() ? FBStringField.LONG_TRUE : FBStringField.LONG_FALSE; + } + + public boolean getBoolean() throws SQLException { + if (isNull()) { + return BOOLEAN_NULL_VALUE; + } + return field.decodeBoolean(getFieldData()); + } + + public void setByte(byte value) throws SQLException { + setBoolean(value != 0); + } + + public void setShort(short value) throws SQLException { + setBoolean(value != 0); + } + + public void setInteger(int value) throws SQLException { + setBoolean(value != 0); + } + + public void setLong(long value) throws SQLException { + setBoolean(value != 0); + } + + public void setFloat(float value) throws SQLException { + //TODO What if NaN? + setBoolean(value != 0); + } + + public void setDouble(double value) throws SQLException { + //TODO What if NaN? + setBoolean(value != 0); + } + + public void setBigDecimal(BigDecimal value) throws SQLException { + if (value == null) { + setNull(); + } else { + setBoolean(value.compareTo(BigDecimal.ZERO) != 0); + } + } + + /** + * {@inheritDoc} + * <p> + * Uses similar rules as {@link org.firebirdsql.jdbc.field.FBStringField#getBoolean()}. Sets this boolean to true for (case insensitive, ignoring whitespace): + * <ul> + * <li>true</li> + * <li>Y</li> + * <li>T</li> + * <li>1</li> + * </ul> + * Sets to false for all other values. + * </p> + */ + public void setString(String value) throws SQLException { + if (value == null) { + setNull(); + } else { + final String trimmedValue = value.trim(); + setBoolean(trimmedValue.equalsIgnoreCase(FBStringField.LONG_TRUE) + || trimmedValue.equalsIgnoreCase(FBStringField.SHORT_TRUE) + || trimmedValue.equalsIgnoreCase(FBStringField.SHORT_TRUE_2) + || trimmedValue.equalsIgnoreCase(FBStringField.SHORT_TRUE_3)); + } + } + + public void setBoolean(boolean value) throws SQLException { + setFieldData(field.encodeBoolean(value)); + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBBooleanField.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBField.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBField.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBField.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -245,6 +245,9 @@ case ISCConstants.SQL_NULL: return false; + case ISCConstants.SQL_BOOLEAN: + return type == Types.BOOLEAN; + default: return false; } @@ -284,6 +287,7 @@ case ISCConstants.SQL_INT64: case ISCConstants.SQL_LONG: case ISCConstants.SQL_SHORT: + case ISCConstants.SQL_BOOLEAN: return (type == Types.DOUBLE) || (type == Types.FLOAT) || (type == Types.REAL) || @@ -293,7 +297,8 @@ (type == Types.TINYINT) || (type == Types.NUMERIC) || (type == Types.DECIMAL) || - (type == Types.BIT) + (type == Types.BIT) || // TODO: We don't support BIT + (type == Types.BOOLEAN) ; case ISCConstants.SQL_TEXT: @@ -418,6 +423,8 @@ } } else if (FBField.isType(field, Types.ARRAY)) { throw new FBDriverNotCapableException(FBField.SQL_ARRAY_NOT_SUPPORTED); + } else if (FBField.isType(field, Types.BOOLEAN)) { + return new FBBooleanField(field, dataProvider, Types.BOOLEAN); } else if (FBField.isNullType(field)) { return new FBNullField(field, dataProvider, Types.NULL); } else { Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBStringField.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBStringField.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/field/FBStringField.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -60,12 +60,12 @@ * TODO check if the setBinaryStream(null) is allowed by specs. */ public class FBStringField extends FBField { - private static final String SHORT_TRUE = "Y"; - private static final String SHORT_FALSE = "N"; - private static final String LONG_TRUE = "true"; - private static final String LONG_FALSE = "false"; - private static final String SHORT_TRUE_2 = "T"; - private static final String SHORT_TRUE_3 = "1"; + static final String SHORT_TRUE = "Y"; + static final String SHORT_FALSE = "N"; + static final String LONG_TRUE = "true"; + static final String LONG_FALSE = "false"; + static final String SHORT_TRUE_2 = "T"; + static final String SHORT_TRUE_3 = "1"; protected int possibleCharLength; protected int bytesPerCharacter; @@ -166,10 +166,11 @@ public boolean getBoolean() throws SQLException { if (getFieldData()==null) return BOOLEAN_NULL_VALUE; - return getString().trim().equalsIgnoreCase(LONG_TRUE) || - getString().trim().equalsIgnoreCase(SHORT_TRUE) || - getString().trim().equalsIgnoreCase(SHORT_TRUE_2) || - getString().trim().equalsIgnoreCase(SHORT_TRUE_3); + final String trimmedValue = getString().trim(); + return trimmedValue.equalsIgnoreCase(LONG_TRUE) || + trimmedValue.equalsIgnoreCase(SHORT_TRUE) || + trimmedValue.equalsIgnoreCase(SHORT_TRUE_2) || + trimmedValue.equalsIgnoreCase(SHORT_TRUE_3); } public String getString() throws SQLException { if (getFieldData()==null) return STRING_NULL_VALUE; Added: client-java/trunk/src/test/org/firebirdsql/jdbc/TestBooleanSupport.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestBooleanSupport.java (rev 0) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestBooleanSupport.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -0,0 +1,392 @@ +/* + * $Id$ + * + * Firebird Open Source J2ee connector - jdbc driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc; + +import org.firebirdsql.common.*; +import org.junit.Before; +import org.junit.Test; + +import java.sql.*; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * Tests the boolean support, which is only available in Firebird 3.0 or higher. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + */ +public class TestBooleanSupport extends FBJUnit4TestBase { + + private static final String CREATE_TABLE = + "CREATE TABLE withboolean ( id INTEGER, bool BOOLEAN )"; + private static final String DROP_TABLE = + "DROP TABLE withboolean"; + private static final String INSERT = "INSERT INTO withboolean (id, bool) VALUES (?, ?)"; + private static final String SELECT = "SELECT id, bool FROM withboolean"; + private static final String SELECT_CONDITION_BOOL_FIELD = SELECT + " WHERE bool = ?"; + private static final String SELECT_CONDITION_SINGLETON = SELECT + " WHERE ?"; + private static final String[] TEST_DATA = { + "INSERT INTO withboolean (id, bool) VALUES (0, FALSE)", + "INSERT INTO withboolean (id, bool) VALUES (1, TRUE)", + "INSERT INTO withboolean (id, bool) VALUES (2, UNKNOWN)" + }; + + @Before + public void setUp() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + assumeTrue("Test only works on Firebird 3 or higher", con.getMetaData().getDatabaseMajorVersion() >= 3); + + DdlHelper.executeDropTable(con, DROP_TABLE); + DdlHelper.executeCreateTable(con, CREATE_TABLE); + con.setAutoCommit(false); + Statement stmt = con.createStatement(); + try { + for (String query : TEST_DATA) { + stmt.execute(query); + } + } finally { + JdbcResourceHelper.closeQuietly(stmt); + } + con.commit(); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test if a simple select returns the right boolean values in the ResultSet. + */ + @Test + public void testSimpleSelect_Values() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = null; + ResultSet rs = null; + try { + stmt = con.createStatement(); + rs = stmt.executeQuery(SELECT); + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt(1); + boolean bool = rs.getBoolean(2); + switch (id) { + case 0: + assertFalse("Column with value FALSE should have value false", bool); + assertFalse("Column with value FALSE should not be null", rs.wasNull()); + break; + case 1: + assertTrue("Column with value TRUE should have value true", bool); + assertFalse("Column with value TRUE should not be null", rs.wasNull()); + break; + case 2: + assertFalse("Column with value UNKNOWN should have value false", bool); + assertTrue("Column with value UNKNOWN should be null", rs.wasNull()); + break; + default: + fail("Unexpected row in result set"); + } + } + assertEquals("Expected 3 rows", 3, count); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(stmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Tests if the ResultSetMetaData contains the right information on boolean columns. + */ + @Test + public void testSimpleSelect_ResultSetMetaData() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = null; + ResultSet rs = null; + try { + stmt = con.createStatement(); + rs = stmt.executeQuery(SELECT); + final ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("Unexpected type for boolean column", Types.BOOLEAN, rsmd.getColumnType(2)); + assertEquals("Unexpected type name for boolean column", "BOOLEAN", rsmd.getColumnTypeName(2)); + assertEquals("Unexpected precision for boolean column", 1, rsmd.getPrecision(2)); + // Not testing other values + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(stmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Tests if boolean values inserted using a parametrized query are correctly roundtripped in a query. + */ + @Test + public void testParametrizedInsert() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + pstmt = con.prepareStatement(INSERT); + pstmt.setInt(1, 3); + pstmt.setBoolean(2, false); + pstmt.executeUpdate(); + + pstmt.setInt(1, 4); + pstmt.setBoolean(2, true); + pstmt.executeUpdate(); + + pstmt.setInt(1, 5); + pstmt.setNull(2, Types.BOOLEAN); + pstmt.executeUpdate(); + + // Testing for inserted values + stmt = con.createStatement(); + rs = stmt.executeQuery(SELECT); + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt(1); + boolean bool = rs.getBoolean(2); + switch (id) { + case 0: + case 1: + case 2: + continue; + case 3: + assertFalse("Column with value FALSE should have value false", bool); + assertFalse("Column with value FALSE should not be null", rs.wasNull()); + break; + case 4: + assertTrue("Column with value TRUE should have value true", bool); + assertFalse("Column with value TRUE should not be null", rs.wasNull()); + break; + case 5: + assertFalse("Column with value UNKNOWN should have value false", bool); + assertTrue("Column with value null should be null", rs.wasNull()); + break; + default: + fail("Unexpected row in result set"); + } + } + assertEquals("Expected 6 rows", 6, count); + + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(stmt); + JdbcResourceHelper.closeQuietly(pstmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Tests if the ParameterMetaData contains the right information on boolean columns. + */ + @Test + public void testParametrizedInsert_ParameterMetaData() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement pstmt = null; + try { + pstmt = con.prepareStatement(INSERT); + final ParameterMetaData parameterMetaData = pstmt.getParameterMetaData(); + assertEquals("Unexpected type for boolean column", Types.BOOLEAN, parameterMetaData.getParameterType(2)); + assertEquals("Unexpected type name for boolean column", "BOOLEAN", parameterMetaData.getParameterTypeName(2)); + assertEquals("Unexpected precision for boolean column", 1, parameterMetaData.getPrecision(2)); + // Not testing other values + } finally { + JdbcResourceHelper.closeQuietly(pstmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test select with condition on a boolean field. + */ + @Test + public void testSelectFieldCondition() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + pstmt = con.prepareStatement(SELECT_CONDITION_BOOL_FIELD); + pstmt.setBoolean(1, true); + rs = pstmt.executeQuery(); + + assertTrue("Expected a row", rs.next()); + assertEquals("Expected row with id=1", 1, rs.getInt(1)); + assertFalse("Did not expect a second row", rs.next()); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(pstmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test a select with a boolean parameter only (ie <code>WHERE ?"</code>) with value true + */ + @Test + public void testSelect_ConditionOnly_true() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + pstmt = con.prepareStatement(SELECT_CONDITION_SINGLETON); + pstmt.setBoolean(1, true); + rs = pstmt.executeQuery(); + + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt(1); + boolean bool = rs.getBoolean(2); + switch (id) { + case 0: + assertFalse("Column with value FALSE should have value false", bool); + assertFalse("Column with value FALSE should not be null", rs.wasNull()); + break; + case 1: + assertTrue("Column with value TRUE should have value true", bool); + assertFalse("Column with value TRUE should not be null", rs.wasNull()); + break; + case 2: + assertFalse("Column with value UNKNOWN should have value false", bool); + assertTrue("Column with value UNKNOWN should be null", rs.wasNull()); + break; + default: + fail("Unexpected row in result set"); + } + } + assertEquals("Expected 3 rows", 3, count); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(pstmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test a select with a boolean parameter only (ie <code>WHERE ?"</code>) with value false + */ + @Test + public void testSelect_ConditionOnly_false() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + pstmt = con.prepareStatement(SELECT_CONDITION_SINGLETON); + pstmt.setBoolean(1, false); + rs = pstmt.executeQuery(); + + assertFalse("Expected no rows", rs.next()); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(pstmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test a select with a boolean parameter only (ie <code>WHERE ?"</code>) with value null + */ + @Test + public void testSelect_ConditionOnly_null() throws Exception { + Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + pstmt = con.prepareStatement(SELECT_CONDITION_SINGLETON); + pstmt.setNull(1, Types.BOOLEAN); + rs = pstmt.executeQuery(); + + assertFalse("Expected no rows", rs.next()); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(pstmt); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Tests the value returned by {@link FBDatabaseMetaData#getTypeInfo()} (specifically only for BOOLEAN). + */ + @Test + public void testMetaData_TypeInfo() throws Exception { + // TODO Create separate test for all typeinfo information + Connection con = FBTestProperties.getConnectionViaDriverManager(); + ResultSet rs = null; + try { + DatabaseMetaData dbmd = con.getMetaData(); + rs = dbmd.getTypeInfo(); + + boolean foundBooleanType = false; + while (rs.next()) { + if (!"BOOLEAN".equals(rs.getString("TYPE_NAME"))) { + continue; + } + foundBooleanType = true; + assertEquals("Unexpected DATA_TYPE", Types.BOOLEAN, rs.getInt("DATA_TYPE")); + assertEquals("Unexpected PRECISION", 1, rs.getInt("PRECISION")); + assertEquals("Unexpected NULLABLE", DatabaseMetaData.typeNullable, rs.getInt("NULLABLE")); + assertFalse("Unexpected CASE_SENSITIVE", rs.getBoolean("CASE_SENSITIVE")); + assertEquals("Unexpected SEARCHABLE", DatabaseMetaData.typePredBasic, rs.getInt("SEARCHABLE")); + assertTrue("Unexpected UNSIGNED_ATTRIBUTE", rs.getBoolean("UNSIGNED_ATTRIBUTE")); + assertTrue("Unexpected FIXED_PREC_SCALE", rs.getBoolean("FIXED_PREC_SCALE")); + assertFalse("Unexpected AUTO_INCREMENT", rs.getBoolean("AUTO_INCREMENT")); + assertEquals("Unexpected NUM_PREC_RADIX", 2, rs.getInt("NUM_PREC_RADIX")); + // Not testing other values + } + assertTrue("Expected to find boolean type in typeInfo", foundBooleanType); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(con); + } + } + + /** + * Test {@link FBDatabaseMetaData#getColumns(String, String, String, String)} for a boolean column. + */ + @Test + public void testMetaData_getColumns() throws Exception { + // TODO Consider moving to TestFBDatabaseMetaDataColumns + Connection con = FBTestProperties.getConnectionViaDriverManager(); + ResultSet rs = null; + try { + DatabaseMetaData dbmd = con.getMetaData(); + rs = dbmd.getColumns(null, null, "WITHBOOLEAN", "BOOL"); + assertTrue("Expected a row", rs.next()); + assertEquals("Unexpected COLUMN_NAME", "BOOL", rs.getString("COLUMN_NAME")); + assertEquals("Unexpected DATA_TYPE", Types.BOOLEAN, rs.getInt("DATA_TYPE")); + assertEquals("Unexpected TYPE_NAME", "BOOLEAN", rs.getString("TYPE_NAME")); + assertEquals("Unexpected COLUMN_SIZE", 1, rs.getInt("COLUMN_SIZE")); + assertEquals("Unexpected NUM_PREC_RADIX", 2, rs.getInt("NUM_PREC_RADIX")); + assertEquals("Unexpected NULLABLE", DatabaseMetaData.columnNullable, rs.getInt("NULLABLE")); + assertEquals("Unexpected IS_AUTOINCREMENT", "NO", rs.getString("IS_AUTOINCREMENT")); + + assertFalse("Expected no second row", rs.next()); + } finally { + JdbcResourceHelper.closeQuietly(rs); + JdbcResourceHelper.closeQuietly(con); + } + } +} Property changes on: client-java/trunk/src/test/org/firebirdsql/jdbc/TestBooleanSupport.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataProcedureColumns.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataProcedureColumns.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/TestFBDatabaseMetaDataProcedureColumns.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -150,7 +150,6 @@ column.put(ProcedureColumnMetaData.PRECISION, 10); column.put(ProcedureColumnMetaData.LENGTH, 4); column.put(ProcedureColumnMetaData.SCALE, 0); - column.put(ProcedureColumnMetaData.RADIX, 10); column.put(ProcedureColumnMetaData.ORDINAL_POSITION, 2); column.put(ProcedureColumnMetaData.SPECIFIC_NAME, column.get(ProcedureColumnMetaData.PROCEDURE_NAME)); expectedColumns.add(column); @@ -187,7 +186,6 @@ column.put(ProcedureColumnMetaData.PRECISION, 10); column.put(ProcedureColumnMetaData.LENGTH, 4); column.put(ProcedureColumnMetaData.SCALE, 0); - column.put(ProcedureColumnMetaData.RADIX, 10); column.put(ProcedureColumnMetaData.ORDINAL_POSITION, 2); column.put(ProcedureColumnMetaData.SPECIFIC_NAME, column.get(ProcedureColumnMetaData.PROCEDURE_NAME)); expectedColumns.add(column); @@ -199,7 +197,6 @@ column.put(ProcedureColumnMetaData.TYPE_NAME, "DOUBLE PRECISION"); column.put(ProcedureColumnMetaData.PRECISION, 15); column.put(ProcedureColumnMetaData.LENGTH, 8); - column.put(ProcedureColumnMetaData.RADIX, 10); column.put(ProcedureColumnMetaData.ORDINAL_POSITION, 3); column.put(ProcedureColumnMetaData.SPECIFIC_NAME, column.get(ProcedureColumnMetaData.PROCEDURE_NAME)); expectedColumns.add(column); @@ -224,7 +221,6 @@ column.put(ProcedureColumnMetaData.PRECISION, 18); column.put(ProcedureColumnMetaData.LENGTH, 8); column.put(ProcedureColumnMetaData.SCALE, 2); - column.put(ProcedureColumnMetaData.RADIX, 10); column.put(ProcedureColumnMetaData.REMARKS, "Some comment"); column.put(ProcedureColumnMetaData.ORDINAL_POSITION, 2); column.put(ProcedureColumnMetaData.SPECIFIC_NAME, column.get(ProcedureColumnMetaData.PROCEDURE_NAME)); @@ -238,7 +234,6 @@ column.put(ProcedureColumnMetaData.PRECISION, 4); column.put(ProcedureColumnMetaData.LENGTH, 2); column.put(ProcedureColumnMetaData.SCALE, 3); - column.put(ProcedureColumnMetaData.RADIX, 10); column.put(ProcedureColumnMetaData.ORDINAL_POSITION, 3); column.put(ProcedureColumnMetaData.SPECIFIC_NAME, column.get(ProcedureColumnMetaData.PROCEDURE_NAME)); expectedColumns.add(column); @@ -305,7 +300,7 @@ defaults.put(ProcedureColumnMetaData.PROCEDURE_CAT, null); defaults.put(ProcedureColumnMetaData.PROCEDURE_SCHEM, null); defaults.put(ProcedureColumnMetaData.SCALE, null); - defaults.put(ProcedureColumnMetaData.RADIX, null); + defaults.put(ProcedureColumnMetaData.RADIX, 10); defaults.put(ProcedureColumnMetaData.NULLABLE, DatabaseMetaData.procedureNullable); defaults.put(ProcedureColumnMetaData.REMARKS, null); defaults.put(ProcedureColumnMetaData.COLUMN_DEF, null); Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java 2013-08-11 09:22:45 UTC (rev 58485) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/field/BaseJUnit4TestFBField.java 2013-08-11 12:07:02 UTC (rev 58486) @@ -55,10 +55,10 @@ * all methods of FBField, while extending tests will override (and add) tests for the * specific implementation. * </p> - * + * * @param <T> FBField implementation under test * @param <O> Object type of FBField implementation under test - * + * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> */ @RunWith(JMock.class) @@ -68,13 +68,13 @@ protected static final String RELATION_NAME_VALUE = "relationnamevalue"; protected Mockery context = new JUnit4Mockery(); - + // TODO Convert exception expectation to @Rule (needs to wait until JMock 2.6 is released) - + protected FieldDataProvider fieldData; protected XSQLVAR xsqlvar; protected T field; - + @Before public void setUp() throws Exception { context.setImposteriser(ClassImposteriser.INSTANCE); @@ -84,325 +84,325 @@ xsqlvar.sqlname = NAME_VALUE; xsqlvar.relname = RELATION_NAME_VALUE; } - + /** - * @throws SQLException + * @throws SQLException */ @Test public void getAlias() throws SQLException { assertEquals("Unexpected value for getAlias()", ALIAS_VALUE, field.getAlias()); } - + @Test(expected = FBDriverNotCapableException.class) public void getArrayNonNull() throws SQLException { field.getArray(); } - + @Test(expected = TypeConversionException.class) public void getAsciiStreamNonNull() throws SQLException { field.getAsciiStream(); } - + @Test(expected = TypeConversionException.class) public void setAsciiStreamNonNull() throws SQLException { field.setAsciiStream(context.mock(InputStream.class), 100); } - + @Test(expected = TypeConversionException.class) public void getBigDecimalNonNull() throws SQLException { field.getBigDecimal(); } - + @Test(expected = TypeConversionException.class) public void setBigDecimalNonNull() throws SQLException { field.setBigDecimal(BigDecimal.ONE); } - + @Test(expected = TypeConversionException.class) public void getBigDecimalIntNonNull() throws SQLException { field.getBigDecimal(1); } - + @Test(expected = TypeConversionException.class) public void getBinaryStreamNonNull() throws SQLException { field.getBinaryStream(); } - + @Test(expected = TypeConversionException.class) public void setBinaryStreamNonNull() throws SQLException { field.setBinaryStream(context.mock(InputStream.class), 100); } - + @Test(expected = TypeConversionException.class) public void getBlobNonNull() throws SQLException { field.getBlob(); } - + @Test(expected = TypeConversionException.class) public void setBlobNonNull() throws SQLException { field.setBlob(context.mock(FBBlob.class)); } - + @Test(expected = TypeConversionException.class) public void getBooleanNonNull() throws SQLException { field.getBoolean(); } - + @Test(expected = TypeConversionException.class) public void setBoolean() throws SQLException { field.setBoolean(true); } - + @Test(expected = TypeConversionException.class) public void getByteNonNull() throws SQLException { field.getByte(); } - + @Test(expected = TypeConversionException.class) public void setByte() throws SQLException { field.setByte((byte)1); } - + @Test(expected = TypeConversionException.class) public void getBytesNonNull() throws SQLException { field.getBytes(); } - + @Test(expected = TypeConversionException.class) public void setBytesNonNull() throws SQLException { field.setBytes(new byte[] { 1, 2 }); } - + @Test(expected = TypeConversionException.class) public void getCharacterStreamNonNull() throws SQLException { field.getCharacterStream(); } - + @Test(expected = TypeConversionException.class) public void setCharacterStreamNonNull() throws SQLException { field.setCharacterStream(context.mock(Reader.class), 100); } - + @Test(expected = TypeConversionException.class) public void getClobNonNull() throws SQLException { field.getClob(); } - + @Test(expected = TypeConversionException.class) public void setClobNonNull() throws SQLException { field.setClob(context.mock(FBClob.class)); } - + @Test(expected = TypeConversionException.class) public void getDateNonNull() throws SQLException { field.getDate(); } - + @Test(expected = TypeConversionException.class) public void setDateNonNull() throws SQLException { field.setDate(java.sql.Date.valueOf("2012-03-11")); } - + @Test(expected = TypeConversionException.class) public void getDateCalendarNonNull() throws SQLException { field.getDate(Calendar.getInstance()); } - + @Test(expected = TypeConversionException.class) public void setDateCalendarNonNull() throws SQLException { field.setDate(java.sql.Date.valueOf("2012-03-11"), Calendar.getInstance()); } - + @Test(expected = TypeConversionException.class) public void getDoubleNonNull() throws SQLException { field.getDouble(); } - + @Test(expected = TypeConversionException.class) public void setDouble() throws SQLException { field.setDouble(1.0); } - + @Test(expected = TypeConversionException.class) public void getFloatNonNull() throws SQLException { field.getFloat(); } - + @Test(expected = TypeConversionException.class) public void setFloat() throws SQLException { field.setFloat(1.0f); } - + @Test(expected = TypeConversionException.class) public void getIntNonNull() throws SQLException { field.getInt(); } - + @Test(expected = TypeConversionException.class) public void setInteger() throws SQLException { field.setInteger(1); } - + @Test(expected = TypeConversionException.class) public void getLongNonNull() throws SQLException { field.getLong(); } - + @Test(expected = TypeConversionException.class) public void setLong() throws SQLException { field.setLong(1); } - + /** - * @throws SQLException + * @throws SQLException */ @Test public void setNull() throws SQLException { setNullExpectations(); - + field.setNull(); } - + /** - * @throws SQLException + * @throws SQLException */ @Test public void getName() throws SQLException { assertEquals("Unexpected value for getName()", NAME_VALUE, field.getName()); } - + @Test(expected = TypeConversionException.class) public void getObjectNonNull() throws SQLException { field.getObject(); } - + @Test(expected = TypeConversionException.class) public void setObjectNonNull() throws SQLException { field.setObject(getNonNullObject()); } - + @Test(expected = TypeConversionException.class) public void setObjectUnsupportedType() throws SQLException { field.setObject(new Object()); } - + @Test public void setObjectNull() throws SQLException { setNullExpectations(); - + field.setObject(null); } - + @Test(expected = FBDriverNotCapableException.class) public void getObjectMapNonNull() throws SQLException { field.getObject(new HashMap<String,Class<?>>()); } - + @Test(expected = FBDriverNotCapableException.class) public void getRefNonNull() throws SQLException { field.getRef(); } - + /** - * @throws SQLException + * @throws SQLException */ @Test public void getRelationName() throws SQLException { assertEquals("Unexpected value for getRelationName()", RELATION_NAME_VALUE, field.getRelationName()); } - + @Test(expected = TypeConversionException.class) public void getShortNonNull() throws SQLException { field.getShort(); } - + @Test(expected = TypeConversionException.class) public void setShort() throws SQLException { field.setShort((short)1); } - + @Test(expected = TypeConversionException.class) public void getStringNonNull() throws SQLException { field.getString(); } - + @Test(expected = TypeConversionException.class) public void setStringNonNull() throws SQLException { field.setString(""); } - + @Test(expected = TypeConversionException.class) public void getTimeNonNull() throws SQLException { field.getTime(); } - + @Test(expected = TypeConversionException.class) public void setTimeNonNull() throws SQLException { field.setTime(java.sql.Time.valueOf("01:00:01")); } - + @Test(expected = TypeConversionException.class) public void getTimeCalendarNonNull() throws SQLException { field.getTime(Calendar.getInstance()); } - + @Test(expected = TypeConversionException.class) public void setTimeCalendarNonNull() throws SQLException { field.setTime(java.sql.Time.valueOf("01:00:01"), Calendar.getInstance()); } - + @Test(expected = TypeConversionException.class) public void getTimestampNonNull() throws SQLException { field.getTimestamp(); } - + @Test(expected = TypeConversionException.class) public void setTimestampNonNull() throws SQLException { field.setTimestamp(new java.sql.Timestamp(Calendar.getInstance().getTimeInMillis())); } - + @Test(expected = TypeConversionException.class) public void getTimestampCalendarNonNull() throws SQLException { field.getTimestamp(Calendar.getInstance()); } - + @Test(expected = TypeConversionException.class) public void setTimestampCalendarNonNull() throws SQLException { field.setTimestamp(new java.sql.Timestamp(Calendar.... [truncated message content] |
From: <mro...@us...> - 2013-08-17 09:59:23
|
Revision: 58501 http://sourceforge.net/p/firebird/code/58501 Author: mrotteveel Date: 2013-08-17 09:59:19 +0000 (Sat, 17 Aug 2013) Log Message: ----------- Cleanup + move/rename .jdbc.FBConnectionHelper to .gds.ParameterBufferHelper in preparation for wire protocol changes Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbExceptionBuilder.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionProperties.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriver.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriverPropertyManager.java client-java/trunk/src/main/org/firebirdsql/jdbc/FBTpbMapper.java client-java/trunk/src/test/org/firebirdsql/jdbc/field/TestFBBooleanField.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/gds/ParameterBufferHelper.java Removed Paths: ------------- client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionHelper.java Copied: client-java/trunk/src/main/org/firebirdsql/gds/ParameterBufferHelper.java (from rev 58461, client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionHelper.java) =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ParameterBufferHelper.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ParameterBufferHelper.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -0,0 +1,359 @@ +/* + * Firebird Open Source J2ee connector - jdbc driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ + +package org.firebirdsql.gds; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * This class maps the extended JDBC properties to parameter buffer types (for transaction and database + * parameter buffers). It uses <code>java.lang.reflection</code> to determine correct type of the parameter + * passed to the {@link java.sql.Driver#connect(String, Properties)} method. + * + * @author <a href="mailto:rro...@us...">Roman Rokytskyy</a> + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @version 1.0 + */ +public class ParameterBufferHelper { + + private static final DpbParameterType UNKNOWN_DPB_TYPE = new DpbParameterType(null, null, DpbValueType.TYPE_UNKNOWN); + + public static final String DPB_PREFIX = "isc_dpb_"; + public static final String TPB_PREFIX = "isc_tpb_"; + + public static final String ISC_DPB_TYPES_RESOURCE = "isc_dpb_types.properties"; + + private static final Map<String, Integer> dpbTypes; + private static final Map<String, DpbParameterType> dpbParameterTypes; + private static final Map<String, Integer> tpbTypes; + + /* + * Initialize mappings between various GDS constant names and + * their values. This operation should be executed only once. + */ + static { + final Map<String, Integer> tempDpbTypes = new HashMap<String, Integer>(); + final Map<String, Integer> tempTpbTypes = new HashMap<String, Integer>(); + Class<ISCConstants> iscClass = ISCConstants.class; + + Field[] fields = iscClass.getFields(); + + for (Field field : fields) { + if (!field.getType().getName().equals("int")) + continue; + + String name = field.getName(); + Integer value; + try { + value = (Integer) field.get(null); + } catch (IllegalAccessException iaex) { + continue; + } + + if (name.startsWith(DPB_PREFIX)) { + // put the correct parameter name + tempDpbTypes.put(name.substring(DPB_PREFIX.length()), value); + // put the full name to tolerate people's mistakes + tempDpbTypes.put(name, value); + } else if (name.startsWith(TPB_PREFIX)) { + // put the correct parameter name + tempTpbTypes.put(name.substring(TPB_PREFIX.length()), value); + // put the full name to tolerate people's mistakes + tempTpbTypes.put(name, value); + } + } + + dpbTypes = Collections.unmodifiableMap(tempDpbTypes); + tpbTypes = Collections.unmodifiableMap(tempTpbTypes); + dpbParameterTypes = Collections.unmodifiableMap(loadDpbParameterTypes()); + } + + /** + * Get integer value of the DPB key corresponding to the specified name. + * + * @param name + * name of the key. + * @return instance of {@link Integer} corresponding to the specified name + * or <code>null</code> if value is not known. + */ + public static Integer getDpbKey(String name) { + return dpbTypes.get(name); + } + + /** + * Gets the {@link DpbParameterType} for the specified dpb item name (short or long) + * + * @param name + * Name of the dpb item + * @return <code>DpbParameterType</code> instance, or <code>null</code> if there is no item with this name + */ + public static DpbParameterType getDpbParameterType(final String name) { + DpbParameterType dpbParameterType = dpbParameterTypes.get(name); + if (dpbParameterType == null) { + // No explicit type defined + Integer dpbKey = getDpbKey(name); + if (dpbKey != null) { + final String canonicalName = name.startsWith(DPB_PREFIX) ? name : DPB_PREFIX + name; + dpbParameterType = new DpbParameterType(canonicalName, dpbKey, DpbValueType.TYPE_UNKNOWN); + } + } + return dpbParameterType; + } + + /** + * Get mapping between DPB names and their keys. + * + * @return instance of {@link Map}, where key is the name of DPB parameter, + * value is its DPB key. + */ + public static Map<String, Integer> getDpbMap() { + return dpbTypes; + } + + public static Object parseDpbString(String name, Object value) { + DpbParameterType type = dpbParameterTypes.get(name); + + if (type == null) + type = UNKNOWN_DPB_TYPE; + + return type.parseDpbString(value); + } + + /** + * Get value of TPB parameter for the specified name. This method tries to + * match string representation of the TPB parameter with its value. + * + * @param name + * string representation of TPB parameter, can have "isc_tpb_" + * prefix. + * @return value corresponding to the specified parameter name or null if + * nothing was found. + */ + public static Integer getTpbParam(String name) { + return tpbTypes.get(name); + } + + /** + * Load properties from the specified resource. This method uses the same + * class loader that loaded this class. + * + * @param resource + * path to the resource relative to the root of the + * classloader. + * @return instance of {@link Properties} containing loaded resources or + * <code>null</code> if resource was not found. + * @throws IOException + * if I/O error occured. + */ + private static Properties loadProperties(String resource) throws IOException { + ClassLoader cl = ParameterBufferHelper.class.getClassLoader(); + InputStream in; + + // get the stream from the classloader or system classloader + if (cl == null) + in = ClassLoader.getSystemResourceAsStream(resource); + else + in = cl.getResourceAsStream(resource); + + if (in == null) + return null; + + try { + Properties props = new Properties(); + props.load(in); + return props; + } finally { + in.close(); + } + } + + /** + * Load mapping between DPB key and their parameter types. + */ + private static Map<String, DpbParameterType> loadDpbParameterTypes() { + Properties props; + try { + props = loadProperties(ISC_DPB_TYPES_RESOURCE); + } catch (IOException ex) { + // TODO Log, throw error? + ex.printStackTrace(); + return Collections.emptyMap(); + } + final Map<String, DpbParameterType> tempDpbParameterTypes = new HashMap<String, DpbParameterType>(); + + for (Map.Entry<Object, Object> entry : props.entrySet()) { + String key = (String) entry.getKey(); + String value = (String) entry.getValue(); + + // Remove text intended as comment (which due to the properties format is part of the value) + int hashIndex = value.indexOf('#'); + if (hashIndex != -1) { + value = value.substring(0, hashIndex).trim(); + } + + final DpbValueType typeValue; + if ("boolean".equals(value)) { + typeValue = DpbValueType.TYPE_BOOLEAN; + } else if ("byte".equals(value)) { + typeValue = DpbValueType.TYPE_BYTE; + } else if ("int".equals(value)) { + typeValue = DpbValueType.TYPE_INT; + } else if ("string".equals(value)) { + typeValue = DpbValueType.TYPE_STRING; + } else { + continue; + } + final Integer dpbKey = dpbTypes.get(key); + final DpbParameterType dpbParameterType = new DpbParameterType(key, dpbKey, typeValue); + tempDpbParameterTypes.put(key, dpbParameterType); + tempDpbParameterTypes.put(dpbParameterType.getShortName(), dpbParameterType); + } + return tempDpbParameterTypes; + } + + /** + * Enum with the various Dpb value types, and conversion from String to that type. + */ + public enum DpbValueType { + TYPE_UNKNOWN { + @Override + public Object parseDpbString(String value) { + /* set the value of the DPB by probing to convert string + * into int or byte value, this method gives very good result + * for guessing the method to call from the actual value; + * null values and empty strings are assumed to be boolean. + */ + if (value == null || "".equals(value)) + return Boolean.TRUE; + + try { + // try to deal with a value as a byte or int + int intValue = Integer.parseInt(value); + // TODO Find out if this is intentional + if (intValue < 256) + return (byte) intValue; + else + return intValue; + } catch (NumberFormatException nfex) { + // all else fails: return as is (string) + return value; + } + } + }, + TYPE_BOOLEAN { + @Override + public Object parseDpbString(String value) { + return "".equals(value) ? Boolean.TRUE : Boolean.valueOf(value); + } + }, + TYPE_BYTE { + @Override + public Object parseDpbString(String value) { + return Byte.valueOf(value); + } + }, + TYPE_INT { + @Override + public Object parseDpbString(String value) { + return Integer.valueOf(value); + } + }, + TYPE_STRING { + @Override + public Object parseDpbString(String value) { + return value; + } + }; + + /** + * Parses the supplied Object (which should be a String, Boolean, Byte or Integer) to + * the type appropriate for this DpbValueType. + * + * @param value + * The value to parse + * @return Parsed value (either a Boolean, Byte, Integer or String) + */ + public abstract Object parseDpbString(String value); + } + + /** + * Dpb type, which is the name, the key for the dpb and its value type. + * <p> + * Provides conversion to the required type. + * </p> + */ + public static class DpbParameterType { + private final String name; + private final String shortName; + private final Integer dpbKey; + private final DpbValueType type; + + public DpbParameterType(String name, Integer dpbKey, DpbValueType type) { + this.name = name; + shortName = name != null ? name.substring(DPB_PREFIX.length()) : null; + this.dpbKey = dpbKey; + this.type = type; + } + + public String getName() { + return name; + } + + public String getShortName() { + return shortName; + } + + public Integer getDpbKey() { + return dpbKey; + } + + public DpbValueType getType() { + return type; + } + + /** + * Parses the supplied Object (which should be a String, Boolean, Byte or Integer) to + * the type appropriate for this DpbParameterType. + * + * @param value + * The value to parse + * @return Parsed value (either a Boolean, Byte, Integer or String) + */ + public Object parseDpbString(Object value) { + // for the sake of unification we allow passing boolean, byte and integer + // types too, we loose some cycles here, but that is called relatively + // rarely, a trade off between code maintainability and CPU cycles. + if (!(value instanceof String)) { + if (value instanceof Boolean || value instanceof Byte || value instanceof Integer) + return value; + + // if passed value is not string, throw an exception + if (value != null) + throw new ClassCastException(value.getClass().getName()); + } + return type.parseDpbString((String) value); + } + } +} Modified: client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/AbstractJavaGDSImpl.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -913,15 +913,9 @@ for (int i = 0; i < xsqlda.sqld; i++) { XSQLVAR xsqlvar = xsqlda.sqlvar[i]; if (log != null && log.isDebugEnabled()) { - if (out == null) { - log.debug("db.out null in writeSQLDatum"); - } if (xsqlvar.sqldata == null) { - log.debug("sqldata null in writeSQLDatum: " + xsqlvar); + log.debug("sqldata null in writeSQLData: " + xsqlvar); } - if (xsqlvar.sqldata == null) { - log.debug("sqldata still null in writeSQLDatum: " + xsqlvar); - } } int len = xsqlda.ioLength[i]; byte[] buffer = xsqlvar.sqldata; @@ -952,7 +946,7 @@ } } else { // decrement length because it was incremented before - // TODO Where was it incremented? + // increment happens in calculateIOLength len--; if (buffer != null) { int buflen = buffer.length; @@ -1170,7 +1164,6 @@ op = nextOperation(db.in); if (op == op_response) { receiveResponse(db, op); - continue; } } while (false); } @@ -1429,7 +1422,7 @@ (blobParameterBuffer == null) ? op_open_blob : op_open_blob2); } - private final void openOrCreateBlob(IscDbHandle db_handle, + private void openOrCreateBlob(IscDbHandle db_handle, IscTrHandle tr_handle, IscBlobHandle blob_handle, // contains // blob_id BlobParameterBuffer blobParameterBuffer, int op) @@ -1523,7 +1516,7 @@ // has no data // return buffer; // } - int len = 0; + int len; int srcpos = 0; int destpos = 0; // TODO It looks like this might cause IndexOutOfBounds if srcpos = bufferLength - 1 and bufferLength = buffer.length @@ -1885,7 +1878,7 @@ protected int nextOperation(XdrInputStream in) throws IOException { boolean debug = log != null && log.isDebugEnabled(); - int op = 0; + int op; do { op = in.readInt(); if (debug && op == op_dummy) { @@ -1978,7 +1971,7 @@ } public static void calculateBLR(XSQLDA xsqlda) throws GDSException { - int blr_len = 0; + int blr_len; if (xsqlda != null) { // Determine the BLR length @@ -2112,13 +2105,13 @@ int dataLength = iscVaxInteger2(info, ++i); i += 2; int statementType = iscVaxInteger(info, i, dataLength); - ((isc_stmt_handle_impl)stmt_handle).setStatementType(statementType); + stmt_handle.setStatementType(statementType); i += dataLength; } XSQLDA xsqlda = new XSQLDA(); int lastindex = 0; - int index = 0; + int index; while ((index = parseTruncSqlInfo(i + 2, info, infoLength, xsqlda, lastindex)) > 0) { byte[] new_items = new byte[4 + items.length - 1]; new_items[0] = ISCConstants.isc_info_sql_sqlda_start; Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbExceptionBuilder.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbExceptionBuilder.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbExceptionBuilder.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -202,6 +202,7 @@ * @param type * Type of exception * @param errorCode + * The Firebird error code */ private void setNextExceptionInformation(Type type, final int errorCode) { current = new ExceptionInformation(type, errorCode); @@ -358,8 +359,7 @@ public SQLException createSQLException(final String message, final String sqlState, final int errorCode) { return new SQLNonTransientException(message, sqlState, errorCode); } - } - ; + }; private final String defaultSQLState; Deleted: client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionHelper.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionHelper.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionHelper.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -1,318 +0,0 @@ -/* - * Firebird Open Source J2ee connector - jdbc driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ - -package org.firebirdsql.jdbc; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.sql.Connection; -import java.util.*; - -import org.firebirdsql.gds.GDS; -import org.firebirdsql.gds.ISCConstants; -import org.firebirdsql.jca.FBConnectionRequestInfo; -import org.firebirdsql.jca.FBResourceException; - -/** - * This class maps the extended JDBC properties to the - * {@link FBConnectionRequestInfo} instance. It uses - * <code>java.lang.reflection.</code> to determine correct type of the parameter - * passed to the {@link java.sql.Driver#connect(String, Properties)} method. - * - * @author <a href="mailto:rro...@us...">Roman Rokytskyy</a> - * @version 1.0 - */ -public class FBConnectionHelper { - - private static final int TYPE_UNKNOWN = 0; - private static final int TYPE_BOOLEAN = 1; - private static final int TYPE_BYTE = 2; - private static final int TYPE_INT = 3; - private static final int TYPE_STRING = 4; - - public static final String TRANSACTION_SERIALIZABLE = "TRANSACTION_SERIALIZABLE"; - public static final String TRANSACTION_REPEATABLE_READ = "TRANSACTION_REPEATABLE_READ"; - public static final String TRANSACTION_READ_COMMITTED = "TRANSACTION_READ_COMMITTED"; - - - public static final String DPB_PREFIX = "isc_dpb_"; - public static final String TPB_PREFIX = "isc_tpb_"; - - public static final String TPB_MAPPING_PROPERTY = "tpb_mapping"; - - public static final String ISC_DPB_TYPES_RESOURCE = - "isc_dpb_types.properties"; - - private static final Map<String, Integer> dpbTypes; - private static final Map<String, Integer> dpbParameterTypes; - private static final Map<String, Integer> tpbTypes; - - /* - * Initialize mappings between various GDS constant names and - * their values. This operation should be executed only once. - */ - static { - final Map<String, Integer> tempDpbTypes = new HashMap<String, Integer>(); - final Map<String, Integer> tempTpbTypes = new HashMap<String, Integer>(); - Class<ISCConstants> iscClass = ISCConstants.class; - - Field[] fields = iscClass.getFields(); - - for(int i = 0; i < fields.length; i++) { - if (!fields[i].getType().getName().equals("int")) - continue; - - String name = fields[i].getName(); - Integer value; - try { - value = (Integer)fields[i].get(null); - } catch(IllegalAccessException iaex) { - continue; - } - - if (name.startsWith(DPB_PREFIX)) { - // put the correct parameter name - tempDpbTypes.put(name.substring(DPB_PREFIX.length()), value); - // put the full name to tolerate people's mistakes - tempDpbTypes.put(name, value); - } else if (name.startsWith(TPB_PREFIX)) { - // put the correct parameter name - tempTpbTypes.put(name.substring(TPB_PREFIX.length()), value); - // put the full name to tolerate people's mistakes - tempTpbTypes.put(name, value); - } - } - - dpbTypes = Collections.unmodifiableMap(tempDpbTypes); - tpbTypes = Collections.unmodifiableMap(tempTpbTypes); - dpbParameterTypes = Collections.unmodifiableMap(loadDpbParameterTypes()); - } - - /** - * Get integer value of the DPB key corresponding to the specified name. - * - * @param name name of the key. - * - * @return instance of {@link Integer} corresponding to the specified name - * or <code>null</code> if value is not known. - */ - public static Integer getDpbKey(String name) { - return dpbTypes.get(name); - } - - /** - * Get mapping between DPB names and their keys. - * - * @return instance of {@link Map}, where key is the name of DPB parameter, - * value is its DPB key. - */ - public static Map<String, Integer> getDpbMap() { - return dpbTypes; - } - - public static Object parseDpbString(String name, Object value) { - - // for the sake of unification we allow passing boolean, byte and integer - // types too, we loose some cycles here, but that is called relatively - // rarely, a tradeoff between code maintainability and CPU cycles. - if (value instanceof Boolean || value instanceof Byte || value instanceof Integer) - return value; - - // if passed value is not string, throw an exception - if (value != null && !(value instanceof String)) - throw new ClassCastException(value.getClass().getName()); - - Integer type = dpbParameterTypes.get(name); - - if (type == null) - type = Integer.valueOf(TYPE_UNKNOWN); - - switch(type.intValue()) { - case TYPE_BOOLEAN : - return "".equals(value) ? Boolean.TRUE : Boolean.valueOf((String)value); - - case TYPE_BYTE : - return Byte.valueOf((String)value); - - case TYPE_INT : - return Integer.valueOf((String)value); - - case TYPE_STRING : - return value; - - case TYPE_UNKNOWN : - default : - /* set the value of the DPB by probing to convert string - * into int or byte value, this method gives very good result - * for guessing the method to call from the actual value; - * null values and empty strings are assumed to be booleans. - */ - if (value == null || "".equals(value)) - return Boolean.TRUE; - - try { - // try to deal with a value as a byte or int - int intValue = Integer.parseInt((String)value); - // TODO Find out if this is intentional - if (intValue < 256) - return Byte.valueOf((byte) intValue); - else - return Integer.valueOf(intValue); - } catch (NumberFormatException nfex) { - // all else fails: return as is (string) - return value; - } - } - } - - /** - * This method extracts TPB mapping information from the connection - * parameters and adds it to the connectionProperties. Two formats are supported: - * <ul> - * <li><code>info</code> contains <code>"tpb_mapping"</code> parameter - * pointing to a resource bundle with mapping information; - * <li><code>info</code> contains separate mappings for each of following - * transaction isolation levels: <code>"TRANSACTION_SERIALIZABLE"</code>, - * <code>"TRANSACTION_REPEATABLE_READ"</code> and - * <code>"TRANSACTION_READ_COMMITTED"</code>. - * </ul> - * - * @param gds GDS object - * @param connectionProperties FirebirdConnectionProperties to set transaction state - * @param info connection parameters passed into a driver. - * - * @throws FBResourceException if specified mapping is incorrect. - */ - public static void processTpbMapping(GDS gds, - FirebirdConnectionProperties connectionProperties, Properties info) - throws FBResourceException { - - if (info.containsKey(TRANSACTION_SERIALIZABLE)) - connectionProperties.setTransactionParameters( - Connection.TRANSACTION_SERIALIZABLE, - FBTpbMapper.processMapping(gds, - info.getProperty(TRANSACTION_SERIALIZABLE))); - - if (info.containsKey(TRANSACTION_REPEATABLE_READ)) - connectionProperties.setTransactionParameters( - Connection.TRANSACTION_REPEATABLE_READ, - FBTpbMapper.processMapping(gds, - info.getProperty(TRANSACTION_REPEATABLE_READ))); - - if (info.containsKey(TRANSACTION_READ_COMMITTED)) - connectionProperties.setTransactionParameters( - Connection.TRANSACTION_READ_COMMITTED, - FBTpbMapper.processMapping(gds, - info.getProperty(TRANSACTION_READ_COMMITTED))); - - } - - /** - * Get value of TPB parameter for the specified name. This method tries to - * match string representation of the TPB parameter with its value. - * - * @param name string representation of TPB parameter, can have "isc_tpb_" - * prefix. - * - * @return value corresponding to the specified parameter name or null if - * nothing was found. - */ - public static Integer getTpbParam(String name) { - return tpbTypes.get(name); - } - - /** - * Load properties from the specified resource. This method uses the same - * class loader that loaded this class. - * - * @param resource path to the resource relative to the root of the - * classloader. - * - * @return instance of {@link Properties} containing loaded resources or - * <code>null</code> if resource was not found. - * - * @throws IOException if I/O error occured. - */ - private static Properties loadProperties(String resource) throws IOException { - ClassLoader cl = FBConnectionHelper.class.getClassLoader(); - - InputStream in = null; - - // get the stream from the classloader or system classloader - if (cl == null) - in = ClassLoader.getSystemResourceAsStream(resource); - else - in = cl.getResourceAsStream(resource); - - if (in == null) - return null; - - try { - Properties props = new Properties(); - props.load(in); - return props; - } finally { - in.close(); - } - } - - /** - * Load mapping between DPB key and their parameter types. - */ - private static Map<String, Integer> loadDpbParameterTypes() { - Properties props; - try { - props = loadProperties(ISC_DPB_TYPES_RESOURCE); - } catch(IOException ex) { - // TODO Log, throw error? - ex.printStackTrace(); - return Collections.emptyMap(); - } - final Map<String, Integer> tempDpbParameterTypes = new HashMap<String, Integer>(); - - for (Map.Entry<Object, Object> entry : props.entrySet()) { - String key = (String)entry.getKey(); - String shortKey = key.substring(DPB_PREFIX.length()); - String value = (String)entry.getValue(); - - // Remove text intended as comment (which due to the properties format is part of the value) - int hashIndex = value.indexOf('#'); - if (hashIndex != -1) { - value = value.substring(0, hashIndex).trim(); - } - - Integer typeValue; - if ("boolean".equals(value)) { - typeValue = Integer.valueOf(TYPE_BOOLEAN); - } else if ("byte".equals(value)) { - typeValue = Integer.valueOf(TYPE_BYTE); - } else if ("int".equals(value)) { - typeValue = Integer.valueOf(TYPE_INT); - } else if ("string".equals(value)) { - typeValue = Integer.valueOf(TYPE_STRING); - } else { - continue; - } - tempDpbParameterTypes.put(key, typeValue); - tempDpbParameterTypes.put(shortKey, typeValue); - } - return tempDpbParameterTypes; - } -} Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionProperties.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionProperties.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBConnectionProperties.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -28,10 +28,12 @@ import org.firebirdsql.encodings.EncodingFactory; import org.firebirdsql.gds.DatabaseParameterBuffer; import org.firebirdsql.gds.GDS; +import org.firebirdsql.gds.ParameterBufferHelper; import org.firebirdsql.gds.TransactionParameterBuffer; import org.firebirdsql.gds.impl.GDSFactory; import org.firebirdsql.gds.impl.GDSType; import org.firebirdsql.jca.FBResourceException; +import org.firebirdsql.util.ObjectUtils; public class FBConnectionProperties implements FirebirdConnectionProperties, Serializable, Cloneable { @@ -70,11 +72,7 @@ private int getIntProperty(String name) { Integer value = (Integer) properties.get(getCanonicalName(name)); - - if (value == null) - return 0; - - return value.intValue(); + return value != null ? value : 0; } private String getCanonicalName(String propertyName) { @@ -95,7 +93,7 @@ } private void setIntProperty(String name, int value) { - properties.put(getCanonicalName(name), Integer.valueOf(value)); + properties.put(getCanonicalName(name), value); } private void setStringProperty(String name, String value) { @@ -105,7 +103,7 @@ setType(value); name = getCanonicalName(name); - Object objValue = FBConnectionHelper.parseDpbString(name, value); + Object objValue = ParameterBufferHelper.parseDpbString(name, value); properties.put(name, objValue); } @@ -119,13 +117,7 @@ } public int hashCode() { - int result = 17; - - // Hash is built only from fields that are least likely to change (see JDBC-249) - result = result * 151 + (type != null ? type.hashCode() : 0); - result = result * 151 + (database != null ? database.hashCode() : 0); - - return result; + return ObjectUtils.hash(type, database); } public boolean equals(Object obj) { @@ -140,13 +132,13 @@ boolean result = true; result &= this.properties.equals(that.properties); - result &= this.type != null ? this.type.equals(that.type) : that.type == null; - result &= this.database != null ? this.database.equals(that.database) : that.database == null; - result &= this.tpbMapping != null ? this.tpbMapping.equals(that.tpbMapping) : that.tpbMapping == null; + result &= ObjectUtils.equals(this.type, that.type); + result &= ObjectUtils.equals(this.database, that.database); + result &= ObjectUtils.equals(this.tpbMapping, that.tpbMapping); result &= this.defaultTransactionIsolation == that.defaultTransactionIsolation; result &= this.customMapping.equals(that.customMapping); // If one or both are null we are identical (see also JDBC-249) - result &= (this.mapper == null || that.mapper == null) ? true : this.mapper.equals(that.mapper); + result &= (this.mapper == null || that.mapper == null) || this.mapper.equals(that.mapper); return result; } @@ -377,9 +369,7 @@ StringBuilder value = new StringBuilder(); boolean keyProcessed = false; - for (int i = 0; i < chars.length; i++) { - char ch = chars[i]; - + for (char ch : chars) { boolean isSeparator = Character.isWhitespace(ch) || ch == '=' || ch == ':'; // if no key was processed, ignore white spaces @@ -388,12 +378,10 @@ if (!keyProcessed && !isSeparator) { key.append(ch); - } else if (!keyProcessed && isSeparator) { + } else if (!keyProcessed) { keyProcessed = true; - } else if (keyProcessed && value.length() == 0 && isSeparator) { - continue; - } else if (keyProcessed) { - value.append(ch); + } else if (value.length() != 0 || !isSeparator) { + value.append(ch); } } @@ -411,22 +399,22 @@ String propertyName = entry.getKey(); Object value = entry.getValue(); - Integer dpbType = FBConnectionHelper.getDpbKey(propertyName); + Integer dpbType = ParameterBufferHelper.getDpbKey(propertyName); if (dpbType == null) continue; if (value instanceof Boolean) { - if (((Boolean) value).booleanValue()) - dpb.addArgument(dpbType.intValue()); + if ((Boolean) value) + dpb.addArgument(dpbType); } else if (value instanceof Byte) { - dpb.addArgument(dpbType.intValue(), new byte[] { ((Byte) value).byteValue() }); + dpb.addArgument(dpbType, new byte[] { (Byte) value }); } else if (value instanceof Integer) { - dpb.addArgument(dpbType.intValue(), ((Integer) value).intValue()); + dpb.addArgument(dpbType, (Integer) value); } else if (value instanceof String) { - dpb.addArgument(dpbType.intValue(), (String) value); + dpb.addArgument(dpbType, (String) value); } else if (value == null) - dpb.addArgument(dpbType.intValue()); + dpb.addArgument(dpbType); } return dpb; } @@ -467,11 +455,11 @@ if (mapper != null) return mapper.getMapping(isolation); else - return customMapping.get(Integer.valueOf(isolation)); + return customMapping.get(isolation); } public void setTransactionParameters(int isolation, TransactionParameterBuffer tpb) { - customMapping.put(Integer.valueOf(isolation), tpb); + customMapping.put(isolation, tpb); if (mapper != null) mapper.setMapping(isolation, tpb); } @@ -492,7 +480,7 @@ Integer isolation = entry.getKey(); TransactionParameterBuffer tpb = entry.getValue(); - mapper.setMapping(isolation.intValue(), tpb); + mapper.setMapping(isolation, tpb); } return mapper; Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriver.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriver.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriver.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -54,7 +54,7 @@ * standard connection. */ - private Map<FBManagedConnectionFactory, FBDataSource> mcfToDataSourceMap = + private final Map<FBManagedConnectionFactory, FBDataSource> mcfToDataSourceMap = Collections.synchronizedMap(new WeakHashMap<FBManagedConnectionFactory, FBDataSource>()); static { @@ -116,7 +116,7 @@ mcf.setNonStandardProperty(entry.getKey(), entry.getValue()); } - FBConnectionHelper.processTpbMapping(mcf.getGDS(), mcf, originalInfo); + FBTpbMapper.processMapping(mcf.getGDS(), mcf, originalInfo); mcf = mcf.canonicalize(); @@ -132,7 +132,7 @@ } private FBDataSource createDataSource(FBManagedConnectionFactory mcf) throws ResourceException { - FBDataSource dataSource = null; + FBDataSource dataSource; synchronized (mcfToDataSourceMap) { dataSource = mcfToDataSourceMap.get(mcf); @@ -148,7 +148,7 @@ GDSType type = GDSType.getType(properties.getType()); if (type == null) - type = ((AbstractGDS)GDSFactory.getDefaultGDS()).getType(); + type = GDSFactory.getDefaultGDS().getType(); try { FBManagedConnectionFactory mcf = new FBManagedConnectionFactory(type); Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriverPropertyManager.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriverPropertyManager.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBDriverPropertyManager.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -32,6 +32,8 @@ import java.util.StringTokenizer; import org.firebirdsql.encodings.EncodingFactory; +import org.firebirdsql.gds.ParameterBufferHelper; +import org.firebirdsql.util.ObjectUtils; /** * Manager of the DPB properties. @@ -53,12 +55,12 @@ * Container class for the driver properties. */ private static class PropertyInfo { - private String alias; - private String dpbName; - private Integer dpbKey; - private String description; + private final String alias; + private final String dpbName; + private final Integer dpbKey; + private final String description; - private int hashCode; + private final int hashCode; public PropertyInfo(String alias, String dpbName, Integer dpbKey, String description) { @@ -66,13 +68,8 @@ this.dpbName = dpbName; this.dpbKey = dpbKey; this.description = description; - - hashCode = 17; - if (alias != null) - hashCode ^= alias.hashCode(); - - hashCode ^= dpbName.hashCode(); - hashCode ^= dpbKey.intValue(); + + hashCode = ObjectUtils.hash(alias, dpbName, dpbKey); } public int hashCode() { @@ -85,9 +82,7 @@ PropertyInfo that = (PropertyInfo)obj; - boolean result = true; - - result &= this.alias.equals(that.alias); + boolean result = ObjectUtils.equals(this.alias, that.alias); result &= this.dpbName.equals(that.dpbName); result &= this.dpbKey.equals(that.dpbKey); @@ -118,10 +113,10 @@ dpbName = value.trim(); // skip incorrect mappings - if (!dpbName.startsWith(FBConnectionHelper.DPB_PREFIX)) + if (!dpbName.startsWith(ParameterBufferHelper.DPB_PREFIX)) continue; - Integer dpbKey = FBConnectionHelper.getDpbKey(dpbName); + Integer dpbKey = ParameterBufferHelper.getDpbKey(dpbName); // skip unknown elements if (dpbKey == null) @@ -136,11 +131,11 @@ } // fill rest of the properties - for (Map.Entry<String, Integer> entry : FBConnectionHelper.getDpbMap().entrySet()) { + for (Map.Entry<String, Integer> entry : ParameterBufferHelper.getDpbMap().entrySet()) { String dpbName = entry.getKey(); Integer dpbKey = entry.getValue(); - if (!dpbName.startsWith(FBConnectionHelper.DPB_PREFIX)) + if (!dpbName.startsWith(ParameterBufferHelper.DPB_PREFIX)) continue; if (tempDpbMap.containsKey(dpbName)) @@ -168,9 +163,7 @@ */ public static Map<String, String> normalize(String url, Properties props) throws SQLException { Map<String, String> tempProps = new HashMap<String, String>(); - // TODO: Replace with iterating over stringPropertyNames() when Java 5 support is dropped - for (Enumeration<?> propertyNames = props.propertyNames(); propertyNames.hasMoreElements();) { - String propertyName = (String)propertyNames.nextElement(); + for (String propertyName : props.stringPropertyNames()) { tempProps.put(propertyName, props.getProperty(propertyName)); } @@ -188,7 +181,7 @@ if (propInfo != null) { String originalName = propInfo.dpbName; String shortName = propInfo.dpbName.substring( - FBConnectionHelper.DPB_PREFIX.length()); + ParameterBufferHelper.DPB_PREFIX.length()); boolean hasDuplicate = tempProps.keySet().contains(originalName) @@ -208,8 +201,8 @@ // the full list if (propInfo == null) { String tempKey = propName; - if (!tempKey.startsWith(FBConnectionHelper.DPB_PREFIX)) - tempKey = FBConnectionHelper.DPB_PREFIX + tempKey; + if (!tempKey.startsWith(ParameterBufferHelper.DPB_PREFIX)) + tempKey = ParameterBufferHelper.DPB_PREFIX + tempKey; propInfo = dpbMap.get(tempKey); } @@ -231,8 +224,8 @@ if (propInfo == null) { String tempKey = propertyName; - if (!tempKey.startsWith(FBConnectionHelper.DPB_PREFIX)) - tempKey = FBConnectionHelper.DPB_PREFIX + tempKey; + if (!tempKey.startsWith(ParameterBufferHelper.DPB_PREFIX)) + tempKey = ParameterBufferHelper.DPB_PREFIX + tempKey; propInfo = dpbMap.get(tempKey); } @@ -321,9 +314,7 @@ */ public static DriverPropertyInfo[] getDriverPropertyInfo(Properties props) { List<DriverPropertyInfo> result = new ArrayList<DriverPropertyInfo>(); - // TODO: Replace with iterating over stringPropertyNames() when Java 5 support is dropped - for (Enumeration<?> propertyNames = props.propertyNames(); propertyNames.hasMoreElements();) { - String propName = (String)propertyNames.nextElement(); + for (String propName : props.stringPropertyNames()) { Object propValue = props.getProperty(propName); PropertyInfo propInfo = aliases.get(propName); @@ -332,8 +323,8 @@ // the full list if (propInfo == null) { String tempKey = propName; - if (!tempKey.startsWith(FBConnectionHelper.DPB_PREFIX)) - tempKey = FBConnectionHelper.DPB_PREFIX + tempKey; + if (!tempKey.startsWith(ParameterBufferHelper.DPB_PREFIX)) + tempKey = ParameterBufferHelper.DPB_PREFIX + tempKey; propInfo = dpbMap.get(tempKey); } Modified: client-java/trunk/src/main/org/firebirdsql/jdbc/FBTpbMapper.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/jdbc/FBTpbMapper.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/main/org/firebirdsql/jdbc/FBTpbMapper.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -22,18 +22,14 @@ import org.firebirdsql.gds.GDS; import org.firebirdsql.gds.ISCConstants; +import org.firebirdsql.gds.ParameterBufferHelper; import org.firebirdsql.gds.TransactionParameterBuffer; import org.firebirdsql.jca.FBResourceException; +import org.firebirdsql.util.ObjectUtils; import java.io.Serializable; import java.sql.Connection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import java.util.StringTokenizer; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -46,8 +42,6 @@ private static final long serialVersionUID = 1690658870275668176L; - public static final String DEFAULT_MAPPING_RESOURCE = "isc_tpb_mapping"; - public static FBTpbMapper getDefaultMapper(GDS gds) { return new FBTpbMapper(gds); } @@ -173,9 +167,9 @@ readCommittedTpb.addArgument(ISCConstants.isc_tpb_read_committed); readCommittedTpb.addArgument(ISCConstants.isc_tpb_rec_version); - mapping.put(Integer.valueOf(Connection.TRANSACTION_SERIALIZABLE), serializableTpb); - mapping.put(Integer.valueOf(Connection.TRANSACTION_REPEATABLE_READ), repeatableReadTpb); - mapping.put(Integer.valueOf(Connection.TRANSACTION_READ_COMMITTED), readCommittedTpb); + mapping.put(Connection.TRANSACTION_SERIALIZABLE, serializableTpb); + mapping.put(Connection.TRANSACTION_REPEATABLE_READ, repeatableReadTpb); + mapping.put(Connection.TRANSACTION_READ_COMMITTED, readCommittedTpb); } /** @@ -230,7 +224,7 @@ String jdbcTxIsolation = entry.getKey(); Integer isolationLevel; try { - isolationLevel = Integer.valueOf(getTransactionIsolationLevel(jdbcTxIsolation)); + isolationLevel = getTransactionIsolationLevel(jdbcTxIsolation); } catch (IllegalArgumentException ex) { throw new FBResourceException( "Transaction isolation " + jdbcTxIsolation + @@ -275,6 +269,43 @@ } /** + * This method extracts TPB mapping information from the connection + * parameters and adds it to the connectionProperties. Two formats are supported: + * <ul> + * <li><code>info</code> contains <code>"tpb_mapping"</code> parameter + * pointing to a resource bundle with mapping information; + * <li><code>info</code> contains separate mappings for each of following + * transaction isolation levels: <code>"TRANSACTION_SERIALIZABLE"</code>, + * <code>"TRANSACTION_REPEATABLE_READ"</code> and + * <code>"TRANSACTION_READ_COMMITTED"</code>. + * </ul> + * + * @param gds GDS object + * @param connectionProperties FirebirdConnectionProperties to set transaction state + * @param info connection parameters passed into a driver. + * + * @throws FBResourceException if specified mapping is incorrect. + */ + public static void processMapping(GDS gds, FirebirdConnectionProperties connectionProperties, Properties info) + throws FBResourceException { + + if (info.containsKey(TRANSACTION_SERIALIZABLE)) + connectionProperties.setTransactionParameters( + Connection.TRANSACTION_SERIALIZABLE, + processMapping(gds, info.getProperty(TRANSACTION_SERIALIZABLE))); + + if (info.containsKey(TRANSACTION_REPEATABLE_READ)) + connectionProperties.setTransactionParameters( + Connection.TRANSACTION_REPEATABLE_READ, + processMapping(gds, info.getProperty(TRANSACTION_REPEATABLE_READ))); + + if (info.containsKey(TRANSACTION_READ_COMMITTED)) + connectionProperties.setTransactionParameters( + Connection.TRANSACTION_READ_COMMITTED, + processMapping(gds, info.getProperty(TRANSACTION_READ_COMMITTED))); + } + + /** * Process comma-separated list of keywords and convert them into TPB * values. * @@ -291,12 +322,12 @@ StringTokenizer st = new StringTokenizer(mapping, ","); while (st.hasMoreTokens()) { String token = st.nextToken(); - Integer value = FBConnectionHelper.getTpbParam(token); + Integer value = ParameterBufferHelper.getTpbParam(token); if (value == null) throw new FBResourceException( "Keyword " + token + " unknown. Please check your mapping."); - result.addArgument(value.intValue()); + result.addArgument(value); } return result; @@ -319,11 +350,11 @@ case Connection.TRANSACTION_SERIALIZABLE: case Connection.TRANSACTION_REPEATABLE_READ: case Connection.TRANSACTION_READ_COMMITTED: - return mapping.get(Integer.valueOf(transactionIsolation)).deepCopy(); + return mapping.get(transactionIsolation).deepCopy(); // promote transaction case Connection.TRANSACTION_READ_UNCOMMITTED: - return mapping.get(Integer.valueOf(Connection.TRANSACTION_READ_COMMITTED)).deepCopy(); + return mapping.get(Connection.TRANSACTION_READ_COMMITTED).deepCopy(); case Connection.TRANSACTION_NONE: default: @@ -347,7 +378,7 @@ case Connection.TRANSACTION_SERIALIZABLE: case Connection.TRANSACTION_REPEATABLE_READ: case Connection.TRANSACTION_READ_COMMITTED: - mapping.put(Integer.valueOf(transactionIsolation), tpb); + mapping.put(transactionIsolation, tpb); break; case Connection.TRANSACTION_READ_UNCOMMITTED: @@ -366,7 +397,7 @@ * @return mapping for the default transaction isolation level. */ public TransactionParameterBuffer getDefaultMapping() { - return mapping.get(Integer.valueOf(defaultIsolationLevel)); + return mapping.get(defaultIsolationLevel); } public int getDefaultTransactionIsolation() { @@ -387,19 +418,15 @@ } FBTpbMapper that = (FBTpbMapper) obj; - boolean result = true; - result &= this.mapping.equals(that.mapping); + boolean result = this.mapping.equals(that.mapping); result &= (this.defaultIsolationLevel == that.defaultIsolationLevel); return result; } public int hashCode() { - int result = 31; // TODO both these values are mutable, so potentially unstable hashcode - result = result * 83 + mapping.hashCode(); - result = result * 83 + defaultIsolationLevel; - return result; + return ObjectUtils.hash(mapping, defaultIsolationLevel); } public Object clone() { Modified: client-java/trunk/src/test/org/firebirdsql/jdbc/field/TestFBBooleanField.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/jdbc/field/TestFBBooleanField.java 2013-08-17 00:27:29 UTC (rev 58500) +++ client-java/trunk/src/test/org/firebirdsql/jdbc/field/TestFBBooleanField.java 2013-08-17 09:59:19 UTC (rev 58501) @@ -21,7 +21,6 @@ package org.firebirdsql.jdbc.field; import org.firebirdsql.gds.ISCConstants; -import org.firebirdsql.gds.XSQLVAR; import org.junit.Before; import org.junit.Test; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-08-25 11:30:20
|
Revision: 58544 http://sourceforge.net/p/firebird/code/58544 Author: mrotteveel Date: 2013-08-25 11:30:13 +0000 (Sun, 25 Aug 2013) Log Message: ----------- Populate dpb for new wire protocol (without support for non-standard properties) Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java client-java/trunk/src/resources/isc_dpb_types.properties client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Property Changed: ---------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -42,11 +42,9 @@ /** * Attach to a database. * - * @param dpb - * The DatabaseParameterBuffer with all required values * @throws SQLException */ - void attach(DatabaseParameterBuffer dpb) throws SQLException; + void attach() throws SQLException; /** * Detaches from the current database. Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -26,6 +26,7 @@ */ package org.firebirdsql.gds.ng.wire; +import org.firebirdsql.gds.DatabaseParameterBuffer; import org.firebirdsql.gds.ng.FbTransaction; /** @@ -77,5 +78,20 @@ */ FbWireTransaction createTransaction(FbWireDatabase database); + /** + * Create {@link org.firebirdsql.gds.ng.FbStatement} implementation for this protocol. + * + * @param database FbWireDatabase of the current database + * @return FbStatement implementation + */ FbWireStatement createStatement(FbWireDatabase database); + + /** + * Create {@link DatabaseParameterBuffer} implementation and populate it with supported + * properties for this protocol version. + * + * @param connection Connection + * @return DatabaseParameterBuffer implementation + */ + DatabaseParameterBuffer createDatabaseParameterBuffer(WireConnection connection); } Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -86,19 +86,6 @@ /** * Creates a WireConnection (without establishing a connection to the - * server) with the default protocol collection. - * - * @param connectionProperties - * Connection properties - * @param encodingFactory - * Factory for encoding definitions - */ - public WireConnection(IConnectionProperties connectionProperties, IEncodingFactory encodingFactory) throws SQLException { - this(connectionProperties, encodingFactory, ProtocolCollection.getDefaultCollection()); - } - - /** - * Creates a WireConnection (without establishing a connection to the * server). * * @param connectionProperties Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/WireConnection.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -27,7 +27,6 @@ import org.firebirdsql.gds.ISCConstants; import org.firebirdsql.gds.TransactionParameterBuffer; import org.firebirdsql.gds.impl.DatabaseParameterBufferExtension; -import org.firebirdsql.gds.impl.wire.DatabaseParameterBufferImp; import org.firebirdsql.gds.impl.wire.XdrInputStream; import org.firebirdsql.gds.impl.wire.XdrOutputStream; import org.firebirdsql.gds.impl.wire.Xdrable; @@ -58,8 +57,6 @@ private static final Logger log = LoggerFactory.getLogger(V10Database.class, false); - // TODO Eliminate DatabaseParameterBuffer parameter from various methods - private final Object syncObject = new Object(); private final XdrStreamHolder xdrStreamHolder; private final AtomicBoolean attached = new AtomicBoolean(); @@ -223,16 +220,11 @@ } @Override - public void attach(DatabaseParameterBuffer dpb) throws SQLException { + public void attach() throws SQLException { + final DatabaseParameterBuffer dpb = protocolDescriptor.createDatabaseParameterBuffer(connection); attachOrCreate(dpb, false); } - protected DatabaseParameterBuffer generateDatabaseParameterBufferFromConnectionProperties() { - final IConnectionProperties connectionProperties = connection.getConnectionProperties(); - final DatabaseParameterBufferImp dpb = new DatabaseParameterBufferImp(); - throw new UnsupportedOperationException(); - } - /** * @param dpb * Database parameter buffer @@ -300,7 +292,7 @@ xdrOut.writeString(connection.getDatabaseName(), filenameEncoding); dpb = ((DatabaseParameterBufferExtension) dpb).removeExtensionParams(); - // TODO Include ProcessID and ProcessName as in JavaGDSImpl implementation (or move that to different part?) + // TODO Include ProcessID and ProcessName as in JavaGDSImpl implementation (or move that to different part?) See also Version10ProtocolDescriptor xdrOut.writeTyped(ISCConstants.isc_dpb_version1, (Xdrable) dpb); } Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -20,14 +20,16 @@ */ package org.firebirdsql.gds.ng.wire.version10; +import org.firebirdsql.gds.DatabaseParameterBuffer; +import org.firebirdsql.gds.ISCConstants; +import org.firebirdsql.gds.impl.wire.DatabaseParameterBufferImp; import org.firebirdsql.gds.impl.wire.WireProtocolConstants; -import org.firebirdsql.gds.ng.FbTransaction; +import org.firebirdsql.gds.ng.IConnectionProperties; import org.firebirdsql.gds.ng.wire.*; /** * The {@link ProtocolDescriptor} for the Firebird version 10 protocol. This version applies to Firebird 1.x and 2.0, - * but - * also works with newer Firebird versions. + * but also works with newer Firebird versions. * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @since 2.3 @@ -44,12 +46,12 @@ } @Override - public FbWireDatabase createDatabase(WireConnection connection) { + public FbWireDatabase createDatabase(final WireConnection connection) { return new V10Database(connection, this); } @Override - public FbWireTransaction createTransaction(FbWireDatabase database) { + public FbWireTransaction createTransaction(final FbWireDatabase database) { return new V10Transaction(database); } @@ -57,4 +59,35 @@ public FbWireStatement createStatement(final FbWireDatabase database) { return new V10Statement(database); } + + @Override + public DatabaseParameterBuffer createDatabaseParameterBuffer(final WireConnection connection) { + final IConnectionProperties connectionProperties = connection.getConnectionProperties(); + final DatabaseParameterBuffer dpb = new DatabaseParameterBufferImp(); + + // Map standard properties + dpb.addArgument(ISCConstants.isc_dpb_lc_ctype, connection.getEncodingDefinition().getFirebirdEncodingName()); + if (connectionProperties.getPageCacheSize() != IConnectionProperties.DEFAULT_BUFFERS_NUMBER) { + dpb.addArgument(ISCConstants.isc_dpb_num_buffers, connectionProperties.getPageCacheSize()); + } + if (connectionProperties.getUser() != null) { + dpb.addArgument(ISCConstants.isc_dpb_user_name, connectionProperties.getUser()); + } + if (connectionProperties.getPassword() != null) { + dpb.addArgument(ISCConstants.isc_dpb_password, connectionProperties.getPassword()); + } + if (connectionProperties.getRoleName() != null) { + dpb.addArgument(ISCConstants.isc_dpb_sql_role_name, connectionProperties.getRoleName()); + } + dpb.addArgument(ISCConstants.isc_dpb_sql_dialect, connectionProperties.getConnectionDialect()); + if (connectionProperties.getConnectTimeout() != IConnectionProperties.DEFAULT_CONNECT_TIMEOUT) { + dpb.addArgument(ISCConstants.isc_dpb_connect_timeout, connectionProperties.getConnectTimeout()); + } + // TODO Include ProcessID and ProcessName here or elsewhere? + + // Map non-standard properties + // TODO Implement support for non-standard properties + + return dpb; + } } Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/resources/isc_dpb_types.properties =================================================================== --- client-java/trunk/src/resources/isc_dpb_types.properties 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/resources/isc_dpb_types.properties 2013-08-25 11:30:13 UTC (rev 58544) @@ -10,7 +10,7 @@ isc_dpb_lc_messages string # not applicable to Jaybird isc_dpb_license string # not applicable to Firebird isc_dpb_no_reserve byte # 0 - reserve space, 1 - no reserve -isc_dpb_num_buffers byte +isc_dpb_num_buffers int isc_dpb_password string isc_dpb_password_enc string isc_dpb_sys_user_name string Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -20,6 +20,7 @@ */ package org.firebirdsql.gds.ng; +import org.firebirdsql.gds.DatabaseParameterBuffer; import org.firebirdsql.gds.ng.wire.*; /** @@ -30,6 +31,7 @@ * </p> * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 */ public class EmptyProtocolDescriptor extends AbstractProtocolDescriptor { @@ -51,4 +53,9 @@ public FbWireStatement createStatement(final FbWireDatabase database) { return null; } + + @Override + public DatabaseParameterBuffer createDatabaseParameterBuffer(final WireConnection connection) { + return null; + } } Property changes on: client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -70,6 +70,8 @@ connectionInfo = new FbConnectionProperties(); connectionInfo.setServerName(FBTestProperties.DB_SERVER_URL); connectionInfo.setPortNumber(FBTestProperties.DB_SERVER_PORT); + connectionInfo.setUser(DB_USER); + connectionInfo.setPassword(DB_PASSWORD); connectionInfo.setDatabaseName(FBTestProperties.getDatabasePath()); connectionInfo.setEncoding("NONE"); } @@ -180,7 +182,7 @@ SimpleWarningMessageCallback callback = new SimpleWarningMessageCallback(); db.setWarningMessageCallback(callback); - SQLException warning = new FbExceptionBuilder().warning(ISCConstants.isc_numeric_out_of_range).toSQLException(); + SQLWarning warning = new FbExceptionBuilder().warning(ISCConstants.isc_numeric_out_of_range).toSQLException(SQLWarning.class); GenericResponse genericResponse = new GenericResponse(-1, -1, null, warning); db.processResponseWarnings(genericResponse); @@ -219,12 +221,7 @@ db = gdsConnection.identify(); assertEquals("Unexpected FbWireDatabase implementation", V10Database.class, db.getClass()); - DatabaseParameterBufferImp dpb = new DatabaseParameterBufferImp(); - dpb.addArgument(ISCConstants.isc_dpb_sql_dialect, 3); - dpb.addArgument(ISCConstants.isc_dpb_user_name, DB_USER); - dpb.addArgument(ISCConstants.isc_dpb_password, DB_PASSWORD); - - db.attach(dpb); + db.attach(); System.out.println(db.getHandle()); assertTrue("Expected isAttached() to return true", db.isAttached()); @@ -249,18 +246,13 @@ @Test public void testAttach_NonExistentDatabase() throws Exception { WireConnection gdsConnection = new WireConnection(connectionInfo, EncodingFactory.getDefaultInstance(), ProtocolCollection.create(new Version10Descriptor())); - FbWireDatabase db = null; + FbWireDatabase db; try { gdsConnection.socketConnect(); db = gdsConnection.identify(); assertEquals("Unexpected FbWireDatabase implementation", V10Database.class, db.getClass()); - DatabaseParameterBufferImp dpb = new DatabaseParameterBufferImp(); - dpb.addArgument(ISCConstants.isc_dpb_sql_dialect, 3); - dpb.addArgument(ISCConstants.isc_dpb_user_name, DB_USER); - dpb.addArgument(ISCConstants.isc_dpb_password, DB_PASSWORD); - - db.attach(dpb); + db.attach(); fail("Expected the attach to fail because the database doesn't exist"); } catch (SQLException e) { // TODO Is this actually the right SQLState? @@ -277,7 +269,7 @@ @Test public void testBasicCreateAndDrop() throws Exception { WireConnection gdsConnection = new WireConnection(connectionInfo, EncodingFactory.getDefaultInstance(), ProtocolCollection.create(new Version10Descriptor())); - FbWireDatabase db = null; + FbWireDatabase db; File dbFile = new File(gdsConnection.getDatabaseName()); try { gdsConnection.socketConnect(); @@ -301,7 +293,7 @@ if (gdsConnection.isConnected()) { gdsConnection.disconnect(); } - if (dbFile != null && dbFile.exists()) { + if (dbFile.exists()) { dbFile.delete(); } } Property changes on: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-08-25 01:31:10 UTC (rev 58543) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-08-25 11:30:13 UTC (rev 58544) @@ -26,7 +26,6 @@ import org.firebirdsql.gds.TransactionParameterBuffer; import org.firebirdsql.gds.impl.jni.EmbeddedGDSImpl; import org.firebirdsql.gds.impl.jni.NativeGDSImpl; -import org.firebirdsql.gds.impl.wire.DatabaseParameterBufferImp; import org.firebirdsql.gds.impl.wire.TransactionParameterBufferImpl; import org.firebirdsql.gds.ng.FbConnectionProperties; import org.firebirdsql.gds.ng.FbStatement; @@ -64,6 +63,8 @@ connectionInfo = new FbConnectionProperties(); connectionInfo.setServerName(FBTestProperties.DB_SERVER_URL); connectionInfo.setPortNumber(FBTestProperties.DB_SERVER_PORT); + connectionInfo.setUser(DB_USER); + connectionInfo.setPassword(DB_PASSWORD); connectionInfo.setDatabaseName(FBTestProperties.getDatabasePath()); connectionInfo.setEncoding("NONE"); } @@ -87,12 +88,7 @@ db = gdsConnection.identify(); assertEquals("Unexpected FbWireDatabase implementation", V10Database.class, db.getClass()); - DatabaseParameterBufferImp dpb = new DatabaseParameterBufferImp(); - dpb.addArgument(ISCConstants.isc_dpb_sql_dialect, 3); - dpb.addArgument(ISCConstants.isc_dpb_user_name, DB_USER); - dpb.addArgument(ISCConstants.isc_dpb_password, DB_PASSWORD); - - db.attach(dpb); + db.attach(); } @Test Property changes on: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-09-12 09:18:04
|
Revision: 58607 http://sourceforge.net/p/firebird/code/58607 Author: mrotteveel Date: 2013-09-12 09:17:59 +0000 (Thu, 12 Sep 2013) Log Message: ----------- Execute statement with parameters, process returned rows Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/WireProtocolConstants.java client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldDescriptor.java client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptor.java client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptorBuilder.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/ProtocolDescriptor.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10StatementInfoProcessor.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/Version10Descriptor.java client-java/trunk/src/test/org/firebirdsql/gds/ng/EmptyProtocolDescriptor.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/gds/BlrConstants.java client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/BlrCalculator.java client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldValue.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/AbstractListenerDispatcher.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10BlrCalculator.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleRowListener.java Added: client-java/trunk/src/main/org/firebirdsql/gds/BlrConstants.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/BlrConstants.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/BlrConstants.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,378 @@ +/* + * $Id$ + * + * Public Firebird Java API. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.firebirdsql.gds; + +/** + * Constants for the blr. + * <p> + * TODO: Cleanup (eg remove blr constants not relevant for the wire protocol). + * </p> + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +@SuppressWarnings("unused") +public interface BlrConstants { + int blr_text = 14; + int blr_text2 = 15; + int blr_short = 7; + int blr_long = 8; + int blr_quad = 9; + int blr_float = 10; + int blr_double = 27; + int blr_d_float = 11; + int blr_timestamp = 35; + int blr_date = blr_timestamp; // Historical alias for pre V6 applications + int blr_varying = 37; + int blr_varying2 = 38; + int blr_blob = 261; + int blr_cstring = 40; + int blr_cstring2 = 41; + int blr_blob_id = 45; + int blr_sql_date = 12; + int blr_sql_time = 13; + int blr_int64 = 16; + int blr_blob2 = 17; + int blr_domain_name = 18; + int blr_domain_name2 = 19; + int blr_not_nullable = 20; + int blr_column_name = 21; + int blr_column_name2 = 22; + int blr_bool = 23; + + // first sub parameter for blr_domain_name[2] + int blr_domain_type_of = 0; + int blr_domain_full = 1; + + int blr_inner = 0; + int blr_left = 1; + int blr_right = 2; + int blr_full = 3; + int blr_gds_code = 0; + int blr_sql_code = 1; + int blr_exception = 2; + int blr_trigger_code = 3; + int blr_default_code = 4; + int blr_raise = 5; + int blr_exception_msg = 6; + int blr_exception_params = 7; + int blr_version4 = 4; + int blr_version5 = 5; + //int blr_version6 = 6; + int blr_eoc = 76; + int blr_end = 255; + int blr_assignment = 1; + int blr_begin = 2; + int blr_dcl_variable = 3; + int blr_message = 4; + int blr_erase = 5; + int blr_fetch = 6; + int blr_for = 7; + int blr_if = 8; + int blr_loop = 9; + int blr_modify = 10; + int blr_handler = 11; + int blr_receive = 12; + int blr_select = 13; + int blr_send = 14; + int blr_store = 15; + int blr_label = 17; + int blr_leave = 18; + int blr_store2 = 19; + int blr_post = 20; + int blr_literal = 21; + int blr_dbkey = 22; + int blr_field = 23; + int blr_fid = 24; + int blr_parameter = 25; + int blr_variable = 26; + int blr_average = 27; + int blr_count = 28; + int blr_maximum = 29; + int blr_minimum = 30; + int blr_total = 31; + + // unused codes: 32..33 + + int blr_add = 34; + int blr_subtract = 35; + int blr_multiply = 36; + int blr_divide = 37; + int blr_negate = 38; + int blr_concatenate = 39; + int blr_substring = 40; + int blr_parameter2 = 41; + int blr_from = 42; + int blr_via = 43; + int blr_user_name = 44; + int blr_null = 45; + int blr_equiv = 46; + int blr_eql = 47; + int blr_neq = 48; + int blr_gtr = 49; + int blr_geq = 50; + int blr_lss = 51; + int blr_leq = 52; + int blr_containing = 53; + int blr_matching = 54; + int blr_starting = 55; + int blr_between = 56; + int blr_or = 57; + int blr_and = 58; + int blr_not = 59; + int blr_any = 60; + int blr_missing = 61; + int blr_unique = 62; + int blr_like = 63; + + // unused codes: 64..66 + + int blr_rse = 67; + int blr_first = 68; + int blr_project = 69; + int blr_sort = 70; + int blr_boolean = 71; + int blr_ascending = 72; + int blr_descending = 73; + int blr_relation = 74; + int blr_rid = 75; + int blr_union = 76; + int blr_map = 77; + int blr_group_by = 78; + int blr_aggregate = 79; + int blr_join_type = 80; + + // unused codes: 81..82 + + int blr_agg_count = 83; + int blr_agg_max = 84; + int blr_agg_min = 85; + int blr_agg_total = 86; + int blr_agg_average = 87; + int blr_parameter3 = 88; /* same as Rdb definition */ + + /* unsupported + int blr_run_max = 89; + int blr_run_min = 90; + int blr_run_total = 91; + int blr_run_average = 92; + */ + + int blr_agg_count2 = 93; + int blr_agg_count_distinct = 94; + int blr_agg_total_distinct = 95; + int blr_agg_average_distinct = 96; + + // unused codes: 97..99 + + int blr_function = 100; + int blr_gen_id = 101; + //int blr_prot_mask = 102; + int blr_upcase = 103; + //int blr_lock_state = 104; + int blr_value_if = 105; + int blr_matching2 = 106; + int blr_index = 107; + int blr_ansi_like = 108; + int blr_scrollable = 109; + + // unused codes: 110..117 + + int blr_run_count = 118; + int blr_rs_stream = 119; + int blr_exec_proc = 120; + + // unused codes: 121..123 + + int blr_procedure = 124; + int blr_pid = 125; + int blr_exec_pid = 126; + int blr_singular = 127; + int blr_abort = 128; + int blr_block = 129; + int blr_error_handler = 130; + int blr_cast = 131; + int blr_pid2 = 132; + int blr_procedure2 = 133; + int blr_start_savepoint = 134; + int blr_end_savepoint = 135; + + // unused codes: 136..138 + + int blr_plan = 139; /* access plan items */ + int blr_merge = 140; + int blr_join = 141; + int blr_sequential = 142; + int blr_navigational = 143; + int blr_indices = 144; + int blr_retrieve = 145; + int blr_relation2 = 146; + int blr_rid2 = 147; + + // unused codes: 148..149 + + int blr_set_generator = 150; + int blr_ansi_any = 151; /* required for NULL handling */ + int blr_exists = 152; /* required for NULL handling */ + + // unused codes: 153 + + int blr_record_version = 154; /* get tid of record */ + int blr_stall = 155; /* fake server stall */ + + // unused codes: 156..157 + + int blr_ansi_all = 158; /* required for NULL handling */ + int blr_extract = 159; + + /* sub parameters for blr_extract */ + int blr_extract_year = 0; + int blr_extract_month = 1; + int blr_extract_day = 2; + int blr_extract_hour = 3; + int blr_extract_minute = 4; + int blr_extract_second = 5; + int blr_extract_weekday = 6; + int blr_extract_yearday = 7; + int blr_extract_millisecond = 8; + int blr_extract_week = 9; + int blr_current_date = 160; + int blr_current_timestamp = 161; + int blr_current_time = 162; + + /* These codes reuse BLR code space */ + int blr_post_arg = 163; + int blr_exec_into = 164; + int blr_user_savepoint = 165; + int blr_dcl_cursor = 166; + int blr_cursor_stmt = 167; + int blr_current_timestamp2 = 168; + int blr_current_time2 = 169; + int blr_agg_list = 170; + int blr_agg_list_distinct = 171; + int blr_modify2 = 172; + + // unused codes: 173 + + /* FB 1.0 specific BLR */ + int blr_current_role = 174; + int blr_skip = 175; + + /* FB 1.5 specific BLR */ + int blr_exec_sql = 176; + int blr_internal_info = 177; + int blr_nullsfirst = 178; + int blr_writelock = 179; + int blr_nullslast = 180; + + /* FB 2.0 specific BLR */ + int blr_lowcase = 181; + int blr_strlen = 182; + + /* sub parameter for blr_strlen */ + int blr_strlen_bit = 0; + int blr_strlen_char = 1; + int blr_strlen_octet = 2; + int blr_trim = 183; + + /* first sub parameter for blr_trim */ + int blr_trim_both = 0; + int blr_trim_leading = 1; + int blr_trim_trailing = 2; + + /* second sub parameter for blr_trim */ + int blr_trim_spaces = 0; + int blr_trim_characters = 1; + + /* These codes are actions for user-defined savepoints */ + int blr_savepoint_set = 0; + int blr_savepoint_release = 1; + int blr_savepoint_undo = 2; + int blr_savepoint_release_single = 3; + + /* These codes are actions for cursors */ + int blr_cursor_open = 0; + int blr_cursor_close = 1; + int blr_cursor_fetch = 2; + int blr_cursor_fetch_scroll = 3; + + /* scroll options */ + int blr_scroll_forward = 0; + int blr_scroll_backward = 1; + int blr_scroll_bof = 2; + int blr_scroll_eof = 3; + int blr_scroll_absolute = 4; + int blr_scroll_relative = 5; + + /* FB 2.1 specific BLR */ + int blr_init_variable = 184; + int blr_recurse = 185; + int blr_sys_function = 186; + + // FB 2.5 specific BLR + int blr_auto_trans = 187; + int blr_similar = 188; + int blr_exec_stmt = 189; + + // subcodes of blr_exec_stmt + int blr_exec_stmt_inputs = 1; // input parameters count + int blr_exec_stmt_outputs = 2; // output parameters count + int blr_exec_stmt_sql = 3; + int blr_exec_stmt_proc_block = 4; + int blr_exec_stmt_data_src = 5; + int blr_exec_stmt_user = 6; + int blr_exec_stmt_pwd = 7; + int blr_exec_stmt_tran = 8; // not implemented yet + int blr_exec_stmt_tran_clone = 9; // make transaction parameters equal to current transaction + int blr_exec_stmt_privs = 10; + int blr_exec_stmt_in_params = 11; // not named input parameters + int blr_exec_stmt_in_params2 = 12; // named input parameters + int blr_exec_stmt_out_params = 13; // output parameters + int blr_exec_stmt_role = 14; + int blr_stmt_expr = 190; + int blr_derived_expr = 191; + + // FB 3.0 specific BLR + int blr_procedure3 = 192; + int blr_exec_proc2 = 193; + int blr_function2 = 194; + int blr_window = 195; + int blr_partition_by = 196; + int blr_continue_loop = 197; + int blr_procedure4 = 198; + int blr_agg_function = 199; + int blr_substring_similar = 200; + int blr_bool_as_value = 201; + int blr_coalesce = 202; + int blr_decode = 203; + int blr_exec_subproc = 204; + int blr_subproc_decl = 205; + int blr_subproc = 206; + int blr_subfunc_decl = 207; + int blr_subfunc = 208; + int blr_record_version2 = 209; +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/BlrConstants.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/WireProtocolConstants.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/WireProtocolConstants.java 2013-09-12 06:47:20 UTC (rev 58606) +++ client-java/trunk/src/main/org/firebirdsql/gds/impl/wire/WireProtocolConstants.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -216,4 +216,7 @@ int ptype_batch_send = 3; // Batch sends, no asynchrony int ptype_out_of_band = 4; // Batch sends w/ out of band notification int ptype_lazy_send = 5; // Deferred packets delivery + + int FETCH_OK = 0; + int FETCH_NO_MORE_ROWS = 100; } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-12 06:47:20 UTC (rev 58606) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -21,12 +21,18 @@ package org.firebirdsql.gds.ng; import org.firebirdsql.gds.ISCConstants; +import org.firebirdsql.gds.ng.fields.FieldValue; +import org.firebirdsql.gds.ng.fields.RowDescriptor; +import org.firebirdsql.gds.ng.listeners.RowListener; +import org.firebirdsql.gds.ng.listeners.RowListenerDispatcher; import java.sql.SQLException; +import java.sql.SQLNonTransientException; +import java.sql.SQLTransientException; import java.util.Collections; import java.util.EnumSet; +import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; /** * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> @@ -35,13 +41,17 @@ public abstract class AbstractFbStatement implements FbStatement { private final Object syncObject = new Object(); - private final AtomicReference<StatementState> state = new AtomicReference<StatementState>(StatementState.CLOSED); - private final AtomicReference<StatementType> type = new AtomicReference<StatementType>(StatementType.NONE); + protected final RowListenerDispatcher rowListenerDispatcher = new RowListenerDispatcher(); + private volatile boolean allRowsFetched = false; + private volatile StatementState state = StatementState.CLOSED; + private volatile StatementType type = StatementType.NONE; + private volatile RowDescriptor parameterDescriptor; + private volatile RowDescriptor fieldDescriptor; /** * Plan information items */ - private static final byte[] DESCRIBE_PLAN_INFO_ITEMS = new byte[] { + private static final byte[] DESCRIBE_PLAN_INFO_ITEMS = new byte[]{ ISCConstants.isc_info_sql_get_plan }; @@ -49,7 +59,7 @@ * Records affected items * TODO: Compare with current implementation */ - private static final byte[] ROWS_AFFECTED_INFO_ITEMS = new byte[] { + private static final byte[] ROWS_AFFECTED_INFO_ITEMS = new byte[]{ ISCConstants.isc_info_sql_records }; @@ -59,13 +69,14 @@ } @Override - public void close() { + public void close() throws SQLException { synchronized (getSynchronizationObject()) { if (getState() == StatementState.CLOSED) return; // TODO do additional checks (see also old implementation and .NET) try { free(ISCConstants.DSQL_drop); } finally { + rowListenerDispatcher.removeAllListeners(); setState(StatementState.CLOSED); setType(StatementType.NONE); } @@ -103,7 +114,7 @@ @Override public StatementState getState() { - return state.get(); + return state; } /** @@ -114,12 +125,14 @@ */ protected void setState(StatementState state) { // TODO Check valid transition? - this.state.set(state); + synchronized (getSynchronizationObject()) { + this.state = state; + } } @Override - public StatementType getType() { - return type.get(); + public final StatementType getType() { + return type; } /** @@ -129,7 +142,9 @@ * New type */ protected void setType(StatementType type) { - this.type.set(type); + synchronized (getSynchronizationObject()) { + this.type = type; + } } public byte[] getDescribePlanInfoItems() { @@ -141,6 +156,107 @@ } /** + * Queues row data for consumption + * + * @param rowData + * Row data + */ + protected final void queueRowData(List<FieldValue> rowData) { + rowListenerDispatcher.newRow(this, rowData); + } + + /** + * Sets the <code>allRowsFetched</code> property. + * <p> + * When set to true all registered {@link RowListener} instances are notified for the {@link RowListener#allRowsFetched(FbStatement)} + * event. + * </p> + * + * @param allRowsFetched + * <code>true</code>: all rows fetched, <code>false</code> not all rows fetched. + */ + protected final void setAllRowsFetched(boolean allRowsFetched) { + synchronized (getSynchronizationObject()) { + this.allRowsFetched = allRowsFetched; + } + if (allRowsFetched) { + rowListenerDispatcher.allRowsFetched(this); + } + } + + protected final boolean isAllRowsFetched() { + return allRowsFetched; + } + + /** + * Reset statement state, equivalent to calling {@link #reset(boolean)} with <code>false</code> + */ + protected final void reset() { + reset(false); + } + + /** + * Reset statement state and clear parameter description, equivalent to calling {@link #reset(boolean)} with <code>true</code> + */ + protected final void resetAll() { + reset(true); + } + + /** + * Resets the statement for next execution. Implementation in derived class must synchronize on {@link #getSynchronizationObject()} and + * call <code>super.reset(resetAll)</code> + * + * @param resetAll + * Also reset field and parameter info + */ + protected void reset(boolean resetAll) { + synchronized (getSynchronizationObject()) { + setAllRowsFetched(false); + + if (resetAll) { + setParameterDescriptor(null); + setFieldDescriptor(null); + } + } + } + + @Override + public final RowDescriptor getParameterDescriptor() throws SQLException { + checkStatementOpen(); + return parameterDescriptor; + } + + /** + * Sets the parameter descriptor. + * + * @param parameterDescriptor + * Parameter descriptor + */ + protected final void setParameterDescriptor(RowDescriptor parameterDescriptor) { + synchronized (getSynchronizationObject()) { + this.parameterDescriptor = parameterDescriptor; + } + } + + @Override + public final RowDescriptor getFieldDescriptor() throws SQLException { + checkStatementOpen(); + return fieldDescriptor; + } + + /** + * Sets the (result set) field descriptor. + * + * @param fieldDescriptor + * Field descriptor + */ + protected final void setFieldDescriptor(RowDescriptor fieldDescriptor) { + synchronized (getSynchronizationObject()) { + this.fieldDescriptor = fieldDescriptor; + } + } + + /** * @return The (full) statement info request items. * @see #getParameterDescriptionInfoRequestItems() */ @@ -180,5 +296,83 @@ */ public abstract byte[] getSqlInfo(byte[] requestItems, int bufferLength) throws SQLException; - protected abstract void free(int option); + /** + * Frees the currently allocated statement (either close the cursor with {@link ISCConstants#DSQL_close} or drop the statement + * handle using {@link ISCConstants#DSQL_drop}. + * + * @param option + * Free option + * @throws SQLException + */ + protected abstract void free(int option) throws SQLException; + + /** + * Validates if the number of parameters matches the expected number and types, and if all values have been set. + * + * @param parameters + * List of parameters + * @throws SQLException + * When the number or type of parameters does not match {@link #getParameterDescriptor()}, or when a parameter has not been set. + */ + protected void validateParameters(final List<FieldValue> parameters) throws SQLException { + final RowDescriptor parameterDescriptor = getParameterDescriptor(); + final int expectedSize = parameterDescriptor != null ? parameterDescriptor.getCount() : 0; + final int actualSize = parameters.size(); + // TODO Externalize sqlstates + if (actualSize != expectedSize) { + // TODO use HY021 (inconsistent descriptor information) instead? + throw new SQLNonTransientException(String.format("Invalid number of parameters, expected %d, got %d", + expectedSize, actualSize), "07008"); // invalid descriptor count + } + for (int fieldIndex = 0; fieldIndex < actualSize; fieldIndex++) { + FieldValue fieldValue = parameters.get(fieldIndex); + if (fieldValue == null || !fieldValue.isInitialized()) { + // Communicating 1-based index, so it doesn't cause confusion when JDBC user sees this. + // TODO use HY000 (dynamic parameter value needed) instead? + throw new SQLTransientException(String.format("Parameter with index %d was not set", + fieldIndex + 1), "0700C"); // undefined DATA value + } + if (!fieldValue.getFieldDescriptor().equals(parameterDescriptor.getFieldDescriptor(fieldIndex))) { + // Communicating 1-based index, so it doesn't cause confusion when JDBC user sees this. + // TODO use HY021 (inconsistent descriptor information) or HY091 (invalid descriptor field identifier) instead? + // TODO Use isc_field_ref_err? + throw new SQLNonTransientException(String.format("Parameter with index %d has an unexpected descriptor (expected %s, got %s)", + fieldIndex + 1, parameterDescriptor.getFieldDescriptor(fieldIndex), fieldValue.getFieldDescriptor()), "07009"); // invalid descriptor index + } + } + } + + @Override + public final void addRowListener(RowListener rowListener) { + // TODO What to do after statement close? + rowListenerDispatcher.addListener(rowListener); + } + + @Override + public final void removeRowListener(RowListener rowListener) { + rowListenerDispatcher.removeListener(rowListener); + } + + /** + * Checks if this statement is not in {@link StatementState#CLOSED}, and throws an <code>SQLException</code> if it is. + * + * @throws SQLException + * When this statement is closed. + */ + protected final void checkStatementOpen() throws SQLException { + if (getState() == StatementState.CLOSED) { + // TODO Externalize sqlstate + // TODO See if there is a firebird error code matching this (isc_cursor_not_open is not exactly the same) + throw new SQLException("Statement closed", "24000"); + } + } + + @Override + protected void finalize() throws Throwable { + try { + if (getState() != StatementState.CLOSED) close(); + } finally { + super.finalize(); + } + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-12 06:47:20 UTC (rev 58606) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -26,9 +26,12 @@ */ package org.firebirdsql.gds.ng; +import org.firebirdsql.gds.ng.fields.FieldValue; import org.firebirdsql.gds.ng.fields.RowDescriptor; +import org.firebirdsql.gds.ng.listeners.RowListener; import java.sql.SQLException; +import java.util.List; /** * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> @@ -53,12 +56,12 @@ /** * @return descriptor of the parameters of this statement */ - RowDescriptor getParameters() throws SQLException; + RowDescriptor getParameterDescriptor() throws SQLException; /** * @return descriptor of the fields returned by this statement */ - RowDescriptor getFields() throws SQLException; + RowDescriptor getFieldDescriptor() throws SQLException; /** * @return The statement type @@ -102,9 +105,13 @@ /** * Execute the statement. * + * @param parameters + * The list of parameter values to use for execution. * @throws SQLException + * When the number of type of parameters does not match the types returned by {@link #getParameterDescriptor()}, + * a parameter value was not set, or when an error occurred executing this statement. */ - void execute() throws SQLException; + void execute(List<FieldValue> parameters) throws SQLException; /** * Prepares and executes the statement. This method cannot be used for statements expecting parameters. @@ -121,4 +128,32 @@ * @return object, cannot be <code>null</code>. */ Object getSynchronizationObject(); + + /** + * Requests this statement to fetch the next <code>fetchSize</code> rows. + * <p> + * Fetched rows are not returned from this method, but sent to the registered {@link RowListener} instances. + * </p> + * + * @param fetchSize + * Number of rows to fetch (must be <code>> 0</code>) + * @throws SQLException + * For database access errors, when called on a closed statement, when no cursor is open or when the fetch + * size is not <code>> 0</code>. + */ + void fetchRows(int fetchSize) throws SQLException; + + /** + * Registers a {@link RowListener}. + * + * @param rowListener The row listener + */ + void addRowListener(RowListener rowListener); + + /** + * Removes a {@link RowListener}. + * + * @param rowListener The row listener + */ + void removeRowListener(RowListener rowListener); } Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/BlrCalculator.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/BlrCalculator.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/BlrCalculator.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,57 @@ +/* + * $Id$ + * + * Public Firebird Java API. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.firebirdsql.gds.ng.fields; + +import java.sql.SQLException; + +/** + * Functional interface for calculating the blr (binary language representation) of a row. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public interface BlrCalculator { + + /** + * Calculates the blr for the row descriptor. + * + * @param rowDescriptor + * Row descriptor + * @return Byte array with the blr + * @throws SQLException When the {@link RowDescriptor} contains an unsupported field type. + */ + byte[] calculateBlr(RowDescriptor rowDescriptor) throws SQLException; + + /** + * Calculates the io length for the field descriptor. + * + * @param fieldDescriptor + * Field descriptor + * @return The io length + */ + public int calculateIoLength(FieldDescriptor fieldDescriptor) throws SQLException; +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/BlrCalculator.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldDescriptor.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldDescriptor.java 2013-09-12 06:47:20 UTC (rev 58606) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldDescriptor.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -30,7 +30,10 @@ /** * The class <code>FieldDescriptor</code> contains the column metadata of the XSQLVAR server - * data structure used to describe one column for input or output. FieldDescriptor is an immutable type. + * data structure used to describe one column for input or output. + * <p> + * FieldDescriptor is an immutable type, the value of a field is maintained separately in an instance of {@link FieldValue}. + * </p> * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @version 2.3 @@ -146,6 +149,15 @@ return ownerName; } + /** + * Creates a default, uninitialized {@link FieldValue} + * + * @return A new {@link FieldValue} + */ + public FieldValue createDefaultFieldValue() { + return new FieldValue(this); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldValue.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldValue.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldValue.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,113 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.fields; + +import org.firebirdsql.jdbc.field.FieldDataProvider; + +/** + * Holder object for the value of a (statement) parameter or result set field. + * <p> + * TODO: Consider revision, see for example .NET provider where DbValue stores the (decoded) object instead of the encoded byte array + * </p> + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public final class FieldValue implements FieldDataProvider { + + private final FieldDescriptor fieldDescriptor; + private byte[] fieldData; + private boolean initialized; + + /** + * Creates an uninitialized FieldValue instance with the supplied {@link FieldDescriptor}. + * + * @param fieldDescriptor + * <code>FieldDescriptor</code> object. + */ + public FieldValue(FieldDescriptor fieldDescriptor) { + this(fieldDescriptor, null, false); + } + + /** + * Creates an initialized FieldValue instance with the supplied {@link FieldDescriptor} and <code>fieldData</code>. + * + * @param fieldDescriptor + * <code>FieldDescriptor</code> object. + * @param fieldData + * Byte array with the value encoded as required by the type described in <code>fieldDescriptor</code> + */ + public FieldValue(final FieldDescriptor fieldDescriptor, final byte[] fieldData) { + this(fieldDescriptor, fieldData, true); + } + + /** + * Creates a FieldValue instance with the supplied {@link FieldDescriptor} and <code>fieldData</code> and <code>initialized</code> value. + * + * @param fieldDescriptor + * <code>FieldDescriptor</code> object. + * @param fieldData + * Byte array with the value encoded as required by the type described in <code>fieldDescriptor</code> + * @param initialized + * Is this field in the initialized state + */ + private FieldValue(final FieldDescriptor fieldDescriptor, final byte[] fieldData, final boolean initialized) { + this.fieldDescriptor = fieldDescriptor; + // TODO Defensively copy fieldData? + this.fieldData = fieldData; + this.initialized = initialized; + } + + @Override + public byte[] getFieldData() { + return fieldData; + } + + @Override + public void setFieldData(byte[] fieldData) { + this.fieldData = fieldData; + initialized = true; + } + + /** + * Is this field in an initialized state (meaning: was it explicitly set to a value (or null)). + * + * @return <code>true</code> if initialized, <code>false</code> otherwise + */ + public boolean isInitialized() { + return initialized; + } + + /** + * Resets this field to an uninitialized state. + */ + public void reset() { + initialized = false; + fieldData = null; + } + + /** + * @return The field descriptor instance of this field. + */ + public FieldDescriptor getFieldDescriptor() { + return fieldDescriptor; + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/FieldValue.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptor.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptor.java 2013-09-12 06:47:20 UTC (rev 58606) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptor.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -30,7 +30,11 @@ /** * The class <code>RowDescriptor</code> is a java mapping of the XSQLDA server - * data structure used to represent one row for input or output. RowDescriptor is an immutable type. + * data structure used to describe the row metadata of one row for input or output. + * <p> + * RowDescriptor is an immutable, values of a row are maintained separately in a collection of {@link FieldValue} instances. The only exception + * is the cached value for the calculated blr. + * </p> * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @version 2.3 @@ -38,16 +42,24 @@ public final class RowDescriptor implements Iterable<FieldDescriptor> { public static final RowDescriptor EMPTY = new RowDescriptor(new FieldDescriptor[0]); + public static final List<FieldValue> EMPTY_FIELD_VALUES = Collections.emptyList(); private final FieldDescriptor[] fieldDescriptors; private int hash; - public RowDescriptor(FieldDescriptor[] fieldDescriptors) { + /** + * Creates an instance of <code>RowDescriptor</code> with the supplied array of + * {@link FieldDescriptor} instances. + * + * @param fieldDescriptors + * The field descriptors (array is cloned before use) + */ + private RowDescriptor(final FieldDescriptor[] fieldDescriptors) { this.fieldDescriptors = fieldDescriptors.clone(); } /** - * @return The number of columns. + * @return The number of fields. */ public int getCount() { return fieldDescriptors.length; @@ -71,6 +83,24 @@ return Collections.unmodifiableList(Arrays.asList(fieldDescriptors)); } + /** + * Creates a {@link List} with default {@link FieldValue} instances as returned by {@link FieldDescriptor#createDefaultFieldValue()}. + * <p> + * The (0-based) index of the FieldValue in the list corresponds with the (0-based) index of the {@link FieldDescriptor} + * within this <code>RowDescriptor</code>. + * </p> + * + * @return Default <code>FieldValue</code> instances for the <code>FieldDescriptor</code> instance contained in this instance. + */ + public List<FieldValue> createDefaultFieldValues() { + if (getCount() == 0) return EMPTY_FIELD_VALUES; + List<FieldValue> fieldValues = new ArrayList<FieldValue>(getCount()); + for (FieldDescriptor fieldDescriptor : fieldDescriptors) { + fieldValues.add(fieldDescriptor.createDefaultFieldValue()); + } + return fieldValues; + } + @Override public Iterator<FieldDescriptor> iterator() { return getFieldDescriptors().iterator(); @@ -112,4 +142,16 @@ } return hash; } + + /** + * Creates an instance of <code>RowDescriptor</code> with the supplied {@link FieldDescriptor} instances. + * + * @param fieldDescriptors + * The field descriptors (array is cloned before use) + * @return <code>RowDescriptor</code> instance + */ + public static RowDescriptor createRowDescriptor(final FieldDescriptor[] fieldDescriptors) { + if (fieldDescriptors.length == 0) return EMPTY; + return new RowDescriptor(fieldDescriptors); + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptorBuilder.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptorBuilder.java 2013-09-12 06:47:20 UTC (rev 58606) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/fields/RowDescriptorBuilder.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -23,7 +23,7 @@ /** * Builder to construct an immutable {@link RowDescriptor}. * <p> - * The row is constructed by defining the fields, and using {@link #addField()} to add the current field + * The row descriptor is constructed by defining the fields, and using {@link #addField()} to add the current field * definition to the row. The field data is then reset (as if {@link #resetField()} was called, * to prepare for the next field to add. * </p> @@ -34,7 +34,7 @@ * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @since 2.3 */ -public class RowDescriptorBuilder { +public final class RowDescriptorBuilder { private int type; private int subType; @@ -55,7 +55,7 @@ * @param size * Number of fields */ - public RowDescriptorBuilder(int size) { + public RowDescriptorBuilder(final int size) { fieldDescriptors = new FieldDescriptor[size]; } @@ -168,8 +168,19 @@ return this; } - public RowDescriptorBuilder setFieldIndex(int index) { - if (index >= fieldDescriptors.length) { + /** + * Sets the field index for the current field under construction. + * + * @param index + * Index of the field + * @return this builder + * @throws IndexOutOfBoundsException + * When <code>index</code> is not between 0 (inclusive) and {@link #getSize()} (exclusive) + * @throws IllegalStateException + * When a {@link FieldDescriptor} is already defined on the specified <code>index</code> + */ + public RowDescriptorBuilder setFieldIndex(final int index) { + if (index < 0 || index >= fieldDescriptors.length) { throw new IndexOutOfBoundsException(String.format("The index '%d' exceeds the expected size (%d) of this RowDescriptorBuilder", index, fieldDescriptors.length)); } if (fieldDescriptors[index] != null) { @@ -208,7 +219,7 @@ } /** - * Resets the fields of this builder the Java defaults. + * Resets the fields of this builder to the Java defaults. */ public RowDescriptorBuilder resetField() { type = 0; @@ -224,13 +235,13 @@ } /** - * Set this builder with the values of the source {@link FieldDescriptor}. + * Set this builder with the values of the source {@link FieldDescriptor} for further modification through this builder. * * @param sourceFieldDescriptor * Source for the initial values * @return this builder */ - public RowDescriptorBuilder copyFieldFrom(FieldDescriptor sourceFieldDescriptor) { + public RowDescriptorBuilder copyFieldFrom(final FieldDescriptor sourceFieldDescriptor) { type = sourceFieldDescriptor.getType(); subType = sourceFieldDescriptor.getSubType(); scale = sourceFieldDescriptor.getScale(); @@ -247,19 +258,25 @@ * Adds the current field data to the row and prepares this builder for the next field by resetting all values. * * @return this builder + * @see #resetField() */ public RowDescriptorBuilder addField() { return addField(toFieldDescriptor()).resetField(); } /** - * Adds the {@link FieldDescriptor} as the next in the row + * Adds the {@link FieldDescriptor} on the current fieldIndex as the next in the row, and increments the current + * field index by 1. + * <p> + * This method does not call {@link #resetField()}, so a partial definition of a field can exist + * inside this builder after calling this method. + * </p> * * @param fieldDescriptor * FieldDescriptor to add * @return this builder */ - public RowDescriptorBuilder addField(FieldDescriptor fieldDescriptor) { + public RowDescriptorBuilder addField(final FieldDescriptor fieldDescriptor) { if (currentFieldIndex >= fieldDescriptors.length) { throw new IndexOutOfBoundsException(String.format("The index '%d' exceeds the expected size (%d) of this RowDescriptorBuilder", currentFieldIndex, fieldDescriptors.length)); } @@ -269,21 +286,27 @@ } /** - * Constructs the {@link RowDescriptor}. + * Constructs the {@link RowDescriptor} with the current content. + * <p> + * This method can also return a partially filled {@link RowDescriptor}. Caller can check for completeness by + * calling {@link #isComplete()}. + * </p> * * @return RowDescriptor instance. + * @see #isComplete() */ public RowDescriptor toRowDescriptor() { - // NOTE: The correctness of this depends on the fact that RowDescriptor copies the content of the list - return new RowDescriptor(fieldDescriptors); + // TODO Reconsider allowing partial construction? + // NOTE: The correctness of this depends on the fact that RowDescriptor copies the content of the array + return RowDescriptor.createRowDescriptor(fieldDescriptors); } /** * @return <tt>true</tt> when all {@link FieldDescriptor} entries have been defined */ public boolean isComplete() { - for (int idx = 0; idx < fieldDescriptors.length; idx++) { - if (fieldDescriptors[idx] == null) { + for (FieldDescriptor fieldDescriptor : fieldDescriptors) { + if (fieldDescriptor == null) { return false; } } Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/AbstractListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/AbstractListenerDispatcher.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/AbstractListenerDispatcher.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.listeners; + +import java.util.*; + +/** + * Dispatcher to maintain a list of listeners of type <code>TListener</code> + * + * @param <TListener> Listener type + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public class AbstractListenerDispatcher<TListener> implements Iterable<TListener> { + + private final Set<TListener> listeners = Collections.synchronizedSet(new HashSet<TListener>()); + + /** + * Adds the supplied listener to this dispatcher. + * + * @param listener Listener object + */ + public final void addListener(TListener listener) { + if (listener == this) { + throw new IllegalArgumentException("Adding this instance to itself is not allowed"); + } + listeners.add(listener); + } + + /** + * Removes the supplied listener from this dispatcher. + * + * @param listener Listener object + */ + public final void removeListener(TListener listener) { + listeners.remove(listener); + } + + public final void removeAllListeners() { + listeners.clear(); + } + + @Override + public final Iterator<TListener> iterator() { + synchronized (listeners) { + return new ArrayList<TListener>(listeners).iterator(); + } + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/AbstractListenerDispatcher.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,77 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.fields.FieldValue; + +import java.util.List; + +/** + * Listener interface for receiving rows and related information as retrieved by + * an {@link org.firebirdsql.gds.ng.FbStatement#fetchRows(int)}, or {@link org.firebirdsql.gds.ng.FbStatement#execute(java.util.List)} with a singleton result. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public interface RowListener { + + /** + * Method to receive a new row of data. + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @param rowData + * The rowData as list. Implementer may choose to use an immutable <code>List</code>. + */ + void newRow(FbStatement sender, List<FieldValue> rowData); + + /** + * Method to be notified when all rows have been fetched. + * <p> + * This method may also be called when the statement did not produce any rows (or did not open a result set). + * </p> + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @see #statementExecuted(FbStatement, boolean) + */ + void allRowsFetched(FbStatement sender); + + /** + * Method to be notified when a statement has been executed. + * <p> + * This event with <code>hasResultSet=true</code> can be seen as the counter part of {@link #allRowsFetched(FbStatement)}. + * </p> + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @param hasResultSet + * <code>true</code> there is a result set, <code>false</code> there is no result set + * @param hasSingletonResult + * <code>true</code> singleton result, <code>false</code> statement will produce indeterminate number of rows; + * can be ignored when <code>hasResultSet</code> is false. + */ + void statementExecuted(FbStatement sender, boolean hasResultSet, boolean hasSingletonResult); + + // TODO Statement close, next execute? + +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,56 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.fields.FieldValue; + +import java.util.*; + +/** + * Dispatcher to maintain and notify other RowListener. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since + */ +public final class RowListenerDispatcher extends AbstractListenerDispatcher<RowListener> implements RowListener { + + @Override + public void newRow(final FbStatement sender, final List<FieldValue> rowData) { + for (RowListener listener : this) { + listener.newRow(sender, rowData); + } + } + + @Override + public void allRowsFetched(final FbStatement sender) { + for (RowListener listener : this) { + listener.allRowsFetched(sender); + } + } + + @Override + public void statementExecuted(final FbStatement sender, final boolean hasResultSet, final boolean hasSingletonResult) { + for (RowListener listener : this) { + listener.statementExecuted(sender, hasResultSet, hasSingletonResult); + } + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java 2013-09-12 09:17:59 UTC (rev 58607) @@ -0,0 +1,113 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.wire; + +import org.firebirdsql.encodings.Encoding; +import org.firebirdsql.encodings.IEncodingFactory; +import org.firebirdsql.gds.impl.wire.XdrInputStream; +import org.firebirdsql.gds.impl.wire.XdrOutputStream; + +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public abstract class AbstractFbWireDatabase implements FbWireDatabase { + + protected final AtomicBoolean attached = new AtomicBoolean(); + protected final ProtocolDescriptor protocolDescriptor; + protected final WireConnection connection; + private final XdrStreamHolder xdrStreamHolder; + private final Object syncObject = new Object(); + private short databaseDialect; + + /** + * Creates a V10Database instance. + * + * @param connection + * A WireConnection with an established connection to the server. + * @param descriptor + * The ProtocolDescriptor that created this connection (this is + * used for creating further dependent objects). + */ + protected AbstractFbWireDatabase(WireConnection connection, ProtocolDescriptor descriptor) { + if (connection == null) throw new IllegalArgumentException("parameter connection should be non-null"); + if (descriptor == null) throw new IllegalArgumentException("parameter descriptor should be non-null"); + this.connection = connection; + xdrStreamHolder = new XdrStreamHolder(connection); + protocolDescriptor = descriptor; + } + + @Override + public final Object getSynchronizationObject() { + return syncObject; + } + + @Override + public final short getConnectionDialect() { + return connection.getConnectionDialect(); + } + + @Override + public final IEncodingFactory getEncodingFactory() { + return connection.getEncodingFactory(); + } + + @Override + public final Encoding getEncoding() { + return connection.getEncoding(); + } + + @Override + public final XdrInputStream getXdrIn() throws SQLException { + return xdrStreamHolder.getXdrIn(); + } + + @Override + public final XdrOutputStream getXdrOut() throws SQLException { + return xdrStreamHolder.getXdrOut(); + } + + @Override + public final boolean isAttached(... [truncated message content] |
From: <mro...@us...> - 2013-09-12 10:12:19
|
Revision: 58609 http://sourceforge.net/p/firebird/code/58609 Author: mrotteveel Date: 2013-09-12 10:12:15 +0000 (Thu, 12 Sep 2013) Log Message: ----------- Move + rename transactioneventlistener, rename rowlistener to statement listener, add statement state change event Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbTransaction.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbTransaction.java client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java Removed Paths: ------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionEventListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleRowListener.java Property Changed: ---------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbTransaction.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbTransaction.java client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -23,8 +23,8 @@ import org.firebirdsql.gds.ISCConstants; import org.firebirdsql.gds.ng.fields.FieldValue; import org.firebirdsql.gds.ng.fields.RowDescriptor; -import org.firebirdsql.gds.ng.listeners.RowListener; -import org.firebirdsql.gds.ng.listeners.RowListenerDispatcher; +import org.firebirdsql.gds.ng.listeners.StatementListener; +import org.firebirdsql.gds.ng.listeners.StatementListenerDispatcher; import java.sql.SQLException; import java.sql.SQLNonTransientException; @@ -33,6 +33,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; /** * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> @@ -41,9 +42,9 @@ public abstract class AbstractFbStatement implements FbStatement { private final Object syncObject = new Object(); - protected final RowListenerDispatcher rowListenerDispatcher = new RowListenerDispatcher(); + protected final StatementListenerDispatcher statementListenerDispatcher = new StatementListenerDispatcher(); private volatile boolean allRowsFetched = false; - private volatile StatementState state = StatementState.CLOSED; + private AtomicReference<StatementState> state = new AtomicReference<StatementState>(StatementState.CLOSED); private volatile StatementType type = StatementType.NONE; private volatile RowDescriptor parameterDescriptor; private volatile RowDescriptor fieldDescriptor; @@ -76,8 +77,8 @@ try { free(ISCConstants.DSQL_drop); } finally { - rowListenerDispatcher.removeAllListeners(); - setState(StatementState.CLOSED); + statementListenerDispatcher.removeAllListeners(); + switchState(StatementState.CLOSED); setType(StatementType.NONE); } } @@ -107,26 +108,33 @@ } } finally { // TODO Close in case of exception? - setState(StatementState.IDLE); + switchState(StatementState.IDLE); } } } @Override public StatementState getState() { - return state; + return state.get(); } /** * Sets the StatementState. * - * @param state + * @param newState * New state */ - protected void setState(StatementState state) { + protected final void switchState(final StatementState newState) throws SQLException { // TODO Check valid transition? - synchronized (getSynchronizationObject()) { - this.state = state; + final StatementState currentState = this.state.get(); + if (state.compareAndSet(currentState, newState)) { + statementListenerDispatcher.statementStateChanged(this, newState, currentState); + } else { + // Note: race condition when generating message (get() could return same value as currentState) + // TODO Include sqlstate + throw new SQLException(String.format( + "Unable to change statement state: expected current state %s, but was %s", currentState, + state.get())); } } @@ -162,13 +170,13 @@ * Row data */ protected final void queueRowData(List<FieldValue> rowData) { - rowListenerDispatcher.newRow(this, rowData); + statementListenerDispatcher.newRow(this, rowData); } /** * Sets the <code>allRowsFetched</code> property. * <p> - * When set to true all registered {@link RowListener} instances are notified for the {@link RowListener#allRowsFetched(FbStatement)} + * When set to true all registered {@link org.firebirdsql.gds.ng.listeners.StatementListener} instances are notified for the {@link org.firebirdsql.gds.ng.listeners.StatementListener#allRowsFetched(FbStatement)} * event. * </p> * @@ -180,7 +188,7 @@ this.allRowsFetched = allRowsFetched; } if (allRowsFetched) { - rowListenerDispatcher.allRowsFetched(this); + statementListenerDispatcher.allRowsFetched(this); } } @@ -343,14 +351,14 @@ } @Override - public final void addRowListener(RowListener rowListener) { + public final void addStatementListener(StatementListener statementListener) { // TODO What to do after statement close? - rowListenerDispatcher.addListener(rowListener); + statementListenerDispatcher.addListener(statementListener); } @Override - public final void removeRowListener(RowListener rowListener) { - rowListenerDispatcher.removeListener(rowListener); + public final void removeStatementListener(StatementListener statementListener) { + statementListenerDispatcher.removeListener(statementListener); } /** Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbTransaction.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbTransaction.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbTransaction.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -20,12 +20,10 @@ */ package org.firebirdsql.gds.ng; +import org.firebirdsql.gds.ng.listeners.TransactionListener; +import org.firebirdsql.gds.ng.listeners.TransactionListenerDispatcher; + import java.sql.SQLException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicReference; /** @@ -34,9 +32,7 @@ */ public abstract class AbstractFbTransaction implements FbTransaction { - private static final Object PRESENT = new Object(); - private final Map<TransactionEventListener, Object> transactionEventListeners = Collections - .synchronizedMap(new WeakHashMap<TransactionEventListener, Object>()); + protected final TransactionListenerDispatcher transactionListenerDispatcher = new TransactionListenerDispatcher(); private final AtomicReference<TransactionState> state = new AtomicReference<TransactionState>( TransactionState.NO_TRANSACTION); @@ -54,13 +50,13 @@ * If the requested state transition is not allowed or if the * current state is also changed in a concurrent thread. */ - protected final void switchState(TransactionState newState) throws SQLException { - TransactionState currentState = state.get(); + protected final void switchState(final TransactionState newState) throws SQLException { + final TransactionState currentState = state.get(); if (currentState.isValidTransition(newState)) { if (state.compareAndSet(currentState, newState)) { - fireTransactionStateChanged(newState, currentState); + transactionListenerDispatcher.transactionStateChanged(this, newState, currentState); } else { - // TODO: race condition when generating message (get() could return same value as currentState) + // Note: race condition when generating message (get() could return same value as currentState) // TODO Include sqlstate throw new SQLException(String.format( "Unable to change transaction state: expected current state %s, but was %s", currentState, @@ -74,13 +70,13 @@ } @Override - public final void addTransactionEventListener(TransactionEventListener listener) { - transactionEventListeners.put(listener, PRESENT); + public final void addTransactionListener(TransactionListener listener) { + transactionListenerDispatcher.addListener(listener); } @Override - public final void removeTransactionEventListener(TransactionEventListener listener) { - transactionEventListeners.remove(listener); + public final void removeTransactionListener(TransactionListener listener) { + transactionListenerDispatcher.removeListener(listener); } @Override @@ -96,20 +92,4 @@ } } - /** - * Fires the transactionStateChanged event to all listeners. - * - * @param newState - * The new state of the transaction - * @param previousState - * The previous state of the transaction - */ - protected final void fireTransactionStateChanged(TransactionState newState, TransactionState previousState) { - Set<TransactionEventListener> listeners = new HashSet<TransactionEventListener>( - transactionEventListeners.keySet()); - - for (TransactionEventListener listener : listeners) { - listener.transactionStateChanged(this, newState, previousState); - } - } } Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbTransaction.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -28,7 +28,7 @@ import org.firebirdsql.gds.ng.fields.FieldValue; import org.firebirdsql.gds.ng.fields.RowDescriptor; -import org.firebirdsql.gds.ng.listeners.RowListener; +import org.firebirdsql.gds.ng.listeners.StatementListener; import java.sql.SQLException; import java.util.List; @@ -113,14 +113,14 @@ */ void execute(List<FieldValue> parameters) throws SQLException; - /** - * Prepares and executes the statement. This method cannot be used for statements expecting parameters. - * - * @param statementText - * Statement text - * @throws SQLException - */ - void execute(String statementText) throws SQLException; +// /** +// * Prepares and executes the statement. This method cannot be used for statements expecting parameters. +// * +// * @param statementText +// * Statement text +// * @throws SQLException +// */ +// void execute(String statementText) throws SQLException; /** * Get synchronization object. @@ -132,7 +132,7 @@ /** * Requests this statement to fetch the next <code>fetchSize</code> rows. * <p> - * Fetched rows are not returned from this method, but sent to the registered {@link RowListener} instances. + * Fetched rows are not returned from this method, but sent to the registered {@link org.firebirdsql.gds.ng.listeners.StatementListener} instances. * </p> * * @param fetchSize @@ -144,16 +144,16 @@ void fetchRows(int fetchSize) throws SQLException; /** - * Registers a {@link RowListener}. + * Registers a {@link org.firebirdsql.gds.ng.listeners.StatementListener}. * - * @param rowListener The row listener + * @param statementListener The row listener */ - void addRowListener(RowListener rowListener); + void addStatementListener(StatementListener statementListener); /** - * Removes a {@link RowListener}. + * Removes a {@link org.firebirdsql.gds.ng.listeners.StatementListener}. * - * @param rowListener The row listener + * @param statementListener The row listener */ - void removeRowListener(RowListener rowListener); + void removeStatementListener(StatementListener statementListener); } Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbTransaction.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbTransaction.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbTransaction.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -27,6 +27,7 @@ package org.firebirdsql.gds.ng; import org.firebirdsql.gds.TransactionParameterBuffer; +import org.firebirdsql.gds.ng.listeners.TransactionListener; import java.sql.SQLException; @@ -49,25 +50,20 @@ int getHandle(); /** - * Adds a {@link TransactionEventListener} to the list of listeners. - * <p> - * The implementation may use {@link java.lang.ref.WeakReference} for the listeners, so - * make sure the listener remains strongly reachable for its useful - * lifetime. - * </p> + * Adds a {@link org.firebirdsql.gds.ng.listeners.TransactionListener} to the list of listeners. * * @param listener - * TransactionEventListener to register + * TransactionListener to register */ - void addTransactionEventListener(TransactionEventListener listener); + void addTransactionListener(TransactionListener listener); /** - * Removes the {@link TransactionEventListener} from the list of listeners. + * Removes the {@link org.firebirdsql.gds.ng.listeners.TransactionListener} from the list of listeners. * * @param listener - * TransactionEventListener to remove + * TransactionListener to remove */ - void removeTransactionEventListener(TransactionEventListener listener); + void removeTransactionListener(TransactionListener listener); /** * Begin the transaction. Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbTransaction.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -30,9 +30,10 @@ * Statement states for {@link FbStatement} implementations * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 */ public enum StatementState { - + // TODO Add state 'NEW' to distinguish between close() called, or newly constructed statement? /** * Statement is closed or has been deallocated */ Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Deleted: client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionEventListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionEventListener.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionEventListener.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -1,41 +0,0 @@ -/* - * $Id$ - * - * Public Firebird Java API. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.firebirdsql.gds.ng; - -/** - * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> - * @since 2.3 - */ -public interface TransactionEventListener { - - /** - * Signals that the transaction state changed. - * - * @param transaction {@link FbTransaction} that changed state - */ - void transactionStateChanged(FbTransaction transaction, TransactionState newState, TransactionState previousState); -} Deleted: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -1,77 +0,0 @@ -/* - * $Id$ - * - * Firebird Open Source J2EE Connector - JDBC Driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ -package org.firebirdsql.gds.ng.listeners; - -import org.firebirdsql.gds.ng.FbStatement; -import org.firebirdsql.gds.ng.fields.FieldValue; - -import java.util.List; - -/** - * Listener interface for receiving rows and related information as retrieved by - * an {@link org.firebirdsql.gds.ng.FbStatement#fetchRows(int)}, or {@link org.firebirdsql.gds.ng.FbStatement#execute(java.util.List)} with a singleton result. - * - * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> - * @since 2.3 - */ -public interface RowListener { - - /** - * Method to receive a new row of data. - * - * @param sender - * The <code>FbStatement</code> that called this method. - * @param rowData - * The rowData as list. Implementer may choose to use an immutable <code>List</code>. - */ - void newRow(FbStatement sender, List<FieldValue> rowData); - - /** - * Method to be notified when all rows have been fetched. - * <p> - * This method may also be called when the statement did not produce any rows (or did not open a result set). - * </p> - * - * @param sender - * The <code>FbStatement</code> that called this method. - * @see #statementExecuted(FbStatement, boolean) - */ - void allRowsFetched(FbStatement sender); - - /** - * Method to be notified when a statement has been executed. - * <p> - * This event with <code>hasResultSet=true</code> can be seen as the counter part of {@link #allRowsFetched(FbStatement)}. - * </p> - * - * @param sender - * The <code>FbStatement</code> that called this method. - * @param hasResultSet - * <code>true</code> there is a result set, <code>false</code> there is no result set - * @param hasSingletonResult - * <code>true</code> singleton result, <code>false</code> statement will produce indeterminate number of rows; - * can be ignored when <code>hasResultSet</code> is false. - */ - void statementExecuted(FbStatement sender, boolean hasResultSet, boolean hasSingletonResult); - - // TODO Statement close, next execute? - -} Deleted: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -1,56 +0,0 @@ -/* - * $Id$ - * - * Firebird Open Source J2EE Connector - JDBC Driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ -package org.firebirdsql.gds.ng.listeners; - -import org.firebirdsql.gds.ng.FbStatement; -import org.firebirdsql.gds.ng.fields.FieldValue; - -import java.util.*; - -/** - * Dispatcher to maintain and notify other RowListener. - * - * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> - * @since - */ -public final class RowListenerDispatcher extends AbstractListenerDispatcher<RowListener> implements RowListener { - - @Override - public void newRow(final FbStatement sender, final List<FieldValue> rowData) { - for (RowListener listener : this) { - listener.newRow(sender, rowData); - } - } - - @Override - public void allRowsFetched(final FbStatement sender) { - for (RowListener listener : this) { - listener.allRowsFetched(sender); - } - } - - @Override - public void statementExecuted(final FbStatement sender, final boolean hasResultSet, final boolean hasSingletonResult) { - for (RowListener listener : this) { - listener.statementExecuted(sender, hasResultSet, hasSingletonResult); - } - } -} Copied: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java (from rev 58607, client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListener.java) =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -0,0 +1,93 @@ +/* + * $Id$ + * + * Public Firebird Java API. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.StatementState; +import org.firebirdsql.gds.ng.fields.FieldValue; + +import java.util.List; + +/** + * Listener interface for receiving rows and related information as retrieved by + * an {@link org.firebirdsql.gds.ng.FbStatement#fetchRows(int)}, or {@link org.firebirdsql.gds.ng.FbStatement#execute(java.util.List)} with a singleton result. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public interface StatementListener { + + /** + * Method to receive a new row of data. + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @param rowData + * The rowData as list. Implementer may choose to use an immutable <code>List</code>. + */ + void newRow(FbStatement sender, List<FieldValue> rowData); + + /** + * Method to be notified when all rows have been fetched. + * <p> + * This method may also be called when the statement did not produce any rows (or did not open a result set). + * </p> + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @see #statementExecuted(FbStatement, boolean, boolean) + */ + void allRowsFetched(FbStatement sender); + + /** + * Method to be notified when a statement has been executed. + * <p> + * This event with <code>hasResultSet=true</code> can be seen as the counter part of {@link #allRowsFetched(FbStatement)}. + * </p> + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @param hasResultSet + * <code>true</code> there is a result set, <code>false</code> there is no result set + * @param hasSingletonResult + * <code>true</code> singleton result, <code>false</code> statement will produce indeterminate number of rows; + * can be ignored when <code>hasResultSet</code> is false. + */ + void statementExecuted(FbStatement sender, boolean hasResultSet, boolean hasSingletonResult); + + /** + * Method to be notified when the state of a statement has changed. + * + * @param sender + * The <code>FbStatement</code> that called this method. + * @param newState + * The new state of the statement + * @param previousState + * The old state of the statement + */ + void statementStateChanged(FbStatement sender, StatementState newState, StatementState previousState); +} Copied: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java (from rev 58607, client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/RowListenerDispatcher.java) =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -0,0 +1,64 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.StatementState; +import org.firebirdsql.gds.ng.fields.FieldValue; + +import java.util.*; + +/** + * Dispatcher to maintain and notify other {@link StatementListener}. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since + */ +public final class StatementListenerDispatcher extends AbstractListenerDispatcher<StatementListener> implements StatementListener { + + @Override + public void newRow(final FbStatement sender, final List<FieldValue> rowData) { + for (StatementListener listener : this) { + listener.newRow(sender, rowData); + } + } + + @Override + public void allRowsFetched(final FbStatement sender) { + for (StatementListener listener : this) { + listener.allRowsFetched(sender); + } + } + + @Override + public void statementExecuted(final FbStatement sender, final boolean hasResultSet, final boolean hasSingletonResult) { + for (StatementListener listener : this) { + listener.statementExecuted(sender, hasResultSet, hasSingletonResult); + } + } + + @Override + public void statementStateChanged(FbStatement sender, StatementState newState, StatementState previousState) { + for (StatementListener listener : this) { + listener.statementStateChanged(sender, newState, previousState); + } + } +} Copied: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java (from rev 58461, client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionEventListener.java) =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -0,0 +1,44 @@ +/* + * $Id$ + * + * Public Firebird Java API. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbTransaction; +import org.firebirdsql.gds.ng.TransactionState; + +/** + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public interface TransactionListener { + + /** + * Signals that the transaction state changed. + * + * @param transaction {@link org.firebirdsql.gds.ng.FbTransaction} that changed state + */ + void transactionStateChanged(FbTransaction transaction, TransactionState newState, TransactionState previousState); +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -0,0 +1,39 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbTransaction; +import org.firebirdsql.gds.ng.TransactionState; + +/** + * Dispatcher to maintain and notify other {@link TransactionListener}. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public class TransactionListenerDispatcher extends AbstractListenerDispatcher<TransactionListener> implements TransactionListener { + @Override + public void transactionStateChanged(FbTransaction transaction, TransactionState newState, TransactionState previousState) { + for (TransactionListener listener : this) { + listener.transactionStateChanged(transaction, newState, previousState); + } + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -31,6 +31,7 @@ import org.firebirdsql.gds.impl.wire.Xdrable; import org.firebirdsql.gds.ng.*; import org.firebirdsql.gds.ng.fields.BlrCalculator; +import org.firebirdsql.gds.ng.listeners.TransactionListener; import org.firebirdsql.gds.ng.wire.*; import org.firebirdsql.jdbc.FBDriverNotCapableException; import org.firebirdsql.jdbc.FBSQLException; @@ -52,7 +53,7 @@ * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @since 2.3 */ -public class V10Database extends AbstractFbWireDatabase implements FbWireDatabase, TransactionEventListener { +public class V10Database extends AbstractFbWireDatabase implements FbWireDatabase, TransactionListener { private static final Logger log = LoggerFactory.getLogger(V10Database.class, false); Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -196,7 +196,7 @@ // Reset statement information reset(option == ISCConstants.DSQL_drop); } catch (IOException e) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(e).toSQLException(); } } @@ -271,7 +271,7 @@ } } catch (SQLException ex) { if (getState() == StatementState.ALLOCATED) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); } throw ex; } @@ -346,12 +346,12 @@ sendExecuteToBuffer(performExecute2 ? WireProtocolConstants.op_execute2 : WireProtocolConstants.op_execute, parameters); getXdrOut().flush(); } catch (IOException ex) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { final boolean hasResultSet = getFieldDescriptor() != null && getFieldDescriptor().getCount() > 0; - rowListenerDispatcher.statementExecuted(this, hasResultSet, performExecute2); + statementListenerDispatcher.statementExecuted(this, hasResultSet, performExecute2); if (performExecute2) { processStoredProcedureExecuteResponse(getDatabase().readSqlResponse()); setAllRowsFetched(true); @@ -360,9 +360,9 @@ // TODO .NET implementation retrieves affected rows here - setState(StatementState.EXECUTED); + switchState(StatementState.EXECUTED); } catch (IOException ex) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -453,13 +453,13 @@ sendFetchToBuffer(fetchSize); getXdrOut().flush(); } catch (IOException ex) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { processFetchResponse(); } catch (IOException ex) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -608,11 +608,6 @@ } } - @Override - public void execute(final String statementText) throws SQLException { - //To change body of implemented methods use File | Settings | File Templates. - } - /** * Allocates a statement handle on the server * @@ -628,13 +623,13 @@ sendAllocateToBuffer(); getXdrOut().flush(); } catch (IOException ex) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { processAllocateResponse(getDatabase().readGenericResponse()); } catch (IOException ex) { - setState(StatementState.ERROR); + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -664,7 +659,7 @@ synchronized (getSynchronizationObject()) { setHandle(response.getObjectHandle()); setAllRowsFetched(false); - setState(StatementState.ALLOCATED); + switchState(StatementState.ALLOCATED); setType(StatementType.NONE); } } Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Deleted: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleRowListener.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleRowListener.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleRowListener.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -1,74 +0,0 @@ -/* - * $Id$ - * - * Firebird Open Source J2EE Connector - JDBC Driver - * - * Distributable under LGPL license. - * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * LGPL License for more details. - * - * This file was created by members of the firebird development team. - * All individual contributions remain the Copyright (C) of those - * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. - * - * All rights reserved. - */ -package org.firebirdsql.gds.ng.wire; - -import org.firebirdsql.gds.ng.FbStatement; -import org.firebirdsql.gds.ng.fields.FieldValue; -import org.firebirdsql.gds.ng.listeners.RowListener; - -import java.util.ArrayList; -import java.util.List; - -/** - * Simple implementation of {@link RowListener} for testing purposes - * - * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> - * @since 2.3 - */ -public class SimpleRowListener implements RowListener { - - private final List<List<FieldValue>> rows = new ArrayList<List<FieldValue>>(); - private Boolean allRowsFetched; - private Boolean hasResultSet; - private Boolean hasSingletonResult; - - @Override - public void newRow(FbStatement sender, List<FieldValue> rowData) { - rows.add(rowData); - } - - @Override - public void allRowsFetched(FbStatement sender) { - allRowsFetched = true; - } - - @Override - public void statementExecuted(FbStatement sender, boolean hasResultSet, boolean hasSingletonResult) { - this.hasResultSet = hasResultSet; - this.hasSingletonResult = hasSingletonResult; - } - - public Boolean isAllRowsFetched() { - return allRowsFetched; - } - - public Boolean hasResultSet() { - return hasResultSet; - } - - public Boolean hasSingletonResult() { - return hasSingletonResult; - } - - public List<List<FieldValue>> getRows() { - return rows; - } -} Copied: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java (from rev 58607, client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleRowListener.java) =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java (rev 0) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -0,0 +1,80 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.wire; + +import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.StatementState; +import org.firebirdsql.gds.ng.fields.FieldValue; +import org.firebirdsql.gds.ng.listeners.StatementListener; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple implementation of {@link org.firebirdsql.gds.ng.listeners.StatementListener} for testing purposes + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public class SimpleStatementListener implements StatementListener { + + private final List<List<FieldValue>> rows = new ArrayList<List<FieldValue>>(); + private Boolean allRowsFetched; + private Boolean hasResultSet; + private Boolean hasSingletonResult; + + @Override + public void newRow(FbStatement sender, List<FieldValue> rowData) { + rows.add(rowData); + } + + @Override + public void allRowsFetched(FbStatement sender) { + allRowsFetched = true; + } + + @Override + public void statementExecuted(FbStatement sender, boolean hasResultSet, boolean hasSingletonResult) { + this.hasResultSet = hasResultSet; + this.hasSingletonResult = hasSingletonResult; + } + + @Override + public void statementStateChanged(FbStatement sender, StatementState newState, StatementState previousState) { + // unused for now + } + + public Boolean isAllRowsFetched() { + return allRowsFetched; + } + + public Boolean hasResultSet() { + return hasResultSet; + } + + public Boolean hasSingletonResult() { + return hasSingletonResult; + } + + public List<List<FieldValue>> getRows() { + return rows; + } +} Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-12 09:18:59 UTC (rev 58608) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-12 10:12:15 UTC (rev 58609) @@ -36,7 +36,7 @@ import org.firebirdsql.gds.ng.fields.RowDescriptor; import org.firebirdsql.gds.ng.wire.FbWireDatabase; import org.firebirdsql.gds.ng.wire.ProtocolCollection; -import org.firebirdsql.gds.ng.wire.SimpleRowListener; +import org.firebirdsql.gds.ng.wire.SimpleStatementListener; import org.firebirdsql.gds.ng.wire.WireConnection; import org.firebirdsql.management.FBManager; import org.junit.*; @@ -131,20 +131,20 @@ "SELECT RDB$DESCRIPTION AS \"Description\", RDB$RELATION_ID, RDB$SECURITY_CLASS, RDB$CHARACTER_SET_NAME " + "FROM RDB$DATABASE"); - final SimpleRowListener rowListener = new SimpleRowListener(); - statement.addRowListener(rowListener); + final SimpleStatementListener statementListener = new SimpleStatementListener(); + statement.addStatementListener(statementListener); statement.execute(Collections.<FieldValue>emptyList()); - assertEquals("Expected hasResultSet to be set to true", Boolean.TRUE, rowListener.hasResultSet()); - assertEquals("Expected hasSingletonResult to be set to false", Boolean.FALSE, rowListener.hasSingletonResult()); - assertNull("Expected allRowsFetched not set yet", rowListener.isAllRowsFetched()); - assertEquals("Expected no rows to be fetched yet", 0, rowListener.getRows().size()); + assertEquals("Expected hasResultSet to be set to true", Boolean.TRUE, statementListener.hasResultSet()); + assertEquals("Expected hasSingletonResult to be set to false", Boolean.FALSE, statementListener.hasSingletonResult()); + assertNull("Expected allRowsFetched not set yet", statementListener.isAllRowsFetched()); + assertEquals("Expected no rows to be fetched yet", 0, statementListener.getRows().size()); statement.fetchRows(10); - assertEquals("Expected allRowsFetched to be set to true", Boolean.TRUE, rowListener.isAllRowsFetched()); - assertEquals("Expected a single row to have been fetched", 1, rowListener.getRows().size()); + assertEquals("Expected allRowsFetched to be set to true", Boolean.TRUE, statementListener.isAllRowsFetched()); + assertEquals("Expected a single row to have been fetched", 1, statementListener.getRows().size()); statement.close(); } @@ -196,22 +196,22 @@ FieldValue param1 = new FieldValue(descriptor.getFieldDescriptor(0), new byte[] { 0, 0, 0, 3 }); // int = 3 (id of UNICODE_FSS) FieldValue param2 = new FieldValue(descriptor.getFieldDescriptor(1), new byte[] { 0, 0, 0, 1 }); // int = 1 (single byte character sets) - final SimpleRowListener rowListener = new SimpleRowListener(); - statement.addRowListener(rowListener); + final SimpleStatementListener statementListener = new SimpleStatementListener(); + statement.addStatementListener(statementListener); statement.execute(Arrays.asList(param1, param2)); - assertEquals("Expected hasResultSet to be set to true", Boolean.TRUE, rowListener.hasResultSet()); - assertEquals("Expected hasSingletonResult to be set to false", Boolean.FALSE, rowListener.hasSingletonResult()); - assertNull("Expected allRowsFetched not set yet", rowListener.isAllRowsFetched()); - assertEquals("Expected no rows to be fetched yet", 0, rowListener.getRows().size()); + assertEquals("Expected hasResultSet to be set to true", Boolean.TRUE, statementListener.hasResultSet()); + assertEquals("Expected hasSingletonResult to be set to false", Boolean.FALSE, statementListener.hasSingletonResult()); + assertNull("Expected allRowsFetched not set yet", statementListener.isAllRowsFetched()); + assertEquals("Expected no rows to be fetched yet", 0, statementListener.getRows().size()); // 100 should be sufficient to fetch all character sets statement.fetchRows(100); - assertEquals("Expected allRowsFetched to be set to true", Boolean.TRUE, rowListener.isAllRowsFetched()); + assertEquals("Expected allRowsFetched to be set to true", Boolean.TRUE, statementListener.isAllRowsFetched()); // Number is database dependent (unicode_fss + all single byte character sets) - assertTrue("Expected more than two rows", rowListener.getRows().size() > 2); + assertTrue("Expected more than two rows", statementListener.getRows().size() > 2); statement.close(); } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-09-15 14:15:40
|
Revision: 58622 http://sourceforge.net/p/firebird/code/58622 Author: mrotteveel Date: 2013-09-15 14:15:36 +0000 (Sun, 15 Sep 2013) Log Message: ----------- Make allocate statement public + changes on handling results Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementType.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-15 00:32:13 UTC (rev 58621) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-15 14:15:36 UTC (rev 58622) @@ -29,10 +29,8 @@ import java.sql.SQLException; import java.sql.SQLNonTransientException; import java.sql.SQLTransientException; -import java.util.Collections; import java.util.EnumSet; import java.util.List; -import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** @@ -44,7 +42,7 @@ private final Object syncObject = new Object(); protected final StatementListenerDispatcher statementListenerDispatcher = new StatementListenerDispatcher(); private volatile boolean allRowsFetched = false; - private AtomicReference<StatementState> state = new AtomicReference<StatementState>(StatementState.CLOSED); + private AtomicReference<StatementState> state = new AtomicReference<StatementState>(StatementState.NEW); private volatile StatementType type = StatementType.NONE; private volatile RowDescriptor parameterDescriptor; private volatile RowDescriptor fieldDescriptor; @@ -71,44 +69,34 @@ @Override public void close() throws SQLException { + if (getState() == StatementState.CLOSED) return; synchronized (getSynchronizationObject()) { - if (getState() == StatementState.CLOSED) return; // TODO do additional checks (see also old implementation and .NET) try { - free(ISCConstants.DSQL_drop); + if (getState() != StatementState.NEW) { + free(ISCConstants.DSQL_drop); + } } finally { - statementListenerDispatcher.removeAllListeners(); switchState(StatementState.CLOSED); setType(StatementType.NONE); + statementListenerDispatcher.removeAllListeners(); } } } - /** - * StatementState values indicating that cursor is closed - * TODO Should also include ALLOCATED and PREPARED? - */ - protected static final Set<StatementState> STATE_CURSOR_CLOSED = - Collections.unmodifiableSet(EnumSet.of(StatementState.CLOSED, StatementState.IDLE)); - - /** - * StatementType values that can have a cursor - */ - protected static final Set<StatementType> TYPE_HAS_CURSOR = - Collections.unmodifiableSet(EnumSet.of(StatementType.SELECT, StatementType.SELECT_FOR_UPDATE, StatementType.STORED_PROCEDURE)); - @Override - public void closeCursor() throws SQLException { + public final void closeCursor() throws SQLException { synchronized (getSynchronizationObject()) { - if (STATE_CURSOR_CLOSED.contains(getState())) return; + if (!getState().isCursorOpen()) return; // TODO do additional checks (see also old implementation and .NET) try { - if (TYPE_HAS_CURSOR.contains(getType())) { + if (getType().isTypeWithCursor()) { free(ISCConstants.DSQL_close); } } finally { // TODO Close in case of exception? - switchState(StatementState.IDLE); + // TODO Any statement types that cannot be prepared and would need to go to ALLOCATED? + switchState(StatementState.PREPARED); } } } @@ -126,7 +114,7 @@ */ protected final void switchState(final StatementState newState) throws SQLException { // TODO Check valid transition? - final StatementState currentState = this.state.get(); + final StatementState currentState = state.get(); if (state.compareAndSet(currentState, newState)) { statementListenerDispatcher.statementStateChanged(this, newState, currentState); } else { @@ -229,8 +217,7 @@ } @Override - public final RowDescriptor getParameterDescriptor() throws SQLException { - checkStatementOpen(); + public final RowDescriptor getParameterDescriptor() { return parameterDescriptor; } @@ -247,8 +234,7 @@ } @Override - public final RowDescriptor getFieldDescriptor() throws SQLException { - checkStatementOpen(); + public final RowDescriptor getFieldDescriptor() { return fieldDescriptor; } @@ -352,7 +338,7 @@ @Override public final void addStatementListener(StatementListener statementListener) { - // TODO What to do after statement close? + if (getState() == StatementState.CLOSED) return; statementListenerDispatcher.addListener(statementListener); } @@ -361,18 +347,26 @@ statementListenerDispatcher.removeListener(statementListener); } + private static final EnumSet<StatementState> STATEMENT_CLOSED = EnumSet.of(StatementState.NEW, StatementState.CLOSED); + /** - * Checks if this statement is not in {@link StatementState#CLOSED}, and throws an <code>SQLException</code> if it is. + * Checks if this statement is not in {@link StatementState#CLOSED}, {@link StatementState#NEW} or {@link StatementState#ERROR}, + * and throws an <code>SQLException</code> if it is. * * @throws SQLException - * When this statement is closed. + * When this statement is closed or in error state. */ - protected final void checkStatementOpen() throws SQLException { - if (getState() == StatementState.CLOSED) { + protected final void checkStatementValid() throws SQLException { + if (STATEMENT_CLOSED.contains(getState())) { // TODO Externalize sqlstate // TODO See if there is a firebird error code matching this (isc_cursor_not_open is not exactly the same) - throw new SQLException("Statement closed", "24000"); + throw new SQLNonTransientException("Statement closed", "24000"); } + if (getState() == StatementState.ERROR) { + // TODO SQLState? + // TODO See if there is a firebird error code matching this + throw new SQLNonTransientException("Statement is in error state and needs to be closed"); + } } @Override Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-15 00:32:13 UTC (rev 58621) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-15 14:15:36 UTC (rev 58622) @@ -45,6 +45,14 @@ FbTransaction getTransaction() throws SQLException; /** + * Allocate a statement handle for this statement on the server. + * + * @throws SQLException + * If a database access error occurs, or if the statement has been allocated already. + */ + void allocateStatement() throws SQLException; + + /** * Associates a transaction with this statement * * @param transaction @@ -56,12 +64,12 @@ /** * @return descriptor of the parameters of this statement */ - RowDescriptor getParameterDescriptor() throws SQLException; + RowDescriptor getParameterDescriptor(); /** * @return descriptor of the fields returned by this statement */ - RowDescriptor getFieldDescriptor() throws SQLException; + RowDescriptor getFieldDescriptor(); /** * @return The statement type @@ -98,7 +106,7 @@ * * @param statementText * Statement text - * @throws SQLException + * @throws SQLException If a database access error occurs, or if no statement handle as been allocated, or this statement is currently executing a query. */ void prepare(String statementText) throws SQLException; @@ -146,14 +154,16 @@ /** * Registers a {@link org.firebirdsql.gds.ng.listeners.StatementListener}. * - * @param statementListener The row listener + * @param statementListener + * The row listener */ void addStatementListener(StatementListener statementListener); /** * Removes a {@link org.firebirdsql.gds.ng.listeners.StatementListener}. * - * @param statementListener The row listener + * @param statementListener + * The row listener */ void removeStatementListener(StatementListener statementListener); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-15 00:32:13 UTC (rev 58621) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-15 14:15:36 UTC (rev 58622) @@ -33,10 +33,13 @@ * @since 2.3 */ public enum StatementState { - // TODO Add state 'NEW' to distinguish between close() called, or newly constructed statement? /** - * Statement is closed or has been deallocated + * Statement is new and no statement handle has been allocated on the server. */ + NEW, + /** + * Statement is closed or has been de-allocated + */ CLOSED, /** * Statement has been allocated @@ -47,16 +50,41 @@ */ PREPARED, /** - * Statement has been executed + * A statement is being executed, this is an ephemeral state that should only last as long as the execute call to the database takes. */ - EXECUTED, + EXECUTING, /** - * Statement has been executed, and its cursor has been closed. Last statement executed is still prepared - * TODO: Merge with prepared? + * Statement has been executed, cursor is still open */ - IDLE, + EXECUTED { + @Override + public boolean isCursorOpen() { + return true; + } + }, /** * Last statement execute or prepare resulted in an error */ - ERROR + ERROR { + /** + * {@inheritDoc} + * <p> + * When in error state, a cursor might be open (or not), as we don't know how we + * transitioned into this state. + * </p> + */ + @Override + public boolean isCursorOpen() { + return true; + } + }; + + /** + * Can a cursor be open in the current state? + * + * @return <code>true</code> a cursor can be open in this state + */ + public boolean isCursorOpen() { + return false; + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementType.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementType.java 2013-09-15 00:32:13 UTC (rev 58621) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementType.java 2013-09-15 14:15:36 UTC (rev 58622) @@ -36,18 +36,33 @@ public enum StatementType { NONE(0), - SELECT(ISCConstants.isc_info_sql_stmt_select), + SELECT(ISCConstants.isc_info_sql_stmt_select) { + @Override + public boolean isTypeWithCursor() { + return true; + } + }, INSERT(ISCConstants.isc_info_sql_stmt_insert), UPDATE(ISCConstants.isc_info_sql_stmt_update), DELETE(ISCConstants.isc_info_sql_stmt_delete), DDL(ISCConstants.isc_info_sql_stmt_ddl), GET_SEGMENT(ISCConstants.isc_info_sql_stmt_get_segment), PUT_SEGMENT(ISCConstants.isc_info_sql_stmt_put_segment), - STORED_PROCEDURE(ISCConstants.isc_info_sql_stmt_exec_procedure), + STORED_PROCEDURE(ISCConstants.isc_info_sql_stmt_exec_procedure) { + @Override + public boolean isTypeWithSingletonResult() { + return true; + } + }, START_TRANSACTION(ISCConstants.isc_info_sql_stmt_start_trans), COMMIT(ISCConstants.isc_info_sql_stmt_commit), ROLLBACK(ISCConstants.isc_info_sql_stmt_rollback), - SELECT_FOR_UPDATE(ISCConstants.isc_info_sql_stmt_select_for_upd), + SELECT_FOR_UPDATE(ISCConstants.isc_info_sql_stmt_select_for_upd) { + @Override + public boolean isTypeWithCursor() { + return true; + } + }, SET_GENERATOR(ISCConstants.isc_info_sql_stmt_set_generator), SAVE_POINT(ISCConstants.isc_info_sql_stmt_savepoint); @@ -80,6 +95,30 @@ } /** + * Indicates whether this statement type has a cursor. + * <p> + * Implementation assumes that this is the same for all Firebird versions. + * </p> + * + * @return <code>true</code> statement type has a cursor. + */ + public boolean isTypeWithCursor() { + return false; + } + + /** + * Indicates whether this statement type will produce a singleton result. + * <p> + * Implementation assumes that this is the same for all Firebird versions. + * </p> + * + * @return <code>true</code> statement type will produce a singleton result. + */ + public boolean isTypeWithSingletonResult() { + return false; + } + + /** * Gets the enum value matching statementTypeCode. * * @param statementTypeCode Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-15 00:32:13 UTC (rev 58621) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-15 14:15:36 UTC (rev 58622) @@ -28,6 +28,7 @@ import org.firebirdsql.gds.ng.fields.BlrCalculator; import org.firebirdsql.gds.ng.fields.FieldDescriptor; import org.firebirdsql.gds.ng.fields.FieldValue; +import org.firebirdsql.gds.ng.fields.RowDescriptor; import org.firebirdsql.gds.ng.wire.*; import org.firebirdsql.jdbc.FBSQLException; @@ -40,10 +41,13 @@ /** * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 */ public class V10Statement extends AbstractFbWireStatement implements FbWireStatement { - // TODO Handle error state in a consistent way + // TODO Handle error state in a consistent way (eg when does an exception lead to the error state, or when is it 'just' valid feedback) + // TODO Fix state transitions + // TODO Statement warnings? private static final int NULL_INDICATOR_NOT_NULL = 0; private static final int NULL_INDICATOR_NULL = -1; @@ -113,7 +117,7 @@ synchronized (getSynchronizationObject()) { synchronized (getDatabase().getSynchronizationObject()) { try { - sendInfoSqlToBuffer(requestItems, bufferLength); + sendInfoSql(requestItems, bufferLength); getXdrOut().flush(); } catch (IOException ex) { throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); @@ -137,7 +141,7 @@ * @throws IOException * @throws SQLException */ - protected void sendInfoSqlToBuffer(final byte[] requestItems, final int bufferLength) throws IOException, SQLException { + protected void sendInfoSql(final byte[] requestItems, final int bufferLength) throws IOException, SQLException { synchronized (getDatabase().getSynchronizationObject()) { final XdrOutputStream xdrOut = getXdrOut(); xdrOut.writeInt(WireProtocolConstants.op_info_sql); @@ -161,9 +165,6 @@ @Override protected void free(final int option) throws SQLException { - if (freeNotNeeded(option)) { - return; - } synchronized (getSynchronizationObject()) { synchronized (getDatabase().getSynchronizationObject()) { try { @@ -191,12 +192,11 @@ protected void doFreePacket(int option) throws SQLException { synchronized (getDatabase().getSynchronizationObject()) { try { - sendFreeToBuffer(option); + sendFree(option); // Reset statement information reset(option == ISCConstants.DSQL_drop); } catch (IOException e) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(e).toSQLException(); } } @@ -210,7 +210,7 @@ * @throws IOException * @throws SQLException */ - protected void sendFreeToBuffer(int option) throws IOException, SQLException { + protected void sendFree(int option) throws IOException, SQLException { synchronized (getDatabase().getSynchronizationObject()) { final XdrOutputStream xdrOut = getXdrOut(); xdrOut.writeInt(WireProtocolConstants.op_free_statement); @@ -231,50 +231,42 @@ // No processing needed } + private static final EnumSet<StatementState> PREPARE_ALLOWED_STATES = EnumSet.of(StatementState.ALLOCATED, StatementState.PREPARED); + /** - * Decides whether sending free is actually required. + * Is a call to {@link #prepare(String)} allowed for the supplied {@link StatementState}. * - * @param option - * Free statement option - * @return <code>true</code> when freeing is needed, <code>false</code> otherwise + * @param state The statement state + * @return <code>true</code> call to <code>prepare</code> is allowed */ - protected boolean freeNotNeeded(final int option) { - // Does not seem to be possible or necessary to close an execute procedure statement. - // TODO Verify if this is correct - return getType() == StatementType.STORED_PROCEDURE && option == ISCConstants.DSQL_close; + protected boolean isPrepareAllowed(final StatementState state) { + return PREPARE_ALLOWED_STATES.contains(state); } @Override public void prepare(final String statementText) throws SQLException { synchronized (getSynchronizationObject()) { - // TODO Do we actually need a transaction for the prepare? + checkStatementValid(); if (getTransaction() == null || getTransaction().getState() != TransactionState.ACTIVE) { throw new SQLNonTransientException("No transaction or transaction not ACTIVE", FBSQLException.SQL_STATE_INVALID_TX_STATE); } - reset(true); + final StatementState currentState = getState(); + if (!isPrepareAllowed(currentState)) { + throw new SQLNonTransientException(String.format("Current statement state (%s) does not allow call to prepare", currentState)); + } + resetAll(); synchronized (getDatabase().getSynchronizationObject()) { try { - if (getState() == StatementState.CLOSED) { - allocateStatement(); - } - - try { - sendPrepareToBuffer(statementText); - getXdrOut().flush(); - } catch (IOException ex) { - throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); - } - try { - processPrepareResponse(getDatabase().readGenericResponse()); - } catch (IOException ex) { - throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); - } - } catch (SQLException ex) { - if (getState() == StatementState.ALLOCATED) { - switchState(StatementState.ERROR); - } - throw ex; + sendPrepare(statementText); + getXdrOut().flush(); + } catch (IOException ex) { + throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } + try { + processPrepareResponse(getDatabase().readGenericResponse()); + } catch (IOException ex) { + throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); + } } } } @@ -287,7 +279,7 @@ * @throws SQLException * @throws IOException */ - protected void sendPrepareToBuffer(final String statementText) throws SQLException, IOException { + protected void sendPrepare(final String statementText) throws SQLException, IOException { synchronized (getDatabase().getSynchronizationObject()) { final XdrOutputStream xdrOut = getXdrOut(); xdrOut.writeInt(WireProtocolConstants.op_prepare_statement); @@ -331,38 +323,37 @@ @Override public void execute(final List<FieldValue> parameters) throws SQLException { - if (getState() == StatementState.CLOSED) { - // TODO Throw correct error, sqlstate, etc - throw new SQLException("Invalid statement state"); - } + checkStatementValid(); // TODO Validate transaction state? synchronized (getSynchronizationObject()) { validateParameters(parameters); reset(false); - final boolean performExecute2 = getType() == StatementType.STORED_PROCEDURE; // TODO Records affected? synchronized (getDatabase().getSynchronizationObject()) { + // TODO Which state to switch to when an exception occurs (always ERROR might be wrong, see to do at start of class) + switchState(StatementState.EXECUTING); try { - sendExecuteToBuffer(performExecute2 ? WireProtocolConstants.op_execute2 : WireProtocolConstants.op_execute, parameters); + sendExecute(getType().isTypeWithSingletonResult() ? WireProtocolConstants.op_execute2 : WireProtocolConstants.op_execute, parameters); getXdrOut().flush(); } catch (IOException ex) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { - final boolean hasResultSet = getFieldDescriptor() != null && getFieldDescriptor().getCount() > 0; - statementListenerDispatcher.statementExecuted(this, hasResultSet, performExecute2); - if (performExecute2) { - processStoredProcedureExecuteResponse(getDatabase().readSqlResponse()); + final boolean hasFields = getFieldDescriptor() != null && getFieldDescriptor().getCount() > 0; + if (getType().isTypeWithSingletonResult()) { + // A type with a singleton result (ie an execute procedure), doesn't actually have a result set that will be fetched, instead we have a singleton result if we have fields + statementListenerDispatcher.statementExecuted(this, false, hasFields); + processExecuteSingletonResponse(getDatabase().readSqlResponse()); setAllRowsFetched(true); + } else { + // A normal execute is never a singleton result (even if it only produces a single result) + statementListenerDispatcher.statementExecuted(this, hasFields, false); } processExecuteResponse(getDatabase().readGenericResponse()); // TODO .NET implementation retrieves affected rows here - - switchState(StatementState.EXECUTED); + switchState(getType().isTypeWithCursor() ? StatementState.EXECUTED : StatementState.PREPARED); } catch (IOException ex) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -379,7 +370,7 @@ * @throws IOException * @throws SQLException */ - protected void sendExecuteToBuffer(final int operation, final List<FieldValue> parameters) throws IOException, SQLException { + protected void sendExecute(final int operation, final List<FieldValue> parameters) throws IOException, SQLException { assert operation == WireProtocolConstants.op_execute || operation == WireProtocolConstants.op_execute2 : "Needs to be called with operation op_execute or op_execute2"; synchronized (getDatabase().getSynchronizationObject()) { final XdrOutputStream xdrOut = getXdrOut(); @@ -387,9 +378,9 @@ xdrOut.writeInt(getHandle()); xdrOut.writeInt(getTransaction().getHandle()); - // TODO What if 0 parameters? - if (getParameterDescriptor() != null) { - xdrOut.writeBuffer(calculateBlr(getParameterDescriptor())); + final RowDescriptor parameterDescriptor = getParameterDescriptor(); + if (parameterDescriptor != null && parameterDescriptor.getCount() > 0) { + xdrOut.writeBuffer(calculateBlr(parameterDescriptor)); xdrOut.writeInt(0); // message number = in_message_type xdrOut.writeInt(1); // Number of messages writeSqlData(parameters); @@ -400,24 +391,24 @@ } if (operation == WireProtocolConstants.op_execute2) { - // TODO What if 0 fields? - xdrOut.writeBuffer(getFieldDescriptor() != null ? calculateBlr(getFieldDescriptor()) : null); + final RowDescriptor fieldDescriptor = getFieldDescriptor(); + xdrOut.writeBuffer(fieldDescriptor != null && fieldDescriptor.getCount() > 0 ? calculateBlr(fieldDescriptor) : null); xdrOut.writeInt(0); // out_message_number = out_message_type } } } /** - * Process the execute response for stored procedures (<code>op_execute2</code>. + * Process the execute response for statements with a singleton response (<code>op_execute2</code>; stored procedures). * * @param sqlResponse * SQL response object * @throws SQLException * @throws IOException */ - protected void processStoredProcedureExecuteResponse(SqlResponse sqlResponse) throws SQLException, IOException { + protected void processExecuteSingletonResponse(SqlResponse sqlResponse) throws SQLException, IOException { if (sqlResponse.getCount() > 0) { - queueRowData(readRowData()); + queueRowData(readSqlData()); } } @@ -431,35 +422,25 @@ // Nothing to do here } - // TODO Validate that other types don't return rows - protected static final EnumSet<StatementType> STATEMENT_TYPES_WITH_ROWS = EnumSet.of(StatementType.SELECT, StatementType.SELECT_FOR_UPDATE); - @Override public void fetchRows(int fetchSize) throws SQLException { synchronized (getSynchronizationObject()) { - checkStatementOpen(); - if (getState() != StatementState.EXECUTED) { - // TODO Check if this state is sufficient + checkStatementValid(); + if (!getState().isCursorOpen()) { throw new FbExceptionBuilder().exception(ISCConstants.isc_cursor_not_open).toSQLException(); } - if (!STATEMENT_TYPES_WITH_ROWS.contains(getType())) { - // TODO Throw exception instead? - return; - } if (isAllRowsFetched()) return; synchronized (getDatabase().getSynchronizationObject()) { try { - sendFetchToBuffer(fetchSize); + sendFetch(fetchSize); getXdrOut().flush(); } catch (IOException ex) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { processFetchResponse(); } catch (IOException ex) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -478,9 +459,10 @@ while (!isAllRowsFetched() && (response = getDatabase().readResponse()) instanceof FetchResponse) { final FetchResponse fetchResponse = (FetchResponse) response; if (fetchResponse.getCount() > 0 && fetchResponse.getStatus() == WireProtocolConstants.FETCH_OK) { - queueRowData(readRowData()); + queueRowData(readSqlData()); } else if (fetchResponse.getStatus() == WireProtocolConstants.FETCH_NO_MORE_ROWS) { setAllRowsFetched(true); + // TODO Close cursor if everything has been fetched? } else { // TODO Log, raise exception, or simply 'not possible'? break; @@ -490,13 +472,13 @@ } /** - * Sends the fetch requestion to the database. + * Sends the fetch request to the database. * * @param fetchSize Number of rows to fetch. * @throws SQLException * @throws IOException */ - protected void sendFetchToBuffer(int fetchSize) throws SQLException, IOException { + protected void sendFetch(int fetchSize) throws SQLException, IOException { synchronized (getDatabase().getSynchronizationObject()) { final XdrOutputStream xdrOut = getXdrOut(); xdrOut.writeInt(WireProtocolConstants.op_fetch); @@ -514,7 +496,7 @@ * @throws SQLException * @throws IOException */ - protected List<FieldValue> readRowData() throws SQLException, IOException { + protected List<FieldValue> readSqlData() throws SQLException, IOException { final List<FieldValue> rowData = new ArrayList<FieldValue>(getFieldDescriptor().getCount()); final BlrCalculator blrCalculator = getDatabase().getBlrCalculator(); @@ -591,7 +573,7 @@ // increment happens in BlrCalculator.calculateIoLength len--; if (buffer != null) { - int buflen = buffer.length; + final int buflen = buffer.length; if (buflen >= len) { xdrOut.write(buffer, 0, len, (4 - len) & 3); } else { @@ -608,28 +590,22 @@ } } - /** - * Allocates a statement handle on the server - * - * @throws SQLException - */ - protected void allocateStatement() throws SQLException { - if (getState() != StatementState.CLOSED) { + @Override + public void allocateStatement() throws SQLException { + if (getState() != StatementState.NEW) { // TODO Is there a better sqlstate? - throw new SQLNonTransientException("allocateStatement only allowed when current state is CLOSED", FBSQLException.SQL_STATE_GENERAL_ERROR); + throw new SQLNonTransientException("allocateStatement only allowed when current state is NEW", FBSQLException.SQL_STATE_GENERAL_ERROR); } synchronized (getDatabase().getSynchronizationObject()) { try { - sendAllocateToBuffer(); + sendAllocate(); getXdrOut().flush(); } catch (IOException ex) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { processAllocateResponse(getDatabase().readGenericResponse()); } catch (IOException ex) { - switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -641,7 +617,7 @@ * @throws SQLException * @throws IOException */ - protected void sendAllocateToBuffer() throws SQLException, IOException { + protected void sendAllocate() throws SQLException, IOException { synchronized (getDatabase().getSynchronizationObject()) { final XdrOutputStream xdrOut = getXdrOut(); xdrOut.writeInt(WireProtocolConstants.op_allocate_statement); @@ -655,7 +631,7 @@ * @param response * GenericResponse */ - protected void processAllocateResponse(GenericResponse response) { + protected void processAllocateResponse(GenericResponse response) throws SQLException { synchronized (getSynchronizationObject()) { setHandle(response.getObjectHandle()); setAllRowsFetched(false); Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-15 00:32:13 UTC (rev 58621) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-15 14:15:36 UTC (rev 58622) @@ -20,7 +20,9 @@ */ package org.firebirdsql.gds.ng.wire.version10; +import org.firebirdsql.common.DdlHelper; import org.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.JdbcResourceHelper; import org.firebirdsql.encodings.EncodingFactory; import org.firebirdsql.gds.ISCConstants; import org.firebirdsql.gds.TransactionParameterBuffer; @@ -42,6 +44,7 @@ import org.junit.*; import org.junit.rules.ExpectedException; +import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLNonTransientException; import java.util.Arrays; @@ -58,6 +61,39 @@ */ public class TestV10Statement { + private static final String CREATE_EXECUTABLE_STORED_PROCEDURE = + "CREATE PROCEDURE increment " + + " (intvalue INTEGER) " + + "RETURNS " + + " (outvalue INTEGER) " + + "AS " + + "BEGIN " + + " outvalue = intvalue + 1; " + + "END"; + + private static final String EXECUTE_EXECUTABLE_STORED_PROCEDURE = + "EXECUTE PROCEDURE INCREMENT(?)"; + + private static final String CREATE_SELECTABLE_STORED_PROCEDURE = + "CREATE PROCEDURE range " + + " (startvalue INTEGER, rowcount INTEGER) " + + "RETURNS " + + " (outvalue INTEGER) " + + "AS " + + "DECLARE VARIABLE actualcount INTEGER; " + + "BEGIN " + + " actualcount = 0; " + + " WHILE (actualcount < rowcount) DO " + + " BEGIN " + + " outvalue = startvalue + actualcount; " + + " suspend; " + + " actualcount = actualcount + 1; " + + " END " + + "END"; + + private static final String EXECUTE_SELECTABLE_STORED_PROCEDURE = + "SELECT OUTVALUE FROM RANGE(?, ?)"; + private final FbConnectionProperties connectionInfo; private FbWireDatabase db; FBManager fbManager; @@ -86,6 +122,14 @@ @Before public void setUp() throws Exception { fbManager = defaultDatabaseSetUp(); + Connection con = FBTestProperties.getConnectionViaDriverManager(); + try { + DdlHelper.executeDDL(con, CREATE_EXECUTABLE_STORED_PROCEDURE, new int[] {}); + DdlHelper.executeDDL(con, CREATE_SELECTABLE_STORED_PROCEDURE, new int[] {}); + } finally { + JdbcResourceHelper.closeQuietly(con); + } + WireConnection gdsConnection = new WireConnection(connectionInfo, EncodingFactory.getDefaultInstance(), ProtocolCollection.create(new Version10Descriptor())); gdsConnection.socketConnect(); db = gdsConnection.identify(); @@ -95,9 +139,10 @@ } @Test - public void testSelect_NoParameters_Describe() throws Exception { + public void testSelect_NoParameters_Describe() throws Exception { final FbTransaction transaction = getTransaction(); final FbStatement statement = db.createStatement(); + statement.allocateStatement(); statement.setTransaction(transaction); statement.prepare( "SELECT RDB$DESCRIPTION AS \"Description\", RDB$RELATION_ID, RDB$SECURITY_CLASS, RDB$CHARACTER_SET_NAME " + @@ -126,6 +171,7 @@ public void testSelect_NoParameters_Execute_and_Fetch() throws Exception { final FbTransaction transaction = getTransaction(); final FbStatement statement = db.createStatement(); + statement.allocateStatement(); statement.setTransaction(transaction); statement.prepare( "SELECT RDB$DESCRIPTION AS \"Description\", RDB$RELATION_ID, RDB$SECURITY_CLASS, RDB$CHARACTER_SET_NAME " + @@ -153,6 +199,7 @@ public void testSelect_WithParameters_Describe() throws Exception { final FbTransaction transaction = getTransaction(); final FbStatement statement = db.createStatement(); + statement.allocateStatement(); statement.setTransaction(transaction); statement.prepare( "SELECT a.RDB$CHARACTER_SET_NAME " + @@ -186,6 +233,7 @@ public void testSelect_WithParameters_Execute_and_Fetch() throws Exception { final FbTransaction transaction = getTransaction(); final FbStatement statement = db.createStatement(); + statement.allocateStatement(); statement.setTransaction(transaction); statement.prepare( "SELECT a.RDB$CHARACTER_SET_NAME " + @@ -219,16 +267,75 @@ // TODO Test with executable stored procedure @Test - public void testAllocate_NotClosed() throws Exception { + public void testAllocate_NotNew() throws Exception { final V10Statement statement = (V10Statement) db.createStatement(); statement.allocateStatement(); expectedException.expect(SQLNonTransientException.class); - expectedException.expectMessage("allocateStatement only allowed when current state is CLOSED"); + expectedException.expectMessage("allocateStatement only allowed when current state is NEW"); statement.allocateStatement(); } + @Test + public void test_PrepareExecutableStoredProcedure() throws Exception { + final FbTransaction transaction = getTransaction(); + final FbStatement statement = db.createStatement(); + statement.allocateStatement(); + statement.setTransaction(transaction); + statement.prepare(EXECUTE_EXECUTABLE_STORED_PROCEDURE); + + assertEquals("Unexpected StatementType", StatementType.STORED_PROCEDURE, statement.getType()); + + final RowDescriptor fields = statement.getFieldDescriptor(); + assertNotNull("Fields", fields); + List<FieldDescriptor> expectedFields = + Arrays.asList( + new FieldDescriptor(ISCConstants.SQL_LONG | 1, 0, 0, 4, "OUTVALUE", null, "OUTVALUE", "INCREMENT", "SYSDBA") + ); + assertEquals("Unexpected values for fields", expectedFields, fields.getFieldDescriptors()); + + final RowDescriptor parameters = statement.getParameterDescriptor(); + assertNotNull("Parameters", parameters); + List<FieldDescriptor> expectedParameters = + Arrays.asList( + new FieldDescriptor(ISCConstants.SQL_LONG | 1, 0, 0, 4, null, null, null, null, null) + ); + assertEquals("Unexpected values for parameters", expectedParameters, parameters.getFieldDescriptors()); + + statement.close(); + } + + @Test + public void test_PrepareSelectableStoredProcedure() throws Exception { + final FbTransaction transaction = getTransaction(); + final FbStatement statement = db.createStatement(); + statement.allocateStatement(); + statement.setTransaction(transaction); + statement.prepare(EXECUTE_SELECTABLE_STORED_PROCEDURE); + + assertEquals("Unexpected StatementType", StatementType.SELECT, statement.getType()); + + final RowDescriptor fields = statement.getFieldDescriptor(); + assertNotNull("Fields", fields); + List<FieldDescriptor> expectedFields = + Arrays.asList( + new FieldDescriptor(ISCConstants.SQL_LONG | 1, 0, 0, 4, "OUTVALUE", null, "OUTVALUE", "RANGE", "SYSDBA") + ); + assertEquals("Unexpected values for fields", expectedFields, fields.getFieldDescriptors()); + + final RowDescriptor parameters = statement.getParameterDescriptor(); + assertNotNull("Parameters", parameters); + List<FieldDescriptor> expectedParameters = + Arrays.asList( + new FieldDescriptor(ISCConstants.SQL_LONG | 1, 0, 0, 4, null, null, null, null, null), + new FieldDescriptor(ISCConstants.SQL_LONG | 1, 0, 0, 4, null, null, null, null, null) + ); + assertEquals("Unexpected values for parameters", expectedParameters, parameters.getFieldDescriptors()); + + statement.close(); + } + private FbTransaction getTransaction() throws SQLException { TransactionParameterBuffer tpb = new TransactionParameterBufferImpl(); tpb.addArgument(ISCConstants.isc_tpb_read_committed); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-09-18 11:13:07
|
Revision: 58631 http://sourceforge.net/p/firebird/code/58631 Author: mrotteveel Date: 2013-09-18 11:13:02 +0000 (Wed, 18 Sep 2013) Log Message: ----------- Various changes to transaction, statement and database events + other improvements to the new protocol implementation Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionState.java client-java/trunk/src/main/org/firebirdsql/gds/ng/WarningMessageCallback.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireTransaction.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Transaction.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListenerDispatcher.java client-java/trunk/src/test/org/firebirdsql/gds/ng/SimpleDatabaseListener.java Removed Paths: ------------- client-java/trunk/src/test/org/firebirdsql/gds/ng/SimpleWarningMessageCallback.java Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -0,0 +1,117 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng; + +import org.firebirdsql.gds.ng.listeners.DatabaseListener; +import org.firebirdsql.gds.ng.listeners.DatabaseListenerDispatcher; + +import java.sql.SQLException; +import java.sql.SQLWarning; + +/** + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public abstract class AbstractFbDatabase implements FbDatabase { + + protected final DatabaseListenerDispatcher databaseListenerDispatcher = new DatabaseListenerDispatcher(); + private final WarningMessageCallback warningCallback = new WarningMessageCallback() { + @Override + public void processWarning(SQLWarning warning) { + databaseListenerDispatcher.warningReceived(AbstractFbDatabase.this, warning); + } + }; + private short databaseDialect; + + /** + * @return The warning callback for this database. + */ + protected final WarningMessageCallback getDatabaseWarningCallback() { + return warningCallback; + } + + @Override + public final short getDatabaseDialect() { + return databaseDialect; + } + + /** + * Sets the dialect of the database. + * <p> + * This method should only be called by this instance. + * </p> + * + * @param dialect + * Dialect of the database/connection + */ + protected final void setDatabaseDialect(short dialect) { + this.databaseDialect = dialect; + } + + @Override + public final void addDatabaseListener(DatabaseListener listener) { + // TODO Don't register if closed? + databaseListenerDispatcher.addListener(listener); + } + + @Override + public final void removeDatabaseListener(DatabaseListener listener) { + databaseListenerDispatcher.removeListener(listener); + } + + /** + * Checks if the database is connected, and throws a {@link SQLException} if it isn't connected. + */ + protected abstract void checkConnected() throws SQLException; + + /** + * Actual implementation of database detach. + * <p> + * Implementations of this method should only be called from {@link #detach()}, and should not notify database + * listeners of the database {@link DatabaseListener#detaching(FbDatabase)} and + * {@link DatabaseListener#detached(FbDatabase)} events. + * </p> + */ + protected abstract void internalDetach() throws SQLException; + + /** + * {@inheritDoc} + * <p> + * Implementation note: Calls {@link #checkConnected()} and notifies database listeners of the detaching event, then + * calls {@link #internalDetach()} and finally notifies database listeners of database detach and removes all listeners. + * </p> + */ + @Override + public final void detach() throws SQLException { + checkConnected(); + synchronized (getSynchronizationObject()) { + databaseListenerDispatcher.detaching(this); + try { + internalDetach(); + } finally { + databaseListenerDispatcher.detached(this); + databaseListenerDispatcher.removeAllListeners(); + } + } + } + + // TODO Unregister all listeners on close +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -25,13 +25,15 @@ import org.firebirdsql.gds.ng.fields.RowDescriptor; import org.firebirdsql.gds.ng.listeners.StatementListener; import org.firebirdsql.gds.ng.listeners.StatementListenerDispatcher; +import org.firebirdsql.gds.ng.listeners.TransactionListener; +import org.firebirdsql.jdbc.FBSQLException; import java.sql.SQLException; import java.sql.SQLNonTransientException; import java.sql.SQLTransientException; +import java.sql.SQLWarning; import java.util.EnumSet; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; /** * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> @@ -39,15 +41,70 @@ */ public abstract class AbstractFbStatement implements FbStatement { + /** + * Set of states that will be reset to {@link StatementState#PREPARED} on transaction change + */ + private static final EnumSet<StatementState> RESET_TO_PREPARED = EnumSet.of(StatementState.EXECUTING, StatementState.EXECUTED); + private final Object syncObject = new Object(); + private final WarningMessageCallback warningCallback = new WarningMessageCallback() { + @Override + public void processWarning(SQLWarning warning) { + statementListenerDispatcher.warningReceived(AbstractFbStatement.this, warning); + } + }; protected final StatementListenerDispatcher statementListenerDispatcher = new StatementListenerDispatcher(); private volatile boolean allRowsFetched = false; - private AtomicReference<StatementState> state = new AtomicReference<StatementState>(StatementState.NEW); + private volatile StatementState state = StatementState.NEW; private volatile StatementType type = StatementType.NONE; private volatile RowDescriptor parameterDescriptor; private volatile RowDescriptor fieldDescriptor; + private volatile FbTransaction transaction; + private final TransactionListener transactionListener = new TransactionListener() { + @Override + public void transactionStateChanged(FbTransaction transaction, TransactionState newState, TransactionState previousState) { + synchronized (getSynchronizationObject()) { + try { + if (RESET_TO_PREPARED.contains(getState())) { + // Cursor has been closed due to commit, rollback, etc, back to prepared state + try { + switchState(StatementState.PREPARED); + } catch (SQLException e) { + throw new IllegalStateException("Received an SQLException when none was expected", e); + } + reset(false); + } + } finally { + transaction.removeTransactionListener(this); + try { + setTransaction(null); + } catch (SQLException e) { + throw new IllegalStateException("Received an SQLException when none was expected", e); + } + } + } + } + }; + /** + * Gets the {@link TransactionListener} instance for this statement. + * <p> + * This method should only be called by this object itself. Subclasses may provide their own transaction listener, but + * the instance returned by this method should be the same for the lifetime of this {@link FbStatement}. + * </p> + * + * @return The transaction listener instance for this statement. + */ + protected final TransactionListener getTransactionListener() { + return transactionListener; + } + + protected final WarningMessageCallback getStatementWarningCallback() { + return warningCallback; + } + + /** * Plan information items */ private static final byte[] DESCRIBE_PLAN_INFO_ITEMS = new byte[]{ @@ -80,6 +137,7 @@ switchState(StatementState.CLOSED); setType(StatementType.NONE); statementListenerDispatcher.removeAllListeners(); + setTransaction(null); } } } @@ -93,17 +151,19 @@ if (getType().isTypeWithCursor()) { free(ISCConstants.DSQL_close); } - } finally { - // TODO Close in case of exception? // TODO Any statement types that cannot be prepared and would need to go to ALLOCATED? switchState(StatementState.PREPARED); + } catch (SQLException e) { + // TODO Close in case of exception? + switchState(StatementState.ERROR); + throw e; } } } @Override public StatementState getState() { - return state.get(); + return state; } /** @@ -111,18 +171,19 @@ * * @param newState * New state + * @throws SQLException + * When the state is changed to an illegal next state */ protected final void switchState(final StatementState newState) throws SQLException { - // TODO Check valid transition? - final StatementState currentState = state.get(); - if (state.compareAndSet(currentState, newState)) { - statementListenerDispatcher.statementStateChanged(this, newState, currentState); - } else { - // Note: race condition when generating message (get() could return same value as currentState) - // TODO Include sqlstate - throw new SQLException(String.format( - "Unable to change statement state: expected current state %s, but was %s", currentState, - state.get())); + synchronized (getSynchronizationObject()) { + final StatementState currentState = state; + if (currentState == newState || currentState == StatementState.CLOSED) return; + if (currentState.isValidTransition(newState)) { + state = newState; + statementListenerDispatcher.statementStateChanged(this, newState, currentState); + } else { + throw new SQLNonTransientException(String.format("Statement state %s only allows next states %s, received %s", currentState, currentState.validTransitionSet(), newState)); + } } } @@ -212,6 +273,7 @@ if (resetAll) { setParameterDescriptor(null); setFieldDescriptor(null); + setType(StatementType.NONE); } } } @@ -276,7 +338,10 @@ * @throws SQLException * For errors retrieving or transforming the response. */ - public abstract <T> T getSqlInfo(byte[] requestItems, int bufferLength, InfoProcessor<T> infoProcessor) throws SQLException; + @Override + public final <T> T getSqlInfo(final byte[] requestItems, final int bufferLength, final InfoProcessor<T> infoProcessor) throws SQLException { + return infoProcessor.process(getSqlInfo(requestItems, bufferLength)); + } /** * Request statement info. @@ -347,8 +412,6 @@ statementListenerDispatcher.removeListener(statementListener); } - private static final EnumSet<StatementState> STATEMENT_CLOSED = EnumSet.of(StatementState.NEW, StatementState.CLOSED); - /** * Checks if this statement is not in {@link StatementState#CLOSED}, {@link StatementState#NEW} or {@link StatementState#ERROR}, * and throws an <code>SQLException</code> if it is. @@ -357,18 +420,34 @@ * When this statement is closed or in error state. */ protected final void checkStatementValid() throws SQLException { - if (STATEMENT_CLOSED.contains(getState())) { + switch (getState()) { + case NEW: + case CLOSED: // TODO Externalize sqlstate // TODO See if there is a firebird error code matching this (isc_cursor_not_open is not exactly the same) throw new SQLNonTransientException("Statement closed", "24000"); - } - if (getState() == StatementState.ERROR) { + case ERROR: // TODO SQLState? // TODO See if there is a firebird error code matching this throw new SQLNonTransientException("Statement is in error state and needs to be closed"); + default: + // Valid state, continue + break; } } + /** + * Checks if this statement has a transaction and that the transaction is {@link TransactionState#ACTIVE}. + * + * @throws SQLException + * When this statement does not have a transaction, or if that transaction is not active. + */ + protected final void checkTransactionActive() throws SQLException { + if (transaction == null || transaction.getState() != TransactionState.ACTIVE) { + throw new SQLNonTransientException("No transaction or transaction not ACTIVE", FBSQLException.SQL_STATE_INVALID_TX_STATE); + } + } + @Override protected void finalize() throws Throwable { try { @@ -377,4 +456,41 @@ super.finalize(); } } + + @Override + public final FbTransaction getTransaction() throws SQLException { + return transaction; + } + + /** + * Method to decide if a transaction implementation class is valid for the statement implementation. + * <p> + * Eg a V10Statement will only work with an FbWireTransaction implementation. + * </p> + * + * @param transactionClass + * Class of the transaction + * @return <code>true</code> when the transaction class is valid for the statement implementation. + */ + protected abstract boolean isValidTransactionClass(Class<? extends FbTransaction> transactionClass); + + @Override + public final void setTransaction(final FbTransaction newTransaction) throws SQLException { + if (newTransaction == null || isValidTransactionClass(newTransaction.getClass())) { + // TODO Is there a statement or transaction state where we should not be switching transactions? + synchronized (getSynchronizationObject()) { + if (newTransaction == transaction) return; + if (transaction != null) { + transaction.removeTransactionListener(getTransactionListener()); + } + transaction = newTransaction; + if (newTransaction != null) { + newTransaction.addTransactionListener(getTransactionListener()); + } + } + } else { + throw new SQLNonTransientException(String.format("Invalid transaction handle type, got \"%s\"", newTransaction.getClass().getName()), + FBSQLException.SQL_STATE_GENERAL_ERROR); + } + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -30,6 +30,7 @@ import org.firebirdsql.encodings.IEncodingFactory; import org.firebirdsql.gds.DatabaseParameterBuffer; import org.firebirdsql.gds.TransactionParameterBuffer; +import org.firebirdsql.gds.ng.listeners.DatabaseListener; import java.sql.SQLException; @@ -160,14 +161,6 @@ int getTransactionCount(); /** - * Sets the WarningMessageCallback for this database. - * - * @param callback - * WarningMessageCallback - */ - void setWarningMessageCallback(WarningMessageCallback callback); - - /** * Current attachment status of the database. * * @return <code>true</code> if connected to the server and attached to a @@ -204,7 +197,23 @@ /** * @return The connection encoding (should be the same as returned from calling {@link org.firebirdsql.encodings.IEncodingFactory#getDefaultEncoding()} - * on the result of {@link #getEncodingFactory()}. + * on the result of {@link #getEncodingFactory()}. */ Encoding getEncoding(); + + /** + * Adds a {@link DatabaseListener} instance to this database. + * + * @param listener + * Database listener + */ + void addDatabaseListener(DatabaseListener listener); + + /** + * Removes a {@link DatabaseListener} instance from this database. + * + * @param listener + * Database Listener + */ + void removeDatabaseListener(DatabaseListener listener); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -166,4 +166,20 @@ * The row listener */ void removeStatementListener(StatementListener statementListener); + + /** + * Request statement info. + * + * @param requestItems + * Array of info items to request + * @param bufferLength + * Response buffer length to use + * @param infoProcessor + * Implementation of {@link InfoProcessor} to transform + * the info response + * @return Transformed info response of type T + * @throws SQLException + * For errors retrieving or transforming the response. + */ + <T> T getSqlInfo(final byte[] requestItems, final int bufferLength, final InfoProcessor<T> infoProcessor) throws SQLException; } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -26,6 +26,10 @@ */ package org.firebirdsql.gds.ng; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + /** * Statement states for {@link FbStatement} implementations * @@ -36,23 +40,48 @@ /** * Statement is new and no statement handle has been allocated on the server. */ - NEW, + NEW { + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(ERROR, ALLOCATED, CLOSED, NEW); + } + }, /** * Statement is closed or has been de-allocated */ - CLOSED, + CLOSED { + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(CLOSED); + } + }, /** * Statement has been allocated */ - ALLOCATED, + ALLOCATED { + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(ERROR, PREPARED, CLOSED); + } + }, /** * Statement has been prepared */ - PREPARED, + PREPARED { + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(ERROR, EXECUTING, CLOSED, PREPARED); + } + }, /** * A statement is being executed, this is an ephemeral state that should only last as long as the execute call to the database takes. */ - EXECUTING, + EXECUTING { + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(ERROR, EXECUTED, PREPARED, CLOSED); + } + }, /** * Statement has been executed, cursor is still open */ @@ -61,6 +90,11 @@ public boolean isCursorOpen() { return true; } + + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(ERROR, PREPARED, CLOSED); + } }, /** * Last statement execute or prepare resulted in an error @@ -77,8 +111,15 @@ public boolean isCursorOpen() { return true; } + + @Override + EnumSet<StatementState> createValidTransitionSet() { + return EnumSet.of(ERROR, CLOSED); + } }; + private Set<StatementState> validTransitions; + /** * Can a cursor be open in the current state? * @@ -87,4 +128,32 @@ public boolean isCursorOpen() { return false; } + + /** + * Is the transition to <code>toState</code> valid from this state. + * + * @param toState + * The next state + * @return <code>true</code> transition is valid + */ + public final boolean isValidTransition(StatementState toState) { + return validTransitionSet().contains(toState); + } + + /** + * @return Set of valid transitions from this state + */ + public final Set<StatementState> validTransitionSet() { + if (validTransitions == null) { + validTransitions = Collections.unmodifiableSet(createValidTransitionSet()); + } + return validTransitions; + } + + /** + * Create the set of valid transitions. + * + * @return Set of valid transitions from this state + */ + abstract Set<StatementState> createValidTransitionSet(); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionState.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionState.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/TransactionState.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -26,7 +26,9 @@ */ package org.firebirdsql.gds.ng; +import java.util.Collections; import java.util.EnumSet; +import java.util.Set; /** * Transactions states. @@ -37,24 +39,51 @@ public enum TransactionState { NO_TRANSACTION { @Override - public boolean isValidTransition(TransactionState toState) { - return EnumSet.of(ACTIVE, NO_TRANSACTION).contains(toState); + Set<TransactionState> createValidTransitionSet() { + return EnumSet.of(ACTIVE, NO_TRANSACTION); } }, ACTIVE { @Override - public boolean isValidTransition(TransactionState toState) { + Set<TransactionState> createValidTransitionSet() { // TODO Verify if these are the supported transitions - return EnumSet.of(ACTIVE, PREPARED, NO_TRANSACTION).contains(toState); + return EnumSet.of(ACTIVE, PREPARED, NO_TRANSACTION); } }, PREPARED { @Override - public boolean isValidTransition(TransactionState toState) { + Set<TransactionState> createValidTransitionSet() { // TODO Verify if these are the supported transitions - return EnumSet.of(ACTIVE, PREPARED, NO_TRANSACTION).contains(toState); + return EnumSet.of(ACTIVE, PREPARED, NO_TRANSACTION); } }; - - public abstract boolean isValidTransition(TransactionState toState); + + private Set<TransactionState> validTransitions; + + /** + * Is the transition to <code>toState</code> valid from this state. + * + * @param toState The next state + * @return <code>true</code> transition is valid + */ + public final boolean isValidTransition(TransactionState toState) { + return validTransitionSet().contains(toState); + } + + /** + * @return Set of valid transitions from this state + */ + public final Set<TransactionState> validTransitionSet() { + if (validTransitions == null) { + validTransitions = Collections.unmodifiableSet(createValidTransitionSet()); + } + return validTransitions; + } + + /** + * Create the set of valid transitions. + * + * @return Set of valid transitions from this state + */ + abstract Set<TransactionState> createValidTransitionSet(); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/WarningMessageCallback.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/WarningMessageCallback.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/WarningMessageCallback.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -37,15 +37,6 @@ public interface WarningMessageCallback { /** - * Dummy WarningMessageCallback that does nothing - */ - WarningMessageCallback DUMMY = new WarningMessageCallback() { - @Override - public void processWarning(SQLWarning warning) { - } - }; - - /** * Signals the warning to the callback * * @param warning Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListener.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListener.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -0,0 +1,71 @@ +/* + * $Id$ + * + * Public Firebird Java API. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbDatabase; + +import java.sql.SQLWarning; + +/** + * Listener for database events + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public interface DatabaseListener { + + /** + * Called before the <code>database</code> will be detached. + * <p> + * This event is intended for cleanup action, implementer should take care that + * no exceptions are thrown from this method. + * </p> + */ + void detaching(FbDatabase database); + + /** + * Called when the <code>database</code> connection has been detached + * + * @param database + * The database object that was detached + */ + void detached(FbDatabase database); + + /** + * Called when a warning was received for the <code>database</code> connection. + * <p> + * In implementation it is possible that some warnings are not sent to listeners on the database, but only to listeners on + * specific connection derived objects (like an {@link org.firebirdsql.gds.ng.FbStatement} implementation). + * </p> + * + * @param database + * Database receiving the warning + * @param warning + * Warning + */ + void warningReceived(FbDatabase database, SQLWarning warning); +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListener.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListenerDispatcher.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListenerDispatcher.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng.listeners; + +import org.firebirdsql.gds.ng.FbDatabase; + +import java.sql.SQLWarning; + +/** + * Dispatcher to maintain and notify other {@link DatabaseListener}. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public class DatabaseListenerDispatcher extends AbstractListenerDispatcher<DatabaseListener> implements DatabaseListener { + + @Override + public void detaching(FbDatabase database) { + for (DatabaseListener listener : this) { + try { + listener.detached(database); + } catch (Exception e) { + // Ignore // TODO: log + } + } + } + + @Override + public void detached(FbDatabase database) { + for (DatabaseListener listener : this) { + try { + listener.detached(database); + } catch (Exception e) { + // Ignore // TODO: log + } + } + } + + @Override + public void warningReceived(FbDatabase database, SQLWarning warning) { + for (DatabaseListener listener : this) { + try { + listener.warningReceived(database, warning); + } catch (Exception e) { + // Ignore // TODO: log + } + } + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/DatabaseListenerDispatcher.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -30,6 +30,7 @@ import org.firebirdsql.gds.ng.StatementState; import org.firebirdsql.gds.ng.fields.FieldValue; +import java.sql.SQLWarning; import java.util.List; /** @@ -89,5 +90,16 @@ * @param previousState * The old state of the statement */ + // TODO Replace with (or add) specific events? void statementStateChanged(FbStatement sender, StatementState newState, StatementState previousState); + + /** + * Called when a warning was received for the <code>sender</code> statement. + * + * @param sender + * Statement receiving the warning + * @param warning + * Warning + */ + void warningReceived(FbStatement sender, SQLWarning warning); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -24,41 +24,69 @@ import org.firebirdsql.gds.ng.StatementState; import org.firebirdsql.gds.ng.fields.FieldValue; +import java.sql.SQLWarning; import java.util.*; /** * Dispatcher to maintain and notify other {@link StatementListener}. * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> - * @since + * @since 2.3 */ public final class StatementListenerDispatcher extends AbstractListenerDispatcher<StatementListener> implements StatementListener { @Override public void newRow(final FbStatement sender, final List<FieldValue> rowData) { for (StatementListener listener : this) { - listener.newRow(sender, rowData); + try { + listener.newRow(sender, rowData); + } catch (Exception e) { + // Ignore // TODO: log + } } } @Override public void allRowsFetched(final FbStatement sender) { for (StatementListener listener : this) { - listener.allRowsFetched(sender); + try { + listener.allRowsFetched(sender); + } catch (Exception e) { + // Ignore // TODO: log + } } } @Override public void statementExecuted(final FbStatement sender, final boolean hasResultSet, final boolean hasSingletonResult) { for (StatementListener listener : this) { - listener.statementExecuted(sender, hasResultSet, hasSingletonResult); + try { + listener.statementExecuted(sender, hasResultSet, hasSingletonResult); + } catch (Exception e) { + // Ignore // TODO: log + } } } @Override public void statementStateChanged(FbStatement sender, StatementState newState, StatementState previousState) { for (StatementListener listener : this) { - listener.statementStateChanged(sender, newState, previousState); + try { + listener.statementStateChanged(sender, newState, previousState); + } catch (Exception e) { + // Ignore // TODO: log + } } } + + @Override + public void warningReceived(FbStatement sender, SQLWarning warning) { + for (StatementListener listener : this) { + try { + listener.warningReceived(sender, warning); + } catch (Exception e) { + // Ignore // TODO: log + } + } + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListener.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -40,5 +40,6 @@ * * @param transaction {@link org.firebirdsql.gds.ng.FbTransaction} that changed state */ + // TODO Replace with (or add) specific events? void transactionStateChanged(FbTransaction transaction, TransactionState newState, TransactionState previousState); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/TransactionListenerDispatcher.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -33,7 +33,11 @@ @Override public void transactionStateChanged(FbTransaction transaction, TransactionState newState, TransactionState previousState) { for (TransactionListener listener : this) { - listener.transactionStateChanged(transaction, newState, previousState); + try { + listener.transactionStateChanged(transaction, newState, previousState); + } catch (Exception e) { + // Ignore // TODO: log + } } } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireDatabase.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -24,6 +24,7 @@ import org.firebirdsql.encodings.IEncodingFactory; import org.firebirdsql.gds.impl.wire.XdrInputStream; import org.firebirdsql.gds.impl.wire.XdrOutputStream; +import org.firebirdsql.gds.ng.AbstractFbDatabase; import java.sql.SQLException; import java.util.concurrent.atomic.AtomicBoolean; @@ -32,14 +33,13 @@ * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @since 2.3 */ -public abstract class AbstractFbWireDatabase implements FbWireDatabase { +public abstract class AbstractFbWireDatabase extends AbstractFbDatabase implements FbWireDatabase { protected final AtomicBoolean attached = new AtomicBoolean(); protected final ProtocolDescriptor protocolDescriptor; protected final WireConnection connection; private final XdrStreamHolder xdrStreamHolder; private final Object syncObject = new Object(); - private short databaseDialect; /** * Creates a V10Database instance. @@ -92,22 +92,4 @@ public final boolean isAttached() { return attached.get() && connection.isConnected(); } - - @Override - public final short getDatabaseDialect() { - return databaseDialect; - } - - /** - * Sets the dialect of the database. - * <p> - * This method should only be called by this instance. - * </p> - * - * @param dialect - * Dialect of the database/connection - */ - protected final void setDatabaseDialect(short dialect) { - this.databaseDialect = dialect; - } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -25,14 +25,11 @@ import org.firebirdsql.gds.ng.AbstractFbStatement; import org.firebirdsql.gds.ng.FbTransaction; import org.firebirdsql.gds.ng.fields.RowDescriptor; -import org.firebirdsql.jdbc.FBSQLException; import java.sql.SQLException; -import java.sql.SQLNonTransientException; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; -import java.util.concurrent.atomic.AtomicReference; /** * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> @@ -40,7 +37,6 @@ */ public abstract class AbstractFbWireStatement extends AbstractFbStatement implements FbWireStatement { - private final AtomicReference<FbTransaction> transaction = new AtomicReference<FbTransaction>(); private final Map<RowDescriptor, byte[]> blrCache = Collections.synchronizedMap(new WeakHashMap<RowDescriptor, byte[]>()); private volatile int handle; private final XdrStreamHolder xdrStreamHolder; @@ -64,24 +60,6 @@ } @Override - public FbTransaction getTransaction() throws SQLException { - return transaction.get(); - } - - @Override - public void setTransaction(FbTransaction transaction) throws SQLException { - if (!(transaction instanceof FbWireTransaction)) { - throw new SQLNonTransientException(String.format("Invalid transaction handle, expected instance of FbWireTransaction, got \"%s\"", transaction.getClass().getName()), - FBSQLException.SQL_STATE_GENERAL_ERROR); - } - // TODO Needs synchronization? - // TODO Is there a statement or transaction state where we should not be switching transactions? - if (transaction == this.transaction.get()) return; - this.transaction.set(transaction); - // TODO Implement + add transaction listener - } - - @Override public final int getHandle() { return handle; } @@ -111,15 +89,20 @@ return blr; } + @Override public void close() throws SQLException { synchronized (getSynchronizationObject()) { try { super.close(); } finally { database = null; - transaction.set(null); blrCache.clear(); } } } + + @Override + protected boolean isValidTransactionClass(Class<? extends FbTransaction> transactionClass) { + return FbWireTransaction.class.isAssignableFrom(transactionClass); + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -27,6 +27,7 @@ package org.firebirdsql.gds.ng.wire; import org.firebirdsql.gds.ng.FbDatabase; +import org.firebirdsql.gds.ng.WarningMessageCallback; import org.firebirdsql.gds.ng.fields.BlrCalculator; import java.io.IOException; @@ -42,13 +43,15 @@ /** * Reads the response from the server. * + * @param callback + * Callback object for warnings, <code>null</code> for default callback * @return {@link Response} read. * @throws SQLException * For errors returned from the server, or when attempting to read * @throws IOException * For errors reading the response from the connection. */ - Response readResponse() throws SQLException, IOException; + Response readResponse(WarningMessageCallback callback) throws SQLException, IOException; /** * Release object. TODO Review usage @@ -64,6 +67,8 @@ /** * Convenience method to read a Response to a GenericResponse * + * @param callback + * Callback object for warnings, <code>null</code> for default callback * @return GenericResponse * @throws SQLException * For errors returned from the server, or when attempting to @@ -71,11 +76,13 @@ * @throws IOException * For errors reading the response from the connection. */ - GenericResponse readGenericResponse() throws SQLException, IOException; + GenericResponse readGenericResponse(WarningMessageCallback callback) throws SQLException, IOException; /** * Convenience method to read a Response to a SqlResponse * + * @param callback + * Callback object for warnings, <code>null</code> for default callback * @return SqlResponse * @throws SQLException * For errors returned from the server, or when attempting to @@ -83,7 +90,7 @@ * @throws IOException * For errors reading the response from the connection. */ - SqlResponse readSqlResponse() throws SQLException, IOException; + SqlResponse readSqlResponse(WarningMessageCallback callback) throws SQLException, IOException; /** * @return The {@link BlrCalculator} instance for this database. Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireTransaction.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireTransaction.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireTransaction.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -29,7 +29,7 @@ import org.firebirdsql.gds.ng.FbTransaction; /** - * Interface for transactions created for the wire protocol implementaton. + * Interface for transactions created for the wire protocol implementation. * * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> * @since 2.3 Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -58,13 +58,6 @@ private static final Logger log = LoggerFactory.getLogger(V10Database.class, false); private final AtomicInteger transactionCount = new AtomicInteger(); - /** - * Callback for warnings. Only change using {@link #setWarningMessageCallback(org.firebirdsql.gds.ng.WarningMessageCallback)}. - * <p> - * Should never be null. - * </p> - */ - private WarningMessageCallback warningCallback = WarningMessageCallback.DUMMY; private int handle; private int odsMajor; private int odsMinor; @@ -151,13 +144,6 @@ } @Override - public void setWarningMessageCallback(WarningMessageCallback callback) { - synchronized (getSynchronizationObject()) { - warningCallback = callback != null ? callback : WarningMessageCallback.DUMMY; - } - } - - @Override public void attach() throws SQLException { final DatabaseParameterBuffer dpb = protocolDescriptor.createDatabaseParameterBuffer(connection); attachOrCreate(dpb, false); @@ -186,7 +172,7 @@ throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(e).toSQLException(); } try { - processAttachOrCreateResponse(readGenericResponse()); + processAttachOrCreateResponse(readGenericResponse(null)); } catch (IOException e) { throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(e).toSQLException(); } @@ -262,8 +248,7 @@ } @Override - public void detach() throws SQLException { - checkConnected(); + protected void internalDetach() throws SQLException { synchronized (getSynchronizationObject()) { if (getTransactionCount() > 0) { // Register open transactions as warning, we are going to detach and close the connection anyway @@ -271,11 +256,11 @@ // TODO: Rollback transactions? FbExceptionBuilder builder = new FbExceptionBuilder(); builder.warning(ISCConstants.isc_open_trans).messageParameter(getTransactionCount()); - warningCallback.processWarning(builder.toSQLException(SQLWarning.class)); + getDatabaseWarningCallback().processWarning(builder.toSQLException(SQLWarning.class)); } - final XdrOutputStream xdrOut = getXdrOut(); try { + final XdrOutputStream xdrOut = getXdrOut(); if (attached.get()) { xdrOut.writeInt(op_detach); xdrOut.writeInt(getHandle()); @@ -324,7 +309,6 @@ connection.disconnect(); } finally { attached.set(false); - setWarningMessageCallback(null); } } } @@ -349,7 +333,7 @@ throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ioex).toSQLException(); } try { - readResponse(); + readResponse(null); } catch (IOException ioex) { throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ioex).toSQLException(); } @@ -412,7 +396,7 @@ throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { - GenericResponse genericResponse = readGenericResponse(); + GenericResponse genericResponse = readGenericResponse(null); byte[] data = genericResponse.getData(); int responseLength = Math.min(maxBufferLength, data.length); // TODO Can't we just return data? @@ -426,8 +410,8 @@ } @Override - public Response readResponse() throws SQLException, IOException { - Response response = readSingleResponse(); + public Response readResponse(WarningMessageCallback warningCallback) throws SQLException, IOException { + Response response = readSingleResponse(warningCallback); processResponse(response); return response; } @@ -443,7 +427,7 @@ throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { - processReleaseObjectResponse(readResponse()); + processReleaseObjectResponse(readResponse(null)); } catch (IOException ex) { throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } @@ -478,13 +462,13 @@ } @Override - public GenericResponse readGenericResponse() throws SQLException, IOException { - return (GenericResponse) readResponse(); + public GenericResponse readGenericResponse(WarningMessageCallback warningCallback) throws SQLException, IOException { + return (GenericResponse) readResponse(warningCallback); } @Override - public SqlResponse readSqlResponse() throws SQLException, IOException { - return (SqlResponse) readResponse(); + public SqlResponse readSqlResponse(WarningMessageCallback warningCallback) throws SQLException, IOException { + return (SqlResponse) readResponse(warningCallback); } @Override @@ -498,6 +482,7 @@ /** * Reads the response from the server. * + * @param warningCallback Callback object for signalling warnings, <code>null</code> to register warning on the default callback * @return Response * @throws SQLException * For errors returned from the server, or when attempting to @@ -505,9 +490,9 @@ * @throws IOException * For errors reading the response from the connection. */ - protected Response readSingleResponse() throws SQLException, IOException { + protected Response readSingleResponse(WarningMessageCallback warningCallback) throws SQLException, IOException { Response response = processOperation(readNextOperation()); - processResponseWarnings(response); + processResponseWarnings(response, warningCallback); return response; } @@ -534,7 +519,10 @@ * @param response * Response to process */ - protected void processResponseWarnings(Response response) { + protected void processResponseWarnings(final Response response, WarningMessageCallback warningCallback) { + if (warningCallback == null) { + warningCallback = getDatabaseWarningCallback(); + } if (response instanceof GenericResponse) { GenericResponse genericResponse = (GenericResponse) response; SQLException exception = genericResponse.getException(); @@ -847,6 +835,7 @@ * @throws SQLException * If not connected. */ + @Override protected final void checkConnected() throws SQLException { if (!connection.isConnected()) { // TODO Update message / externalize Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-18 09:37:59 UTC (rev 58630) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-18 11:13:02 UTC (rev 58631) @@ -47,7 +47,6 @@ // TODO Handle error state in a consistent way (eg when does an exception lead to the error state, or when is it 'just' valid feedback) // TODO Fix state transitions - // TODO Statement warnings? private static final int NULL_INDICATOR_NOT_NULL = 0; private static final int NULL_INDICATOR_NULL = -1; @@ -108,11 +107,6 @@ } @Override - public <T> T getSqlInfo(final byte[] requestItems, final int bufferLength, final InfoProcessor<T> infoProcessor) throws SQLException { - return infoProcessor.process(getSqlInfo(requestItems, bufferLength)); - } - - @Override public byte[] getSqlInfo(final byte[] requestItems, final int bufferLength) throws SQLException { synchronized (getSynchronizationObject()) { synchronized (getDatabase().getSynchronizationObject()) { @@ -120,11 +114,13 @@ sendInfoSql(requestItems, bufferLength); getXdrOut().flush(); } catch (IOException ex) { + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { - return processInfoSqlResponse(getDatabase().readGenericResponse()); + return processInfoSqlResponse(getDatabase().readGenericResponse(getStatementWarningCallback())); } catch (IOException ex) { + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -171,11 +167,13 @@ doFreePacket(option); getXdrOut().flush(); } catch (IOException ex) { + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(ex).toSQLException(); } try { - processFreeResponse(getDatabase().readResponse()); + processFreeResponse(getDatabase().readResponse(getStatementWarningCallback())); } catch (IOException ex) { + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_read_err).cause(ex).toSQLException(); } } @@ -197,6 +195,7 @@ // Reset statement information reset(option == ISCConstants.DSQL_drop); } catch (IOException e) { + switchState(StatementState.ERROR); throw new FbExceptionBuilder().exception(ISCConstants.isc_net_write_err).cause(e).toSQLException(); } } @@ -247,9 +246,7 @@ public void prepare(final String statementText) throws SQLException { synchronized (getSynchronizationObject()) { checkStatementValid(); - if (getTransaction() == null || getTransaction().getState() != TransactionState.ACTIVE) { - throw new SQLNonTransientException("No transaction or transaction not ACTIVE", FBSQLException.SQL_STA... [truncated message content] |
From: <mro...@us...> - 2013-09-19 11:08:14
|
Revision: 58643 http://sourceforge.net/p/firebird/code/58643 Author: mrotteveel Date: 2013-09-19 11:08:11 +0000 (Thu, 19 Sep 2013) Log Message: ----------- Execution plan retrieval Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/encodings/Encoding.java client-java/trunk/src/main/org/firebirdsql/encodings/EncodingGeneric.java client-java/trunk/src/main/org/firebirdsql/encodings/EncodingSingleByte.java client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireStatement.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/gds/ng/ExecutionPlanProcessor.java Modified: client-java/trunk/src/main/org/firebirdsql/encodings/Encoding.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/encodings/Encoding.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/encodings/Encoding.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -53,6 +53,19 @@ String decodeFromCharset(byte[] in); /** + * Decodes a part of the supplied byte array to a String. + * + * @param in + * byte array to decode + * @param offset + * Offset into the byte array + * @param length + * Length in bytes to decode + * @return String after decoding the byte array + */ + String decodeFromCharset(byte[] in, int offset, int length); + + /** * Derives an {@link Encoding} that applies the specified character translation * * @param translator Modified: client-java/trunk/src/main/org/firebirdsql/encodings/EncodingGeneric.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/encodings/EncodingGeneric.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/encodings/EncodingGeneric.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -51,6 +51,11 @@ } @Override + public String decodeFromCharset(final byte[] in, final int offset, final int length) { + return new String(in, offset, length, charset); + } + + @Override public Encoding withTranslation(final CharacterTranslator translator) { return new EncodingGenericWithTranslation(translator); } @@ -78,10 +83,16 @@ @Override public String decodeFromCharset(final byte[] in) { - String result = EncodingGeneric.this.decodeFromCharset(in); + final String result = EncodingGeneric.this.decodeFromCharset(in); return new String(translate(result.toCharArray())); } + @Override + public String decodeFromCharset(final byte[] in, final int off, final int len) { + final String result = EncodingGeneric.this.decodeFromCharset(in, off, len); + return new String(translate(result.toCharArray())); + } + /** * In a slight deviation from the contract of {@link Encoding#withTranslation(CharacterTranslator)}, * this implementation returns the result of the parent implementation {@link Modified: client-java/trunk/src/main/org/firebirdsql/encodings/EncodingSingleByte.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/encodings/EncodingSingleByte.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/encodings/EncodingSingleByte.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -59,9 +59,13 @@ @Override public String decodeFromCharset(final byte[] in) { - final int length = in.length; + return decodeFromCharset(in, 0, in.length); + } + + @Override + public String decodeFromCharset(final byte[] in, final int offset, final int length) { final char[] bufferC = new char[length]; - for (int i = 0; i < length; i++) { + for (int i = offset, limit = offset + length; i < limit; i++) { bufferC[i] = byteToChar[in[i] & 0xFF]; } return new String(bufferC); Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -105,13 +105,6 @@ } /** - * Plan information items - */ - private static final byte[] DESCRIBE_PLAN_INFO_ITEMS = new byte[]{ - ISCConstants.isc_info_sql_get_plan - }; - - /** * Records affected items * TODO: Compare with current implementation */ @@ -204,10 +197,6 @@ } } - public byte[] getDescribePlanInfoItems() { - return DESCRIBE_PLAN_INFO_ITEMS.clone(); - } - public byte[] getRowsAffectedInfoItems() { return ROWS_AFFECTED_INFO_ITEMS.clone(); } @@ -343,17 +332,19 @@ return infoProcessor.process(getSqlInfo(requestItems, bufferLength)); } + @Override + public final String getExecutionPlan() throws SQLException { + checkStatementValid(); + final ExecutionPlanProcessor processor = createExecutionPlanProcessor(); + return getSqlInfo(processor.getDescribePlanInfoItems(), getDefaultSqlInfoSize(), processor); + } + /** - * Request statement info. - * - * @param requestItems - * Array of info items to request - * @param bufferLength - * Response buffer length to use - * @return Response buffer - * @throws SQLException + * @return New instance of {@link ExecutionPlanProcessor} (or subclass) for this statement. */ - public abstract byte[] getSqlInfo(byte[] requestItems, int bufferLength) throws SQLException; + protected ExecutionPlanProcessor createExecutionPlanProcessor() { + return new ExecutionPlanProcessor(this); + } /** * Frees the currently allocated statement (either close the cursor with {@link ISCConstants#DSQL_close} or drop the statement @@ -422,6 +413,9 @@ protected final void checkStatementValid() throws SQLException { switch (getState()) { case NEW: + // TODO Externalize sqlstate + // TODO See if there is a firebird error code matching this (isc_cursor_not_open is not exactly the same) + throw new SQLNonTransientException("Statement not yet allocated", "24000"); case CLOSED: // TODO Externalize sqlstate // TODO See if there is a firebird error code matching this (isc_cursor_not_open is not exactly the same) Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/ExecutionPlanProcessor.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/ExecutionPlanProcessor.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/ExecutionPlanProcessor.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -0,0 +1,86 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng; + +import org.firebirdsql.gds.ISCConstants; + +import java.sql.SQLException; + +/** + * InfoProcessor to retrieve the (normal) execution plan of a statement. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public class ExecutionPlanProcessor implements InfoProcessor<String> { + + /** + * Plan information items + */ + private static final byte[] DESCRIBE_PLAN_INFO_ITEMS = new byte[]{ + ISCConstants.isc_info_sql_get_plan, + ISCConstants.isc_info_end + }; + + private final FbStatement statement; + + public ExecutionPlanProcessor(FbStatement statement) { + this.statement = statement; + } + + @Override + public String process(byte[] buffer) throws SQLException { + if (buffer[0] == ISCConstants.isc_info_end) { + return ""; + } + + if (buffer[0] == ISCConstants.isc_info_truncated) { + buffer = statement.getSqlInfo(getDescribePlanInfoItems(), statement.getMaxSqlInfoSize()); + if (buffer[0] == ISCConstants.isc_info_truncated) { + return null; + } + } + + if (buffer[0] != ISCConstants.isc_info_sql_get_plan) { + // We only expect isc_info_sql_get_plan + throw new FbExceptionBuilder().exception(ISCConstants.isc_infunk).toSQLException(); + } + + int len = statement.getDatabase().iscVaxInteger2(buffer, 1); + if (len > 1) { + // Trimming, because first character is a linefeed (0x0A) + // Not skipping to prevent (potential) encoding issues + return statement.getDatabase().getEncoding().decodeFromCharset(buffer, 3, len).trim(); + } else { + return ""; + } + } + + /** + * Get the byte array with the describe plan info items as supported by this processor, for use with + * {@link FbStatement#getSqlInfo(byte[], int, InfoProcessor)} or {@link FbStatement#getSqlInfo(byte[], int)}. + * + * @return plan info items + */ + public byte[] getDescribePlanInfoItems() { + return DESCRIBE_PLAN_INFO_ITEMS.clone(); + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/ExecutionPlanProcessor.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -216,4 +216,62 @@ * Database Listener */ void removeDatabaseListener(DatabaseListener listener); + + /** + * Reads Vax style integers from the supplied buffer, starting at + * <code>startPosition</code> and reading for <code>length</code> bytes. + * <p> + * This method is useful for lengths up to 4 bytes (ie normal Java integers + * (<code>int</code>). Use {@link #iscVaxLong(byte[], int, int)} for reading + * values with length up to 8 bytes. + * </p> + * + * @param buffer + * The byte array from which the integer is to be retrieved + * @param startPosition + * The offset starting position from which to start retrieving + * byte values + * @return The integer value retrieved from the bytes + * @see #iscVaxLong(byte[], int, int) + * @see #iscVaxInteger2(byte[], int) + */ + int iscVaxInteger(byte[] buffer, int startPosition, int length); + + /** + * Reads Vax style integers from the supplied buffer, starting at + * <code>startPosition</code> and reading for <code>length</code> bytes. + * <p> + * This method is useful for lengths up to 8 bytes (ie normal Java longs ( + * <code>long</code>). + * </p> + * + * @param buffer + * The byte array from which the integer is to be retrieved + * @param startPosition + * The offset starting position from which to start retrieving + * byte values + * @return The integer value retrieved from the bytes + * @see #iscVaxLong(byte[], int, int) + * @see #iscVaxInteger2(byte[], int) + */ + long iscVaxLong(byte[] buffer, int startPosition, int length); + + /** + * Variant of {@link #iscVaxInteger(byte[], int, int)} specifically + * for two-byte integers. + * <p> + * Implementations can either delegate to {@link #iscVaxInteger(byte[], int, int)}, + * or implement an optimized version. + * </p> + * + * @param buffer + * The byte array from which the integer is to be retrieved + * @param startPosition + * The offset starting position from which to start retrieving + * byte values + * @return The integer value retrieved from the bytes + * @see #iscVaxInteger(byte[], int, int) + * @see #iscVaxLong(byte[], int, int) + */ + int iscVaxInteger2(byte[] buffer, int startPosition); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -45,6 +45,11 @@ FbTransaction getTransaction() throws SQLException; /** + * @return The database connection that created this statement + */ + FbDatabase getDatabase() throws SQLException; + + /** * Allocate a statement handle for this statement on the server. * * @throws SQLException @@ -106,7 +111,8 @@ * * @param statementText * Statement text - * @throws SQLException If a database access error occurs, or if no statement handle as been allocated, or this statement is currently executing a query. + * @throws SQLException + * If a database access error occurs, or if no statement handle as been allocated, or this statement is currently executing a query. */ void prepare(String statementText) throws SQLException; @@ -182,4 +188,33 @@ * For errors retrieving or transforming the response. */ <T> T getSqlInfo(final byte[] requestItems, final int bufferLength, final InfoProcessor<T> infoProcessor) throws SQLException; + + /** + * Request statement info. + * + * @param requestItems + * Array of info items to request + * @param bufferLength + * Response buffer length to use + * @return Response buffer + * @throws SQLException + */ + byte[] getSqlInfo(byte[] requestItems, int bufferLength) throws SQLException; + + /** + * @return The default size to use for the sql info buffer + */ + int getDefaultSqlInfoSize(); + + /** + * @return The maximum size to use for the sql info buffer + */ + int getMaxSqlInfoSize(); + + /** + * @return The execution plan of the currently prepared statement + * @throws SQLException + * If this statement is closed. + */ + String getExecutionPlan() throws SQLException; } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/AbstractFbWireStatement.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -55,7 +55,8 @@ return xdrStreamHolder.getXdrOut(); } - protected final FbWireDatabase getDatabase() { + @Override + public final FbWireDatabase getDatabase() { return database; } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireDatabase.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -106,61 +106,4 @@ */ int readNextOperation() throws IOException; - /** - * Reads Vax style integers from the supplied buffer, starting at - * <code>startPosition</code> and reading for <code>length</code> bytes. - * <p> - * This method is useful for lengths up to 4 bytes (ie normal Java integers - * (<code>int</code>). Use {@link #iscVaxLong(byte[], int, int)} for reading - * values with length up to 8 bytes. - * </p> - * - * @param buffer - * The byte array from which the integer is to be retrieved - * @param startPosition - * The offset starting position from which to start retrieving - * byte values - * @return The integer value retrieved from the bytes - * @see #iscVaxLong(byte[], int, int) - * @see #iscVaxInteger2(byte[], int) - */ - int iscVaxInteger(byte[] buffer, int startPosition, int length); - - /** - * Reads Vax style integers from the supplied buffer, starting at - * <code>startPosition</code> and reading for <code>length</code> bytes. - * <p> - * This method is useful for lengths up to 8 bytes (ie normal Java longs ( - * <code>long</code>). - * </p> - * - * @param buffer - * The byte array from which the integer is to be retrieved - * @param startPosition - * The offset starting position from which to start retrieving - * byte values - * @return The integer value retrieved from the bytes - * @see #iscVaxLong(byte[], int, int) - * @see #iscVaxInteger2(byte[], int) - */ - long iscVaxLong(byte[] buffer, int startPosition, int length); - - /** - * Variant of {@link #iscVaxInteger(byte[], int, int)} specifically - * for two-byte integers. - * <p> - * Implementations can either delegate to {@link #iscVaxInteger(byte[], int, int)}, - * or implement an optimized version. - * </p> - * - * @param buffer - * The byte array from which the integer is to be retrieved - * @param startPosition - * The offset starting position from which to start retrieving - * byte values - * @return The integer value retrieved from the bytes - * @see #iscVaxInteger(byte[], int, int) - * @see #iscVaxLong(byte[], int, int) - */ - int iscVaxInteger2(byte[] buffer, int startPosition); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireStatement.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/FbWireStatement.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -36,13 +36,4 @@ */ public interface FbWireStatement extends FbStatement { - /** - * @return The default size to use for the sql info buffer - */ - int getDefaultSqlInfoSize(); - - /** - * @return The maximum size to use for the sql info buffer - */ - int getMaxSqlInfoSize(); } Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-19 11:05:30 UTC (rev 58642) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-19 11:08:11 UTC (rev 58643) @@ -53,7 +53,6 @@ import static org.firebirdsql.common.FBTestProperties.*; import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; /** @@ -64,42 +63,42 @@ private static final String CREATE_EXECUTABLE_STORED_PROCEDURE = "CREATE PROCEDURE increment " + - " (intvalue INTEGER) " + - "RETURNS " + - " (outvalue INTEGER) " + - "AS " + - "BEGIN " + - " outvalue = intvalue + 1; " + - "END"; + " (intvalue INTEGER) " + + "RETURNS " + + " (outvalue INTEGER) " + + "AS " + + "BEGIN " + + " outvalue = intvalue + 1; " + + "END"; private static final String EXECUTE_EXECUTABLE_STORED_PROCEDURE = "EXECUTE PROCEDURE INCREMENT(?)"; private static final String CREATE_SELECTABLE_STORED_PROCEDURE = "CREATE PROCEDURE range " + - " (startvalue INTEGER, rowcount INTEGER) " + - "RETURNS " + - " (outvalue INTEGER) " + - "AS " + - "DECLARE VARIABLE actualcount INTEGER; " + - "BEGIN " + - " actualcount = 0; " + - " WHILE (actualcount < rowcount) DO " + - " BEGIN " + - " outvalue = startvalue + actualcount; " + - " suspend; " + - " actualcount = actualcount + 1; " + - " END " + - "END"; + " (startvalue INTEGER, rowcount INTEGER) " + + "RETURNS " + + " (outvalue INTEGER) " + + "AS " + + "DECLARE VARIABLE actualcount INTEGER; " + + "BEGIN " + + " actualcount = 0; " + + " WHILE (actualcount < rowcount) DO " + + " BEGIN " + + " outvalue = startvalue + actualcount; " + + " suspend; " + + " actualcount = actualcount + 1; " + + " END " + + "END"; private static final String EXECUTE_SELECTABLE_STORED_PROCEDURE = "SELECT OUTVALUE FROM RANGE(?, ?)"; private static final String CREATE_KEY_VALUE_TABLE = "CREATE TABLE keyvalue ( " + - " thekey INTEGER, " + - " thevalue VARCHAR(100)" + - ")"; + " thekey INTEGER, " + + " thevalue VARCHAR(5)" + + ")"; private static final String INSERT_RETURNING_KEY_VALUE = "INSERT INTO keyvalue (thevalue) VALUES (?) RETURNING thekey"; @@ -134,8 +133,8 @@ fbManager = defaultDatabaseSetUp(); Connection con = FBTestProperties.getConnectionViaDriverManager(); try { - DdlHelper.executeDDL(con, CREATE_EXECUTABLE_STORED_PROCEDURE, new int[] {}); - DdlHelper.executeDDL(con, CREATE_SELECTABLE_STORED_PROCEDURE, new int[] {}); + DdlHelper.executeDDL(con, CREATE_EXECUTABLE_STORED_PROCEDURE, new int[]{ }); + DdlHelper.executeDDL(con, CREATE_SELECTABLE_STORED_PROCEDURE, new int[]{ }); DdlHelper.executeCreateTable(con, CREATE_KEY_VALUE_TABLE); } finally { JdbcResourceHelper.closeQuietly(con); @@ -252,8 +251,8 @@ "WHERE a.RDB$CHARACTER_SET_ID = ? OR a.RDB$BYTES_PER_CHARACTER = ?"); RowDescriptor descriptor = statement.getParameterDescriptor(); - FieldValue param1 = new FieldValue(descriptor.getFieldDescriptor(0), new byte[] { 0, 0, 0, 3 }); // int = 3 (id of UNICODE_FSS) - FieldValue param2 = new FieldValue(descriptor.getFieldDescriptor(1), new byte[] { 0, 0, 0, 1 }); // int = 1 (single byte character sets) + FieldValue param1 = new FieldValue(descriptor.getFieldDescriptor(0), new byte[]{ 0, 0, 0, 3 }); // int = 3 (id of UNICODE_FSS) + FieldValue param2 = new FieldValue(descriptor.getFieldDescriptor(1), new byte[]{ 0, 0, 0, 1 }); // int = 1 (single byte character sets) final SimpleStatementListener statementListener = new SimpleStatementListener(); statement.addStatementListener(statementListener); @@ -377,6 +376,58 @@ statement.close(); } + @Test + public void test_GetExecutionPlan_withStatementPrepared() throws Exception { + final FbTransaction transaction = getTransaction(); + final FbStatement statement = db.createStatement(); + statement.allocateStatement(); + statement.setTransaction(transaction); + statement.prepare( + "SELECT RDB$DESCRIPTION AS \"Description\", RDB$RELATION_ID, RDB$SECURITY_CLASS, RDB$CHARACTER_SET_NAME " + + "FROM RDB$DATABASE"); + + String executionPlan = statement.getExecutionPlan(); + + assertEquals("Unexpected plan for prepared statement", "PLAN (RDB$DATABASE NATURAL)", executionPlan); + } + + @Test + public void test_GetExecutionPlan_noStatementPrepared() throws Exception { + final FbTransaction transaction = getTransaction(); + final FbStatement statement = db.createStatement(); + statement.allocateStatement(); + statement.setTransaction(transaction); + + String executionPlan = statement.getExecutionPlan(); + + assertEquals("Unexpected plan for allocated statement (not prepared)", "", executionPlan); + } + + @Test + public void test_GetExecutionPlan_notAllocated() throws Exception { + expectedException.expect(SQLNonTransientException.class); + expectedException.expectMessage("Statement not yet allocated"); + final FbStatement statement = db.createStatement(); + + statement.getExecutionPlan(); + } + + @Test + public void test_GetExecutionPlan_StatementClosed() throws Exception { + expectedException.expect(SQLNonTransientException.class); + expectedException.expectMessage("Statement closed"); + final FbTransaction transaction = getTransaction(); + final FbStatement statement = db.createStatement(); + statement.allocateStatement(); + statement.setTransaction(transaction); + statement.prepare( + "SELECT RDB$DESCRIPTION AS \"Description\", RDB$RELATION_ID, RDB$SECURITY_CLASS, RDB$CHARACTER_SET_NAME " + + "FROM RDB$DATABASE"); + statement.close(); + + statement.getExecutionPlan(); + } + private FbTransaction getTransaction() throws SQLException { TransactionParameterBuffer tpb = new TransactionParameterBufferImpl(); tpb.addArgument(ISCConstants.isc_tpb_read_committed); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <mro...@us...> - 2013-09-20 12:35:57
|
Revision: 58648 http://sourceforge.net/p/firebird/code/58648 Author: mrotteveel Date: 2013-09-20 12:35:53 +0000 (Fri, 20 Sep 2013) Log Message: ----------- Implement retrieval of affected rows Modified Paths: -------------- client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java Added Paths: ----------- client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountHolder.java client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountProcessor.java Modified: client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/impl/GDSServerVersion.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -51,6 +51,11 @@ public static final String TYPE_BETA = "T"; public static final String TYPE_DEVELOPMENT = "X"; + /** + * GDSServerVersion that can be used as a dummy/invalid object when a version object is required, but none is available. + */ + public static final GDSServerVersion INVALID_VERSION = new GDSServerVersion("INVALID", "", "", "", 0, 0, 0, 0, "", ""); + private static final Pattern VERSION_PATTERN = Pattern.compile("((\\w{2})-(\\w)(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)) ([^-,]+)(?:[-,](.*))?"); Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbDatabase.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -20,8 +20,12 @@ */ package org.firebirdsql.gds.ng; +import org.firebirdsql.gds.impl.GDSServerVersion; +import org.firebirdsql.gds.impl.GDSServerVersionException; import org.firebirdsql.gds.ng.listeners.DatabaseListener; import org.firebirdsql.gds.ng.listeners.DatabaseListenerDispatcher; +import org.firebirdsql.logging.Logger; +import org.firebirdsql.logging.LoggerFactory; import java.sql.SQLException; import java.sql.SQLWarning; @@ -32,6 +36,8 @@ */ public abstract class AbstractFbDatabase implements FbDatabase { + private static final Logger log = LoggerFactory.getLogger(AbstractFbDatabase.class, false); + protected final DatabaseListenerDispatcher databaseListenerDispatcher = new DatabaseListenerDispatcher(); private final WarningMessageCallback warningCallback = new WarningMessageCallback() { @Override @@ -40,6 +46,9 @@ } }; private short databaseDialect; + private int odsMajor; + private int odsMinor; + private GDSServerVersion serverVersion; /** * @return The warning callback for this database. @@ -113,5 +122,103 @@ } } + @Override + public int getOdsMajor() { + return odsMajor; + } + + /** + * Sets the ODS (On Disk Structure) major version of the database associated + * with this connection. + * <p> + * This method should only be called by this instance. + * </p> + * + * @param odsMajor + * ODS major version + */ + protected void setOdsMajor(int odsMajor) { + this.odsMajor = odsMajor; + } + + @Override + public int getOdsMinor() { + return odsMinor; + } + + /** + * Sets the ODS (On Disk Structure) minor version of the database associated + * with this connection. + * <p> + * This method should only be called by this instance. + * </p> + * + * @param odsMinor + * The ODS minor version + */ + protected void setOdsMinor(int odsMinor) { + this.odsMinor = odsMinor; + } + + @Override + public GDSServerVersion getServerVersion() { + return serverVersion; + } + + /** + * Sets the Firebird version string. + * <p> + * This method should only be called by this instance. + * </p> + * + * @param versionString + * Raw version string + */ + protected void setServerVersion(String versionString) { + try { + serverVersion = GDSServerVersion.parseRawVersion(versionString); + } catch (GDSServerVersionException e) { + log.error(String.format("Received unsupported server version \"%s\", replacing with dummy invalid version ", versionString), e); + serverVersion = GDSServerVersion.INVALID_VERSION; + } + } + + @Override + public int iscVaxInteger(final byte[] buffer, final int startPosition, int length) { + if (length > 4) { + return 0; + } + int value = 0; + int shift = 0; + + int index = startPosition; + while (--length >= 0) { + value += (buffer[index++] & 0xff) << shift; + shift += 8; + } + return value; + } + + @Override + public long iscVaxLong(final byte[] buffer, final int startPosition, int length) { + if (length > 8) { + return 0; + } + long value = 0; + int shift = 0; + + int index = startPosition; + while (--length >= 0) { + value += (buffer[index++] & 0xffL) << shift; + shift += 8; + } + return value; + } + + @Override + public int iscVaxInteger2(final byte[] buffer, final int startPosition) { + return (buffer[startPosition] & 0xff) | ((buffer[startPosition + 1] & 0xff) << 8); + } + // TODO Unregister all listeners on close } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/AbstractFbStatement.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -44,7 +44,7 @@ /** * Set of states that will be reset to {@link StatementState#PREPARED} on transaction change */ - private static final EnumSet<StatementState> RESET_TO_PREPARED = EnumSet.of(StatementState.EXECUTING, StatementState.EXECUTED); + private static final EnumSet<StatementState> RESET_TO_PREPARED = EnumSet.of(StatementState.EXECUTING, StatementState.CURSOR_OPEN); private final Object syncObject = new Object(); private final WarningMessageCallback warningCallback = new WarningMessageCallback() { @@ -346,7 +346,29 @@ return new ExecutionPlanProcessor(this); } + @Override + public SqlCountHolder getSqlCounts() throws SQLException { + checkStatementValid(); + if (getState() == StatementState.CURSOR_OPEN && !isAllRowsFetched()) { + // We disallow fetching count when we haven't fetched all rows yet. + // TODO SQLState + throw new SQLNonTransientException("Cursor still open, fetch all rows or close cursor before fetching SQL counts"); + } + final SqlCountProcessor countProcessor = createSqlCountProcessor(); + // NOTE: implementation of SqlCountProcessor assumes the default buffer size is sufficient (actual requirement is 49 bytes max) and does not handle truncation + final SqlCountHolder sqlCounts = getSqlInfo(countProcessor.getRecordCountInfoItems(), getDefaultSqlInfoSize(), countProcessor); + statementListenerDispatcher.sqlCounts(this, sqlCounts); + return sqlCounts; + } + /** + * @return New instance of {@link SqlCountProcessor} (or subclass) for this statement. + */ + protected SqlCountProcessor createSqlCountProcessor() { + return new SqlCountProcessor(this); + } + + /** * Frees the currently allocated statement (either close the cursor with {@link ISCConstants#DSQL_close} or drop the statement * handle using {@link ISCConstants#DSQL_drop}. * Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbDatabase.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -30,6 +30,7 @@ import org.firebirdsql.encodings.IEncodingFactory; import org.firebirdsql.gds.DatabaseParameterBuffer; import org.firebirdsql.gds.TransactionParameterBuffer; +import org.firebirdsql.gds.impl.GDSServerVersion; import org.firebirdsql.gds.ng.listeners.DatabaseListener; import java.sql.SQLException; @@ -188,7 +189,7 @@ /** * @return Firebird version string */ - String getVersionString(); + GDSServerVersion getServerVersion(); /** * @return The {@link IEncodingFactory} for this connection @@ -222,8 +223,9 @@ * <code>startPosition</code> and reading for <code>length</code> bytes. * <p> * This method is useful for lengths up to 4 bytes (ie normal Java integers - * (<code>int</code>). Use {@link #iscVaxLong(byte[], int, int)} for reading - * values with length up to 8 bytes. + * (<code>int</code>). For larger lengths it will return 0. Use + * {@link #iscVaxLong(byte[], int, int)} for reading values with length up + * to 8 bytes. * </p> * * @param buffer @@ -242,7 +244,7 @@ * <code>startPosition</code> and reading for <code>length</code> bytes. * <p> * This method is useful for lengths up to 8 bytes (ie normal Java longs ( - * <code>long</code>). + * <code>long</code>). For larger lengths it will return 0. * </p> * * @param buffer Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/FbStatement.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -217,4 +217,21 @@ * If this statement is closed. */ String getExecutionPlan() throws SQLException; + + /** + * Retrieves the SQL counts for the last execution of this statement. + * <p> + * The retrieved SQL counts are also notified to all registered {@link StatementListener}s. + * </p> + * <p> + * In general the {@link FbStatement} will (should) retrieve and notify listeners of the SQL counts automatically at times were + * it is relevant (eg after executing a statement that does not produce multiple rows, or after fetching all rows). + * </p> + * + * @return The SQL counts of the last execution of this statement + * @throws SQLException + * If this statement is closed, or if this statement is in state {@link StatementState#CURSOR_OPEN} and not + * all rows have been fetched. + */ + SqlCountHolder getSqlCounts() throws SQLException; } Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountHolder.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountHolder.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountHolder.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -0,0 +1,119 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng; + +/** + * Class for holding the SQL counts (update, delete, select, insert) for a statement execution. + * <p> + * The <code>long</code> values returned from the <code>getLongXXXCount</code> methods should be considered as unsigned + * </p> + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public final class SqlCountHolder { + + private final long updateCount; + private final long deleteCount; + private final long insertCount; + private final long selectCount; + + public SqlCountHolder(final long updateCount, final long deleteCount, final long insertCount, final long selectCount) { + this.updateCount = updateCount; + this.deleteCount = deleteCount; + this.insertCount = insertCount; + this.selectCount = selectCount; + } + + /** + * Returns the count as an <code>int</code>. Values larger than {@link Integer#MAX_VALUE} are returned as 0. + * + * @param count The count value to convert + * @return Converted value + */ + private static int countAsInteger(long count) { + if (count > Integer.MAX_VALUE) { + // TODO Use Integer.MAX_VALUE? + return 0; + } + return (int) count; + } + + /** + * @return Update count as <code>int</code>, or 0 if the update count was too large. + * @see #getLongUpdateCount() + */ + public int getIntegerUpdateCount() { + return countAsInteger(updateCount); + } + + /** + * @return Number of updated records + */ + public long getLongUpdateCount() { + return updateCount; + } + + /** + * @return Delete count as <code>int</code>, or 0 if the delete count was too large. + * @see #getLongDeleteCount() + */ + public int getIntegerDeleteCount() { + return countAsInteger(deleteCount); + } + + /** + * @return Number of deleted records + */ + public long getLongDeleteCount() { + return deleteCount; + } + + /** + * @return Insert count as <code>int</code>, or 0 if the insert count was too large. + * @see #getLongInsertCount() + */ + public int getIntegerInsertCount() { + return countAsInteger(insertCount); + } + + /** + * @return Number of inserted records + */ + public long getLongInsertCount() { + return insertCount; + } + + /** + * @return Select count as <code>int</code>, or 0 if the select count was too large. + * @see #getLongSelectCount() + */ + public int getIntegerSelectCount() { + return countAsInteger(selectCount); + } + + /** + * @return Number of selected records + */ + public long getLongSelectCount() { + return selectCount; + } +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountHolder.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountProcessor.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountProcessor.java (rev 0) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountProcessor.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -0,0 +1,93 @@ +/* + * $Id$ + * + * Firebird Open Source J2EE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a CVS history command. + * + * All rights reserved. + */ +package org.firebirdsql.gds.ng; + +import org.firebirdsql.gds.ISCConstants; + +import java.sql.SQLException; + +/** + * Info processor for retrieving affected record count. + * + * @author <a href="mailto:mro...@us...">Mark Rotteveel</a> + * @since 2.3 + */ +public class SqlCountProcessor implements InfoProcessor<SqlCountHolder> { + + private static final byte[] RECORD_COUNT_INFO_ITEMS = { + ISCConstants.isc_info_sql_records, + ISCConstants.isc_info_end + }; + + private final FbStatement statement; + + public SqlCountProcessor(FbStatement statement) { + this.statement = statement; + } + + @Override + public SqlCountHolder process(byte[] infoResponse) throws SQLException { + assert infoResponse.length > 0 : "Information response buffer should be non-zero length"; + int pos = 0; + if (infoResponse[pos++] == ISCConstants.isc_info_sql_records) { + // Skipping info size + pos += 2; + long updateCount = 0; + long insertCount = 0; + long deleteCount = 0; + long selectCount = 0; + int t; + while ((t = infoResponse[pos++]) != ISCConstants.isc_info_end) { + final int countLength = statement.getDatabase().iscVaxInteger2(infoResponse, pos); + pos += 2; + final long count = statement.getDatabase().iscVaxLong(infoResponse, pos, countLength); + switch (t) { + case ISCConstants.isc_info_req_select_count: + selectCount = count; + break; + case ISCConstants.isc_info_req_insert_count: + insertCount = count; + break; + case ISCConstants.isc_info_req_update_count: + updateCount = count; + break; + case ISCConstants.isc_info_req_delete_count: + deleteCount = count; + break; + default: + // TODO Log, throw exception? + break; + } + pos += countLength; + } + return new SqlCountHolder(updateCount, deleteCount, insertCount, selectCount); + // TODO Handle isc_info_truncated, or do we simply assume we always use a sufficiently large buffer? + } else { + // TODO SQL state, better error? + throw new FbExceptionBuilder().exception(ISCConstants.isc_infunk).toSQLException(); + } + } + + public byte[] getRecordCountInfoItems() { + return RECORD_COUNT_INFO_ITEMS.clone(); + } + +} Property changes on: client-java/trunk/src/main/org/firebirdsql/gds/ng/SqlCountProcessor.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-java-source \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +Author Date Id Revision \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/StatementState.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -79,13 +79,13 @@ EXECUTING { @Override EnumSet<StatementState> createValidTransitionSet() { - return EnumSet.of(ERROR, EXECUTED, PREPARED, CLOSED); + return EnumSet.of(ERROR, CURSOR_OPEN, PREPARED, CLOSED); } }, /** * Statement has been executed, cursor is still open */ - EXECUTED { + CURSOR_OPEN { @Override public boolean isCursorOpen() { return true; Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListener.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -27,6 +27,7 @@ package org.firebirdsql.gds.ng.listeners; import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.SqlCountHolder; import org.firebirdsql.gds.ng.StatementState; import org.firebirdsql.gds.ng.fields.FieldValue; @@ -102,4 +103,14 @@ * Warning */ void warningReceived(FbStatement sender, SQLWarning warning); + + /** + * Called when the SQL counts of a statement have been retrieved. + * + * @param sender + * Statement that called this method + * @param sqlCounts + * SQL counts + */ + void sqlCounts(FbStatement sender, SqlCountHolder sqlCounts); } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/listeners/StatementListenerDispatcher.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -21,6 +21,7 @@ package org.firebirdsql.gds.ng.listeners; import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.SqlCountHolder; import org.firebirdsql.gds.ng.StatementState; import org.firebirdsql.gds.ng.fields.FieldValue; @@ -89,4 +90,15 @@ } } } + + @Override + public void sqlCounts(FbStatement sender, SqlCountHolder sqlCounts) { + for (StatementListener listener : this) { + try { + listener.sqlCounts(sender, sqlCounts); + } catch (Exception e) { + // Ignore // TODO: log + } + } + } } Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Database.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -59,9 +59,6 @@ private final AtomicInteger transactionCount = new AtomicInteger(); private int handle; - private int odsMajor; - private int odsMinor; - private String versionString; private BlrCalculator blrCalculator; /** @@ -83,62 +80,6 @@ } @Override - public int getOdsMajor() { - return odsMajor; - } - - /** - * Sets the ODS (On Disk Structure) major version of the database associated - * with this connection. - * <p> - * This method should only be called by this instance. - * </p> - * - * @param odsMajor - * ODS major version - */ - protected void setOdsMajor(int odsMajor) { - this.odsMajor = odsMajor; - } - - @Override - public int getOdsMinor() { - return odsMinor; - } - - /** - * Sets the ODS (On Disk Structure) minor version of the database associated - * with this connection. - * <p> - * This method should only be called by this instance. - * </p> - * - * @param odsMinor - * The ODS minor version - */ - protected void setOdsMinor(int odsMinor) { - this.odsMinor = odsMinor; - } - - @Override - public String getVersionString() { - return versionString; - } - - /** - * Sets the Firebird version string. - * <p> - * This method should only be called by this instance. - * </p> - * - * @param versionString - * Raw version string - */ - protected void setVersionString(String versionString) { - this.versionString = versionString; - } - - @Override public int getTransactionCount() { return transactionCount.get(); } @@ -634,90 +575,6 @@ // TODO: Move iscVax* up in inheritance tree, or move to helper class /** - * Reads Vax style integers from the supplied buffer, starting at - * <code>startPosition</code> and reading for <code>length</code> bytes. - * <p> - * This method is useful for lengths up to 4 bytes (ie normal Java integers - * (<code>int</code>). For larger lengths the values read will overflow. Use - * {@link #iscVaxLong(byte[], int, int)} for reading values with length up - * to 8 bytes. - * </p> - * - * @param buffer - * The byte array from which the integer is to be retrieved - * @param startPosition - * The offset starting position from which to start retrieving - * byte values - * @return The integer value retrieved from the bytes - * @see #iscVaxLong(byte[], int, int) - * @see #iscVaxInteger2(byte[], int) - */ - @Override - public int iscVaxInteger(final byte[] buffer, final int startPosition, int length) { - int value = 0; - int shift = 0; - - int index = startPosition; - while (--length >= 0) { - value += (buffer[index++] & 0xff) << shift; - shift += 8; - } - return value; - } - - /** - * Reads Vax style integers from the supplied buffer, starting at - * <code>startPosition</code> and reading for <code>length</code> bytes. - * <p> - * This method is useful for lengths up to 8 bytes (ie normal Java longs ( - * <code>long</code>). For larger lengths the values read will overflow. - * </p> - * - * @param buffer - * The byte array from which the integer is to be retrieved - * @param startPosition - * The offset starting position from which to start retrieving - * byte values - * @return The integer value retrieved from the bytes - * @see #iscVaxLong(byte[], int, int) - * @see #iscVaxInteger2(byte[], int) - */ - @Override - public long iscVaxLong(final byte[] buffer, final int startPosition, int length) { - long value = 0; - int shift = 0; - - int index = startPosition; - while (--length >= 0) { - value += (buffer[index++] & 0xffL) << shift; - shift += 8; - } - return value; - } - - /** - * Implementation of {@link #iscVaxInteger(byte[], int, int)} specifically - * for two-byte integers. - * <p> - * Use of this method has a small performance benefit over generic - * {@link #iscVaxInteger(byte[], int, int)} - * </p> - * - * @param buffer - * The byte array from which the integer is to be retrieved - * @param startPosition - * The offset starting position from which to start retrieving - * byte values - * @return The integer value retrieved from the bytes - * @see #iscVaxInteger(byte[], int, int) - * @see #iscVaxLong(byte[], int, int) - */ - @Override - public int iscVaxInteger2(final byte[] buffer, final int startPosition) { - return (buffer[startPosition] & 0xff) | ((buffer[startPosition + 1] & 0xff) << 8); - } - - /** * Info-request block for database information. * <p> * TODO Move to FbDatabase interface? Will this vary with versions of @@ -777,7 +634,7 @@ i += 2; String firebirdVersion = new String(info, i + 2, len - 2); i += len; - setVersionString(firebirdVersion); + setServerVersion(firebirdVersion); if (debug) log.debug("isc_info_firebird_version:" + firebirdVersion); break; case ISCConstants.isc_info_truncated: Modified: client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java =================================================================== --- client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/main/org/firebirdsql/gds/ng/wire/version10/V10Statement.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -333,8 +333,17 @@ synchronized (getDatabase().getSynchronizationObject()) { // TODO Which state to switch to when an exception occurs (always ERROR might be wrong, see to do at start of class) switchState(StatementState.EXECUTING); + final StatementType statementType = getType(); + final SqlCountProcessor sqlCountProcessor; try { - sendExecute(getType().isTypeWithSingletonResult() ? WireProtocolConstants.op_execute2 : WireProtocolConstants.op_execute, parameters); + sendExecute(statementType.isTypeWithSingletonResult() ? WireProtocolConstants.op_execute2 : WireProtocolConstants.op_execute, parameters); + if (!statementType.isTypeWithCursor()) { + // TODO Test if batching requests like this also work with earlier version of Firebird (works on 2.5) + sqlCountProcessor = createSqlCountProcessor(); + sendInfoSql(sqlCountProcessor.getRecordCountInfoItems(), getDefaultSqlInfoSize()); + } else { + sqlCountProcessor = null; + } getXdrOut().flush(); } catch (IOException ex) { switchState(StatementState.ERROR); @@ -342,20 +351,26 @@ } try { final boolean hasFields = getFieldDescriptor() != null && getFieldDescriptor().getCount() > 0; - if (getType().isTypeWithSingletonResult()) { + final WarningMessageCallback statementWarningCallback = getStatementWarningCallback(); + if (statementType.isTypeWithSingletonResult()) { // A type with a singleton result (ie an execute procedure), doesn't actually have a result set that will be fetched, instead we have a singleton result if we have fields statementListenerDispatcher.statementExecuted(this, false, hasFields); - processExecuteSingletonResponse(getDatabase().readSqlResponse(getStatementWarningCallback())); - setAllRowsFetched(true); + processExecuteSingletonResponse(getDatabase().readSqlResponse(statementWarningCallback)); + if (hasFields) { + setAllRowsFetched(true); + } } else { // A normal execute is never a singleton result (even if it only produces a single result) statementListenerDispatcher.statementExecuted(this, hasFields, false); } - processExecuteResponse(getDatabase().readGenericResponse(getStatementWarningCallback())); + processExecuteResponse(getDatabase().readGenericResponse(statementWarningCallback)); + if (sqlCountProcessor != null) { + statementListenerDispatcher.sqlCounts(this, sqlCountProcessor.process(processInfoSqlResponse(getDatabase().readGenericResponse(statementWarningCallback)))); + } // TODO .NET implementation retrieves affected rows here if (getState() != StatementState.ERROR) { - switchState(getType().isTypeWithCursor() ? StatementState.EXECUTED : StatementState.PREPARED); + switchState(statementType.isTypeWithCursor() ? StatementState.CURSOR_OPEN : StatementState.PREPARED); } } catch (IOException ex) { switchState(StatementState.ERROR); @@ -469,7 +484,8 @@ queueRowData(readSqlData()); } else if (fetchResponse.getStatus() == WireProtocolConstants.FETCH_NO_MORE_ROWS) { setAllRowsFetched(true); - // TODO Close cursor if everything has been fetched? + getSqlCounts(); + // Note: we are not explicitly 'closing' the cursor here } else { // TODO Log, raise exception, or simply 'not possible'? break; Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/SimpleStatementListener.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -21,6 +21,7 @@ package org.firebirdsql.gds.ng.wire; import org.firebirdsql.gds.ng.FbStatement; +import org.firebirdsql.gds.ng.SqlCountHolder; import org.firebirdsql.gds.ng.StatementState; import org.firebirdsql.gds.ng.fields.FieldValue; import org.firebirdsql.gds.ng.listeners.StatementListener; @@ -43,6 +44,7 @@ private Boolean allRowsFetched; private Boolean hasResultSet; private Boolean hasSingletonResult; + private SqlCountHolder sqlCounts; @Override public void newRow(FbStatement sender, List<FieldValue> rowData) { @@ -70,6 +72,11 @@ warnings.add(warning); } + @Override + public void sqlCounts(FbStatement sender, SqlCountHolder sqlCounts) { + this.sqlCounts = sqlCounts; + } + public Boolean isAllRowsFetched() { return allRowsFetched; } @@ -93,4 +100,8 @@ public void clear() { warnings.clear(); } + + public SqlCountHolder getSqlCounts() { + return sqlCounts; + } } Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Database.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -23,6 +23,7 @@ import org.firebirdsql.common.FBTestProperties; import org.firebirdsql.encodings.EncodingFactory; import org.firebirdsql.gds.ISCConstants; +import org.firebirdsql.gds.impl.GDSServerVersion; import org.firebirdsql.gds.impl.jni.EmbeddedGDSImpl; import org.firebirdsql.gds.impl.jni.NativeGDSImpl; import org.firebirdsql.gds.impl.wire.DatabaseParameterBufferImp; @@ -225,7 +226,8 @@ System.out.println(db.getHandle()); assertTrue("Expected isAttached() to return true", db.isAttached()); - assertNotNull("Expected version string to be not null", db.getVersionString()); + assertNotNull("Expected version string to be not null", db.getServerVersion()); + assertNotEquals("Expected version should not be invalid", GDSServerVersion.INVALID_VERSION, db.getServerVersion()); } finally { if (db != null) { try { Modified: client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java =================================================================== --- client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-20 08:28:41 UTC (rev 58647) +++ client-java/trunk/src/test/org/firebirdsql/gds/ng/wire/version10/TestV10Statement.java 2013-09-20 12:35:53 UTC (rev 58648) @@ -104,6 +104,7 @@ "INSERT INTO keyvalue (thevalue) VALUES (?) RETURNING thekey"; private final FbConnectionProperties connectionInfo; + private final SimpleStatementListener listener = new SimpleStatementListener(); private FbWireDatabase db; FBManager fbManager; @@ -243,6 +244,7 @@ public void testSelect_WithParameters_Execute_and_Fetch() throws Exception { final FbTransaction transaction = getTransaction(); final FbStatement statement = db.createStatement(); + statement.addStatementListener(listener); statement.allocateStatement(); statement.setTransaction(transaction); statement.prepare( @@ -254,23 +256,24 @@ FieldValue param1 = new FieldValue(descriptor.getFieldDescriptor(0), new byte[]{ 0, 0, 0, 3 }); // int = 3 (id of UNICODE_FSS) FieldValue param2 = new FieldValue(descriptor.getFieldDescriptor(1), new byte[]{ 0, 0, 0, 1 }); // int = 1 (single byte character sets) - final SimpleStatementListener statementListener = new SimpleStatementListener(); - statement.addStatementListener(statementListener); - statement.execute(Arrays.asList(param1, param2)); - assertEquals("Expected hasResultSet to be set to true", Boolean.TRUE, statementListener.hasResultSet()); - assertEquals("Expected hasSingletonResult to be set to false", Boolean.FALSE, statementListener.hasSingletonResult()); - assertNull("Expected allRowsFetched not set yet", statementListener.isAllRowsFetched()); - assertEquals("Expected no rows to be fetched yet", 0, statementListener.getRows().size()); + assertEquals("Expected hasResultSet to be set to true", Boolean.TRUE, listener.hasResultSet()); + assertEquals("Expected hasSingletonResult to be set to false", Boolean.FALSE, listener.hasSingletonResult()); + assertNull("Expected allRowsFetched not set yet", listener.isAllRowsFetched()); + assertEquals("Expected no rows to be fetched yet", 0, listener.getRows().size()); + assertNull("Expected no SQL counts yet", listener.getSqlCounts()); // 100 should be sufficient to fetch all character sets statement.fetchRows(100); - assertEquals("Expected allRowsFetched to be set to true", Boolean.TRUE, statementListener.isAllRowsFetched()); + assertEquals("Expected allRowsFetched to be set to true", Boolean.TRUE, listener.isAllRowsFetched()); // Number is database dependent (unicode_fss + all single byte character sets) - assertTrue("Expected more than two rows", statementListener.getRows().size() > 2); + assertTrue("Expected more than two rows", listener.getRows().size() > 2); + assertNotNull("Expected SQL counts", listener.getSqlCounts()); + assertEquals("Unexpected select count", listener.getRows().size(), listener.getSqlCounts().getLongSelectCount()); + statement.close(); } @@ -428,6 +431,27 @@ statement.getExecutionPlan(); } + @Test + public void test_ExecuteInsert() throws Exception { + final FbTransaction transaction = getTransaction(); + final FbStatement statement = db.createStatement(); + statement.addStatementListener(listener); + statement.allocateStatement(); + statement.setTransaction(transaction); + statement.prepare("INSERT INTO keyvalue (thekey, thevalue) VALUES (?, ?)"); + + FieldValue parameter1 = statement.getParameterDescriptor().getFieldDescriptor(0).createDefaultFieldValue(); + FieldValue parameter2 = statement.getParameterDescriptor().getFieldDescriptor(1).createDefaultFieldValue(); + parameter1.setFieldData(new byte[]{ 1, 0, 0, 0 }); + parameter2.setFieldData(db.getEncoding().encodeToCharset("test")); + + statement.execute(Arrays.asList(parameter1, parameter2)); + + assertNotNull("Expected SQL counts on listener", listener.getSqlCounts()); + assertEquals("Expected one row to have been inserted", 1, listener.getSqlCounts().getLongInsertCount()); + statement.close(); + } + private FbTransaction getTransaction() throws SQLException { TransactionParameterBuffer tpb = new TransactionParameterBufferImpl(); tpb.addArgument(ISCConstants.isc_tpb_read_committed); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |