Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
bc8c54e
Add NpgsqlLogSequenceNumber and PgLsnHandler to handle the pg_lsn type
Brar Sep 12, 2020
73cdc62
Convert timestamp and timestamp with time zone directly to DateTime
Brar Sep 23, 2020
8cdacfa
Implement the PostgreSQL Streaming Replication Protocol
Brar Sep 13, 2020
53e75ff
Disable feedback timers when cancelling
Brar Oct 31, 2020
8e49da7
Remove [PublicAPI]
roji Nov 1, 2020
8eb4de6
Update test/Npgsql.Tests/Replication/SafeReplicationTestBase.cs
roji Nov 1, 2020
f44caa7
Update src/Npgsql/Replication/NpgsqlReplicationConnection.cs
roji Nov 1, 2020
72f04b4
Small fixes and nits
roji Nov 1, 2020
62e25cf
Simplify some test names
roji Nov 1, 2020
d31df95
Tweak name setup in tests to use [CallMemberName] etc.
roji Nov 1, 2020
dbf3e36
DRY some PG version checks in tests
roji Nov 1, 2020
71d4e55
Add some missing cancellation token checks
roji Nov 1, 2020
baf6c85
Update src/Npgsql/Replication/Logical/Internal/NpgsqlLogicalReplicati…
roji Nov 1, 2020
99a3e4f
Remove some needed cancellation token checks
roji Nov 1, 2020
4aae8c4
Cleanup of LSN
roji Nov 1, 2020
781ee73
Make cancellation token mandatory for StartReplication
roji Nov 1, 2020
c760050
Ignore ReplicationSurvivesPausesLongerThanWalSenderTimeout except on CI
roji Nov 1, 2020
1147c74
Add some more PG config checks for skipping tests that would otherwis…
roji Nov 1, 2020
2399af0
Correct mess done in last commit
roji Nov 1, 2020
1ae6d3b
Refactor StartReplication methods
roji Nov 1, 2020
6848312
Read from IAsyncEnumerable directly in tests
roji Nov 1, 2020
6cf84ef
Clean up state management mechanism
Brar Nov 1, 2020
9a3a37e
Fix truncate PG version checks in tests being a bit too DRY
Brar Nov 1, 2020
bb60011
Fix TimelineHistoryNonExisting test for PG10 physical replication
Brar Nov 1, 2020
c64cbf7
Clean up some leftover stuff
Brar Nov 1, 2020
b0b2329
Tiny assertion tweak
roji Nov 2, 2020
ebed714
Make null-terminated string reading more robust
roji Nov 3, 2020
a39b6a6
Make some methods local
roji Nov 5, 2020
4825c12
And another local method
roji Nov 5, 2020
1b14b5f
Refactor namespaces and type names
roji Nov 2, 2020
a3e965a
Recycle logical replication messages
roji Nov 6, 2020
03d6a95
Slight simplification in TestDecoding
roji Nov 6, 2020
461ea05
Recycle XLogData too
roji Nov 6, 2020
15aec30
Handle all feedback locking inside SendFeedback
roji Nov 6, 2020
6b09f21
Refactor command creation
roji Nov 6, 2020
b0360b3
Recycle test_decoding message as well
roji Nov 6, 2020
89f32f5
Fix tiny namespace confusion
roji Nov 6, 2020
873fc42
Fixup
roji Nov 6, 2020
1908979
Remove Npgsql prefix everywhere
roji Nov 6, 2020
f53b581
Add constructors with connection strings to connections
roji Nov 6, 2020
4c1da9f
Fixup
roji Nov 6, 2020
416d414
Fix StringBuilder.AppendJoin() missing in .NET Standard 2.0
Brar Nov 6, 2020
3be4e28
And remove another unused thing
roji Nov 8, 2020
a3ed79e
Revert "And remove another unused thing"
roji Nov 8, 2020
c781f9d
Timer concurrency logic
roji Nov 8, 2020
110ec52
Disable cancel-on-dispose for now
roji Nov 8, 2020
38d2238
Implement proper cancel-on-dispose
roji Nov 8, 2020
6dd1fc3
Fix up some silliness
roji Nov 8, 2020
324fc8b
Provide more information when cancellation fails in tests
roji Nov 8, 2020
744e7b9
Work on logical plugin options
roji Nov 8, 2020
cba3374
Rename OpenAsync to Open
roji Nov 8, 2020
405f0e2
Actually rename OpenAsync to Open
roji Nov 8, 2020
3226d14
Use the regular user action mechanism for state management
roji Nov 8, 2020
faf49e6
Make physical replication tests explicit for now
roji Nov 8, 2020
6afa695
Skip empty transactions in tests
roji Nov 8, 2020
43fbc68
Add @Brar as replication code owner
roji Nov 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* @roji @YohDeadfall
* @roji @YohDeadfall
/src/Npgsql/Replication/ @Brar
12 changes: 12 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,16 @@ jobs:
sudo sed -i 's/#ssl = off/ssl = on/' $PGDATA/postgresql.conf
sudo sed -i 's/#max_prepared_transactions = 0/max_prepared_transactions = 10/' $PGDATA/postgresql.conf
sudo sed -i 's/#password_encryption = md5/password_encryption = scram-sha-256/' $PGDATA/postgresql.conf
sudo sed -i 's/#wal_level =/wal_level = logical #/' $PGDATA/postgresql.conf
sudo sed -i 's/#max_wal_senders =/max_wal_senders = 50 #/' $PGDATA/postgresql.conf
sudo sed -i 's/#wal_sender_timeout =/wal_sender_timeout = 3s #/' $PGDATA/postgresql.conf
sudo sed -i "s/#synchronous_standby_names =/synchronous_standby_names = 'npgsql_test_sync_standby' #/" $PGDATA/postgresql.conf
sudo sed -i "s/#synchronous_commit =/synchronous_commit = local #/" $PGDATA/postgresql.conf
# Disable trust authentication, requiring MD5 passwords - some tests must fail if a password isn't provided.
sudo sh -c "echo 'local all all trust' > $PGDATA/pg_hba.conf"
sudo sh -c "echo 'host all npgsql_tests_scram all scram-sha-256' >> $PGDATA/pg_hba.conf"
sudo sh -c "echo 'host all all all md5' >> $PGDATA/pg_hba.conf"
sudo sh -c "echo 'host replication all all md5' >> $PGDATA/pg_hba.conf"
sudo pg_ctlcluster ${{ matrix.pg_major }} main restart

# user 'npgsql_tests_scram' must be created with password encrypted as scram-sha-256 (which only applies after restart)
Expand Down Expand Up @@ -114,6 +120,11 @@ jobs:
pgsql/bin/initdb -D pgsql/PGDATA -E UTF8 -U postgres
SOCKET_DIR=$(echo "$LOCALAPPDATA\Temp" | sed 's|\\|/|g')
sed -i "s|#unix_socket_directories = ''|unix_socket_directories = '$SOCKET_DIR'|" pgsql/PGDATA/postgresql.conf
sed -i "s|#wal_level =|wal_level = logical #|" pgsql/PGDATA/postgresql.conf
sed -i "s|#max_wal_senders =|max_wal_senders = 50 #|" pgsql/PGDATA/postgresql.conf
sed -i "s|#wal_sender_timeout =|wal_sender_timeout = 3s #|" pgsql/PGDATA/postgresql.conf
sed -i "s|#synchronous_standby_names =|synchronous_standby_names = 'npgsql_test_sync_standby' #|" pgsql/PGDATA/postgresql.conf
sed -i "s|#synchronous_commit =|synchronous_commit = local #|" pgsql/PGDATA/postgresql.conf
pgsql/bin/pg_ctl -D pgsql/PGDATA -l logfile -o '-c max_prepared_transactions=10 -c ssl=true -c ssl_cert_file=../server.crt -c ssl_key_file=../server.key' start

# Configure test account
Expand All @@ -133,6 +144,7 @@ jobs:
# Disable trust authentication, requiring MD5 passwords - some tests must fail if a password isn't provided.
echo "host all npgsql_tests_scram all scram-sha-256" > pgsql/PGDATA/pg_hba.conf
echo "host all all all md5" >> pgsql/PGDATA/pg_hba.conf
echo "host replication all all md5" >> pgsql/PGDATA/pg_hba.conf

# TODO: Once test/Npgsql.Specification.Tests work, switch to just testing on the solution
- name: Test
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<NoWarn>$(NoWarn);NU5105</NoWarn>

<!-- Language -->
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisLevel>latest</AnalysisLevel>
Expand Down
1 change: 1 addition & 0 deletions Npgsql.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=MSDTC/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=multiquery/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Noda/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=NOEXPORT/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Npgsql/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Npgsql_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PGTZ/@EntryIndexedValue">True</s:Boolean>
Expand Down
1 change: 1 addition & 0 deletions src/Npgsql/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static class FrontendMessageCode
internal const byte Bind = (byte)'B';
internal const byte Close = (byte)'C';
internal const byte Query = (byte)'Q';
internal const byte CopyData = (byte)'d';
internal const byte CopyDone = (byte)'c';
internal const byte CopyFail = (byte)'f';
internal const byte Terminate = (byte)'X';
Expand Down
17 changes: 9 additions & 8 deletions src/Npgsql/NpgsqlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,14 +462,15 @@ public ConnectionState FullState
? ConnectionState.Open // When unbound, we only know we're open
: Connector.State switch
{
ConnectorState.Ready => ConnectionState.Open,
ConnectorState.Executing => ConnectionState.Open | ConnectionState.Executing,
ConnectorState.Fetching => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Copy => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Waiting => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Connecting => ConnectionState.Connecting,
ConnectorState.Broken => ConnectionState.Broken,
ConnectorState.Closed => throw new InvalidOperationException("Internal Npgsql bug: connection is in state Open but connector is in state Closed"),
ConnectorState.Ready => ConnectionState.Open,
ConnectorState.Executing => ConnectionState.Open | ConnectionState.Executing,
ConnectorState.Fetching => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Copy => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Replication => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Waiting => ConnectionState.Open | ConnectionState.Fetching,
ConnectorState.Connecting => ConnectionState.Connecting,
ConnectorState.Broken => ConnectionState.Broken,
ConnectorState.Closed => throw new InvalidOperationException("Internal Npgsql bug: connection is in state Open but connector is in state Closed"),
_ => throw new InvalidOperationException($"Internal Npgsql bug: unexpected value {Connector.State} of enum {nameof(ConnectorState)}. Please file a bug.")
},
_ => _fullState
Expand Down
53 changes: 52 additions & 1 deletion src/Npgsql/NpgsqlConnectionStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using System.Reflection;
using System.Text;
using Npgsql.Replication;

namespace Npgsql
{
Expand Down Expand Up @@ -89,7 +90,7 @@ void Init()
static NpgsqlConnectionStringBuilder()
{
var properties = typeof(NpgsqlConnectionStringBuilder)
.GetProperties()
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(p => p.GetCustomAttribute<NpgsqlConnectionStringPropertyAttribute>() != null)
.ToArray();

Expand Down Expand Up @@ -1252,6 +1253,30 @@ public bool LoadTableComposites
}
bool _loadTableComposites;

/// <summary>
/// Set the replication mode of the connection
/// </summary>
/// <remarks>
/// This property and its corresponding enum are intentionally kept internal as they
/// should not be set by users or even be visible in their connection strings.
/// Replication connections are a special kind of connection that is encapsulated in
/// <see cref="PhysicalReplicationConnection"/>
/// and <see cref="LogicalReplicationConnection"/>.
/// </remarks>

[NpgsqlConnectionStringProperty]
[DisplayName("Replication Mode")]
internal ReplicationMode ReplicationMode
{
get => _replicationMode;
set
{
_replicationMode = value;
SetValue(nameof(ReplicationMode), value);
}
}
ReplicationMode _replicationMode;

/// <summary>
/// Set PostgreSQL configuration parameter default values for the connection.
/// </summary>
Expand Down Expand Up @@ -1698,5 +1723,31 @@ public enum SslMode
Require,
}

/// <summary>
/// Specifies whether the connection shall be initialized as a physical or
/// logical replication connection
/// </summary>
/// <remarks>
/// This enum and its corresponding property are intentionally kept internal as they
/// should not be set by users or even be visible in their connection strings.
/// Replication connections are a special kind of connection that is encapsulated in
/// <see cref="PhysicalReplicationConnection"/>
/// and <see cref="LogicalReplicationConnection"/>.
/// </remarks>
enum ReplicationMode
{
/// <summary>
/// Replication disabled. This is the default
/// </summary>
Off,
/// <summary>
/// Physical replication enabled
/// </summary>
Physical,
/// <summary>
/// Logical replication enabled
/// </summary>
Logical
}
#endregion
}
41 changes: 31 additions & 10 deletions src/Npgsql/NpgsqlConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ internal void FlagAsWritableForMultiplexing()
CopyInResponseMessage? _copyInResponseMessage;
CopyOutResponseMessage? _copyOutResponseMessage;
CopyDataMessage? _copyDataMessage;
CopyBothResponseMessage? _copyBothResponseMessage;

#endregion

Expand Down Expand Up @@ -393,15 +394,16 @@ internal ConnectorState State
bool IsConnected
=> State switch
{
ConnectorState.Ready => true,
ConnectorState.Executing => true,
ConnectorState.Fetching => true,
ConnectorState.Waiting => true,
ConnectorState.Copy => true,
ConnectorState.Closed => false,
ConnectorState.Connecting => false,
ConnectorState.Broken => false,
_ => throw new ArgumentOutOfRangeException("Unknown state: " + State)
ConnectorState.Ready => true,
ConnectorState.Executing => true,
ConnectorState.Fetching => true,
ConnectorState.Waiting => true,
ConnectorState.Copy => true,
ConnectorState.Replication => true,
ConnectorState.Closed => false,
ConnectorState.Connecting => false,
ConnectorState.Broken => false,
_ => throw new ArgumentOutOfRangeException("Unknown state: " + State)
};

internal bool IsReady => State == ConnectorState.Ready;
Expand Down Expand Up @@ -536,6 +538,16 @@ void WriteStartupMessage(string username)
if (timezone != null)
startupParams["TimeZone"] = timezone;

switch (Settings.ReplicationMode)
{
case ReplicationMode.Logical:
startupParams["replication"] = "database";
break;
case ReplicationMode.Physical:
startupParams["replication"] = "true";
break;
}

WriteStartup(startupParams);
}

Expand Down Expand Up @@ -1281,6 +1293,9 @@ await ReadMessageLong(DataRowLoadingMode.Skip, readingNotifications2: false, isR
return (_copyOutResponseMessage ??= new CopyOutResponseMessage()).Load(ReadBuffer);
case BackendMessageCode.CopyData:
return (_copyDataMessage ??= new CopyDataMessage()).Load(len);
case BackendMessageCode.CopyBothResponse:
return (_copyBothResponseMessage ??= new CopyBothResponseMessage()).Load(ReadBuffer);

case BackendMessageCode.CopyDone:
return CopyDoneMessage.Instance;

Expand Down Expand Up @@ -1834,7 +1849,7 @@ internal void UnprepareAll()
internal UserAction StartUserAction(NpgsqlCommand command)
=> StartUserAction(ConnectorState.Executing, command);

internal UserAction StartUserAction(ConnectorState newState=ConnectorState.Executing, NpgsqlCommand? command = null)
internal UserAction StartUserAction(ConnectorState newState = ConnectorState.Executing, NpgsqlCommand? command = null)
{
// If keepalive is enabled, we must protect state transitions with a SemaphoreSlim
// (which itself must be protected by a lock, since its dispose isn't thread-safe).
Expand Down Expand Up @@ -1884,6 +1899,7 @@ UserAction DoStartUserAction()
case ConnectorState.Executing:
case ConnectorState.Fetching:
case ConnectorState.Waiting:
case ConnectorState.Replication:
case ConnectorState.Connecting:
case ConnectorState.Copy:
var currentCommand = _currentCommand;
Expand Down Expand Up @@ -2205,6 +2221,11 @@ enum ConnectorState
/// The connector is engaged in a COPY operation.
/// </summary>
Copy,

/// <summary>
/// The connector is engaged in streaming replication.
/// </summary>
Replication,
}

#pragma warning disable CA1717
Expand Down
1 change: 1 addition & 0 deletions src/Npgsql/NpgsqlDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ internal async Task Close(bool connectionClosing, bool async)
break;
case ConnectorState.Waiting:
case ConnectorState.Copy:
case ConnectorState.Replication:
Debug.Fail("Bad connector state when closing reader: " + Connector.State);
break;
default:
Expand Down
63 changes: 47 additions & 16 deletions src/Npgsql/NpgsqlReadBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,9 @@ public int Read(Span<byte> output)

public ValueTask<int> ReadAsync(Memory<byte> output, CancellationToken cancellationToken = default)
{
if (output.Length == 0)
return new ValueTask<int>(0);

var readFromBuffer = Math.Min(ReadBytesLeft, output.Length);
if (readFromBuffer > 0)
{
Expand All @@ -513,9 +516,6 @@ public ValueTask<int> ReadAsync(Memory<byte> output, CancellationToken cancellat
return new ValueTask<int>(readFromBuffer);
}

if (output.Length == 0)
return new ValueTask<int>(0);

return ReadAsyncLong();

async ValueTask<int> ReadAsyncLong()
Expand Down Expand Up @@ -549,29 +549,60 @@ public Stream GetStream(int len, bool canSeek)
/// Seeks the first null terminator (\0) and returns the string up to it. The buffer must already
/// contain the entire string and its terminator.
/// </summary>
public string ReadNullTerminatedString() => ReadNullTerminatedString(TextEncoding);
public string ReadNullTerminatedString()
=> ReadNullTerminatedString(TextEncoding, async: false).GetAwaiter().GetResult();

/// <summary>
/// Seeks the first null terminator (\0) and returns the string up to it. The buffer must already
/// contain the entire string and its terminator. If any character could not be decoded, a question
/// mark character is returned instead of throwing an exception.
/// </summary>
public string ReadNullTerminatedStringRelaxed() => ReadNullTerminatedString(RelaxedTextEncoding);
public string ReadNullTerminatedStringRelaxed()
=> ReadNullTerminatedString(RelaxedTextEncoding, async: false).GetAwaiter().GetResult();

public ValueTask<string> ReadNullTerminatedString(bool async, CancellationToken cancellationToken = default)
=> ReadNullTerminatedString(TextEncoding, async, cancellationToken);

/// <summary>
/// Seeks the first null terminator (\0) and returns the string up to it. The buffer must already
/// contain the entire string and its terminator.
/// Seeks the first null terminator (\0) and returns the string up to it. Reads additional data from the network if a null
/// terminator isn't found in the buffered data.
/// </summary>
/// <param name="encoding">Decodes the messages with this encoding.</param>
string ReadNullTerminatedString(Encoding encoding)
ValueTask<string> ReadNullTerminatedString(Encoding encoding, bool async, CancellationToken cancellationToken = default)
{
int i;
for (i = ReadPosition; Buffer[i] != 0; i++)
Debug.Assert(i <= ReadPosition + ReadBytesLeft);
Debug.Assert(i >= ReadPosition);
var result = encoding.GetString(Buffer, ReadPosition, i - ReadPosition);
ReadPosition = i + 1;
return result;
return ReadFromBuffer(out var s)
? new ValueTask<string>(s)
: ReadLong(s);

bool ReadFromBuffer(out string s)
{
var start = ReadPosition;
while (ReadPosition < FilledBytes)
{
if (Buffer[ReadPosition++] == 0)
{
s = encoding.GetString(Buffer, start, ReadPosition - start - 1);
return true;
}
}

s = encoding.GetString(Buffer, start, ReadPosition - start);
return false;
}

async ValueTask<string> ReadLong(string s)
{
var builder = new StringBuilder(s);
bool complete;
do
{
await ReadMore(async, cancellationToken);
complete = ReadFromBuffer(out s);
builder.Append(s);
}
while (!complete);

return builder.ToString();
}
Comment on lines +592 to +605
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a bit late, but I think this code will fail if the split is in the middle of a unicode character that uses more than 1 byte to represent. Better Ensure the max length of a string is already present in the buffer (as it was previously), or create a temporary byte buffer with all the data which you then convert to a string in one go using the encoding.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're technically right... However, note that this is for reading null-terminated strings, which AFAIK are only used for certain short ASCII control strings, rather than actual user data - so I doubt there's a real bug here. But you can open a new issue to at least have it tracked.

BTW if we do want to preemptively fix this, then rather than entirely buffering the strings them in a temporary buffer, we can use System.Text.Decoder to incrementally load more chunks a decode. Note that we haven't even done this optimization for actual user strings (which are probably much more important, see TextHandler.ReadLong).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least in the replication protocol they are used for user data as well, for example table names, which can contain non-ascii characters.

I don't see why System.Text.Decoder would be anything better. It gives you some chars on every iteration. These chars are temporary anyway and must then be made into a string, after which the previously buffered chars are discarded. But generally a char[] takes up more memory than the corresponding byte[] in UTF8, so I think it's better to just buffer everything as bytes. The encoder can then convert these to a string in-place, with no extra temporary space.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right... IIRC that's why I didn't do it in TextHandler either.

Open an issue for us to fix this?

}

public ReadOnlySpan<byte> GetNullTerminatedBytes()
Expand Down
11 changes: 11 additions & 0 deletions src/Npgsql/NpgsqlTypes/NpgsqlDbType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,17 @@ public enum NpgsqlDbType
[BuiltInPostgresType("tid", PostgresTypeOIDs.Tid)]
Tid = 53,

/// <summary>
/// Corresponds to the PostgreSQL "pg_lsn" type, which can be used to store LSN (Log Sequence Number) data which
/// is a pointer to a location in the WAL.
/// </summary>
/// <remarks>
/// See: https://www.postgresql.org/docs/current/datatype-pg-lsn.html and
/// https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=7d03a83f4d0736ba869fa6f93973f7623a27038a
/// </remarks>
[BuiltInPostgresType("pg_lsn", 3220)]
PgLsn = 59,

#endregion

#region Special
Expand Down
Loading