Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ env:
TEST_PROJECT: TeamTools.TSQL.LinterTests
PUBLISH_PROJECT_PATH: TeamTools.TSQL.Linter/TeamTools.TSQL.Linter.csproj
OUTPUT_PATH: ${{ github.workspace }}/.bin/
PUBLISH_PATH: ${{ github.workspace }}/.pub/
PUBLISH_PATH: ${{ github.workspace }}/.pub
NUPKG_PATH: ${{ github.workspace }}\.nupkgs

jobs:
semver:
Expand Down Expand Up @@ -89,6 +90,7 @@ jobs:
run: dotnet --info

- name: Cache NuGet packages
id: cache-nugets
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/packages
Expand Down Expand Up @@ -143,33 +145,77 @@ jobs:
-p:BaseOutputPath="${{ env.OUTPUT_PATH }}"
-p:PublishDir="${{ env.PUBLISH_PATH }}/Release/net8.0"

- name: Pack
if: ${{ matrix.publish }}
run: >
dotnet pack "${{ github.workspace}}\TeamTools.TSQL.Common\TeamTools.Common.Linting.csproj"
--no-build
--output "${{ env.NUPKG_PATH }}"
-p:Configuration=Release
-p:BaseOutputPath="${{ env.OUTPUT_PATH }}"
-p:VersionPrefix=${{ needs.semver.outputs.next-version }}
&&
dotnet pack "${{ github.workspace}}\TeamTools.TSQL.ExpressionEvaluator\TeamTools.TSQL.ExpressionEvaluator.csproj"
--no-build
--output "${{ env.NUPKG_PATH }}"
-p:Configuration=Release
-p:BaseOutputPath="${{ env.OUTPUT_PATH }}"
-p:VersionPrefix=${{ needs.semver.outputs.next-version }}
&&
dotnet pack "${{ env.PUBLISH_PROJECT_PATH }}"
--no-build
--output "${{ env.NUPKG_PATH }}"
-p:Configuration=Release
-p:BaseOutputPath="${{ env.OUTPUT_PATH }}"
-p:VersionPrefix=${{ needs.semver.outputs.next-version }}
-p:Authors="Ivan Starostin et al"
-p:Copyright="2019- (c) Ivan Starostin"
-p:RepositoryCommit=${{ github.sha }}
-p:RepositoryUrl="${{ github.repositoryUrl }}"
-p:RepositoryType=git
-p:RepositoryBranch="${{ github.ref_name }}"

- name: Upload build artifacts 2.0
uses: actions/upload-artifact@v4
if: ${{ matrix.publish }}
with:
name: ${{ env.PRODUCT_NAME }}-${{ needs.semver.outputs.next-version }}-${{ matrix.configuration }}-netstandard2.0
path: ${{ env.PUBLISH_PATH }}/${{ matrix.configuration }}/netstandard2.0
if-no-files-found: error

- name: Upload build artifacts 6.0
uses: actions/upload-artifact@v4
if: ${{ matrix.publish }}
with:
name: ${{ env.PRODUCT_NAME }}-${{ needs.semver.outputs.next-version }}-${{ matrix.configuration }}-net6.0
path: ${{ env.PUBLISH_PATH }}/${{ matrix.configuration }}/net6.0
if-no-files-found: error

- name: Upload build artifacts 8.0
uses: actions/upload-artifact@v4
if: ${{ matrix.publish }}
with:
name: ${{ env.PRODUCT_NAME }}-${{ needs.semver.outputs.next-version }}-${{ matrix.configuration }}-net8.0
path: ${{ env.PUBLISH_PATH }}/${{ matrix.configuration }}/net8.0
if-no-files-found: error

- name: Upload nugets
uses: actions/upload-artifact@v4
if: ${{ matrix.publish }}
with:
name: nuget-packages
include-hidden-files: true
if-no-files-found: error
path: |
${{ env.NUPKG_PATH }}\*.nupkg

- name: Upload test bundle
uses: actions/upload-artifact@v4
if: ${{ matrix.configuration == 'Debug' && matrix.os == 'windows-latest' && steps.build.conclusion == 'success' && !cancelled() }}
with:
name: test-bundle
path: ${{ env.OUTPUT_PATH }}/${{ matrix.configuration }}/net8.0
path: ${{ env.OUTPUT_PATH }}${{ matrix.configuration }}/net8.0
if-no-files-found: error

- name: Test
if: ${{ !matrix.coverage }}
Expand All @@ -194,6 +240,7 @@ jobs:
with:
name: test-results
path: TestResults
if-no-files-found: error

- name: Test Report
uses: dorny/test-reporter@v2
Expand All @@ -202,6 +249,7 @@ jobs:
name: NUnit testing ${{ matrix.configuration }} build on ${{ matrix.os }}
path: "**/TestResults/*.trx,*.trx"
reporter: dotnet-trx
if-no-files-found: error

validate-markdown:
runs-on: ubuntu-latest
Expand Down
10 changes: 5 additions & 5 deletions TeamTools.TSQL.Common/TeamTools.Common.Linting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
<!-- Packing options -->
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageOutputPath>../.nupkg</PackageOutputPath>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>./readme.md</PackageReadmeFile>
<PackageTags>StaticCodeAnalysis;CodeQuality;TeamTools</PackageTags>
<PackageOutputPath>../.nupkgs</PackageOutputPath>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>

<!-- Dependencies -->
Expand All @@ -22,7 +21,8 @@

<!-- Manual includes -->
<ItemGroup>
<Content Include="readme.md" Pack="true" PackagePath="\" />
<Content Include="readme.md" Pack="true" PackagePath="" />
<Content Include="$(MSBuildThisFileDirectory)..\LICENSE" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Strings.Designer.cs">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.ArgumentDto;
using TeamTools.TSQL.ExpressionEvaluator.TypeHandling;
using TeamTools.TSQL.ExpressionEvaluator.Values;

namespace TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.Abstractions
{
public abstract class CurrentMomentFunction<TOut> : SqlZeroArgFunctionHandler
where TOut : SqlValue
{
private readonly TimeDetails timeDetails;
private readonly DateDetails dateDetails;

protected CurrentMomentFunction(string funcName, string outputType, TimeDetails timeDetails, DateDetails dateDetails) : base(funcName, outputType)
{
this.timeDetails = timeDetails;
this.dateDetails = dateDetails;
}

protected override SqlValue DoEvaluateResultValue(CallSignature<ZeroArgs> call)
{
var value = call.Context.Converter.ImplicitlyConvert<TOut>(base.DoEvaluateResultValue(call));

if (value is null)
{
return default;
}

var newRange = new SqlDateTimeValueRange(new SqlDateTimeRelativeValue(DateTimeRangeKind.CurrentMoment, timeDetails, dateDetails));

return ApplyNewRange(value, newRange, call.Context.NewSource);
}

protected abstract TOut ApplyNewRange(TOut value, SqlDateTimeValueRange newRange, SqlValueSource src);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.ArgumentDto;
using System.Diagnostics.CodeAnalysis;
using TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.ArgumentDto;
using TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.ArgumentValidators;
using TeamTools.TSQL.ExpressionEvaluator.Evaluation;
using TeamTools.TSQL.ExpressionEvaluator.Properties;
Expand All @@ -15,6 +16,10 @@ protected ExplicitConvertionFunction(string funcName, int requiredArgs = 2) : ba
{
}

protected ExplicitConvertionFunction(string funcName, int minArgs, int maxArgs) : base(funcName, minArgs, maxArgs)
{
}

private static string MsgSourceIsAlreadyOfThatType => Strings.ViolationDetails_RedundantTypeConversionViolation_ValueIsAlreadyOfThisType;

public override bool ValidateArgumentValues(CallSignature<ConvertArgs> call)
Expand All @@ -27,6 +32,15 @@ public override bool ValidateArgumentValues(CallSignature<ConvertArgs> call)
.When(ArgumentIsValue.Validate)
.Then(v => call.ValidatedArgs.SrcValue = v);

if (MaxArgs > 2 && call.RawArgs.Count == 3)
{
ValidationScenario
.For("STYLE", call.RawArgs[2], call.Context)
.When(ArgumentIsValue.Validate)
.And(ArgumentIsValidInt.Validate)
.Then(v => call.ValidatedArgs.ConvertionStyle = v);
}

return ValidationScenario
.For("TYPE", call.RawArgs[1], call.Context)
.When(ArgumentIsType.Validate)
Expand All @@ -43,7 +57,12 @@ protected override SqlValue DoEvaluateResultValue(CallSignature<ConvertArgs> cal
}

if (call.ValidatedArgs.TargetType is SqlStrTypeReference str && str.IsUnicode
&& call.ValidatedArgs.SrcValue is SqlIntTypeValue intValue)
&& (call.ValidatedArgs.SrcValue is SqlIntTypeValue
|| call.ValidatedArgs.SrcValue is SqlBinaryTypeValue
|| call.ValidatedArgs.SrcValue is SqlDecimalTypeValue
|| call.ValidatedArgs.SrcValue is SqlDateTimeValue
|| call.ValidatedArgs.SrcValue is SqlDateOnlyValue
|| call.ValidatedArgs.SrcValue is SqlTimeOnlyValue))
{
// TODO : If this conversion result is used in concatenation with other NVARCHAR strings
// then no violation should be issued.
Expand All @@ -55,7 +74,26 @@ protected override SqlValue DoEvaluateResultValue(CallSignature<ConvertArgs> cal
}
else if (call.ValidatedArgs.SrcValue.SourceKind == SqlValueSourceKind.Literal && call.ValidatedArgs.SrcValue.IsPreciseValue)
{
msg = string.Format(Strings.ViolationDetails_NumbersHaveNoUnicode_LiteralValueIsNumber, FunctionName, intValue.Value.ToString());
string numericSourceValue = null;

if (call.ValidatedArgs.SrcValue is SqlIntTypeValue intValue)
{
numericSourceValue = intValue.Value.ToString();
}
else if (call.ValidatedArgs.SrcValue is SqlDateTimeValue datetimeValue)
{
numericSourceValue = datetimeValue.Value.ToString();
}
else if (call.ValidatedArgs.SrcValue is SqlDateOnlyValue dateValue)
{
numericSourceValue = dateValue.Value.ToString();
}
else if (call.ValidatedArgs.SrcValue is SqlTimeOnlyValue timeValue)
{
numericSourceValue = timeValue.Value.ToString();
}

msg = string.Format(Strings.ViolationDetails_NumbersHaveNoUnicode_LiteralValueIsNumber, FunctionName, numericSourceValue);
}
else
{
Expand Down Expand Up @@ -90,11 +128,14 @@ private static void RegisterRedundantConversionViolation(SqlValue srcValue, Eval
context.Violations.RegisterViolation(new RedundantTypeConversionViolation(violationMessage, context.NewSource));
}

[ExcludeFromCodeCoverage]
public class ConvertArgs
{
public SqlTypeReference TargetType { get; set; }

public SqlValue SrcValue { get; set; }

public SqlIntTypeValue ConvertionStyle { get; set; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.ArgumentDto;
using TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.ArgumentValidators;
using TeamTools.TSQL.ExpressionEvaluator.Routines;
using TeamTools.TSQL.ExpressionEvaluator.TypeHandling;
using TeamTools.TSQL.ExpressionEvaluator.Values;

namespace TeamTools.TSQL.ExpressionEvaluator.BuiltInFunctions.Abstractions
{
public abstract class RoundingFunction<TArgs> : SqlGenericFunctionHandler<TArgs>
where TArgs : RoundingFunction<TArgs>.RoundingFunctionArgs, new()
{
private static readonly int DefaultRequiredArgumentCount = 1;
private static readonly string FallbackResultType = TSqlDomainAttributes.Types.Int;

protected RoundingFunction(string funcName, int minArgs, int maxArgs) : base(funcName, minArgs, maxArgs)
{
}

protected RoundingFunction(string funcName) : base(funcName, DefaultRequiredArgumentCount)
{
}

public override bool ValidateArgumentValues(CallSignature<TArgs> call)
{
return ValidationScenario
.For("SOURCE_VALUE", call.RawArgs[0], call.Context)
.When(ArgumentIsValue.Validate)
.Then(n => call.ValidatedArgs.SourceValue = n);
}

protected override string DoEvaluateResultType(CallSignature<TArgs> call)
{
if (call.ValidatedArgs.SourceValue is SqlIntTypeValue i)
{
// For int types output type is always INT
// TODO : except for BIT - it is FLOAT
// docs: https://learn.microsoft.com/en-us/sql/t-sql/functions/ceiling-transact-sql?view=sql-server-ver17#return-types
return i.TypeHandler.IntValueFactory.FallbackTypeName;
}

if (call.ValidatedArgs.SourceValue is SqlBigIntTypeValue
|| call.ValidatedArgs.SourceValue is SqlDecimalTypeValue)
{
return call.ValidatedArgs.SourceValue.TypeName;
}

return FallbackResultType;
}

protected override SqlValue DoEvaluateResultValue(CallSignature<TArgs> call)
{
// TODO : could be int or bigint
var value = call.Context.Converter.ImplicitlyConvert<SqlDecimalTypeValue>(base.DoEvaluateResultValue(call));

if (value is null)
{
return value;
}

if (call.ValidatedArgs.SourceValue.IsNull)
{
return call.ResultTypeHandler.ValueFactory.NewNull(call.Context.Node);
}

if (call.ValidatedArgs.SourceValue is SqlIntTypeValue i)
{
call.Context.RedundantCall("Source is a number without fractions");

// the result is the same as source
return value.ChangeTo((decimal)i.Value, call.Context.NewSource);
}

if (call.ValidatedArgs.SourceValue is SqlBigIntTypeValue bi)
{
call.Context.RedundantCall("Source is a number without fractions");

// the result is the same as source
return value.ChangeTo((decimal)bi.Value, call.Context.NewSource);
}

if (call.ValidatedArgs.SourceValue is SqlDecimalTypeValue decSrc)
{
if (decSrc.EstimatedSize.Scale == 0)
{
call.Context.RedundantCall("Source is a number without fractions");
}

if (decSrc.IsPreciseValue)
{
return value.ChangeTo(ProduceRounding(decSrc.Value, call.ValidatedArgs), call.Context.NewSource);
}
}

// TODO : return precise value if possible or a limited value range
return value;
}

protected abstract decimal ProduceRounding(decimal value, TArgs arguments);

public class RoundingFunctionArgs
{
public SqlValue SourceValue { get; set; }
}
}
}
Loading
Loading