com.microsoft.sqlserver.jdbc.TVP Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
//---------------------------------------------------------------------------------------------------------------------------------
// 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;
}
}