All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.TVP Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
//---------------------------------------------------------------------------------------------------------------------------------
// File: TVP.java
//
//
// Microsoft JDBC Driver for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), 
//  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
//  IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
 
 
package com.microsoft.sqlserver.jdbc;

import java.util.*;
import java.util.Map.Entry;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.MessageFormat;

enum TVPType
{
	ResultSet,
	ISQLServerDataRecord,
	SQLServerDataTable,
	Null
}

class TVP {

	String TVPName;
	String TVP_owningSchema;
	String TVP_dbName;
	ResultSet sourceResultSet = null;
	SQLServerDataTable sourceDataTable = null;
	Map columnMetadata = null;
	Iterator> sourceDataTableRowIterator = null;
	ISQLServerDataRecord sourceRecord = null;
	
	TVPType tvpType = null;
	//MultiPartIdentifierState
	enum MPIState
	{
		MPI_Value,
		MPI_ParseNonQuote,
		MPI_LookForSeparator,
		MPI_LookForNextCharOrSeparator,
		MPI_ParseQuote,
		MPI_RightQuote,
	};
    	
	void initTVP(TVPType type, String tvpPartName) throws SQLServerException
	{
		tvpType = type;
		columnMetadata = new LinkedHashMap();
		parseTypeName(tvpPartName);		
	}
	
	TVP(String tvpPartName) throws SQLServerException
	{
		initTVP(TVPType.Null, tvpPartName);
	}

	// Name used in CREATE TYPE 
	TVP(String tvpPartName, SQLServerDataTable tvpDataTable) throws SQLServerException
	{
		initTVP(TVPType.SQLServerDataTable, tvpPartName);
		sourceDataTable = tvpDataTable;
		sourceDataTableRowIterator = sourceDataTable.getIterator();
		populateMetadataFromDataTable();		
	}

	TVP(String tvpPartName, ResultSet tvpResultSet) throws SQLServerException
	{
		initTVP(TVPType.ResultSet, tvpPartName);
		sourceResultSet = tvpResultSet;
		// Populate TVP metdata from ResultSetMetadta.
		populateMetadataFromResultSet();
	}

	TVP(String tvpPartName, ISQLServerDataRecord tvpRecord) throws SQLServerException
	{
		initTVP(TVPType.ISQLServerDataRecord, tvpPartName);
		sourceRecord = tvpRecord;
		// Populate TVP metdata from ISQLServerDataRecord.
		populateMetadataFromDataRecord();
		
		// validate sortOrdinal and throw all relavent exceptions before proceeding
		validateOrderProperty();
	}
	
	boolean isNull()
	{
		return (TVPType.Null == tvpType);
	}
	
	Object[] getRowData() throws SQLServerException
	{
		if (TVPType.ResultSet == tvpType)
		{
			int colCount = columnMetadata.size();
			Object[] rowData = new Object[colCount];
			for (int i = 0; i < colCount; i++)
			{
				try
				{
					rowData[i] = sourceResultSet.getObject(i + 1);
				}
				catch(SQLException e)
				{
		            throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);            									
				}
			}
			return rowData;
		}
		else if (TVPType.SQLServerDataTable == tvpType)
		{
			 Map.Entry rowPair = (Map.Entry) sourceDataTableRowIterator.next();
			 return rowPair.getValue();	
		}
		else
			return sourceRecord.getRowData();
	}
	
	boolean next() throws SQLServerException
	{
		if (TVPType.ResultSet == tvpType)
		{
			try
			{
				return sourceResultSet.next();
			}
			catch(SQLException e)
			{
	            throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveSourceData"), e);            				
			}
		}
		else if (TVPType.SQLServerDataTable == tvpType)
		{
			return sourceDataTableRowIterator.hasNext();
		}		
		else
			return sourceRecord.next();
	}
	
	void populateMetadataFromDataTable() throws SQLServerException
	{
		assert null != sourceDataTable;
		
		Map dataTableMetaData = sourceDataTable.getColumnMetadata();
		if (null == dataTableMetaData || dataTableMetaData.isEmpty())
		{
            throw new SQLServerException(
                    SQLServerException.getErrString("R_TVPEmptyMetadata"),
                    null);
		}
		Iterator> columnsIterator = dataTableMetaData.entrySet().iterator();
		while(columnsIterator.hasNext())
		{
			Map.Entry pair = (Map.Entry)columnsIterator.next();
			// duplicate column names for the dataTable will be checked in the SQLServerDataTable.
			columnMetadata.put(
					pair.getKey(), 
					new SQLServerMetaData(
							pair.getValue().columnName,
							pair.getValue().javaSqlType,
							pair.getValue().precision,
							pair.getValue().scale));			
		}				
	}

	void populateMetadataFromResultSet() throws SQLServerException
	{
		assert null != sourceResultSet;
		try
		{
			ResultSetMetaData rsmd = sourceResultSet.getMetaData();
			for (int i = 0; i < rsmd.getColumnCount(); i++)
			{
				SQLServerMetaData columnMetaData = new SQLServerMetaData(
						rsmd.getColumnName(i+1), 
						rsmd.getColumnType(i+1),
						rsmd.getPrecision(i + 1),
						rsmd.getScale(i + 1));
				columnMetadata.put(i, columnMetaData);
			}
		}
		catch(SQLException e)
		{
            throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);                        
		}
	}

	void populateMetadataFromDataRecord() throws SQLServerException
	{
		assert null != sourceRecord;
		if (0 >= sourceRecord.getColumnCount())
		{
            throw new SQLServerException(
                    SQLServerException.getErrString("R_TVPEmptyMetadata"),
                    null);
		}
		for (int i = 0; i < sourceRecord.getColumnCount(); i++)
		{
			// Make a copy here as we do not want to change user's metadata.
			Util.checkDuplicateColumnName (sourceRecord.getColumnMetaData(i + 1).columnName, columnMetadata);
			SQLServerMetaData metaData = new SQLServerMetaData(sourceRecord.getColumnMetaData(i + 1));
			columnMetadata.put(i, metaData);
		}			
	}
	
	void validateOrderProperty() throws SQLServerException
	{
		int columnCount = columnMetadata.size();
		boolean[] sortOrdinalSpecified = new boolean[columnCount];
		
		int maxSortOrdinal = -1;
		int sortCount = 0;
		Iterator> columnsIterator = columnMetadata.entrySet().iterator();
		while(columnsIterator.hasNext())
		{
			Map.Entry columnPair = (Map.Entry)columnsIterator.next();
			SQLServerSortOrder columnSortOrder = columnPair.getValue().sortOrder;
			int columnSortOrdinal = columnPair.getValue().sortOrdinal;
			
			if(SQLServerSortOrder.Unspecified != columnSortOrder)
			{
				// check if there's no way sort order could be monotonically increasing
				if (columnCount <= columnSortOrdinal)
				{
					MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPSortOrdinalGreaterThanFieldCount"));
			        throw new SQLServerException(form.format(new Object[] {columnSortOrdinal , columnPair.getKey()}), null, 0, null);
				}
				
				// Check to make sure we haven't seen this ordinal before
				if (sortOrdinalSpecified[columnSortOrdinal]) 
				{
                    MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPDuplicateSortOrdinal"));
			        throw new SQLServerException(form.format(new Object[] {columnSortOrdinal}), null, 0, null);
                }
				
				sortOrdinalSpecified[columnSortOrdinal] = true;
				if(columnSortOrdinal > maxSortOrdinal)
					maxSortOrdinal = columnSortOrdinal;
				
				sortCount++;
			}
		}
		
		if (0 < sortCount) 
		{
            // validate monotonically increasing sort order. watch for values outside of the sortCount range.
			if (maxSortOrdinal >= sortCount)
            {
                // there is at least one hole, find the first one
                int i;
                for (i = 0; i < sortCount; i++) 
                {
                    if (!sortOrdinalSpecified[i]) 
                        break;
                }
                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPMissingSortOrdinal"));
		        throw new SQLServerException(form.format(new Object[] {i}), null, 0, null);
            }
		}
	}
	
	void parseTypeName(String name) throws SQLServerException
	{		
		String leftQuote ="[\"";
		String rightQuote = "]\"";
		char separator = '.';
		int limit = 3;		// DbName, SchemaName, table type name
		String[] parsedNames = new String[limit];              
        int stringCount = 0;                        // index of current string in the buffer
        
		if((null == name) || (0 == name.length()))
		{
			MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidTVPName"));
			Object[] msgArgs = {};
			throw new SQLServerException(null , form.format(msgArgs) , null, 0 , false);   
		}        
        
        StringBuilder sb = new StringBuilder(name.length());
        
        // String buffer to hold white space used when parsing nonquoted strings  'a b .  c d' = 'a b' and 'c d'
        StringBuilder whitespaceSB = null;
        
        // Right quote character to use given the left quote character found.
        char rightQuoteChar = ' ';
        MPIState state = MPIState.MPI_Value;
        
        for (int index = 0; index < name.length(); ++index) 
        {
        	char testchar = name.charAt(index);
        	switch(state)
        	{
        	case MPI_Value:
        		int quoteIndex;
                if (Character.isWhitespace(testchar))     // skip the whitespace
                    continue;
                else if (testchar == separator) 
                {  
					// If separator was found,but no string was found, initialize the string we are parsing to Empty.
					parsedNames[stringCount] = "";
					stringCount++;
                }
                else if (-1 != (quoteIndex = leftQuote.indexOf(testchar))) 
                { 
					// If we are at left quote                                                                                                                          
					rightQuoteChar = rightQuote.charAt(quoteIndex); // record the corresponding right quote for the left quote
					sb.setLength(0);
					state = MPIState.MPI_ParseQuote;                                                          
                }        
                else if (-1 != rightQuote.indexOf(testchar)) 
                { 
					// If we shouldn't see a right quote
                	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
        			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);   
                }        
                else 
                {
					sb.setLength(0);
					sb.append (testchar);
					state = MPIState.MPI_ParseNonQuote;                                                                                  
                }
        		break;
        		
        	case MPI_ParseNonQuote: 
                if (testchar == separator) 
                {
                    parsedNames[stringCount] = sb.toString (); // set the currently parsed string
                    stringCount = incrementStringCount (parsedNames, stringCount);
                    state = MPIState.MPI_Value;
                }
                // Quotes are not valid inside a non-quoted name
                else if ((-1 != rightQuote.indexOf (testchar)) 
                		|| (-1 != leftQuote.indexOf(testchar)))
                {  
                	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
        			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);  
                }
                else if (Character.isWhitespace(testchar)) 
                { 
                	// If it is Whitespace 
                    parsedNames[stringCount] = sb.toString (); // Set the currently parsed string
                    if (null == whitespaceSB)
                        whitespaceSB = new StringBuilder ();
                    whitespaceSB.setLength(0);
                    // start to record the white space, if we are parsing a name like "foo bar" we should return "foo bar"
                    whitespaceSB.append (testchar);
                    state = MPIState.MPI_LookForNextCharOrSeparator;
                }
                else 
                    sb.append (testchar);                
        	
                break;
                
        	case MPI_LookForNextCharOrSeparator :                         
                if (!Character.isWhitespace(testchar)) 
                { 
                	// If it is not whitespace
                    if (testchar == separator) 
                    {
                    	stringCount = incrementStringCount (parsedNames, stringCount);
                        state = MPIState.MPI_Value;
                    }
                    else 
                    { 
                    	// If its not a separator and not whitespace
                        sb.append (whitespaceSB);
                        sb.append (testchar);
                        parsedNames[stringCount] = sb.toString (); // Need to set the name here in case the string ends here.
                        state = MPIState.MPI_ParseNonQuote;                                
                    }
                }
                else 
                {
                	 if (null == whitespaceSB)
                	 {
                         whitespaceSB = new StringBuilder ();
                	 }
                    whitespaceSB.append (testchar);
                }
                break;
                
        	case MPI_ParseQuote: 
        		// if are on a right quote see if we are escapeing the right quote or ending the quoted string
                if (testchar == rightQuoteChar)                             
                    state = MPIState.MPI_RightQuote;                             
                else 
                    sb.append (testchar); // Append what we are currently parsing
                break;
        	
        	case MPI_RightQuote : 
                if (testchar == rightQuoteChar) 
                { 
                   // If the next char is a another right quote then we were escapeing the right quote
                   sb.append (testchar);
                   state = MPIState.MPI_ParseQuote;                           
                }
                else if (testchar == separator) 
                {      
                   // If its a separator then record what we've parsed
                   parsedNames[stringCount] = sb.toString ();
                   stringCount = incrementStringCount (parsedNames, stringCount);
                   state = MPIState.MPI_Value;
                }
                else if (!Character.isWhitespace(testchar)) 
                { 
                	// If it is not white space we got problems
                	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
        			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);  
                }                        
                else 
                {                          
                	// It is a whitespace character
                	// the following char should be whitespace, separator, or end of string anything else is bad
                   parsedNames[stringCount] = sb.toString ();
                   state = MPIState.MPI_LookForSeparator;
                }
                break;
            
        	case MPI_LookForSeparator : 
                if (!Character.isWhitespace(testchar)) 
                { 
                	// If it is not whitespace
                    if (testchar == separator) 
                    { 
                    	// If it is a separator 
                    	stringCount = incrementStringCount (parsedNames, stringCount);
                        state = MPIState.MPI_Value;
                    }
                    else 
                    { 
                    	// not a separator
                    	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
            			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);  
                    }
                }
                break;
        	}
        }

        if(stringCount > limit-1)
        {
        	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);   
        }
        
        // Resolve final states after parsing the string            
        switch (state) {
            case MPI_Value :       // These states require no extra action
            case MPI_LookForSeparator :
            case MPI_LookForNextCharOrSeparator :
            break;
                
            case MPI_ParseNonQuote: // Dump what ever was parsed
            case MPI_RightQuote :
                parsedNames[stringCount] = sb.toString ();
            break;
            
            case MPI_ParseQuote: // Invalid Ending States
            default:
            	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
    			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);  
        }
        
        if (parsedNames[0] == null) 
        {
        	MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
			throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);   
        }
        else 
        {
            // Shuffle the parsed name, from left justification to right justification, ie [a][b][null][null] goes to [null][null][a][b]
            int offset = limit - stringCount - 1;
            if (offset > 0) {
                for (int x = limit - 1; x >= offset; --x) 
                {
                    parsedNames[x] = parsedNames[x - offset];
                    parsedNames[x - offset] = null;
                }
            }
        }
    	this.TVPName = parsedNames[2];
    	this.TVP_owningSchema= parsedNames[1];
    	this.TVP_dbName= parsedNames[0];
	}
	
    /* parsing the multipart identifer string.
     * paramaters: name - string to parse
     * leftquote:  set of characters which are valid quoteing characters to initiate a quote
     * rightquote: set of characters which are valid to stop a quote, array index's correspond to the the leftquote array.
     * separator:  separator to use
     * limit:      number of names to parse out
     * removequote:to remove the quotes on the returned string 
     */
	private int incrementStringCount(String[] ary, int position) throws SQLServerException 
	{
		++position;
		int limit = ary.length;
		if (position >= limit) 
		{
		MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidThreePartName"));
		throw new SQLServerException(null , form.format(new Object[]{}) , null, 0 , false);   
		}
		ary[position] = new String();
		return position;
	}
 
	String getTVPName()
	{
		return TVPName;
	}
	
	String getDbNameTVP()
	{
		return TVP_dbName;
	}
	
	String getOwningSchemaNameTVP()
	{
		return TVP_owningSchema;
	}
	
	int getTVPColumnCount()
	{
		return columnMetadata.size();
	}
	
	Map getColumnMetadata()
	{
		return columnMetadata;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy