@Override public String toString() { return "LSN{" + asString() + '}'; } }
private void doFlushLsn(LogSequenceNumber lsn) throws SQLException { stream.setFlushedLSN(lsn); stream.setAppliedLSN(lsn); stream.forceUpdateStatus(); }
private PGReplicationStream startPgReplicationStream(final LogSequenceNumber lsn, Function<ChainedLogicalStreamBuilder, ChainedLogicalStreamBuilder> configurator) throws SQLException { assert lsn != null; ChainedLogicalStreamBuilder streamBuilder = pgConnection() .getReplicationAPI() .replicationStream() .logical() .withSlotName(slotName) .withStartPosition(lsn); streamBuilder = configurator.apply(streamBuilder); if (statusUpdateIntervalMillis != null && statusUpdateIntervalMillis > 0) { streamBuilder.withStatusInterval(statusUpdateIntervalMillis, TimeUnit.MILLISECONDS); } PGReplicationStream stream = streamBuilder.start(); // TODO DBZ-508 get rid of this // Needed by tests when connections are opened and closed in a fast sequence try { Thread.sleep(10); } catch (Exception e) { } stream.forceUpdateStatus(); return stream; }
@Override public void readPending(ReplicationMessageProcessor processor) throws SQLException, InterruptedException { ByteBuffer read = stream.readPending(); // the lsn we started from is inclusive, so we need to avoid sending back the same message twice if (read == null || lsnLong >= stream.getLastReceiveLSN().asLong()) { return; } deserializeMessages(read, processor); }
@Override public void read(ReplicationMessageProcessor processor) throws SQLException, InterruptedException { ByteBuffer read = stream.read(); // the lsn we started from is inclusive, so we need to avoid sending back the same message twice if (lsnLong >= stream.getLastReceiveLSN().asLong()) { return; } deserializeMessages(read, processor); }
private boolean processKeepAliveMessage(ByteBuffer buffer) { lastServerLSN = LogSequenceNumber.valueOf(buffer.getLong()); if (lastServerLSN.asLong() > lastReceiveLSN.asLong()) { lastReceiveLSN = lastServerLSN; } long lastServerClock = buffer.getLong(); boolean replyRequired = buffer.get() != 0; if (LOGGER.isLoggable(Level.FINEST)) { Date clockTime = new Date( TimeUnit.MILLISECONDS.convert(lastServerClock, TimeUnit.MICROSECONDS) + POSTGRES_EPOCH_2000_01_01); LOGGER.log(Level.FINEST, " <=BE Keepalive(lastServerWal: {0}, clock: {1} needReply: {2})", new Object[]{lastServerLSN.asString(), clockTime, replyRequired}); } return replyRequired; }
private Long tryParseLsn(String slotName, String pluginName, String database, ResultSet rs, String column) throws ConnectException, SQLException { Long lsn = null; String lsnStr = rs.getString(column); if (lsnStr == null) { return null; } try { lsn = LogSequenceNumber.valueOf(lsnStr).asLong(); } catch (Exception e) { throw new ConnectException("Value " + column + " in the pg_replication_slots table for slot = '" + slotName + "', plugin = '" + pluginName + "', database = '" + database + "' is not valid. This is an abnormal situation and the database status should be checked."); } if (lsn == LogSequenceNumber.INVALID_LSN.asLong()) { throw new ConnectException("Invalid LSN returned from database"); } return lsn; }
/** * START_REPLICATION SLOT slot_name LOGICAL XXX/XXX [ ( option_name [option_value] [, ... ] ) ] */ private String createStartLogicalQuery(LogicalReplicationOptions options) { StringBuilder builder = new StringBuilder(); builder.append("START_REPLICATION SLOT ") .append(options.getSlotName()) .append(" LOGICAL ") .append(options.getStartLSNPosition().asString()); Properties slotOptions = options.getSlotOptions(); if (slotOptions.isEmpty()) { return builder.toString(); } //todo replace on java 8 builder.append(" ("); boolean isFirst = true; for (String name : slotOptions.stringPropertyNames()) { if (isFirst) { isFirst = false; } else { builder.append(", "); } builder.append('\"').append(name).append('\"').append(" ") .append('\'').append(slotOptions.getProperty(name)).append('\''); } builder.append(")"); return builder.toString(); }
@Override public Long lastReceivedLsn() { return lastReceivedLSN != null ? lastReceivedLSN.asLong() : null; }
/** * START_REPLICATION [SLOT slot_name] [PHYSICAL] XXX/XXX. */ private String createStartPhysicalQuery(PhysicalReplicationOptions options) { StringBuilder builder = new StringBuilder(); builder.append("START_REPLICATION"); if (options.getSlotName() != null) { builder.append(" SLOT ").append(options.getSlotName()); } builder.append(" PHYSICAL ").append(options.getStartLSNPosition().asString()); return builder.toString(); }
@Override public void flushLsn(long lsn) throws SQLException { doFlushLsn(LogSequenceNumber.valueOf(lsn)); }
private void deserializeMessages(ByteBuffer buffer, ReplicationMessageProcessor processor) throws SQLException, InterruptedException { lastReceivedLSN = stream.getLastReceiveLSN(); messageDecoder.processMessage(buffer, processor, typeRegistry); }
@Override public void close() throws SQLException { processWarnings(true); stream.close(); }
/** * @param value numeric represent position in the write-ahead log stream * @return not null LSN instance */ public static LogSequenceNumber valueOf(long value) { return new LogSequenceNumber(value); }
@Override public PGReplicationConnection getReplicationAPI() { return new PGReplicationConnectionImpl(this); }
/** * Returns the current position in the server tx log. * * @return a long value, never negative * @throws SQLException if anything unexpected fails. */ public long currentXLogLocation() throws SQLException { AtomicLong result = new AtomicLong(0); int majorVersion = connection().getMetaData().getDatabaseMajorVersion(); query(majorVersion >= 10 ? "select * from pg_current_wal_lsn()" : "select * from pg_current_xlog_location()", rs -> { if (!rs.next()) { throw new IllegalStateException("there should always be a valid xlog position"); } result.compareAndSet(0, LogSequenceNumber.valueOf(rs.getString(1)).asLong()); }); return result.get(); }
private ByteBuffer processXLogData(ByteBuffer buffer) { long startLsn = buffer.getLong(); lastServerLSN = LogSequenceNumber.valueOf(buffer.getLong()); long systemClock = buffer.getLong(); switch (replicationType) { case LOGICAL: lastReceiveLSN = LogSequenceNumber.valueOf(startLsn); break; case PHYSICAL: int payloadSize = buffer.limit() - buffer.position(); lastReceiveLSN = LogSequenceNumber.valueOf(startLsn + payloadSize); break; } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, " <=BE XLogData(currWal: {0}, lastServerWal: {1}, clock: {2})", new Object[]{lastReceiveLSN.asString(), lastServerLSN.asString(), systemClock}); } return buffer.slice(); }
/** * Create LSN instance by string represent LSN. * * @param strValue not null string as two hexadecimal numbers of up to 8 digits each, separated by * a slash. For example {@code 16/3002D50}, {@code 0/15D68C50} * @return not null LSN instance where if specified string represent have not valid form {@link * LogSequenceNumber#INVALID_LSN} */ public static LogSequenceNumber valueOf(String strValue) { int slashIndex = strValue.lastIndexOf('/'); if (slashIndex <= 0) { return INVALID_LSN; } String logicalXLogStr = strValue.substring(0, slashIndex); int logicalXlog = (int) Long.parseLong(logicalXLogStr, 16); String segmentStr = strValue.substring(slashIndex + 1, strValue.length()); int segment = (int) Long.parseLong(segmentStr, 16); ByteBuffer buf = ByteBuffer.allocate(8); buf.putInt(logicalXlog); buf.putInt(segment); buf.position(0); long value = buf.getLong(); return LogSequenceNumber.valueOf(value); }
@Override public ReplicationStream startStreaming(Long offset) throws SQLException, InterruptedException { connect(); if (offset == null || offset <= 0) { offset = defaultStartingPos; } LogSequenceNumber lsn = LogSequenceNumber.valueOf(offset); LOGGER.debug("starting streaming from LSN '{}'", lsn.asString()); return createReplicationStream(lsn); }