-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
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 |
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. |
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 |
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} | ||
"""; | ||
} | ||
} |
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 GitHub Actions / build (6.0.x)
Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs GitHub Actions / build (6.0.x)
Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs GitHub Actions / build (8.0.x)
Check warning on line 17 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs 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 GitHub Actions / build (6.0.x)
Check warning on line 43 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs 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 GitHub Actions / build (6.0.x)
Check warning on line 66 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs GitHub Actions / build (8.0.x)
|
||
) | ||
Check warning on line 67 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs GitHub Actions / build (6.0.x)
Check warning on line 67 in src/dapper/kasthack.Autotagging.DapperProxySourceGenerator/DapperSourceGenerator.cs 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> |