Skip to content

Commit 4dee8d2

Browse files
feat: support uuid as id column in Postgres (#261)
* feat: support uuid as id column * fix: add docs on data types that can be used as ids for :execlastid annotation
1 parent 70803db commit 4dee8d2

24 files changed

Lines changed: 104 additions & 94 deletions

Drivers/ColumnMapping.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class ColumnMapping(
1111
Func<int, string>? readerArrayFn = null,
1212
string? usingDirective = null,
1313
Func<string, bool, bool, string>? writerFn = null,
14-
string? convertFunc = null,
14+
Func<string, string>? convertFunc = null,
1515
string? sqlMapper = null,
1616
string? sqlMapperImpl = null)
1717
{
@@ -20,7 +20,7 @@ public class ColumnMapping(
2020
public Func<int, string>? ReaderArrayFn { get; } = readerArrayFn;
2121
public string? UsingDirective { get; } = usingDirective;
2222
public Func<string, bool, bool, string>? WriterFn { get; } = writerFn;
23-
public string? ConvertFunc { get; } = convertFunc;
23+
public Func<string, string>? ConvertFunc { get; } = convertFunc;
2424
public string? SqlMapper { get; } = sqlMapper;
2525
public string? SqlMapperImpl { get; } = sqlMapperImpl;
2626
}

Drivers/DbDriver.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ public abstract class DbDriver
5555
"NpgsqlCidr",
5656
];
5757

58-
protected const string IntTo32 = "Convert.ToInt32";
59-
protected const string IntTo64 = "Convert.ToInt64";
60-
6158

6259
public abstract Dictionary<string, ColumnMapping> ColumnMappings { get; }
6360

@@ -339,11 +336,13 @@ public string GetIdColumnType(Query query)
339336
public virtual string[] GetLastIdStatement(Query query)
340337
{
341338
var idColumnType = GetIdColumnType(query);
342-
var convertFunc = ColumnMappings[idColumnType].ConvertFunc ?? throw new InvalidOperationException($"ConvertFunc is missing for id column type {idColumnType}");
339+
var convertFunc = ColumnMappings[idColumnType].ConvertFunc ??
340+
throw new InvalidOperationException($"ConvertFunc is missing for id column type {idColumnType}");
341+
var convertFuncCall = convertFunc(Variable.Result.AsVarName());
343342
return
344343
[
345344
$"var {Variable.Result.AsVarName()} = await {Variable.Command.AsVarName()}.ExecuteScalarAsync();",
346-
$"return {convertFunc}({Variable.Result.AsVarName()});"
345+
$"return {convertFuncCall};"
347346
];
348347
}
349348

Drivers/MySqlConnectorDriver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public partial class MySqlConnectorDriver(
4848
{ "bigint", new() }
4949
},
5050
ordinal => $"reader.GetInt64({ordinal})",
51-
convertFunc: IntTo64
51+
convertFunc: x => $"Convert.ToInt64{x}"
5252
),
5353
["byte"] = new ColumnMapping(
5454
new()
@@ -99,7 +99,7 @@ public partial class MySqlConnectorDriver(
9999
{ "mediumint", new() }
100100
},
101101
ordinal => $"reader.GetInt32({ordinal})",
102-
convertFunc: IntTo32
102+
convertFunc: x => $"Convert.ToInt32{x}"
103103
),
104104
["double"] = new(
105105
new()

Drivers/NpgsqlDriver.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public NpgsqlDriver(
4343
},
4444
readerFn: ordinal => $"reader.GetInt64({ordinal})",
4545
readerArrayFn: ordinal => $"reader.GetFieldValue<long[]>({ordinal})",
46-
convertFunc: IntTo64
46+
convertFunc: x => $"Convert.ToInt64({x})"
4747
),
4848
["byte[]"] = new(
4949
new()
@@ -81,7 +81,8 @@ public NpgsqlDriver(
8181
{ "uuid", new() }
8282
},
8383
readerFn: ordinal => $"reader.GetFieldValue<Guid>({ordinal})",
84-
readerArrayFn: ordinal => $"reader.GetFieldValue<Guid[]>({ordinal})"
84+
readerArrayFn: ordinal => $"reader.GetFieldValue<Guid[]>({ordinal})",
85+
convertFunc: x => $"Guid.Parse({x}?.ToString())"
8586
),
8687
["TimeSpan"] = new(
8788
new()
@@ -126,7 +127,8 @@ public NpgsqlDriver(
126127
{ "int2", new() }
127128
},
128129
readerFn: ordinal => $"reader.GetInt16({ordinal})",
129-
readerArrayFn: ordinal => $"reader.GetFieldValue<short[]>({ordinal})"
130+
readerArrayFn: ordinal => $"reader.GetFieldValue<short[]>({ordinal})",
131+
convertFunc: x => $"Convert.ToInt16({x})"
130132
),
131133
["int"] = new(
132134
new()
@@ -137,7 +139,8 @@ public NpgsqlDriver(
137139
{ "serial", new() }
138140
},
139141
readerFn: ordinal => $"reader.GetInt32({ordinal})",
140-
readerArrayFn: ordinal => $"reader.GetFieldValue<int[]>({ordinal})"
142+
readerArrayFn: ordinal => $"reader.GetFieldValue<int[]>({ordinal})",
143+
convertFunc: x => $"Convert.ToInt32({x})"
141144
),
142145
["float"] = new(
143146
new()

Drivers/SqliteDriver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public partial class SqliteDriver(
4040
{ "integernotnulldefaultunixepoch", new() }
4141
},
4242
ordinal => $"reader.GetInt32({ordinal})",
43-
convertFunc: IntTo32
43+
convertFunc: x => $"Convert.ToInt32({x})"
4444
),
4545
["decimal"] = new(
4646
new()

docs/04_Postgres.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22
<details>
33
<summary>:execlastid - Implementation</summary>
44

5-
Implemented via a `RETURNING` clause, allowing the `INSERT` command to return the newly created id, which can be of any
6-
data type that can have a unique constraint.
5+
Implemented via a `RETURNING` clause, allowing the `INSERT` command to return the newly created id.
6+
The data types that can be used as id data types for this annotation are:
7+
1. uuid
8+
2. bigint
9+
3. integer
10+
4. smallint (less recommended due to small id range, but possible)
11+
12+
```sql
13+
INSERT INTO tab1 (field1, field2) VALUES ('a', 1) RETURNING id_field;
14+
```
715
</details>
816

917
<details>

docs/06_Sqlite.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
<summary>:execlastid - Implementation</summary>
44

55
## :execlastid - Implementation
6-
Implemented via a `RETURNING` clause, allowing the `INSERT` command to return the newly created id, which can be of any
7-
data type that can have a unique constraint.
8-
6+
Implemented via a `RETURNING` clause, allowing the `INSERT` command to return the newly created id.
7+
Only integer data type is supported as id for this annotation.
8+
99
```sql
1010
INSERT INTO tab1 (field1, field2) VALUES ('a', 1) RETURNING id_field;
1111
```
12-
1312
</details>
1413

1514
<details>

examples/NpgsqlDapperExample/Models.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class Author
1414
};
1515
public class Book
1616
{
17-
public required long Id { get; init; }
17+
public required Guid Id { get; init; }
1818
public required string Name { get; init; }
1919
public required long AuthorId { get; init; }
2020
public string? Description { get; init; }

examples/NpgsqlDapperExample/QuerySql.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,14 +361,14 @@ public async Task<List<GetAuthorsByIdsAndNamesRow>> GetAuthorsByIdsAndNames(GetA
361361
private const string CreateBookSql = "INSERT INTO books (name, author_id) VALUES (@name, @author_id) RETURNING id";
362362
public class CreateBookRow
363363
{
364-
public required long Id { get; init; }
364+
public required Guid Id { get; init; }
365365
};
366366
public class CreateBookArgs
367367
{
368368
public required string Name { get; init; }
369369
public required long AuthorId { get; init; }
370370
};
371-
public async Task<long> CreateBook(CreateBookArgs args)
371+
public async Task<Guid> CreateBook(CreateBookArgs args)
372372
{
373373
var queryParams = new Dictionary<string, object?>();
374374
queryParams.Add("name", args.Name);
@@ -377,7 +377,7 @@ public async Task<long> CreateBook(CreateBookArgs args)
377377
{
378378
using (var connection = new NpgsqlConnection(ConnectionString))
379379
{
380-
return await connection.QuerySingleAsync<long>(CreateBookSql, queryParams);
380+
return await connection.QuerySingleAsync<Guid>(CreateBookSql, queryParams);
381381
}
382382
}
383383

@@ -386,7 +386,7 @@ public async Task<long> CreateBook(CreateBookArgs args)
386386
throw new System.InvalidOperationException("Transaction is provided, but its connection is null.");
387387
}
388388

389-
return await this.Transaction.Connection.QuerySingleAsync<long>(CreateBookSql, queryParams, transaction: this.Transaction);
389+
return await this.Transaction.Connection.QuerySingleAsync<Guid>(CreateBookSql, queryParams, transaction: this.Transaction);
390390
}
391391

392392
private const string ListAllAuthorsBooksSql = "SELECT authors . id , authors . name, authors . bio, books . id, books . name, books . author_id, books . description FROM authors INNER JOIN books ON authors . id = books . author_id ORDER BY authors . name ";
@@ -407,7 +407,7 @@ public async Task<List<ListAllAuthorsBooksRow>> ListAllAuthorsBooks()
407407
{
408408
var result = new List<ListAllAuthorsBooksRow>();
409409
while (await reader.ReadAsync())
410-
result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
410+
result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetFieldValue<Guid>(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
411411
return result;
412412
}
413413
}
@@ -424,7 +424,7 @@ public async Task<List<ListAllAuthorsBooksRow>> ListAllAuthorsBooks()
424424
{
425425
var result = new List<ListAllAuthorsBooksRow>();
426426
while (await reader.ReadAsync())
427-
result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
427+
result.Add(new ListAllAuthorsBooksRow { Author = new Author { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2) }, Book = new Book { Id = reader.GetFieldValue<Guid>(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
428428
return result;
429429
}
430430
}
@@ -496,7 +496,7 @@ public async Task<List<GetAuthorsByBookNameRow>> GetAuthorsByBookName(GetAuthors
496496
{
497497
var result = new List<GetAuthorsByBookNameRow>();
498498
while (await reader.ReadAsync())
499-
result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
499+
result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetFieldValue<Guid>(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
500500
return result;
501501
}
502502
}
@@ -514,7 +514,7 @@ public async Task<List<GetAuthorsByBookNameRow>> GetAuthorsByBookName(GetAuthors
514514
{
515515
var result = new List<GetAuthorsByBookNameRow>();
516516
while (await reader.ReadAsync())
517-
result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetInt64(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
517+
result.Add(new GetAuthorsByBookNameRow { Id = reader.GetInt64(0), Name = reader.GetString(1), Bio = reader.IsDBNull(2) ? null : reader.GetString(2), Book = new Book { Id = reader.GetFieldValue<Guid>(3), Name = reader.GetString(4), AuthorId = reader.GetInt64(5), Description = reader.IsDBNull(6) ? null : reader.GetString(6) } });
518518
return result;
519519
}
520520
}

examples/NpgsqlDapperExample/request.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"name": "books"
7676
},
7777
"type": {
78-
"name": "bigserial"
78+
"name": "uuid"
7979
}
8080
},
8181
{
@@ -33003,7 +33003,7 @@
3300333003
"name": "books"
3300433004
},
3300533005
"type": {
33006-
"name": "bigserial"
33006+
"name": "uuid"
3300733007
},
3300833008
"originalName": "id"
3300933009
}

0 commit comments

Comments
 (0)