Skip to content

Commit f0186d1

Browse files
authored
DLPX-93245 HOP : Migrating Existing Kettle Patches related to DB to the forked local HOP project repo (#10)
* DLPX-93245 HOP : Migrating Existing Kettle Patches related to DB to the forked local HOP project repo PR URL: https://www.github.com/delphix/hop/pull/10
1 parent 4edcfb0 commit f0186d1

File tree

5 files changed

+164
-38
lines changed

5 files changed

+164
-38
lines changed

core/src/main/java/org/apache/hop/core/database/Database.java

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public class Database implements IVariables, ILoggingObject {
6666

6767
private int rowlimit;
6868
private int commitsize;
69+
private boolean delphixSpecial = false;
70+
private int fetchSize = 0;
6971

7072
private Connection connection;
7173

@@ -587,6 +589,23 @@ public void cancelStatement(Statement statement) throws HopDatabaseException {
587589
* @param commsize The number of rows to wait before doing a commit on the connection.
588590
*/
589591
public void setCommit(int commsize) {
592+
/*
593+
* We override this method to set fetch size in the case where it was called
594+
* previously with a "magic" value. It would be much cleaner to just add a
595+
* setFetchSize() method, but the way we build dms-core-gate, changes here
596+
* aren't visible to link against when building our code.
597+
*/
598+
if (commsize == Integer.MIN_VALUE + 42) {
599+
delphixSpecial = true;
600+
return;
601+
}
602+
if (delphixSpecial) {
603+
fetchSize = commsize;
604+
delphixSpecial = false;
605+
return;
606+
}
607+
// End Delphix extra-special case.
608+
590609
commitsize = commsize;
591610
String onOff = (commitsize <= 0 ? "on" : "off");
592611
try {
@@ -1041,7 +1060,7 @@ && getDatabaseMetaData().supportsBatchUpdates()
10411060
&& databaseMeta.supportsBatchUpdates()
10421061
&& Utils.isEmpty(connectionGroup);
10431062
} catch (SQLException e) {
1044-
throw createHopDatabaseBatchException("Error determining whether to use batch", e);
1063+
throw createHopDatabaseBatchException("Error determining whether to use batch", e ,this.log.getLogLevel());
10451064
}
10461065
}
10471066

@@ -1108,10 +1127,10 @@ public boolean insertRow(PreparedStatement ps, boolean batch, boolean handleComm
11081127

11091128
return rowsAreSafe;
11101129
} catch (BatchUpdateException ex) {
1111-
throw createHopDatabaseBatchException("Error updating batch", ex);
1130+
throw createHopDatabaseBatchException("Error updating batch", ex, this.log.getLogLevel());
11121131
} catch (SQLException ex) {
11131132
if (isBatchUpdate) {
1114-
throw createHopDatabaseBatchException("Error updating batch", ex);
1133+
throw createHopDatabaseBatchException("Error updating batch", ex, this.log.getLogLevel());
11151134
} else {
11161135
throw new HopDatabaseException("Error inserting/updating row", ex);
11171136
}
@@ -1219,9 +1238,13 @@ public void emptyAndCommit(
12191238
}
12201239
}
12211240

1241+
public static HopDatabaseBatchException createHopDatabaseBatchException( String message, SQLException ex) {
1242+
return createHopDatabaseBatchException(message, ex, DefaultLogLevel.getLogLevel());
1243+
}
1244+
12221245
public static HopDatabaseBatchException createHopDatabaseBatchException(
1223-
String message, SQLException ex) {
1224-
HopDatabaseBatchException kdbe = new HopDatabaseBatchException(message, ex);
1246+
String message, SQLException ex, LogLevel logLevel1) {
1247+
HopDatabaseBatchException kdbe = new HopDatabaseBatchException(message, ex, logLevel1);
12251248
if (ex instanceof BatchUpdateException) {
12261249
kdbe.setUpdateCounts(((BatchUpdateException) ex).getUpdateCounts());
12271250
} else {
@@ -1465,13 +1488,13 @@ public ResultSet openQuery(
14651488

14661489
if (canWeSetFetchSize(pstmt)) {
14671490
int maxRows = pstmt.getMaxRows();
1468-
int fs = Const.FETCH_SIZE <= maxRows ? maxRows : Const.FETCH_SIZE;
1491+
int fs = Math.max(maxRows, fetchSize > 0 ? fetchSize : Const.FETCH_SIZE);
14691492
if (databaseMeta.isMySqlVariant()) {
14701493
setMysqlFetchSize(pstmt, fs, maxRows);
14711494
} else {
14721495
pstmt.setFetchSize(fs);
14731496
}
1474-
1497+
log.logBasic("Statement fetch size set to " + fs);
14751498
pstmt.setFetchDirection(fetchMode);
14761499
}
14771500

@@ -1487,13 +1510,14 @@ public ResultSet openQuery(
14871510
selStmt = connection.createStatement();
14881511
log.snap(Metrics.METRIC_DATABASE_CREATE_SQL_STOP, databaseMeta.getName());
14891512
if (canWeSetFetchSize(selStmt)) {
1490-
int fs =
1491-
Const.FETCH_SIZE <= selStmt.getMaxRows() ? selStmt.getMaxRows() : Const.FETCH_SIZE;
1513+
int fs = Math.max(selStmt.getMaxRows(), fetchSize > 0 ? fetchSize : Const.FETCH_SIZE);
14921514
if (databaseMeta.getIDatabase().isMySqlVariant() && databaseMeta.isStreamingResults()) {
14931515
selStmt.setFetchSize(Integer.MIN_VALUE);
14941516
} else {
14951517
selStmt.setFetchSize(fs);
14961518
}
1519+
log.logBasic("Statement fetch size set to " + fs);
1520+
14971521
selStmt.setFetchDirection(fetchMode);
14981522
}
14991523
if (rowlimit > 0 && databaseMeta.supportsSetMaxRows()) {
@@ -1523,10 +1547,16 @@ public ResultSet openQuery(
15231547
}
15241548

15251549
private boolean canWeSetFetchSize(Statement statement) throws SQLException {
1526-
return databaseMeta.isFetchSizeSupported()
1527-
&& (statement.getMaxRows() > 0
1550+
if (!databaseMeta.isFetchSizeSupported()) {
1551+
return false;
1552+
}
1553+
// Override for delphix
1554+
if (fetchSize > 0) {
1555+
return true;
1556+
}
1557+
return statement.getMaxRows() > 0
15281558
|| databaseMeta.getIDatabase().isPostgresVariant()
1529-
|| (databaseMeta.isMySqlVariant() && databaseMeta.isStreamingResults()));
1559+
|| ( databaseMeta.isMySqlVariant() && databaseMeta.isStreamingResults() );
15301560
}
15311561

15321562
public ResultSet openQuery(PreparedStatement ps, IRowMeta params, Object[] data)
@@ -1541,24 +1571,25 @@ public ResultSet openQuery(PreparedStatement ps, IRowMeta params, Object[] data)
15411571
setValues(params, data, ps); // set the parameters!
15421572
log.snap(Metrics.METRIC_DATABASE_SQL_VALUES_STOP, databaseMeta.getName());
15431573

1574+
if ( rowlimit > 0 && databaseMeta.supportsSetMaxRows() ) {
1575+
ps.setMaxRows( rowlimit );
1576+
}
1577+
15441578
if (canWeSetFetchSize(ps)) {
15451579
int maxRows = ps.getMaxRows();
1546-
int fs = Const.FETCH_SIZE <= maxRows ? maxRows : Const.FETCH_SIZE;
1580+
int fs = Math.max(maxRows, fetchSize > 0 ? fetchSize : Const.FETCH_SIZE);
15471581
// mysql have some restriction on fetch size assignment
15481582
if (databaseMeta.isMySqlVariant()) {
15491583
setMysqlFetchSize(ps, fs, maxRows);
15501584
} else {
15511585
// other databases seems not.
15521586
ps.setFetchSize(fs);
15531587
}
1588+
log.logBasic("Statement fetch size set to " + fs);
15541589

15551590
ps.setFetchDirection(ResultSet.FETCH_FORWARD);
15561591
}
15571592

1558-
if (rowlimit > 0 && databaseMeta.supportsSetMaxRows()) {
1559-
ps.setMaxRows(rowlimit);
1560-
}
1561-
15621593
log.snap(Metrics.METRIC_DATABASE_EXECUTE_SQL_START, databaseMeta.getName());
15631594
res = ps.executeQuery();
15641595
log.snap(Metrics.METRIC_DATABASE_EXECUTE_SQL_STOP, databaseMeta.getName());

core/src/main/java/org/apache/hop/core/exception/HopDatabaseBatchException.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@
1717

1818
package org.apache.hop.core.exception;
1919

20+
import java.sql.SQLException;
2021
import java.util.List;
2122

23+
import org.apache.hop.core.Const;
24+
import org.apache.hop.core.logging.DefaultLogLevel;
25+
import org.apache.hop.core.logging.LogLevel;
26+
27+
2228
/** This exception is used by the Database class. */
2329
public class HopDatabaseBatchException extends HopDatabaseException {
2430
public static final long serialVersionUID = 0x8D8EA0264F7A1C0EL;
@@ -27,6 +33,8 @@ public class HopDatabaseBatchException extends HopDatabaseException {
2733

2834
private List<Exception> exceptionsList;
2935

36+
private LogLevel logLevel = DefaultLogLevel.getLogLevel();
37+
3038
/** Constructs a new throwable with null as its detail message. */
3139
public HopDatabaseBatchException() {
3240
super();
@@ -65,6 +73,20 @@ public HopDatabaseBatchException(String message, Throwable cause) {
6573
super(message, cause);
6674
}
6775

76+
/**
77+
* Constructs a new throwable with the specified detail message and cause.
78+
*
79+
* @param message
80+
* the detail message (which is saved for later retrieval by the getMessage() method).
81+
* @param cause
82+
* the cause (which is saved for later retrieval by the getCause() method). (A null value is permitted, and
83+
* indicates that the cause is nonexistent or unknown.)
84+
*/
85+
public HopDatabaseBatchException( String message, Throwable cause, LogLevel logLevel ) {
86+
super( message, cause );
87+
this.logLevel = logLevel;
88+
}
89+
6890
/** @return Returns the updateCounts. */
6991
public int[] getUpdateCounts() {
7092
return updateCounts;
@@ -82,4 +104,42 @@ public void setExceptionsList(List<Exception> exceptionsList) {
82104
public List<Exception> getExceptionsList() {
83105
return exceptionsList;
84106
}
107+
108+
/*
109+
* This method exists to fix DLPX-56268 (Masking does not see Hop getNextException to surface the cause of a SQLException).
110+
*
111+
* The HopException class overrides this method of Exception class to get the root cause. This
112+
* class is a subclass of HopException class. It has an additional member field to store list
113+
* of exceptions. The static method createHopDatabaseBatchException in Database class is used
114+
* to create an instance of this class. In that method all the exceptions associated with SQLException
115+
* are retrieved using the getNextException and stored in this list.
116+
*
117+
* While logging the error message, the getMessage method is used. Since it is a subclass of HopException class, it
118+
* uses the method defined there. That definition ignores this list of exceptions. Thus these messages are supressed.
119+
*
120+
* We fix this by overriding the getMessage method here and log all the exceptions present in the exceptionList variable.
121+
* It also calls the HopException's getMessage method so that the previous behavior is maintained while adding
122+
* additional log messages.
123+
*/
124+
@Override
125+
public String getMessage(){
126+
String retval = Const.CR;
127+
retval += super.getMessage() + Const.CR;
128+
129+
for(Exception exc: exceptionsList){
130+
if(exc instanceof SQLException){
131+
SQLException sqlException = (SQLException)exc;
132+
retval += "Next Exception: SQLState( "
133+
+ sqlException.getSQLState() + ") ErrorCode("
134+
+ sqlException.getErrorCode() + ")";
135+
if(this.logLevel.isDetailed()){
136+
retval += " Message: " + sqlException.getMessage();
137+
}
138+
retval += Const.CR;
139+
} else {
140+
retval += "Next Exception: " + exc.getClass() + Const.CR;
141+
}
142+
}
143+
return retval;
144+
}
85145
}

core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ public class ValueMetaBase implements IValueMeta {
168168
protected int originalNullable;
169169
protected boolean originalSigned;
170170

171+
// Added for DLPX-46230 / DLPX-82789 to prevent using setBlob on non-blob columns in Oracle
172+
protected boolean isLargeObject = false;
173+
174+
171175
protected boolean ignoreWhitespace;
172176

173177
protected final Comparator<Object> comparator;
@@ -4118,11 +4122,7 @@ public Object convertDataFromString(
41184122
boolean isStringValue = outValueType == IValueMeta.TYPE_STRING;
41194123
Object emptyValue = isStringValue ? Const.NULL_STRING : null;
41204124

4121-
boolean isEmptyAndNullDiffer =
4122-
convertStringToBoolean(
4123-
Const.NVL(System.getProperty(Const.HOP_EMPTY_STRING_DIFFERS_FROM_NULL, "N"), "N"));
4124-
4125-
if (pol == null && isStringValue && isEmptyAndNullDiffer) {
4125+
if ( pol == null && isStringValue && emptyStringAndNullAreDifferent ) {
41264126
pol = Const.NULL_STRING;
41274127
}
41284128

@@ -4681,6 +4681,7 @@ public IValueMeta getValueFromSqlType(
46814681
int precision = -1;
46824682
int valtype = IValueMeta.TYPE_NONE;
46834683
boolean isClob = false;
4684+
isLargeObject = false;
46844685

46854686
int type = rm.getColumnType(index);
46864687
boolean signed = false;
@@ -5116,6 +5117,32 @@ public IValueMeta getMetadataPreview(
51165117
}
51175118
}
51185119

5120+
/*
5121+
* This logic exists to fix DLPX-51415 (Sybase - Numeric Conversion Error).
5122+
*
5123+
* The logic above will typically force DECIMAL/NUMERIC types with a scale ('precision' in Kettle terms) > 0
5124+
* to be cast to Java Doubles. A Double is a binary type, so it can not exactly represent arbitrary decimal
5125+
* values.
5126+
*
5127+
* This is particularly problematic for Sybase, because Sybase has some inconsistencies related to how it
5128+
* deals with inserts/updates of values with greater precision than can be held by type of the column, for
5129+
* instance inserting a value 1.2345 into a column with a type DECIMAL(4,2). For certain combinations of
5130+
* jTDS/jConnect and batched/non-batched mode, Sybase will throw an error of the form
5131+
*
5132+
* Scale error during implicit conversion of NUMERIC value '1.2345' to a DECIMAL field.
5133+
*
5134+
* This causes a problem when the inexact floating point values read from Sybase columns are inserted back
5135+
* into Sybase NUMERIC/DECIMAL columns. We fix this by reading NUMERIC/DECIMAL values into a Java decimal
5136+
* type which can store the exact value correctly. We will likely want this behavior for all platforms
5137+
* eventually, but for now we restrict this behavior to Sybase, in order to minimize the change, and
5138+
* thereby the risk associated with the patch.
5139+
*/
5140+
if (databaseMeta.getIDatabase().isSybaseVariant()
5141+
&& (type == Types.NUMERIC || type == Types.DECIMAL)
5142+
&& precision > 0) {
5143+
valtype = IValueMeta.TYPE_BIGNUMBER;
5144+
}
5145+
51195146
break;
51205147

51215148
case Types.TIMESTAMP:
@@ -5153,6 +5180,8 @@ public IValueMeta getMetadataPreview(
51535180

51545181
case Types.BINARY:
51555182
case Types.BLOB:
5183+
isLargeObject = true;
5184+
// fallthrough
51565185
case Types.VARBINARY:
51575186
case Types.LONGVARBINARY:
51585187
valtype = IValueMeta.TYPE_BINARY;
@@ -5167,9 +5196,10 @@ public IValueMeta getMetadataPreview(
51675196
} else if ((databaseMeta.getIDatabase().isOracleVariant())
51685197
&& (originalColumnType == Types.VARBINARY
51695198
|| originalColumnType == Types.LONGVARBINARY)) {
5170-
// set the length for Oracle "RAW" or "LONGRAW" data types
5171-
valtype = IValueMeta.TYPE_STRING;
5172-
length = originalColumnDisplaySize;
5199+
// set the length for Oracle "RAW" or "LONGRAW" data
5200+
// For DLPX-82789, these type are fundamentally binary and should be treated as such
5201+
// valtype = IValueMeta.TYPE_STRING;
5202+
// length = originalColumnDisplaySize;
51735203
} else if (databaseMeta.isMySqlVariant()
51745204
&& (originalColumnType == Types.VARBINARY
51755205
|| originalColumnType == Types.LONGVARBINARY)) {
@@ -5246,7 +5276,7 @@ public Object getValueFromResultSet(IDatabase iDatabase, ResultSet resultSet, in
52465276
}
52475277
break;
52485278
case IValueMeta.TYPE_BINARY:
5249-
if (iDatabase.supportsGetBlob()) {
5279+
if ( isLargeObject && iDatabase.supportsGetBlob()) {
52505280
Blob blob = resultSet.getBlob(index + 1);
52515281
if (blob != null) {
52525282
data = blob.getBytes(1L, (int) blob.length());
@@ -5314,19 +5344,23 @@ public void setPreparedStatementValue(
53145344
}
53155345
break;
53165346
case IValueMeta.TYPE_INTEGER:
5317-
if (!isNull(data)) {
5318-
if (databaseMeta.supportsSetLong()) {
5347+
if (databaseMeta.supportsSetLong()) {
5348+
if (!isNull(data)) {
53195349
preparedStatement.setLong(index, getInteger(data).longValue());
53205350
} else {
5351+
preparedStatement.setNull( index, Types.BIGINT );
5352+
}
5353+
} else {
5354+
if (!isNull(data)) {
53215355
double d = getNumber(data).doubleValue();
53225356
if (databaseMeta.supportsFloatRoundingOnUpdate() && getPrecision() >= 0) {
53235357
preparedStatement.setDouble(index, d);
53245358
} else {
53255359
preparedStatement.setDouble(index, Const.round(d, getPrecision()));
53265360
}
5361+
} else {
5362+
preparedStatement.setNull(index, Types.DOUBLE);
53275363
}
5328-
} else {
5329-
preparedStatement.setNull(index, Types.INTEGER);
53305364
}
53315365
break;
53325366
case IValueMeta.TYPE_STRING:

core/src/test/java/org/apache/hop/core/row/value/ValueMetaBaseTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,8 @@ public void testConvertDataFromStringToString() throws HopValueException {
318318
inputValueNullString, inValueMetaString, nullIf, ifNull, trimType);
319319
assertEquals(
320320
"HOP_EMPTY_STRING_DIFFERS_FROM_NULL = Y: "
321-
+ "Conversion from null string must return empty string",
322-
StringUtils.EMPTY,
321+
+ "Conversion from null string must return null",
322+
null,
323323
result);
324324
}
325325

0 commit comments

Comments
 (0)