Skip to content

Commit 37daa48

Browse files
add sqlc.embed support for all databases (#202)
1 parent 19b977e commit 37daa48

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1856
-332
lines changed

CodeGenerator/ClassMember.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ public enum ClassMember
66
{
77
Row,
88
Args,
9-
Sql
9+
Sql,
10+
Model
1011
}
1112

1213
public static class ClassMemberTypeExtensions
@@ -18,6 +19,7 @@ public static string Name(this ClassMember me)
1819
ClassMember.Sql => "Sql",
1920
ClassMember.Row => "Row",
2021
ClassMember.Args => "Args",
22+
ClassMember.Model => string.Empty,
2123
_ => throw new ArgumentOutOfRangeException(nameof(me), me, null)
2224
};
2325
}

CodeGenerator/CodeGenerator.cs

+42-152
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using Microsoft.CodeAnalysis.CSharp;
2-
using Microsoft.CodeAnalysis.CSharp.Syntax;
31
using Plugin;
42
using SqlcGenCsharp.Drivers;
53
using SqlcGenCsharp.Generators;
@@ -8,72 +6,47 @@
86
using System.IO;
97
using System.Linq;
108
using System.Threading.Tasks;
11-
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
12-
using File = Plugin.File;
139

1410
namespace SqlcGenCsharp;
1511

1612
public class CodeGenerator
1713
{
18-
private static readonly string[] ResharperDisables =
19-
[
20-
"UnusedAutoPropertyAccessor.Global",
21-
"NotAccessedPositionalProperty.Global",
22-
"ConvertToUsingDeclaration",
23-
"UseAwaitUsing"
24-
];
25-
26-
private string? _namespaceName;
2714
private Options? _options;
15+
private Dictionary<string, Table>? _tables;
2816
private DbDriver? _dbDriver;
29-
private DataClassesGen? _dataClassesGen;
30-
private RootGen? _rootGen;
17+
private QueriesGen? _queriesGen;
18+
private ModelsGen? _modelsGen;
3119
private UtilsGen? _utilsGen;
3220
private CsprojGen? _csprojGen;
3321

34-
private void InitGenerators(GenerateRequest generateRequest)
35-
{
36-
var outputDirectory = generateRequest.Settings.Codegen.Out;
37-
var projectName = new DirectoryInfo(outputDirectory).Name;
38-
Options = new Options(generateRequest);
39-
NamespaceName = Options.NamespaceName == string.Empty ? projectName : Options.NamespaceName;
40-
DbDriver = InstantiateDriver();
41-
42-
// initialize file generators
43-
CsprojGen = new CsprojGen(outputDirectory, projectName, NamespaceName, Options);
44-
RootGen = new RootGen(Options);
45-
UtilsGen = new UtilsGen(NamespaceName, Options);
46-
DataClassesGen = new DataClassesGen(DbDriver);
47-
}
48-
49-
private string NamespaceName
50-
{
51-
get => _namespaceName!;
52-
set => _namespaceName = value;
53-
}
54-
5522
private Options Options
5623
{
5724
get => _options!;
5825
set => _options = value;
5926
}
6027

28+
private Dictionary<string, Table> Tables
29+
{
30+
get => _tables!;
31+
set => _tables = value;
32+
}
33+
6134
private DbDriver DbDriver
6235
{
6336
get => _dbDriver!;
6437
set => _dbDriver = value;
6538
}
6639

67-
private DataClassesGen DataClassesGen
40+
private QueriesGen QueriesGen
6841
{
69-
get => _dataClassesGen!;
70-
set => _dataClassesGen = value;
42+
get => _queriesGen!;
43+
set => _queriesGen = value;
7144
}
7245

73-
private RootGen RootGen
46+
private ModelsGen ModelsGen
7447
{
75-
get => _rootGen!;
76-
set => _rootGen = value;
48+
get => _modelsGen!;
49+
set => _modelsGen = value;
7750
}
7851

7952
private UtilsGen UtilsGen
@@ -88,13 +61,35 @@ private CsprojGen CsprojGen
8861
set => _csprojGen = value;
8962
}
9063

64+
private void InitGenerators(GenerateRequest generateRequest)
65+
{
66+
var outputDirectory = generateRequest.Settings.Codegen.Out;
67+
var projectName = new DirectoryInfo(outputDirectory).Name;
68+
Options = new Options(generateRequest);
69+
70+
// TODO currently only default schema is considered - should consider all non-internal schemas
71+
Tables = generateRequest.Catalog.Schemas
72+
.Where(schema => schema.Name == generateRequest.Catalog.DefaultSchema)
73+
.SelectMany(schema => schema.Tables)
74+
.ToDictionary(table => table.Rel.Name, table => table);
75+
76+
var namespaceName = Options.NamespaceName == string.Empty ? projectName : Options.NamespaceName;
77+
DbDriver = InstantiateDriver();
78+
79+
// initialize file generators
80+
CsprojGen = new CsprojGen(outputDirectory, projectName, namespaceName, Options);
81+
QueriesGen = new QueriesGen(DbDriver, Options, namespaceName);
82+
ModelsGen = new ModelsGen(DbDriver, Options, namespaceName);
83+
UtilsGen = new UtilsGen(namespaceName, Options);
84+
}
85+
9186
private DbDriver InstantiateDriver()
9287
{
9388
return Options.DriverName switch
9489
{
95-
DriverName.MySqlConnector => new MySqlConnectorDriver(Options),
96-
DriverName.Npgsql => new NpgsqlDriver(Options),
97-
DriverName.Sqlite => new SqliteDriver(Options),
90+
DriverName.MySqlConnector => new MySqlConnectorDriver(Options, Tables),
91+
DriverName.Npgsql => new NpgsqlDriver(Options, Tables),
92+
DriverName.Sqlite => new SqliteDriver(Options, Tables),
9893
_ => throw new ArgumentException($"unknown driver: {Options.DriverName}")
9994
};
10095
}
@@ -104,7 +99,8 @@ public Task<GenerateResponse> Generate(GenerateRequest generateRequest)
10499
InitGenerators(generateRequest); // the request is necessary in order to know which generators are needed
105100
var fileQueries = GetFileQueries();
106101
var files = fileQueries
107-
.Select(fq => GenerateFile(fq.Value, fq.Key))
102+
.Select(fq => QueriesGen.GenerateFile(fq.Value, fq.Key))
103+
.Append(ModelsGen.GenerateFile(Tables))
108104
.Append(UtilsGen.GenerateFile())
109105
.AppendIf(CsprojGen.GenerateFile(), Options.GenerateCsproj);
110106

@@ -126,110 +122,4 @@ string QueryFilenameToClassName(string filenameWithExtension)
126122
Path.GetExtension(filenameWithExtension)[1..].ToPascalCase());
127123
}
128124
}
129-
130-
private File GenerateFile(IEnumerable<Query> queries, string className)
131-
{
132-
var (usingDirectives, classDeclaration) = GenerateClass(queries, className);
133-
var root = RootGen.CompilationRootGen(
134-
IdentifierName(NamespaceName), usingDirectives.ToArray(), classDeclaration);
135-
root = AddResharperDisables(root);
136-
root = root.AddCommentOnTop(Consts.AutoGeneratedComment);
137-
138-
return new File
139-
{
140-
Name = $"{className}.cs",
141-
Contents = root.ToByteString()
142-
};
143-
}
144-
145-
private static CompilationUnitSyntax AddResharperDisables(CompilationUnitSyntax compilationUnit)
146-
{
147-
return ResharperDisables
148-
.Aggregate(compilationUnit, (current, resharperDisable) =>
149-
current.AddCommentOnTop($"// ReSharper disable {resharperDisable}"));
150-
}
151-
152-
private (IList<UsingDirectiveSyntax>, MemberDeclarationSyntax) GenerateClass(IEnumerable<Query> queries,
153-
string className)
154-
{
155-
var usingDirectives = DbDriver.GetUsingDirectives();
156-
var classMembers = queries.SelectMany(GetMembersForSingleQuery);
157-
return (usingDirectives, GetClassDeclaration(className, classMembers));
158-
}
159-
160-
private ClassDeclarationSyntax GetClassDeclaration(string className,
161-
IEnumerable<MemberDeclarationSyntax> classMembers)
162-
{
163-
var optionalDapperConfig = Options.UseDapper
164-
? Environment.NewLine + " Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;"
165-
: "";
166-
var classDeclaration = (ClassDeclarationSyntax)ParseMemberDeclaration(
167-
$$"""
168-
class {{className}}
169-
{
170-
public {{className}}(string {{Variable.ConnectionString.AsVarName()}})
171-
{
172-
this.{{Variable.ConnectionString.AsPropertyName()}} = {{Variable.ConnectionString.AsVarName()}};{{optionalDapperConfig}}
173-
}
174-
private string {{Variable.ConnectionString.AsPropertyName()}} { get; }
175-
}
176-
""")!
177-
.AddModifiers(Token(SyntaxKind.PublicKeyword));
178-
179-
return classDeclaration.AddMembers(classMembers.ToArray());
180-
}
181-
182-
private IEnumerable<MemberDeclarationSyntax> GetMembersForSingleQuery(Query query)
183-
{
184-
return new List<MemberDeclarationSyntax>()
185-
.Append(GetQueryTextConstant(query))
186-
.AppendIfNotNull(GetQueryColumnsDataclass(query))
187-
.AppendIfNotNull(GetQueryParamsDataclass(query))
188-
.Append(AddMethodDeclaration(query));
189-
}
190-
191-
private MemberDeclarationSyntax? GetQueryColumnsDataclass(Query query)
192-
{
193-
return query.Columns.Count <= 0
194-
? null
195-
: DataClassesGen.Generate(query.Name, ClassMember.Row, query.Columns, Options);
196-
}
197-
198-
private MemberDeclarationSyntax? GetQueryParamsDataclass(Query query)
199-
{
200-
if (query.Params.Count <= 0) return null;
201-
var columns = query.Params.Select(DbDriver.GetColumnFromParam).ToList();
202-
return DataClassesGen.Generate(query.Name, ClassMember.Args, columns, Options);
203-
}
204-
205-
private MemberDeclarationSyntax GetQueryTextConstant(Query query)
206-
{
207-
return ParseMemberDeclaration(
208-
$"private const string {query.Name}{ClassMember.Sql.Name()} = \"{DbDriver.TransformQueryText(query)}\";")
209-
!
210-
.AppendNewLine();
211-
}
212-
213-
private MemberDeclarationSyntax AddMethodDeclaration(Query query)
214-
{
215-
var queryTextConstant = GetInterfaceName(ClassMember.Sql);
216-
var argInterface = GetInterfaceName(ClassMember.Args);
217-
var returnInterface = GetInterfaceName(ClassMember.Row);
218-
219-
return query.Cmd switch
220-
{
221-
":exec" => DbDriver.ExecDeclare(queryTextConstant, argInterface, query),
222-
":one" => DbDriver.OneDeclare(queryTextConstant, argInterface, returnInterface, query),
223-
":many" => DbDriver.ManyDeclare(queryTextConstant, argInterface, returnInterface, query),
224-
":execrows" => ((IExecRows)DbDriver).ExecRowsDeclare(queryTextConstant, argInterface, query),
225-
":execlastid" => ((IExecLastId)DbDriver).ExecLastIdDeclare(queryTextConstant, argInterface, query),
226-
":copyfrom" => ((ICopyFrom)DbDriver).CopyFromDeclare(queryTextConstant, argInterface, query),
227-
_ => throw new NotImplementedException($"{query.Cmd} is not implemented")
228-
};
229-
230-
string GetInterfaceName(ClassMember classMemberType)
231-
{
232-
return $"{query.Name}{classMemberType.Name()}";
233-
}
234-
}
235125
}

CodeGenerator/Generators/DataClassesGen.cs

+18-11
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@ internal class DataClassesGen(DbDriver dbDriver)
1212
{
1313
public MemberDeclarationSyntax Generate(string name, ClassMember classMember, IList<Column> columns, Options options)
1414
{
15+
var className = classMember == ClassMember.Model ? $"{name.ToModelName()}" : $"{name}{classMember.Name()}";
1516
if (options.DotnetFramework.LatestDotnetSupported() && !options.UseDapper)
16-
return GenerateAsRecord(name, classMember, columns);
17-
return GenerateAsCLass(name, classMember, columns);
17+
return GenerateAsRecord(className, columns);
18+
return GenerateAsCLass(className, columns);
1819
}
1920

20-
private RecordDeclarationSyntax GenerateAsRecord(string name, ClassMember classMember, IList<Column> columns)
21+
private RecordDeclarationSyntax GenerateAsRecord(string className, IList<Column> columns)
2122
{
22-
return RecordDeclaration(
23-
Token(SyntaxKind.StructKeyword),
24-
$"{name}{classMember.Name()}")
23+
return RecordDeclaration(Token(SyntaxKind.StructKeyword), className)
2524
.AddModifiers(
2625
Token(SyntaxKind.PublicKeyword),
2726
Token(SyntaxKind.ReadOnlyKeyword),
@@ -33,15 +32,20 @@ private RecordDeclarationSyntax GenerateAsRecord(string name, ClassMember classM
3332
ParameterListSyntax ColumnsToParameterList()
3433
{
3534
return ParameterList(SeparatedList(columns
36-
.Select(column => Parameter(Identifier(column.Name.ToPascalCase()))
37-
.WithType(ParseTypeName(dbDriver.GetColumnType(column)))
35+
.Select(column =>
36+
{
37+
var fieldName = column.EmbedTable == null
38+
? column.Name.ToPascalCase()
39+
: column.Name.ToModelName();
40+
return Parameter(Identifier(fieldName))
41+
.WithType(ParseTypeName(dbDriver.GetColumnType(column)));
42+
}
3843
)));
3944
}
4045
}
4146

42-
private ClassDeclarationSyntax GenerateAsCLass(string name, ClassMember classMember, IList<Column> columns)
47+
private ClassDeclarationSyntax GenerateAsCLass(string className, IList<Column> columns)
4348
{
44-
var className = $"{name}{classMember.Name()}";
4549
return ClassDeclaration(className)
4650
.AddModifiers(Token(SyntaxKind.PublicKeyword))
4751
.AddMembers(ColumnsToProperties())
@@ -52,8 +56,11 @@ MemberDeclarationSyntax[] ColumnsToProperties()
5256
return columns.Select(column =>
5357
{
5458
var propertyType = dbDriver.GetColumnType(column);
59+
var propertyName = column.EmbedTable == null
60+
? column.Name.ToPascalCase()
61+
: column.Name.ToModelName();
5562
return ParseMemberDeclaration(
56-
$"public {propertyType} {column.Name.ToPascalCase()} {{ get; set; }}");
63+
$"public {propertyType} {propertyName} {{ get; set; }}");
5764
})
5865
.Cast<MemberDeclarationSyntax>()
5966
.ToArray();

CodeGenerator/Generators/ModelsGen.cs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Microsoft.CodeAnalysis.CSharp.Syntax;
2+
using Plugin;
3+
using SqlcGenCsharp.Drivers;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
7+
8+
9+
namespace SqlcGenCsharp.Generators;
10+
11+
internal class ModelsGen(DbDriver dbDriver, Options options, string namespaceName)
12+
{
13+
private RootGen RootGen { get; } = new(options);
14+
15+
private DataClassesGen DataClassesGen { get; } = new(dbDriver);
16+
17+
public File GenerateFile(Dictionary<string, Table> tables)
18+
{
19+
var models = GenerateModelsDataClasses(tables);
20+
var root = RootGen.CompilationRootGen(IdentifierName(namespaceName),
21+
[UsingDirective(ParseName("System"))], models);
22+
root = root.AddCommentOnTop(Consts.AutoGeneratedComment);
23+
24+
return new File
25+
{
26+
Name = "Models.cs",
27+
Contents = root.ToByteString()
28+
};
29+
}
30+
31+
private MemberDeclarationSyntax[] GenerateModelsDataClasses(Dictionary<string, Table> tables)
32+
{
33+
return (
34+
from table in tables.Values
35+
let className = $"{table.Rel.Schema}_{table.Rel.Name}"
36+
select DataClassesGen.Generate(className.ToModelName(), ClassMember.Model, table.Columns, options)
37+
).ToArray();
38+
}
39+
}

0 commit comments

Comments
 (0)