Skip to content

Commit 91c300d

Browse files
1. Scaffolding parameterized tests in data types aligned tests (#224)
2. refactor assertions in equals methods implementation 3. fix nulls loading in mysql :copyfrom implementation 4. unify string nullability behavior and tests
1 parent 97a74fd commit 91c300d

Some content is hidden

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

46 files changed

+1551
-1287
lines changed

.env

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
SOURCE_WASM_FILE_UBUNTU="WasmRunner/obj/release/net8.0/wasi-wasm/wasm/for-publish/WasmRunner.wasm"
22
SOURCE_WASM_FILE="WasmRunner/bin/Release/net8.0/wasi-wasm/AppBundle/WasmRunner.wasm"
3-
MYSQL_CONNECTION_STRING="server=localhost;database=tests;user=root;AllowLoadLocalInfile=true"
4-
POSTGRES_CONNECTION_STRING="host=localhost;database=tests;username=postgres;password=pass"
3+
MYSQL_CONNECTION_STRING="server=localhost;database=tests;user=root;AllowLoadLocalInfile=true;ConvertZeroDateTime=True"
4+
POSTGRES_CONNECTION_STRING="host=localhost;database=tests;username=postgres;password=pass;IncludeErrorDetail=true"
55
POSTGRES_USER="postgres"
66
POSTGRES_PASSWORD="pass"
77
TESTS_DB="tests"

CodeGenerator/CodeGenerator.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ private void InitGenerators(GenerateRequest generateRequest)
7878

7979
// initialize file generators
8080
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);
81+
QueriesGen = new QueriesGen(DbDriver, namespaceName);
82+
ModelsGen = new ModelsGen(DbDriver, namespaceName);
83+
UtilsGen = new UtilsGen(DbDriver, namespaceName);
8484
}
8585

8686
private DbDriver InstantiateDriver()

CodeGenerator/Generators/ModelsGen.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
namespace SqlcGenCsharp.Generators;
1010

11-
internal class ModelsGen(DbDriver dbDriver, Options options, string namespaceName)
11+
internal class ModelsGen(DbDriver dbDriver, string namespaceName)
1212
{
1313
private const string ClassName = "Models";
1414

15-
private RootGen RootGen { get; } = new(options);
15+
private RootGen RootGen { get; } = new(dbDriver.Options);
1616

1717
private DataClassesGen DataClassesGen { get; } = new(dbDriver);
1818

@@ -35,7 +35,7 @@ private MemberDeclarationSyntax[] GenerateModelsDataClasses(Dictionary<string, T
3535
return (
3636
from table in tables.Values
3737
let className = $"{table.Rel.Schema}_{table.Rel.Name}"
38-
select DataClassesGen.Generate(className.ToModelName(), ClassMember.Model, table.Columns, options)
38+
select DataClassesGen.Generate(className.ToModelName(), ClassMember.Model, table.Columns, dbDriver.Options)
3939
).ToArray();
4040
}
4141
}

CodeGenerator/Generators/QueriesGen.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace SqlcGenCsharp.Generators;
1212

13-
internal class QueriesGen(DbDriver dbDriver, Options options, string namespaceName)
13+
internal class QueriesGen(DbDriver dbDriver, string namespaceName)
1414
{
1515
private static readonly string[] ResharperDisables =
1616
[
@@ -21,7 +21,7 @@ internal class QueriesGen(DbDriver dbDriver, Options options, string namespaceNa
2121
"UseObjectOrCollectionInitializer"
2222
];
2323

24-
private RootGen RootGen { get; } = new(options);
24+
private RootGen RootGen { get; } = new(dbDriver.Options);
2525

2626
private DataClassesGen DataClassesGen { get; } = new(dbDriver);
2727

@@ -58,7 +58,7 @@ private static CompilationUnitSyntax AddResharperDisables(CompilationUnitSyntax
5858
private ClassDeclarationSyntax GetClassDeclaration(string className,
5959
IEnumerable<MemberDeclarationSyntax> classMembers)
6060
{
61-
var optionalDapperConfig = options.UseDapper
61+
var optionalDapperConfig = dbDriver.Options.UseDapper
6262
? Environment.NewLine + " Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;"
6363
: "";
6464
var classDeclaration = (ClassDeclarationSyntax)ParseMemberDeclaration(
@@ -89,14 +89,14 @@ private IEnumerable<MemberDeclarationSyntax> GetMembersForSingleQuery(Query quer
8989
private MemberDeclarationSyntax? GetQueryColumnsDataclass(Query query)
9090
{
9191
if (query.Columns.Count <= 0) return null;
92-
return DataClassesGen.Generate(query.Name, ClassMember.Row, query.Columns, options);
92+
return DataClassesGen.Generate(query.Name, ClassMember.Row, query.Columns, dbDriver.Options);
9393
}
9494

9595
private MemberDeclarationSyntax? GetQueryParamsDataclass(Query query)
9696
{
9797
if (query.Params.Count <= 0) return null;
9898
var columns = query.Params.Select(dbDriver.GetColumnFromParam).ToList();
99-
return DataClassesGen.Generate(query.Name, ClassMember.Args, columns, options);
99+
return DataClassesGen.Generate(query.Name, ClassMember.Args, columns, dbDriver.Options);
100100
}
101101

102102
private MemberDeclarationSyntax? GetQueryTextConstant(Query query)

CodeGenerator/Generators/UtilsGen.cs

+35-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using SqlcGenCsharp.Drivers;
4+
using System.Collections.Generic;
5+
using System.Linq;
36
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
47
using File = Plugin.File;
58

69
namespace SqlcGenCsharp.Generators;
710

8-
internal class UtilsGen(string namespaceName, Options options)
11+
internal class UtilsGen(DbDriver dbDriver, string namespaceName)
912
{
1013
private const string ClassName = "Utils";
1114

1215
private string NamespaceName { get; } = namespaceName;
1316

14-
private RootGen RootGen { get; } = new(options);
17+
private RootGen RootGen { get; } = new(dbDriver.Options);
1518

1619
public File GenerateFile()
1720
{
@@ -26,20 +29,31 @@ public File GenerateFile()
2629
};
2730
}
2831

29-
private static UsingDirectiveSyntax[] GetUsingDirectives()
32+
private UsingDirectiveSyntax[] GetUsingDirectives()
3033
{
31-
return
34+
IEnumerable<UsingDirectiveSyntax> usingDirectives =
3235
[
3336
UsingDirective(ParseName("System")),
3437
UsingDirective(ParseName("System.Data")),
3538
UsingDirective(ParseName("System.Linq")),
3639
UsingDirective(ParseName("System.Text.RegularExpressions"))
3740
];
41+
42+
if (dbDriver.Options.DriverName is DriverName.MySqlConnector)
43+
{
44+
usingDirectives = usingDirectives.Concat([
45+
UsingDirective(ParseName("CsvHelper.TypeConversion")),
46+
UsingDirective(ParseName("CsvHelper")),
47+
UsingDirective(ParseName("CsvHelper.Configuration"))
48+
]);
49+
}
50+
51+
return usingDirectives.ToArray();
3852
}
3953

4054
private MemberDeclarationSyntax GetUtilsClass()
4155
{
42-
var optionalTransformQueryForSqliteBatch = options.DriverName is DriverName.Sqlite
56+
var optionalTransformQueryForSqliteBatch = dbDriver.Options.DriverName is DriverName.Sqlite
4357
? """
4458
private static readonly Regex ValuesRegex = new Regex(@"VALUES\s*\((?<params>[^)]*)\)", RegexOptions.IgnoreCase);
4559
@@ -61,9 +75,25 @@ public static string TransformQueryForSqliteBatch(string originalSql, int cntRec
6175
}
6276
""" :
6377
string.Empty;
78+
79+
var optionalNullToNStringConverter = dbDriver.Options.DriverName is DriverName.MySqlConnector
80+
? $$"""
81+
public class NullToNStringConverter : DefaultTypeConverter
82+
{
83+
public override {{dbDriver.AddNullableSuffixIfNeeded("string", true)}} ConvertToString(
84+
{{dbDriver.AddNullableSuffixIfNeeded("object", true)}} value, IWriterRow row, MemberMapData memberMapData)
85+
{
86+
return value == null ? @"\N" : base.ConvertToString(value, row, memberMapData);
87+
}
88+
}
89+
"""
90+
: string.Empty;
91+
6492
var utilsClassDeclaration = $$"""
6593
public static class {{ClassName}}
6694
{
95+
{{optionalNullToNStringConverter}}
96+
6797
public static byte[] GetBytes(IDataRecord reader, int ordinal)
6898
{
6999
const int bufferSize = 100000;

Drivers/DbDriver.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public abstract class DbDriver
1717

1818
private HashSet<string> NullableTypesInDotnetCore { get; } = ["string", "object"];
1919

20-
private HashSet<string> NullableTypes { get; } = ["long", "double", "int", "float", "bool", "DateTime"];
20+
private HashSet<string> NullableTypes { get; } = ["short", "long", "double", "decimal", "int", "float", "bool", "DateTime"];
2121

2222
protected abstract List<ColumnMapping> ColumnMappings { get; }
2323

Drivers/Generators/CommonGen.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,7 @@ string GetReadExpression(Column column, int ordinal)
143143
string GetNullExpression(Column column)
144144
{
145145
var csharpType = dbDriver.GetCsharpType(column);
146-
if (csharpType == "string")
147-
return "string.Empty";
148-
return !dbDriver.Options.DotnetFramework.IsDotnetCore() && dbDriver.IsTypeNullable(csharpType)
149-
? $"({csharpType}) null"
150-
: "null";
146+
return dbDriver.IsTypeNullable(csharpType) ? $"({csharpType}) null" : "null";
151147
}
152148

153149
string CheckNullExpression(int ordinal)

Drivers/MySqlConnectorDriver.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public string GetCopyFromImpl(Query query, string queryTextConstant)
169169

170170
var loaderColumns = query.Params.Select(p => $"\"{p.Column.Name}\"").JoinByComma();
171171
var (establishConnection, connectionOpen) = EstablishConnection(query);
172-
return $$"""
172+
return $$"""
173173
const string supportedDateTimeFormat = "yyyy-MM-dd H:mm:ss";
174174
var {{Variable.Config.AsVarName()}} = new CsvConfiguration(CultureInfo.CurrentCulture) { Delimiter = "{{csvDelimiter}}" };
175175
using (var {{Variable.Writer.AsVarName()}} = new StreamWriter("{{tempCsvFilename}}", false, new UTF8Encoding(false)))
@@ -178,6 +178,12 @@ public string GetCopyFromImpl(Query query, string queryTextConstant)
178178
var options = new TypeConverterOptions { Formats = new[] { supportedDateTimeFormat } };
179179
{{csvWriterVar}}.Context.TypeConverterOptionsCache.AddOptions<DateTime>(options);
180180
{{csvWriterVar}}.Context.TypeConverterOptionsCache.AddOptions<DateTime?>(options);
181+
{{csvWriterVar}}.Context.TypeConverterCache.AddConverter<bool?>(new Utils.NullToNStringConverter());
182+
{{csvWriterVar}}.Context.TypeConverterCache.AddConverter<short?>(new Utils.NullToNStringConverter());
183+
{{csvWriterVar}}.Context.TypeConverterCache.AddConverter<int?>(new Utils.NullToNStringConverter());
184+
{{csvWriterVar}}.Context.TypeConverterCache.AddConverter<long?>(new Utils.NullToNStringConverter());
185+
{{csvWriterVar}}.Context.TypeConverterCache.AddConverter<DateTime?>(new Utils.NullToNStringConverter());
186+
{{csvWriterVar}}.Context.TypeConverterCache.AddConverter<string>(new Utils.NullToNStringConverter());
181187
await {{csvWriterVar}}.WriteRecordsAsync({{Variable.Args.AsVarName()}});
182188
}
183189
@@ -188,9 +194,10 @@ public string GetCopyFromImpl(Query query, string queryTextConstant)
188194
{
189195
Local = true,
190196
TableName = "{{query.InsertIntoTable.Name}}",
191-
FieldTerminator = "{{csvDelimiter}}",
192197
FileName = "{{tempCsvFilename}}",
198+
FieldTerminator = "{{csvDelimiter}}",
193199
FieldQuotationCharacter = '"',
200+
FieldQuotationOptional = true,
194201
NumberOfLinesToSkip = 1
195202
};
196203
{{loaderVar}}.Columns.AddRange(new List<string> { {{loaderColumns}} });

Drivers/NpgsqlDriver.cs

+16-7
Original file line numberDiff line numberDiff line change
@@ -59,31 +59,40 @@ public NpgsqlDriver(Options options, Dictionary<string, Table> tables) : base(op
5959
new("DateTime",
6060
new Dictionary<string, string?>
6161
{
62-
{ "date", "NpgsqlDbType.Date" }, { "timestamp", "NpgsqlDbType.Timestamp" },
62+
{ "date", "NpgsqlDbType.Date" },
63+
{ "timestamp", "NpgsqlDbType.Timestamp" }
6364
}, ordinal => $"reader.GetDateTime({ordinal})"),
6465
new("object",
65-
new Dictionary<string, string?> { { "json", null } }, ordinal => $"reader.GetString({ordinal})"),
66+
new Dictionary<string, string?>
67+
{
68+
{ "json", null }
69+
}, ordinal => $"reader.GetString({ordinal})"),
70+
new("short",
71+
new Dictionary<string, string?>
72+
{
73+
{ "int2", "NpgsqlDbType.Smallint" }
74+
}, ordinal => $"reader.GetInt16({ordinal})",
75+
ordinal => $"reader.GetFieldValue<short[]>({ordinal})"),
6676
new("int",
6777
new Dictionary<string, string?>
6878
{
6979
{ "integer", "NpgsqlDbType.Integer" },
7080
{ "int", "NpgsqlDbType.Integer" },
71-
{ "int2", "NpgsqlDbType.Smallint" },
7281
{ "int4", "NpgsqlDbType.Integer" },
7382
{ "serial", "NpgsqlDbType.Integer" }
7483
}, ordinal => $"reader.GetInt32({ordinal})",
7584
ordinal => $"reader.GetFieldValue<int[]>({ordinal})"),
7685
new("float",
7786
new Dictionary<string, string?>
7887
{
79-
{ "numeric", "NpgsqlDbType.Numeric" },
80-
{ "float4", "NpgsqlDbType.Real" },
81-
{ "float8", "NpgsqlDbType.Real" }
88+
{ "float4", "NpgsqlDbType.Real" }
8289
}, ordinal => $"reader.GetFloat({ordinal})"),
8390
new("decimal",
8491
new Dictionary<string, string?>
8592
{
86-
{ "decimal", "NpgsqlDbType.Real" }
93+
{ "numeric", "NpgsqlDbType.Numeric" },
94+
{ "float8", "NpgsqlDbType.Real" },
95+
{ "decimal", "NpgsqlDbType.Decimal" }
8796
}, ordinal => $"reader.GetDecimal({ordinal})"),
8897
new("bool",
8998
new Dictionary<string, string?>

end2end/EndToEndScaffold/Config.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace EndToEndScaffold;
55
public readonly record struct ClassGenConfig(
66
string TestNamespace,
77
string LegacyTestNamespace,
8-
HashSet<KnownTestType> TestTypes
8+
SortedSet<KnownTestType> TestTypes
99
);
1010

1111
public enum KnownTestType
@@ -47,8 +47,7 @@ internal static class Config
4747
{
4848
TestNamespace = "MySqlConnectorExampleGen",
4949
LegacyTestNamespace = "MySqlConnectorLegacyExampleGen",
50-
TestTypes =
51-
[
50+
TestTypes = [
5251
KnownTestType.One,
5352
KnownTestType.Many,
5453
KnownTestType.Exec,

end2end/EndToEndScaffold/Program.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@ private static bool RecordsAreInUse(string testClassName, bool isLegacyDotnet)
5757
private static string GetTestImplementation(string testClassName, bool isLegacyDotnet, KnownTestType testType)
5858
{
5959
var testGen = Templates.TestImplementations[testType];
60-
return testGen.Impl.Replace(
61-
Templates.UnknownRecordValuePlaceholder,
62-
RecordsAreInUse(testClassName, isLegacyDotnet) ? ".Value" : string.Empty);
60+
var impl = testGen.Impl
61+
.Replace(Templates.UnknownRecordValuePlaceholder,
62+
RecordsAreInUse(testClassName, isLegacyDotnet) ? ".Value" : string.Empty)
63+
.Replace(Templates.UnknownNullableIndicatorPlaceholder,
64+
isLegacyDotnet ? string.Empty : "?");
65+
return impl;
6366
}
6467
}

0 commit comments

Comments
 (0)