Skip to content

Commit

Permalink
Merge develop into main for 3.2.0 release (#259)
Browse files Browse the repository at this point in the history
v3.2.0 release
  • Loading branch information
jas88 committed Mar 4, 2024
1 parent d27405c commit cf8c769
Show file tree
Hide file tree
Showing 29 changed files with 290 additions and 250 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-core.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: .NET Core
name: Build and test

on:
push
Expand Down
18 changes: 15 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion FAnsiSql/Connections/IManagedConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface IManagedConnection : IDisposable
/// <summary>
/// Optional - transaction being run (See <see cref="DiscoveredServer.BeginNewTransactedConnection"/>. If this is not null then <see cref="Transaction"/> should also be not null.
/// </summary>
IManagedTransaction ManagedTransaction { get; }
IManagedTransaction? ManagedTransaction { get; }

/// <summary>
/// True to close the connection in the Dispose step. If <see cref="IManagedConnection"/> opened the connection itself during construction then this flag will default
Expand Down
6 changes: 3 additions & 3 deletions FAnsiSql/Connections/ManagedConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public sealed class ManagedConnection : IManagedConnection
public DbTransaction? Transaction { get; }

/// <inheritdoc/>
public IManagedTransaction ManagedTransaction { get; }
public IManagedTransaction? ManagedTransaction { get; }

/// <inheritdoc/>
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);
Expand All @@ -37,7 +37,7 @@ internal ManagedConnection(DiscoveredServer discoveredServer, IManagedTransactio
Connection.Open();
}

public ManagedConnection Clone() => (ManagedConnection) MemberwiseClone();
public ManagedConnection Clone() => (ManagedConnection)MemberwiseClone();

/// <summary>
/// Closes and disposes the DbConnection unless this class is part of an <see cref="IManagedTransaction"/>
Expand Down
41 changes: 24 additions & 17 deletions FAnsiSql/Discovery/BulkCopy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
using System.Data;
using System.Globalization;
using System.Linq;
using System.Threading;
using FAnsi.Connections;
using TypeGuesser;
using TypeGuesser.Deciders;

namespace FAnsi.Discovery;

/// <inheritdoc/>
public abstract class BulkCopy:IBulkCopy
public abstract class BulkCopy : IBulkCopy
{
public CultureInfo Culture { get; }

Expand All @@ -28,7 +29,9 @@ public abstract class BulkCopy:IBulkCopy
/// The cached columns found on the <see cref="TargetTable"/>. If you alter the table midway through a bulk insert you must
/// call <see cref="InvalidateTableSchema"/> to refresh this.
/// </summary>
protected DiscoveredColumn[] TargetTableColumns;
protected DiscoveredColumn[] TargetTableColumns => _targetTableColumns.Value;

private Lazy<DiscoveredColumn[]> _targetTableColumns;

/// <summary>
/// When calling GetMapping if there are DataColumns in the input table that you are trying to bulk insert that are not matched
Expand All @@ -55,7 +58,9 @@ protected BulkCopy(DiscoveredTable targetTable, IManagedConnection connection, C
Culture = culture;
TargetTable = targetTable;
Connection = connection;
InvalidateTableSchema();
_targetTableColumns = new Lazy<DiscoveredColumn[]>(
() => TargetTable.DiscoverColumns(Connection.ManagedTransaction),
LazyThreadSafetyMode.ExecutionAndPublication);
AllowUnmatchedInputColumns = false;
DateTimeDecider = new DateTimeTypeDecider(culture);
}
Expand All @@ -68,7 +73,9 @@ protected BulkCopy(DiscoveredTable targetTable, IManagedConnection connection, C
/// </summary>
public void InvalidateTableSchema()
{
TargetTableColumns = TargetTable.DiscoverColumns(Connection.ManagedTransaction);
_targetTableColumns = new Lazy<DiscoveredColumn[]>(
() => TargetTable.DiscoverColumns(Connection.ManagedTransaction),
LazyThreadSafetyMode.ExecutionAndPublication);
}

/// <summary>
Expand Down Expand Up @@ -100,25 +107,25 @@ public virtual int Upload(DataTable dt)
/// <param name="dt"></param>
protected void ConvertStringTypesToHardTypes(DataTable dt)
{
var dict = GetMapping(dt.Columns.Cast<DataColumn>(),out _);
var dict = GetMapping(dt.Columns.Cast<DataColumn>(), out _);

var factory = new TypeDeciderFactory(Culture);

//These are the problematic Types
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)
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -198,5 +205,5 @@ protected Dictionary<DataColumn, DiscoveredColumn> GetMapping(IEnumerable<DataCo
/// </summary>
/// <param name="inputColumns"></param>
/// <returns></returns>
protected Dictionary<DataColumn,DiscoveredColumn> GetMapping(IEnumerable<DataColumn> inputColumns) => GetMapping(inputColumns, out _);
protected Dictionary<DataColumn, DiscoveredColumn> GetMapping(IEnumerable<DataColumn> inputColumns) => GetMapping(inputColumns, out _);
}
15 changes: 6 additions & 9 deletions FAnsiSql/Discovery/Constraints/DiscoveredRelationship.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,22 @@ public sealed class DiscoveredRelationship(string fkName, DiscoveredTable pkTabl
/// </summary>
public CascadeRule CascadeDelete { get; private set; } = deleteRule;

private DiscoveredColumn[] _pkColumns;
private DiscoveredColumn[] _fkColumns;
private DiscoveredColumn[]? _pkColumns;
private DiscoveredColumn[]? _fkColumns;

/// <summary>
/// Discovers and adds the provided pair to <see cref="Keys"/>. Column names must be members of <see cref="PrimaryKeyTable"/> and <see cref="ForeignKeyTable"/> (respectively)
/// </summary>
/// <param name="primaryKeyCol"></param>
/// <param name="foreignKeyCol"></param>
/// <param name="transaction"></param>
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))
);
}
Expand Down
16 changes: 9 additions & 7 deletions FAnsiSql/Discovery/DiscoveredColumn.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FAnsi.Discovery.QuerySyntax;
using System.Diagnostics.CodeAnalysis;
using FAnsi.Discovery.QuerySyntax;
using FAnsi.Naming;
using TypeGuesser;

Expand Down Expand Up @@ -29,7 +30,7 @@ public sealed class DiscoveredColumn(DiscoveredTable table, string name, bool al
/// <summary>
/// True if the column allows rows with nulls in this column
/// </summary>
public bool AllowNulls { get; } = allowsNulls;
public readonly bool AllowNulls = allowsNulls;

/// <summary>
/// True if the column is part of the <see cref="Table"/> primary key (a primary key can consist of mulitple columns)
Expand All @@ -50,12 +51,12 @@ public sealed class DiscoveredColumn(DiscoveredTable table, string name, bool al
/// <summary>
/// The data type of the column found (includes String Length and Scale/Precision).
/// </summary>
public DiscoveredDataType DataType { get; set; }
public DiscoveredDataType? DataType { get; set; }

/// <summary>
/// The character set of the column (if char)
/// </summary>
public string Format { get; set; }
public string? Format { get; set; }

private readonly string _name = name;
private readonly IQuerySyntaxHelper _querySyntaxHelper = table.Database.Server.GetQuerySyntaxHelper();
Expand All @@ -64,13 +65,14 @@ public sealed class DiscoveredColumn(DiscoveredTable table, string name, bool al
/// The unqualified name of the column e.g. "MyCol"
/// </summary>
/// <returns></returns>
public string? GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_name);
public string GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_name);

/// <summary>
/// The fully qualified name of the column e.g. [MyDb].dbo.[MyTable].[MyCol] or `MyDb`.`MyCol`
/// </summary>
/// <returns></returns>
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);


/// <summary>
Expand Down Expand Up @@ -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 <see cref="GetFullyQualifiedName()"/> to return the full name including table/database/schema.
/// </summary>
/// <returns></returns>
public string GetWrappedName() => Table.GetQuerySyntaxHelper().EnsureWrapped(GetRuntimeName());
public string? GetWrappedName() => Table.GetQuerySyntaxHelper().EnsureWrapped(GetRuntimeName());
}
2 changes: 1 addition & 1 deletion FAnsiSql/Discovery/DiscoveredDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public IEnumerable<DiscoveredTableValuedFunction> DiscoverTableValuedFunctions(I
/// Returns the name of the database without any qualifiers
/// </summary>
/// <returns></returns>
public string? GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_database);
public string GetRuntimeName() => _querySyntaxHelper.GetRuntimeName(_database);

/// <summary>
/// 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]").
Expand Down
2 changes: 1 addition & 1 deletion FAnsiSql/Discovery/DiscoveredDatabaseHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction? t
/// <param name="transaction"></param>
/// <param name="performanceFigures">Line number the batch started at and the time it took to complete it</param>
/// <param name="timeout">Timeout in seconds to run each batch in the <paramref name="sql"/></param>
public void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction transaction, out Dictionary<int, Stopwatch> performanceFigures, int timeout = 30)
public void ExecuteBatchNonQuery(string sql, DbConnection conn, DbTransaction? transaction, out Dictionary<int, Stopwatch> performanceFigures, int timeout = 30)
{
performanceFigures = [];

Expand Down
Loading

0 comments on commit cf8c769

Please sign in to comment.