changeset 682:78253fdb3c3f

Corrected returned SQL TypeName values for the MonetDB interval types: 'day_interval', 'month_interval' and 'sec_interval'. Those MonetDB type names can not be used in CREATE TABLE statements. Instead one has to use SQL type names: 'interval day', 'interval month' or 'interval second'. The JDBC driver now returns those SQL type names. This applies to methods: DatabaseMetaData.getTypeInfo() for the output column TYPE_NAME (the output column LOCAL_TYPE_NAME now returns the original type name), ResultSetMetaData.getColumnTypeName() and ParameterMetaData.getParameterTypeName().
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Fri, 04 Nov 2022 00:04:42 +0100 (2022-11-03)
parents 32e7ac7b979e
children 79f246478ca8
files ChangeLog src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java src/main/java/org/monetdb/jdbc/MonetResultSet.java tests/JDBC_API_Tester.java
diffstat 5 files changed, 168 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,17 @@
 # ChangeLog file for monetdb-java
 # This file is updated with Maddlog
 
+* Thu Nov  3 2022 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Corrected returned SQL TypeName values for the MonetDB interval types:
+  'day_interval', 'month_interval' and 'sec_interval'.  Those MonetDB
+  type names can not be used in CREATE TABLE statements. Instead
+  one has to use SQL type names: 'interval day', 'interval month' or
+  'interval second'. The JDBC driver now returns those SQL type names.
+  This applies to methods: DatabaseMetaData.getTypeInfo() for the output
+  column TYPE_NAME (the output column LOCAL_TYPE_NAME now returns
+  the original type name), ResultSetMetaData.getColumnTypeName() and
+  ParameterMetaData.getParameterTypeName().
+
 * Thu Oct 27 2022 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Corrected PreparedStatement methods getMetaData().getColumnDisplaySize(),
   getMetaData().getPrecision() and getParameterMetaData().getPrecision().
--- a/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
+++ b/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
@@ -3035,9 +3035,11 @@ public final class MonetDatabaseMetaData
 	 */
 	@Override
 	public ResultSet getTypeInfo() throws SQLException {
-		final StringBuilder query = new StringBuilder(2300);
-		query.append("SELECT \"sqlname\" AS \"TYPE_NAME\", " +
-			// TODO map 'day_interval' to 'interval day' (or 'interval day to second'), 'month_interval' to 'interval month' (or 'interval year to month'), 'sec_interval' to 'interval second'
+		final StringBuilder query = new StringBuilder(3200);
+		query.append("SELECT CASE \"sqlname\" WHEN 'day_interval' THEN 'interval day'" +
+				" WHEN 'month_interval' THEN 'interval month'" +
+				" WHEN 'sec_interval' THEN 'interval second'" +
+				" ELSE \"sqlname\" END AS \"TYPE_NAME\", " +
 			"cast(").append(MonetDriver.getSQLTypeMap("\"sqlname\"")).append(" AS int) AS \"DATA_TYPE\", " +
 			"\"digits\" AS \"PRECISION\", " +	// note that when radix is 2 the precision shows the number of bits
 			"cast(CASE WHEN \"sqlname\" IN ('char','varchar','sec_interval','day_interval','month_interval') THEN ''''" +
@@ -3059,7 +3061,7 @@ public final class MonetDatabaseMetaData
 				",'day_interval','month_interval','sec_interval') THEN false ELSE true END AS \"UNSIGNED_ATTRIBUTE\", " +
 			"CASE \"sqlname\" WHEN 'decimal' THEN true ELSE false END AS \"FIXED_PREC_SCALE\", " +
 			"CASE WHEN \"sqlname\" IN ('tinyint','smallint','int','bigint') THEN true ELSE false END AS \"AUTO_INCREMENT\", " +
-			"\"systemname\" AS \"LOCAL_TYPE_NAME\", " +
+			"CASE WHEN \"sqlname\" IN ('sec_interval','day_interval','month_interval') THEN \"sqlname\" ELSE \"systemname\" END AS \"LOCAL_TYPE_NAME\", " +
 			"cast(0 AS smallint) AS \"MINIMUM_SCALE\", " +
 			"cast(CASE WHEN \"sqlname\" = 'decimal' THEN (CASE \"systemname\" WHEN 'int' THEN 9 WHEN 'lng' THEN 18 WHEN 'sht' THEN 4 WHEN 'hge' THEN 38 WHEN 'bte' THEN 2 ELSE 0 END)" +
 				" WHEN \"sqlname\" IN ('time','timetz','timestamp','timestamptz','sec_interval') THEN 6 ELSE 0 END AS smallint) AS \"MAXIMUM_SCALE\", " +
@@ -3069,6 +3071,7 @@ public final class MonetDatabaseMetaData
 		"FROM \"sys\".\"types\" " +
 		"ORDER BY \"DATA_TYPE\", \"sqlname\", \"id\"");
 
+		/* if (query.length() >= 3200) System.err.println("getTypeInfo(), extend query default size to: " + query.length()); */
 		return executeMetaDataQuery(query.toString());
 	}
 
--- a/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java
+++ b/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java
@@ -775,7 +775,19 @@ public class MonetPreparedStatement
 			@Override
 			public String getColumnTypeName(final int column) throws SQLException {
 				try {
-					return monetdbType[getColumnIdx(column)];
+					final String monettype = monetdbType[getColumnIdx(column)];
+					if (monettype.endsWith("_interval")) {
+						/* convert the interval type names to valid SQL data type names,
+						 * such that generic applications can use them in create table statements
+						 */
+						if ("day_interval".equals(monettype))
+							return "interval day";
+						if ("month_interval".equals(monettype))
+							return "interval month";
+						if ("sec_interval".equals(monettype))
+							return "interval second";
+					}
+					return monettype;
 				} catch (IndexOutOfBoundsException e) {
 					throw MonetResultSet.newSQLInvalidColumnIndexException(column);
 				}
@@ -994,7 +1006,18 @@ public class MonetPreparedStatement
 			@Override
 			public String getParameterTypeName(final int param) throws SQLException {
 				try {
-					return monetdbType[getParamIdx(param)];
+					final String monettype = monetdbType[getParamIdx(param)];
+					if (monettype.endsWith("_interval")) {
+						/* convert the interval type names to valid SQL data type names
+						 */
+						if ("day_interval".equals(monettype))
+							return "interval day";
+						if ("month_interval".equals(monettype))
+							return "interval month";
+						if ("sec_interval".equals(monettype))
+							return "interval second";
+					}
+					return monettype;
 				} catch (IndexOutOfBoundsException e) {
 					throw newSQLInvalidParameterIndexException(param);
 				}
--- a/src/main/java/org/monetdb/jdbc/MonetResultSet.java
+++ b/src/main/java/org/monetdb/jdbc/MonetResultSet.java
@@ -1752,13 +1752,13 @@ public class MonetResultSet
 					{
 						// these data types may have a variable scale, max scale is 38
 
-						// Special handling for: day_interval and sec_interval as they
-						// are mapped to these column types (see MonetDriver typemap)
+						// Special handling for: day_interval and sec_interval as they are
+						// mapped to Types.NUMERIC and Types.DECIMAL types (see MonetDriver typeMap)
 						// They appear to have a fixed scale (tested against Oct2020)
 						final String monettype = getColumnTypeName(column);
-						if ("day_interval".equals(monettype))
+						if ("interval day".equals(monettype))
 							return 0;
-						if ("sec_interval".equals(monettype))
+						if ("interval second".equals(monettype))
 							return 3;
 
 						if (scales != null) {
@@ -1989,7 +1989,19 @@ public class MonetResultSet
 			public String getColumnTypeName(final int column) throws SQLException {
 				checkColumnIndexValidity(column);
 				try {
-					return types[column - 1];
+					final String monettype = types[column - 1];
+					if (monettype.endsWith("_interval")) {
+						/* convert the interval type names to valid SQL data type names,
+						 * such that generic applications can use them in create table statements
+						 */
+						if ("day_interval".equals(monettype))
+							return "interval day";
+						if ("month_interval".equals(monettype))
+							return "interval month";
+						if ("sec_interval".equals(monettype))
+							return "interval second";
+					}
+					return monettype;
 				} catch (IndexOutOfBoundsException e) {
 					throw MonetResultSet.newSQLInvalidColumnIndexException(column);
 				}
--- a/tests/JDBC_API_Tester.java
+++ b/tests/JDBC_API_Tester.java
@@ -63,6 +63,7 @@ final public class JDBC_API_Tester {
 		jt.Test_DBCmetadata();
 		jt.Test_FetchSize();
 		jt.Test_Int128();
+		jt.Test_Interval_Types();
 		jt.Test_PlanExplainTraceDebugCmds();
 		jt.Test_PSgeneratedkeys();
 		jt.Test_PSgetObject();
@@ -1484,6 +1485,84 @@ final public class JDBC_API_Tester {
 			"SUCCESS\n");
 	}
 
+	private void Test_Interval_Types() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		PreparedStatement pstmt = null;
+		try {
+			stmt = con.createStatement();
+			stmt.executeUpdate("create table Test_Interval_Types (c1 interval day, c2 interval month, c3 interval second)");
+			rs = stmt.executeQuery("select * from Test_Interval_Types");
+			if (rs != null) {
+				sb.append("Showing query ResultSetMetaData\n");
+				ResultSetMetaData rsmd = rs.getMetaData();
+				int colCount = rsmd.getColumnCount();
+				for (int col = 1; col <= colCount; col++) {
+					sb.append("ColumnName: ").append(rsmd.getColumnName(col))
+					.append("\tColumnTypeName: ").append(rsmd.getColumnTypeName(col))
+					.append("\tPrecision: ").append(rsmd.getPrecision(col))
+					.append("\tScale: ").append(rsmd.getScale(col))
+					.append("\tColumnDisplaySize: ").append(rsmd.getColumnDisplaySize(col))
+					.append("\tColumnType: ").append(rsmd.getColumnType(col))
+					.append("\tColumnClassName: ").append(rsmd.getColumnClassName(col))
+					.append("\n");
+				}
+				rs.close();
+				rs = null;
+			}
+			pstmt = con.prepareStatement("select * from Test_Interval_Types where c1 = ? or c2 = ? or c3 = ?");
+			if (pstmt != null) {
+				sb.append("Showing prepared query ResultSetMetaData\n");
+				ResultSetMetaData rsmd = pstmt.getMetaData();
+				int colCount = rsmd.getColumnCount();
+				for (int col = 1; col <= colCount; col++) {
+					sb.append("ColumnName: ").append(rsmd.getColumnName(col))
+					.append("\tColumnTypeName: ").append(rsmd.getColumnTypeName(col))
+					.append("\tPrecision: ").append(rsmd.getPrecision(col))
+					.append("\tScale: ").append(rsmd.getScale(col))
+					.append("\tColumnDisplaySize: ").append(rsmd.getColumnDisplaySize(col))
+					.append("\tColumnType: ").append(rsmd.getColumnType(col))
+					.append("\tColumnClassName: ").append(rsmd.getColumnClassName(col))
+					.append("\n");
+				}
+				sb.append("Showing prepared query ParameterMetaData\n");
+				ParameterMetaData pmd = pstmt.getParameterMetaData();
+				int paramCount = pmd.getParameterCount();
+				for (int param = 1; param <= paramCount; param++) {
+					sb.append("ParameterTypeName: ").append(pmd.getParameterTypeName(param))
+					.append("\tPrecision: ").append(pmd.getPrecision(param))
+					.append("\tScale: ").append(pmd.getScale(param))
+					.append("\tParameterType: ").append(pmd.getParameterType(param))
+					.append("\tParameterClassName: ").append(pmd.getParameterClassName(param))
+					.append("\n");
+				}
+				pstmt.close();
+				pstmt = null;
+			}
+			stmt.executeUpdate("drop table Test_Interval_Types");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(pstmt, null);
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Interval_Types",
+			"Showing query ResultSetMetaData\n" +
+			"ColumnName: c1	ColumnTypeName: interval day	Precision: 4	Scale: 0	ColumnDisplaySize: 5	ColumnType: 2	ColumnClassName: java.math.BigDecimal\n" +
+			"ColumnName: c2	ColumnTypeName: interval month	Precision: 10	Scale: 0	ColumnDisplaySize: 1	ColumnType: 4	ColumnClassName: java.lang.Integer\n" +
+			"ColumnName: c3	ColumnTypeName: interval second	Precision: 13	Scale: 3	ColumnDisplaySize: 5	ColumnType: 3	ColumnClassName: java.math.BigDecimal\n" +
+			"Showing prepared query ResultSetMetaData\n" +
+			"ColumnName: c1	ColumnTypeName: interval day	Precision: 4	Scale: 0	ColumnDisplaySize: 4	ColumnType: 2	ColumnClassName: java.math.BigDecimal\n" +
+			"ColumnName: c2	ColumnTypeName: interval month	Precision: 10	Scale: 0	ColumnDisplaySize: 10	ColumnType: 4	ColumnClassName: java.lang.Integer\n" +
+			"ColumnName: c3	ColumnTypeName: interval second	Precision: 13	Scale: 0	ColumnDisplaySize: 13	ColumnType: 3	ColumnClassName: java.math.BigDecimal\n" +
+			"Showing prepared query ParameterMetaData\n" +
+			"ParameterTypeName: interval day	Precision: 4	Scale: 0	ParameterType: 2	ParameterClassName: java.math.BigDecimal\n" +
+			"ParameterTypeName: interval month	Precision: 10	Scale: 0	ParameterType: 4	ParameterClassName: java.lang.Integer\n" +
+			"ParameterTypeName: interval second	Precision: 13	Scale: 0	ParameterType: 3	ParameterClassName: java.math.BigDecimal\n");
+	}
+
 	private void Test_PlanExplainTraceDebugCmds() {
 		sb.setLength(0);	// clear the output log buffer
 
@@ -5904,13 +5983,41 @@ final public class JDBC_API_Tester {
 	}
 
 	private void compareExpectedOutput(String testname, String expected) {
-		if (!expected.equals(sb.toString())) {
+		final String produced = sb.toString();
+		if (!expected.equals(produced)) {
 			foundDifferences = true;
 			System.err.print("Test '");
 			System.err.print(testname);
 			if (!testname.endsWith(")") && !testname.endsWith(";"))
 				System.err.print("()");
 			System.err.println("' produced different output!");
+			int expLen = expected.length();
+			int prodLen = produced.length();
+			int max_pos = expLen;
+			if (prodLen > max_pos)
+				max_pos = prodLen;
+			int line = 1;
+			int rowpos = 0;
+			for (int pos = 0; pos < max_pos; pos++) {
+				char a = (pos < expLen ? expected.charAt(pos) : '~');
+				char b = (pos < prodLen ? produced.charAt(pos) : '~');
+				if (a == '\n') {
+					line++;
+					rowpos = 0;
+				}
+				rowpos++;
+				if (a != b) {
+					if (pos + 30 < expLen)
+						expLen = pos + 30;
+					if (pos + 30 < prodLen)
+						prodLen = pos + 30;
+					System.err.println("Difference found at line " + line + " position " + rowpos
+						+ ": Expected \"" + expected.substring(pos < expLen ? pos : expLen-1, expLen-1)
+						+ "\" but gotten \"" + produced.substring(pos < prodLen ? pos : prodLen-1, prodLen-1) + "\"");
+					pos = max_pos;
+				}
+			}
+			System.err.println();
 			System.err.println("Expected:");
 			System.err.println(expected);
 			System.err.println("Gotten:");