db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From banda...@apache.org
Subject svn commit: r156209 [1/4] - in incubator/derby/code/trunk: java/build/org/apache/derbyBuild/ java/drda/ java/engine/org/apache/derby/catalog/ java/engine/org/apache/derby/impl/jdbc/ java/testing/org/apache/derbyTesting/functionTests/master/ java/testing/org/apache/derbyTesting/functionTests/master/DerbyNet/ java/testing/org/apache/derbyTesting/functionTests/suites/ java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/ tools/ant/properties/
Date Sat, 05 Mar 2005 00:21:07 GMT
Author: bandaram
Date: Fri Mar  4 16:20:59 2005
New Revision: 156209

URL: http://svn.apache.org/viewcvs?view=rev&rev=156209
Log:
Derby-107: Add or change metadata support for ODBC.

Submitted by Army Brown(qozinx@sbcglobal.net)

Added:
    incubator/derby/code/trunk/java/build/org/apache/derbyBuild/ODBCMetadataGenerator.java   (with props)
    incubator/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties   (with props)
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/master/DerbyNet/odbc_metadata.out   (with props)
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/master/odbc_metadata.out   (with props)
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/metadata_test.java   (with props)
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/odbc_metadata.java   (with props)
Modified:
    incubator/derby/code/trunk/java/build/org/apache/derbyBuild/build.xml
    incubator/derby/code/trunk/java/drda/build.xml
    incubator/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java
    incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedDatabaseMetaData.java
    incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/metadata.properties
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/suites/derbylang.runall
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/suites/derbynetmats.runall
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/suites/j9derbynetmats.runall
    incubator/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/metadata.java
    incubator/derby/code/trunk/tools/ant/properties/dirs.properties

Added: incubator/derby/code/trunk/java/build/org/apache/derbyBuild/ODBCMetadataGenerator.java
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/build/org/apache/derbyBuild/ODBCMetadataGenerator.java?view=auto&rev=156209
==============================================================================
--- incubator/derby/code/trunk/java/build/org/apache/derbyBuild/ODBCMetadataGenerator.java (added)
+++ incubator/derby/code/trunk/java/build/org/apache/derbyBuild/ODBCMetadataGenerator.java Fri Mar  4 16:20:59 2005
@@ -0,0 +1,1207 @@
+/*
+
+   Derby - Class org.apache.derby.catalog.ODBCProcedureColsVTI
+
+   Copyright 2000, 2004 The Apache Software Foundation or its licensors, as applicable.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derbyBuild;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.FileWriter;
+
+import java.util.Properties;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+import org.apache.derby.iapi.services.sanity.SanityManager;
+
+/* ****
+ * This class is used at COMPILE TIME ONLY.  It is responsible for generating
+ * ODBC metadata queries based on existing JDBC queries.  In a word,
+ * this class reads from the org/apache/derby/impl/jdbc/metadata.properties
+ * file (which is where the JDBC queries are stored), and for each query,
+ * performs the changes/additions required to make it comply with ODBC
+ * standards.  The generated ODBC queries are written to an output file
+ * that is then used, at build time, to create a full set of both JDBC and
+ * ODBC queries, all of which are then loaded into the database system
+ * tables at creation time.
+ *
+ * For more on the ODBC specification of the metadata methods in question,
+ * see:
+ *
+ * "http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/
+ *  htm/odbcsqlprocedures.asp"
+ *
+ * For more on how the generated queries are used at execution time, see
+ * EmbedDatabaseMetadata.java and SystemProcedures.java in the codeline.
+ *
+ */
+public class ODBCMetadataGenerator {
+
+	// Types of changes that are possible.  There are three
+	// types that we handle here:
+	//
+	//	1. Column rename:
+	//		Rename a column to have an ODBC-specified name.
+	//		For ex. change "SCALE" to "DECIMAL_DIGITS"
+	//	2. Type and/or value change:
+	//		Cast a column to an OBDC-specified type.  At time
+	//		of writing, this was just for casting INTs to
+	//		SMALLINTs; OR modify an existing JDBC value
+	//		to match the ODBC specification.
+	//	3. Additional column(s):
+	//		Add a new, ODBC-specified column to an existing
+	//		result set.
+	private final byte COL_RENAME_CHANGE = 0x01;
+	private final byte TYPE_VALUE_CHANGE = 0x02;
+	private final byte ADD_COLUMN_CHANGE = 0x04;
+
+	// Notice written before each generated ODBC statement.
+	private final String ODBC_QUERY_NOTICE =
+		"#\n# *** NOTE! ***  The following query was generated\n" +
+		"# AUTOMATICALLY at build time based on the existing\n" +
+		"# JDBC version of the query.  DO NOT MODIFY this\n" +
+		"# generated query by hand.  Instead, modify either\n" +
+		"# 1) the JDBC version of the query in the codeline \n" +
+		"# file \"metadata.properties\" (which will then get\n" +
+		"# propagated at build time), 2) the relevant SQL\n" +
+		"# fragments in 'odbcgen_fragments.properties' in\n" +
+		"# the codleine, or 3) the ODBCMetadataGenerator\n" +
+		"# class in the org/apache/derbyBuild directory.\n";
+
+	// Prefix to append to all ODBC queries.  NOTE: if you change
+	// this value, you'll have to modify EmbedDatabaseMetadata.java
+	// to reflect the change.
+	private final String ODBC_QUERY_PREFIX = "odbc_";
+
+	// Name to use when making JDBC queries into subqueries
+	// (loaded from odbcFragments).  NOTE: if you change this value,
+	// you'll have to modify "odbcgen_fragments.properties" to
+	// reflect the change.
+	private final String SUBQUERY_NAME = "JDBC_SUBQUERY";
+
+	// Mock value used to accomplish insertion of new columns.
+	private final String NEW_COL_PLACEHOLDER = "COLUMN_POSITION_HOLDER";
+
+	// Used for trimming 'whitespace'.
+	private final short FOLLOWING = 1;
+	private final short PRECEDING = -1;
+
+	// List of what types of changes are required for a given
+	// metadata procedure.
+	private HashMap changeMap;
+
+	// SQL fragments and keywords that are used in composing
+	// ODBC metadata queries.  These are loaded from a file
+	// once and then used throughout the generation process
+	// to build the ODBC queries piece-by-piece.
+	private Properties odbcFragments;
+
+	// Output file; all processed statements are written to this
+	// file.  At BUILD TIME, this file will clobber the copy of
+	// "metadata.properties" that is in the BUILD/CLASSES
+	// directory.  NOTE: this will NOT clobber the metadata
+	// properties file that is in the SOURCE/CODELINE.
+	private FileWriter odbcMetaFile;
+
+	/* ****
+	 * Constructor.
+	 * Initializes SQL fragments used for generation, and
+	 * then opens the output file,
+	 */
+	public ODBCMetadataGenerator() throws IOException {
+
+		// SQL fragments.
+		odbcFragments = new Properties();
+		odbcFragments.load(this.getClass().getResourceAsStream(
+			"odbcgen_fragments.properties"));
+
+		// Prep output file.
+		odbcMetaFile = new FileWriter("odbc_metadata.properties");
+
+	}
+
+	/* ****
+	 * main:
+	 * Open the metadata.properties file (the copy that is in the
+	 * build directory, NOT the one in the source directory),
+	 * figure out what changes are needed for the various metadata
+	 * queries, and then generate the ODBC-compliant versions
+	 * where needed.
+	 * @param args Ignored.
+	 * @return ODBC-compliant metadata statements have been
+	 *  	generated and written out to "odbc_metadata.properties"
+	 *		in the running directory.
+	 */
+	public static void main(String [] args) throws IOException {
+
+		ODBCMetadataGenerator odbcGen = new ODBCMetadataGenerator();
+		odbcGen.initChanges();
+		odbcGen.generateODBCQueries(odbcGen.getClass().getResourceAsStream(
+			"/org/apache/derby/impl/jdbc/metadata.properties"));
+
+	}
+
+	/* ****
+	 * initChanges
+	 * Create a listing of the types of changes that need to be
+	 * made for each metadata query to be ODBC-compliant.
+	 * If a metadata query has no entry in this map, then
+	 * it is left unchanged and no ODBC-version will be created.
+	 * Having this mapping allows us to skip over String
+	 * parsing (which can be slow) when it's not required.
+	 * For details on the changes, see the appropriate methods
+	 * below.
+	 * @return Map holding the list of changes to be made for
+	 * 	each metadata query has been initialized.
+	 */
+	private void initChanges() {
+
+		changeMap = new HashMap();
+
+		changeMap.put("getProcedures",
+			new Byte(COL_RENAME_CHANGE));
+
+		changeMap.put("getProcedureColumns",
+			new Byte((byte)(COL_RENAME_CHANGE
+				| TYPE_VALUE_CHANGE
+				| ADD_COLUMN_CHANGE)));
+
+		changeMap.put("getColumns",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getVersionColumns",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getBestRowIdentifierPrimaryKeyColumns",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getBestRowIdentifierUniqueKeyColumns",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getBestRowIdentifierUniqueIndexColumns",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getBestRowIdentifierAllColumns",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getPrimaryKeys",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		changeMap.put("getTypeInfo",
+			new Byte((byte)(COL_RENAME_CHANGE
+				| TYPE_VALUE_CHANGE
+				| ADD_COLUMN_CHANGE)));
+
+		changeMap.put("getIndexInfo",
+			new Byte(TYPE_VALUE_CHANGE));
+
+		return;
+
+	}
+
+	/* ****
+	 * generateODBCQueries:
+	 * Reads the existing (JDBC) metadata queries from
+	 * metadata.properties and, for each one, makes a call
+	 * to generate an ODBC-compliant version.
+	 * @param is InputStream for reading metadata.properties.
+	 */
+	public void generateODBCQueries(InputStream is)
+		throws IOException
+	{
+
+		// JDBC query that we read from metadata.properties.
+		StringBuffer query = new StringBuffer();
+
+		// We assume no single line/query is greater than 1K in
+		// length, and we'll fail if this isn't the case.  The
+		// limit of 1K was just picked arbitrarily; this can be
+		// increased if needed at a later time.
+		char [] line = new char[1024];
+
+		for (int count = readLine(is, line);
+		 	count != -1; count = readLine(is, line))
+		{
+
+			if (count == 0)
+			// blank line; ignore
+				continue;
+			else if (line[0] == '#') {
+			// comment; write it to file.
+				odbcMetaFile.write(line, 0, count);
+				odbcMetaFile.write("\n");
+				continue;
+			}
+
+			// Verify that we haven't passed our limit.
+			if (count >= line.length) {
+				throw new IOException(
+					"Encountered line longer than expected when reading metadata " +
+					"file; either shorten the line, or increase the limit...");
+			}
+
+			// "+1" in next line because we added a "\n" at the end and
+			// we want to include that, for sake of easier reading.
+			query.append(line, 0, count+1);
+
+			if (line[count-1] == '\\')
+			// then continue building the query.
+				continue;
+
+			// Take the query and see if we need to generate an ODBC-
+			// compliant version.
+			generateODBCQuery(query);
+
+			// Prep for another query.
+			query.delete(0, query.length());
+
+		}
+
+		// Make sure we didn't end up with an incomplete query somewhere.
+		if (query.length() > 0) {
+			throw new IOException(
+				"Encountered non-terminated query while reading metadata file.");
+		}
+
+		// Close out.
+		odbcMetaFile.flush();
+		odbcMetaFile.close();
+
+	}
+
+	/* ****
+	 * generateODBCQuery
+	 * Takes a specific JDBC query, writes it to the output file,
+	 * and then creates an ODBC-compliant version of that
+	 * query (if needed) and writes that to the output file,
+	 * as well.
+	 * @param queryText SQL text from a JDBC metadata query
+	 *	that was read from metadata.properties.
+	 */
+	private void generateODBCQuery(StringBuffer queryText)
+		throws IOException
+	{
+
+		// Create a string for purposes of using "indexOf"
+		// calls, which aren't allowed on a StringBuffer
+		// for JDBC 2.0.
+		String queryAsString = queryText.toString().trim();
+
+		if (queryAsString.startsWith(ODBC_QUERY_PREFIX))
+		// this query was automatically generated (presumably
+		// by this class), so ignore it now.
+			return;
+
+		// Write the original (JDBC) query.
+		odbcMetaFile.write(queryAsString, 0, queryAsString.length());
+		odbcMetaFile.write("\n\n");
+
+		// Parse out the name of this particular query.
+		int pos = queryAsString.indexOf("=");
+		if (pos == -1) {
+			throw new IOException(
+				"Failed to extract query name from a JDBC metadata query.");
+		}
+		String queryName = queryText.substring(0, pos);
+
+		// Parse out the ORDER BY clause since they are not allowed
+		// in subqueries; we'll re-attach it later.
+		String orderBy = "";
+		int orderByPos = queryAsString.lastIndexOf("ORDER BY");
+		if (orderByPos != -1)
+			orderBy = queryAsString.substring(orderByPos, queryAsString.length());
+
+		// Isolate query text (remove ORDER BY clause and then query name,
+		// in that order).
+		if (orderByPos != -1)
+			queryText.delete(orderByPos, queryText.length());
+		queryText.delete(0, pos+1);
+
+		// Three types of modifications that we may need to do.
+
+		// -- #1: Column renaming.
+		StringBuffer outerQueryText = new StringBuffer();
+		boolean haveODBCChanges = renameColsForODBC(queryName, queryText);
+
+		// Get a list of the column definitions in the subquery, for
+		// use by subsequent operations.
+		ArrayList colDefs = new ArrayList();
+		pos = getSelectColDefinitions(queryText, colDefs);
+
+		// In some cases, we need to add "helper" columns to the
+		// subquery so that we can use them in calculations for
+		// the outer query.
+		addHelperColsToSubquery(queryName, queryText, pos);
+
+		// -- #2.A: Prep to add new ODBC columns.  Note: we need
+		// to do this BEFORE we generate the outer SELECT statement.
+		markNewColPosition(queryName, colDefs);
+
+		// If we're going to use a subquery, generate the outer
+		// SELECT statement.  This is where we enforce column
+		// types (via CAST) if needed.
+		generateSELECTClause(queryName, colDefs, outerQueryText);
+
+		// -- #3: Alter column values, where needed.
+		changeValuesForODBC(queryName, outerQueryText);
+
+		// -- #2.B: Add new ODBC columns.
+		addNewColumnsForODBC(queryName, outerQueryText);
+
+		haveODBCChanges = (haveODBCChanges || (outerQueryText.length() > 0));
+		if (!haveODBCChanges)
+		// we didn't change anything, so nothing left to do.
+			return;
+
+		// Write out the new, ODBC version of the query.
+
+		odbcMetaFile.write(ODBC_QUERY_NOTICE);
+		odbcMetaFile.write(ODBC_QUERY_PREFIX);
+		odbcMetaFile.write(queryName);
+		odbcMetaFile.write("=");
+
+		if (outerQueryText.length() == 0) {
+		// all we did was change column names, so just write out the
+		// original query with the new column names.
+			odbcMetaFile.write(queryText.toString());
+			odbcMetaFile.write("\n\n");
+			return;
+		}
+
+		// Else, we need to make the original query a subquery so that we
+		// can change types/values and/or add columns.
+		queryAsString = queryText.toString().trim();
+		odbcMetaFile.write(outerQueryText.toString());
+		odbcMetaFile.write(queryAsString);
+		if (queryText.charAt(queryAsString.length()-1) == '\\')
+			odbcMetaFile.write("\n\\\n) ");
+		else
+			odbcMetaFile.write(" \\\n\\\n) ");
+		odbcMetaFile.write(SUBQUERY_NAME);
+		if (orderBy.length() == 0)
+			odbcMetaFile.write("\n");
+		else {
+		// re-attach ORDER BY clause.
+			odbcMetaFile.write(" \\\n");
+			odbcMetaFile.write(orderBy);
+		}
+		odbcMetaFile.write("\n\n");
+		return;
+
+	}
+
+	/* ****
+	 * renameColsForODBC
+	 * Renames any columns in the received query so that they are
+	 * ODBC-compliant.
+	 * @param queryName Name of the query being processed.
+	 * @param queryText Text of the query being processed.
+	 * @return All columns requiring renaming have been renamed IN
+	 *	PLACE in the received StringBuffer.  True is returned if
+	 *	at least one column was renamed; false otherwise.
+	 */
+	private boolean renameColsForODBC(String queryName, StringBuffer queryText) {
+
+		// If we know the received query doesn't have any columns to
+		// be renamed, then there's nothing to do here.
+		if (!stmtNeedsChange(queryName, COL_RENAME_CHANGE))
+			return false;
+
+		// Which columns are renamed, and what the new names are,
+		// depends on which query we're processing.
+
+		if (queryName.equals("getProcedures")) {
+			renameColForODBC(queryText, "RESERVED1", "NUM_INPUT_PARAMS");
+			renameColForODBC(queryText, "RESERVED2", "NUM_OUTPUT_PARAMS");
+			renameColForODBC(queryText, "RESERVED3", "NUM_RESULT_SETS");
+			return true;
+		}
+		else if (queryName.equals("getProcedureColumns")) {
+			renameColForODBC(queryText, "PRECISION", "COLUMN_SIZE");
+			renameColForODBC(queryText, "LENGTH", "BUFFER_LENGTH");
+			renameColForODBC(queryText, "SCALE", "DECIMAL_DIGITS");
+			renameColForODBC(queryText, "RADIX", "NUM_PREC_RADIX");
+			return true;
+		}
+		else if (queryName.equals("getTypeInfo")) {
+			renameColForODBC(queryText, "PRECISION", "COLUMN_SIZE");
+			renameColForODBC(queryText, "AUTO_INCREMENT", "AUTO_UNIQUE_VAL");
+			return true;
+		}
+
+		// No renaming was necessary.
+		return false;
+
+	}
+
+	/* ****
+	 * renameColForODBC
+	 * Searches for the old column name in the received String
+	 * buffer and replaces it with the new column name.  Note
+	 * that we only replace the old column name where it is
+	 * preceded by "AS", because this is the instance that
+	 * determines the column name in the final result set.
+	 * @param queryText The query text in which we're doing the
+	 *	rename operation.
+	 * @param oldVal The old column name.
+	 * @param newVal The new column name.
+	 * @return Occurence of <"AS " + oldVal> in the query text
+	 *	has been changed IN PLACE to newVal.
+	 */
+	private void renameColForODBC(StringBuffer queryText,
+		String oldVal, String newVal)
+	{
+
+		String queryString = queryText.toString();
+		int pos = queryString.indexOf(oldVal);
+		while (pos != -1) {
+
+			// Next line will set pos2 to be the index of the
+			// first (reading left-to-right) ignorable char
+			// preceding the old column name.  That means
+			// that the letters immediately preceding this
+			// position should be "AS".  If not, don't
+			// replace this instance.
+			int pos2 = trimIgnorable(PRECEDING, queryString, pos);
+			if (((pos2 - 2) > 0) && (queryString.charAt(pos2-2) == 'A')
+				&& (queryString.charAt(pos2-1) == 'S'))
+			{ // then this is the one we want to replace.
+				break;
+			}
+			else {
+			// look for next occurrence.
+				pos = queryString.indexOf(oldVal, pos+1);
+			}
+
+		}
+
+		if (pos == -1) {
+		// couldn't find the one to replace; leave unchanged.
+			return;
+		}
+
+		// Do the renaming.
+		queryText.replace(pos, pos + oldVal.length(), newVal);
+
+	}
+
+	/* ****
+	 * generateSELECTClause
+	 * Generates an outer SELECT clause that is then wrapped around a
+	 * JDBC query to change the types and/or values of the JDBC
+	 * result set.  The JDBC query thus becomes a subquery.
+	 *
+	 * Ex. if we have a JDBC query "SELECT A, B FROM T1" and ODBC
+	 * requires that "A" be a smallint, this method will generate
+	 * a select clause "SELECT CAST (T2.A AS SMALLINT), T2.B FROM"
+	 * that is then used to wrap the JDBC query, as follows:
+	 *
+	 *		SELECT CAST (T2.A AS SMALLINT), T2.B FROM
+	 *			(SELECT A, B FROM T1) T2
+	 *
+	 * @param queryName Name of the query being processed.
+	 * @param selectColDefs Array list of the SELECT columns that
+	 * 	exist for the JDBC version of the query.  For the above
+	 *  example, this would be an array list with two String
+	 *  elements, "A" and "B".
+	 * @param newQueryText StringBuffer to which the generated
+	 *  outer SELECT will be appended.
+	 * @return An outer SELECT clause has been generated and
+	 *  appended to the received buffer.  The "FROM" keyword
+	 *  has been appended, but the subquery itself is NOT
+	 *  added here.
+	 */
+	private void generateSELECTClause(String queryName,
+		ArrayList selectColDefs, StringBuffer newQueryText)
+	{
+
+		if (!stmtNeedsChange(queryName, TYPE_VALUE_CHANGE) &&
+			!stmtNeedsChange(queryName, ADD_COLUMN_CHANGE))
+		{ // then we don't need to generate a SELECT, because we
+		  // don't need to use a subquery (we're only renaming).
+			return;
+		}
+
+		// Begin the SELECT clause.
+		newQueryText.append("SELECT \\\n\\\n");
+
+		// For each of the SELECT columns in JDBC, either
+		// just grab the column name and use it directly in
+		// the generated clause, or else cast the column
+		// to the required type, if appropriate.
+		String colName;
+		String castInfo;
+		for (int i = 0; i < selectColDefs.size(); i++) {
+			if (i > 0)
+				newQueryText.append(", \\\n");
+			colName = extractColName((String)selectColDefs.get(i));
+			castInfo = getCastInfoForCol(queryName, colName);
+			if (castInfo != null)
+				newQueryText.append("CAST (");
+			newQueryText.append(SUBQUERY_NAME);
+			newQueryText.append(".");
+			newQueryText.append(colName);
+			if (castInfo != null) {
+				newQueryText.append(" AS ");
+				newQueryText.append(castInfo);
+				newQueryText.append(")");
+			}
+			if (!colName.equals(NEW_COL_PLACEHOLDER)) {
+			// don't append the "AS" clause if this is just our
+			// place-holder for adding new columns.
+				newQueryText.append(" AS ");
+				newQueryText.append(colName);
+			}
+		}
+
+		if (newQueryText.charAt(newQueryText.length() - 1) != '\\')
+			newQueryText.append(" \\");
+
+		// End the SELECT clause.
+		newQueryText.append("\nFROM ( ");
+		return;
+
+	}
+
+	/* ****
+	 * changeValuesForODBC
+	 * Searches for a JDBC column name in the received String
+	 * buffer and replaces the first occurrence with an ODBC-
+	 * compliant value.  This method determines what specific
+	 * columns need updated values for a given query, and then
+	 * makes the appropriate call for each column.
+	 * @param queryName Name of the query being processed.
+	 * @param newQueryText The query text in which we're doing the
+	 *	change-value operation.
+	 * @return All relevant columns have been updated IN PLACE
+	 *	to return the required ODBC-compliant values.
+	 */
+	private void changeValuesForODBC(String queryName,
+		StringBuffer newQueryText)
+	{
+
+		if (!stmtNeedsChange(queryName, TYPE_VALUE_CHANGE))
+			return;
+
+		// Which column values are changed, and what the new
+		// values are, depends on which query we're processing.
+
+		if (queryName.equals("getColumns")) {
+			changeColValueToODBC(queryName, "BUFFER_LENGTH", newQueryText);
+			changeColValueToODBC(queryName, "DECIMAL_DIGITS", newQueryText);
+			changeColValueToODBC(queryName, "NUM_PREC_RADIX", newQueryText);
+			changeColValueToODBC(queryName, "SQL_DATA_TYPE", newQueryText);
+			changeColValueToODBC(queryName, "SQL_DATETIME_SUB", newQueryText);
+			changeColValueToODBC(queryName, "CHAR_OCTET_LENGTH", newQueryText);
+		}
+		else if (queryName.startsWith("getBestRowIdentifier")) {
+			changeColValueToODBC(queryName, "BUFFER_LENGTH", newQueryText);
+			changeColValueToODBC(queryName, "DECIMAL_DIGITS", newQueryText);
+		}
+		else if (queryName.equals("getTypeInfo")) {
+			changeColValueToODBC(queryName, "NUM_PREC_RADIX", newQueryText);
+			changeColValueToODBC(queryName, "SQL_DATA_TYPE", newQueryText);
+			changeColValueToODBC(queryName, "SQL_DATETIME_SUB", newQueryText);
+			changeColValueToODBC(queryName, "UNSIGNED_ATTRIBUTE", newQueryText);
+			changeColValueToODBC(queryName, "AUTO_UNIQUE_VAL", newQueryText);
+		}
+		else if (queryName.equals("getProcedureColumns")) {
+			changeColValueToODBC(queryName, "NUM_PREC_RADIX", newQueryText);
+			changeColValueToODBC(queryName, "DECIMAL_DIGITS", newQueryText);
+		}
+
+	}
+
+	/* ****
+	 * changeColValueToODBC
+	 * Searches for the received column name in the received String
+	 * buffer and replaces it with an ODBC-compliant value.
+	 * @param queryName Name of the query being processed.
+	 * @param colName Name of the specific column to update.
+	 * @param newQueryText The query text in which we're doing
+	 *	the change-value operation.
+	 * @return The received column has been updated IN PLACE
+	 *	to return the required ODBC-compliant value.
+	 */
+	private void changeColValueToODBC(String queryName, String colName,
+		StringBuffer newQueryText)
+	{
+
+		colName = SUBQUERY_NAME + "." + colName;
+		int pos = newQueryText.toString().indexOf(colName);
+		if (pos == -1)
+		// column we're supposed to change isn't in the query.
+			return;
+
+		if (colName.endsWith("CHAR_OCTET_LENGTH")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("CHAR_OCTET_FOR_ODBC"));
+		}
+		else if (colName.endsWith("BUFFER_LENGTH")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("BUFFER_LEN_FOR_ODBC"));
+		}
+		else if (colName.endsWith("SQL_DATA_TYPE")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("SQL_DATA_TYPE_FOR_ODBC"));
+		}
+		else if (colName.endsWith("SQL_DATETIME_SUB")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("DATETIME_SUB_FOR_ODBC"));
+		}
+		else if (colName.endsWith("UNSIGNED_ATTRIBUTE")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("UNSIGNED_ATTR_FOR_ODBC"));
+		}
+		else if (colName.endsWith("AUTO_UNIQUE_VAL")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("AUTO_UNIQUE_FOR_ODBC"));
+		}
+		else if (colName.endsWith("DECIMAL_DIGITS")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("DECIMAL_DIGITS_FOR_ODBC"));
+		}
+		else if (colName.endsWith("NUM_PREC_RADIX")) {
+			newQueryText.replace(pos, pos + colName.length(),
+				getFragment("RADIX_FOR_ODBC"));
+		}
+		else if (colName.endsWith(NEW_COL_PLACEHOLDER)) {
+		// This is a special case indication that we need to add new columns.
+			if (queryName.equals("getProcedureColumns")) {
+				newQueryText.replace(pos, pos + colName.length(),
+					getFragment("GET_PROC_COLS_NEW_COLS"));
+			}
+			else if (queryName.equals("getTypeInfo")) {
+				newQueryText.replace(pos, pos + colName.length(),
+					getFragment("GET_TYPE_INFO_NEW_COLS"));
+			}
+		}
+
+	}
+
+	/* ****
+	 * getSelectColDefinitions
+	 * Parses the SELECT clause of a JDBC metadata SQL query
+	 * and returns a list of the columns being selected.  For
+	 * example, if the received statement was "SELECT A,
+	 * B AS C, D * 2 FROM T1", this method will return an
+	 * ArrayList with three string elements: 1) "A", 2) "B
+	 * AS C", and 3) "D * 2".
+	 * @param query The query from which we are extracting
+	 *	the SELECT columns.
+	 * @param colDefList ArrayList in which we want to
+	 * 	store the column definitions that we find.
+	 * @return Received ArrayList has one string value for
+	 *	each of the columns found in the received query.
+	 *	Also, an integer is returned indicating the index
+	 *	in the received query of the start of the FROM
+	 *	clause, for later use by the calling method.
+	 */
+	private int getSelectColDefinitions(StringBuffer queryText,
+		ArrayList colDefList)
+	{
+
+		// Create a string for purposes of using "indexOf"
+		// calls, which aren't allowed on a StringBuffer
+		// for JDBC 2.0.
+		String query = queryText.toString().trim();
+		char [] queryChars = query.toCharArray();
+
+		// Move beyond the "SELECT" keyword, if there is one.
+		int start = query.indexOf("SELECT");
+		if (start != -1)
+		// "+6" in the next line is length of "SELECT".
+			start += 6;
+		else
+		// just start at the first character.
+			start = 0;
+
+		// Have to read character-by-character in order to
+		// figure out where each column description ends.
+		int fromClauseIndex = -1;
+		int parenDepth = 0;
+		for (int i = start; i < queryChars.length; i++) {
+
+			if (queryChars[i] == '(')
+				parenDepth++;
+			else if (queryChars[i] == ')')
+				parenDepth--;
+			else if ((queryChars[i] == ',') && (parenDepth == 0)) {
+			// this is a naive way of determining the end of a
+			// column definition (it'll work so long as there are no
+			// string constants in the query that have commas in them,
+			// which was true at the time of writing.
+				colDefList.add(new String(queryChars, start, (i - start)).trim());
+				// Skip over non-important whitespace to find start
+				// of next column definition.  Next line will set i to
+				// just before the next non-whitespace character.
+				i = trimIgnorable(FOLLOWING, queryChars, i);
+				start = i + 1;
+			}
+			else if (((i+3) < queryChars.length)
+				&& (parenDepth == 0)
+				&& (queryChars[i] == 'F')
+				&& (queryChars[i+1] == 'R')
+				&& (queryChars[i+2] == 'O')
+				&& (queryChars[i+3] == 'M'))
+			{ // this is the end of final column definition; store it
+			  // and then exit the loop, after trimming off non-important
+			  // whitespace.  Next line will set i to just after the
+			  // last (reading left-to-right) non-whitespace character
+			  // before the FROM.
+				i = trimIgnorable(PRECEDING, queryChars, i);
+				fromClauseIndex = i;
+				colDefList.add(new String(queryChars, start, (i - start)).trim());
+				break;
+
+			}
+
+		}
+
+		return fromClauseIndex;
+
+	}
+
+	/* ****
+	 * addHelperColsToSubquery
+	 * For some of the metadata queries, the ODBC version
+	 * needs to access values that are only available in
+	 * the JDBC subquery.  In such cases, we want to add
+	 * those values as additional "helper" columns to
+	 * the subquery result set, so that they can be
+	 * referenced from the new ODBC outer query (without
+	 * requiring a join).  For example, assume we have 2
+	 * tables T1(int i, int j) and T2 (int a), and a
+	 * subquery "SELECT T1.i, T1.j + T2.a from T1, T2)".
+	 * Then we have an outer query that, instead of
+	 * returning "T1.j + T2.a", needs to return the
+	 * value of "2 * T2.a":
+	 *
+	 * SELECT VT.i, 2 * T2.a FROM
+	 *	(SELECT T1.i, T1.j + T2.a FROM T1, T2) VT
+	 *
+	 * The above statement WON'T work, because the outer
+	 * query can't see the value "T2.a".  So in such a
+	 * a case, this method will add "T2.a" to the list
+	 * of columns returned by the subquery, so that the
+	 * outer query can then access it:
+	 *
+	 * SELECT VT.i, 2 * VT.a FROM
+	 * 	(SELECT T1.i, T1.j + T2.a, T2.a FROM T1, T2) VT
+	 *
+	 * Which specific columns are added to the subquery
+	 * depends on the query in question.
+	 *
+	 * @param queryName Name of the query in question.
+	 * @param subqueryText text of the subquery in question.
+	 * @param insertPos Index into the received buffer
+	 *	marking the position where the helper columns
+	 * 	should be inserted.
+	 */
+	private void addHelperColsToSubquery(String queryName,
+		StringBuffer subqueryText, int insertPos)
+	{
+
+		if (queryName.equals("getColumns")) {
+			subqueryText.insert(insertPos,
+				getFragment("GET_COLS_HELPER_COLS"));
+		}
+		else if (queryName.startsWith("getBestRowIdentifier")) {
+			subqueryText.insert(insertPos,
+				getFragment("BEST_ROW_ID_HELPER_COLS"));
+		}
+
+	}
+
+	/* ****
+	 * extractColName
+	 * Takes a single column definition from a SELECT clause
+	 * and returns only the unqualified name of the column.
+	 * Assumption here is that any column definition we see
+	 * here will either 1) end with an "AS <COLUMN_NAME>"
+	 * clause, or 2) consist of ONLY a column name, such
+	 * as "A" or "A.B".  At the time of writing, these
+	 * assumptions were true for all relevant metadata
+	 * queries.
+	 *
+	 * Ex. If colDef is "A", this method will return "A".
+	 * If colDef is "A.B", this method will return "B".
+	 * If colDef is "<bunch of SQL> AS C", this method
+	 * will return "C".
+	 *
+	 * @param colDef Column definition from which we're
+	 *	trying to extract the name.
+	 * @return Name of the column that is referenced in
+	 *	the received column definition.
+	 */
+	private String extractColName(String colDef) {
+
+		// Find out where the column name starts.
+		int pos = colDef.lastIndexOf("AS ");
+		if (pos == -1) {
+		// we assume that the col def is _just_ a column name,
+		// so start at the beginning.
+			pos = 0;
+		}
+		else {
+			// Move beyond the "AS".
+			pos += 2;
+
+			// Skip any non-important whitespace or backslashes.
+			char c = colDef.charAt(pos);
+			while ((c == '\\') || Character.isWhitespace(c))
+				c = colDef.charAt(++pos);
+		}
+
+		// Check to see if it's a qualified name.
+		int pos2 = colDef.indexOf(".", pos);
+		if (pos2 == -1)
+		// it's not a qualified name, so just return it.
+			return colDef.substring(pos, colDef.length());
+
+		// Else, strip off the schema and just return the col name.
+		return colDef.substring(pos2+1, colDef.length());
+
+	}
+
+	/* ****
+	 * getCastInfoForCol
+	 * Returns the target type for a result set column that
+	 * needs to be cast into an ODBC type.  This is usually
+	 * for casting integers to "SMALLINT".
+	 * @param queryName Name of query being processed.
+	 * @param colName Name of the specific column for which
+	 * 	we are trying to find the target type.
+	 * @return The target type if one exists, or else null
+	 *  if the received column in the received query has
+	 * 	no known target type.
+	 */
+	private String getCastInfoForCol(String queryName,
+		String colName)
+	{
+
+		if (queryName.equals("getTypeInfo")) {
+			if (colName.equals("NULLABLE") ||
+				colName.equals("CASE_SENSITIVE") ||
+				colName.equals("SEARCHABLE") ||
+				colName.equals("UNSIGNED_ATTRIBUTE") ||
+				colName.equals("FIXED_PREC_SCALE") ||
+				colName.equals("AUTO_UNIQUE_VAL") ||
+				colName.equals("SQL_DATA_TYPE") ||
+				colName.equals("SQL_DATETIME_SUB") ||
+				colName.equals("MINIMUM_SCALE") ||
+				colName.equals("MAXIMUM_SCALE"))
+			{
+				return "SMALLINT";
+			}
+		}
+		else if (queryName.equals("getColumns")) {
+			if (colName.equals("DECIMAL_DIGITS") ||
+				colName.equals("NULLABLE") ||
+				colName.equals("NUM_PREC_RADIX") ||
+				colName.equals("SQL_DATA_TYPE") ||
+				colName.equals("SQL_DATETIME_SUB"))
+			{
+				return "SMALLINT";
+			}
+		}
+		else if (queryName.equals("getVersionColumns")) {
+			if (colName.equals("SCOPE") ||
+				colName.equals("DATA_TYPE") ||
+				colName.equals("DECIMAL_DIGITS") ||
+				colName.equals("PSEUDO_COLUMN"))
+			{
+				return "SMALLINT";
+			}
+		}
+		else if (queryName.equals("getPrimaryKeys")) {
+			if (colName.equals("KEY_SEQ"))
+				return "SMALLINT";
+		}
+		else if (queryName.equals("getIndexInfo")) {
+			if (colName.equals("NON_UNIQUE") ||
+				colName.equals("TYPE") ||
+				colName.equals("ORDINAL_POSITION"))
+			{
+				return "SMALLINT";
+			}
+		}
+
+		// No target type for the received column
+		// in the received query (leave it unchanged).
+		return null;
+
+	}
+
+	/* ****
+	 * markNewColPosition
+	 * In effect, "marks" the position at which additional
+	 * columns are to be added for ODBC compliance.  This
+	 * is accomplished by adding a dummy column name to
+	 * the list of SELECT columns.  Later, in the method
+	 * that actually adds the columns, we'll do a find-
+	 * replace on this dummy value.
+	 * @param queryName Name of the query.
+	 * @param selectColDefs Array list of the SELECT
+	 * 	columns that exist in the ODBC version of
+	 *	the query thus far.
+	 * @return A dummy column name has been added to
+	 *	the received list of columns at the position
+	 *	at which new ODBC columns should be added.
+	 *  If a query doesn't require additional
+	 * 	columns to be ODBC compliant, this method
+	 *	leaves the received column list unchanged.
+	 */
+	private void markNewColPosition(String queryName,
+		ArrayList selectColDefs)
+	{
+
+		if (!stmtNeedsChange(queryName, ADD_COLUMN_CHANGE))
+			return;
+
+		if (queryName.equals("getProcedureColumns")) {
+		// Add the new columns in front of the Derby-specific ones.
+		// The "-2" in the next line is because there are 2 Derby-
+		// specific columns in the JDBC version of getProcedureCols
+		// (PARAMETER_ID and METHOD_ID).
+			selectColDefs.add(selectColDefs.size() - 2, NEW_COL_PLACEHOLDER);
+		}
+		else if (queryName.equals("getTypeInfo")) {
+		// just add the new column to the end.
+			selectColDefs.add(NEW_COL_PLACEHOLDER);
+		}
+
+	}
+
+	/* ****
+	 * addNewColumnsForODBC
+	 * Adds new columns to the ODBC version of a metadata
+	 * query (the ODBC version is at this point being
+	 * built up in newQueryText).  Before this method
+	 * was called, a dummy placeholder should have been
+	 * placed in the newQueryText buffer (by a call to
+	 * "markNewColPosition").  This method simply replaces
+	 * that dummy placeholder with the SQL text for the
+	 * new columns.
+	 * @param queryName Name of query being processed.
+	 * @newQueryText The buffer in which we want to
+	 * 	add the new column.
+	 * @return The dummy placeholder in the received
+	 *  buffer has been replaced with any ODBC columns
+	 *  that need to be added to the query in question
+	 *  for ODBC compliance.
+	 */
+	private void addNewColumnsForODBC(String queryName,
+		StringBuffer newQueryText)
+	{
+
+		if (!stmtNeedsChange(queryName, ADD_COLUMN_CHANGE))
+			return;
+
+		changeColValueToODBC(queryName, NEW_COL_PLACEHOLDER, newQueryText);
+
+		// It's possible that the new column fragments we added
+		// have placeholders in them for _other_ fragments.  We
+		// need to do the substitution here.
+		if (queryName.equals("getProcedureColumns")) {
+			fragSubstitution("SQL_DATA_TYPE_FOR_ODBC", newQueryText);
+			fragSubstitution("DATETIME_SUB_FOR_ODBC", newQueryText);
+		}
+
+		return;
+
+	}
+
+	/* ****
+	 * fragSubstitution
+	 * Replaces a single occurrence of the received
+	 * fragment key with the text corresponding to
+	 * that key.
+	 * @param fragKey The fragment key for which we are
+	 *	going to do the substitution.
+	 * @queryText The buffer in which we are going to do
+	 * 	the substitution.
+	 * @return fragKey has been substituted (IN PLACE)
+	 *	with the fragment corresponding to it in the
+	 *	received buffer.  If the fragment key could not
+	 * 	be found, the buffer remains unchanged.
+	 */
+	private void fragSubstitution(String fragKey,
+		StringBuffer queryText)
+	{
+
+		int pos = queryText.toString().indexOf(fragKey);
+		if (pos != -1) {
+			// NOTE: the " + 1" and " - 1" in the next line
+			// are needed because the fragment key is
+			// enclosed within curly braces ("{}").
+			queryText.replace(pos - 1, pos + fragKey.length() + 1,
+				getFragment(fragKey));
+		}
+
+	}
+
+	/* ****
+	 * readLine
+	 * Reads a line from the received input stream and stores it
+	 * into the received character array.  In this method, we
+	 * consider the end of the line to be either 1) "\n" char, or
+	 * 2) a single backslash "\", which is used in metadata
+	 * queries to indicate line continuation.  After reading
+	 * a line, we append an EOL to it for formatting purposes,
+	 * but that last EOL is NOT included in the count of
+	 * characters.
+	 * @param is The input stream from which we're reading.
+	 * @param line The char array into which we're reading.
+	 * @return the number of characters read from the
+	 *	stream; -1 if we reached end of the stream.
+	 */
+	private int readLine(InputStream is, char [] line)
+		throws IOException
+	{
+
+		int count = 0;
+		boolean atLeastOneNonWSChar = false;
+
+		char ch;
+		int byteRead;
+		for (byteRead = is.read();
+			(byteRead != -1) && (count < line.length);
+			byteRead = is.read())
+		{
+			ch = (char)byteRead;
+			line[count++] = ch;
+			atLeastOneNonWSChar = true;
+			if ((ch == '\\') || (ch == '\n'))
+				break;
+		}
+
+		if ((byteRead == -1) && (count == 0))
+		// end of file.
+			return -1;
+
+		// Take off trailing whitespace.
+		while ((count > 0) && Character.isWhitespace(line[count-1]))
+			count--;
+
+		// Add an EOL for ease of reading, but don't include it in
+		// "count" total.
+		line[count] = '\n';
+		return count;
+
+	}
+
+	/* ****
+	 * trimIgnorable
+	 * Removes all 'ignorable' chars that immediately precede or
+	 * follow (depending on the direction) the character at
+	 * the received index.  "Ignorable" here means whitespace
+	 * OR a single backslash ("\"), which is used in the
+	 * metadata.properties file to indicate line continuation.
+	 * @param direction +1 if we want to trim following, -1
+	 *	if we want to trim preceding.
+	 * @param chars The character array being processed.
+	 * @param index The point before/after which to start
+	 * 	trimming.
+	 * @return The index into the received char array of the
+	 *	"last" ignorable character w.r.t the received index
+	 *	and direction.  In other words, if we're trimming
+	 *	the chars FOLLOWING, the returned index will be of
+	 * 	the last (reading left-to-right) ignorable char; if
+	 *	we're trimming the chars PRECEDING, the returned index
+	 *	will be of the first (reading left-to-right) ignorable
+	 *	character.
+	 */
+	private int trimIgnorable(short direction, char [] chars, int index) {
+
+		index += direction;
+		while ((index >= 0) && (index < chars.length) &&
+			((chars[index] == '\\') ||
+			Character.isWhitespace(chars[index])))
+		{
+			index += direction;
+		}
+
+		// index is now on the final non-ignorable character
+		// in the given direction.  Move it back one so that
+		// it's on the "last" ignorable character (with
+		// respect to direction).
+		index -= direction;
+
+		return index;
+
+	}
+
+	/* ****
+	 * trimIgnorable
+	 * Same as trimIgnorable above, except with String argument
+	 * instead of char[].
+	 */
+	private int trimIgnorable(short direction, String str, int index) {
+
+		index += direction;
+		while ((index >= 0) && (index < str.length()) &&
+			((str.charAt(index) == '\\') ||
+			Character.isWhitespace(str.charAt(index))))
+		{
+			index += direction;
+		}
+
+		// index is now on the final non-ignorable character
+		// in the given direction.  Move it back one so that
+		// it's on the "first" ignorable character (with
+		// respect to direction).
+		index -= direction;
+
+		return index;
+
+	}
+
+	/* ****
+	 * stmtNeedsChange
+	 * Returns whether or not a specific metadata statement
+	 * requires the received type of change.  This is determined
+	 * based on the info stored in the "changeMaps" mapping.
+	 * @param queryName Name of the query in question.
+	 * @param changeType The type of change in question.
+	 */
+	private boolean stmtNeedsChange(String queryName, byte changeType) {
+
+		Byte changeByte = (Byte)changeMap.get(queryName);
+		if (changeByte == null)
+		// No entry means change is not needed.
+			return false;
+
+		return ((changeByte.byteValue() & changeType) == changeType);
+
+	}
+
+	/* ****
+	 * getFragment
+	 * Looks up an SQL fragment and returns the value as a String.
+	 * @param String fragId id of the fragment to look up.
+	 * @return The string fragment corresponding to the received
+	 * 	fragment id.
+	 */
+	private String getFragment(String fragId) {
+		return (String)(odbcFragments.get(fragId));
+	}
+	
+}

Propchange: incubator/derby/code/trunk/java/build/org/apache/derbyBuild/ODBCMetadataGenerator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/derby/code/trunk/java/build/org/apache/derbyBuild/build.xml
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/build/org/apache/derbyBuild/build.xml?view=diff&r1=156208&r2=156209
==============================================================================
--- incubator/derby/code/trunk/java/build/org/apache/derbyBuild/build.xml (original)
+++ incubator/derby/code/trunk/java/build/org/apache/derbyBuild/build.xml Fri Mar  4 16:20:59 2005
@@ -20,6 +20,7 @@
   <target name="build" depends="compile"/>
 
   <target name="compile">
+    <copy file="${derby.build.src.dir}/org/apache/derbyBuild/odbcgen_fragments.properties" tofile="${out.dir}/org/apache/derbyBuild/odbcgen_fragments.properties"/>
     <javac
       bootclasspath="${empty}"
       nowarn="on"

Added: incubator/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties?view=auto&rev=156209
==============================================================================
--- incubator/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties (added)
+++ incubator/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties Fri Mar  4 16:20:59 2005
@@ -0,0 +1,269 @@
+#
+# This file contains SQL fragments that are used as
+# part of the ODBC metadata generation process.
+# This file is NOT, and is NOT intended to become,
+# an ODBC substitute for metadata.properties.
+# Rather, it is a place to store fragments of ODBC
+# metadata statements that do not apply to JDBC
+# metadata, but which are used as part of the ODBC
+# metadata generation process (these fragments are
+# plugged into the ODBC versions of the queries for
+# which they are required).  When all is done,
+# the fragments in this file will show up as part
+# of the generated ODBC metadata queries, which
+# will then be appended to Derby's internal list
+# of metadata queries (in metadata.properties),
+# which will, finally, serve as the basis for
+# for both JDBC and ODBC metadata calls.
+#
+# This file, like the ODBCMetadataGenerator class
+# that uses it, is ONLY USED AT BUILD TIME; and 
+# like the ODBCMetadataGenerator class, it is NOT
+# included in the final org.apache.derby package
+# (and thus will NOT be included in the Derby jar
+# file).
+#
+# Note: In this file, words between curly brackets
+# (ex "{SQL_DATA_TYPE_FOR_ODBC}") are placeholders
+# for other fragments in this file.  The actual
+# substitutions for these placeholders occur as
+# part of the ODBCMetadataGenerator class's work.
+#
+# Finally, note that starting a line in this file
+# with the "\\\n" sequence allows formatting (esp.
+# tabs and newlines) to be preserved, so that the
+# generated ODBC queries are human-readable.
+
+# ----------
+#
+# SQL_DATA_TYPE:
+# Set SQL_DATA_TYPE, which is unused by JDBC (and
+# thus returns NULL for JDBC), to the value as
+# defined by the ODBC specification.  Note that
+# any metadata statement requiring this fragment
+# must already have a column named "DATA_TYPE"
+# as part of the JDBC subquery's result set.
+#
+# Date, time, and timestamp columns are supposed
+# to return a generic "SQL_DATETIME" value here;
+# that's defined as the value "9".  All other
+# types return their normal DATA_TYPE value.
+#
+SQL_DATA_TYPE_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::DATE, \
+\\\n	java.sql.Types::TIME, java.sql.Types::TIMESTAMP)) \
+\\\n		 THEN 9 \
+\\\n		 ELSE JDBC_SUBQUERY.DATA_TYPE END
+
+# ----------
+#
+# SQL_DATETIME_SUB:
+# Set SQL_DATETIME_SUB, which is unused by JDBC (and
+# thus returns NULL for JDBC), to the value as
+# defined by the ODBC specification.  Note that
+# any metadata statement requiring this fragment
+# must already have a column named "DATA_TYPE"
+# as part of the JDBC subquery's result set.
+#
+# This value is null for all types except date, time,
+# and timestamp.  For those, the values are defined
+# as follows:
+#
+#	SQL_CODE_DATE       1
+#	SQL_CODE_TIME       2
+#	SQL_CODE_TIMESTAMP  3
+#
+DATETIME_SUB_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE = java.sql.Types::DATE) \
+\\\n	THEN 1 \
+\\\n	ELSE (CASE WHEN (JDBC_SUBQUERY.DATA_TYPE = java.sql.Types::TIME) \
+\\\n		THEN 2 \
+\\\n		ELSE (CASE WHEN (JDBC_SUBQUERY.DATA_TYPE = java.sql.Types::TIMESTAMP) \
+\\\n			THEN 3 \
+\\\n			ELSE CAST (NULL AS SMALLINT) END ) END ) END
+
+# ----------
+#
+# UNSIGNED_ATTRIBUTE:
+# Set UNSIGNED_ATTRIBUTE, which defaults to "true"
+# for non-numeric types in JDBC, to be NULL for
+# non-numeric types according the definition as
+# given in the ODBC specification.  Note that
+# any metadata statement requiring this fragment
+# must already have a column named "DATA_TYPE"
+# and a column named "UNSIGNED_ATTRIBUTE" as
+# part of the JDBC subquery's result set.
+#
+UNSIGNED_ATTR_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::DECIMAL, \
+\\\n	java.sql.Types::NUMERIC, java.sql.Types::INTEGER, \
+\\\n	java.sql.Types::SMALLINT, java.sql.Types::TINYINT, \
+\\\n	java.sql.Types::BIGINT, java.sql.Types::DOUBLE, \
+\\\n	java.sql.Types::FLOAT, java.sql.Types::REAL, \
+\\\n	java.sql.Types::DATE, java.sql.Types::TIME, \
+\\\n	java.sql.Types::TIMESTAMP)) \
+\\\n		THEN JDBC_SUBQUERY.UNSIGNED_ATTRIBUTE \
+\\\n		ELSE CAST (NULL AS SMALLINT) END
+
+# ----------
+#
+# AUTO_UNIQUE_VAL:
+# Set AUTO_UNIQUE_VAL, which defaults to "false"
+# for non-numeric types in JDBC, to be NULL for
+# non-numeric types according the definition as
+# given in the ODBC specification.  Note that
+# any metadata statement requiring this fragment
+# must already have a column named "DATA_TYPE"
+# and a column named "AUTO_UNIQUE_VAL" as
+# part of the JDBC subquery's result set.
+#
+AUTO_UNIQUE_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::DECIMAL, \
+\\\n	java.sql.Types::NUMERIC, java.sql.Types::INTEGER, \
+\\\n	java.sql.Types::SMALLINT, java.sql.Types::TINYINT, \
+\\\n	java.sql.Types::BIGINT, java.sql.Types::DOUBLE, \
+\\\n	java.sql.Types::FLOAT, java.sql.Types::REAL, \
+\\\n	java.sql.Types::DATE, java.sql.Types::TIME, \
+\\\n	java.sql.Types::TIMESTAMP)) \
+\\\n		THEN JDBC_SUBQUERY.AUTO_UNIQUE_VAL \
+\\\n		ELSE CAST (NULL AS SMALLINT) END
+
+# ----------
+#
+# NUM_PREC_RADIX:
+# Set NUM_PREC_RADIX, which is "10" for datetime
+# values in JDBC, to be "2" for datetime values
+# in ODBC, as given in the ODBC specification.
+# Note that any metadata statement requiring this
+# fragment must already have a column named
+# "DATA_TYPE" and a column named "NUM_PREC_RADIX"
+# as part of the JDBC subquery's result set.
+#
+RADIX_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::DATE, \
+\\\n	java.sql.Types::TIME, java.sql.Types::TIMESTAMP)) \
+\\\n		THEN CAST (2 AS SMALLINT) \
+\\\n		ELSE JDBC_SUBQUERY.NUM_PREC_RADIX END
+
+# ----------
+#
+# DECIMAL_DIGITS:
+# Set DECIMAL_DIGITS to be NULL for DATE columns
+# in ODBC, as given in the ODBC specification.
+# Note that any metadata statement requiring this
+# fragment must already have a column named
+# "DATA_TYPE" and a column named "DECIMAL_DIGITS"
+# as part of the JDBC subquery's result set.
+#
+DECIMAL_DIGITS_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::DATE)) \
+\\\n		THEN CAST (NULL AS SMALLINT) \
+\\\n		ELSE JDBC_SUBQUERY.DECIMAL_DIGITS END
+
+# ----------
+#
+# Columns that need to be added to the getProcedureColumns result
+# set for ODBC compliance.
+#
+GET_PROC_COLS_NEW_COLS=\
+CAST (NULL AS VARCHAR(254)) AS COLUMN_DEF, \
+\\\nCAST (({SQL_DATA_TYPE_FOR_ODBC}) AS SMALLINT) \
+\\\n	AS SQL_DATA_TYPE, \
+\\\nCAST (({DATETIME_SUB_FOR_ODBC}) AS SMALLINT) \
+\\\n	AS SQL_DATETIME_SUB, \
+\\\nCASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::CHAR, \
+\\\n		java.sql.Types::VARCHAR, java.sql.Types::BINARY, \
+\\\n		java.sql.Types::VARBINARY)) \
+\\\n			THEN JDBC_SUBQUERY.BUFFER_LENGTH \
+\\\n			ELSE CAST (NULL AS INT) END \
+\\\n		AS CHAR_OCTET_LENGTH, \
+\\\nCAST ((JDBC_SUBQUERY.PARAMETER_ID + 1) AS INT) AS ORDINAL_POSITION, \
+\\\nCAST ((CASE WHEN (JDBC_SUBQUERY.NULLABLE IN \
+\\\n		(java.sql.DatabaseMetaData::procedureNullable)) \
+\\\n			THEN 'YES' \
+\\\n			ELSE 'NO' END) \
+\\\n		AS VARCHAR(128)) AS IS_NULLABLE
+
+# ----------
+#
+# Columns that need to be added to the getTypeInfo result
+# set for ODBC compliance.
+#
+GET_TYPE_INFO_NEW_COLS=\
+CAST (NULL AS SMALLINT) AS INTERVAL_PRECISION
+
+# ----------
+#
+# In order to correctly determine the BUFFER_LENGTH
+# and CHAR_OCTET_LENGTH values for the ODBC version
+# of getColumns, we need to retrieve the max width
+# value of the column in question.  Since this
+# specific value isn't returned as part of the JDBC
+# metadata, we need to add it as a "helper" column
+# to the JDBC subquery result set.  See the
+# addHelperColsToSubquery method in the ODBC meta-
+# data generator class for more details.
+#
+GET_COLS_HELPER_COLS=, \
+\\\n		C.COLUMNDATATYPE.getMaximumWidth() AS COL_MAX_WIDTH
+
+# ----------
+#
+# In order to correctly determine the BUFFER_LENGTH
+# value for the ODBC versions of the getBestRow*
+# queries, we need to retrieve the max width
+# value of the column in question.  Since this
+# specific value isn't returned as part of the JDBC
+# metadata, we need to add it as a "helper" column
+# to the JDBC subquery result set.  See the
+# addHelperColsToSubquery method in the ODBC meta-
+# data generator class for more details.
+#
+BEST_ROW_ID_HELPER_COLS=, \
+\\\n		COLS.COLUMNDATATYPE.getMaximumWidth() AS COL_MAX_WIDTH
+
+# ----------
+#
+# BUFFER_LENGTH:
+# Set BUFFER_LENGTH, which is unused by JDBC (and
+# thus returns NULL for JDBC), to the value as
+# defined by the ODBC specification.  Note that
+# any metadata statement requiring this fragment
+# must already have a column named "DATA_TYPE"
+# and a "helper" column named "COL_MAX_WIDTH"
+# as part of the JDBC subquery's result set.
+#
+BUFFER_LEN_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::CHAR, \
+\\\n	java.sql.Types::VARCHAR)) \
+\\\n		THEN (CASE WHEN (JDBC_SUBQUERY.COL_MAX_WIDTH * 2.0 > 2147483647) \
+\\\n			THEN 2147483647 \
+\\\n			ELSE (JDBC_SUBQUERY.COL_MAX_WIDTH * 2) END) \
+\\\n		ELSE (CASE WHEN (JDBC_SUBQUERY.COL_MAX_WIDTH > 2147483647) \
+\\\n			THEN 2147483647 \
+\\\n			ELSE JDBC_SUBQUERY.COL_MAX_WIDTH END) END
+
+# ----------
+#
+# CHAR_OCTET_LENGTH:
+# Make CHAR_OCTET_LENGTH, which only applies to
+# char cols in JDBC, apply to both char and binary
+# columns per the ODBC specification.  Note that
+# any metadata statement requiring this fragment
+# must already have a column named "DATA_TYPE"
+# and a "helper" column named "COL_MAX_WIDTH"
+# as part of the JDBC subquery's result set.
+#
+CHAR_OCTET_FOR_ODBC=\
+CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (java.sql.Types::CHAR, \
+\\\n	java.sql.Types::VARCHAR)) \
+\\\n		THEN (CASE WHEN (JDBC_SUBQUERY.COL_MAX_WIDTH * 2.0 > 2147483647) \
+\\\n			THEN 2147483647 \
+\\\n			ELSE (JDBC_SUBQUERY.COL_MAX_WIDTH * 2) END) \
+\\\n		ELSE (CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN ( \
+\\\n			java.sql.Types::BINARY, java.sql.Types::VARBINARY)) \
+\\\n				THEN (CASE WHEN (JDBC_SUBQUERY.COL_MAX_WIDTH > 2147483647) \
+\\\n					THEN 2147483647 \
+\\\n					ELSE JDBC_SUBQUERY.COL_MAX_WIDTH END) \
+\\\n				ELSE CAST(NULL AS INT) END) END
+

Propchange: incubator/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/derby/code/trunk/java/drda/build.xml
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/drda/build.xml?view=diff&r1=156208&r2=156209
==============================================================================
--- incubator/derby/code/trunk/java/drda/build.xml (original)
+++ incubator/derby/code/trunk/java/drda/build.xml Fri Mar  4 16:20:59 2005
@@ -38,8 +38,64 @@
     <ant dir="${derby.drda.dir}/impl/drda"/>
     <ant dir="${derby.drda.dir}/drda"/>
     <ant dir="${derby.drda.dir}/loc/drda"/>
+    <antcall target="odbcMeta"/>
   </target>
 
+<!-- Generate ODBC-compliant metadata statements from JDBC metadata       -->
+<!-- Doing so involves three steps:                                       -->
+<!-- 1. Get a fresh copy of the metadata.properties file (from the source -->
+<!--    dir, since that one hasn't been modified by the ODBC generation   -->
+<!--    code), to be safe.                                                -->
+<!-- 2. Run the ODBC metadata generator program, which reads the metadata -->
+<!--    file from step 1 and creates an output file that has all of the   -->
+<!--    queries from metadata.properties PLUS any ODBC versions of those  -->
+<!--    queries that are required for ODBC compliance.  The name of this  -->
+<!--    output file is "odbc_metadata.properties".                        -->
+<!-- 3. Move odbc_metadata.properties to clobber the metadata.properties  -->
+<!--    file that is in the BUILD directory (not the one in the source    -->
+<!--    dir).                                                             -->
+
+  <target name="odbcMeta" depends="odbcprops,verifyodbcgen,noODBC,chkodbcgen" unless="odbcgen.done">
+    <delete file="${metadata.out.dir}/metadata.properties"/>
+    <copy file="${derby.metadata.src.dir}/metadata.properties" tofile="${metadata.out.dir}/metadata.properties"/>
+    <java classname="org.apache.derbyBuild.ODBCMetadataGenerator"
+          classpath="${out.dir}"
+          dir="${metadata.out.dir}"
+          fork="yes"
+          failonerror="true">
+    </java>
+    <move file="${metadata.out.dir}/odbc_metadata.properties" tofile="${metadata.out.dir}/metadata.properties"/>
+    <touch file="${odbc.donefile}"/>
+  </target>
+
+  <target name="odbcprops">
+    <property name="odbc.donefile" value="${metadata.out.dir}/odbcmeta.done"/>
+  </target>
+
+  <target name="chkodbcgen">
+    <uptodate property="odbcgen.done"
+              targetfile="${odbc.donefile}" >
+      <srcfiles dir="${metadata.out.dir}" includes="metadata.properties" />
+      <srcfiles dir="${out.dir}/org/apache/derbyBuild" includes="ODBCMetadataGenerator.class,odbcgen_fragments.properties" />
+    </uptodate>
+  </target>
+
+  <target name="verifyodbcgen">
+    <available classname="org.apache.derbyBuild.ODBCMetadataGenerator"
+               property="odbcgen.available">
+      <classpath>
+        <pathelement path="${out.dir}"/>
+      </classpath>
+    </available>
+  </target>
+
+  <target name="noODBC" unless="odbcgen.available">
+    <echo message=""/>
+    <echo message="***** ODBC Metadata not available *****"/>
+    <echo message=" ***** Run &quot;all&quot; target first *****"/>
+    <echo message=""/>
+    <fail message="  Exiting ant build..."/>
+  </target>
 
 <!--             ============= End Targets ==============                -->
 

Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java?view=diff&r1=156208&r2=156209
==============================================================================
--- incubator/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java (original)
+++ incubator/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java Fri Mar  4 16:20:59 2005
@@ -40,6 +40,7 @@
 import org.apache.derby.impl.jdbc.Util;
 import org.apache.derby.impl.load.Export;
 import org.apache.derby.impl.load.Import;
+import org.apache.derby.impl.jdbc.EmbedDatabaseMetaData;
 
 import org.apache.derby.impl.sql.execute.JarDDL;
 import org.apache.derby.iapi.util.IdUtil;
@@ -60,6 +61,8 @@
 
 	private final static int SQL_BEST_ROWID = 1;
 	private final static int SQL_ROWVER = 2;
+	private final static String DRIVER_TYPE_OPTION = "DATATYPE";
+	private final static String ODBC_DRIVER_OPTION = "'ODBC'";
 
 	/**
 	  Method used by Cloudscape Network Server to get localized message (original call
@@ -178,11 +181,16 @@
 	 *  @param schemaName  SYSIBM.SQLProcedures SchemaName  varchar(128),
 	 *  @param procName    SYSIBM.SQLProcedures ProcName    varchar(128),
 	 *  @param options     SYSIBM.SQLProcedures Options     varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLPROCEDURES (String catalogName, String schemaName, String procName,
 										String options, ResultSet[] rs) throws SQLException
 	{
-		rs[0] = getDMD().getProcedures(catalogName, schemaName, procName);
+		rs[0] = isForODBC(options)
+			? ((EmbedDatabaseMetaData)getDMD()).getProceduresForODBC(
+				catalogName, schemaName, procName)
+			: getDMD().getProcedures(catalogName, schemaName, procName);
 	}
 
 	/**
@@ -306,8 +314,7 @@
 		else
 			return options.substring(valueStart + 1, valueEnd);
 	}
-
-
+	
 	/**
 	 *  Map SQLProcedureCols to EmbedDatabaseMetaData.getProcedureColumns
 	 *
@@ -318,12 +325,17 @@
 	 *  @param procName    SYSIBM.SQLProcedureCols ProcName    varchar(128),
 	 *  @param paramName   SYSIBM.SQLProcedureCols ParamName   varchar(128),
 	 *  @param options     SYSIBM.SQLProcedureCols Options     varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLPROCEDURECOLS (String catalogName, String schemaName, String procName,
 										String paramName, String options, ResultSet[] rs)
 		throws SQLException
 	{
-		rs[0] = getDMD().getProcedureColumns(catalogName, schemaName, procName, paramName);
+		rs[0] = isForODBC(options)
+			? ((EmbedDatabaseMetaData)getDMD()).getProcedureColumnsForODBC(
+				catalogName, schemaName, procName, paramName)
+			: getDMD().getProcedureColumns(catalogName, schemaName, procName, paramName);
 	}
 
 	/**
@@ -336,12 +348,17 @@
 	 *  @param tableName   SYSIBM.SQLColumns TableName   varchar(128),
 	 *  @param columnName  SYSIBM.SQLColumns ColumnName  varchar(128),
 	 *  @param options     SYSIBM.SQLColumns Options     varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLCOLUMNS (String catalogName, String schemaName, String tableName,
 										String columnName, String options, ResultSet[] rs)
 		throws SQLException
 	{
-		rs[0] = getDMD().getColumns(catalogName, schemaName, tableName, columnName);
+		rs[0] = isForODBC(options)
+			? ((EmbedDatabaseMetaData)getDMD()).getColumnsForODBC(
+				catalogName, schemaName, tableName, columnName)
+			: getDMD().getColumns(catalogName, schemaName, tableName, columnName);
 	}
 
 	/**
@@ -388,11 +405,16 @@
 	 *  @param schemaName  SYSIBM.SQLPrimaryKeys SchemaName  varchar(128),
 	 *  @param tableName   SYSIBM.SQLPrimaryKeys TableName   varchar(128),
 	 *  @param options     SYSIBM.SQLPrimaryKeys Options     varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLPRIMARYKEYS (String catalogName, String schemaName, String tableName, String options, ResultSet[] rs)
 		throws SQLException
 	{
-		rs[0] = getDMD().getPrimaryKeys(catalogName, schemaName, tableName);
+		rs[0] = isForODBC(options)
+			? ((EmbedDatabaseMetaData)getDMD()).getPrimaryKeysForODBC(
+				catalogName, schemaName, tableName)
+			: getDMD().getPrimaryKeys(catalogName, schemaName, tableName);
 	}
 
 	/**
@@ -401,11 +423,15 @@
 	 *  @param resultset output parameter, the resultset object containing the result of getTypeInfo
 	 *  @param datatType SYSIBM.SQLGetTypeInfo DataType smallint,
 	 *  @param options   SYSIBM.SQLGetTypeInfo Options  varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLGETTYPEINFO (short dataType, String options, ResultSet[] rs)
 		throws SQLException
 	{
-		rs[0] = getDMD().getTypeInfo();
+		rs[0] = isForODBC(options)
+			? ((EmbedDatabaseMetaData)getDMD()).getTypeInfoForODBC()
+			: getDMD().getTypeInfo();
 	}
 
 	/**
@@ -419,6 +445,8 @@
 	 *  @param unique      SYSIBM.SQLStatistics Unique      smallint; 0=SQL_INDEX_UNIQUE(0); 1=SQL_INDEX_ALL(1),
 	 *  @param approximate SYSIBM.SQLStatistics Approximate smallint; 1=true; 0=false,
 	 *  @param options     SYSIBM.SQLStatistics Options     varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLSTATISTICS (String catalogName, String schemaName, String tableName,
 										short unique, short approximate, String options, ResultSet[] rs)
@@ -427,7 +455,10 @@
 		boolean boolUnique = (unique == 0) ? true: false;
 		boolean boolApproximate = (approximate == 1) ? true: false;
 			
-		rs[0] = getDMD().getIndexInfo(catalogName, schemaName, tableName, boolUnique, boolApproximate);
+		rs[0] = isForODBC(options)
+			? ((EmbedDatabaseMetaData)getDMD()).getIndexInfoForODBC(
+				catalogName, schemaName, tableName, boolUnique, boolApproximate)
+			: getDMD().getIndexInfo(catalogName, schemaName, tableName, boolUnique, boolApproximate);
 	}
 
 	/**
@@ -443,6 +474,8 @@
 	 *  @param scope       SYSIBM.SQLSpecialColumns Scope       smallint,
 	 *  @param nullable    SYSIBM.SQLSpecialColumns Nullable    smallint; 0=false, 1=true,
 	 *  @param options     SYSIBM.SQLSpecialColumns Options     varchar(4000))
+	 *  	If options contains the string 'DATATYPE='ODBC'', call the ODBC
+	 *  	version of this procedure.
 	 */
 	public static void SQLSPECIALCOLUMNS (short colType, String catalogName, String schemaName, String tableName,
 										short scope, short nullable, String options, ResultSet[] rs)
@@ -452,11 +485,17 @@
 		boolean boolNullable = (nullable == 1) ? true: false;
 		if (colType == SQL_BEST_ROWID)
 		{
-			rs[0] = getDMD().getBestRowIdentifier(catalogName, schemaName, tableName, scope, boolNullable);
+			rs[0] = isForODBC(options)
+				? ((EmbedDatabaseMetaData)getDMD()).getBestRowIdentifierForODBC(
+					catalogName, schemaName, tableName, scope, boolNullable)
+				: getDMD().getBestRowIdentifier(catalogName, schemaName, tableName, scope, boolNullable);
 		}
 		else // colType must be SQL_ROWVER
 		{
-			rs[0] = getDMD().getVersionColumns(catalogName, schemaName, tableName);
+			rs[0] = isForODBC(options)
+				? ((EmbedDatabaseMetaData)getDMD()).getVersionColumnsForODBC(
+					catalogName, schemaName, tableName)
+				: getDMD().getVersionColumns(catalogName, schemaName, tableName);
 		}
 	}
 
@@ -517,6 +556,18 @@
 		PreparedStatement ps = conn.prepareStatement("execute statement SYSIBM.METADATA");
 		rs[0] = ps.executeQuery();
 		conn.close();
+	}
+
+	/**
+	 * Helper for ODBC metadata calls.
+	 * @param options	String containig the options to search through.
+	 * @return True if options contain ODBC indicator; false otherwise.
+	 */
+	private static boolean isForODBC(String options) {
+
+		String optionValue = getOption(DRIVER_TYPE_OPTION, options);
+		return ((optionValue != null) && optionValue.toUpperCase().equals(ODBC_DRIVER_OPTION));
+
 	}
 
     /**

Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedDatabaseMetaData.java
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedDatabaseMetaData.java?view=diff&r1=156208&r2=156209
==============================================================================
--- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedDatabaseMetaData.java (original)
+++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedDatabaseMetaData.java Fri Mar  4 16:20:59 2005
@@ -1419,7 +1419,34 @@
 	public ResultSet getProcedures(String catalog, String schemaPattern,
 			String procedureNamePattern) throws SQLException {
 
-		PreparedStatement s = getPreparedQuery("getProcedures");
+		return doGetProcs(catalog, schemaPattern,
+			procedureNamePattern, "getProcedures");
+	}
+
+	/**
+	 * Get a description of stored procedures available in a
+	 * catalog.  Same as getProcedures() above, except that
+	 * the result set will conform to ODBC specifications.
+	 */
+	public ResultSet getProceduresForODBC(String catalog, String schemaPattern,
+			String procedureNamePattern) throws SQLException {
+
+		return doGetProcs(catalog, schemaPattern,
+			procedureNamePattern, "odbc_getProcedures");
+	}
+
+	/**
+	 * Does the actual work for the getProcedures metadata calls.
+	 * See getProcedures() method above for parameter descriptions.
+	 * @param queryName Name of the query to execute; is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetProcs(String catalog, String schemaPattern,
+		String procedureNamePattern, String queryName)
+		throws SQLException {
+
+		PreparedStatement s = getPreparedQuery(queryName);
 		s.setString(1, swapNull(catalog));
 		s.setString(2, swapNull(schemaPattern));
 		s.setString(3, swapNull(procedureNamePattern));
@@ -1487,8 +1514,38 @@
 			String procedureNamePattern,
 			String columnNamePattern) throws SQLException {
 
+		return doGetProcCols(catalog, schemaPattern,
+			procedureNamePattern, columnNamePattern,
+			"getProcedureColumns");
+	}
+
+	/**
+	 * Get a description of a catalog's stored procedure parameters
+	 * and result columns.  Same as getProcedureColumns() above,
+	 * except that the result set will conform to ODBC specifications.
+	 */
+	public ResultSet getProcedureColumnsForODBC(String catalog,
+			String schemaPattern, String procedureNamePattern,
+			String columnNamePattern) throws SQLException {
+
+		return doGetProcCols(catalog, schemaPattern,
+			procedureNamePattern, columnNamePattern,
+			"odbc_getProcedureColumns");
+	}
+
+	/**
+	 * Does the actual work for the getProcedureColumns metadata
+	 * calls. See getProcedureColumns() method above for parameter
+	 * descriptions.
+	 * @param queryName Name of the query to execute; is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetProcCols(String catalog, String schemaPattern,
+			String procedureNamePattern, String columnNamePattern,
+			String queryName) throws SQLException {
 
-		PreparedStatement s = getPreparedQuery("getProcedureColumns");
+		PreparedStatement s = getPreparedQuery(queryName);
 		// 
                 // catalog is not part of the query
                 //
@@ -1687,7 +1744,36 @@
 	public ResultSet getColumns(String catalog, String schemaPattern,
 		String tableNamePattern, String columnNamePattern)
 					throws SQLException {
-		PreparedStatement s = getPreparedQuery("getColumns");
+
+		return doGetCols(catalog, schemaPattern, tableNamePattern,
+			columnNamePattern, "getColumns");
+	}
+
+	/**
+	 * Get a description of table columns available in a catalog.
+	 * Same as getColumns() above, except that the result set
+	 * will conform to ODBC specifications.
+	 */
+	public ResultSet getColumnsForODBC(String catalog, String schemaPattern,
+		String tableNamePattern, String columnNamePattern)
+		throws SQLException {
+
+		return doGetCols(catalog, schemaPattern, tableNamePattern,
+			columnNamePattern, "odbc_getColumns");
+	}
+
+	/**
+	 * Does the actual work for the getColumns metadata calls.
+	 * See getColumns() method above for parameter descriptions.
+	 * @param queryName Name of the query to execute; is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetCols(String catalog, String schemaPattern,
+		String tableNamePattern, String columnNamePattern,
+		String queryName) throws SQLException {
+
+		PreparedStatement s = getPreparedQuery(queryName);
 		s.setString(1, swapNull(catalog));
 		s.setString(2, swapNull(schemaPattern));
 		s.setString(3, swapNull(tableNamePattern));
@@ -1821,6 +1907,37 @@
 		boolean nullable
 	) throws SQLException
 	{
+		return doGetBestRowId(catalogPattern, schemaPattern, tablePattern,
+			scope, nullable, "");
+	}
+
+	/**
+	 * Get a description of a table's optimal set of columns that
+	 * uniquely identifies a row. They are ordered by SCOPE.
+	 * Same as getBestRowIdentifier() above, except that the result
+	 * set will conform to ODBC specifications.
+	 */
+	public ResultSet getBestRowIdentifierForODBC(String catalogPattern,
+		String schemaPattern, String tablePattern, int scope,
+		boolean nullable) throws SQLException {
+
+		return doGetBestRowId(catalogPattern, schemaPattern, tablePattern,
+			scope, nullable, "odbc_");
+	}
+
+	/**
+	 * Does the actual work for the getBestRowIdentifier metadata
+	 * calls.  See getBestRowIdentifier() method above for parameter
+	 * descriptions.
+	 * @param queryPrefix Prefix to be appended to the names of
+	 *	the queries used in this method.  This is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetBestRowId(String catalogPattern,
+		String schemaPattern, String tablePattern, int scope,
+		boolean nullable, String queryPrefix) throws SQLException {
+
 		int nullableInIntForm = 0;
 		if (nullable)
 			nullableInIntForm = 1;
@@ -1868,7 +1985,7 @@
 				// this one's it, do the real thing and return it.
 				// we don't need to check catalog, schema, table name
 				// or scope again.
-				ps = getPreparedQuery("getBestRowIdentifierPrimaryKeyColumns");
+				ps = getPreparedQuery(queryPrefix + "getBestRowIdentifierPrimaryKeyColumns");
 				ps.setString(1,constraintId);
 				ps.setString(2,constraintId);
 				// note, primary key columns aren't nullable,
@@ -1897,7 +2014,7 @@
 			if (done) 
 			{
 				// this one's it, do the real thing and return it.
-				ps = getPreparedQuery("getBestRowIdentifierUniqueKeyColumns");
+				ps = getPreparedQuery(queryPrefix + "getBestRowIdentifierUniqueKeyColumns");
 				ps.setString(1,constraintId);
 				ps.setString(2,constraintId);
 				ps.setInt(3,nullableInIntForm);
@@ -1927,7 +2044,7 @@
 			ps.close();
 			if (done) {
 				// this one's it, do the real thing and return it.
-				ps = getPreparedQuery("getBestRowIdentifierUniqueIndexColumns");
+				ps = getPreparedQuery(queryPrefix + "getBestRowIdentifierUniqueIndexColumns");
 				ps.setLong(1,indexNum);
 				ps.setInt(2,nullableInIntForm);
 				return ps.executeQuery();
@@ -1935,7 +2052,7 @@
 
 			// last try -- just return all columns of the table
 			// the not null ones if that restriction is upon us.
-			ps = getPreparedQuery("getBestRowIdentifierAllColumns");
+			ps = getPreparedQuery(queryPrefix + "getBestRowIdentifierAllColumns");
 			ps.setString(1,catalogPattern);
 			ps.setString(2,schemaPattern);
 			ps.setString(3,tablePattern);
@@ -1976,7 +2093,32 @@
      */
 	public ResultSet getVersionColumns(String catalog, String schema,
 				String table) throws SQLException {
-		PreparedStatement s = getPreparedQuery("getVersionColumns");
+		return doGetVersionCols(catalog, schema, table, "getVersionColumns");
+	}
+
+	/**
+	 * Get a description of a table's columns that are automatically
+	 * updated when any value in a row is updated.  They are
+	 * unordered.  Same as getVersionColumns() above, except that
+	 * the result set will conform to ODBC specifications.
+	 */
+	public ResultSet getVersionColumnsForODBC(String catalog, String schema,
+				String table) throws SQLException {
+		return doGetVersionCols(catalog, schema, table, "odbc_getVersionColumns");
+	}
+
+	/**
+	 * Does the actual work for the getVersionColumns metadata
+	 * calls.  See getVersionColumns() method above for parameter
+	 * descriptions.
+	 * @param queryName Name of the query to execute; is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetVersionCols(String catalog, String schema,
+		String table, String queryName) throws SQLException {
+
+		PreparedStatement s = getPreparedQuery(queryName);
 		s.setString(1, swapNull(catalog));
 		s.setString(2, swapNull(schema));
 		s.setString(3, swapNull(table));
@@ -2007,7 +2149,31 @@
      */
 	public ResultSet getPrimaryKeys(String catalog, String schema,
 				String table) throws SQLException {
-		PreparedStatement s = getPreparedQuery("getPrimaryKeys");
+		return doGetPrimaryKeys(catalog, schema, table, "getPrimaryKeys");
+	}
+
+	/**
+	 * Get a description of a table's primary key columns.  They
+	 * are ordered by COLUMN_NAME.  Same as getPrimaryKeys above,
+	 * except that the result set will conform to ODBC specifications.
+	 */
+	public ResultSet getPrimaryKeysForODBC(String catalog, String schema,
+				String table) throws SQLException {
+		return doGetPrimaryKeys(catalog, schema, table, "odbc_getPrimaryKeys");
+	}
+
+	/**
+	 * Does the actual work for the getPrimaryKeys metadata
+	 * calls.  See getPrimaryKeys() method above for parameter
+	 * descriptions.
+	 * @param queryName Name of the query to execute; is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetPrimaryKeys(String catalog, String schema,
+		String table, String queryName) throws SQLException {
+
+		PreparedStatement s = getPreparedQuery(queryName);
 		s.setString(1, swapNull(catalog));
 		s.setString(2, swapNull(schema));
 		s.setString(3, swapNull(table));
@@ -2312,6 +2478,17 @@
 		return getSimpleQuery("getTypeInfo");
 	}
 
+	/**
+	 * Get 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.
+	 * Same as getTypeInfo above, except that the result set will
+	 * conform to ODBC specifications.
+	 */
+	public ResultSet getTypeInfoForODBC() throws SQLException {
+		return getSimpleQuery("odbc_getTypeInfo");
+	}
+
     /**
      * Get a description of a table's indices and statistics. They are
      * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION.
@@ -2367,9 +2544,36 @@
 	public ResultSet getIndexInfo(String catalog, String schema, String table,
 			boolean unique, boolean approximate)
 					throws SQLException {
+		return doGetIndexInfo(catalog, schema, table, unique, approximate, "getIndexInfo");
+	}
+
+	/**
+	 * Get a description of a table's indices and statistics. They are
+	 * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION.
+	 * Same as getIndexInfo above, except that the result set will
+	 * conform to ODBC specifications.
+	 */
+	public ResultSet getIndexInfoForODBC(String catalog, String schema, String table,
+		boolean unique, boolean approximate) throws SQLException
+	{
+		return doGetIndexInfo(catalog, schema, table, unique, approximate, "odbc_getIndexInfo");
+	}
+
+	/**
+	 * Does the actual work for the getIndexInfo metadata
+	 * calls.  See getIndexInfo() method above for parameter
+	 * descriptions.
+	 * @param queryName Name of the query to execute; is used
+	 *	to determine whether the result set should conform to
+	 *	JDBC or ODBC specifications.
+	 */
+	private ResultSet doGetIndexInfo(String catalog, String schema, String table,
+		boolean unique, boolean approximate, String queryName)
+		throws SQLException {
+
 		int approximateInInt = 0;
 		if (approximate) approximateInInt = 1;
-		PreparedStatement s = getPreparedQuery("getIndexInfo");
+		PreparedStatement s = getPreparedQuery(queryName);
 		s.setString(1, swapNull(catalog));
 		s.setString(2, swapNull(schema));
 		s.setString(3, swapNull(table));

Modified: incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/metadata.properties
URL: http://svn.apache.org/viewcvs/incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/metadata.properties?view=diff&r1=156208&r2=156209
==============================================================================
--- incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/metadata.properties (original)
+++ incubator/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/metadata.properties Fri Mar  4 16:20:59 2005
@@ -8,6 +8,18 @@
 # you can be pretty sure that the JDBC driver was looking for a result set,
 # because it specifies what it wants the column names to be.
 #
+# ** NOTE ** At build time, this file is treated as INPUT into an ODBC
+# query generation process that reads these queries and, where required,
+# performs alterations on them to create ODBC-compliant versions.  The
+# output of that query generation process is a file containing 1) all of
+# the queries in this file, PLUS 2) an additional set of ODBC-compliant
+# queries based on the queries here.  That automatically-generated file
+# is the one that makes it into the CLASSES directory, and the one from
+# which the Derby engine will load and process metadata queries.  That
+# said, please realize that changes you make here could affect the ODBC
+# metadata, as well--so in the even that you make any changes here, you
+# should make SURE you run all of the metadata tests (JDBC and ODBC alike)
+# to verify that the results are correct for BOTH types of clients.
 #
 # Note that property values can span multiple lines, by ending the line with a \
 #
@@ -248,7 +260,8 @@
 # parameter 3 = pattern for table name
 # parameter 4 = pattern for column name
 getColumnPrivileges=\
-	SELECT * \
+	SELECT TABLE_CAT, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, \
+		GRANTOR, GRANTEE, PRIVILEGE, IS_GRANTABLE \
 	FROM ( VALUES (CAST ('' AS VARCHAR(128)), CAST ('' AS VARCHAR(128)), \
 		CAST ('' AS VARCHAR(128)), CAST ('' AS VARCHAR(128)), \
 		CAST ('' AS VARCHAR(128)), CAST ('' AS VARCHAR(128)), \
@@ -266,7 +279,8 @@
 # parameter 2 = pattern for schema name
 # parameter 3 = pattern for table name
 getTablePrivileges=\
-	SELECT * \
+	SELECT TABLE_CAT, TABLE_SCHEM, TABLE_NAME, \
+		GRANTOR, GRANTEE, PRIVILEGE, IS_GRANTABLE \
 	FROM ( VALUES (CAST ('' AS VARCHAR(128)), CAST ('' AS VARCHAR(128)), \
 		CAST ('' AS VARCHAR(128)), CAST ('' AS VARCHAR(128)), \
 		CAST ('' AS VARCHAR(128)), CAST ('' AS VARCHAR(128)), \
@@ -282,7 +296,8 @@
 # parameter 2 = pattern for schema name
 # parameter 3 = pattern for table name
 getVersionColumns=\
-	SELECT * \
+	SELECT SCOPE, COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_SIZE, \
+		BUFFER_LENGTH, DECIMAL_DIGITS, PSEUDO_COLUMN \
 	FROM ( VALUES (1, CAST ('' AS VARCHAR(128)), 1, CAST ('' AS VARCHAR(128)), 1, 1, 1, 1) ) \
 		AS VERSIONCOLUMNS (SCOPE, COLUMN_NAME, DATA_TYPE, \
 			TYPE_NAME, COLUMN_SIZE, BUFFER_LENGTH, \
@@ -658,7 +673,8 @@
 # of the right shape 
 #
 getBestRowIdentifierEmpty=\
-	SELECT * \
+	SELECT SCOPE, COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_SIZE, \
+		BUFFER_LENGTH, DECIMAL_DIGITS, PSEUDO_COLUMN \
 	FROM (VALUES \
 		(CAST (2  AS SMALLINT), \
 		 CAST ('' AS VARCHAR(128)), \



Mime
View raw message