<commit_before>/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2021 DBeaver Corp and others
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jkiss.dbeaver.ext.mssql.model;

import org.eclipse.core.runtime.IAdaptable;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ModelPreferences;
import org.jkiss.dbeaver.ext.mssql.SQLServerConstants;
import org.jkiss.dbeaver.ext.mssql.SQLServerUtils;
import org.jkiss.dbeaver.ext.mssql.model.session.SQLServerSessionManager;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.access.DBAUserChangePassword;
import org.jkiss.dbeaver.model.admin.sessions.DBAServerSessionManager;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.exec.jdbc.*;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCDataSource;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCRemoteInstance;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectCache;
import org.jkiss.dbeaver.model.impl.net.SSLHandlerTrustStoreImpl;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLQuery;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.BeanUtils;
import org.jkiss.utils.CommonUtils;

import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

public class SQLServerDataSource extends JDBCDataSource implements DBSInstanceContainer, DBPObjectStatisticsCollector, IAdaptable, DBCQueryTransformProviderExt {

    private static final Log log = Log.getLog(SQLServerDataSource.class);

    // Delegate data type reading to the driver
    private final SystemDataTypeCache dataTypeCache = new SystemDataTypeCache();
    private final DatabaseCache databaseCache = new DatabaseCache();
    private final ServerLoginCache serverLoginCache = new ServerLoginCache();

    private boolean supportsColumnProperty;
    private String serverVersion;

    private volatile transient boolean hasStatistics;

    public SQLServerDataSource(DBRProgressMonitor monitor, DBPDataSourceContainer container)
        throws DBException
    {
        super(monitor, container, new SQLServerDialect());
    }

    public boolean supportsColumnProperty() {
        return supportsColumnProperty;
    }

    @Override
    protected DBPDataSourceInfo createDataSourceInfo(DBRProgressMonitor monitor, @NotNull JDBCDatabaseMetaData metaData)
    {
        SQLServerDataSourceInfo info = new SQLServerDataSourceInfo(this, metaData);
        if (isDataWarehouseServer(monitor)) {
            info.setSupportsResultSetScroll(false);
        }
        return info;
    }

    public boolean isDataWarehouseServer(DBRProgressMonitor monitor) {
        return getServerVersion(monitor).contains(SQLServerConstants.SQL_DW_SERVER_LABEL);
    }

    public String getServerVersion() {
        return serverVersion;
    }

    private boolean isNotBabelfishDatasource() {
        boolean isBabelfish = SQLServerUtils.isDriverBabelfish(getContainer().getDriver());
        return !isBabelfish;
    }

    public boolean supportsTriggers() {
        return isNotBabelfishDatasource();
    }

    public boolean supportsSynonyms() {
        return isNotBabelfishDatasource();
    }

    public boolean supportsSequences() {
        return isNotBabelfishDatasource();
    }

    String getServerVersion(DBRProgressMonitor monitor) {
        if (serverVersion == null) {
            try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Read server version")) {
                serverVersion = JDBCUtils.queryString(session, "SELECT @@VERSION");
            } catch (Exception e) {
                log.debug("Error reading SQL Server version: " + e.getMessage());
                serverVersion = "";
            }
        }
        return serverVersion;
    }

    @NotNull
    @Override
    public DBPDataSource getDataSource() {
        return this;
    }

    public DatabaseCache getDatabaseCache() {
        return databaseCache;
    }

    @Association
    public List<SQLServerLogin> getLogins(@NotNull DBRProgressMonitor monitor) throws DBException {
        return serverLoginCache.getAllObjects(monitor, this);
    }

    @Association
    public SQLServerLogin getLogin(@NotNull DBRProgressMonitor monitor, @NotNull String loginName) throws DBException {
        return serverLoginCache.getObject(monitor, this, loginName);
    }

    public ServerLoginCache getServerLoginCache() {
        return serverLoginCache;
    }

    @Override
    protected Properties getAllConnectionProperties(@NotNull DBRProgressMonitor monitor, JDBCExecutionContext context, String purpose, DBPConnectionConfiguration connectionInfo) throws DBCException {
        Properties properties = super.getAllConnectionProperties(monitor, context, purpose, connectionInfo);

        if (!getContainer().getPreferenceStore().getBoolean(ModelPreferences.META_CLIENT_NAME_DISABLE)) {
            // App name
            properties.put(
                SQLServerUtils.isDriverJtds(getContainer().getDriver()) ? SQLServerConstants.APPNAME_CLIENT_PROPERTY : SQLServerConstants.APPLICATION_NAME_CLIENT_PROPERTY,
                CommonUtils.truncateString(DBUtils.getClientApplicationName(getContainer(), context, purpose), 64));
        }

        fillConnectionProperties(connectionInfo, properties);

        SQLServerAuthentication authSchema = SQLServerUtils.detectAuthSchema(connectionInfo);

        authSchema.getInitializer().initializeAuthentication(connectionInfo, properties);

        final DBWHandlerConfiguration sslConfig = getContainer().getActualConnectionConfiguration().getHandler(SQLServerConstants.HANDLER_SSL);
        if (sslConfig != null && sslConfig.isEnabled()) {
            initSSL(monitor, properties, sslConfig);
        }

        return properties;
    }

    private void initSSL(DBRProgressMonitor monitor, Properties properties, DBWHandlerConfiguration sslConfig) throws DBCException {
        monitor.subTask("Install SSL certificates");

        try {
//            SSLHandlerTrustStoreImpl.initializeTrustStore(monitor, this, sslConfig);
//            DBACertificateStorage certificateStorage = getContainer().getPlatform().getCertificateStorage();
//            String keyStorePath = certificateStorage.getKeyStorePath(getContainer(), "ssl").getAbsolutePath();

            properties.put("encrypt", "true");
            properties.put("trustServerCertificate", sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_TRUST_SERVER_CERTIFICATE));

            final String keystoreFileProp;
            final String keystorePasswordProp;

            if (CommonUtils.isEmpty(sslConfig.getStringProperty(SSLHandlerTrustStoreImpl.PROP_SSL_METHOD))) {
                // Backward compatibility
                keystoreFileProp = sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_KEYSTORE);
                keystorePasswordProp = sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_KEYSTORE_PASSWORD);
            } else {
                keystoreFileProp = sslConfig.getStringProperty(SSLHandlerTrustStoreImpl.PROP_SSL_KEYSTORE);
                keystorePasswordProp = sslConfig.getPassword();
            }

            if (!CommonUtils.isEmpty(keystoreFileProp)) {
                properties.put("trustStore", keystoreFileProp);
            }

            if (!CommonUtils.isEmpty(keystorePasswordProp)) {
                properties.put("trustStorePassword", keystorePasswordProp);
            }

            final String keystoreHostnameProp = sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_KEYSTORE_HOSTNAME);
            if (!CommonUtils.isEmpty(keystoreHostnameProp)) {
                properties.put("hostNameInCertificate", keystoreHostnameProp);
            }
        } catch (Exception e) {
            throw new DBCException("Error initializing SSL trust store", e);
        }
    }

    @Override
    protected JDBCExecutionContext createExecutionContext(JDBCRemoteInstance instance, String type) {
        return new SQLServerExecutionContext(instance, type);
    }

    @Override
    protected void initializeContextState(@NotNull DBRProgressMonitor monitor, @NotNull JDBCExecutionContext context, JDBCExecutionContext initFrom) throws DBException {
        super.initializeContextState(monitor, context, initFrom);
        if (initFrom != null) {
            if (!isDataWarehouseServer(monitor)) {
                SQLServerExecutionContext ssContext = (SQLServerExecutionContext) initFrom;
                SQLServerDatabase defaultObject = ssContext.getDefaultCatalog();
                if (defaultObject != null) {
                    ((SQLServerExecutionContext)context).setCurrentDatabase(monitor, defaultObject);
                }
                SQLServerSchema defaultSchema = ssContext.getDefaultSchema();
                if (defaultSchema != null && !isDataWarehouseServer(monitor)) {
                    ((SQLServerExecutionContext)context).setDefaultSchema(monitor, defaultSchema);
                }
            }
        } else {
            ((SQLServerExecutionContext)context).refreshDefaults(monitor, true);
        }
    }

    @Override
    public Object getDataSourceFeature(String featureId) {
        switch (featureId) {
            case DBPDataSource.FEATURE_LIMIT_AFFECTS_DML:
                return true;
            case DBPDataSource.FEATURE_MAX_STRING_LENGTH:
                return 8000;
        }
        return super.getDataSourceFeature(featureId);
    }

    @Override
    public void initialize(@NotNull DBRProgressMonitor monitor) throws DBException {
        super.initialize(monitor);

        this.dataTypeCache.getAllObjects(monitor, this);
        this.databaseCache.getAllObjects(monitor, this);

        try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Load data source meta info")) {
            try {
                JDBCUtils.queryString(session, "SELECT COLUMNPROPERTY(0, NULL, NULL)");
                this.supportsColumnProperty = true;
            } catch (Exception e) {
                this.supportsColumnProperty = false;
            }
        } catch (Throwable e) {
            log.error("Error during connection initialization", e);
        }
    }

    //////////////////////////////////////////////////////////////////
    // Data types

    @NotNull
    @Override
    public DBPDataKind resolveDataKind(@NotNull String typeName, int valueType) {
        return getLocalDataType(valueType).getDataKind();
    }

    @Override
    public List<SQLServerDataType> getLocalDataTypes() {
        return dataTypeCache.getCachedObjects();
    }

    SQLServerDataType getSystemDataType(int systemTypeId) {
        for (SQLServerDataType dt : dataTypeCache.getCachedObjects()) {
            if (dt.getObjectId() == systemTypeId) {
                return dt;
            }
        }
        if (systemTypeId != SQLServerConstants.TABLE_TYPE_SYSTEM_ID) { // 243 - ID of user defined table types
            log.debug("System data type " + systemTypeId + " not found");
        }
        SQLServerDataType sdt = new SQLServerDataType(this, String.valueOf(systemTypeId), systemTypeId, DBPDataKind.OBJECT, java.sql.Types.OTHER);
        dataTypeCache.cacheObject(sdt);
        return sdt;
    }

    @Override
    public SQLServerDataType getLocalDataType(String typeName) {
        return dataTypeCache.getCachedObject(typeName);
    }

    @Override
    public SQLServerDataType getLocalDataType(int typeID) {
        DBSDataType dt = super.getLocalDataType(typeID);
        if (dt == null) {
            log.debug("System data type " + typeID + " not found");
        }
        return (SQLServerDataType) dt;
    }

    @Override
    public String getDefaultDataTypeName(@NotNull DBPDataKind dataKind) {
        switch (dataKind) {
            case BOOLEAN: return "bit";
            case NUMERIC: return "int";
            case STRING: return "varchar";
            case DATETIME: return SQLServerConstants.TYPE_DATETIME;
            case BINARY:
            case CONTENT: return "varbinary";
            case ROWID: return "uniqueidentifier";
            default:
                return super.getDefaultDataTypeName(dataKind);
        }
    }

    //////////////////////////////////////////////////////////
    // Databases

    protected boolean isShowAllSchemas() {
        return CommonUtils.toBoolean(getContainer().getConnectionConfiguration().getProviderProperty(SQLServerConstants.PROP_SHOW_ALL_SCHEMAS));
    }

    //////////////////////////////////////////////////////////////
    // Databases

    @Association
    public Collection<SQLServerDatabase> getDatabases(DBRProgressMonitor monitor) throws DBException {
        return databaseCache.getAllObjects(monitor, this);
    }

    public SQLServerDatabase getDatabase(DBRProgressMonitor monitor, String name) throws DBException {
        return databaseCache.getObject(monitor, this, name);
    }

    public SQLServerDatabase getDatabase(DBRProgressMonitor monitor, long dbId) throws DBException {
        for (SQLServerDatabase db : databaseCache.getAllObjects(monitor, this)) {
            if (db.getDatabaseId() == dbId) {
                return db;
            }
        }
        return null;
    }

    public SQLServerDatabase getDatabase(String name) {
        return databaseCache.getCachedObject(name);
    }

    public SQLServerDatabase getDefaultDatabase(@NotNull DBRProgressMonitor monitor) {
        return ((SQLServerExecutionContext)getDefaultInstance().getDefaultContext(monitor, true)).getDefaultCatalog();
    }

    @Override
    public Collection<? extends DBSObject> getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
        return databaseCache.getAllObjects(monitor, this);
    }

    @Override
    public DBSObject getChild(@NotNull DBRProgressMonitor monitor, @NotNull String childName) throws DBException {
        return databaseCache.getObject(monitor, this, childName);
    }

    @NotNull
    @Override
    public Class<? extends DBSObject> getPrimaryChildType(@Nullable DBRProgressMonitor monitor) throws DBException {
        return SQLServerDatabase.class;
    }

    @Override
    public void cacheStructure(@NotNull DBRProgressMonitor monitor, int scope) throws DBException {
        databaseCache.getAllObjects(monitor, this);
    }

    @Override
    public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException {
        databaseCache.clearCache();
        serverLoginCache.clearCache();
        hasStatistics = false;
        return super.refreshObject(monitor);
    }

    @Override
    public DBCQueryTransformer createQueryTransformer(DBCQueryTransformType type) {
        if (type == DBCQueryTransformType.RESULT_SET_LIMIT) {
            //if (!SQLServerUtils.isDriverAzure(getContainer().getDriver())) {
                return new QueryTransformerTop();
            //}
        }
        return super.createQueryTransformer(type);
    }

    @Override
    public <T> T getAdapter(Class<T> adapter) {
        if (adapter == DBSStructureAssistant.class) {
            return adapter.cast(new SQLServerStructureAssistant(this));
        } else if (adapter == DBAServerSessionManager.class) {
            return adapter.cast(new SQLServerSessionManager(this));
        } else if (adapter == DBAUserChangePassword.class) {
            return adapter.cast(new SQLServerChangeLoginPassword(this));
        }
        return super.getAdapter(adapter);
    }

    @Override
    public ErrorPosition[] getErrorPosition(DBRProgressMonitor monitor, DBCExecutionContext context, String query, Throwable error) {
        Throwable rootCause = GeneralUtils.getRootCause(error);
        if (rootCause != null && SQLServerConstants.SQL_SERVER_EXCEPTION_CLASS_NAME.equals(rootCause.getClass().getName())) {
            // Read line number from SQLServerError class
            try {
                Object serverError = rootCause.getClass().getMethod("getSQLServerError").invoke(rootCause);
                if (serverError != null) {
                    Object serverErrorLine = BeanUtils.readObjectProperty(serverError, "lineNumber");
                    if (serverErrorLine instanceof Number) {
                        ErrorPosition pos = new ErrorPosition();
                        pos.line = ((Number) serverErrorLine).intValue() - 1;
                        return new ErrorPosition[] {pos};
                    }
                }
            } catch (Throwable e) {
                // ignore
            }
        }

        return super.getErrorPosition(monitor, context, query, error);
    }

    @Override
    public boolean isStatisticsCollected() {
        return hasStatistics;
    }

    @Override
    public void collectObjectStatistics(DBRProgressMonitor monitor, boolean totalSizeOnly, boolean forceRefresh) throws DBException {
        if (hasStatistics && !forceRefresh) {
            return;
        }
        if (SQLServerUtils.isDriverAzure(getContainer().getDriver()) || SQLServerUtils.isDriverBabelfish(getContainer().getDriver()) || isDataWarehouseServer(monitor)) {
            hasStatistics = true;
            return;
        }
        try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Load schema statistics")) {
            try (JDBCStatement dbStat = session.createStatement()) {
                try (JDBCResultSet dbResult = dbStat.executeQuery("SELECT database_id, SUM(size)\n" +
                    "FROM sys.master_files WITH(NOWAIT)\n" +
                    "GROUP BY database_id")) {
                    while (dbResult.next()) {
                        long dbId = JDBCUtils.safeGetLong(dbResult, 1);
                        long bytes = dbResult.getLong(2) * 8 * 1024;
                        SQLServerDatabase database = getDatabase(monitor, dbId);
                        if (database != null) {
                            database.setDatabaseTotalSize(bytes);
                        }
                    }
                }
            }
        } catch (SQLException e) {
            throw new DBCException("Error reading database statistics", e);
        } finally {
            hasStatistics = true;
        }
    }

    @Override
    public boolean isForceTransform(DBCSession session, SQLQuery sqlQuery) {
        try {
            SQLServerTableBase table = SQLServerUtils.getTableFromQuery(session, sqlQuery, this);
            return table != null && table.isClustered(session.getProgressMonitor());
        } catch (DBException | SQLException e) {
            log.debug("Table not found. ", e);
        }
        return false;
    }

    static class DatabaseCache extends JDBCObjectCache<SQLServerDataSource, SQLServerDatabase> {
        DatabaseCache() {
            setListOrderComparator(DBUtils.nameComparator());
        }

        @NotNull
        @Override
        protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull SQLServerDataSource owner) throws SQLException {
            StringBuilder sql;
            if(owner.isNotBabelfishDatasource()) {
                sql = new StringBuilder("SELECT db.* FROM sys.databases db");
            } else {
                sql = new StringBuilder("SELECT db.* FROM (SELECT * FROM sys.databases WHERE name = db_name()) db");
            }

            DBSObjectFilter databaseFilters = owner.getContainer().getObjectFilter(SQLServerDatabase.class, null, false);
            if (databaseFilters != null && databaseFilters.isEnabled()) {
                JDBCUtils.appendFilterClause(sql, databaseFilters, "name", true, owner);
            }
            sql.append("\nORDER BY db.name");
            JDBCPreparedStatement dbStat = session.prepareStatement(sql.toString());
            if (databaseFilters != null) {
                JDBCUtils.setFilterParameters(dbStat, 1, databaseFilters);
            }
            return dbStat;
        }

        @Override
        protected SQLServerDatabase fetchObject(@NotNull JDBCSession session, @NotNull SQLServerDataSource owner, @NotNull JDBCResultSet resultSet) throws SQLException, DBException {
            return new SQLServerDatabase(session, owner, resultSet);
        }<commit_msg>#14669 simplify database filtering query for babelfish<commit_after>/*
 * DBeaver - Universal Database Manager
 * Copyright (C) 2010-2021 DBeaver Corp and others
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jkiss.dbeaver.ext.mssql.model;

import org.eclipse.core.runtime.IAdaptable;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ModelPreferences;
import org.jkiss.dbeaver.ext.mssql.SQLServerConstants;
import org.jkiss.dbeaver.ext.mssql.SQLServerUtils;
import org.jkiss.dbeaver.ext.mssql.model.session.SQLServerSessionManager;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.access.DBAUserChangePassword;
import org.jkiss.dbeaver.model.admin.sessions.DBAServerSessionManager;
import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.exec.jdbc.*;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCDataSource;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCExecutionContext;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCRemoteInstance;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.cache.JDBCObjectCache;
import org.jkiss.dbeaver.model.impl.net.SSLHandlerTrustStoreImpl;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLQuery;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.BeanUtils;
import org.jkiss.utils.CommonUtils;

import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

public class SQLServerDataSource extends JDBCDataSource implements DBSInstanceContainer, DBPObjectStatisticsCollector, IAdaptable, DBCQueryTransformProviderExt {

    private static final Log log = Log.getLog(SQLServerDataSource.class);

    // Delegate data type reading to the driver
    private final SystemDataTypeCache dataTypeCache = new SystemDataTypeCache();
    private final DatabaseCache databaseCache = new DatabaseCache();
    private final ServerLoginCache serverLoginCache = new ServerLoginCache();

    private boolean supportsColumnProperty;
    private String serverVersion;

    private volatile transient boolean hasStatistics;
    private boolean isBabelfish;

    public SQLServerDataSource(DBRProgressMonitor monitor, DBPDataSourceContainer container)
        throws DBException
    {
        super(monitor, container, new SQLServerDialect());
        isBabelfish = SQLServerUtils.isDriverBabelfish(getContainer().getDriver());
    }

    public boolean supportsColumnProperty() {
        return supportsColumnProperty;
    }

    @Override
    protected DBPDataSourceInfo createDataSourceInfo(DBRProgressMonitor monitor, @NotNull JDBCDatabaseMetaData metaData)
    {
        SQLServerDataSourceInfo info = new SQLServerDataSourceInfo(this, metaData);
        if (isDataWarehouseServer(monitor)) {
            info.setSupportsResultSetScroll(false);
        }
        return info;
    }

    public boolean isDataWarehouseServer(DBRProgressMonitor monitor) {
        return getServerVersion(monitor).contains(SQLServerConstants.SQL_DW_SERVER_LABEL);
    }

    public String getServerVersion() {
        return serverVersion;
    }

    public boolean supportsTriggers() {
        return !isBabelfish;
    }

    public boolean supportsSynonyms() {
        return !isBabelfish;
    }

    public boolean supportsSequences() {
        return !isBabelfish;
    }

    String getServerVersion(DBRProgressMonitor monitor) {
        if (serverVersion == null) {
            try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Read server version")) {
                serverVersion = JDBCUtils.queryString(session, "SELECT @@VERSION");
            } catch (Exception e) {
                log.debug("Error reading SQL Server version: " + e.getMessage());
                serverVersion = "";
            }
        }
        return serverVersion;
    }

    @NotNull
    @Override
    public DBPDataSource getDataSource() {
        return this;
    }

    public DatabaseCache getDatabaseCache() {
        return databaseCache;
    }

    @Association
    public List<SQLServerLogin> getLogins(@NotNull DBRProgressMonitor monitor) throws DBException {
        return serverLoginCache.getAllObjects(monitor, this);
    }

    @Association
    public SQLServerLogin getLogin(@NotNull DBRProgressMonitor monitor, @NotNull String loginName) throws DBException {
        return serverLoginCache.getObject(monitor, this, loginName);
    }

    public ServerLoginCache getServerLoginCache() {
        return serverLoginCache;
    }

    @Override
    protected Properties getAllConnectionProperties(@NotNull DBRProgressMonitor monitor, JDBCExecutionContext context, String purpose, DBPConnectionConfiguration connectionInfo) throws DBCException {
        Properties properties = super.getAllConnectionProperties(monitor, context, purpose, connectionInfo);

        if (!getContainer().getPreferenceStore().getBoolean(ModelPreferences.META_CLIENT_NAME_DISABLE)) {
            // App name
            properties.put(
                SQLServerUtils.isDriverJtds(getContainer().getDriver()) ? SQLServerConstants.APPNAME_CLIENT_PROPERTY : SQLServerConstants.APPLICATION_NAME_CLIENT_PROPERTY,
                CommonUtils.truncateString(DBUtils.getClientApplicationName(getContainer(), context, purpose), 64));
        }

        fillConnectionProperties(connectionInfo, properties);

        SQLServerAuthentication authSchema = SQLServerUtils.detectAuthSchema(connectionInfo);

        authSchema.getInitializer().initializeAuthentication(connectionInfo, properties);

        final DBWHandlerConfiguration sslConfig = getContainer().getActualConnectionConfiguration().getHandler(SQLServerConstants.HANDLER_SSL);
        if (sslConfig != null && sslConfig.isEnabled()) {
            initSSL(monitor, properties, sslConfig);
        }

        return properties;
    }

    private void initSSL(DBRProgressMonitor monitor, Properties properties, DBWHandlerConfiguration sslConfig) throws DBCException {
        monitor.subTask("Install SSL certificates");

        try {
//            SSLHandlerTrustStoreImpl.initializeTrustStore(monitor, this, sslConfig);
//            DBACertificateStorage certificateStorage = getContainer().getPlatform().getCertificateStorage();
//            String keyStorePath = certificateStorage.getKeyStorePath(getContainer(), "ssl").getAbsolutePath();

            properties.put("encrypt", "true");
            properties.put("trustServerCertificate", sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_TRUST_SERVER_CERTIFICATE));

            final String keystoreFileProp;
            final String keystorePasswordProp;

            if (CommonUtils.isEmpty(sslConfig.getStringProperty(SSLHandlerTrustStoreImpl.PROP_SSL_METHOD))) {
                // Backward compatibility
                keystoreFileProp = sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_KEYSTORE);
                keystorePasswordProp = sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_KEYSTORE_PASSWORD);
            } else {
                keystoreFileProp = sslConfig.getStringProperty(SSLHandlerTrustStoreImpl.PROP_SSL_KEYSTORE);
                keystorePasswordProp = sslConfig.getPassword();
            }

            if (!CommonUtils.isEmpty(keystoreFileProp)) {
                properties.put("trustStore", keystoreFileProp);
            }

            if (!CommonUtils.isEmpty(keystorePasswordProp)) {
                properties.put("trustStorePassword", keystorePasswordProp);
            }

            final String keystoreHostnameProp = sslConfig.getStringProperty(SQLServerConstants.PROP_SSL_KEYSTORE_HOSTNAME);
            if (!CommonUtils.isEmpty(keystoreHostnameProp)) {
                properties.put("hostNameInCertificate", keystoreHostnameProp);
            }
        } catch (Exception e) {
            throw new DBCException("Error initializing SSL trust store", e);
        }
    }

    @Override
    protected JDBCExecutionContext createExecutionContext(JDBCRemoteInstance instance, String type) {
        return new SQLServerExecutionContext(instance, type);
    }

    @Override
    protected void initializeContextState(@NotNull DBRProgressMonitor monitor, @NotNull JDBCExecutionContext context, JDBCExecutionContext initFrom) throws DBException {
        super.initializeContextState(monitor, context, initFrom);
        if (initFrom != null) {
            if (!isDataWarehouseServer(monitor)) {
                SQLServerExecutionContext ssContext = (SQLServerExecutionContext) initFrom;
                SQLServerDatabase defaultObject = ssContext.getDefaultCatalog();
                if (defaultObject != null) {
                    ((SQLServerExecutionContext)context).setCurrentDatabase(monitor, defaultObject);
                }
                SQLServerSchema defaultSchema = ssContext.getDefaultSchema();
                if (defaultSchema != null && !isDataWarehouseServer(monitor)) {
                    ((SQLServerExecutionContext)context).setDefaultSchema(monitor, defaultSchema);
                }
            }
        } else {
            ((SQLServerExecutionContext)context).refreshDefaults(monitor, true);
        }
    }

    @Override
    public Object getDataSourceFeature(String featureId) {
        switch (featureId) {
            case DBPDataSource.FEATURE_LIMIT_AFFECTS_DML:
                return true;
            case DBPDataSource.FEATURE_MAX_STRING_LENGTH:
                return 8000;
        }
        return super.getDataSourceFeature(featureId);
    }

    @Override
    public void initialize(@NotNull DBRProgressMonitor monitor) throws DBException {
        super.initialize(monitor);

        this.dataTypeCache.getAllObjects(monitor, this);
        this.databaseCache.getAllObjects(monitor, this);

        try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Load data source meta info")) {
            try {
                JDBCUtils.queryString(session, "SELECT COLUMNPROPERTY(0, NULL, NULL)");
                this.supportsColumnProperty = true;
            } catch (Exception e) {
                this.supportsColumnProperty = false;
            }
        } catch (Throwable e) {
            log.error("Error during connection initialization", e);
        }
    }

    //////////////////////////////////////////////////////////////////
    // Data types

    @NotNull
    @Override
    public DBPDataKind resolveDataKind(@NotNull String typeName, int valueType) {
        return getLocalDataType(valueType).getDataKind();
    }

    @Override
    public List<SQLServerDataType> getLocalDataTypes() {
        return dataTypeCache.getCachedObjects();
    }

    SQLServerDataType getSystemDataType(int systemTypeId) {
        for (SQLServerDataType dt : dataTypeCache.getCachedObjects()) {
            if (dt.getObjectId() == systemTypeId) {
                return dt;
            }
        }
        if (systemTypeId != SQLServerConstants.TABLE_TYPE_SYSTEM_ID) { // 243 - ID of user defined table types
            log.debug("System data type " + systemTypeId + " not found");
        }
        SQLServerDataType sdt = new SQLServerDataType(this, String.valueOf(systemTypeId), systemTypeId, DBPDataKind.OBJECT, java.sql.Types.OTHER);
        dataTypeCache.cacheObject(sdt);
        return sdt;
    }

    @Override
    public SQLServerDataType getLocalDataType(String typeName) {
        return dataTypeCache.getCachedObject(typeName);
    }

    @Override
    public SQLServerDataType getLocalDataType(int typeID) {
        DBSDataType dt = super.getLocalDataType(typeID);
        if (dt == null) {
            log.debug("System data type " + typeID + " not found");
        }
        return (SQLServerDataType) dt;
    }

    @Override
    public String getDefaultDataTypeName(@NotNull DBPDataKind dataKind) {
        switch (dataKind) {
            case BOOLEAN: return "bit";
            case NUMERIC: return "int";
            case STRING: return "varchar";
            case DATETIME: return SQLServerConstants.TYPE_DATETIME;
            case BINARY:
            case CONTENT: return "varbinary";
            case ROWID: return "uniqueidentifier";
            default:
                return super.getDefaultDataTypeName(dataKind);
        }
    }

    //////////////////////////////////////////////////////////
    // Databases

    protected boolean isShowAllSchemas() {
        return CommonUtils.toBoolean(getContainer().getConnectionConfiguration().getProviderProperty(SQLServerConstants.PROP_SHOW_ALL_SCHEMAS));
    }

    //////////////////////////////////////////////////////////////
    // Databases

    @Association
    public Collection<SQLServerDatabase> getDatabases(DBRProgressMonitor monitor) throws DBException {
        return databaseCache.getAllObjects(monitor, this);
    }

    public SQLServerDatabase getDatabase(DBRProgressMonitor monitor, String name) throws DBException {
        return databaseCache.getObject(monitor, this, name);
    }

    public SQLServerDatabase getDatabase(DBRProgressMonitor monitor, long dbId) throws DBException {
        for (SQLServerDatabase db : databaseCache.getAllObjects(monitor, this)) {
            if (db.getDatabaseId() == dbId) {
                return db;
            }
        }
        return null;
    }

    public SQLServerDatabase getDatabase(String name) {
        return databaseCache.getCachedObject(name);
    }

    public SQLServerDatabase getDefaultDatabase(@NotNull DBRProgressMonitor monitor) {
        return ((SQLServerExecutionContext)getDefaultInstance().getDefaultContext(monitor, true)).getDefaultCatalog();
    }

    @Override
    public Collection<? extends DBSObject> getChildren(@NotNull DBRProgressMonitor monitor) throws DBException {
        return databaseCache.getAllObjects(monitor, this);
    }

    @Override
    public DBSObject getChild(@NotNull DBRProgressMonitor monitor, @NotNull String childName) throws DBException {
        return databaseCache.getObject(monitor, this, childName);
    }

    @NotNull
    @Override
    public Class<? extends DBSObject> getPrimaryChildType(@Nullable DBRProgressMonitor monitor) throws DBException {
        return SQLServerDatabase.class;
    }

    @Override
    public void cacheStructure(@NotNull DBRProgressMonitor monitor, int scope) throws DBException {
        databaseCache.getAllObjects(monitor, this);
    }

    @Override
    public DBSObject refreshObject(@NotNull DBRProgressMonitor monitor) throws DBException {
        databaseCache.clearCache();
        serverLoginCache.clearCache();
        hasStatistics = false;
        return super.refreshObject(monitor);
    }

    @Override
    public DBCQueryTransformer createQueryTransformer(DBCQueryTransformType type) {
        if (type == DBCQueryTransformType.RESULT_SET_LIMIT) {
            //if (!SQLServerUtils.isDriverAzure(getContainer().getDriver())) {
                return new QueryTransformerTop();
            //}
        }
        return super.createQueryTransformer(type);
    }

    @Override
    public <T> T getAdapter(Class<T> adapter) {
        if (adapter == DBSStructureAssistant.class) {
            return adapter.cast(new SQLServerStructureAssistant(this));
        } else if (adapter == DBAServerSessionManager.class) {
            return adapter.cast(new SQLServerSessionManager(this));
        } else if (adapter == DBAUserChangePassword.class) {
            return adapter.cast(new SQLServerChangeLoginPassword(this));
        }
        return super.getAdapter(adapter);
    }

    @Override
    public ErrorPosition[] getErrorPosition(DBRProgressMonitor monitor, DBCExecutionContext context, String query, Throwable error) {
        Throwable rootCause = GeneralUtils.getRootCause(error);
        if (rootCause != null && SQLServerConstants.SQL_SERVER_EXCEPTION_CLASS_NAME.equals(rootCause.getClass().getName())) {
            // Read line number from SQLServerError class
            try {
                Object serverError = rootCause.getClass().getMethod("getSQLServerError").invoke(rootCause);
                if (serverError != null) {
                    Object serverErrorLine = BeanUtils.readObjectProperty(serverError, "lineNumber");
                    if (serverErrorLine instanceof Number) {
                        ErrorPosition pos = new ErrorPosition();
                        pos.line = ((Number) serverErrorLine).intValue() - 1;
                        return new ErrorPosition[] {pos};
                    }
                }
            } catch (Throwable e) {
                // ignore
            }
        }

        return super.getErrorPosition(monitor, context, query, error);
    }

    @Override
    public boolean isStatisticsCollected() {
        return hasStatistics;
    }

    @Override
    public void collectObjectStatistics(DBRProgressMonitor monitor, boolean totalSizeOnly, boolean forceRefresh) throws DBException {
        if (hasStatistics && !forceRefresh) {
            return;
        }
        if (SQLServerUtils.isDriverAzure(getContainer().getDriver()) || SQLServerUtils.isDriverBabelfish(getContainer().getDriver()) || isDataWarehouseServer(monitor)) {
            hasStatistics = true;
            return;
        }
        try (JDBCSession session = DBUtils.openMetaSession(monitor, this, "Load schema statistics")) {
            try (JDBCStatement dbStat = session.createStatement()) {
                try (JDBCResultSet dbResult = dbStat.executeQuery("SELECT database_id, SUM(size)\n" +
                    "FROM sys.master_files WITH(NOWAIT)\n" +
                    "GROUP BY database_id")) {
                    while (dbResult.next()) {
                        long dbId = JDBCUtils.safeGetLong(dbResult, 1);
                        long bytes = dbResult.getLong(2) * 8 * 1024;
                        SQLServerDatabase database = getDatabase(monitor, dbId);
                        if (database != null) {
                            database.setDatabaseTotalSize(bytes);
                        }
                    }
                }
            }
        } catch (SQLException e) {
            throw new DBCException("Error reading database statistics", e);
        } finally {
            hasStatistics = true;
        }
    }

    @Override
    public boolean isForceTransform(DBCSession session, SQLQuery sqlQuery) {
        try {
            SQLServerTableBase table = SQLServerUtils.getTableFromQuery(session, sqlQuery, this);
            return table != null && table.isClustered(session.getProgressMonitor());
        } catch (DBException | SQLException e) {
            log.debug("Table not found. ", e);
        }
        return false;
    }

    static class DatabaseCache extends JDBCObjectCache<SQLServerDataSource, SQLServerDatabase> {
        DatabaseCache() {
            setListOrderComparator(DBUtils.nameComparator());
        }

        @NotNull
        @Override
        protected JDBCStatement prepareObjectsStatement(@NotNull JDBCSession session, @NotNull SQLServerDataSource owner) throws SQLException {
            StringBuilder sql = new StringBuilder("SELECT db.* FROM sys.databases db");
            if (owner.isBabelfish) {
                sql.append("\nWHERE db.name = db_name()");
            }
            DBSObjectFilter databaseFilters = owner.getContainer().getObjectFilter(SQLServerDatabase.class, null, false);
            if (databaseFilters != null && databaseFilters.isEnabled()) {
                JDBCUtils.appendFilterClause(sql, databaseFilters, "name", !owner.isBabelfish, owner);
            }
            sql.append("\nORDER BY db.name");
            JDBCPreparedStatement dbStat = session.prepareStatement(sql.toString());
            if (databaseFilters != null) {
                JDBCUtils.setFilterParameters(dbStat, 1, databaseFilters);
            }
            return dbStat;
        }

        @Override
        protected SQLServerDatabase fetchObject(@NotNull JDBCSession session, @NotNull SQLServerDataSource owner, @NotNull JDBCResultSet resultSet) throws SQLException, DBException {
            return new SQLServerDatabase(session, owner, resultSet);
        }
{'fraction_non_alphanumeric': 0.09191089155939772, 'fraction_numerical': 0.0015912221536123119, 'mean_word_length': 2.7831985624438453, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1121808', 'n_tokens_mistral': 10773, 'n_tokens_neox': 10450, 'n_words': 2295}