Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 20 additions & 0 deletions api/src/org/labkey/api/data/MaterializedQueryHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -1074,5 +1074,25 @@ public void testUnloggedReset()
assertEquals(Integer.valueOf(1), new SqlSelector(temp, new SQLFragment("SELECT COUNT(*) FROM ").append(after)).getObject(Integer.class));
}
}

@Test
public void testUnloggedMarkerDropped()
{
DbSchema temp = DbSchema.getTemp();
Assume.assumeTrue("UNLOGGED is only supported on PostgreSQL", temp.getSqlDialect().isPostgreSQL());

UnloggedTableGeneration.checkForTest();
String before = UnloggedTableGeneration.markerNameForTest();
assertNotNull("The initial check must create a marker", before);
long generation = Long.parseLong(UnloggedTableGeneration.current());

new SqlExecutor(temp).execute(new SQLFragment("DROP TABLE temp.").appendIdentifier(before));
UnloggedTableGeneration.checkForTest();

assertTrue("A dropped marker must bump the generation", Long.parseLong(UnloggedTableGeneration.current()) > generation);
String after = UnloggedTableGeneration.markerNameForTest();
assertNotEquals("A dropped marker must be replaced under a new name", before, after);
assertEquals(Integer.valueOf(1), new SqlSelector(temp, new SQLFragment("SELECT COUNT(*) FROM temp.").appendIdentifier(after)).getObject(Integer.class));
}
}
}
26 changes: 22 additions & 4 deletions api/src/org/labkey/api/data/UnloggedTableGeneration.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ static void checkForTest()
}
}

static @Nullable String markerNameForTest()
{
return _markerName;
}

/**
* Empties the marker as a reset would and checks it immediately. Callers must empty their own UNLOGGED tables.
* Invalidates every UNLOGGED materialized table on the server, not just the caller's.
Expand Down Expand Up @@ -146,6 +151,14 @@ private static String failureKind(Exception x)

private static void verifyMarker(DbScope scope, Connection conn, String markerName)
{
// A missing table fails the EXISTS query at parse time, and SqlSelector logs that failure at ERROR
Comment thread
labkey-nicka marked this conversation as resolved.
Resolved
SQLFragment found = new SQLFragment("SELECT to_regclass(?) IS NOT NULL", markerTable(markerName).getRawSQL());
if (!Boolean.TRUE.equals(new SqlSelector(scope, conn, found).getObject(Boolean.class)))
{
replaceMissingMarker(scope, conn, markerName);
return;
}

try
{
SQLFragment exists = new SQLFragment("SELECT EXISTS (SELECT 1 FROM ").append(markerTable(markerName)).append(")");
Expand All @@ -161,15 +174,20 @@ private static void verifyMarker(DbScope scope, Connection conn, String markerNa
{
if (!SqlDialect.isObjectNotFoundException(x))
throw x;
createMarker(scope, conn);
_generation.incrementAndGet();
LOG.info("UNLOGGED table marker {} is missing. Materialized views will rebuild.", markerName);
replaceMissingMarker(scope, conn, markerName);
}
}

private static void replaceMissingMarker(DbScope scope, Connection conn, String markerName)
{
createMarker(scope, conn);
_generation.incrementAndGet();
LOG.info("UNLOGGED table marker {} is missing. Materialized views will rebuild.", markerName);
}

private static void createMarker(DbScope scope, Connection conn)
{
// A fresh name never collides with tables TempTableTracker purges asynchronously at startup
TempTableTracker.init();
String name = "unlogged_marker_" + GUID.makeHash();
new SqlExecutor(scope, conn).execute(new SQLFragment("CREATE UNLOGGED TABLE ").append(markerTable(name)).append(" AS SELECT 1 AS x"));
TempTableTracker.track(name, MARKER_REF);
Expand Down
Loading