Example #1
0
        async IAsyncEnumerator <PgOutputReplicationMessage> StartReplicationInternal(CancellationToken cancellationToken)
        {
            var stream = _connection.StartLogicalReplication(
                _slot, cancellationToken, _walLocation, _options.GetOptionPairs(), bypassingStream: true);
            var buf = _connection.Connector !.ReadBuffer;

            await foreach (var xLogData in stream.WithCancellation(cancellationToken))
            {
                await buf.EnsureAsync(1);

                var messageCode = (BackendReplicationMessageCode)buf.ReadByte();
                switch (messageCode)
                {
                case BackendReplicationMessageCode.Begin:
                {
                    await buf.EnsureAsync(20);

                    yield return(_beginMessage.Populate(
                                     xLogData.WalStart,
                                     xLogData.WalEnd,
                                     xLogData.ServerClock,
                                     new NpgsqlLogSequenceNumber(buf.ReadUInt64()),
                                     TimestampHandler.FromPostgresTimestamp(buf.ReadInt64()),
                                     buf.ReadUInt32()
                                     ));

                    continue;
                }

                case BackendReplicationMessageCode.Commit:
                {
                    await buf.EnsureAsync(25);

                    yield return(_commitMessage.Populate(
                                     xLogData.WalStart,
                                     xLogData.WalEnd,
                                     xLogData.ServerClock,
                                     buf.ReadByte(),
                                     new NpgsqlLogSequenceNumber(buf.ReadUInt64()),
                                     new NpgsqlLogSequenceNumber(buf.ReadUInt64()),
                                     TimestampHandler.FromPostgresTimestamp(buf.ReadInt64())
                                     ));

                    continue;
                }

                case BackendReplicationMessageCode.Origin:
                {
                    await buf.EnsureAsync(9);

                    yield return(_originMessage.Populate(
                                     xLogData.WalStart,
                                     xLogData.WalEnd,
                                     xLogData.ServerClock,
                                     new NpgsqlLogSequenceNumber(buf.ReadUInt64()),
                                     await buf.ReadNullTerminatedString(async: true, cancellationToken)));

                    continue;
                }

                case BackendReplicationMessageCode.Relation:
                {
                    await buf.EnsureAsync(6);

                    var relationId = buf.ReadUInt32();
                    var ns         = await buf.ReadNullTerminatedString(async : true, cancellationToken);

                    var relationName = await buf.ReadNullTerminatedString(async : true, cancellationToken);

                    await buf.EnsureAsync(3);

                    var relationReplicaIdentitySetting = (char)buf.ReadByte();
                    var numColumns = buf.ReadUInt16();
                    if (numColumns > _relationalMessageColumns.Length)
                    {
                        _relationalMessageColumns = new RelationMessage.Column[numColumns];
                    }
                    for (var i = 0; i < numColumns; i++)
                    {
                        await buf.EnsureAsync(2);

                        var flags      = buf.ReadByte();
                        var columnName = await buf.ReadNullTerminatedString(async : true, cancellationToken);

                        await buf.EnsureAsync(8);

                        var dateTypeId   = buf.ReadUInt32();
                        var typeModifier = buf.ReadInt32();
                        _relationalMessageColumns[i] = new RelationMessage.Column(flags, columnName, dateTypeId, typeModifier);
                    }

                    yield return(_relationMessage.Populate(
                                     xLogData.WalStart,
                                     xLogData.WalEnd,
                                     xLogData.ServerClock,
                                     relationId,
                                     ns,
                                     relationName,
                                     relationReplicaIdentitySetting,
                                     new ReadOnlyMemory <RelationMessage.Column>(_relationalMessageColumns, 0, numColumns)
                                     ));

                    continue;
                }

                case BackendReplicationMessageCode.Type:
                {
                    await buf.EnsureAsync(5);

                    var typeId = buf.ReadUInt32();
                    var ns     = await buf.ReadNullTerminatedString(async : true, cancellationToken);

                    var name = await buf.ReadNullTerminatedString(async : true, cancellationToken);

                    yield return(_typeMessage.Populate(xLogData.WalStart, xLogData.WalEnd, xLogData.ServerClock, typeId, ns, name));

                    continue;
                }

                case BackendReplicationMessageCode.Insert:
                {
                    await buf.EnsureAsync(7);

                    var relationId    = buf.ReadUInt32();
                    var tupleDataType = (TupleType)buf.ReadByte();
                    Debug.Assert(tupleDataType == TupleType.NewTuple);
                    var numColumns = buf.ReadUInt16();
                    var newRow     = await ReadTupleDataAsync(ref _tupleDataArray1, numColumns);

                    yield return(_insertMessage.Populate(xLogData.WalStart, xLogData.WalEnd, xLogData.ServerClock, relationId, newRow));

                    continue;
                }

                case BackendReplicationMessageCode.Update:
                {
                    await buf.EnsureAsync(7);

                    var relationId = buf.ReadUInt32();
                    var tupleType  = (TupleType)buf.ReadByte();
                    var numColumns = buf.ReadUInt16();
                    switch (tupleType)
                    {
                    case TupleType.Key:
                        var keyRow = await ReadTupleDataAsync(ref _tupleDataArray1, numColumns);

                        await buf.EnsureAsync(3);

                        tupleType = (TupleType)buf.ReadByte();
                        Debug.Assert(tupleType == TupleType.NewTuple);
                        numColumns = buf.ReadUInt16();
                        var newRow = await ReadTupleDataAsync(ref _tupleDataArray2, numColumns);

                        yield return(_indexUpdateMessage.Populate(xLogData.WalStart, xLogData.WalEnd,
                                                                  xLogData.ServerClock, relationId, newRow, keyRow));

                        continue;

                    case TupleType.OldTuple:
                        var oldRow = await ReadTupleDataAsync(ref _tupleDataArray1, numColumns);

                        await buf.EnsureAsync(3);

                        tupleType = (TupleType)buf.ReadByte();
                        Debug.Assert(tupleType == TupleType.NewTuple);
                        numColumns = buf.ReadUInt16();
                        newRow     = await ReadTupleDataAsync(ref _tupleDataArray2, numColumns);

                        yield return(_fullUpdateMessage.Populate(xLogData.WalStart, xLogData.WalEnd,
                                                                 xLogData.ServerClock, relationId, newRow, oldRow));

                        continue;

                    case TupleType.NewTuple:
                        newRow = await ReadTupleDataAsync(ref _tupleDataArray1, numColumns);

                        yield return(_updateMessage.Populate(xLogData.WalStart, xLogData.WalEnd,
                                                             xLogData.ServerClock, relationId, newRow));

                        continue;

                    default:
                        throw new NotSupportedException($"The tuple type '{tupleType}' is not supported.");
                    }
                }

                case BackendReplicationMessageCode.Delete:
                {
                    await buf.EnsureAsync(7);

                    var relationId    = buf.ReadUInt32();
                    var tupleDataType = (TupleType)buf.ReadByte();
                    var numColumns    = buf.ReadUInt16();
                    switch (tupleDataType)
                    {
                    case TupleType.Key:
                        yield return(_keyDeleteMessage.Populate(xLogData.WalStart, xLogData.WalEnd, xLogData.ServerClock,
                                                                relationId, await ReadTupleDataAsync(ref _tupleDataArray1, numColumns)));

                        continue;

                    case TupleType.OldTuple:
                        yield return(_fullDeleteMessage.Populate(xLogData.WalStart, xLogData.WalEnd, xLogData.ServerClock,
                                                                 relationId, await ReadTupleDataAsync(ref _tupleDataArray1, numColumns)));

                        continue;

                    default:
                        throw new NotSupportedException($"The tuple type '{tupleDataType}' is not supported.");
                    }
                }

                case BackendReplicationMessageCode.Truncate:
                {
                    await buf.EnsureAsync(9);

                    // Don't dare to truncate more than 2147483647 tables at once!
                    var numRels         = checked ((int)buf.ReadUInt32());
                    var truncateOptions = (TruncateOptions)buf.ReadByte();
                    var relationIds     = new uint[numRels];
                    await buf.EnsureAsync(checked (numRels * 4));

                    for (var i = 0; i < numRels; i++)
                    {
                        relationIds[i] = buf.ReadUInt32();
                    }

                    yield return(_truncateMessage.Populate(
                                     xLogData.WalStart, xLogData.WalEnd, xLogData.ServerClock, truncateOptions, relationIds));

                    continue;
                }

                default:
                    throw new NotSupportedException(
                              $"Invalid message code {messageCode} in Logical Replication Protocol.");
                }
            }

            // We never get here - the above is an endless loop that terminates only with a cancellation exception

            ValueTask <ReadOnlyMemory <TupleData> > ReadTupleDataAsync(ref TupleData[] array, ushort numberOfColumns)
            {
                if (array.Length < numberOfColumns)
                {
                    array = new TupleData[numberOfColumns];
                }
                var nonRefArray = array;

                return(ReadTupleDataAsync2());

                async ValueTask <ReadOnlyMemory <TupleData> > ReadTupleDataAsync2()
                {
                    for (var i = 0; i < numberOfColumns; i++)
                    {
                        await buf.EnsureAsync(1);

                        var subMessageKind = (TupleDataKind)buf.ReadByte();
                        switch (subMessageKind)
                        {
                        case TupleDataKind.Null:
                        case TupleDataKind.UnchangedToastedValue:
                            nonRefArray[i] = new TupleData(subMessageKind);
                            continue;

                        case TupleDataKind.TextValue:
                            await buf.EnsureAsync(4);

                            var len = buf.ReadInt32();
                            await buf.EnsureAsync(len);

                            nonRefArray ![i] = new TupleData(buf.ReadString(len));
                            continue;