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.toUpperCase(Locale.US));
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                                                                case Types.SMALLINT:
265                                                                        return new ColumnType(ColumnTypeEnum.SMALLINT, length);
266                                                                default:
267                                                                        throw new IllegalArgumentException(
268                                                                                        Msg.code(34) + "Don't know how to handle datatype " + dataType
269                                                                                                        + " for column " + theColumnName
270                                                                                                        + " on table " + theTableName);
271                                                        }
272                                                }
273                                        }
274
275                                        ourLog.debug("Unable to find column {} in table {}.", theColumnName, theTableName);
276                                        return null;
277
278                                } catch (SQLException e) {
279                                        throw new InternalErrorException(Msg.code(35) + e);
280                                }
281                        });
282                }
283        }
284
285        /**
286         * Retrieve all index names. The returned names will be in upper case
287         * always.
288         */
289        public static Set<String> getForeignKeys(
290                        DriverTypeEnum.ConnectionProperties theConnectionProperties,
291                        String theTableName,
292                        @Nullable String theForeignTable)
293                        throws SQLException {
294                DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
295
296                try (Connection connection = dataSource.getConnection()) {
297                        TransactionTemplate txTemplate = theConnectionProperties.getTxTemplate();
298                        return txTemplate.execute(t -> {
299                                DatabaseMetaData metadata;
300                                try {
301                                        metadata = connection.getMetaData();
302                                        String catalog = connection.getCatalog();
303                                        String schema = connection.getSchema();
304
305                                        List<String> parentTables = new ArrayList<>();
306                                        if (theTableName != null) {
307                                                parentTables.add(massageIdentifier(metadata, theTableName));
308                                        } else {
309                                                // If no foreign table is specified, we'll try all of them
310                                                parentTables.addAll(JdbcUtils.getTableNames(theConnectionProperties));
311                                        }
312
313                                        String foreignTable = massageIdentifier(metadata, theForeignTable);
314
315                                        Set<String> fkNames = new HashSet<>();
316                                        for (String nextParentTable : parentTables) {
317                                                try (ResultSet indexes = metadata.getCrossReference(
318                                                                catalog, schema, nextParentTable, catalog, schema, foreignTable)) {
319                                                        while (indexes.next()) {
320                                                                String fkName = indexes.getString("FK_NAME");
321                                                                fkName = fkName.toUpperCase(Locale.US);
322                                                                fkNames.add(fkName);
323                                                        }
324                                                }
325                                        }
326
327                                        return fkNames;
328                                } catch (SQLException e) {
329                                        throw new InternalErrorException(Msg.code(36) + e);
330                                }
331                        });
332                }
333        }
334
335        /**
336         * Retrieve names of foreign keys that reference a specified foreign key column.
337         */
338        public static Set<String> getForeignKeysForColumn(
339                        DriverTypeEnum.ConnectionProperties theConnectionProperties,
340                        String theForeignKeyColumn,
341                        String theForeignTable)
342                        throws SQLException {
343                DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
344
345                try (Connection connection = dataSource.getConnection()) {
346                        return theConnectionProperties.getTxTemplate().execute(t -> {
347                                DatabaseMetaData metadata;
348                                try {
349                                        metadata = connection.getMetaData();
350                                        String catalog = connection.getCatalog();
351                                        String schema = connection.getSchema();
352
353                                        List<String> parentTables = new ArrayList<>();
354                                        parentTables.addAll(JdbcUtils.getTableNames(theConnectionProperties));
355
356                                        String foreignTable = massageIdentifier(metadata, theForeignTable);
357
358                                        Set<String> fkNames = new HashSet<>();
359                                        for (String nextParentTable : parentTables) {
360                                                try (ResultSet indexes = metadata.getCrossReference(
361                                                                catalog, schema, nextParentTable, catalog, schema, foreignTable)) {
362                                                        while (indexes.next()) {
363                                                                if (theForeignKeyColumn.equals(indexes.getString("FKCOLUMN_NAME"))) {
364                                                                        String fkName = indexes.getString("FK_NAME");
365                                                                        fkName = fkName.toUpperCase(Locale.US);
366                                                                        fkNames.add(fkName);
367                                                                }
368                                                        }
369                                                }
370                                        }
371
372                                        return fkNames;
373                                } catch (SQLException e) {
374                                        throw new InternalErrorException(Msg.code(37) + e);
375                                }
376                        });
377                }
378        }
379
380        /**
381         * Retrieve all index names
382         */
383        public static Set<String> getColumnNames(
384                        DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName) throws SQLException {
385                DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
386                try (Connection connection = dataSource.getConnection()) {
387                        return theConnectionProperties.getTxTemplate().execute(t -> {
388                                DatabaseMetaData metadata;
389                                try {
390                                        metadata = connection.getMetaData();
391                                        LinkedCaseInsensitiveMap<String> columnNames = new LinkedCaseInsensitiveMap<>();
392
393                                        try (ResultSet indexes = metadata.getColumns(
394                                                        connection.getCatalog(),
395                                                        connection.getSchema(),
396                                                        massageIdentifier(metadata, theTableName),
397                                                        null)) {
398
399                                                while (indexes.next()) {
400                                                        String tableName = indexes.getString("TABLE_NAME").toUpperCase(Locale.US);
401                                                        if (!theTableName.equalsIgnoreCase(tableName)) {
402                                                                continue;
403                                                        }
404
405                                                        String columnName = indexes.getString("COLUMN_NAME");
406                                                        columnName = columnName.toUpperCase(Locale.US);
407                                                        columnNames.put(columnName, columnName);
408                                                }
409                                        }
410
411                                        return columnNames.keySet();
412                                } catch (SQLException e) {
413                                        throw new InternalErrorException(Msg.code(38) + e);
414                                }
415                        });
416                }
417        }
418
419        public static Set<String> getSequenceNames(DriverTypeEnum.ConnectionProperties theConnectionProperties)
420                        throws SQLException {
421                List<SequenceInformation> sequenceInformation = getSequenceInformation(theConnectionProperties);
422
423                return sequenceInformation.stream()
424                                .map(SequenceInformation::getSequenceName)
425                                .map(QualifiedSequenceName::getSequenceName)
426                                .map(Identifier::getText)
427                                .collect(Collectors.toSet());
428        }
429
430        @Nonnull
431        public static List<SequenceInformation> getSequenceInformation(
432                        DriverTypeEnum.ConnectionProperties theConnectionProperties) throws SQLException {
433                DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
434                try (Connection connection = dataSource.getConnection()) {
435                        return Objects.requireNonNull(
436                                        theConnectionProperties.getTxTemplate().execute(t -> {
437                                                try {
438                                                        DialectResolver dialectResolver = new StandardDialectResolver();
439                                                        Dialect dialect = dialectResolver.resolveDialect(
440                                                                        new DatabaseMetaDataDialectResolutionInfoAdapter(connection.getMetaData()));
441
442                                                        List<SequenceInformation> sequenceInformation = new ArrayList<>();
443                                                        if (dialect.getSequenceSupport().supportsSequences()) {
444
445                                                                // Use Hibernate to get a list of current sequences
446                                                                SequenceInformationExtractor sequenceInformationExtractor =
447                                                                                dialect.getSequenceInformationExtractor();
448                                                                ExtractionContext extractionContext = new EmptyExtractionContext(connection, dialect);
449                                                                Iterable<SequenceInformation> sequenceInformationIterator =
450                                                                                sequenceInformationExtractor.extractMetadata(extractionContext);
451
452                                                                return StreamSupport.stream(sequenceInformationIterator.spliterator(), false)
453                                                                                .collect(Collectors.toList());
454                                                        }
455                                                        return sequenceInformation;
456                                                } catch (SQLException e) {
457                                                        throw new InternalErrorException(Msg.code(39) + e);
458                                                }
459                                        }));
460                }
461        }
462
463        public static Set<String> getTableNames(DriverTypeEnum.ConnectionProperties theConnectionProperties)
464                        throws SQLException {
465                DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
466                try (Connection connection = dataSource.getConnection()) {
467                        return theConnectionProperties.getTxTemplate().execute(t -> {
468                                DatabaseMetaData metadata;
469                                try {
470                                        metadata = connection.getMetaData();
471                                        Set<String> columnNames = new HashSet<>();
472
473                                        try (ResultSet tables =
474                                                        metadata.getTables(connection.getCatalog(), connection.getSchema(), null, null)) {
475
476                                                while (tables.next()) {
477                                                        String tableName = tables.getString("TABLE_NAME");
478                                                        tableName = tableName.toUpperCase(Locale.US);
479
480                                                        String tableType = tables.getString("TABLE_TYPE");
481                                                        if ("SYSTEM TABLE".equalsIgnoreCase(tableType)) {
482                                                                continue;
483                                                        }
484                                                        if (SchemaMigrator.HAPI_FHIR_MIGRATION_TABLENAME.equalsIgnoreCase(tableName)) {
485                                                                continue;
486                                                        }
487
488                                                        columnNames.add(tableName);
489                                                }
490                                        }
491
492                                        return columnNames;
493                                } catch (SQLException e) {
494                                        throw new InternalErrorException(Msg.code(40) + e);
495                                }
496                        });
497                }
498        }
499
500        public static boolean isColumnNullable(
501                        DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theColumnName)
502                        throws SQLException {
503                DataSource dataSource = Objects.requireNonNull(theConnectionProperties.getDataSource());
504                try (Connection connection = dataSource.getConnection()) {
505                        //noinspection ConstantConditions
506                        return theConnectionProperties.getTxTemplate().execute(t -> {
507                                DatabaseMetaData metadata;
508                                try {
509                                        metadata = connection.getMetaData();
510                                        try (ResultSet tables = metadata.getColumns(
511                                                        connection.getCatalog(),
512                                                        connection.getSchema(),
513                                                        massageIdentifier(metadata, theTableName),
514                                                        null)) {
515
516                                                while (tables.next()) {
517                                                        String tableName = tables.getString("TABLE_NAME").toUpperCase(Locale.US);
518                                                        if (!theTableName.equalsIgnoreCase(tableName)) {
519                                                                continue;
520                                                        }
521
522                                                        if (theColumnName.equalsIgnoreCase(tables.getString("COLUMN_NAME"))) {
523                                                                String nullable = tables.getString("IS_NULLABLE");
524                                                                if ("YES".equalsIgnoreCase(nullable)) {
525                                                                        return true;
526                                                                } else if ("NO".equalsIgnoreCase(nullable)) {
527                                                                        return false;
528                                                                } else {
529                                                                        throw new IllegalStateException(Msg.code(41) + "Unknown nullable: " + nullable);
530                                                                }
531                                                        }
532                                                }
533                                        }
534
535                                        throw new IllegalStateException(Msg.code(42) + "Did not find column " + theColumnName);
536                                } catch (SQLException e) {
537                                        throw new InternalErrorException(Msg.code(43) + e);
538                                }
539                        });
540                }
541        }
542
543        public static void executeSql(
544                        DriverTypeEnum.ConnectionProperties theConnectionProperties,
545                        @Language("SQL") String theSql,
546                        Object... theArgs) {
547                theConnectionProperties.getTxTemplate().execute(t -> {
548                        theConnectionProperties.newJdbcTemplate().update(theSql, theArgs);
549                        return null;
550                });
551        }
552
553        public static List<Map<String, Object>> executeQuery(
554                        DriverTypeEnum.ConnectionProperties theConnectionProperties,
555                        @Language("SQL") String theSql,
556                        Object... theArgs) {
557                return theConnectionProperties.getTxTemplate().execute(t -> theConnectionProperties
558                                .newJdbcTemplate()
559                                .query(theSql, theArgs, new ColumnMapRowMapper()));
560        }
561
562        public static String massageIdentifier(DatabaseMetaData theMetadata, String theIdentifier) throws SQLException {
563                String retVal = theIdentifier;
564                if (theIdentifier == null) {
565                        return null;
566                } else if (theMetadata.storesLowerCaseIdentifiers()) {
567                        retVal = retVal.toLowerCase();
568                } else {
569                        retVal = retVal.toUpperCase();
570                }
571                return retVal;
572        }
573
574        public static class ColumnType {
575                private final ColumnTypeEnum myColumnTypeEnum;
576                private final Long myLength;
577
578                public ColumnType(ColumnTypeEnum theColumnType, Long theLength) {
579                        myColumnTypeEnum = theColumnType;
580                        myLength = theLength;
581                }
582
583                public ColumnType(ColumnTypeEnum theColumnType, int theLength) {
584                        this(theColumnType, (long) theLength);
585                }
586
587                public ColumnType(ColumnTypeEnum theColumnType) {
588                        this(theColumnType, null);
589                }
590
591                @Override
592                public boolean equals(Object theO) {
593                        if (this == theO) {
594                                return true;
595                        }
596
597                        if (theO == null || getClass() != theO.getClass()) {
598                                return false;
599                        }
600
601                        ColumnType that = (ColumnType) theO;
602
603                        return new EqualsBuilder()
604                                        .append(myColumnTypeEnum, that.myColumnTypeEnum)
605                                        .append(myLength, that.myLength)
606                                        .isEquals();
607                }
608
609                @Override
610                public int hashCode() {
611                        return new HashCodeBuilder(17, 37)
612                                        .append(myColumnTypeEnum)
613                                        .append(myLength)
614                                        .toHashCode();
615                }
616
617                @Override
618                public String toString() {
619                        ToStringBuilder b = new ToStringBuilder(this);
620                        b.append("type", myColumnTypeEnum);
621                        if (myLength != null) {
622                                b.append("length", myLength);
623                        }
624                        return b.toString();
625                }
626
627                public ColumnTypeEnum getColumnTypeEnum() {
628                        return myColumnTypeEnum;
629                }
630
631                public Long getLength() {
632                        return myLength;
633                }
634
635                public boolean equals(ColumnTypeEnum theTaskColumnType, Long theTaskColumnLength) {
636                        ourLog.debug(
637                                        "Comparing existing {} {} to new {} {}",
638                                        myColumnTypeEnum,
639                                        myLength,
640                                        theTaskColumnType,
641                                        theTaskColumnLength);
642                        return myColumnTypeEnum == theTaskColumnType
643                                        && (theTaskColumnLength == null || theTaskColumnLength.equals(myLength));
644                }
645        }
646
647        private static class EmptyExtractionContext extends ExtractionContext.EmptyExtractionContext {
648
649                private final Connection myConnection;
650                private final Dialect myDialect;
651
652                public EmptyExtractionContext(Connection theConnection, Dialect theDialect) {
653                        this.myConnection = theConnection;
654                        this.myDialect = theDialect;
655                }
656
657                @Override
658                public Connection getJdbcConnection() {
659                        return myConnection;
660                }
661
662                @Override
663                public ServiceRegistry getServiceRegistry() {
664                        return super.getServiceRegistry();
665                }
666
667                @Override
668                public JdbcEnvironment getJdbcEnvironment() {
669                        return new JdbcEnvironment() {
670
671                                @Override
672                                public Dialect getDialect() {
673                                        return myDialect;
674                                }
675
676                                @Override
677                                public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
678                                        return null;
679                                }
680
681                                @Override
682                                public ExtractedDatabaseMetaData getExtractedDatabaseMetaData() {
683                                        return null;
684                                }
685
686                                @Override
687                                public Identifier getCurrentCatalog() {
688                                        return null;
689                                }
690
691                                @Override
692                                public Identifier getCurrentSchema() {
693                                        return null;
694                                }
695
696                                @Override
697                                public QualifiedObjectNameFormatter getQualifiedObjectNameFormatter() {
698                                        return null;
699                                }
700
701                                @Override
702                                public IdentifierHelper getIdentifierHelper() {
703                                        return new NormalizingIdentifierHelperImpl(this, null, true, true, true, true, null, null, null);
704                                }
705
706                                @Override
707                                public NameQualifierSupport getNameQualifierSupport() {
708                                        return null;
709                                }
710
711                                @Override
712                                public SqlExceptionHelper getSqlExceptionHelper() {
713                                        return null;
714                                }
715
716                                @Override
717                                public LobCreatorBuilder getLobCreatorBuilder() {
718                                        return null;
719                                }
720                        };
721                }
722        }
723}