diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 863202f..c58c2ac 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,3 +37,20 @@ jobs: uses: github/codeql-action/analyze@v3 with: category: "/language:csharp" + upload: False + output: sarif-results + + - name: filter-sarif + uses: advanced-security/filter-sarif@v1 + with: + patterns: | + +**/* + -**/*.g.cs + -**/Tests/** + input: sarif-results/csharp.sarif + output: sarif-results/csharp.sarif + + - name: Upload SARIF + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: sarif-results/csharp.sarif diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 102adbf..d8126c1 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -1,4 +1,4 @@ -name: .NET Core +name: Build and test on: push diff --git a/CHANGELOG.md b/CHANGELOG.md index 0537b77..ba92555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.2.0] - 2023-03-04 + +- Target .Net 8.0 +- Enable AOT compatibility, though upstream dependencies still have issues +- Nullable annotations enabled (some warnings remain) +- Bump HIC.TypeGuesser from 1.1.0 to 1.2.3 +- Bump Microsoft.Data.SqlClient from 5.1.1 to 5.2.0 +- Bump MySqlConnector from 2.2.6 to 2.3.5 +- Bump Npgsql from 7.0.4 to 8.0.2 +- Bump Oracle.ManagedDataAccess.Core from 3.21.100 to 3.21.130 + ## [3.1.1] - 2023-09-01 - Bugfix: MySQL text was erroneously capped at 64k (TEXT) instead of LONGTEXT (4GiB) @@ -354,9 +365,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed Drop table to work correctly with Views - Exists now works correctly for Views (previously it would return true if there was no view but a table with the same name) -[Unreleased]: https://github.com/HicServices/FAnsiSql/compare/3.1.1...develop -[3.1.1]: https://github.com/HicServices/FAnsiSql/compare/3.1.0...3.1.1 -[3.1.0]: https://github.com/HicServices/FAnsiSql/compare/3.0.1...3.1.0 +[Unreleased]: https://github.com/HicServices/FAnsiSql/compare/v3.2.0...develop +[3.2.0]: https://github.com/HicServices/FAnsiSql/compare/v3.1.1...v3.2.0 +[3.1.1]: https://github.com/HicServices/FAnsiSql/compare/v3.1.0...v3.1.1 +[3.1.0]: https://github.com/HicServices/FAnsiSql/compare/3.0.1...v3.1.0 [3.0.1]: https://github.com/HicServices/FAnsiSql/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/HicServices/FAnsiSql/compare/2.0.5...3.0.0 [2.0.5]: https://github.com/HicServices/FAnsiSql/compare/2.0.4...2.0.5 diff --git a/FAnsiSql/Connections/IManagedConnection.cs b/FAnsiSql/Connections/IManagedConnection.cs index ef3ae5b..f53ca9c 100644 --- a/FAnsiSql/Connections/IManagedConnection.cs +++ b/FAnsiSql/Connections/IManagedConnection.cs @@ -22,7 +22,7 @@ public interface IManagedConnection : IDisposable /// /// Optional - transaction being run (See . If this is not null then should also be not null. /// - IManagedTransaction ManagedTransaction { get; } + IManagedTransaction? ManagedTransaction { get; } /// /// True to close the connection in the Dispose step. If opened the connection itself during construction then this flag will default diff --git a/FAnsiSql/Connections/ManagedConnection.cs b/FAnsiSql/Connections/ManagedConnection.cs index 65f8a24..17e0e62 100644 --- a/FAnsiSql/Connections/ManagedConnection.cs +++ b/FAnsiSql/Connections/ManagedConnection.cs @@ -15,12 +15,12 @@ public sealed class ManagedConnection : IManagedConnection public DbTransaction? Transaction { get; } /// - public IManagedTransaction ManagedTransaction { get; } + public IManagedTransaction? ManagedTransaction { get; } /// public bool CloseOnDispose { get; set; } - internal ManagedConnection(DiscoveredServer discoveredServer, IManagedTransaction managedTransaction) + internal ManagedConnection(DiscoveredServer discoveredServer, IManagedTransaction? managedTransaction) { //get a new connection or use the existing one within the transaction Connection = discoveredServer.GetConnection(managedTransaction); @@ -37,7 +37,7 @@ internal ManagedConnection(DiscoveredServer discoveredServer, IManagedTransactio Connection.Open(); } - public ManagedConnection Clone() => (ManagedConnection) MemberwiseClone(); + public ManagedConnection Clone() => (ManagedConnection)MemberwiseClone(); /// /// Closes and disposes the DbConnection unless this class is part of an diff --git a/FAnsiSql/Discovery/BulkCopy.cs b/FAnsiSql/Discovery/BulkCopy.cs index a26c227..a4f84c3 100644 --- a/FAnsiSql/Discovery/BulkCopy.cs +++ b/FAnsiSql/Discovery/BulkCopy.cs @@ -3,6 +3,7 @@ using System.Data; using System.Globalization; using System.Linq; +using System.Threading; using FAnsi.Connections; using TypeGuesser; using TypeGuesser.Deciders; @@ -10,7 +11,7 @@ namespace FAnsi.Discovery; /// -public abstract class BulkCopy:IBulkCopy +public abstract class BulkCopy : IBulkCopy { public CultureInfo Culture { get; } @@ -28,7 +29,9 @@ public abstract class BulkCopy:IBulkCopy /// The cached columns found on the . If you alter the table midway through a bulk insert you must /// call to refresh this. /// - protected DiscoveredColumn[] TargetTableColumns; + protected DiscoveredColumn[] TargetTableColumns => _targetTableColumns.Value; + + private Lazy _targetTableColumns; /// /// When calling GetMapping if there are DataColumns in the input table that you are trying to bulk insert that are not matched @@ -55,7 +58,9 @@ protected BulkCopy(DiscoveredTable targetTable, IManagedConnection connection, C Culture = culture; TargetTable = targetTable; Connection = connection; - InvalidateTableSchema(); + _targetTableColumns = new Lazy( + () => TargetTable.DiscoverColumns(Connection.ManagedTransaction), + LazyThreadSafetyMode.ExecutionAndPublication); AllowUnmatchedInputColumns = false; DateTimeDecider = new DateTimeTypeDecider(culture); } @@ -68,7 +73,9 @@ protected BulkCopy(DiscoveredTable targetTable, IManagedConnection connection, C /// public void InvalidateTableSchema() { - TargetTableColumns = TargetTable.DiscoverColumns(Connection.ManagedTransaction); + _targetTableColumns = new Lazy( + () => TargetTable.DiscoverColumns(Connection.ManagedTransaction), + LazyThreadSafetyMode.ExecutionAndPublication); } /// @@ -100,7 +107,7 @@ public virtual int Upload(DataTable dt) /// protected void ConvertStringTypesToHardTypes(DataTable dt) { - var dict = GetMapping(dt.Columns.Cast(),out _); + var dict = GetMapping(dt.Columns.Cast(), out _); var factory = new TypeDeciderFactory(Culture); @@ -108,17 +115,17 @@ protected void ConvertStringTypesToHardTypes(DataTable dt) var deciders = factory.Dictionary; //for each column in the destination - foreach(var (dataColumn, discoveredColumn) in dict) + foreach (var (dataColumn, discoveredColumn) in dict) { //if the destination column is a problematic type - var dataType = discoveredColumn.DataType.GetCSharpDataType(); + var dataType = discoveredColumn.DataType?.GetCSharpDataType(); if (!deciders.TryGetValue(dataType, out var decider)) continue; //if it's already not a string then that's fine (hopefully it's a legit Type e.g. DateTime!) - if(dataColumn.DataType != typeof(string)) + if (dataColumn.DataType != typeof(string)) continue; //create a new column hard typed to DateTime - var newColumn = dt.Columns.Add($"{dataColumn.ColumnName}_{Guid.NewGuid()}",dataType); + var newColumn = dt.Columns.Add($"{dataColumn.ColumnName}_{Guid.NewGuid()}", dataType); //if it's a DateTime decider then guess DateTime culture based on values in the table if (decider is DateTimeTypeDecider) @@ -129,30 +136,30 @@ protected void ConvertStringTypesToHardTypes(DataTable dt) } - foreach(DataRow dr in dt.Rows) + foreach (DataRow dr in dt.Rows) try { //parse the value - dr[newColumn] = decider.Parse(dr[dataColumn] as string)??DBNull.Value; + dr[newColumn] = dr[dataColumn] is string v ? decider.Parse(v) ?? DBNull.Value : DBNull.Value; } catch (Exception ex) { - throw new Exception($"Failed to parse value '{dr[dataColumn]}' in column '{dataColumn}'",ex); + throw new Exception($"Failed to parse value '{dr[dataColumn]}' in column '{dataColumn}'", ex); } //if the DataColumn is part of the Primary Key of the DataTable (in memory) //then we need to update the primary key to include the new column not the old one - if(dt.PrimaryKey != null && dt.PrimaryKey.Contains(dataColumn)) - dt.PrimaryKey = dt.PrimaryKey.Except(new [] { dataColumn }).Union(new []{newColumn }).ToArray(); + if (dt.PrimaryKey != null && dt.PrimaryKey.Contains(dataColumn)) + dt.PrimaryKey = dt.PrimaryKey.Except(new[] { dataColumn }).Union(new[] { newColumn }).ToArray(); - var oldOrdinal = dataColumn.Ordinal; + var oldOrdinal = dataColumn.Ordinal; //drop the original column dt.Columns.Remove(dataColumn); //rename the hard typed column to match the old column name newColumn.ColumnName = dataColumn.ColumnName; - if(oldOrdinal != -1) + if (oldOrdinal != -1) newColumn.SetOrdinal(oldOrdinal); } } @@ -198,5 +205,5 @@ protected Dictionary GetMapping(IEnumerable /// /// - protected Dictionary GetMapping(IEnumerable inputColumns) => GetMapping(inputColumns, out _); + protected Dictionary GetMapping(IEnumerable inputColumns) => GetMapping(inputColumns, out _); } \ No newline at end of file diff --git a/FAnsiSql/Discovery/Constraints/DiscoveredRelationship.cs b/FAnsiSql/Discovery/Constraints/DiscoveredRelationship.cs index 3c74a05..de2d3b4 100644 --- a/FAnsiSql/Discovery/Constraints/DiscoveredRelationship.cs +++ b/FAnsiSql/Discovery/Constraints/DiscoveredRelationship.cs @@ -43,8 +43,8 @@ public sealed class DiscoveredRelationship(string fkName, DiscoveredTable pkTabl /// public CascadeRule CascadeDelete { get; private set; } = deleteRule; - private DiscoveredColumn[] _pkColumns; - private DiscoveredColumn[] _fkColumns; + private DiscoveredColumn[]? _pkColumns; + private DiscoveredColumn[]? _fkColumns; /// /// Discovers and adds the provided pair to . Column names must be members of and (respectively) @@ -52,16 +52,13 @@ public sealed class DiscoveredRelationship(string fkName, DiscoveredTable pkTabl /// /// /// - public void AddKeys(string primaryKeyCol, string foreignKeyCol,IManagedTransaction? transaction = null) + public void AddKeys(string primaryKeyCol, string foreignKeyCol, IManagedTransaction? transaction = null) { - if (_pkColumns == null) - { - _pkColumns = PrimaryKeyTable.DiscoverColumns(transaction); - _fkColumns = ForeignKeyTable.DiscoverColumns(transaction); - } + _pkColumns ??= PrimaryKeyTable.DiscoverColumns(transaction); + _fkColumns ??= ForeignKeyTable.DiscoverColumns(transaction); Keys.Add( - _pkColumns.Single(c=>c.GetRuntimeName().Equals(primaryKeyCol,StringComparison.CurrentCultureIgnoreCase)), + _pkColumns.Single(c => c.GetRuntimeName().Equals(primaryKeyCol, StringComparison.CurrentCultureIgnoreCase)), _fkColumns.Single(c => c.GetRuntimeName().Equals(foreignKeyCol, StringComparison.CurrentCultureIgnoreCase)) ); } diff --git a/FAnsiSql/Discovery/DiscoveredColumn.cs b/FAnsiSql/Discovery/DiscoveredColumn.cs index 7b4f8af..a5b85e0 100644 --- a/FAnsiSql/Discovery/DiscoveredColumn.cs +++ b/FAnsiSql/Discovery/DiscoveredColumn.cs @@ -1,4 +1,5 @@ -using FAnsi.Discovery.QuerySyntax; +using System.Diagnostics.CodeAnalysis; +using FAnsi.Discovery.QuerySyntax; using FAnsi.Naming; using TypeGuesser; @@ -29,7 +30,7 @@ public sealed class DiscoveredColumn(DiscoveredTable table, string name, bool al /// /// True if the column allows rows with nulls in this column /// - public bool AllowNulls { get; } = allowsNulls; + public readonly bool AllowNulls = allowsNulls; /// /// True if the column is part of the primary key (a primary key can consist of mulitple columns) @@ -50,12 +51,12 @@ public sealed class DiscoveredColumn(DiscoveredTable table, string name, bool al /// /// The data type of the column found (includes String Length and Scale/Precision). /// - public DiscoveredDataType DataType { get; set; } + public DiscoveredDataType? DataType { get; set; } /// /// The character set of the column (if char) /// - public string Format { get; set; } + public string? Format { get; set; } private readonly string _name = name; private readonly IQuerySyntaxHelper _querySyntaxHelper = table.Database.Server.GetQuerySyntaxHelper(); @@ -64,13 +65,14 @@ public sealed class DiscoveredColumn(DiscoveredTable table, string name, bool al /// The unqualified name of the column e.g. "MyCol" /// /// - public string? GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_name); + public string GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_name); /// /// The fully qualified name of the column e.g. [MyDb].dbo.[MyTable].[MyCol] or `MyDb`.`MyCol` /// /// - public string GetFullyQualifiedName() => _querySyntaxHelper.EnsureFullyQualified(Table.Database.GetRuntimeName(),Table.Schema, Table.GetRuntimeName(), GetRuntimeName(), Table is DiscoveredTableValuedFunction); + public string GetFullyQualifiedName() => _querySyntaxHelper.EnsureFullyQualified(Table.Database.GetRuntimeName(), + Table.Schema, Table.GetRuntimeName(), GetRuntimeName(), Table is DiscoveredTableValuedFunction); /// @@ -131,5 +133,5 @@ public override int GetHashCode() /// Returns the wrapped e.g. "[MyCol]" name of the column including escaping e.g. if you wanted to name a column "][nquisitor" (which would return "[]][nquisitor]"). Use to return the full name including table/database/schema. /// /// - public string GetWrappedName() => Table.GetQuerySyntaxHelper().EnsureWrapped(GetRuntimeName()); + public string? GetWrappedName() => Table.GetQuerySyntaxHelper().EnsureWrapped(GetRuntimeName()); } \ No newline at end of file diff --git a/FAnsiSql/Discovery/DiscoveredDatabase.cs b/FAnsiSql/Discovery/DiscoveredDatabase.cs index eb0601f..8123ec2 100644 --- a/FAnsiSql/Discovery/DiscoveredDatabase.cs +++ b/FAnsiSql/Discovery/DiscoveredDatabase.cs @@ -78,7 +78,7 @@ public IEnumerable DiscoverTableValuedFunctions(I /// Returns the name of the database without any qualifiers /// /// - public string? GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_database); + public string GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_database); /// /// Returns the wrapped e.g. "[MyDatabase]" name of the database including escaping e.g. if you wanted to name a database "][nquisitor" (which would return "[]][nquisitor]"). diff --git a/FAnsiSql/Discovery/DiscoveredDatabaseHelper.cs b/FAnsiSql/Discovery/DiscoveredDatabaseHelper.cs index 143b9f9..a776726 100644 --- a/FAnsiSql/Discovery/DiscoveredDatabaseHelper.cs +++ b/FAnsiSql/Discovery/DiscoveredDatabaseHelper.cs @@ -283,7 +283,7 @@ public void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction? t /// /// Line number the batch started at and the time it took to complete it /// Timeout in seconds to run each batch in the - public void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction transaction, out Dictionary performanceFigures, int timeout = 30) + public void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction? transaction, out Dictionary performanceFigures, int timeout = 30) { performanceFigures = []; diff --git a/FAnsiSql/Discovery/DiscoveredServer.cs b/FAnsiSql/Discovery/DiscoveredServer.cs index 3f4d37f..7334782 100644 --- a/FAnsiSql/Discovery/DiscoveredServer.cs +++ b/FAnsiSql/Discovery/DiscoveredServer.cs @@ -21,9 +21,9 @@ public sealed class DiscoveredServer : IMightNotExist public DbConnectionStringBuilder Builder { get; set; } /// - /// The currently used database + /// The currently used database, if any /// - private DiscoveredDatabase _currentDatabase; + private DiscoveredDatabase? _currentDatabase; /// /// Stateless helper class with DBMS specific implementation of the logic required by . @@ -38,7 +38,7 @@ public sealed class DiscoveredServer : IMightNotExist /// /// The server's name as specified in e.g. localhost\sqlexpress /// - public string Name => Helper.GetServerName(Builder); + public string? Name => Helper.GetServerName(Builder); /// /// Returns the username portion of if specified @@ -90,13 +90,13 @@ public DiscoveredServer(string? connectionString, DatabaseType databaseType) /// Optional username to set in the connection string /// Optional password to set in the connection string /// - public DiscoveredServer(string server,string database, DatabaseType databaseType,string usernameIfAny,string passwordIfAny) + public DiscoveredServer(string server, string database, DatabaseType databaseType, string usernameIfAny, string passwordIfAny) { Helper = ImplementationManager.GetImplementation(databaseType).GetServerHelper(); - Builder = Helper.GetConnectionStringBuilder(server,database,usernameIfAny,passwordIfAny); + Builder = Helper.GetConnectionStringBuilder(server, database, usernameIfAny, passwordIfAny); - if(!string.IsNullOrWhiteSpace(database)) + if (!string.IsNullOrWhiteSpace(database)) _currentDatabase = ExpectDatabase(database); } @@ -185,7 +185,7 @@ public DiscoveredDatabase ExpectDatabase(string database) public void TestConnection(int timeoutInMillis = 10000) { using var con = Helper.GetConnection(Builder); - using(var tokenSource = new CancellationTokenSource(timeoutInMillis)) + using (var tokenSource = new CancellationTokenSource(timeoutInMillis)) using (var openTask = con.OpenAsync(tokenSource.Token)) { try @@ -226,7 +226,7 @@ public void TestConnection(int timeoutInMillis = 10000) /// /// /// - public bool RespondsWithinTime(int timeoutInSeconds, out Exception exception) => Helper.RespondsWithinTime(Builder, timeoutInSeconds, out exception); + public bool RespondsWithinTime(int timeoutInSeconds, out Exception? exception) => Helper.RespondsWithinTime(Builder, timeoutInSeconds, out exception); /// /// Connects to the server and returns a list of databases found as objects @@ -279,13 +279,13 @@ public bool Exists(IManagedTransaction? transaction = null) /// Returns the database that is currently pointed at. /// /// - public DiscoveredDatabase GetCurrentDatabase() + public DiscoveredDatabase? GetCurrentDatabase() { //Is the database name persisted in the connection string? var dbName = Helper.GetCurrentDatabase(Builder); //yes - if(!string.IsNullOrWhiteSpace(dbName)) + if (!string.IsNullOrWhiteSpace(dbName)) return ExpectDatabase(dbName); //no (e.g. Oracle or no default database specified in connection string) @@ -311,7 +311,7 @@ public void EnableAsync() public void ChangeDatabase(string newDatabase) { //change the connection string to point to the newDatabase - Builder = Helper.ChangeDatabase(Builder,newDatabase); + Builder = Helper.ChangeDatabase(Builder, newDatabase); //for DBMS that do not persist database in connection string (Oracle), we must persist this change _currentDatabase = ExpectDatabase(newDatabase); @@ -321,7 +321,7 @@ public void ChangeDatabase(string newDatabase) /// Returns the server /// /// - public override string ToString() => Name; + public override string? ToString() => Name; /// /// Creates a new database with the given . @@ -337,8 +337,8 @@ public DiscoveredDatabase CreateDatabase(string newDatabaseName) Helper.CreateDatabase(Builder, db); - if(!db.Exists()) - throw new Exception(string.Format(FAnsiStrings.DiscoveredServer_CreateDatabase_Helper___0___tried_to_create_database___1___but_the_database_didn_t_exist_after_the_creation_attempt, Helper.GetType().Name,newDatabaseName)); + if (!db.Exists()) + throw new Exception(string.Format(FAnsiStrings.DiscoveredServer_CreateDatabase_Helper___0___tried_to_create_database___1___but_the_database_didn_t_exist_after_the_creation_attempt, Helper.GetType().Name, newDatabaseName)); return db; } @@ -348,7 +348,7 @@ public DiscoveredDatabase CreateDatabase(string newDatabaseName) /// should be wrapped with a using statement since it is . /// /// - public IManagedConnection BeginNewTransactedConnection() => new ManagedConnection(this, Helper.BeginTransaction(Builder)){CloseOnDispose = true}; + public IManagedConnection BeginNewTransactedConnection() => new ManagedConnection(this, Helper.BeginTransaction(Builder)) { CloseOnDispose = true }; /// /// Opens a new or reuses an existing one (if is provided). @@ -410,5 +410,5 @@ public override bool Equals(object? obj) /// Returns the version number of the DBMS e.g. MySql 5.7 /// /// - public Version GetVersion() => Helper.GetVersion(this); + public Version? GetVersion() => Helper.GetVersion(this); } \ No newline at end of file diff --git a/FAnsiSql/Discovery/DiscoveredTable.cs b/FAnsiSql/Discovery/DiscoveredTable.cs index d1e1e8e..cc24e1e 100644 --- a/FAnsiSql/Discovery/DiscoveredTable.cs +++ b/FAnsiSql/Discovery/DiscoveredTable.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Threading; @@ -88,7 +89,7 @@ public virtual bool Exists(IManagedTransaction? transaction = null) /// Returns the unqualified name of the table e.g. "MyTable" /// /// - public virtual string? GetRuntimeName() => QuerySyntaxHelper.GetRuntimeName(TableName); + public virtual string GetRuntimeName() => QuerySyntaxHelper.GetRuntimeName(TableName); /// /// Returns the fully qualified (including schema if appropriate) name of the table e.g. [MyDb].dbo.[MyTable] or `MyDb`.`MyTable` @@ -100,7 +101,7 @@ public virtual bool Exists(IManagedTransaction? transaction = null) /// Returns the wrapped e.g. "[MyTbl]" name of the table including escaping e.g. if you wanted to name a table "][nquisitor" (which would return "[]][nquisitor]"). Use to return the full name including table/database/schema. /// /// - public string? GetWrappedName() => QuerySyntaxHelper.EnsureWrapped(GetRuntimeName()); + public string GetWrappedName() => QuerySyntaxHelper.EnsureWrapped(GetRuntimeName()); /// /// Connects to the server and returns a list of columns found in the table as . diff --git a/FAnsiSql/Discovery/QuerySyntax/IQuerySyntaxHelper.cs b/FAnsiSql/Discovery/QuerySyntax/IQuerySyntaxHelper.cs index 8330447..55b04c4 100644 --- a/FAnsiSql/Discovery/QuerySyntax/IQuerySyntaxHelper.cs +++ b/FAnsiSql/Discovery/QuerySyntax/IQuerySyntaxHelper.cs @@ -41,16 +41,16 @@ public interface IQuerySyntaxHelper /// /// The character that is used to qualify database entity names e.g. "[" for "[My Table]" /// - string OpenQualifier {get;} + string OpenQualifier { get; } /// /// The character that is used to end qualifying database entity names e.g. "]" for "[My Table]". For some DBMS this is the same as /// - string CloseQualifier {get;} + string CloseQualifier { get; } /// /// Separator between table and column names (and database, schema etc). Usually "." /// - string DatabaseTableSeparator {get; } + string DatabaseTableSeparator { get; } /// /// Characters which are not permitted in column names by FAnsi @@ -59,6 +59,7 @@ public interface IQuerySyntaxHelper char ParameterSymbol { get; } + [return: NotNullIfNotNull(nameof(s))] string? GetRuntimeName(string? s); bool TryGetRuntimeName(string s, out string? name); @@ -78,6 +79,7 @@ public interface IQuerySyntaxHelper /// /// /// + [return: NotNullIfNotNull(nameof(databaseOrTableName))] string? EnsureWrapped(string? databaseOrTableName); string EnsureFullyQualified(string? databaseName, string? schemaName, string tableName); @@ -163,7 +165,7 @@ public interface IQuerySyntaxHelper /// The column the parameter is for loading - this is used to determine the DbType for the paramter /// The value to populate into the command, this will be converted to DBNull.Value if the value is nullish /// - DbParameter GetParameter(DbParameter p, DiscoveredColumn discoveredColumn,object value); + DbParameter GetParameter(DbParameter p, DiscoveredColumn discoveredColumn, object value); /// /// Gets a DbParameter hard typed with the correct DbType for the discoveredColumn and the Value set to the correct Value representation (e.g. DBNull for nulls or whitespace). @@ -174,7 +176,7 @@ public interface IQuerySyntaxHelper /// The value to populate into the command, this will be converted to DBNull.Value if the value is nullish /// /// - DbParameter GetParameter(DbParameter p, DiscoveredColumn discoveredColumn,object value,CultureInfo culture); + DbParameter GetParameter(DbParameter p, DiscoveredColumn discoveredColumn, object value, CultureInfo? culture); /// /// Throws if the supplied name is invalid (because it is too long or contains unsupported characters) @@ -200,7 +202,7 @@ public interface IQuerySyntaxHelper /// /// Returns false if the supplied name is invalid (because it is too long or contains unsupported characters) /// - bool IsValidTableName(string tableName, [NotNullWhen(false)] out string reason); + bool IsValidTableName(string tableName, [NotNullWhen(false)] out string? reason); /// /// Returns false if the supplied name is invalid (because it is too long or contains unsupported characters) diff --git a/FAnsiSql/Discovery/QuerySyntaxHelper.cs b/FAnsiSql/Discovery/QuerySyntaxHelper.cs index db83112..5816f3c 100644 --- a/FAnsiSql/Discovery/QuerySyntaxHelper.cs +++ b/FAnsiSql/Discovery/QuerySyntaxHelper.cs @@ -37,7 +37,7 @@ public abstract partial class QuerySyntaxHelper( public abstract int MaximumColumnLength { get; } /// - public virtual char[] IllegalNameChars { get; } = ['.','(',')']; + public virtual char[] IllegalNameChars { get; } = ['.', '(', ')']; /// /// Regex for identifying parameters in blocks of SQL (starts with @ or : (Oracle) @@ -48,17 +48,17 @@ public abstract partial class QuerySyntaxHelper( /// /// Symbols (for all database types) which denote wrapped entity names e.g. [dbo].[mytable] contains qualifiers '[' and ']' /// - public static readonly char[] TableNameQualifiers = ['[', ']', '`' ,'"']; + public static readonly char[] TableNameQualifiers = ['[', ']', '`', '"']; /// - public abstract string OpenQualifier {get;} + public abstract string OpenQualifier { get; } /// - public abstract string CloseQualifier {get;} + public abstract string CloseQualifier { get; } public ITypeTranslater TypeTranslater { get; private set; } = translater; - private readonly Dictionary factories = []; + private readonly Dictionary factories = []; public IAggregateHelper AggregateHelper { get; private set; } = aggregateHelper; public IUpdateHelper UpdateHelper { get; set; } = updateHelper; @@ -85,7 +85,7 @@ private Regex GetAliasRegex() => public string AliasPrefix => GetAliasConst(); //Only look at the start of the string or following an equals or white space and stop at word boundaries - private static readonly Regex ParameterNameRegex = new ($@"(?:^|[\s+\-*/\\=(,])+{ParameterNamesRegex}\b"); + private static readonly Regex ParameterNameRegex = new($@"(?:^|[\s+\-*/\\=(,])+{ParameterNamesRegex}\b"); /// /// Lists the names of all parameters required by the supplied whereSql e.g. @bob = 'bob' would return "@bob" @@ -111,7 +111,7 @@ public static string GetParameterNameFromDeclarationSQL(string parameterSQL) public bool IsValidParameterName(string parameterSQL) => ParameterNamesRegex.IsMatch(parameterSQL); - + [return: NotNullIfNotNull(nameof(s))] public virtual string? GetRuntimeName(string? s) { if (string.IsNullOrWhiteSpace(s)) @@ -131,11 +131,11 @@ public static string GetParameterNameFromDeclarationSQL(string parameterSQL) //Last symbol with no whitespace var lastWord = s[(s.LastIndexOf('.') + 1)..].Trim(); - if(string.IsNullOrWhiteSpace(lastWord) || lastWord.Length<2) + if (string.IsNullOrWhiteSpace(lastWord) || lastWord.Length < 2) return lastWord; //trim off any brackets e.g. return "My Table" for "[My Table]" - if(lastWord.StartsWith(OpenQualifier, StringComparison.Ordinal) && lastWord.EndsWith(CloseQualifier, StringComparison.Ordinal)) + if (lastWord.StartsWith(OpenQualifier, StringComparison.Ordinal) && lastWord.EndsWith(CloseQualifier, StringComparison.Ordinal)) return UnescapeWrappedNameBody(lastWord[1..^1]); return lastWord; @@ -172,16 +172,16 @@ public virtual bool TryGetRuntimeName(string s, out string? name) return databaseOrTableName; if (databaseOrTableName.Contains(DatabaseTableSeparator)) - throw new Exception(string.Format(FAnsiStrings.QuerySyntaxHelper_EnsureWrapped_String_passed_to_EnsureWrapped___0___contained_separators__not_allowed____Prohibited_Separator_is___1__,databaseOrTableName, DatabaseTableSeparator)); + throw new Exception(string.Format(FAnsiStrings.QuerySyntaxHelper_EnsureWrapped_String_passed_to_EnsureWrapped___0___contained_separators__not_allowed____Prohibited_Separator_is___1__, databaseOrTableName, DatabaseTableSeparator)); return EnsureWrappedImpl(databaseOrTableName); } public abstract string EnsureWrappedImpl(string databaseOrTableName); - public abstract string EnsureFullyQualified(string databaseName, string? schema, string tableName); + public abstract string EnsureFullyQualified(string? databaseName, string? schema, string tableName); - public virtual string EnsureFullyQualified(string databaseName, string? schema, string tableName, string columnName, bool isTableValuedFunction = false) => + public virtual string EnsureFullyQualified(string? databaseName, string? schema, string tableName, string columnName, bool isTableValuedFunction = false) => isTableValuedFunction ? $"{GetRuntimeName(tableName)}.{GetRuntimeName(columnName)}" : //table valued functions do not support database name being in the column level selection list area of sql queries $"{EnsureFullyQualified(databaseName, schema, tableName)}.{EnsureWrapped(GetRuntimeName(columnName))}"; @@ -202,7 +202,7 @@ public virtual string EnsureFullyQualified(string databaseName, string? schema, /// /// /// - public virtual bool SplitLineIntoSelectSQLAndAlias(string lineToSplit, out string selectSQL, out string? alias) + public virtual bool SplitLineIntoSelectSQLAndAlias(string lineToSplit, out string selectSQL, [NotNullWhen(true)] out string? alias) { //Ths line is expected to be some SELECT sql so remove trailing whitespace and commas etc lineToSplit = lineToSplit.TrimEnd(',', ' ', '\n', '\r'); @@ -212,7 +212,7 @@ public virtual bool SplitLineIntoSelectSQLAndAlias(string lineToSplit, out strin switch (matches.Count) { case > 1: - throw new SyntaxErrorException(string.Format(FAnsiStrings.QuerySyntaxHelper_SplitLineIntoSelectSQLAndAlias_,matches.Count,lineToSplit)); + throw new SyntaxErrorException(string.Format(FAnsiStrings.QuerySyntaxHelper_SplitLineIntoSelectSQLAndAlias_, matches.Count, lineToSplit)); case 0: selectSQL = lineToSplit; alias = null; @@ -361,9 +361,9 @@ public DbParameter GetParameter(DbParameter p, DiscoveredColumn discoveredColumn else p.Value = value; } - catch(Exception ex) + catch (Exception ex) { - throw new Exception(string.Format(FAnsiStrings.QuerySyntaxHelper_GetParameter_Could_not_GetParameter_for_column___0__, discoveredColumn.GetFullyQualifiedName()),ex); + throw new Exception(string.Format(FAnsiStrings.QuerySyntaxHelper_GetParameter_Could_not_GetParameter_for_column___0__, discoveredColumn.GetFullyQualifiedName()), ex); } return p; @@ -376,12 +376,12 @@ public void ValidateDatabaseName(string? databaseName) } public void ValidateTableName(string tableName) { - if(!IsValidTableName(tableName,out var reason)) + if (!IsValidTableName(tableName, out var reason)) throw new RuntimeNameException(reason); } public void ValidateColumnName(string columnName) { - if(!IsValidColumnName(columnName,out var reason)) + if (!IsValidColumnName(columnName, out var reason)) throw new RuntimeNameException(reason); } @@ -473,7 +473,7 @@ public override bool Equals(object? obj) #endregion - public Dictionary GetParameterNamesFor(T[] columns, Func toStringFunc) where T : notnull + public Dictionary GetParameterNamesFor(T[] columns, Func toStringFunc) where T : notnull { var toReturn = new Dictionary(); @@ -488,10 +488,10 @@ public Dictionary GetParameterNamesFor(T[] columns, Func /// Translates a database proprietary type e.g. 'decimal(10,2)' into a C# type e.g. 'typeof(decimal)' diff --git a/FAnsiSql/Discovery/TypeTranslation/TypeTranslater.cs b/FAnsiSql/Discovery/TypeTranslation/TypeTranslater.cs index e34025f..92231c9 100644 --- a/FAnsiSql/Discovery/TypeTranslation/TypeTranslater.cs +++ b/FAnsiSql/Discovery/TypeTranslation/TypeTranslater.cs @@ -7,7 +7,7 @@ namespace FAnsi.Discovery.TypeTranslation; /// -public abstract partial class TypeTranslater:ITypeTranslater +public abstract partial class TypeTranslater : ITypeTranslater { private const string StringSizeRegexPattern = @"\(([0-9]+)\)"; private const string DecimalsBeforeAndAfterPattern = @"\(([0-9]+),([0-9]+)\)"; @@ -20,7 +20,7 @@ public abstract partial class TypeTranslater:ITypeTranslater protected Regex SmallIntRegex = SmallIntRe(); protected Regex IntRegex = IntRe(); protected Regex LongRegex = LongRe(); - protected Regex DateRegex; + protected readonly Regex DateRegex; protected Regex TimeRegex = TimeRe(); private static readonly Regex StringRegex = StringRe(); private static readonly Regex ByteArrayRegex = ByteArrayRe(); @@ -44,8 +44,9 @@ public abstract partial class TypeTranslater:ITypeTranslater /// /// /// - protected TypeTranslater(int maxStringWidthBeforeMax, int stringWidthWhenNotSupplied) + protected TypeTranslater(Regex dateRegex, int maxStringWidthBeforeMax, int stringWidthWhenNotSupplied) { + DateRegex = dateRegex; MaxStringWidthBeforeMax = maxStringWidthBeforeMax; StringWidthWhenNotSupplied = stringWidthWhenNotSupplied; } @@ -63,10 +64,10 @@ public string GetSQLDBTypeForCSharpType(DatabaseTypeRequest request) if (t == typeof(short) || t == typeof(short) || t == typeof(ushort) || t == typeof(short?) || t == typeof(ushort?)) return GetSmallIntDataType(); - if (t == typeof(int) || t == typeof(int) || t == typeof(uint) || t == typeof(int?) || t == typeof(uint?)) + if (t == typeof(int) || t == typeof(int) || t == typeof(uint) || t == typeof(int?) || t == typeof(uint?)) return GetIntDataType(); - - if (t == typeof (long) || t == typeof(ulong) || t == typeof(long?) || t == typeof(ulong?)) + + if (t == typeof(long) || t == typeof(ulong) || t == typeof(long?) || t == typeof(ulong?)) return GetBigIntDataType(); if (t == typeof(float) || t == typeof(float?) || t == typeof(double) || @@ -81,11 +82,11 @@ public string GetSQLDBTypeForCSharpType(DatabaseTypeRequest request) if (t == typeof(TimeSpan) || t == typeof(TimeSpan?)) return GetTimeDataType(); - - if (t == typeof (byte[])) + + if (t == typeof(byte[])) return GetByteArrayDataType(); - if (t == typeof (Guid)) + if (t == typeof(Guid)) return GetGuidDataType(); throw new TypeNotMappedException(string.Format(FAnsiStrings.TypeTranslater_GetSQLDBTypeForCSharpType_Unsure_what_SQL_type_to_use_for_CSharp_Type___0_____TypeTranslater_was___1__, t.Name, GetType().Name)); @@ -152,7 +153,7 @@ private string GetUnicodeStringDataType(int? maxExpectedStringWidth) [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] - public Type GetCSharpTypeForSQLDBType(string sqlType) => + public Type GetCSharpTypeForSQLDBType(string? sqlType) => TryGetCSharpTypeForSQLDBType(sqlType) ?? throw new TypeNotMappedException(string.Format( FAnsiStrings @@ -163,8 +164,10 @@ public Type GetCSharpTypeForSQLDBType(string sqlType) => [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] - public Type? TryGetCSharpTypeForSQLDBType(string sqlType) + public Type? TryGetCSharpTypeForSQLDBType(string? sqlType) { + if (string.IsNullOrWhiteSpace(sqlType)) return null; + if (IsBit(sqlType)) return typeof(bool); @@ -279,7 +282,7 @@ public virtual DatabaseTypeRequest GetDataTypeRequestForSQLDBType(string sqlType /// /// /// - private static bool IsUnicode(string sqlType) => sqlType != null && sqlType.StartsWith("n",StringComparison.CurrentCultureIgnoreCase); + private static bool IsUnicode(string sqlType) => sqlType != null && sqlType.StartsWith("n", StringComparison.CurrentCultureIgnoreCase); public virtual Guesser GetGuesserFor(DiscoveredColumn discoveredColumn) => GetGuesserFor(discoveredColumn, 0); diff --git a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftQuerySyntaxHelper.cs b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftQuerySyntaxHelper.cs index 6a7f109..612a6fd 100644 --- a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftQuerySyntaxHelper.cs +++ b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftQuerySyntaxHelper.cs @@ -11,8 +11,8 @@ namespace FAnsi.Implementations.MicrosoftSQL; /// public sealed class MicrosoftQuerySyntaxHelper : QuerySyntaxHelper { - public static readonly MicrosoftQuerySyntaxHelper Instance=new(); - private MicrosoftQuerySyntaxHelper() : base(MicrosoftSQLTypeTranslater.Instance,new MicrosoftSQLAggregateHelper(),new MicrosoftSQLUpdateHelper(),DatabaseType.MicrosoftSQLServer) + public static readonly MicrosoftQuerySyntaxHelper Instance = new(); + private MicrosoftQuerySyntaxHelper() : base(MicrosoftSQLTypeTranslater.Instance, new MicrosoftSQLAggregateHelper(), new MicrosoftSQLUpdateHelper(), DatabaseType.MicrosoftSQLServer) { } @@ -80,30 +80,32 @@ 3617 when string.IsNullOrWhiteSpace(sqlE.Message) => true, public override string EnsureWrappedImpl(string databaseOrTableName) => $"[{GetRuntimeNameWithDoubledClosingSquareBrackets(databaseOrTableName)}]"; - protected override string UnescapeWrappedNameBody(string name) => name.Replace("]]","]"); + protected override string UnescapeWrappedNameBody(string name) => name.Replace("]]", "]"); /// /// Returns the runtime name of the string with all ending square brackets escaped by doubling up (but resulting string is not wrapped itself) /// /// /// - private string? GetRuntimeNameWithDoubledClosingSquareBrackets(string s) => GetRuntimeName(s)?.Replace("]","]]"); + private string? GetRuntimeNameWithDoubledClosingSquareBrackets(string s) => GetRuntimeName(s)?.Replace("]", "]]"); - public override string EnsureFullyQualified(string databaseName, string? schema, string tableName) + public override string EnsureFullyQualified(string? databaseName, string? schema, string tableName) { //if there is no schema address it as db..table (which is the same as db.dbo.table in Microsoft SQL Server) if (string.IsNullOrWhiteSpace(schema)) - return EnsureWrapped( GetRuntimeName(databaseName)) + DatabaseTableSeparator + DatabaseTableSeparator + EnsureWrapped(GetRuntimeName(tableName)); + return + $"{EnsureWrapped(GetRuntimeName(databaseName))}{DatabaseTableSeparator}{DatabaseTableSeparator}{EnsureWrapped(GetRuntimeName(tableName))}"; //there is a schema so add it in - return EnsureWrapped(GetRuntimeName(databaseName)) + DatabaseTableSeparator + EnsureWrapped(GetRuntimeName(schema)) + DatabaseTableSeparator + EnsureWrapped( GetRuntimeName(tableName)); + return + $"{EnsureWrapped(GetRuntimeName(databaseName))}{DatabaseTableSeparator}{EnsureWrapped(GetRuntimeName(schema))}{DatabaseTableSeparator}{EnsureWrapped(GetRuntimeName(tableName))}"; } - public override string EnsureFullyQualified(string databaseName, string schema, string tableName, string columnName, bool isTableValuedFunction = false) + public override string EnsureFullyQualified(string? databaseName, string? schema, string tableName, string columnName, bool isTableValuedFunction = false) { if (isTableValuedFunction) return GetRuntimeName(tableName) + DatabaseTableSeparator + EnsureWrapped(GetRuntimeName(columnName));//table valued functions do not support database name being in the column level selection list area of sql queries - return EnsureFullyQualified(databaseName,schema,tableName) + DatabaseTableSeparator + EnsureWrapped(GetRuntimeName(columnName)); + return EnsureFullyQualified(databaseName, schema, tableName) + DatabaseTableSeparator + EnsureWrapped(GetRuntimeName(columnName)); } } \ No newline at end of file diff --git a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLServerHelper.cs b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLServerHelper.cs index d86b334..85f75b0 100644 --- a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLServerHelper.cs +++ b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLServerHelper.cs @@ -25,19 +25,19 @@ private MicrosoftSQLServerHelper() : base(DatabaseType.MicrosoftSQLServer) #region Up Typing public override DbCommand GetCommand(string s, DbConnection con, DbTransaction? transaction = null) => new SqlCommand(s, (SqlConnection)con, transaction as SqlTransaction); - public override DbDataAdapter GetDataAdapter(DbCommand cmd) => new SqlDataAdapter((SqlCommand) cmd); + public override DbDataAdapter GetDataAdapter(DbCommand cmd) => new SqlDataAdapter((SqlCommand)cmd); - public override DbCommandBuilder GetCommandBuilder(DbCommand cmd) => new SqlCommandBuilder((SqlDataAdapter) GetDataAdapter(cmd)); + public override DbCommandBuilder GetCommandBuilder(DbCommand cmd) => new SqlCommandBuilder((SqlDataAdapter)GetDataAdapter(cmd)); - public override DbParameter GetParameter(string parameterName) => new SqlParameter(parameterName,null); + public override DbParameter GetParameter(string parameterName) => new SqlParameter(parameterName, null); public override DbConnection GetConnection(DbConnectionStringBuilder builder) => new SqlConnection(builder.ConnectionString); protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(string connectionString) => new SqlConnectionStringBuilder(connectionString); - protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(string server, string database, string username, string password) + protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(string server, string? database, string username, string password) { - var toReturn = new SqlConnectionStringBuilder { DataSource = server}; + var toReturn = new SqlConnectionStringBuilder { DataSource = server }; if (!string.IsNullOrWhiteSpace(username)) { toReturn.UserID = username; @@ -46,12 +46,12 @@ protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(stri else toReturn.IntegratedSecurity = true; - if(!string.IsNullOrWhiteSpace(database)) + if (!string.IsNullOrWhiteSpace(database)) toReturn.InitialCatalog = database; return toReturn; } - public static string GetDatabaseNameFrom(DbConnectionStringBuilder builder) => ((SqlConnectionStringBuilder) builder).InitialCatalog; + public static string GetDatabaseNameFrom(DbConnectionStringBuilder builder) => ((SqlConnectionStringBuilder)builder).InitialCatalog; #endregion @@ -74,10 +74,10 @@ public override string[] ListDatabases(DbConnection con) { var databases = new List(); - using(var cmd = GetCommand("select name [Database] from master..sysdatabases", con)) + using (var cmd = GetCommand("select name [Database] from master..sysdatabases", con)) using (var r = cmd.ExecuteReader()) while (r.Read()) - databases.Add((string) r["Database"]); + databases.Add((string)r["Database"]); con.Close(); return [.. databases]; @@ -85,7 +85,7 @@ public override string[] ListDatabases(DbConnection con) public override DbConnectionStringBuilder EnableAsync(DbConnectionStringBuilder builder) { - var b = (SqlConnectionStringBuilder) builder; + var b = (SqlConnectionStringBuilder)builder; b.MultipleActiveResultSets = true; @@ -113,7 +113,7 @@ public override void CreateDatabase(DbConnectionStringBuilder builder, IHasRunti cmd.ExecuteNonQuery(); } - public override Dictionary DescribeServer(DbConnectionStringBuilder builder) + public override Dictionary DescribeServer(DbConnectionStringBuilder builder) { var toReturn = new Dictionary(); @@ -126,8 +126,8 @@ public override Dictionary DescribeServer(DbConnectionStringBuild try { using var dt = new DataTable(); - using(var cmd = new SqlCommand("EXEC master..xp_fixeddrives",con)) - using(var da = new SqlDataAdapter(cmd)) + using (var cmd = new SqlCommand("EXEC master..xp_fixeddrives", con)) + using (var da = new SqlDataAdapter(cmd)) da.Fill(dt); foreach (DataRow row in dt.Rows) @@ -142,15 +142,15 @@ public override Dictionary DescribeServer(DbConnectionStringBuild return toReturn; } - public override string GetExplicitUsernameIfAny(DbConnectionStringBuilder builder) + public override string? GetExplicitUsernameIfAny(DbConnectionStringBuilder builder) { - var u = ((SqlConnectionStringBuilder) builder).UserID; - return string.IsNullOrWhiteSpace(u) ? null: u; + var u = ((SqlConnectionStringBuilder)builder).UserID; + return string.IsNullOrWhiteSpace(u) ? null : u; } - public override string GetExplicitPasswordIfAny(DbConnectionStringBuilder builder) + public override string? GetExplicitPasswordIfAny(DbConnectionStringBuilder builder) { - var pwd = ((SqlConnectionStringBuilder) builder).Password; + var pwd = ((SqlConnectionStringBuilder)builder).Password; return string.IsNullOrWhiteSpace(pwd) ? null : pwd; } @@ -165,14 +165,14 @@ protected override void EnforceKeywords(DbConnectionStringBuilder builder) if (msb.Authentication != SqlAuthenticationMethod.NotSpecified) msb.IntegratedSecurity = false; } - public override Version GetVersion(DiscoveredServer server) + public override Version? GetVersion(DiscoveredServer server) { using var con = server.GetConnection(); con.Open(); - using var cmd = server.GetCommand("SELECT @@VERSION",con); + using var cmd = server.GetCommand("SELECT @@VERSION", con); using var r = cmd.ExecuteReader(); - if(r.Read()) - return r[0] == DBNull.Value ? null: CreateVersionFromString((string)r[0]); + if (r.Read()) + return r[0] == DBNull.Value ? null : CreateVersionFromString((string)r[0]); return null; } diff --git a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTableHelper.cs b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTableHelper.cs index 353ee7a..2a0ed6b 100644 --- a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTableHelper.cs +++ b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTableHelper.cs @@ -47,7 +47,7 @@ public override DiscoveredColumn[] DiscoverColumns(DiscoveredTable discoveredTab } } - if(toReturn.Count == 0) + if (toReturn.Count == 0) throw new Exception($"Could not find any columns in table {discoveredTable}"); //don't bother looking for pks if it is a table valued function @@ -56,7 +56,7 @@ public override DiscoveredColumn[] DiscoverColumns(DiscoveredTable discoveredTab var pks = ListPrimaryKeys(connection, discoveredTable); - foreach (var c in toReturn.Where(c => pks.Any(pk=>pk.Equals(c.GetRuntimeName())))) + foreach (var c in toReturn.Where(c => pks.Any(pk => pk.Equals(c.GetRuntimeName())))) c.IsPrimaryKey = true; return [.. toReturn]; @@ -97,11 +97,11 @@ public override void DropTable(DbConnection connection, DiscoveredTable tableToD case TableType.Table: cmd = new SqlCommand($"DROP TABLE {tableToDrop.GetFullyQualifiedName()}", (SqlConnection)connection); break; - case TableType.TableValuedFunction : - DropFunction(connection,(DiscoveredTableValuedFunction) tableToDrop); + case TableType.TableValuedFunction: + DropFunction(connection, (DiscoveredTableValuedFunction)tableToDrop); return; default: - throw new ArgumentOutOfRangeException(nameof(tableToDrop),$"Unknown table type {tableToDrop.TableType}"); + throw new ArgumentOutOfRangeException(nameof(tableToDrop), $"Unknown table type {tableToDrop.TableType}"); } using (cmd) @@ -110,7 +110,7 @@ public override void DropTable(DbConnection connection, DiscoveredTable tableToD public override void DropFunction(DbConnection connection, DiscoveredTableValuedFunction functionToDrop) { - using var cmd = new SqlCommand($"DROP FUNCTION {functionToDrop.Schema??"dbo"}.{functionToDrop.GetRuntimeName()}", (SqlConnection)connection); + using var cmd = new SqlCommand($"DROP FUNCTION {functionToDrop.Schema ?? "dbo"}.{functionToDrop.GetRuntimeName()}", (SqlConnection)connection); cmd.ExecuteNonQuery(); } @@ -122,7 +122,7 @@ public override void DropColumn(DbConnection connection, DiscoveredColumn column } - public override IEnumerable DiscoverTableValuedFunctionParameters(DbConnection connection,DiscoveredTableValuedFunction discoveredTableValuedFunction, DbTransaction transaction) + public override IEnumerable DiscoverTableValuedFunctionParameters(DbConnection connection, DiscoveredTableValuedFunction discoveredTableValuedFunction, DbTransaction transaction) { var toReturn = new List(); @@ -161,7 +161,7 @@ sys.parameters.precision AS PRECISION return toReturn.ToArray(); } - public override IBulkCopy BeginBulkInsert(DiscoveredTable discoveredTable,IManagedConnection connection,CultureInfo culture) => new MicrosoftSQLBulkCopy(discoveredTable,connection,culture); + public override IBulkCopy BeginBulkInsert(DiscoveredTable discoveredTable, IManagedConnection connection, CultureInfo culture) => new MicrosoftSQLBulkCopy(discoveredTable, connection, culture); public override void CreatePrimaryKey(DatabaseOperationArgs args, DiscoveredTable table, DiscoveredColumn[] discoverColumns) { @@ -180,18 +180,18 @@ public override void CreatePrimaryKey(DatabaseOperationArgs args, DiscoveredTabl throw new AlterFailedException(string.Format(FAnsiStrings.DiscoveredTableHelper_CreatePrimaryKey_Failed_to_create_primary_key_on_table__0__using_columns___1__, table, string.Join(",", discoverColumns.Select(static c => c.GetRuntimeName()))), e); } - base.CreatePrimaryKey(args,table, discoverColumns); + base.CreatePrimaryKey(args, table, discoverColumns); } - public override DiscoveredRelationship[] DiscoverRelationships(DiscoveredTable table,DbConnection connection, IManagedTransaction? transaction = null) + public override DiscoveredRelationship[] DiscoverRelationships(DiscoveredTable table, DbConnection connection, IManagedTransaction? transaction = null) { - var toReturn = new Dictionary(); + var toReturn = new Dictionary(); const string sql = "exec sp_fkeys @pktable_name = @table, @pktable_qualifier=@database, @pktable_owner=@schema"; using (var cmd = table.GetCommand(sql, connection)) { - if(transaction != null) + if (transaction != null) cmd.Transaction = transaction.Transaction; var p = cmd.CreateParameter(); @@ -216,22 +216,22 @@ public override DiscoveredRelationship[] DiscoverRelationships(DiscoveredTable t var da = table.Database.Server.GetDataAdapter(cmd); da.Fill(dt); - foreach(DataRow r in dt.Rows) + foreach (DataRow r in dt.Rows) { - var fkName = r["FK_NAME"].ToString(); + var fkName = r["FK_NAME"].ToString() ?? throw new InvalidOperationException("Null foreign key name returned"); //could be a 2+ columns foreign key? if (!toReturn.TryGetValue(fkName, out var current)) { - var pkdb = r["PKTABLE_QUALIFIER"].ToString(); + var pkdb = r["PKTABLE_QUALIFIER"].ToString() ?? throw new InvalidOperationException("Null primary key database name returned"); var pkschema = r["PKTABLE_OWNER"].ToString(); - var pktableName = r["PKTABLE_NAME"].ToString(); + var pktableName = r["PKTABLE_NAME"].ToString() ?? throw new InvalidOperationException("Null primary key table name returned"); var pktable = table.Database.Server.ExpectDatabase(pkdb).ExpectTable(pktableName, pkschema); - var fkdb = r["FKTABLE_QUALIFIER"].ToString(); + var fkdb = r["FKTABLE_QUALIFIER"].ToString() ?? throw new InvalidOperationException("Null foreign key database name returned"); var fkschema = r["FKTABLE_OWNER"].ToString(); - var fktableName = r["FKTABLE_NAME"].ToString(); + var fktableName = r["FKTABLE_NAME"].ToString() ?? throw new InvalidOperationException("Null foreign key name returned"); var fktable = table.Database.Server.ExpectDatabase(fkdb).ExpectTable(fktableName, fkschema); @@ -254,11 +254,11 @@ public override DiscoveredRelationship[] DiscoverRelationships(DiscoveredTable t 2 = set null 3 = set default*/ - current = new DiscoveredRelationship(fkName,pktable,fktable,deleteRule); - toReturn.Add(current.Name,current); + current = new DiscoveredRelationship(fkName, pktable, fktable, deleteRule); + toReturn.Add(current.Name, current); } - current.AddKeys(r["PKCOLUMN_NAME"].ToString(), r["FKCOLUMN_NAME"].ToString(),transaction); + current.AddKeys(r["PKCOLUMN_NAME"].ToString(), r["FKCOLUMN_NAME"].ToString(), transaction); } } @@ -278,7 +278,7 @@ protected override string GetRenameTableSql(DiscoveredTable discoveredTable, str return $"exec sp_rename '{syntax.Escape(oldName)}', '{syntax.Escape(newName)}'"; } - public override void MakeDistinct(DatabaseOperationArgs args,DiscoveredTable discoveredTable) + public override void MakeDistinct(DatabaseOperationArgs args, DiscoveredTable discoveredTable) { var syntax = discoveredTable.GetQuerySyntaxHelper(); @@ -293,9 +293,9 @@ where RowNum > 1 """; var columnList = string.Join(",", - discoveredTable.DiscoverColumns().Select(c=>syntax.EnsureWrapped(c.GetRuntimeName()))); + discoveredTable.DiscoverColumns().Select(c => syntax.EnsureWrapped(c.GetRuntimeName()))); - var sqlToExecute = string.Format(sql,columnList,discoveredTable.GetFullyQualifiedName()); + var sqlToExecute = string.Format(sql, columnList, discoveredTable.GetFullyQualifiedName()); var server = discoveredTable.Database.Server; @@ -310,16 +310,16 @@ where RowNum > 1 private string GetSQLType_FromSpColumnsResult(DbDataReader r) { var columnType = r["TYPE_NAME"] as string; - var lengthQualifier = ""; - - if (HasPrecisionAndScale(columnType)) - lengthQualifier = $"({r["PRECISION"]},{r["SCALE"]})"; - else - if (RequiresLength(columnType)) lengthQualifier = $"({AdjustForUnicodeAndNegativeOne(columnType, Convert.ToInt32(r["LENGTH"]))})"; if (columnType == "text") return "varchar(max)"; + var lengthQualifier = ""; + + if (HasPrecisionAndScale(columnType ?? throw new InvalidOperationException("Null type name returned"))) + lengthQualifier = $"({r["PRECISION"]},{r["SCALE"]})"; + else if (RequiresLength(columnType)) lengthQualifier = $"({AdjustForUnicodeAndNegativeOne(columnType, Convert.ToInt32(r["LENGTH"]))})"; + return columnType + lengthQualifier; } @@ -329,7 +329,7 @@ private static object AdjustForUnicodeAndNegativeOne(string columnType, int leng return "max"; if (columnType.Contains("nvarchar") || columnType.Contains("nchar") || columnType.Contains("ntext")) - return length/2; + return length / 2; return length; } @@ -363,7 +363,7 @@ ORDER BY OBJECT_NAME(ic.OBJECT_ID), ic.key_ordinal cmd.Transaction = con.Transaction; using var r = cmd.ExecuteReader(); while (r.Read()) - toReturn.Add((string) r["ColumnName"]); + toReturn.Add((string)r["ColumnName"]); r.Close(); } diff --git a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTypeTranslater.cs b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTypeTranslater.cs index c4917b7..6321589 100644 --- a/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTypeTranslater.cs +++ b/FAnsiSql/Implementations/MicrosoftSQL/MicrosoftSQLTypeTranslater.cs @@ -9,9 +9,8 @@ public sealed partial class MicrosoftSQLTypeTranslater : TypeTranslater private static readonly Regex AlsoBinaryRegex = AlsoBinaryRe(); - private MicrosoftSQLTypeTranslater() : base(8000, 4000) + private MicrosoftSQLTypeTranslater() : base(DateRe(), 8000, 4000) { - DateRegex = DateRe(); } protected override string GetDateDateTimeDataType() => "datetime2"; diff --git a/FAnsiSql/Implementations/MySql/MySqlServerHelper.cs b/FAnsiSql/Implementations/MySql/MySqlServerHelper.cs index 7becab9..cd9f2c0 100644 --- a/FAnsiSql/Implementations/MySql/MySqlServerHelper.cs +++ b/FAnsiSql/Implementations/MySql/MySqlServerHelper.cs @@ -11,10 +11,10 @@ namespace FAnsi.Implementations.MySql; public sealed class MySqlServerHelper : DiscoveredServerHelper { - public static readonly MySqlServerHelper Instance=new(); + public static readonly MySqlServerHelper Instance = new(); static MySqlServerHelper() { - AddConnectionStringKeyword(DatabaseType.MySql, "AllowUserVariables","True",ConnectionStringKeywordPriority.ApiRule); + AddConnectionStringKeyword(DatabaseType.MySql, "AllowUserVariables", "True", ConnectionStringKeywordPriority.ApiRule); AddConnectionStringKeyword(DatabaseType.MySql, "CharSet", "utf8", ConnectionStringKeywordPriority.ApiRule); } @@ -35,7 +35,7 @@ public override DbCommand GetCommand(string s, DbConnection con, DbTransaction? public override DbCommandBuilder GetCommandBuilder(DbCommand cmd) => new MySqlCommandBuilder((MySqlDataAdapter)GetDataAdapter(cmd)); - public override DbParameter GetParameter(string parameterName) => new MySqlParameter(parameterName,null); + public override DbParameter GetParameter(string parameterName) => new MySqlParameter(parameterName, null); public override DbConnection GetConnection(DbConnectionStringBuilder builder) => new MySqlConnection(builder.ConnectionString); @@ -43,14 +43,14 @@ public override DbConnection GetConnection(DbConnectionStringBuilder builder) => protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(string connectionString) => new MySqlConnectionStringBuilder(connectionString); - protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(string server, string database, string username, string password) + protected override DbConnectionStringBuilder GetConnectionStringBuilderImpl(string server, string? database, string username, string password) { var toReturn = new MySqlConnectionStringBuilder { Server = server }; - if(!string.IsNullOrWhiteSpace(database)) + if (!string.IsNullOrWhiteSpace(database)) toReturn.Database = database; if (!string.IsNullOrWhiteSpace(username)) @@ -77,25 +77,25 @@ public override void CreateDatabase(DbConnectionStringBuilder builder, IHasRunti using var con = new MySqlConnection(b.ConnectionString); con.Open(); - using var cmd = GetCommand($"CREATE DATABASE `{newDatabaseName.GetRuntimeName()}`",con); + using var cmd = GetCommand($"CREATE DATABASE `{newDatabaseName.GetRuntimeName()}`", con); cmd.CommandTimeout = CreateDatabaseTimeoutInSeconds; cmd.ExecuteNonQuery(); } public override Dictionary DescribeServer(DbConnectionStringBuilder builder) => throw new NotImplementedException(); - public override string GetExplicitUsernameIfAny(DbConnectionStringBuilder builder) => ((MySqlConnectionStringBuilder) builder).UserID; + public override string GetExplicitUsernameIfAny(DbConnectionStringBuilder builder) => ((MySqlConnectionStringBuilder)builder).UserID; public override string GetExplicitPasswordIfAny(DbConnectionStringBuilder builder) => ((MySqlConnectionStringBuilder)builder).Password; - public override Version GetVersion(DiscoveredServer server) + public override Version? GetVersion(DiscoveredServer server) { using var con = server.GetConnection(); con.Open(); - using var cmd = server.GetCommand("show variables like \"version\"",con); + using var cmd = server.GetCommand("show variables like \"version\"", con); using var r = cmd.ExecuteReader(); if (r.Read()) - return r["Value"] == DBNull.Value ? null: CreateVersionFromString((string)r["Value"]); + return r["Value"] == DBNull.Value ? null : CreateVersionFromString((string)r["Value"]); return null; } diff --git a/FAnsiSql/Implementations/MySql/MySqlTypeTranslater.cs b/FAnsiSql/Implementations/MySql/MySqlTypeTranslater.cs index a2543e6..db061fd 100644 --- a/FAnsiSql/Implementations/MySql/MySqlTypeTranslater.cs +++ b/FAnsiSql/Implementations/MySql/MySqlTypeTranslater.cs @@ -14,14 +14,13 @@ public sealed partial class MySqlTypeTranslater : TypeTranslater private static readonly Regex AlsoStringRegex = AlsoStringRe(); private static readonly Regex AlsoFloatingPoint = AlsoFloatingPointRe(); - private MySqlTypeTranslater() : base(4000, 4000) + private MySqlTypeTranslater() : base(DateRe(), 4000, 4000) { //match bigint and bigint(20) etc ByteRegex = ByteRe(); SmallIntRegex = SmallIntRe(); IntRegex = IntRe(); LongRegex = LongRe(); - DateRegex = DateRe(); } public override int GetLengthIfString(string sqlType) => diff --git a/FAnsiSql/Implementations/Oracle/OracleQuerySyntaxHelper.cs b/FAnsiSql/Implementations/Oracle/OracleQuerySyntaxHelper.cs index 8c447ec..62aaa3d 100644 --- a/FAnsiSql/Implementations/Oracle/OracleQuerySyntaxHelper.cs +++ b/FAnsiSql/Implementations/Oracle/OracleQuerySyntaxHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using FAnsi.Discovery; using FAnsi.Discovery.QuerySyntax; using FAnsi.Implementations.Oracle.Aggregation; @@ -9,7 +10,7 @@ namespace FAnsi.Implementations.Oracle; public sealed class OracleQuerySyntaxHelper : QuerySyntaxHelper { - public static readonly OracleQuerySyntaxHelper Instance=new(); + public static readonly OracleQuerySyntaxHelper Instance = new(); public override int MaximumDatabaseLength => 30; // JS 2023-05-11 Can be longer, but Oracle RAC limits to 30 public override int MaximumTableLength => 30; public override int MaximumColumnLength => 30; @@ -19,13 +20,14 @@ public sealed class OracleQuerySyntaxHelper : QuerySyntaxHelper public override string CloseQualifier => "\""; - private OracleQuerySyntaxHelper() : base(OracleTypeTranslater.Instance, OracleAggregateHelper.Instance,OracleUpdateHelper.Instance,DatabaseType.Oracle)//no custom translater + private OracleQuerySyntaxHelper() : base(OracleTypeTranslater.Instance, OracleAggregateHelper.Instance, OracleUpdateHelper.Instance, DatabaseType.Oracle)//no custom translater { } public override char ParameterSymbol => ':'; - public override string GetRuntimeName(string s) + [return: NotNullIfNotNull(nameof(s))] + public override string? GetRuntimeName(string? s) { var answer = base.GetRuntimeName(s); @@ -38,7 +40,7 @@ public override string GetRuntimeName(string s) public override string EnsureWrappedImpl(string databaseOrTableName) => $"\"{GetRuntimeName(databaseOrTableName)}\""; - public override string EnsureFullyQualified(string databaseName, string schema, string tableName) + public override string EnsureFullyQualified(string? databaseName, string? schema, string tableName) { //if there is no schema address it as db..table (which is the same as db.dbo.table in Microsoft SQL Server) if (!string.IsNullOrWhiteSpace(schema)) @@ -47,7 +49,7 @@ public override string EnsureFullyQualified(string databaseName, string schema, return $"\"{GetRuntimeName(databaseName)}\"{DatabaseTableSeparator}\"{GetRuntimeName(tableName)}\""; } - public override string EnsureFullyQualified(string databaseName, string schema, string tableName, string columnName, bool isTableValuedFunction = false) => $"{EnsureFullyQualified(databaseName, schema, tableName)}.\"{GetRuntimeName(columnName)}\""; + public override string EnsureFullyQualified(string? databaseName, string? schema, string tableName, string columnName, bool isTableValuedFunction = false) => $"{EnsureFullyQualified(databaseName, schema, tableName)}.\"{GetRuntimeName(columnName)}\""; public override TopXResponse HowDoWeAchieveTopX(int x) => new($"OFFSET 0 ROWS FETCH NEXT {x} ROWS ONLY", QueryComponent.Postfix); @@ -80,7 +82,7 @@ protected override object FormatTimespanForDbParameter(TimeSpan timeSpan) => //Value must be a DateTime even if DBParameter is of Type DbType.Time Convert.ToDateTime(timeSpan.ToString()); - private static readonly HashSet ReservedWords = new( new [] + private static readonly HashSet ReservedWords = new(new[] { "ACCESS", @@ -563,6 +565,6 @@ protected override object FormatTimespanForDbParameter(TimeSpan timeSpan) => "XID", "YEAR", "ZONE" - },StringComparer.CurrentCultureIgnoreCase); + }, StringComparer.CurrentCultureIgnoreCase); } \ No newline at end of file diff --git a/FAnsiSql/Implementations/Oracle/OracleTypeTranslater.cs b/FAnsiSql/Implementations/Oracle/OracleTypeTranslater.cs index 49b6da6..87e9def 100644 --- a/FAnsiSql/Implementations/Oracle/OracleTypeTranslater.cs +++ b/FAnsiSql/Implementations/Oracle/OracleTypeTranslater.cs @@ -20,10 +20,10 @@ public sealed partial class OracleTypeTranslater:TypeTranslater private static readonly Regex AlsoStringRegex = AlsoStringRegexImpl(); - private OracleTypeTranslater(): base(4000, 4000) + private OracleTypeTranslater() : base(DateRegexImpl(), 4000, 4000) { - DateRegex = DateRegexImpl(); } + protected override string GetStringDataTypeImpl(int maxExpectedStringWidth) => $"varchar2({maxExpectedStringWidth})"; protected override string GetUnicodeStringDataTypeImpl(int maxExpectedStringWidth) => $"nvarchar2({maxExpectedStringWidth})"; diff --git a/FAnsiSql/Implementations/PostgreSql/PostgreSqlSyntaxHelper.cs b/FAnsiSql/Implementations/PostgreSql/PostgreSqlSyntaxHelper.cs index 72458e0..08de9a9 100644 --- a/FAnsiSql/Implementations/PostgreSql/PostgreSqlSyntaxHelper.cs +++ b/FAnsiSql/Implementations/PostgreSql/PostgreSqlSyntaxHelper.cs @@ -10,7 +10,7 @@ namespace FAnsi.Implementations.PostgreSql; public sealed class PostgreSqlSyntaxHelper : QuerySyntaxHelper { public static readonly PostgreSqlSyntaxHelper Instance = new(); - private PostgreSqlSyntaxHelper() : base(PostgreSqlTypeTranslater.Instance,PostgreSqlAggregateHelper.Instance,PostgreSqlUpdateHelper.Instance, DatabaseType.PostgreSql) + private PostgreSqlSyntaxHelper() : base(PostgreSqlTypeTranslater.Instance, PostgreSqlAggregateHelper.Instance, PostgreSqlUpdateHelper.Instance, DatabaseType.PostgreSql) { } @@ -30,7 +30,7 @@ protected override object FormatDateTimeForDbParameter(DateTime dateTime) => // Starting with 4.0.0 npgsql crashes if it has to read a DateTime Unspecified Kind // See : https://github.com/npgsql/efcore.pg/issues/2000 // Also it doesn't support DateTime.Kind.Local - dateTime.Kind == DateTimeKind.Unspecified ? dateTime.ToUniversalTime():dateTime; + dateTime.Kind == DateTimeKind.Unspecified ? dateTime.ToUniversalTime() : dateTime; public override string EnsureWrappedImpl(string databaseOrTableName) => $"\"{GetRuntimeNameWithDoubledDoubleQuotes(databaseOrTableName)}\""; @@ -39,25 +39,19 @@ protected override object FormatDateTimeForDbParameter(DateTime dateTime) => /// /// /// - private string GetRuntimeNameWithDoubledDoubleQuotes(string s) => GetRuntimeName(s)?.Replace("\"","\"\""); + private string? GetRuntimeNameWithDoubledDoubleQuotes(string s) => GetRuntimeName(s)?.Replace("\"", "\"\""); - protected override string UnescapeWrappedNameBody(string name) => name.Replace("\"\"","\""); + protected override string UnescapeWrappedNameBody(string name) => name.Replace("\"\"", "\""); - public override string EnsureFullyQualified(string databaseName, string schema, string tableName) - { + public override string EnsureFullyQualified(string? databaseName, string? schema, string tableName) => //if there is no schema address it as db..table (which is the same as db.dbo.table in Microsoft SQL Server) - if(string.IsNullOrWhiteSpace(schema)) - return EnsureWrapped(databaseName) + DatabaseTableSeparator + DefaultPostgresSchema + DatabaseTableSeparator + EnsureWrapped(tableName); - - //there is a schema so add it in - return EnsureWrapped(databaseName) + DatabaseTableSeparator + EnsureWrapped(schema) + DatabaseTableSeparator + EnsureWrapped(tableName); - } + $"{EnsureWrapped(databaseName)}{DatabaseTableSeparator}{(string.IsNullOrWhiteSpace(schema) ? DefaultPostgresSchema : EnsureWrapped(schema))}{DatabaseTableSeparator}{EnsureWrapped(tableName)}"; - public override string EnsureFullyQualified(string databaseName, string schema, string tableName, string columnName, + public override string EnsureFullyQualified(string? databaseName, string? schema, string tableName, string columnName, bool isTableValuedFunction = false) { if (isTableValuedFunction) - return $"{EnsureWrapped(tableName)}.{EnsureWrapped(GetRuntimeName(columnName))}";//table valued functions do not support database name being in the column level selection list area of sql queries + return $"{EnsureWrapped(tableName)}.{EnsureWrapped(GetRuntimeName(columnName))}"; //table valued functions do not support database name being in the column level selection list area of sql queries return $"{EnsureFullyQualified(databaseName, schema, tableName)}.\"{GetRuntimeName(columnName)}\""; } diff --git a/FAnsiSql/Implementations/PostgreSql/PostgreSqlTypeTranslater.cs b/FAnsiSql/Implementations/PostgreSql/PostgreSqlTypeTranslater.cs index 61c0837..508a417 100644 --- a/FAnsiSql/Implementations/PostgreSql/PostgreSqlTypeTranslater.cs +++ b/FAnsiSql/Implementations/PostgreSql/PostgreSqlTypeTranslater.cs @@ -8,9 +8,9 @@ namespace FAnsi.Implementations.PostgreSql; public sealed partial class PostgreSqlTypeTranslater : TypeTranslater { public static readonly PostgreSqlTypeTranslater Instance = new(); - private PostgreSqlTypeTranslater() : base(8000, 4000) + + private PostgreSqlTypeTranslater() : base(DateRegexImpl(), 8000, 4000) { - DateRegex = DateRegexImpl(); TimeRegex = TimeRegexImpl(); //space is important } diff --git a/README.md b/README.md index bb51f5c..b90a10a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # FAnsiSql -[![.NET Core](https://github.com/HicServices/FAnsiSql/actions/workflows/dotnet-core.yml/badge.svg)](https://github.com/HicServices/FAnsiSql/actions/workflows/dotnet-core.yml) [![Total alerts](https://img.shields.io/lgtm/alerts/g/HicServices/FAnsiSql.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/HicServices/FAnsiSql/alerts/) [![NuGet Badge](https://buildstats.info/nuget/HIC.FAnsiSql)](https://www.nuget.org/packages/HIC.FansiSql/) +[![Build and test](https://github.com/HicServices/FAnsiSql/actions/workflows/dotnet-core.yml/badge.svg)](https://github.com/HicServices/FAnsiSql/actions/workflows/dotnet-core.yml) +[![CodeQL](https://github.com/HicServices/FAnsiSql/actions/workflows/codeql.yml/badge.svg)](https://github.com/HicServices/FAnsiSql/actions/workflows/codeql.yml) +[![NuGet Badge](https://buildstats.info/nuget/HIC.FAnsiSql)](https://www.nuget.org/packages/HIC.FansiSql/) - [Nuget](https://www.nuget.org/packages/HIC.FansiSql/) - [Dependencies](./Packages.md) diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs index 7d4f566..2d99543 100644 --- a/SharedAssemblyInfo.cs +++ b/SharedAssemblyInfo.cs @@ -2,10 +2,10 @@ [assembly: AssemblyCompany("Health Informatics Centre, University of Dundee")] [assembly: AssemblyProduct("FAnsiSql")] -[assembly: AssemblyCopyright("Copyright (c) 2018 - 2023")] +[assembly: AssemblyCopyright("Copyright (c) 2018 - 2024")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("3.1.1")] -[assembly: AssemblyFileVersion("3.1.1")] -[assembly: AssemblyInformationalVersion("3.1.1")] +[assembly: AssemblyVersion("3.2.0")] +[assembly: AssemblyFileVersion("3.2.0")] +[assembly: AssemblyInformationalVersion("3.2.0")] diff --git a/Tests/FAnsiTests/Table/CreateTableTests.cs b/Tests/FAnsiTests/Table/CreateTableTests.cs index ef72506..f9eb224 100644 --- a/Tests/FAnsiTests/Table/CreateTableTests.cs +++ b/Tests/FAnsiTests/Table/CreateTableTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.IO; using System.Linq; using System.Reflection; using FAnsi; @@ -12,9 +13,9 @@ namespace FAnsiTests.Table; -internal sealed class CreateTableTests:DatabaseTests +internal sealed class CreateTableTests : DatabaseTests { - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void CreateSimpleTable_Exists(DatabaseType type) { var db = GetTestDatabase(type); @@ -30,7 +31,7 @@ public void CreateSimpleTable_Exists(DatabaseType type) Assert.That(table.Exists(), Is.False); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void TestTableCreation(DatabaseType type) { var database = GetTestDatabase(type); @@ -54,14 +55,14 @@ public void TestTableCreation(DatabaseType type) Assert.That(tbl.Exists()); - var colsDictionary = tbl.DiscoverColumns().ToDictionary(static k => k.GetRuntimeName(), static v => v, StringComparer.InvariantCultureIgnoreCase); + var colsDictionary = tbl.DiscoverColumns().ToDictionary(static k => k.GetRuntimeName() ?? throw new InvalidDataException(), static v => v, StringComparer.InvariantCultureIgnoreCase); var name = colsDictionary["name"]; Assert.Multiple(() => { - Assert.That(name.DataType.GetLengthIfString(), Is.EqualTo(10)); + Assert.That(name.DataType?.GetLengthIfString(), Is.EqualTo(10)); Assert.That(name.AllowNulls, Is.EqualTo(false)); - Assert.That(syntaxHelper.TypeTranslater.GetCSharpTypeForSQLDBType(name.DataType.SQLType), Is.EqualTo(typeof(string))); + Assert.That(syntaxHelper.TypeTranslater.GetCSharpTypeForSQLDBType(name.DataType?.SQLType), Is.EqualTo(typeof(string))); Assert.That(name.IsPrimaryKey); }); @@ -70,8 +71,8 @@ public void TestTableCreation(DatabaseType type) Assert.Multiple(() => { Assert.That(foreignName.AllowNulls, Is.EqualTo(false));//because it is part of the primary key we ignored the users request about nullability - Assert.That(foreignName.DataType.GetLengthIfString(), Is.EqualTo(7)); - Assert.That(syntaxHelper.TypeTranslater.GetCSharpTypeForSQLDBType(foreignName.DataType.SQLType), Is.EqualTo(typeof(string))); + Assert.That(foreignName.DataType?.GetLengthIfString(), Is.EqualTo(7)); + Assert.That(syntaxHelper.TypeTranslater.GetCSharpTypeForSQLDBType(foreignName.DataType?.SQLType), Is.EqualTo(typeof(string))); Assert.That(foreignName.IsPrimaryKey); }); @@ -122,7 +123,7 @@ [new DatabaseColumnRequest("Name", "VARCHAR2(10)")] table.Drop(); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void CreateSimpleTable_VarcharTypeCorrect(DatabaseType type) { var db = GetTestDatabase(type); @@ -160,7 +161,7 @@ public void CreateSimpleTable_VarcharTypeCorrect(DatabaseType type) Assert.That(table.Exists(), Is.False); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void CreateTable_PrimaryKey_FromDataTable(DatabaseType databaseType) { var database = GetTestDatabase(databaseType); @@ -175,7 +176,7 @@ public void CreateTable_PrimaryKey_FromDataTable(DatabaseType databaseType) Assert.That(table.DiscoverColumn("Name").IsPrimaryKey); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void CreateTable_PrimaryKey_FromColumnRequest(DatabaseType databaseType) { var database = GetTestDatabase(databaseType); @@ -196,7 +197,7 @@ public void CreateTable_PrimaryKey_FromColumnRequest(DatabaseType databaseType) [TestCase(DatabaseType.MicrosoftSQLServer, "Latin1_General_CS_AS_KS_WS")] [TestCase(DatabaseType.MySql, "latin1_german1_ci")] - [TestCase(DatabaseType.PostgreSql,"de-DE-x-icu")] + [TestCase(DatabaseType.PostgreSql, "de-DE-x-icu")] //[TestCase(DatabaseType.Oracle, "BINARY_CI")] //Requires 12.2+ oracle https://www.experts-exchange.com/questions/29102764/SQL-Statement-to-create-case-insensitive-columns-and-or-tables-in-Oracle.html public void CreateTable_CollationTest(DatabaseType type, string collation) { @@ -214,13 +215,13 @@ public void CreateTable_CollationTest(DatabaseType type, string collation) Assert.That(tbl.DiscoverColumn("Name").Collation, Is.EqualTo(collation)); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void CreateTable_BoolStrings(DatabaseType type) { var db = GetTestDatabase(type); using var dt = new DataTable(); dt.TableName = "MyTable"; - dt.Columns.Add("MyBoolCol",typeof(bool)); + dt.Columns.Add("MyBoolCol", typeof(bool)); dt.Rows.Add("true"); var tbl = db.CreateTable("MyTable", dt); @@ -305,12 +306,12 @@ public void Test_OracleBit_IsNotStringAnyMore() [TestCase(DatabaseType.MicrosoftSQLServer, "Æther")] [TestCase(DatabaseType.MicrosoftSQLServer, "乗")] [TestCase(DatabaseType.Oracle, "didn’t")] - [TestCase(DatabaseType.Oracle,"Æther")] + [TestCase(DatabaseType.Oracle, "Æther")] [TestCase(DatabaseType.Oracle, "乗")] //[TestCase(DatabaseType.MySql, "didn’t")] //[TestCase(DatabaseType.MySql, "Æther")] //[TestCase(DatabaseType.MySql,"乗")] - public void Test_CreateTable_UnicodeStrings(DatabaseType type,string testString) + public void Test_CreateTable_UnicodeStrings(DatabaseType type, string testString) { var db = GetTestDatabase(type); @@ -318,13 +319,13 @@ public void Test_CreateTable_UnicodeStrings(DatabaseType type,string testString) dt.Columns.Add("Yay"); dt.Rows.Add(testString); - var table = db.CreateTable("GoGo",dt); + var table = db.CreateTable("GoGo", dt); //find the table column created var col = table.DiscoverColumn("Yay"); //value fetched from database should match the one inserted - var dbValue = (string) table.GetDataTable().Rows[0][0]; + var dbValue = (string)table.GetDataTable().Rows[0][0]; Assert.That(dbValue, Is.EqualTo(testString)); table.Drop(); @@ -337,7 +338,7 @@ public void Test_CreateTable_UnicodeStrings(DatabaseType type,string testString) Assert.That(comp.Guess.Unicode); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void Test_CreateTable_UnicodeNames(DatabaseType dbType) { var db = GetTestDatabase(dbType); @@ -359,11 +360,11 @@ public void Test_CreateTable_UnicodeNames(DatabaseType dbType) var col = table.DiscoverColumn("微笑"); Assert.That(col.GetRuntimeName(), Is.EqualTo("微笑")); - table.Insert(new Dictionary {{ "微笑","10" } }); + table.Insert(new Dictionary { { "微笑", "10" } }); Assert.That(table.GetRowCount(), Is.EqualTo(2)); - table.Insert(new Dictionary {{ col,"11" } }); + table.Insert(new Dictionary { { col, "11" } }); Assert.That(table.GetRowCount(), Is.EqualTo(3)); @@ -371,7 +372,7 @@ public void Test_CreateTable_UnicodeNames(DatabaseType dbType) dt2.Columns.Add("微笑"); dt2.Rows.Add(23); - using(var bulk = table.BeginBulkInsert()) + using (var bulk = table.BeginBulkInsert()) bulk.Upload(dt2); Assert.That(table.GetRowCount(), Is.EqualTo(4)); @@ -401,22 +402,22 @@ public void Test_CreateTable_TF(DatabaseType dbType) tbl.Drop(); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void Test_CreateTable_DoNotRetype(DatabaseType dbType) { //T and F is normally True and False. If you want to keep it as a string set DoNotRetype var db = GetTestDatabase(dbType); using var dt = new DataTable(); - dt.Columns.Add("Hb"); + var hb = dt.Columns.Add("Hb"); dt.Rows.Add("T"); dt.Rows.Add("F"); //do not retype string to bool - dt.Columns["Hb"].SetDoNotReType(true); + hb.SetDoNotReType(true); var tbl = db.CreateTable("T1", dt); - Assert.That(tbl.DiscoverColumn("Hb").DataType.GetCSharpDataType(), Is.EqualTo(typeof(string))); + Assert.That(tbl.DiscoverColumn("Hb").DataType?.GetCSharpDataType(), Is.EqualTo(typeof(string))); var dt2 = tbl.GetDataTable(); var values = dt2.Rows.Cast().Select(static c => (string)c[0]).ToArray(); @@ -438,17 +439,17 @@ public void Test_DataTableClone_ExtendedProperties() //the default Type for a DataColumn is string Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string))); - dt.Columns["C1"]?.ExtendedProperties.Add("ff",true); + dt.Columns["C1"]?.ExtendedProperties.Add("ff", true); var dt2 = dt.Clone(); - Assert.That(dt2.Columns["C1"]?.ExtendedProperties.ContainsKey("ff")??false); + Assert.That(dt2.Columns["C1"]?.ExtendedProperties.ContainsKey("ff") ?? false); } [Test] public void Test_GetDoNotRetype_OnlyStringColumns() { using var dt = new DataTable(); - dt.Columns.Add("C1",typeof(int)); + dt.Columns.Add("C1", typeof(int)); dt.SetDoNotReType(true); @@ -464,17 +465,17 @@ public void Test_GetDoNotRetype_OnlyStringColumns() /// /// Tests how CreateTable interacts with of type Object /// - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void CreateTable_ObjectColumns_StringContent(DatabaseType dbType) { //T and F is normally True and False. If you want to keep it as a string set DoNotRetype var db = GetTestDatabase(dbType); var dt = new DataTable(); - dt.Columns.Add("Hb",typeof(object)); + dt.Columns.Add("Hb", typeof(object)); dt.Rows.Add("T"); dt.Rows.Add("F"); - var ex = Assert.Throws(()=>db.CreateTable("T1", dt)); + var ex = Assert.Throws(() => db.CreateTable("T1", dt)); Assert.That(ex?.Message, Does.Contain("System.Object")); @@ -484,8 +485,8 @@ public void CreateTable_ObjectColumns_StringContent(DatabaseType dbType) /// Tests how we can customize how "T" and "F" etc are interpreted (either as boolean true/false or as string). This test /// uses the static defaults in . /// - [TestCase(DatabaseType.MicrosoftSQLServer,true)] - [TestCase(DatabaseType.MicrosoftSQLServer,false)] + [TestCase(DatabaseType.MicrosoftSQLServer, true)] + [TestCase(DatabaseType.MicrosoftSQLServer, false)] public void CreateTable_GuessSettings_StaticDefaults_TF(DatabaseType dbType, bool treatAsBoolean) { //T and F is normally True and False. If you want to keep it as a string set DoNotRetype @@ -517,7 +518,7 @@ public void CreateTable_GuessSettings_StaticDefaults_TF(DatabaseType dbType, boo } } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypes))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypes))] public void TestSomething(DatabaseType dbType) { var db = GetTestDatabase(dbType); @@ -555,8 +556,8 @@ public void TestSomething(DatabaseType dbType) /// Tests how we can customize how "T" and "F" etc are interpreted (either as boolean true/false or as string). This test /// uses the injection. /// - [TestCase(DatabaseType.MicrosoftSQLServer,true)] - [TestCase(DatabaseType.MicrosoftSQLServer,false)] + [TestCase(DatabaseType.MicrosoftSQLServer, true)] + [TestCase(DatabaseType.MicrosoftSQLServer, false)] public void CreateTable_GuessSettings_InArgs_TF(DatabaseType dbType, bool treatAsBoolean) { //T and F is normally True and False. If you want to keep it as a string set DoNotRetype @@ -586,7 +587,7 @@ public void CreateTable_GuessSettings_InArgs_TF(DatabaseType dbType, bool treatA }); } - [TestCaseSource(typeof(All),nameof(All.DatabaseTypesWithBoolFlags))] + [TestCaseSource(typeof(All), nameof(All.DatabaseTypesWithBoolFlags))] public void CreateTable_GuessSettings_ExplicitDateTimeFormat(DatabaseType dbType, bool useCustomDate) { //Values like 013020 would normally be treated as string data (due to leading zero) but maybe the user wants it to be a date? @@ -595,7 +596,7 @@ public void CreateTable_GuessSettings_ExplicitDateTimeFormat(DatabaseType dbType dt.Columns.Add("DateCol"); dt.Rows.Add("013020"); - var args = new CreateTableArgs(db,"Hb",null,dt,false); + var args = new CreateTableArgs(db, "Hb", null, dt, false); Assert.Multiple(() => { Assert.That(GuessSettingsFactory.Defaults.ExplicitDateFormats, Is.EqualTo(args.GuessSettings.ExplicitDateFormats), "Default should match the static default"); @@ -603,15 +604,15 @@ public void CreateTable_GuessSettings_ExplicitDateTimeFormat(DatabaseType dbType }); //change the args settings to treat this date format - args.GuessSettings.ExplicitDateFormats = useCustomDate ? ["MMddyy"] :null; + args.GuessSettings.ExplicitDateFormats = useCustomDate ? ["MMddyy"] : null; var tbl = db.CreateTable(args); var col = tbl.DiscoverColumn("DateCol"); - Assert.That(col.DataType.GetCSharpDataType(), Is.EqualTo(useCustomDate ? typeof(DateTime): typeof(string))); + Assert.That(col.DataType.GetCSharpDataType(), Is.EqualTo(useCustomDate ? typeof(DateTime) : typeof(string))); var dtDown = tbl.GetDataTable(); - Assert.That(dtDown.Rows[0][0], Is.EqualTo(useCustomDate ? new DateTime(2020,01,30): "013020")); + Assert.That(dtDown.Rows[0][0], Is.EqualTo(useCustomDate ? new DateTime(2020, 01, 30) : "013020")); } [Test] public void GuessSettings_CopyProperties()