Skip to content

Commit

Permalink
Merge pull request #72 from folio-org/MODFQMMGR-76
Browse files Browse the repository at this point in the history
MODFQMMGR-76: Periodically refresh materialized views
  • Loading branch information
bvsharp committed Nov 8, 2023
2 parents 1131fa6 + 19db297 commit 913719a
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 3 deletions.
21 changes: 19 additions & 2 deletions descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "@artifactId@-@version@",
"name": "The module descriptor for mod-fqm-manager.",
"name": "FQM Manager Module",
"provides": [
{
"id": "_tenant",
Expand Down Expand Up @@ -78,6 +78,11 @@
"methods": ["DELETE"],
"pathPattern": "/query/{query-id}",
"permissionsRequired": ["fqm.query.async.delete"]
},
{
"methods": ["POST"],
"pathPattern": "/materialized-views/refresh",
"permissionsRequired": ["fqm.materializedViews.post"]
}
]
},
Expand All @@ -87,10 +92,16 @@
"interfaceType": "system",
"handlers": [
{
"methods": [ "POST" ],
"methods": ["POST"],
"pathPattern": "/query/purge",
"unit": "hour",
"delay": "1"
},
{
"methods": ["POST"],
"pathPattern": "/materialized-views/refresh",
"unit": "hour",
"delay": "24"
}
]
}
Expand Down Expand Up @@ -144,6 +155,12 @@
"description": "Run a query synchronously and get results",
"visible": true
},
{
"permissionName": "fqm.materializedViews.post",
"displayName": "FQM - Refresh materialized views",
"description": "Refresh FQM materialized views",
"visible": true
},
{
"permissionName": "fqm.query.all",
"displayName": "FQM - All permissions",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.folio.fqm.repository;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.jooq.DSLContext;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@RequiredArgsConstructor
@Log4j2
public class MaterializedViewRefreshRepository {
private static final String REFRESH_MATERIALIZED_VIEW_SQL = "REFRESH MATERIALIZED VIEW CONCURRENTLY ";

private static final List<String> materializedViewNames = List.of(
"drv_circulation_loan_status",
"drv_inventory_item_status"
);

private final DSLContext jooqContext;

public void refreshMaterializedViews(String tenantId) {
for (String matViewName : materializedViewNames) {
String fullName = tenantId + "_mod_fqm_manager." + matViewName;
log.info("Refreshing materialized view {}", fullName);
jooqContext.execute(REFRESH_MATERIALIZED_VIEW_SQL + fullName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.folio.fqm.resource;

import lombok.RequiredArgsConstructor;
import org.folio.fqm.service.MaterializedViewRefreshService;
import org.folio.spring.FolioExecutionContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class MaterializedViewRefreshController implements MaterializedViewsApi {
private final FolioExecutionContext executionContext;
private final MaterializedViewRefreshService materializedViewRefreshService;

@Override
public ResponseEntity<Void> refreshMaterializedViews() {
materializedViewRefreshService.refreshMaterializedViews(executionContext.getTenantId());
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.folio.fqm.service;

import lombok.RequiredArgsConstructor;
import org.folio.fqm.repository.MaterializedViewRefreshRepository;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MaterializedViewRefreshService {
private final MaterializedViewRefreshRepository materializedViewRefreshRepository;

public void refreshMaterializedViews(String tenantId) {
materializedViewRefreshRepository.refreshMaterializedViews(tenantId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<changeSet id="add-materialized-view-indexes" author="bsharp@ebsco.com">
<!-- ensure other schemas/tables exist (primarily to prevent invalid references in integration tests) -->
<preConditions onFail="CONTINUE">
<sqlCheck expectedResult="1">
SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status';
</sqlCheck>
<sqlCheck expectedResult="1">
SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_circulation_loan_status';
</sqlCheck>
</preConditions>
<createIndex indexName="fqm_item_status_idx" tableName="drv_inventory_item_status" unique="true">
<column name="item_status"/>
</createIndex>
<createIndex indexName="fqm_loan_status_idx" tableName="drv_circulation_loan_status" unique="true">
<column name="loan_status"/>
</createIndex>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">

<include file="sql/refactor drv_item_callnumber_location.xml" relativeToChangelogFile="true"/>
<include file="refactor_drv_item_callnumber_location.xml" relativeToChangelogFile="true"/>
<include file="add_materialized_view_indexes.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
13 changes: 13 additions & 0 deletions src/main/resources/swagger.api/mod-fqm-manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ paths:
$ref: '#/components/responses/badRequestResponse'
'500':
$ref: '#/components/responses/internalServerErrorResponse'
/materialized-views/refresh:
post:
operationId: refreshMaterializedViews
tags:
- materializedViews
description: Refresh all materialized views for a tenant.
responses:
'204':
description: 'Views refreshed'
'400':
$ref: '#/components/responses/badRequestResponse'
'500':
$ref: '#/components/responses/internalServerErrorResponse'


components:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.folio.fqm.controller;

import org.folio.fqm.resource.MaterializedViewRefreshController;
import org.folio.fqm.service.MaterializedViewRefreshService;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.integration.XOkapiHeaders;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(MaterializedViewRefreshController.class)
class MaterializedViewRefreshControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private FolioExecutionContext executionContext;
@MockBean
private MaterializedViewRefreshService materializedViewRefreshService;

@Test
void refreshMaterializedViewsTest() throws Exception {
String tenantId = "tenant_01";
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/materialized-views/refresh")
.header(XOkapiHeaders.TENANT, tenantId)
.contentType(APPLICATION_JSON);
when(executionContext.getTenantId()).thenReturn(tenantId);
doNothing().when(materializedViewRefreshService).refreshMaterializedViews(tenantId);
mockMvc.perform(requestBuilder)
.andExpect(status().isNoContent());
verify(materializedViewRefreshService, times(1)).refreshMaterializedViews(tenantId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.folio.fqm.repository;

import org.jooq.DSLContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class MaterializedViewRefreshRepositoryTest {
@InjectMocks
private MaterializedViewRefreshRepository materializedViewRefreshRepository;
@Mock
private DSLContext jooqContext;

@Test
void refreshMaterializedViewsTest() {
String tenantId = "tenant_01";
String expectedItemStatusSql = "REFRESH MATERIALIZED VIEW CONCURRENTLY tenant_01_mod_fqm_manager.drv_inventory_item_status";
String expectedLoanStatusSql = "REFRESH MATERIALIZED VIEW CONCURRENTLY tenant_01_mod_fqm_manager.drv_circulation_loan_status";
when(jooqContext.execute(anyString())).thenReturn(1);
materializedViewRefreshRepository.refreshMaterializedViews(tenantId);
verify(jooqContext, times(1)).execute(expectedItemStatusSql);
verify(jooqContext, times(1)).execute(expectedLoanStatusSql);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.folio.fqm.service;

import org.folio.fqm.repository.MaterializedViewRefreshRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
class MaterializedViewRefreshServiceTest {
@InjectMocks
private MaterializedViewRefreshService materializedViewRefreshService;
@Mock
private MaterializedViewRefreshRepository materializedViewRefreshRepository;

@Test
void refreshMaterializedViewsTest() {
String tenantId = "tenant_01";
doNothing().when(materializedViewRefreshRepository).refreshMaterializedViews(tenantId);
materializedViewRefreshService.refreshMaterializedViews(tenantId);
verify(materializedViewRefreshRepository, times(1)).refreshMaterializedViews(tenantId);
}
}

0 comments on commit 913719a

Please sign in to comment.