Skip to content

Commit

Permalink
Import source
Browse files Browse the repository at this point in the history
  • Loading branch information
kasthack committed Sep 27, 2024
1 parent 9f3bd33 commit 0000037
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 17 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: .NET

defaults:
run:
working-directory: src
working-directory: src/dapper
on:
push:
branches: [ master ]
Expand All @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: ['7.0.x' ]
dotnet-version: ['8.0.x' ]
steps:
- uses: actions/checkout@v3
- name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Nuget push

defaults:
run:
working-directory: src/dapper
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: ['6.0.x' ]
steps:
- uses: actions/checkout@v3
- name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Push
env:
NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}
run: dotnet nuget push -s https://api.nuget.org/v3/index.json -k $NUGET_TOKEN **/*.nupkg
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
# kasthack.[project]
# kasthack.AutoTagging

## What

[![Github All Releases](https://img.shields.io/github/downloads/kasthack-labs/kasthack.[project]/total.svg)](https://github.com/kasthack-labs/kasthack.[project]/releases/latest)
[![GitHub release](https://img.shields.io/github/release/kasthack-labs/kasthack.[project].svg)](https://github.com/kasthack-labs/kasthack.[project]/releases/latest)
[![license](https://img.shields.io/github/license/kasthack-labs/kasthack.[project].svg)](LICENSE)
[![.NET Status](https://github.com/kasthack-labs/kasthack.[project]/workflows/.NET/badge.svg)](https://github.com/kasthack-labs/kasthack.[project]/actions?query=workflow%3A.NET)
[![Nuget](https://img.shields.io/nuget/v/kasthack.roi.svg)](https://www.nuget.org/packages/kasthack.autotagging.dapperproxy/)
[![NuGet](https://img.shields.io/nuget/dt/kasthack.roi.svg)](https://www.nuget.org/packages/kasthack.autotagging.dapperproxy/)
[![GitHub release](https://img.shields.io/github/release/kasthack-labs/kasthack.AutoTagging.svg)](https://github.com/kasthack-labs/kasthack.AutoTagging/releases/latest)
[![license](https://img.shields.io/github/license/kasthack-labs/kasthack.AutoTagging.svg)](LICENSE)
[![.NET Status](https://github.com/kasthack-labs/kasthack.AutoTagging/workflows/.NET/badge.svg)](https://github.com/kasthack-labs/kasthack.AutoTagging/actions?query=workflow%3A.NET)
[![Patreon pledges](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dkasthack%26type%3Dpledges&style=flat)](https://patreon.com/kasthack)
[![Patreon patrons](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dkasthack%26type%3Dpatrons&style=flat)](https://patreon.com/kasthack)

Automatic SQL tagging proxies for .NET:

* Dapper is currently supported
* I'm working on custom DbConnection proxy.

## Why does this exist

I'm tired of having to set up new repos and having slightly different settings.
I needed to tag database queries in an existing project without rewriting tons of code.

## Usage

Use this repo as a template when creating a repo on github.
* Install nuget package `kasthack.Autotagging.DapperProxy`
* Remove `using Dapper` from your source.
* Add `using kasthack.Autotagging.DapperProxy` to source files / global using.
* (optionally) Set `TaggingSqlMapper.AppName`
* Boom! All database queries sent through dapper start with the comment

```
Usage:
kasthack.[project] [options]
```sql
-- App: {app_name}
-- File: {callerFile}:{callerLine}
-- Method: {callerMethod}

Options:
--version Show version information
-?, -h, --help Show help and usage information
<your query>
```

* Now your DBAs and devops can easily deal detect sources of problematic queries.
4 changes: 2 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<NoWarn>SA1633;SA1600;SA1601;CS1591;SA1300</NoWarn>
Expand All @@ -17,7 +17,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\..\..\README.md" Pack="true" PackagePath="\"/>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down
36 changes: 36 additions & 0 deletions src/dapper/kasthack.Autotagging.Dapper.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kasthack.Autotagging.DapperProxy", "kasthack.Autotagging.DapperProxy\kasthack.Autotagging.DapperProxy.csproj", "{2F5370C8-78DB-4D2E-9520-ED522A778F6B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kasthack.Autotagging.DapperProxySourceGenerator", "kasthack.Autotagging.DapperProxySourceGenerator\kasthack.Autotagging.DapperProxySourceGenerator.csproj", "{3B8CF856-C78D-4CE4-B7B7-DA95FDFC8253}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4AE6FF4E-598F-4E37-8456-889ECEFB34D3}"
ProjectSection(SolutionItems) = preProject
..\Directory.Build.props = ..\Directory.Build.props
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2F5370C8-78DB-4D2E-9520-ED522A778F6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F5370C8-78DB-4D2E-9520-ED522A778F6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F5370C8-78DB-4D2E-9520-ED522A778F6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F5370C8-78DB-4D2E-9520-ED522A778F6B}.Release|Any CPU.Build.0 = Release|Any CPU
{3B8CF856-C78D-4CE4-B7B7-DA95FDFC8253}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B8CF856-C78D-4CE4-B7B7-DA95FDFC8253}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B8CF856-C78D-4CE4-B7B7-DA95FDFC8253}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B8CF856-C78D-4CE4-B7B7-DA95FDFC8253}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6FCC3F58-552A-4F49-8818-EC500EBDBEA4}
EndGlobalSection
EndGlobal
20 changes: 20 additions & 0 deletions src/dapper/kasthack.Autotagging.DapperProxy/QueryTagger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace kasthack.Autotagging.DapperProxy;

internal static class QueryTagger
{
private const string appTagPrefix = "-- App:";
public static string? ApplyTag(string query, string appName, string callerMethod, string callerFile, int callerLine)
{
if (query == null || query.StartsWith(appTagPrefix, StringComparison.OrdinalIgnoreCase))
{
return query;
}

return $"""
{appTagPrefix} {appName}
-- File: {callerFile}:{callerLine}
-- Method: {callerMethod}
{query}
""";
}
}
31 changes: 31 additions & 0 deletions src/dapper/kasthack.Autotagging.DapperProxy/TaggingSqlMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace kasthack.Autotagging.DapperProxy;
using System.Data;
using System.Reflection;

/// <summary>
/// Proxy to Dapper.SqlMapper that adds query tags to every request.
/// </summary>
public static partial class TaggingSqlMapper
{
private static void DummyCheck()
{
// checks if generator worked
_ = TaggingSqlMapper.Dummy;
}

/// <summary>
/// Application name for tagging. Defaults to entry assembly name.
/// </summary>
public static string AppName { get; set; } = Assembly.GetEntryAssembly().GetName().Name;

private static string? ApplyTag(string sql, string callerMethod, string callerFile, int callerLine)
{
return QueryTagger.ApplyTag(sql, AppName, callerMethod, callerFile, callerLine);
}

private static Dapper.CommandDefinition ApplyTag(Dapper.CommandDefinition command, string callerMethod, string callerFile, int callerLine)
{
var sql = ApplyTag(command.CommandText, callerMethod, callerFile, callerLine)!;
return new Dapper.CommandDefinition(sql, command.Parameters, command.Transaction, command.CommandTimeout, command.CommandType, command.Flags, command.CancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<EnforceExtendedAnalyzers>true</EnforceExtendedAnalyzers>
<PackageVersion>0.1.3</PackageVersion>
<PackageDescription>Automatic query tagging extension for dapper</PackageDescription>
<PackageTags>kasthack dapper query-tagging telemetry sql</PackageTags>
<PackageProjectUrl>https://github.com/kasthack-labs/kasthack.Autotagging</PackageProjectUrl>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\kasthack.Autotagging.DapperProxySourceGenerator\kasthack.Autotagging.DapperProxySourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
<!--
https://stackoverflow.com/a/77586784
-->
<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Dapper" Version="2.1.35" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
namespace kasthack.Autotagging.DapperProxySourceGenerator;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using Dapper;

using Microsoft.CodeAnalysis;

using Namotion.Reflection;

[Generator]
public class DapperSourceGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext _) { }

Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)


public void Execute(GeneratorExecutionContext context)
{
Type mapper = typeof(SqlMapper);
System.Collections.Generic.List<MethodInfo> extensionMethods = mapper.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false))
.ToList();

var docs = new Dictionary<string, string>();
List<string> methods = extensionMethods
.Select(m =>
{
string extraArgs = @", [CallerMemberName] string callerMethod = null, [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = -1";
ParameterInfo[] targetMethodParameters = m.GetParameters();
if (!targetMethodParameters.Any(a => a.Name == "cnn"))
{
return null;
}
ParameterInfo taggedParameter = targetMethodParameters
.SingleOrDefault(a => a.Name is "sql" or "command");
if (taggedParameter == null)
return null;

Check warning on line 43 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 43 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

System.Collections.Generic.IEnumerable<string> signatureParameters = targetMethodParameters
.Select((p, idx) => $"{(idx == 0 ? "this " : string.Empty)}{this.GetGenericTypeName(p.ParameterType)} {p.Name}{(!p.HasDefaultValue ? string.Empty : $" = {this.DefaultParam(p)}")}");
string returnTypeName = this.GetGenericTypeName(m.ReturnType);
System.Collections.Generic.IEnumerable<string> signatureGenericArguments = m.GetGenericArguments().Select(a => this.Name(a));
string genericArgString = signatureGenericArguments.Any() ? $"<{string.Join(", ", signatureGenericArguments)}>" : string.Empty;
string signature = $"public static {returnTypeName} {m.Name}{genericArgString}({string.Join(", ", signatureParameters)}{extraArgs})";
var docTag = m.GetXmlDocsElement()?.ToString();
if (docTag == null)
{
docs.TryGetValue(m.Name, out docTag);
}
else
{
docs[m.Name] = docTag;
}
var docresult = string.Join(
"\n",
(
docTag
?.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) ?? ["//no doc"]

Check warning on line 66 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 66 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

)

Check warning on line 67 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (6.0.x)

Check warning on line 67 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

.Concat([
@"<param name=""callerMethod"">Auto-captured method for tagging</param>",
@"<param name=""callerFile"">Auto-captured file for tagging</param>",
@"<param name=""callerLine"">Auto-captured line for tagging</param>",
])
.Select(a => $" ///{a.Trim()}"));
string methodBody = $$"""
{{docresult}}
{{signature}}
{
{{taggedParameter.Name}} = ApplyTag({{taggedParameter.Name}}, callerMethod, callerFile, callerLine);
{{(m.ReturnType != typeof(void) ? "return " : string.Empty)}}Dapper.SqlMapper.{{m.Name}}{{genericArgString}}({{string.Join(", ", m.GetParameters().Select(p => p.Name))}});
}
""";
return methodBody;
})
.Where(a => a is not null)
.Select(a => a!)
.ToList();

string source = $$"""
#nullable disable
using System;
using System.Runtime.CompilerServices;

namespace kasthack.Autotagging.DapperProxy;

public static partial class TaggingSqlMapper
{
public static string Dummy => @"";
{{string.Join($"\n", methods)}}
}
""";

// Add the source code to the compilation
context.AddSource($"dapper.g.cs", source);
}

private string DefaultParam(ParameterInfo p)
{
if (p.DefaultValue == null)
{
return "null";
}

if (p.ParameterType.IsEnum)
{
return p.ParameterType.FullName + "." + p.DefaultValue.ToString();
}

if (p.ParameterType == typeof(string))
{
return $"\"{p.DefaultValue}\""; // unsafe but whatever
}

return p.DefaultValue.ToString().ToLowerInvariant();
}

private string GetGenericTypeName(Type type)
{
return type.IsGenericType
? $"{this.Name(type.GetGenericTypeDefinition()).Split('`').First()}<{string.Join(", ", type.GetGenericArguments().Select(a => this.GetGenericTypeName(a)))}>"
: this.Name(type);
}

private string Name(Type type) => (type.FullName ?? type.Name).Replace("+", ".") switch
{
"System.Void" => "void",
string s => s,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Namotion.Reflection" Version="3.1.1" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Dapper" Version="2.1.35" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGNamotion_Reflection)\lib\netstandard2.0\*" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGDapper)\lib\netstandard2.0\*" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>

0 comments on commit 0000037

Please sign in to comment.