<commit_before>﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.Extensions.Configuration;
using McMaster.Extensions.CommandLineUtils;
using SJP.Schematic.Core;
using SJP.Schematic.Core.Extensions;
using SJP.Schematic.MySql;
using SJP.Schematic.PostgreSql;
using SJP.Schematic.Sqlite;
using SJP.Schematic.SqlServer;
using System.Data;

namespace SJP.Schematic.Tool
{
    internal abstract class DatabaseCommand
    {
        protected DatabaseCommand(CommandLineApplication application)
        {
            Application = application ?? throw new ArgumentNullException(nameof(application));
        }

        private CommandLineApplication Application { get; }

        [Option(Description = "A connection string", LongName = "connection-string", ShortName = "cs")]
        public string ConnectionString { get; set; }

        [Required]
        [AllowedValues("sqlite", "sqlserver", "postgresql", "mysql", Comparer = StringComparison.OrdinalIgnoreCase)]
        [Option(Description = "The database dialect", LongName = "database-dialect", ShortName = "dd")]
        public string DatabaseDialect { get; set; }

        [Option(Description = "A JSON configuration file containing a connection", LongName = "connection-config-json", ShortName = "csj")]
        [LegalFilePath]
        [FileExists]
        public string ConnectionConfigJson { get; set; }

        [Option(Description = "An XML configuration file containing a connection", LongName = "connection-config-xml", ShortName = "csx")]
        [LegalFilePath]
        [FileExists]
        public string ConnectionConfigXml { get; set; }

        [Option(Description = "The name of the connection string in a configuration file", LongName = "connection-config-name", ShortName = "csname")]
        public string ConnectionConfigName { get; set; }

        public static ConnectionStatus GetConnectionStatus(IDatabaseDialect dialect, string connectionString)
        {
            if (dialect == null)
                throw new ArgumentNullException(nameof(dialect));
            if (connectionString.IsNullOrWhiteSpace())
                throw new ArgumentNullException(nameof(connectionString));

            try
            {
                var connection = dialect.CreateConnection(connectionString);
                return ConnectionStatus.Success(connection);
            }
            catch (Exception ex)
            {
                return ConnectionStatus.Failure(ex);
            }
        }

        public bool TryGetConnectionString(out string connectionString)
        {
            if (!ConnectionString.IsNullOrWhiteSpace())
            {
                connectionString = ConnectionString;
                return true;
            }

            if (!ConnectionConfigName.IsNullOrWhiteSpace())
            {
                IConfigurationRoot configuration = null;
                string configurationFile = null;

                if (!ConnectionConfigJson.IsNullOrWhiteSpace())
                {
                    var builder = new ConfigurationBuilder().AddJsonFile(ConnectionConfigJson);
                    configuration = builder.Build();
                    configurationFile = ConnectionConfigJson;
                }
                else if (!ConnectionConfigXml.IsNullOrWhiteSpace())
                {
                    var builder = new ConfigurationBuilder().AddXmlFile(ConnectionConfigXml);
                    configuration = builder.Build();
                    configurationFile = ConnectionConfigXml;
                }
                else
                {
                    Application.Error.WriteLine("A connection configuration name was given but no corresponding configuration file was provided.");
                    connectionString = null;
                    return false;
                }

                var connectionStringSection = configuration.GetSection("ConnectionStrings");
                var connections = connectionStringSection.AsEnumerable();
                var matchingConnection = connections.FirstOrDefault(c => c.Key == ConnectionConfigName);
                if (matchingConnection.Key != ConnectionConfigName)
                {
                    Application.Error.WriteLine($"Could not find a connection named '{ ConnectionConfigName }' in '{ configurationFile }'.");
                    connectionString = null;
                    return false;
                }

                connectionString = matchingConnection.Value;
                return true;
            }

            Application.Error.WriteLine("No valid connection string or connection configuration provided.");

            connectionString = null;
            return false;
        }

        public IDatabaseDialect GetDatabaseDialect()
        {
            if (!_dialects.TryGetValue(DatabaseDialect, out var dialect))
                throw new NotSupportedException("Unsupported dialect: " + DatabaseDialect);

            return dialect;
        }

        public Func<IDatabaseDialect, IDbConnection, IRelationalDatabase> GetRelationalDatabaseFactory()
        {
            if (!_databaseFactories.TryGetValue(DatabaseDialect, out var factory))
                throw new NotSupportedException("Unsupported dialect: " + DatabaseDialect);

            return factory;
        }

        private readonly static IReadOnlyDictionary<string, IDatabaseDialect> _dialects = new Dictionary<string, IDatabaseDialect>
        {
            ["sqlite"] = new SqliteDialect(),
            ["sqlserver"] = new SqlServerDialect(),
            ["mysql"] = new MySqlDialect(),
            ["postgresql"] = new PostgreSqlDialect()
        };

        private readonly static IReadOnlyDictionary<string, Func<IDatabaseDialect, IDbConnection, IRelationalDatabase>> _databaseFactories =
            new Dictionary<string, Func<IDatabaseDialect, IDbConnection, IRelationalDatabase>>
            {
                ["sqlite"] = (d, c) => new SqliteRelationalDatabase(d, c),
                ["sqlserver"] = (d, c) => new SqlServerRelationalDatabase(d, c),
                ["mysql"] = (d, c) => new MySqlRelationalDatabase(d, c),
                ["postgresql"] = (d, c) => new PostgreSqlRelationalDatabase(d, c)
            };

        public sealed class ConnectionStatus
        {
            private ConnectionStatus(bool isConnected, IDbConnection connection, Exception exception)
            {
                IsConnected = isConnected;
                Connection = connection;
                Error = exception;<commit_msg>Add missing Tool change for requiring identifier resolvers.<commit_after>﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.Extensions.Configuration;
using McMaster.Extensions.CommandLineUtils;
using SJP.Schematic.Core;
using SJP.Schematic.Core.Extensions;
using SJP.Schematic.MySql;
using SJP.Schematic.PostgreSql;
using SJP.Schematic.Sqlite;
using SJP.Schematic.SqlServer;
using System.Data;

namespace SJP.Schematic.Tool
{
    internal abstract class DatabaseCommand
    {
        protected DatabaseCommand(CommandLineApplication application)
        {
            Application = application ?? throw new ArgumentNullException(nameof(application));
        }

        private CommandLineApplication Application { get; }

        [Option(Description = "A connection string", LongName = "connection-string", ShortName = "cs")]
        public string ConnectionString { get; set; }

        [Required]
        [AllowedValues("sqlite", "sqlserver", "postgresql", "mysql", Comparer = StringComparison.OrdinalIgnoreCase)]
        [Option(Description = "The database dialect", LongName = "database-dialect", ShortName = "dd")]
        public string DatabaseDialect { get; set; }

        [Option(Description = "A JSON configuration file containing a connection", LongName = "connection-config-json", ShortName = "csj")]
        [LegalFilePath]
        [FileExists]
        public string ConnectionConfigJson { get; set; }

        [Option(Description = "An XML configuration file containing a connection", LongName = "connection-config-xml", ShortName = "csx")]
        [LegalFilePath]
        [FileExists]
        public string ConnectionConfigXml { get; set; }

        [Option(Description = "The name of the connection string in a configuration file", LongName = "connection-config-name", ShortName = "csname")]
        public string ConnectionConfigName { get; set; }

        public static ConnectionStatus GetConnectionStatus(IDatabaseDialect dialect, string connectionString)
        {
            if (dialect == null)
                throw new ArgumentNullException(nameof(dialect));
            if (connectionString.IsNullOrWhiteSpace())
                throw new ArgumentNullException(nameof(connectionString));

            try
            {
                var connection = dialect.CreateConnection(connectionString);
                return ConnectionStatus.Success(connection);
            }
            catch (Exception ex)
            {
                return ConnectionStatus.Failure(ex);
            }
        }

        public bool TryGetConnectionString(out string connectionString)
        {
            if (!ConnectionString.IsNullOrWhiteSpace())
            {
                connectionString = ConnectionString;
                return true;
            }

            if (!ConnectionConfigName.IsNullOrWhiteSpace())
            {
                IConfigurationRoot configuration = null;
                string configurationFile = null;

                if (!ConnectionConfigJson.IsNullOrWhiteSpace())
                {
                    var builder = new ConfigurationBuilder().AddJsonFile(ConnectionConfigJson);
                    configuration = builder.Build();
                    configurationFile = ConnectionConfigJson;
                }
                else if (!ConnectionConfigXml.IsNullOrWhiteSpace())
                {
                    var builder = new ConfigurationBuilder().AddXmlFile(ConnectionConfigXml);
                    configuration = builder.Build();
                    configurationFile = ConnectionConfigXml;
                }
                else
                {
                    Application.Error.WriteLine("A connection configuration name was given but no corresponding configuration file was provided.");
                    connectionString = null;
                    return false;
                }

                var connectionStringSection = configuration.GetSection("ConnectionStrings");
                var connections = connectionStringSection.AsEnumerable();
                var matchingConnection = connections.FirstOrDefault(c => c.Key == ConnectionConfigName);
                if (matchingConnection.Key != ConnectionConfigName)
                {
                    Application.Error.WriteLine($"Could not find a connection named '{ ConnectionConfigName }' in '{ configurationFile }'.");
                    connectionString = null;
                    return false;
                }

                connectionString = matchingConnection.Value;
                return true;
            }

            Application.Error.WriteLine("No valid connection string or connection configuration provided.");

            connectionString = null;
            return false;
        }

        public IDatabaseDialect GetDatabaseDialect()
        {
            if (!_dialects.TryGetValue(DatabaseDialect, out var dialect))
                throw new NotSupportedException("Unsupported dialect: " + DatabaseDialect);

            return dialect;
        }

        public Func<IDatabaseDialect, IDbConnection, IRelationalDatabase> GetRelationalDatabaseFactory()
        {
            if (!_databaseFactories.TryGetValue(DatabaseDialect, out var factory))
                throw new NotSupportedException("Unsupported dialect: " + DatabaseDialect);

            return factory;
        }

        private readonly static IReadOnlyDictionary<string, IDatabaseDialect> _dialects = new Dictionary<string, IDatabaseDialect>
        {
            ["sqlite"] = new SqliteDialect(),
            ["sqlserver"] = new SqlServerDialect(),
            ["mysql"] = new MySqlDialect(),
            ["postgresql"] = new PostgreSqlDialect()
        };

        private readonly static IReadOnlyDictionary<string, Func<IDatabaseDialect, IDbConnection, IRelationalDatabase>> _databaseFactories =
            new Dictionary<string, Func<IDatabaseDialect, IDbConnection, IRelationalDatabase>>
            {
                ["sqlite"] = (d, c) => new SqliteRelationalDatabase(d, c),
                ["sqlserver"] = (d, c) => new SqlServerRelationalDatabase(d, c),
                ["mysql"] = (d, c) => new MySqlRelationalDatabase(d, c),
                ["postgresql"] = (d, c) => new PostgreSqlRelationalDatabase(d, c, new DefaultPostgreSqlIdentifierResolutionStrategy())
            };

        public sealed class ConnectionStatus
        {
            private ConnectionStatus(bool isConnected, IDbConnection connection, Exception exception)
            {
                IsConnected = isConnected;
                Connection = connection;
                Error = exception;
{'fraction_non_alphanumeric': 0.08293821028072552, 'fraction_numerical': 0.0, 'mean_word_length': 2.2307318259178217, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, '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': '3865173', 'n_tokens_mistral': 3108, 'n_tokens_neox': 2975, 'n_words': 752}