Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,22 @@ fn uri_encode(input: &str) -> String {
/// then fail to resolve in the CREATE TABLE. Propagate the calling
/// session's mode so DDL built from Oracle-typed tables works.
fn propagate_compatible_mode(client: &mut Client) {
let mode = Spi::get_one::<String>("SELECT current_setting('ivorysql.compatible_mode', true)");
// Read the GUC via the read-only SPI path. `Spi::get_one` routes through
// `SpiClient::update`, whose `mark_mutable` assigns the calling backend a
// real transaction id. That xid deadlocks create_table: the moonlink RPC
// issued later blocks on CREATE_REPLICATION_SLOT ... USE_SNAPSHOT, whose
// snapshot builder waits for every in-progress write xact — including
// ours — while we sit in block_on waiting for that same RPC to return.
let mode = Spi::connect(|client| {
client
.select(
"SELECT current_setting('ivorysql.compatible_mode', true)",
Some(1),
&[],
)?
.first()
.get_one::<String>()
});
if let Ok(Some(mode)) = mode {
if mode == "oracle" {
client
Expand Down
16 changes: 9 additions & 7 deletions src/sql/bootstrap.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ SELECT duckdb.install_extension('mooncake', 'community');
CREATE FUNCTION public.mooncake_extension_drop_cleanup() RETURNS event_trigger
LANGUAGE plpgsql AS $mooncake_drop_cleanup$
DECLARE
is_mooncake_drop boolean := false;
slot record;
BEGIN
-- Act only when pg_mooncake itself is among the dropped objects.
SELECT true INTO is_mooncake_drop
FROM pg_event_trigger_dropped_objects()
WHERE object_type = 'extension' AND object_name = 'pg_mooncake'
LIMIT 1;

IF NOT is_mooncake_drop THEN
-- Use EXISTS, not SELECT INTO: SELECT INTO with zero rows overwrites the
-- variable with NULL (even when initialized false), and IF NOT NULL is
-- not taken — so the cleanup body would run on EVERY sql_drop, spuriously
-- dropping the slot/publication and self-removing on any DROP TABLE.
IF NOT EXISTS (
SELECT 1
FROM pg_event_trigger_dropped_objects()
WHERE object_type = 'extension' AND object_name = 'pg_mooncake'
) THEN
RETURN;
END IF;

Expand Down
23 changes: 19 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,25 @@ pub(crate) fn block_on<F: Future>(future: F) -> F::Output {

pub(crate) fn get_stream() -> MutexGuard<'static, UnixStream> {
static STREAM: LazyLock<Mutex<UnixStream>> = LazyLock::new(|| {
Mutex::new(
block_on(UnixStream::connect("pg_mooncake/moonlink.sock"))
.expect("Failed to connect to moonlink"),
)
// The moonlink bgworker binds its socket asynchronously after postmaster
// start, so the first client call in a fresh cluster can race it and see
// ECONNREFUSED/ENOENT. Retry with backoff instead of failing the backend:
// a panic here would also poison this LazyLock, permanently breaking every
// later mooncake call in the session.
let mut delay = std::time::Duration::from_millis(50);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
let stream = loop {
match block_on(UnixStream::connect("pg_mooncake/moonlink.sock")) {
Ok(stream) => break stream,
Err(err) if std::time::Instant::now() < deadline => {
pgrx::log!("moonlink not ready ({err}), retrying in {delay:?}");
std::thread::sleep(delay);
delay = (delay * 2).min(std::time::Duration::from_secs(1));
}
Err(err) => panic!("Failed to connect to moonlink: {err:?}"),
}
};
Mutex::new(stream)
});
STREAM.lock().unwrap()
}
Loading