001/*- 002 * #%L 003 * HAPI FHIR Server - SQL Migration 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.migrate; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.migrate.taskdef.ColumnTypeEnum; 024import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 025import jakarta.annotation.Nonnull; 026import jakarta.annotation.Nullable; 027import org.apache.commons.lang3.builder.EqualsBuilder; 028import org.apache.commons.lang3.builder.HashCodeBuilder; 029import org.apache.commons.lang3.builder.ToStringBuilder; 030import org.hibernate.boot.model.naming.Identifier; 031import org.hibernate.boot.model.relational.QualifiedSequenceName; 032import org.hibernate.dialect.Dialect; 033import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver; 034import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter; 035import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; 036import org.hibernate.engine.jdbc.env.internal.NormalizingIdentifierHelperImpl; 037import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; 038import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; 039import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; 040import org.hibernate.engine.jdbc.env.spi.LobCreatorBuilder; 041import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; 042import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter; 043import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; 044import org.hibernate.service.ServiceRegistry; 045import org.hibernate.sql.ast.SqlAstTranslatorFactory; 046import org.hibernate.tool.schema.extract.spi.ExtractionContext; 047import org.hibernate.tool.schema.extract.spi.SequenceInformation; 048import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; 049import org.intellij.lang.annotations.Language; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052import org.springframework.jdbc.core.ColumnMapRowMapper; 053import org.springframework.transaction.support.TransactionTemplate; 054import org.springframework.util.LinkedCaseInsensitiveMap; 055 056import java.sql.Connection; 057import java.sql.DatabaseMetaData; 058import java.sql.ResultSet; 059import java.sql.SQLException; 060import java.sql.Types; 061import java.util.ArrayList; 062import java.util.Collections; 063import java.util.HashSet; 064import java.util.List; 065import java.util.Locale; 066import java.util.Map; 067import java.util.Objects; 068import java.util.Set; 069import java.util.stream.Collectors; 070import java.util.stream.StreamSupport; 071import javax.sql.DataSource; 072 073public class JdbcUtils { 074 private static final Logger ourLog = LoggerFactory.getLogger(JdbcUtils.class); 075 076 /** 077 * Retrieve all index names 078 */ 079 public static Set<String> getPrimaryKeyColumns( 080 DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException { 081 082 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 083 try (Connection connection = dataSource.getConnection()) { 084 return theConnectionProperties.getTxTemplate().execute(t -> { 085 DatabaseMetaData metadata; 086 Set<String> retVal = new HashSet<>(); 087 try { 088 metadata = connection.getMetaData(); 089 090 try (ResultSet results = 091 metadata.getPrimaryKeys(connection.getCatalog(), connection.getSchema(), theTableName)) { 092 while (results.next()) { 093 String columnName = results.getString("COLUMN_NAME"); 094 retVal.add(columnName); 095 } 096 } 097 098 } catch (SQLException e) { 099 throw new InternalErrorException(Msg.code(2562) + e); 100 } 101 return retVal; 102 }); 103 } 104 } 105 106 /** 107 * Retrieve all index names 108 */ 109 public static Set<String> getIndexNames( 110 DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException { 111 112 if (!getTableNames(theConnectionProperties).contains(theTableName)) { 113 return Collections.emptySet(); 114 } 115 116 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 117 try (Connection connection = dataSource.getConnection()) { 118 return theConnectionProperties.getTxTemplate().execute(t -> { 119 DatabaseMetaData metadata; 120 try { 121 metadata = connection.getMetaData(); 122 123 Set<String> indexNames = new HashSet<>(); 124 125 for (boolean unique : Set.of(false, true)) { 126 try (ResultSet indexes = getIndexInfo(theTableName, connection, metadata, unique)) { 127 while (indexes.next()) { 128 ourLog.debug("*** Next index: {}", new ColumnMapRowMapper().mapRow(indexes, 0)); 129 String indexName = indexes.getString("INDEX_NAME"); 130 indexNames.add(indexName); 131 } 132 } 133 } 134 135 indexNames = indexNames.stream() 136 .filter(Objects::nonNull) // filter out the nulls first 137 .map(s -> s.toUpperCase(Locale.US)) // then convert the non-null entries to upper case 138 .collect(Collectors.toSet()); 139 140 return indexNames; 141 142 } catch (SQLException e) { 143 throw new InternalErrorException(Msg.code(29) + e); 144 } 145 }); 146 } 147 } 148 149 @SuppressWarnings("ConstantConditions") 150 public static boolean isIndexUnique( 151 DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theIndexName) 152 throws SQLException { 153 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 154 try (Connection connection = dataSource.getConnection()) { 155 return theConnectionProperties.getTxTemplate().execute(t -> { 156 DatabaseMetaData metadata; 157 try { 158 metadata = connection.getMetaData(); 159 try (ResultSet indexes = getIndexInfo(theTableName, connection, metadata, false)) { 160 161 while (indexes.next()) { 162 String indexName = indexes.getString("INDEX_NAME"); 163 if (theIndexName.equalsIgnoreCase(indexName)) { 164 boolean nonUnique = indexes.getBoolean("NON_UNIQUE"); 165 return !nonUnique; 166 } 167 } 168 } 169 170 } catch (SQLException e) { 171 throw new InternalErrorException(Msg.code(30) + e); 172 } 173 174 throw new InternalErrorException( 175 Msg.code(31) + "Can't find index: " + theIndexName + " on table " + theTableName); 176 }); 177 } 178 } 179 180 private static ResultSet getIndexInfo( 181 String theTableName, Connection theConnection, DatabaseMetaData theMetadata, boolean theUnique) 182 throws SQLException { 183 // FYI Using approximate=false causes a very slow table scan on Oracle 184 boolean approximate = true; 185 return theMetadata.getIndexInfo( 186 theConnection.getCatalog(), 187 theConnection.getSchema(), 188 massageIdentifier(theMetadata, theTableName), 189 theUnique, 190 approximate); 191 } 192 193 /** 194 * Retrieve all index names 195 */ 196 public static ColumnType getColumnType( 197 DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theColumnName) 198 throws SQLException { 199 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 200 try (Connection connection = dataSource.getConnection()) { 201 return theConnectionProperties.getTxTemplate().execute(t -> { 202 DatabaseMetaData metadata; 203 try { 204 metadata = connection.getMetaData(); 205 String catalog = connection.getCatalog(); 206 String schema = connection.getSchema(); 207 try (ResultSet indexes = 208 metadata.getColumns(catalog, schema, massageIdentifier(metadata, theTableName), null)) { 209 210 while (indexes.next()) { 211 212 String tableName = indexes.getString("TABLE_NAME").toUpperCase(Locale.US); 213 if (!theTableName.equalsIgnoreCase(tableName)) { 214 continue; 215 } 216 String columnName = indexes.getString("COLUMN_NAME").toUpperCase(Locale.US); 217 if (!theColumnName.equalsIgnoreCase(columnName)) { 218 continue; 219 } 220 221 int dataType = indexes.getInt("DATA_TYPE"); 222 Long length = indexes.getLong("COLUMN_SIZE"); 223 switch (dataType) { 224 case Types.LONGVARCHAR: 225 return new ColumnType(ColumnTypeEnum.TEXT, length); 226 case Types.BIT: 227 case Types.BOOLEAN: 228 return new ColumnType(ColumnTypeEnum.BOOLEAN, length); 229 case Types.VARCHAR: 230 return new ColumnType(ColumnTypeEnum.STRING, length); 231 case Types.NUMERIC: 232 case Types.BIGINT: 233 case Types.DECIMAL: 234 return new ColumnType(ColumnTypeEnum.LONG, length); 235 case Types.INTEGER: 236 return new ColumnType(ColumnTypeEnum.INT, length); 237 case Types.TIMESTAMP: 238 case Types.TIMESTAMP_WITH_TIMEZONE: 239 return new ColumnType(ColumnTypeEnum.DATE_TIMESTAMP, length); 240 case Types.BLOB: 241 return new ColumnType(ColumnTypeEnum.BLOB, length); 242 case Types.LONGVARBINARY: 243 return new ColumnType(ColumnTypeEnum.BINARY, length); 244 case Types.VARBINARY: 245 if (DriverTypeEnum.MSSQL_2012.equals(theConnectionProperties.getDriverType())) { 246 // MS SQLServer seems to be mapping BLOB to VARBINARY under the covers, 247 // so we need to reverse that mapping 248 return new ColumnType(ColumnTypeEnum.BLOB, length); 249 250 } else { 251 throw new IllegalArgumentException( 252 Msg.code(33) + "Don't know how to handle datatype " + dataType 253 + " for column " + theColumnName 254 + " on table " + theTableName); 255 } 256 case Types.CLOB: 257 return new ColumnType(ColumnTypeEnum.CLOB, length); 258 case Types.DOUBLE: 259 return new ColumnType(ColumnTypeEnum.DOUBLE, length); 260 case Types.FLOAT: 261 return new ColumnType(ColumnTypeEnum.FLOAT, length); 262 case Types.TINYINT: 263 return new ColumnType(ColumnTypeEnum.TINYINT, length); 264 default: 265 throw new IllegalArgumentException( 266 Msg.code(34) + "Don't know how to handle datatype " + dataType 267 + " for column " + theColumnName 268 + " on table " + theTableName); 269 } 270 } 271 } 272 273 ourLog.debug("Unable to find column {} in table {}.", theColumnName, theTableName); 274 return null; 275 276 } catch (SQLException e) { 277 throw new InternalErrorException(Msg.code(35) + e); 278 } 279 }); 280 } 281 } 282 283 /** 284 * Retrieve all index names. The returned names will be in upper case 285 * always. 286 */ 287 public static Set<String> getForeignKeys( 288 DriverTypeEnum.ConnectionProperties theConnectionProperties, 289 String theTableName, 290 @Nullable String theForeignTable) 291 throws SQLException { 292 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 293 294 try (Connection connection = dataSource.getConnection()) { 295 TransactionTemplate txTemplate = theConnectionProperties.getTxTemplate(); 296 return txTemplate.execute(t -> { 297 DatabaseMetaData metadata; 298 try { 299 metadata = connection.getMetaData(); 300 String catalog = connection.getCatalog(); 301 String schema = connection.getSchema(); 302 303 List<String> parentTables = new ArrayList<>(); 304 if (theTableName != null) { 305 parentTables.add(massageIdentifier(metadata, theTableName)); 306 } else { 307 // If no foreign table is specified, we'll try all of them 308 parentTables.addAll(JdbcUtils.getTableNames(theConnectionProperties)); 309 } 310 311 String foreignTable = massageIdentifier(metadata, theForeignTable); 312 313 Set<String> fkNames = new HashSet<>(); 314 for (String nextParentTable : parentTables) { 315 try (ResultSet indexes = metadata.getCrossReference( 316 catalog, schema, nextParentTable, catalog, schema, foreignTable)) { 317 while (indexes.next()) { 318 String fkName = indexes.getString("FK_NAME"); 319 fkName = fkName.toUpperCase(Locale.US); 320 fkNames.add(fkName); 321 } 322 } 323 } 324 325 return fkNames; 326 } catch (SQLException e) { 327 throw new InternalErrorException(Msg.code(36) + e); 328 } 329 }); 330 } 331 } 332 333 /** 334 * Retrieve names of foreign keys that reference a specified foreign key column. 335 */ 336 public static Set<String> getForeignKeysForColumn( 337 DriverTypeEnum.ConnectionProperties theConnectionProperties, 338 String theForeignKeyColumn, 339 String theForeignTable) 340 throws SQLException { 341 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 342 343 try (Connection connection = dataSource.getConnection()) { 344 return theConnectionProperties.getTxTemplate().execute(t -> { 345 DatabaseMetaData metadata; 346 try { 347 metadata = connection.getMetaData(); 348 String catalog = connection.getCatalog(); 349 String schema = connection.getSchema(); 350 351 List<String> parentTables = new ArrayList<>(); 352 parentTables.addAll(JdbcUtils.getTableNames(theConnectionProperties)); 353 354 String foreignTable = massageIdentifier(metadata, theForeignTable); 355 356 Set<String> fkNames = new HashSet<>(); 357 for (String nextParentTable : parentTables) { 358 try (ResultSet indexes = metadata.getCrossReference( 359 catalog, schema, nextParentTable, catalog, schema, foreignTable)) { 360 while (indexes.next()) { 361 if (theForeignKeyColumn.equals(indexes.getString("FKCOLUMN_NAME"))) { 362 String fkName = indexes.getString("FK_NAME"); 363 fkName = fkName.toUpperCase(Locale.US); 364 fkNames.add(fkName); 365 } 366 } 367 } 368 } 369 370 return fkNames; 371 } catch (SQLException e) { 372 throw new InternalErrorException(Msg.code(37) + e); 373 } 374 }); 375 } 376 } 377 378 /** 379 * Retrieve all index names 380 */ 381 public static Set<String> getColumnNames( 382 DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException { 383 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 384 try (Connection connection = dataSource.getConnection()) { 385 return theConnectionProperties.getTxTemplate().execute(t -> { 386 DatabaseMetaData metadata; 387 try { 388 metadata = connection.getMetaData(); 389 LinkedCaseInsensitiveMap<String> columnNames = new LinkedCaseInsensitiveMap<>(); 390 391 try (ResultSet indexes = metadata.getColumns( 392 connection.getCatalog(), 393 connection.getSchema(), 394 massageIdentifier(metadata, theTableName), 395 null)) { 396 397 while (indexes.next()) { 398 String tableName = indexes.getString("TABLE_NAME").toUpperCase(Locale.US); 399 if (!theTableName.equalsIgnoreCase(tableName)) { 400 continue; 401 } 402 403 String columnName = indexes.getString("COLUMN_NAME"); 404 columnName = columnName.toUpperCase(Locale.US); 405 columnNames.put(columnName, columnName); 406 } 407 } 408 409 return columnNames.keySet(); 410 } catch (SQLException e) { 411 throw new InternalErrorException(Msg.code(38) + e); 412 } 413 }); 414 } 415 } 416 417 public static Set<String> getSequenceNames(DriverTypeEnum.ConnectionProperties theConnectionProperties) 418 throws SQLException { 419 List<SequenceInformation> sequenceInformation = getSequenceInformation(theConnectionProperties); 420 421 return sequenceInformation.stream() 422 .map(SequenceInformation::getSequenceName) 423 .map(QualifiedSequenceName::getSequenceName) 424 .map(Identifier::getText) 425 .collect(Collectors.toSet()); 426 } 427 428 @Nonnull 429 public static List<SequenceInformation> getSequenceInformation( 430 DriverTypeEnum.ConnectionProperties theConnectionProperties) throws SQLException { 431 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 432 try (Connection connection = dataSource.getConnection()) { 433 return Objects.requireNonNull( 434 theConnectionProperties.getTxTemplate().execute(t -> { 435 try { 436 DialectResolver dialectResolver = new StandardDialectResolver(); 437 Dialect dialect = dialectResolver.resolveDialect( 438 new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData())); 439 440 List<SequenceInformation> sequenceInformation = new ArrayList<>(); 441 if (dialect.getSequenceSupport().supportsSequences()) { 442 443 // Use Hibernate to get a list of current sequences 444 SequenceInformationExtractor sequenceInformationExtractor = 445 dialect.getSequenceInformationExtractor(); 446 ExtractionContext extractionContext = new EmptyExtractionContext(connection, dialect); 447 Iterable<SequenceInformation> sequenceInformationIterator = 448 sequenceInformationExtractor.extractMetadata(extractionContext); 449 450 return StreamSupport.stream(sequenceInformationIterator.spliterator(), false) 451 .collect(Collectors.toList()); 452 } 453 return sequenceInformation; 454 } catch (SQLException e) { 455 throw new InternalErrorException(Msg.code(39) + e); 456 } 457 })); 458 } 459 } 460 461 public static Set<String> getTableNames(DriverTypeEnum.ConnectionProperties theConnectionProperties) 462 throws SQLException { 463 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 464 try (Connection connection = dataSource.getConnection()) { 465 return theConnectionProperties.getTxTemplate().execute(t -> { 466 DatabaseMetaData metadata; 467 try { 468 metadata = connection.getMetaData(); 469 Set<String> columnNames = new HashSet<>(); 470 471 try (ResultSet tables = 472 metadata.getTables(connection.getCatalog(), connection.getSchema(), null, null)) { 473 474 while (tables.next()) { 475 String tableName = tables.getString("TABLE_NAME"); 476 tableName = tableName.toUpperCase(Locale.US); 477 478 String tableType = tables.getString("TABLE_TYPE"); 479 if ("SYSTEM TABLE".equalsIgnoreCase(tableType)) { 480 continue; 481 } 482 if (SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME.equalsIgnoreCase(tableName)) { 483 continue; 484 } 485 486 columnNames.add(tableName); 487 } 488 } 489 490 return columnNames; 491 } catch (SQLException e) { 492 throw new InternalErrorException(Msg.code(40) + e); 493 } 494 }); 495 } 496 } 497 498 public static boolean isColumnNullable( 499 DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theColumnName) 500 throws SQLException { 501 DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource()); 502 try (Connection connection = dataSource.getConnection()) { 503 //noinspection ConstantConditions 504 return theConnectionProperties.getTxTemplate().execute(t -> { 505 DatabaseMetaData metadata; 506 try { 507 metadata = connection.getMetaData(); 508 try (ResultSet tables = metadata.getColumns( 509 connection.getCatalog(), 510 connection.getSchema(), 511 massageIdentifier(metadata, theTableName), 512 null)) { 513 514 while (tables.next()) { 515 String tableName = tables.getString("TABLE_NAME").toUpperCase(Locale.US); 516 if (!theTableName.equalsIgnoreCase(tableName)) { 517 continue; 518 } 519 520 if (theColumnName.equalsIgnoreCase(tables.getString("COLUMN_NAME"))) { 521 String nullable = tables.getString("IS_NULLABLE"); 522 if ("YES".equalsIgnoreCase(nullable)) { 523 return true; 524 } else if ("NO".equalsIgnoreCase(nullable)) { 525 return false; 526 } else { 527 throw new IllegalStateException(Msg.code(41) + "Unknown nullable: " + nullable); 528 } 529 } 530 } 531 } 532 533 throw new IllegalStateException(Msg.code(42) + "Did not find column " + theColumnName); 534 } catch (SQLException e) { 535 throw new InternalErrorException(Msg.code(43) + e); 536 } 537 }); 538 } 539 } 540 541 public static void executeSql( 542 DriverTypeEnum.ConnectionProperties theConnectionProperties, 543 @Language("SQL") String theSql, 544 Object... theArgs) { 545 theConnectionProperties.getTxTemplate().execute(t -> { 546 theConnectionProperties.newJdbcTemplate().update(theSql, theArgs); 547 return null; 548 }); 549 } 550 551 public static List<Map<String, Object>> executeQuery( 552 DriverTypeEnum.ConnectionProperties theConnectionProperties, 553 @Language("SQL") String theSql, 554 Object... theArgs) { 555 return theConnectionProperties.getTxTemplate().execute(t -> theConnectionProperties 556 .newJdbcTemplate() 557 .query(theSql, theArgs, new ColumnMapRowMapper())); 558 } 559 560 public static String massageIdentifier(DatabaseMetaData theMetadata, String theIdentifier) throws SQLException { 561 String retVal = theIdentifier; 562 if (theIdentifier == null) { 563 return null; 564 } else if (theMetadata.storesLowerCaseIdentifiers()) { 565 retVal = retVal.toLowerCase(); 566 } else { 567 retVal = retVal.toUpperCase(); 568 } 569 return retVal; 570 } 571 572 public static class ColumnType { 573 private final ColumnTypeEnum myColumnTypeEnum; 574 private final Long myLength; 575 576 public ColumnType(ColumnTypeEnum theColumnType, Long theLength) { 577 myColumnTypeEnum = theColumnType; 578 myLength = theLength; 579 } 580 581 public ColumnType(ColumnTypeEnum theColumnType, int theLength) { 582 this(theColumnType, (long) theLength); 583 } 584 585 public ColumnType(ColumnTypeEnum theColumnType) { 586 this(theColumnType, null); 587 } 588 589 @Override 590 public boolean equals(Object theO) { 591 if (this == theO) { 592 return true; 593 } 594 595 if (theO == null || getClass() != theO.getClass()) { 596 return false; 597 } 598 599 ColumnType that = (ColumnType) theO; 600 601 return new EqualsBuilder() 602 .append(myColumnTypeEnum, that.myColumnTypeEnum) 603 .append(myLength, that.myLength) 604 .isEquals(); 605 } 606 607 @Override 608 public int hashCode() { 609 return new HashCodeBuilder(17, 37) 610 .append(myColumnTypeEnum) 611 .append(myLength) 612 .toHashCode(); 613 } 614 615 @Override 616 public String toString() { 617 ToStringBuilder b = new ToStringBuilder(this); 618 b.append("type", myColumnTypeEnum); 619 if (myLength != null) { 620 b.append("length", myLength); 621 } 622 return b.toString(); 623 } 624 625 public ColumnTypeEnum getColumnTypeEnum() { 626 return myColumnTypeEnum; 627 } 628 629 public Long getLength() { 630 return myLength; 631 } 632 633 public boolean equals(ColumnTypeEnum theTaskColumnType, Long theTaskColumnLength) { 634 ourLog.debug( 635 "Comparing existing {} {} to new {} {}", 636 myColumnTypeEnum, 637 myLength, 638 theTaskColumnType, 639 theTaskColumnLength); 640 return myColumnTypeEnum == theTaskColumnType 641 && (theTaskColumnLength == null || theTaskColumnLength.equals(myLength)); 642 } 643 } 644 645 private static class EmptyExtractionContext extends ExtractionContext.EmptyExtractionContext { 646 647 private final Connection myConnection; 648 private final Dialect myDialect; 649 650 public EmptyExtractionContext(Connection theConnection, Dialect theDialect) { 651 this.myConnection = theConnection; 652 this.myDialect = theDialect; 653 } 654 655 @Override 656 public Connection getJdbcConnection() { 657 return myConnection; 658 } 659 660 @Override 661 public ServiceRegistry getServiceRegistry() { 662 return super.getServiceRegistry(); 663 } 664 665 @Override 666 public JdbcEnvironment getJdbcEnvironment() { 667 return new JdbcEnvironment() { 668 669 @Override 670 public Dialect getDialect() { 671 return myDialect; 672 } 673 674 @Override 675 public SqlAstTranslatorFactory getSqlAstTranslatorFactory() { 676 return null; 677 } 678 679 @Override 680 public ExtractedDatabaseMetaData getExtractedDatabaseMetaData() { 681 return null; 682 } 683 684 @Override 685 public Identifier getCurrentCatalog() { 686 return null; 687 } 688 689 @Override 690 public Identifier getCurrentSchema() { 691 return null; 692 } 693 694 @Override 695 public QualifiedObjectNameFormatter getQualifiedObjectNameFormatter() { 696 return null; 697 } 698 699 @Override 700 public IdentifierHelper getIdentifierHelper() { 701 return new NormalizingIdentifierHelperImpl(this, null, true, true, true, true, null, null, null); 702 } 703 704 @Override 705 public NameQualifierSupport getNameQualifierSupport() { 706 return null; 707 } 708 709 @Override 710 public SqlExceptionHelper getSqlExceptionHelper() { 711 return null; 712 } 713 714 @Override 715 public LobCreatorBuilder getLobCreatorBuilder() { 716 return null; 717 } 718 }; 719 } 720 } 721}