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

loresoft / FluentCommand / 26594173245

28 May 2026 06:28PM UTC coverage: 55.553% (+0.7%) from 54.902%
26594173245

push

github

pwelter34
Move JSON support, add docs and examples

1358 of 3215 branches covered (42.24%)

Branch coverage included in aggregate %.

103 of 234 new or added lines in 9 files covered. (44.02%)

371 existing lines in 26 files now uncovered.

4389 of 7130 relevant lines covered (61.56%)

312.89 hits per line

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

66.9
/src/FluentCommand/Query/Generators/PostgresqlGenerator.cs
1
using FluentCommand.Extensions;
2
using FluentCommand.Internal;
3

4
namespace FluentCommand.Query.Generators;
5

6
/// <summary>
7
/// Provides a SQL generator for PostgreSQL, implementing SQL statement and expression generation
8
/// with PostgreSQL-specific syntax and conventions.
9
/// </summary>
10
public class PostgreSqlGenerator : SqlServerGenerator
11
{
12
    /// <summary>
13
    /// Builds a SQL INSERT statement for PostgreSQL, including support for RETURNING and comments.
14
    /// </summary>
15
    /// <param name="insertStatement">The <see cref="InsertStatement"/> containing the INSERT statement configuration.</param>
16
    /// <returns>A SQL INSERT statement string for PostgreSQL.</returns>
17
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="insertStatement"/> is <c>null</c>.</exception>
18
    /// <exception cref="ArgumentException">Thrown if the table or values are not specified.</exception>
19
    public override string BuildInsert(InsertStatement insertStatement)
20
    {
21
        ArgumentNullException.ThrowIfNull(insertStatement);
4✔
22

23
        if (insertStatement.TableExpression == null)
4!
UNCOV
24
            throw new ArgumentException("No table specified to insert into", nameof(insertStatement));
×
25

26
        if (insertStatement.ValueExpressions == null || insertStatement.ValueExpressions.Count == 0)
4!
UNCOV
27
            throw new ArgumentException("No values specified for insert", nameof(insertStatement));
×
28

29
        var insertBuilder = StringBuilderCache.Acquire();
4✔
30

31
        if (insertStatement.CommentExpressions?.Count > 0)
4!
32
        {
33
            insertBuilder
3✔
34
                .AppendJoin(Environment.NewLine, insertStatement.CommentExpressions)
3✔
35
                .AppendLine();
3✔
36
        }
37

38
        var table = TableExpression(insertStatement.TableExpression);
4✔
39
        insertBuilder
4✔
40
            .Append("INSERT INTO ")
4✔
41
            .Append(table);
4✔
42

43
        if (insertStatement.ColumnExpressions?.Count > 0)
4!
44
        {
45
            insertBuilder
4✔
46
                .Append(" (")
4✔
47
                .AppendJoin(", ", insertStatement.ColumnExpressions.Select(ColumnExpression))
4✔
48
                .Append(")");
4✔
49
        }
50

51
        insertBuilder
4✔
52
            .AppendLine()
4✔
53
            .Append("VALUES ")
4✔
54
            .Append("(")
4✔
55
            .AppendJoin(", ", insertStatement.ValueExpressions)
4✔
56
            .Append(")");
4✔
57

58
        if (insertStatement.OutputExpressions?.Count > 0)
4!
59
        {
60
            insertBuilder
3✔
61
                .AppendLine()
3✔
62
                .Append("RETURNING ")
3✔
63
                .AppendJoin(", ", insertStatement.OutputExpressions.Select(ColumnExpression));
3✔
64
        }
65

66
        insertBuilder.AppendLine(";");
4✔
67

68
        return StringBuilderCache.ToString(insertBuilder);
4✔
69
    }
70

71
    /// <summary>
72
    /// Builds a SQL UPSERT statement for PostgreSQL using ON CONFLICT syntax, including support for RETURNING and comments.
73
    /// </summary>
74
    /// <param name="upsertStatement">The <see cref="UpsertStatement"/> containing the UPSERT statement configuration.</param>
75
    /// <returns>A SQL UPSERT statement string for PostgreSQL.</returns>
76
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="upsertStatement"/> is <c>null</c>.</exception>
77
    /// <exception cref="ArgumentException">Thrown if the table, values, keys, or update values are not specified.</exception>
78
    public override string BuildUpsert(UpsertStatement upsertStatement)
79
    {
80
        ValidateUpsert(upsertStatement);
8✔
81

82
        var upsertBuilder = StringBuilderCache.Acquire();
8✔
83

84
        if (upsertStatement.CommentExpressions?.Count > 0)
8!
85
        {
UNCOV
86
            upsertBuilder
×
87
                .AppendJoin(Environment.NewLine, upsertStatement.CommentExpressions)
×
88
                .AppendLine();
×
89
        }
90

91
        var table = TableExpression(upsertStatement.TableExpression);
8✔
92
        upsertBuilder
8✔
93
            .Append("INSERT INTO ")
8✔
94
            .Append(table)
8✔
95
            .Append(" (")
8✔
96
            .AppendJoin(", ", upsertStatement.ColumnExpressions.Select(ColumnExpression))
8✔
97
            .AppendLine(")")
8✔
98
            .Append("VALUES (")
8✔
99
            .AppendJoin(", ", upsertStatement.ValueExpressions)
8✔
100
            .AppendLine(")")
8✔
101
            .Append("ON CONFLICT (")
8✔
102
            .AppendJoin(", ", upsertStatement.KeyExpressions.Select(ColumnExpression))
8✔
103
            .AppendLine(") DO UPDATE")
8✔
104
            .Append("SET ")
8✔
105
            .AppendJoin(", ", upsertStatement.UpdateExpressions.Select(u => $"{ColumnExpression(u)} = EXCLUDED.{ColumnExpression(u)}"));
8✔
106

107
        if (upsertStatement.OutputExpressions?.Count > 0)
8!
108
        {
109
            upsertBuilder
3✔
110
                .AppendLine()
3✔
111
                .Append("RETURNING ")
3✔
112
                .AppendJoin(", ", upsertStatement.OutputExpressions.Select(ColumnExpression));
3✔
113
        }
114

115
        upsertBuilder.AppendLine(";");
8✔
116

117
        return StringBuilderCache.ToString(upsertBuilder);
8✔
118
    }
119

120
    /// <summary>
121
    /// Builds a SQL UPDATE statement for PostgreSQL, including support for FROM, JOIN, WHERE, RETURNING, and comments.
122
    /// </summary>
123
    /// <param name="updateStatement">The <see cref="UpdateStatement"/> containing the UPDATE statement configuration.</param>
124
    /// <returns>A SQL UPDATE statement string for PostgreSQL.</returns>
125
    /// <exception cref="ArgumentException">Thrown if the table or update values are not specified.</exception>
126
    public override string BuildUpdate(UpdateStatement updateStatement)
127
    {
128
        if (updateStatement.UpdateExpressions == null || updateStatement.UpdateExpressions.Count == 0)
2!
UNCOV
129
            throw new ArgumentException("No values specified for update", nameof(updateStatement));
×
130

131
        var updateBuilder = StringBuilderCache.Acquire();
2✔
132

133
        if (updateStatement.CommentExpressions?.Count > 0)
2!
134
        {
135
            updateBuilder
1✔
136
                .AppendJoin(Environment.NewLine, updateStatement.CommentExpressions)
1✔
137
                .AppendLine();
1✔
138
        }
139

140
        var table = TableExpression(updateStatement.TableExpression);
2✔
141

142
        updateBuilder
2✔
143
            .Append("UPDATE ")
2✔
144
            .Append(table)
2✔
145
            .AppendLine()
2✔
146
            .Append("SET ")
2✔
147
            .AppendJoin(", ", updateStatement.UpdateExpressions.Select(UpdateExpression));
2✔
148

149
        if (updateStatement.FromExpressions?.Count > 0)
2!
150
        {
UNCOV
151
            updateBuilder
×
152
                .AppendLine()
×
153
                .Append("FROM ")
×
154
                .AppendJoin(", ", updateStatement.FromExpressions.Select(TableExpression));
×
155
        }
156

157
        if (updateStatement.JoinExpressions?.Count > 0)
2!
158
        {
UNCOV
159
            foreach (var joinExpression in updateStatement.JoinExpressions)
×
160
            {
UNCOV
161
                updateBuilder
×
162
                    .AppendLine()
×
163
                    .Append(JoinExpression(joinExpression));
×
164
            }
165
        }
166

167
        if (updateStatement.WhereExpressions?.Count > 0)
2!
168
        {
169
            updateBuilder
2✔
170
                .AppendLine()
2✔
171
                .Append("WHERE ")
2✔
172
                .Append("(")
2✔
173
                .AppendJoin(" AND ", updateStatement.WhereExpressions.Select(WhereExpression))
2✔
174
                .Append(")");
2✔
175
        }
176

177
        if (updateStatement.OutputExpressions?.Count > 0)
2!
178
        {
179
            updateBuilder
1✔
180
                .AppendLine()
1✔
181
                .Append("RETURNING ")
1✔
182
                .AppendJoin(", ", updateStatement.OutputExpressions.Select(ColumnExpression));
1✔
183
        }
184

185
        updateBuilder.AppendLine(";");
2✔
186

187
        return StringBuilderCache.ToString(updateBuilder);
2✔
188
    }
189

190
    /// <summary>
191
    /// Builds a SQL DELETE statement for PostgreSQL, including support for FROM, JOIN, WHERE, RETURNING, and comments.
192
    /// </summary>
193
    /// <param name="deleteStatement">The <see cref="DeleteStatement"/> containing the DELETE statement configuration.</param>
194
    /// <returns>A SQL DELETE statement string for PostgreSQL.</returns>
195
    /// <exception cref="ArgumentException">Thrown if the table is not specified.</exception>
196
    public override string BuildDelete(DeleteStatement deleteStatement)
197
    {
198
        if (deleteStatement.TableExpression == null)
1!
UNCOV
199
            throw new ArgumentException("No table specified to delete from", nameof(deleteStatement));
×
200

201
        var deleteBuilder = StringBuilderCache.Acquire();
1✔
202

203
        if (deleteStatement.CommentExpressions?.Count > 0)
1!
204
        {
205
            deleteBuilder
1✔
206
                .AppendJoin(Environment.NewLine, deleteStatement.CommentExpressions)
1✔
207
                .AppendLine();
1✔
208
        }
209

210
        var table = TableExpression(deleteStatement.TableExpression);
1✔
211

212
        deleteBuilder
1✔
213
            .Append("DELETE FROM ")
1✔
214
            .Append(table);
1✔
215

216
        if (deleteStatement.FromExpressions?.Count > 0)
1!
217
        {
UNCOV
218
            deleteBuilder
×
219
                .AppendLine()
×
220
                .Append("FROM ")
×
221
                .AppendJoin(", ", deleteStatement.FromExpressions.Select(TableExpression));
×
222
        }
223

224
        if (deleteStatement.JoinExpressions?.Count > 0)
1!
225
        {
UNCOV
226
            foreach (var joinExpression in deleteStatement.JoinExpressions)
×
227
            {
UNCOV
228
                deleteBuilder
×
229
                    .AppendLine()
×
230
                    .Append(JoinExpression(joinExpression));
×
231
            }
232
        }
233

234
        if (deleteStatement.WhereExpressions?.Count > 0)
1!
235
        {
236
            deleteBuilder
1✔
237
                .AppendLine()
1✔
238
                .Append("WHERE ")
1✔
239
                .Append("(")
1✔
240
                .AppendJoin(" AND ", deleteStatement.WhereExpressions.Select(WhereExpression))
1✔
241
                .Append(")");
1✔
242
        }
243

244
        if (deleteStatement.OutputExpressions?.Count > 0)
1!
245
        {
246
            deleteBuilder
1✔
247
                .AppendLine()
1✔
248
                .Append("RETURNING ")
1✔
249
                .AppendJoin(", ", deleteStatement.OutputExpressions.Select(ColumnExpression));
1✔
250
        }
251

252
        deleteBuilder.AppendLine(";");
1✔
253

254
        return StringBuilderCache.ToString(deleteBuilder);
1✔
255
    }
256

257
    /// <summary>
258
    /// Builds a SQL table expression for PostgreSQL, omitting schema (PostgreSQL does not support schema in the same way as SQL Server).
259
    /// </summary>
260
    /// <param name="tableExpression">The <see cref="TableExpression"/> representing the table.</param>
261
    /// <returns>A SQL table expression string for PostgreSQL.</returns>
262
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="tableExpression"/> is <c>null</c>.</exception>
263
    public override string TableExpression(TableExpression tableExpression)
264
    {
265
        ArgumentNullException.ThrowIfNull(tableExpression);
26✔
266

267
        // sqlite doesn't support schema
268
        tableExpression = tableExpression with { TableSchema = null };
26✔
269

270
        return base.TableExpression(tableExpression);
26✔
271
    }
272

273
    /// <summary>
274
    /// Builds a SQL WHERE expression for PostgreSQL, handling various filter operators and parameterization.
275
    /// </summary>
276
    /// <param name="whereExpression">The <see cref="WhereExpression"/> representing the condition.</param>
277
    /// <returns>A SQL WHERE expression string for PostgreSQL.</returns>
278
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="whereExpression"/> is <c>null</c>.</exception>
279
    /// <exception cref="ArgumentException">Thrown if required properties are not specified.</exception>
280
    public override string WhereExpression(WhereExpression whereExpression)
281
    {
282
        ArgumentNullException.ThrowIfNull(whereExpression);
12✔
283

284
        if (string.IsNullOrWhiteSpace(whereExpression.ColumnName))
12!
285
            throw new ArgumentException($"'{nameof(whereExpression.ColumnName)}' property cannot be null or empty.", nameof(whereExpression));
×
286

287
        if (whereExpression.IsRaw)
12✔
288
            return whereExpression.ColumnName;
1✔
289

290
        var parameterlessFilters = new[] { FilterOperators.IsNull, FilterOperators.IsNotNull };
11✔
291
        if (!parameterlessFilters.Contains(whereExpression.FilterOperator) && string.IsNullOrWhiteSpace(whereExpression.ParameterName))
11!
UNCOV
292
            throw new ArgumentException($"'{nameof(whereExpression.ParameterName)}' property cannot be null or empty.", nameof(whereExpression));
×
293

294
        var columnName = ColumnExpression(whereExpression);
11✔
295

296
        return whereExpression.FilterOperator switch
11!
297
        {
11✔
UNCOV
298
            FilterOperators.StartsWith => $"{columnName} LIKE {whereExpression.ParameterName} || '%'",
×
UNCOV
299
            FilterOperators.EndsWith => $"{columnName} LIKE '%' || {whereExpression.ParameterName}",
×
300
            FilterOperators.Contains => $"{columnName} LIKE '%' || {whereExpression.ParameterName} || '%'",
2✔
301
            FilterOperators.Equal => $"{columnName} = {whereExpression.ParameterName}",
7✔
302
            FilterOperators.NotEqual => $"{columnName} != {whereExpression.ParameterName}",
1✔
UNCOV
303
            FilterOperators.LessThan => $"{columnName} < {whereExpression.ParameterName}",
×
UNCOV
304
            FilterOperators.LessThanOrEqual => $"{columnName} <= {whereExpression.ParameterName}",
×
UNCOV
305
            FilterOperators.GreaterThan => $"{columnName} > {whereExpression.ParameterName}",
×
306
            FilterOperators.GreaterThanOrEqual => $"{columnName} >= {whereExpression.ParameterName}",
×
307
            FilterOperators.IsNull => $"{columnName} IS NULL",
×
308
            FilterOperators.IsNotNull => $"{columnName} IS NOT NULL",
×
309
            FilterOperators.In => $"{columnName} IN ({whereExpression.ParameterName})",
1✔
310
            _ => $"{columnName} = {whereExpression.ParameterName}",
×
311
        };
11✔
312
    }
313

314
    /// <summary>
315
    /// Builds a SQL LIMIT/OFFSET expression for PostgreSQL.
316
    /// </summary>
317
    /// <param name="limitExpression">The <see cref="LimitExpression"/> representing the limit and offset.</param>
318
    /// <returns>A SQL LIMIT/OFFSET expression string for PostgreSQL, or an empty string if not applicable.</returns>
319
    public override string LimitExpression(LimitExpression limitExpression)
320
    {
321
        if (limitExpression is null || limitExpression.Size == 0)
4!
UNCOV
322
            return string.Empty;
×
323

324
        return $"LIMIT {limitExpression.Size} OFFSET {limitExpression.Offset}";
4✔
325
    }
326

327
    /// <summary>
328
    /// Quotes an identifier (such as a table or column name) for PostgreSQL, using double quotes.
329
    /// </summary>
330
    /// <param name="name">The identifier to quote.</param>
331
    /// <returns>The quoted identifier, or the original name if quoting is not required.</returns>
332
    public override string QuoteIdentifier(string name)
333
    {
334
        if (name.IsNullOrWhiteSpace())
268!
UNCOV
335
            return string.Empty;
×
336

337
        if (name == "*")
268✔
338
            return name;
1✔
339

340
        if (name.StartsWith("\"") && name.EndsWith("\""))
267!
UNCOV
341
            return name;
×
342

343
        return "\"" + name.Replace("\"", "\"\"") + "\"";
267✔
344
    }
345

346
    /// <summary>
347
    /// Parses a quoted identifier and returns the unquoted name for PostgreSQL.
348
    /// </summary>
349
    /// <param name="name">The quoted identifier.</param>
350
    /// <returns>The unquoted identifier name.</returns>
351
    public override string ParseIdentifier(string name)
352
    {
UNCOV
353
        if (name.StartsWith("\"") && name.EndsWith("\""))
×
UNCOV
354
            return name.Substring(1, name.Length - 2);
×
355

356
        return name;
×
357
    }
358
}
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