Skip to content

Commit c9336ec

Browse files
committed
Address review on Get Fields errors, PostgreSQL NUMERIC, and query fallback
- Re-throw progress-dialog exceptions after the shell is disposed, and drop the extra monitor.done() so a failed Get Fields still shows the error dialog - Clamp PostgreSQL NUMERIC precision to at least the scale (PG < 15 rejects s > p); document the 2.20 NUMERIC(length, scale) meaning - Do not run getQueryFieldsFallback twice when that path already failed - Prefer Hop Timestamp over original JDBC DATE when generating SQL Server DDL - Map unsized NUMERIC/DECIMAL parameters back to Integer
1 parent e28dbf5 commit c9336ec

11 files changed

Lines changed: 80 additions & 16 deletions

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2292,7 +2292,15 @@ public IRowMeta getQueryFields(String sql, boolean param, IRowMeta inform, Objec
22922292
fields = getQueryFieldsFallback(sql, param, inform, data);
22932293
}
22942294
} catch (Exception e) {
2295-
fields = getQueryFieldsFallback(sql, param, inform, data);
2295+
// Only recover from the prepared-statement / data-service paths. The else branch already
2296+
// ran the fallback; calling it again would execute the user's SQL twice.
2297+
if (databaseMeta.supportsPreparedStatementMetadataRetrieval() || isDataServiceConnection()) {
2298+
fields = getQueryFieldsFallback(sql, param, inform, data);
2299+
} else if (e instanceof HopDatabaseException hopDatabaseException) {
2300+
throw hopDatabaseException;
2301+
} else {
2302+
throw new HopDatabaseException(e);
2303+
}
22962304
}
22972305

22982306
// Store in cache!!
@@ -3485,6 +3493,10 @@ public IRowMeta getParameterMetaData(PreparedStatement ps) {
34853493
IValueMeta val = DatabaseTypeMapper.getValueMeta(this, databaseMeta, column, false, false);
34863494
if (val == null) {
34873495
val = new ValueMetaNone(name);
3496+
} else if (isUnsizedExactNumeric(sqlType, precision, scale) && val.isNumber()) {
3497+
// Drivers often report NUMERIC/DECIMAL with precision 0 for untyped parameters.
3498+
// The mapper then yields a double-backed Number; the old parameter path used Integer.
3499+
val = new ValueMetaInteger(name);
34883500
}
34893501
par.addValueMeta(val);
34903502
}
@@ -3496,6 +3508,10 @@ public IRowMeta getParameterMetaData(PreparedStatement ps) {
34963508
return par;
34973509
}
34983510

3511+
private static boolean isUnsizedExactNumeric(int sqlType, int precision, int scale) {
3512+
return (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) && precision <= 0 && scale <= 0;
3513+
}
3514+
34993515
public int countParameters(String sql) {
35003516
int q = 0;
35013517
boolean quoteOpened = false;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,8 +1096,10 @@ public String getQuotedSchemaTableCombination(
10961096
}
10971097

10981098
private String quoteSchema(String schemaName) {
1099-
// A composite "catalog.schema" is split whenever the caller passed one. supportsCatalogs()
1100-
// is a browsing flag and must not collapse an explicit catalog prefix into one identifier.
1099+
// A composite "catalog.schema" is split whenever the caller passed one, including on dialects
1100+
// that return supportsCatalogs() == false (jTDS SQL Server, Access, Gupta, Iris). That flag is
1101+
// a browsing hint and must not collapse mydb.dbo into the unresolvable identifier [mydb.dbo].
1102+
// A schema whose name itself contains a literal dot is vanishingly rare next to catalog.schema.
11011103
int separatorIndex = schemaName.indexOf('.');
11021104
if (separatorIndex > 0 && separatorIndex < schemaName.length() - 1) {
11031105
String catalogName = schemaName.substring(0, separatorIndex);

core/src/test/java/org/apache/hop/core/database/DatabaseTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,23 @@ void getParameterMetaDataMapsNvarcharAndNumeric() throws Exception {
164164
assertEquals(4, rowMeta.getValueMeta(1).getPrecision());
165165
}
166166

167+
@Test
168+
void getParameterMetaDataMapsUnsizedNumericToInteger() throws Exception {
169+
when(meta.getIDatabase()).thenReturn(new NoneDatabaseMeta());
170+
ParameterMetaData parameterMetaData = mock(ParameterMetaData.class);
171+
when(ps.getParameterMetaData()).thenReturn(parameterMetaData);
172+
when(parameterMetaData.getParameterCount()).thenReturn(1);
173+
when(parameterMetaData.getParameterType(1)).thenReturn(Types.NUMERIC);
174+
when(parameterMetaData.getPrecision(1)).thenReturn(0);
175+
when(parameterMetaData.getScale(1)).thenReturn(0);
176+
177+
Database db = new Database(log, variables, meta);
178+
IRowMeta rowMeta = db.getParameterMetaData(ps);
179+
180+
assertEquals(1, rowMeta.size());
181+
assertTrue(rowMeta.getValueMeta(0).isInteger());
182+
}
183+
167184
/**
168185
* When using getLookup calls there is no need to make attempt to retrieve row set metadata for
169186
* every call. That may bring performance penalty depends on jdbc driver implementation. For some

docs/hop-dev-manual/modules/ROOT/pages/database/column-types.adoc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,14 @@ Hop provides the plain JDBC behaviour; a dialect declares the parts it does diff
4545

4646
Two of these carry a meaning that is easy to get wrong.
4747

48-
`length` on a numeric type is the number of digits *before* the decimal, not the total.
49-
A database reports the total, so the scale has to come off.
50-
`NUMERIC(10,2)` is length 8, precision 2, and is written back as `NUMERIC(8+2, 2)`.
51-
Getting this backwards makes a column grow by its scale on every round trip.
48+
`length` on a numeric type is the *total* number of significant digits, the same value the database reports as precision.
49+
`NUMERIC(10,2)` is length 10, precision 2, and is written back as `NUMERIC(10, 2)` (or `DECIMAL(10,2)`).
50+
`numericLength()` on a `DatabaseColumn` is the digits before the decimal (8 for that example) and is only for dialect rules that test whether a declaration is possible.
51+
52+
PostgreSQL used to emit `NUMERIC(length+precision, precision)` when Hop length still meant integer digits.
53+
From 2.20 it emits `NUMERIC(length, precision)`.
54+
A hand-authored Number field of length 10, precision 3 therefore generates `NUMERIC(10, 3)` rather than `NUMERIC(13, 3)`.
55+
If scale is larger than length, precision is widened so PostgreSQL before 15 does not reject the statement.
5256

5357
`precision` of exactly 1 on a `TYPE_DATE` is a marker meaning the value is a date rather than a timestamp.
5458
It is not a digit count.

docs/hop-user-manual/modules/ROOT/pages/database/databases/postgresql.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,11 @@ under the License.
3030
|Documentation | https://jdbc.postgresql.org/documentation/head/index.html[Documentation Link]
3131
|JDBC Url | jdbc:postgresql://host:port/database
3232
|===
33+
34+
== Numeric DDL in 2.20
35+
36+
Hop Number/BigNumber *length* is the total number of significant digits (the database precision), not the digits before the decimal.
37+
38+
Table Output and similar DDL therefore emit `NUMERIC(length, scale)`.
39+
A field authored as length 10, precision 3 generates `NUMERIC(10, 3)`.
40+
In 2.19 and earlier the PostgreSQL dialect added length and scale and emitted `NUMERIC(13, 3)` for that same metadata.

plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,18 @@ public List<IDatabaseTypeRule> getTypeRules() {
7777
* known; new Hop Date fields stay DATETIME, new Timestamp fields become DATETIME2.
7878
*/
7979
static String dateTimeColumnType(IValueMeta valueMeta) {
80+
// Hop type first: a DATE column the user converted to Timestamp must keep the time.
81+
if (valueMeta.getType() == IValueMeta.TYPE_TIMESTAMP
82+
|| "datetime2".equalsIgnoreCase(valueMeta.getOriginalColumnTypeName())) {
83+
return "DATETIME2";
84+
}
8085
int original = valueMeta.getOriginalColumnType();
8186
if (original == Types.DATE) {
8287
return "DATE";
8388
}
8489
if (original == Types.TIME) {
8590
return "TIME";
8691
}
87-
if (valueMeta.getType() == IValueMeta.TYPE_TIMESTAMP
88-
|| "datetime2".equalsIgnoreCase(valueMeta.getOriginalColumnTypeName())) {
89-
return "DATETIME2";
90-
}
9192
return "DATETIME";
9293
}
9394

plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerTypeRulesTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ void aPlainTimestampWritesDatetime2() {
6969
assertEquals("DATETIME2", write(new ValueMetaTimestamp("COL")));
7070
}
7171

72+
@Test
73+
void aTimestampConvertedFromDateKeepsTheTime() {
74+
IValueMeta valueMeta = new ValueMetaTimestamp("COL");
75+
valueMeta.setOriginalColumnType(Types.DATE);
76+
assertEquals("DATETIME2", write(valueMeta));
77+
}
78+
7279
@Test
7380
void nvarcharRoundTrips() throws Exception {
7481
assertEquals("NVARCHAR(20)", write(column(Types.NVARCHAR, "nvarchar", 20, 20)));

plugins/databases/postgresql/src/main/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMeta.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,16 +468,19 @@ public String getFieldDefinition(
468468
} else {
469469
if (length > 0) {
470470
if (precision > 0 || length > 18) {
471-
// Numeric(Precision, Scale): Hop length is the total number of significant digits.
472-
if (length > MAX_NUMERIC_PRECISION) {
471+
// Numeric(p, s): Hop length is the total number of significant digits. PostgreSQL
472+
// before 15 rejects s > p, so widen p when a field was authored with scale larger
473+
// than length.
474+
int numericPrecision = Math.max(length, precision);
475+
if (numericPrecision > MAX_NUMERIC_PRECISION) {
473476
// PostgreSQL refuses a declared precision above 1000 outright: "NUMERIC precision
474477
// 1073741824 must be between 1 and 1000". A length that large only ever arrives
475478
// from the CLOB_LENGTH marker, which means unbounded, and an unconstrained NUMERIC
476479
// is exactly that: it holds 131072 digits before the point, far past anything a
477480
// Hop value carries.
478481
retval += "NUMERIC";
479482
} else {
480-
retval += "NUMERIC(" + length + ", " + precision + ")";
483+
retval += "NUMERIC(" + numericPrecision + ", " + precision + ")";
481484
}
482485
} else {
483486
if (length > 9) {

plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMetaTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ void testSqlStatements() {
809809
nativeMeta.getAddColumnStatement(
810810
"FOO", new ValueMetaNumber("BAR", -10, 7), "", false, "", false));
811811
assertEquals(
812-
"ALTER TABLE FOO ADD COLUMN BAR NUMERIC(5, 7)",
812+
"ALTER TABLE FOO ADD COLUMN BAR NUMERIC(7, 7)",
813813
nativeMeta.getAddColumnStatement(
814814
"FOO", new ValueMetaNumber("BAR", 5, 7), "", false, "", false));
815815
// An ALTER TABLE spells a column the way a CREATE TABLE does: through the dialect's type

ui/src/main/java/org/apache/hop/ui/core/database/dialog/GetQueryFieldsProgressDialog.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ public IRowMeta open() {
7272
e, "Problem encountered determining query fields: " + e.toString());
7373
} finally {
7474
db.disconnect();
75-
monitor.done();
7675
}
7776
};
7877

0 commit comments

Comments
 (0)