• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / FluentCommand / 8975936774

06 May 2024 09:14PM UTC coverage: 53.655%. Remained the same
8975936774

push

github

pwelter34
improve datetime merge

1165 of 2845 branches covered (40.95%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

3679 of 6183 relevant lines covered (59.5%)

702.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.99
/src/FluentCommand.SqlServer/Merge/DataMergeGenerator.cs
1
using System.Data;
2
using System.Text;
3

4
using FluentCommand.Extensions;
5
using FluentCommand.Internal;
6

7
namespace FluentCommand.Merge;
8

9
/// <summary>
10
/// Class used to build data merge SQL statements
11
/// </summary>
12
public static class DataMergeGenerator
13
{
14
    public const string OriginalPrefix = "Original";
15
    public const string CurrentPrefix = "Current";
16
    public const int TabSize = 4;
17

18
    /// <summary>
19
    /// Tables the identifier.
20
    /// </summary>
21
    /// <param name="name">The name.</param>
22
    /// <returns></returns>
23
    public static string TableIdentifier(string name)
24
    {
25
        var parts = name.Split('.');
63✔
26
        for (int i = 0; i < parts.Length; i++)
324✔
27
            parts[i] = QuoteIdentifier(parts[i]);
99✔
28

29
        return string.Join(".", parts);
63✔
30
    }
31

32
    public static string QuoteIdentifier(string name)
33
    {
34
        if (name.StartsWith("[") && name.EndsWith("]"))
1,968✔
35
            return name;
3✔
36

37
        return "[" + name.Replace("]", "]]") + "]";
1,965✔
38
    }
39

40
    public static string ParseIdentifier(string name)
41
    {
42
        if (name.StartsWith("[") && name.EndsWith("]"))
126✔
43
            return name.Substring(1, name.Length - 2);
9✔
44

45
        return name;
117✔
46
    }
47

48

49
    /// <summary>
50
    /// Builds the SQL for the temporary table used in the merge operation.
51
    /// </summary>
52
    /// <param name="mergeDefinition">The merge definition.</param>
53
    /// <returns></returns>
54
    public static string BuildTable(DataMergeDefinition mergeDefinition)
55
    {
56
        var builder = StringBuilderCache.Acquire();
12✔
57
        builder
12✔
58
            .Append("CREATE TABLE ")
12✔
59
            .Append(TableIdentifier(mergeDefinition.TemporaryTable))
12✔
60
            .AppendLine()
12✔
61
            .Append("(")
12✔
62
            .AppendLine();
12✔
63

64
        bool hasColumn = false;
12✔
65
        foreach (var mergeColumn in mergeDefinition.Columns.Where(c => !c.IsIgnored))
384✔
66
        {
67
            bool writeComma = hasColumn;
120✔
68

69
            builder
120✔
70
                .AppendLineIf(",", v => writeComma)
120✔
71
                .Append(' ', TabSize)
120✔
72
                .Append(QuoteIdentifier(mergeColumn.SourceColumn))
120✔
73
                .Append(" ")
120✔
74
                .Append(mergeColumn.NativeType)
120✔
75
                .Append(" NULL");
120✔
76

77
            hasColumn = true;
120✔
78
        }
79

80
        builder
12✔
81
            .AppendLine()
12✔
82
            .Append(")")
12✔
83
            .AppendLine();
12✔
84

85
        return StringBuilderCache.ToString(builder);
12✔
86
    }
87

88
    /// <summary>
89
    /// Builds the SQL merge statement for the merge operation.
90
    /// </summary>
91
    /// <param name="mergeDefinition">The merge definition.</param>
92
    /// <returns>The merge sql statement</returns>
93
    public static string BuildMerge(DataMergeDefinition mergeDefinition)
94
    {
95
        return BuildMerge(mergeDefinition, null);
15✔
96
    }
97

98
    /// <summary>
99
    /// Builds the SQL merge statement for the merge operation.
100
    /// </summary>
101
    /// <param name="mergeDefinition">The merge definition.</param>
102
    /// <param name="reader">The data reader to generate merge statement with.</param>
103
    /// <returns>The merge sql statement</returns>
104
    public static string BuildMerge(DataMergeDefinition mergeDefinition, IDataReader reader)
105
    {
106
        var mergeColumns = mergeDefinition.Columns
36✔
107
            .Where(c => !c.IsIgnored && (c.IsKey || c.CanInsert || c.CanUpdate))
324!
108
            .ToList();
36✔
109

110
        var builder = StringBuilderCache.Acquire();
36✔
111

112
        if (mergeDefinition.IdentityInsert && mergeDefinition.IncludeInsert)
36!
113
        {
114
            builder
×
115
                .Append("SET IDENTITY_INSERT ")
×
116
                .Append(TableIdentifier(mergeDefinition.TargetTable))
×
117
                .AppendLine(" ON;")
×
118
                .AppendLine();
×
119
        }
120

121

122
        builder
36✔
123
            .Append("MERGE INTO ")
36✔
124
            .Append(TableIdentifier(mergeDefinition.TargetTable))
36✔
125
            .Append(" AS t")
36✔
126
            .AppendLine();
36✔
127

128
        if (reader == null)
36✔
129
            AppendUsingSelect(mergeDefinition, mergeColumns, builder);
15✔
130
        else
131
            AppendUsingData(mergeDefinition, mergeColumns, reader, builder);
21✔
132

133
        AppendJoin(mergeColumns, builder);
36✔
134

135
        // Insert
136
        AppendInsert(mergeDefinition, builder);
36✔
137

138
        // Update
139
        AppendUpdate(mergeDefinition, builder);
36✔
140

141
        // Delete
142
        AppendDelete(mergeDefinition, builder);
36✔
143

144
        // Output
145
        AppendOutput(mergeDefinition, builder);
36✔
146

147
        // merge must end with ;
148
        builder.Append(";");
36✔
149

150
        if (mergeDefinition.IdentityInsert && mergeDefinition.IncludeInsert)
36!
151
        {
152
            builder
×
153
                .Append("SET IDENTITY_INSERT ")
×
154
                .Append(TableIdentifier(mergeDefinition.TargetTable))
×
155
                .AppendLine(" OFF;")
×
156
                .AppendLine();
×
157
        }
158

159
        return StringBuilderCache.ToString(builder);
36✔
160
    }
161

162

163
    private static void AppendUsingData(DataMergeDefinition mergeDefinition, List<DataMergeColumn> mergeColumns, IDataReader reader, StringBuilder builder)
164
    {
165
        builder
21✔
166
            .AppendLine("USING")
21✔
167
            .AppendLine("(")
21✔
168
            .Append(' ', TabSize)
21✔
169
            .AppendLine("VALUES");
21✔
170

171
        var fields = new HashSet<string>();
21✔
172

173
        bool wroteRow = false;
21✔
174
        while (reader.Read())
1,242✔
175
        {
176
            bool wrote = false;
1,221✔
177

178
            builder
1,221✔
179
                .AppendLineIf(", ", s => wroteRow)
1,221✔
180
                .Append(' ', TabSize)
1,221✔
181
            .Append("(");
1,221✔
182

183
            for (int i = 0; i < reader.FieldCount; i++)
17,334✔
184
            {
185
                var fieldName = reader.GetName(i);
7,446✔
186

187
                var isFound = mergeColumns.Any(c => c.SourceColumn == fieldName);
35,067✔
188
                if (!isFound)
7,446✔
189
                    continue;
190

191
                fields.Add(fieldName);
7,446✔
192

193
                builder.AppendIf(", ", v => wrote);
14,892✔
194

195
                var value = reader.GetValue(i);
7,446✔
196
                var stringValue = GetValue(value);
7,446✔
197
                var fieldType = reader.GetFieldType(i);
7,446✔
198

199
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse
200
                if (value != null && value != DBNull.Value && NeedQuote(fieldType))
7,446✔
201
                    builder.AppendFormat("'{0}'", stringValue.Replace("'", "''"));
4,920✔
202
                else
203
                    builder.Append(stringValue);
2,526✔
204

205

206
                wrote = true;
7,446✔
207
            }
208
            builder.Append(")");
1,221✔
209

210
            wroteRow = true;
1,221✔
211
        }
212

213
        builder
21✔
214
            .AppendLine()
21✔
215
            .AppendLine(")")
21✔
216
            .AppendLine("AS s")
21✔
217
            .AppendLine("(")
21✔
218
            .Append(' ', TabSize);
21✔
219

220
        bool wroteColumn = false;
21✔
221
        foreach (var field in fields)
414✔
222
        {
223
            if (wroteColumn)
186✔
224
                builder.Append(", ");
165✔
225

226
            builder.Append(QuoteIdentifier(field));
186✔
227
            wroteColumn = true;
186✔
228
        }
229

230
        builder
21✔
231
            .AppendLine()
21✔
232
            .AppendLine(")");
21✔
233
    }
21✔
234

235
    private static void AppendUsingSelect(DataMergeDefinition mergeDefinition, List<DataMergeColumn> mergeColumns, StringBuilder builder)
236
    {
237
        builder
15✔
238
            .AppendLine("USING")
15✔
239
            .AppendLine("(")
15✔
240
            .Append(' ', TabSize)
15✔
241
            .AppendLine("SELECT");
15✔
242

243
        bool hasColumn = false;
15✔
244
        foreach (var mergeColumn in mergeColumns)
306✔
245
        {
246
            bool writeComma = hasColumn;
138✔
247

248
            builder
138✔
249
                .AppendLineIf(",", v => writeComma)
138✔
250
                .Append(' ', TabSize * 2)
138✔
251
                .Append(QuoteIdentifier(mergeColumn.SourceColumn));
138✔
252

253
            hasColumn = true;
138✔
254
        }
255

256
        builder
15✔
257
            .AppendLine()
15✔
258
            .Append(' ', TabSize)
15✔
259
            .Append("FROM ")
15✔
260
            .Append(TableIdentifier(mergeDefinition.TemporaryTable))
15✔
261
            .AppendLine()
15✔
262
            .AppendLine(")")
15✔
263
            .AppendLine("AS s");
15✔
264
    }
15✔
265

266
    private static void AppendJoin(List<DataMergeColumn> mergeColumns, StringBuilder builder)
267
    {
268
        bool hasColumn;
269
        builder
36✔
270
            .AppendLine("ON")
36✔
271
            .AppendLine("(");
36✔
272

273
        hasColumn = false;
36✔
274
        foreach (var mergeColumn in mergeColumns.Where(c => c.IsKey))
468✔
275
        {
276
            bool writeComma = hasColumn;
36✔
277
            builder
36✔
278
                .AppendLineIf(" AND ", v => writeComma)
36✔
279
                .Append(' ', TabSize)
36✔
280
                .Append("t.")
36✔
281
                .Append(QuoteIdentifier(mergeColumn.TargetColumn))
36✔
282
                .Append(" = s.")
36✔
283
                .Append(QuoteIdentifier(mergeColumn.SourceColumn));
36✔
284

285
            hasColumn = true;
36✔
286
        }
287

288
        builder
36✔
289
            .AppendLine()
36✔
290
            .Append(")")
36✔
291
            .AppendLine();
36✔
292
    }
36✔
293

294
    private static void AppendOutput(DataMergeDefinition mergeDefinition, StringBuilder builder)
295
    {
296
        if (!mergeDefinition.IncludeOutput)
36✔
297
            return;
27✔
298

299
        var mergeColumns = mergeDefinition.Columns
9✔
300
            .Where(c => !c.IsIgnored)
54✔
301
            .ToList();
9✔
302

303
        builder
9✔
304
            .AppendLine("OUTPUT")
9✔
305
            .Append(' ', TabSize)
9✔
306
            .Append("$action as [Action]");
9✔
307

308
        foreach (var mergeColumn in mergeColumns)
126✔
309
        {
310
            builder
54✔
311
                .AppendLine(",")
54✔
312
                .Append(' ', TabSize)
54✔
313
                .Append("DELETED.")
54✔
314
                .Append(QuoteIdentifier(mergeColumn.SourceColumn))
54✔
315
                .Append(" as [")
54✔
316
                .Append(OriginalPrefix)
54✔
317
                .Append(ParseIdentifier(mergeColumn.SourceColumn))
54✔
318
                .Append("],")
54✔
319
                .AppendLine();
54✔
320

321
            builder
54✔
322
                .Append(' ', TabSize)
54✔
323
                .Append("INSERTED.")
54✔
324
                .Append(QuoteIdentifier(mergeColumn.SourceColumn))
54✔
325
                .Append(" as [")
54✔
326
                .Append(CurrentPrefix)
54✔
327
                .Append(ParseIdentifier(mergeColumn.SourceColumn))
54✔
328
                .Append("]");
54✔
329
        }
330

331
    }
9✔
332

333
    private static void AppendDelete(DataMergeDefinition mergeDefinition, StringBuilder builder)
334
    {
335
        if (!mergeDefinition.IncludeDelete)
36!
336
            return;
36✔
337

338
        builder
×
339
            .AppendLine("WHEN NOT MATCHED BY SOURCE THEN ")
×
340
            .Append(' ', TabSize)
×
341
            .AppendLine("DELETE");
×
342
    }
×
343

344
    private static void AppendUpdate(DataMergeDefinition mergeDefinition, StringBuilder builder)
345
    {
346
        if (!mergeDefinition.IncludeUpdate)
36!
347
            return;
×
348

349
        var mergeColumns = mergeDefinition.Columns
36✔
350
            .Where(c => !c.IsIgnored && c.CanUpdate)
324!
351
            .ToList();
36✔
352

353
        builder
36✔
354
            .AppendLine("WHEN MATCHED THEN ")
36✔
355
            .Append(' ', TabSize)
36✔
356
            .AppendLine("UPDATE SET");
36✔
357

358
        bool hasColumn = false;
36✔
359
        foreach (var mergeColumn in mergeColumns)
648✔
360
        {
361
            bool writeComma = hasColumn;
288✔
362
            builder
288✔
363
                .AppendLineIf(",", v => writeComma)
288✔
364
                .Append(' ', TabSize * 2)
288✔
365
                .Append("t.")
288✔
366
                .Append(QuoteIdentifier(mergeColumn.TargetColumn))
288✔
367
                .Append(" = s.")
288✔
368
                .Append(QuoteIdentifier(mergeColumn.SourceColumn));
288✔
369

370
            hasColumn = true;
288✔
371
        }
372
        builder.AppendLine();
36✔
373
    }
36✔
374

375
    private static void AppendInsert(DataMergeDefinition mergeDefinition, StringBuilder builder)
376
    {
377
        if (!mergeDefinition.IncludeInsert)
36!
378
            return;
×
379

380
        var mergeColumns = mergeDefinition.Columns
36✔
381
            .Where(c => !c.IsIgnored && c.CanInsert)
324!
382
            .ToList();
36✔
383

384
        builder
36✔
385
            .AppendLine("WHEN NOT MATCHED BY TARGET THEN ")
36✔
386
            .Append(' ', TabSize)
36✔
387
            .AppendLine("INSERT")
36✔
388
            .Append(' ', TabSize)
36✔
389
            .AppendLine("(");
36✔
390

391
        bool hasColumn = false;
36✔
392
        foreach (var mergeColumn in mergeColumns)
720✔
393
        {
394
            bool writeComma = hasColumn;
324✔
395
            builder
324✔
396
                .AppendLineIf(",", v => writeComma)
324✔
397
                .Append(' ', TabSize * 2)
324✔
398
                .Append(QuoteIdentifier(mergeColumn.TargetColumn));
324✔
399

400
            hasColumn = true;
324✔
401
        }
402
        builder.AppendLine();
36✔
403

404
        builder
36✔
405
            .Append(' ', TabSize)
36✔
406
            .AppendLine(")")
36✔
407
            .Append(' ', TabSize)
36✔
408
            .AppendLine("VALUES")
36✔
409
            .Append(' ', TabSize)
36✔
410
            .AppendLine("(");
36✔
411

412
        hasColumn = false;
36✔
413
        foreach (var mergeColumn in mergeColumns)
720✔
414
        {
415
            bool writeComma = hasColumn;
324✔
416
            builder
324✔
417
                .AppendLineIf(",", v => writeComma)
324✔
418
                .Append(' ', TabSize * 2)
324✔
419
                .Append("s.")
324✔
420
                .Append(QuoteIdentifier(mergeColumn.SourceColumn));
324✔
421

422
            hasColumn = true;
324✔
423
        }
424
        builder.AppendLine();
36✔
425

426
        builder
36✔
427
            .Append(' ', TabSize)
36✔
428
            .AppendLine(")");
36✔
429
    }
36✔
430

431

432
    private static bool NeedQuote(Type type)
433
    {
434
        var underType = type.GetUnderlyingType();
4,980✔
435

436
        if (underType == typeof(string))
4,980✔
437
            return true;
4,866✔
438
        if (underType == typeof(TimeSpan))
114✔
439
            return true;
9✔
440
        if (underType == typeof(DateTime))
105✔
441
            return true;
9✔
442
        if (underType == typeof(DateTimeOffset))
96✔
443
            return true;
9✔
444
        if (underType == typeof(Guid))
87✔
445
            return true;
9✔
446
#if NET6_0_OR_GREATER
447
        if (underType == typeof(DateOnly))
78✔
448
            return true;
9✔
449
        if (underType == typeof(TimeOnly))
69✔
450
            return true;
9✔
451
#endif
452

453
        return false;
60✔
454
    }
455

456
    private static string GetValue(object value)
457
    {
458
        if (value == null || value == DBNull.Value)
7,446✔
459
            return "NULL";
2,466✔
460

461
        return value switch
4,980!
462
        {
4,980✔
463
            string stringValue => stringValue,
4,866✔
464
            DateTime dateTimeValue => dateTimeValue.ToString("yyyy-MM-dd HH:mm:ss.fff"),
9✔
465
            DateTimeOffset dateTimeOffset => dateTimeOffset.ToString("yyyy-MM-dd HH:mm:ss.ffffffzzz"),
9✔
UNCOV
466
            byte[] byteArray => ToHex(byteArray),
×
467
            bool boolValue => boolValue ? "1" : "0",
9✔
468
#if NET6_0_OR_GREATER
4,980✔
469
            DateOnly dateValue => dateValue.ToString("yyyy-MM-dd"),
9✔
470
            TimeOnly timeValue => timeValue.ToString("hh:mm:ss.ffffff"),
9✔
471
#endif
4,980✔
472
            _ => Convert.ToString(value)
69✔
473
        };
4,980✔
474
    }
475

476
    private static string ToHex(byte[] bytes)
477
    {
478
#if NETSTANDARD2_0
479
        var s = StringBuilderCache.Acquire();
480
        s.Append("0x");
481

482
        foreach (var b in bytes)
483
            s.Append(b.ToString("x2").ToUpperInvariant());
484

485
        return StringBuilderCache.ToString(s);
486
#else
487
        return Convert.ToHexString(bytes);
×
488
#endif
489
    }
490
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc