diff --git a/NEWS.md b/NEWS.md index be8973475..288e630a7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ * [MODSOURMAN-1200](https://folio-org.atlassian.net/browse/MODSOURMAN-1200) Find record by match id on update generation * [MODINV-1049](https://folio-org.atlassian.net/browse/MODINV-1049) Existing "035" field is not retained the original position in imported record * [MODSOURCE-785](https://folio-org.atlassian.net/browse/MODSOURCE-785) Update 005 field when set MARC for deletion +* [MODSOURMAN-783](https://folio-org.atlassian.net/browse/MODSOURCE-783) Extend MARC-MARC search query to account for qualifiers ## 2024-03-20 5.8.0 * [MODSOURCE-733](https://issues.folio.org/browse/MODSOURCE-733) Reduce Memory Allocation of Strings diff --git a/mod-source-record-storage-server/src/main/java/org/folio/dao/RecordDaoImpl.java b/mod-source-record-storage-server/src/main/java/org/folio/dao/RecordDaoImpl.java index b50397b67..676def094 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/dao/RecordDaoImpl.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/dao/RecordDaoImpl.java @@ -132,7 +132,18 @@ import static org.folio.rest.jooq.Tables.SNAPSHOTS_LB; import static org.folio.rest.jooq.enums.RecordType.MARC_BIB; import static org.folio.rest.util.QueryParamUtil.toRecordType; -import static org.jooq.impl.DSL.*; +import static org.jooq.impl.DSL.condition; +import static org.jooq.impl.DSL.countDistinct; +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.inline; +import static org.jooq.impl.DSL.max; +import static org.jooq.impl.DSL.name; +import static org.jooq.impl.DSL.primaryKey; +import static org.jooq.impl.DSL.select; +import static org.jooq.impl.DSL.table; +import static org.jooq.impl.DSL.trueCondition; +import static org.jooq.impl.DSL.selectDistinct; +import static org.jooq.impl.DSL.exists; @Component public class RecordDaoImpl implements RecordDao { @@ -156,7 +167,9 @@ public class RecordDaoImpl implements RecordDao { static final int INDEXERS_DELETION_LOCK_NAMESPACE_ID = "delete_marc_indexers".hashCode(); public static final String CONTROL_FIELD_CONDITION_TEMPLATE = "\"{partition}\".\"value\" in ({value})"; + public static final String CONTROL_FIELD_CONDITION_TEMPLATE_WITH_QUALIFIER = "\"{partition}\".\"value\" IN ({value}) AND \"{partition}\".\"value\" LIKE {qualifier}"; public static final String DATA_FIELD_CONDITION_TEMPLATE = "\"{partition}\".\"value\" in ({value}) and \"{partition}\".\"ind1\" LIKE '{ind1}' and \"{partition}\".\"ind2\" LIKE '{ind2}' and \"{partition}\".\"subfield_no\" = '{subfield}'"; + public static final String DATA_FIELD_CONDITION_TEMPLATE_WITH_QUALIFIER = "\"{partition}\".\"value\" IN ({value}) AND \"{partition}\".\"value\" LIKE {qualifier} AND \"{partition}\".\"ind1\" LIKE '{ind1}' AND \"{partition}\".\"ind2\" LIKE '{ind2}' AND \"{partition}\".\"subfield_no\" = '{subfield}'"; private static final String VALUE_IN_SINGLE_QUOTES = "'%s'"; private static final String RECORD_NOT_FOUND_BY_ID_TYPE = "Record with %s id: %s was not found"; private static final String INVALID_PARSED_RECORD_MESSAGE_TEMPLATE = "Record %s has invalid parsed record; %s"; @@ -352,18 +365,25 @@ public Future> getMatchedRecordsWithoutIndexersVersionUsage(MatchFi private Condition getMatchedFieldCondition(MatchField matchedField, String partition) { Map params = new HashMap<>(); + var qualifierSearch = false; params.put("partition", partition); params.put("value", getValueInSqlFormat(matchedField.getValue())); + if (matchedField.getQualifierMatch() != null) { + qualifierSearch = true; + params.put("qualifier", getSqlQualifier(matchedField.getQualifierMatch())); + } + String sql; if (matchedField.isControlField()) { - String sql = StrSubstitutor.replace(CONTROL_FIELD_CONDITION_TEMPLATE, params, "{", "}"); - return condition(sql); + sql = qualifierSearch ? StrSubstitutor.replace(CONTROL_FIELD_CONDITION_TEMPLATE_WITH_QUALIFIER, params, "{", "}") + : StrSubstitutor.replace(CONTROL_FIELD_CONDITION_TEMPLATE, params, "{", "}"); } else { params.put("ind1", getSqlInd(matchedField.getInd1())); params.put("ind2", getSqlInd(matchedField.getInd2())); params.put("subfield", matchedField.getSubfield()); - String sql = StrSubstitutor.replace(DATA_FIELD_CONDITION_TEMPLATE, params, "{", "}"); - return condition(sql); + sql = qualifierSearch ? sql = StrSubstitutor.replace(DATA_FIELD_CONDITION_TEMPLATE_WITH_QUALIFIER, params, "{", "}") + : StrSubstitutor.replace(DATA_FIELD_CONDITION_TEMPLATE, params, "{", "}"); } + return condition(sql); } private String getSqlInd(String ind) { @@ -372,6 +392,19 @@ private String getSqlInd(String ind) { return ind; } + private String getSqlQualifier(MatchField.QualifierMatch qualifierMatch) { + if (qualifierMatch == null) { + return null; + } + var value = qualifierMatch.value(); + + return switch (qualifierMatch.qualifier()) { + case BEGINS_WITH -> "'" + value + "%'"; + case ENDS_WITH -> "'%" + value + "'"; + case CONTAINS -> "'%" + value + "%'"; + }; + } + private String getValueInSqlFormat(Value value) { if (Value.ValueType.STRING.equals(value.getType())) { return format(VALUE_IN_SINGLE_QUOTES, value.getValue()); diff --git a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java index 3c93b7057..4d5a7982d 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/MatchField.java @@ -1,6 +1,7 @@ package org.folio.dao.util; import org.folio.processing.value.Value; +import org.folio.rest.jaxrs.model.Filter; import org.marc4j.marc.impl.Verifier; /** @@ -15,6 +16,7 @@ public class MatchField { private final String ind2; private final String subfield; private final Value value; + private final QualifierMatch qualifierMatch; private final String fieldPath; public MatchField(String tag, String ind1, String ind2, String subfield, Value value) { @@ -24,6 +26,20 @@ public MatchField(String tag, String ind1, String ind2, String subfield, Value v this.subfield = subfield; this.value = value; this.fieldPath = tag + ind1 + ind2 + subfield; + this.qualifierMatch = null; + } + + public MatchField(String tag, String ind1, String ind2, String subfield, Value value, QualifierMatch qualifierMatch) { + this.tag = tag; + this.ind1 = ind1; + this.ind2 = ind2; + this.subfield = subfield; + this.value = value; + this.qualifierMatch = qualifierMatch; + this.fieldPath = tag + ind1 + ind2 + subfield; + } + + public record QualifierMatch(Filter.Qualifier qualifier, String value) { } public String getTag() { @@ -46,6 +62,10 @@ public Value getValue() { return value; } + public QualifierMatch getQualifierMatch() { + return qualifierMatch; + } + public boolean isControlField() { return Verifier.isControlField(tag); } diff --git a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java index 3202f44b8..6271de982 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/dao/util/RecordDaoUtil.java @@ -51,6 +51,7 @@ public final class RecordDaoUtil { public static final String RECORD_NOT_FOUND_TEMPLATE = "Record with id '%s' was not found"; private static final String COMMA = ","; + private static final String LIKE_OPERATOR = "%"; private static final List DELETED_LEADER_RECORD_STATUS = Arrays.asList("d", "s", "x"); private RecordDaoUtil() {} @@ -77,6 +78,18 @@ public static Condition getExternalIdsCondition(List externalIds, IdType return getIdCondition(idType, idField -> idField.in(toUUIDs(externalIds))); } + /** + * Get {@link Condition} where in external list ids and {@link IdType} and match by qualifier value + * + * @param externalIds list of external id + * @param idType external id type + * @param qualifier qualifier type and value + * @return condition + */ + public static Condition getExternalIdsConditionWithQualifier(List externalIds, IdType idType, MatchField.QualifierMatch qualifier) { + return getIdConditionWithQualifier(idType, idField -> idField.in(toUUIDs(externalIds)), qualifier); + } + /** * Count query by {@link Condition} * @@ -478,6 +491,19 @@ public static Condition filterRecordByExternalHridValues(List externalHr return RECORDS_LB.EXTERNAL_HRID.in(externalHridValues); } + /** + * Get {@link Condition} to filter by external entity hrid using specified values and match by qualifier value + * + * @param externalHridValues external entity hrid values to equal + * @param qualifier qualifier type and value + * @return condition + */ + public static Condition filterRecordByExternalHridValuesWithQualifier(List externalHridValues, MatchField.QualifierMatch qualifier) { + var qualifierCondition = buildQualifierCondition(RECORDS_LB.EXTERNAL_HRID, qualifier); + var resultCondition = RECORDS_LB.EXTERNAL_HRID.in(externalHridValues); + return qualifierCondition != null ? resultCondition.and(qualifierCondition) : resultCondition; + } + /** * Get {@link Condition} to filter by snapshotId id * @@ -734,20 +760,47 @@ private static Condition getRecordTypeCondition(RecordType recordType) { } private static Condition getIdCondition(IdType idType, Function, Condition> idFieldToConditionMapper) { - IdType idTypeToUse = idType; - RecordType recordType = null; - if (idType == IdType.HOLDINGS) { - idTypeToUse = IdType.EXTERNAL; - recordType = RecordType.MARC_HOLDING; - } else if (idType == IdType.INSTANCE) { - idTypeToUse = IdType.EXTERNAL; - recordType = RecordType.MARC_BIB; - } else if (idType == IdType.AUTHORITY) { - idTypeToUse = IdType.EXTERNAL; - recordType = RecordType.MARC_AUTHORITY; - } + IdType idTypeToUse = getIdType(idType); + RecordType recordType = getRecordType(idTypeToUse); var idField = RECORDS_LB.field(LOWER_CAMEL.to(LOWER_UNDERSCORE, idTypeToUse.getIdField()), UUID.class); return idFieldToConditionMapper.apply(idField).and(getRecordTypeCondition(recordType)); } + private static Condition getIdConditionWithQualifier(IdType idType, Function, Condition> idFieldToConditionMapper, MatchField.QualifierMatch qualifier) { + IdType idTypeToUse = getIdType(idType); + RecordType recordType = getRecordType(idType); + var idField = RECORDS_LB.field(LOWER_CAMEL.to(LOWER_UNDERSCORE, idTypeToUse.getIdField()), UUID.class); + var qualifierCondition = buildQualifierCondition(idField, qualifier); + var resultCondition = idFieldToConditionMapper.apply(idField).and(getRecordTypeCondition(recordType)); + return qualifierCondition != null ? resultCondition.and(qualifierCondition) : resultCondition; + } + + private static Condition buildQualifierCondition(Field field, MatchField.QualifierMatch qualifier) { + if (qualifier == null) { + return null; + } + var value = qualifier.value(); + return switch (qualifier.qualifier()) { + case BEGINS_WITH -> field.like(value + LIKE_OPERATOR); + case ENDS_WITH -> field.like(LIKE_OPERATOR + value); + case CONTAINS -> field.like(LIKE_OPERATOR + value + LIKE_OPERATOR); + }; + } + + private static RecordType getRecordType(IdType idType) { + return switch (idType) { + case HOLDINGS -> RecordType.MARC_HOLDING; + case INSTANCE -> RecordType.MARC_BIB; + case AUTHORITY -> RecordType.MARC_AUTHORITY; + default -> null; + }; + } + + private static IdType getIdType(IdType idType) { + return switch (idType) { + case HOLDINGS, INSTANCE, AUTHORITY -> IdType.EXTERNAL; + default -> idType; + }; + } + } diff --git a/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java b/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java index b61a2b7cf..a145ef32e 100644 --- a/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java +++ b/mod-source-record-storage-server/src/main/java/org/folio/services/RecordServiceImpl.java @@ -9,9 +9,9 @@ import static org.folio.dao.util.RecordDaoUtil.ensureRecordForeignKeys; import static org.folio.dao.util.RecordDaoUtil.ensureRecordHasId; import static org.folio.dao.util.RecordDaoUtil.ensureRecordHasSuppressDiscovery; -import static org.folio.dao.util.RecordDaoUtil.filterRecordByExternalHridValues; +import static org.folio.dao.util.RecordDaoUtil.filterRecordByExternalHridValuesWithQualifier; import static org.folio.dao.util.RecordDaoUtil.filterRecordByState; -import static org.folio.dao.util.RecordDaoUtil.getExternalIdsCondition; +import static org.folio.dao.util.RecordDaoUtil.getExternalIdsConditionWithQualifier; import static org.folio.dao.util.SnapshotDaoUtil.SNAPSHOT_NOT_FOUND_TEMPLATE; import static org.folio.dao.util.SnapshotDaoUtil.SNAPSHOT_NOT_STARTED_MESSAGE_TEMPLATE; import static org.folio.rest.util.QueryParamUtil.toRecordType; @@ -465,7 +465,11 @@ private MatchField prepareMatchField(RecordMatchingDto recordMatchingDto) { String ind1 = filter.getIndicator1() != null ? filter.getIndicator1() : StringUtils.EMPTY; String ind2 = filter.getIndicator2() != null ? filter.getIndicator2() : StringUtils.EMPTY; String subfield = filter.getSubfield() != null ? filter.getSubfield() : StringUtils.EMPTY; - return new MatchField(filter.getField(), ind1, ind2, subfield, ListValue.of(filter.getValues())); + MatchField.QualifierMatch qualifier = null; + if (filter.getQualifier() != null && filter.getQualifierValue() != null) { + qualifier = new MatchField.QualifierMatch(filter.getQualifier(), filter.getQualifierValue()); + } + return new MatchField(filter.getField(), ind1, ind2, subfield, ListValue.of(filter.getValues()), qualifier); } private TypeConnection getTypeConnection(RecordMatchingDto.RecordType recordType) { @@ -480,13 +484,14 @@ private Future processDefaultMatchField(MatchField RecordMatchingDto recordMatchingDto, String tenantId) { Condition condition = filterRecordByState(Record.State.ACTUAL.value()); List values = ((ListValue) matchField.getValue()).getValue(); + var qualifier = matchField.getQualifierMatch(); if (matchField.isMatchedId()) { - condition = condition.and(getExternalIdsCondition(values, IdType.RECORD)); + condition = condition.and(getExternalIdsConditionWithQualifier(values, IdType.RECORD, qualifier)); } else if (matchField.isExternalId()) { - condition = condition.and(getExternalIdsCondition(values, IdType.EXTERNAL)); + condition = condition.and(getExternalIdsConditionWithQualifier(values, IdType.EXTERNAL, qualifier)); } else if (matchField.isExternalHrid()) { - condition = condition.and(filterRecordByExternalHridValues(values)); + condition = condition.and(filterRecordByExternalHridValuesWithQualifier(values, qualifier)); } return recordDao.getRecords(condition, typeConnection.getDbType(), Collections.emptyList(), recordMatchingDto.getOffset(), diff --git a/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java b/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java index 455549408..47f7f62a9 100644 --- a/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java +++ b/mod-source-record-storage-server/src/test/java/org/folio/rest/impl/RecordsMatchingApiTest.java @@ -7,15 +7,16 @@ import org.apache.http.HttpStatus; import org.folio.TestUtil; import org.folio.dao.PostgresClientFactory; +import org.folio.dao.util.MatchField; import org.folio.dao.util.RecordDaoUtil; import org.folio.dao.util.SnapshotDaoUtil; import org.folio.rest.jaxrs.model.ExternalIdsHolder; import org.folio.rest.jaxrs.model.Filter; import org.folio.rest.jaxrs.model.ParsedRecord; import org.folio.rest.jaxrs.model.RawRecord; -import org.folio.rest.jaxrs.model.Record; import org.folio.rest.jaxrs.model.RecordMatchingDto; import org.folio.rest.jaxrs.model.Snapshot; +import org.folio.rest.jaxrs.model.Record; import org.hamcrest.MatcherAssert; import org.junit.After; import org.junit.Before; @@ -45,6 +46,9 @@ public class RecordsMatchingApiTest extends AbstractRestVerticleTest { private static final String PARSED_MARC_AUTHORITY_WITH_999_FIELD_SAMPLE_PATH = "src/test/resources/mock/parsedContents/parsedMarcAuthorityWith999field.json"; private static final String PARSED_MARC_HOLDINGS_WITH_999_FIELD_SAMPLE_PATH = "src/test/resources/mock/parsedContents/marcHoldingsContentWith999field.json"; private static final String PARSED_MARC_WITH_035_FIELD_SAMPLE_PATH = "src/test/resources/parsedMarcRecordContent.sample"; + private static final String FIELD_035 = "12569"; + private static final String FIELD_007 = "12345"; + private static final int SPLIT_INDEX = 2; private static String rawRecordContent; private static String parsedRecordContent; @@ -100,7 +104,9 @@ public void shouldReturnEmptyCollectionIfRecordsDoNotMatch() { .withField("999") .withIndicator1("f") .withIndicator2("f") - .withSubfield("s")))) + .withSubfield("s") + .withQualifier(Filter.Qualifier.BEGINS_WITH) + .withQualifierValue("TEST")))) .post(RECORDS_MATCHING_PATH) .then() .statusCode(HttpStatus.SC_OK) @@ -116,6 +122,8 @@ public void shouldMatchRecordByMatchedIdField() { .body(new RecordMatchingDto() .withRecordType(RecordMatchingDto.RecordType.MARC_BIB) .withFilters(List.of(new Filter() + .withQualifier(Filter.Qualifier.BEGINS_WITH) + .withQualifierValue("acf") .withValues(List.of(existingRecord.getMatchedId())) .withField("999") .withIndicator1("f") @@ -135,6 +143,17 @@ public void shouldMatchMarcBibRecordByInstanceIdField() { shouldMatchRecordByExternalIdField(existingRecord); } + @Test + public void shouldMatchMarcBibRecordByInstanceIdFieldAndQualifier() { + var instanceId = existingRecord.getExternalIdsHolder().getInstanceId(); + var beginWith = new MatchField.QualifierMatch(Filter.Qualifier.BEGINS_WITH, instanceId.substring(0, SPLIT_INDEX)); + var endWith = new MatchField.QualifierMatch(Filter.Qualifier.ENDS_WITH, instanceId.substring(SPLIT_INDEX)); + var contains = new MatchField.QualifierMatch(Filter.Qualifier.CONTAINS, instanceId.substring(SPLIT_INDEX, SPLIT_INDEX + SPLIT_INDEX)); + shouldMatchRecordByExternalIdField(existingRecord, beginWith); + shouldMatchRecordByExternalIdField(existingRecord, endWith); + shouldMatchRecordByExternalIdField(existingRecord, contains); + } + @Test public void shouldMatchMarcAuthorityRecordByAuthorityIdField(TestContext context) throws IOException { String parsedRecordContent = TestUtil.readFileFromPath(PARSED_MARC_AUTHORITY_WITH_999_FIELD_SAMPLE_PATH); @@ -193,6 +212,50 @@ private void shouldMatchRecordByExternalIdField(Record sourceRecord) { .body("identifiers[0].externalId", is(externalId)); } + private void shouldMatchRecordByExternalIdField(Record sourceRecord, MatchField.QualifierMatch qualifier) { + var externalId = RecordDaoUtil.getExternalId(sourceRecord.getExternalIdsHolder(), sourceRecord.getRecordType()); + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.valueOf(sourceRecord.getRecordType().name())) + .withFilters(List.of(new Filter() + .withValues(List.of(externalId)) + .withField("999") + .withIndicator1("f") + .withIndicator2("f") + .withSubfield("i") + .withQualifier(qualifier.qualifier()) + .withQualifierValue(qualifier.value())))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(1)) + .body("identifiers.size()", is(1)) + .body("identifiers[0].recordId", is(sourceRecord.getId())) + .body("identifiers[0].externalId", is(externalId)); + } + + private void shouldMatchRecordByInstanceHridFieldAndQualifier(MatchField.QualifierMatch qualifier) { + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.MARC_BIB) + .withFilters(List.of(new Filter() + .withValues(List.of(existingRecord.getExternalIdsHolder().getInstanceHrid())) + .withField("001") + .withQualifier(qualifier.qualifier()) + .withQualifierValue(qualifier.value())))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(1)) + .body("identifiers.size()", is(1)) + .body("identifiers[0].recordId", is(existingRecord.getId())) + .body("identifiers[0].externalId", is(existingRecord.getExternalIdsHolder().getInstanceId())); + } + @Test public void shouldMatchRecordByInstanceHridField() { RestAssured.given() @@ -212,6 +275,41 @@ public void shouldMatchRecordByInstanceHridField() { .body("identifiers[0].externalId", is(existingRecord.getExternalIdsHolder().getInstanceId())); } + @Test + public void shouldMatchRecordByInstanceHridFieldAndQualifier() { + var hrId = existingRecord.getExternalIdsHolder().getInstanceHrid(); + var beginWith = new MatchField.QualifierMatch(Filter.Qualifier.BEGINS_WITH, hrId.substring(0, SPLIT_INDEX)); + var endWith = new MatchField.QualifierMatch(Filter.Qualifier.ENDS_WITH, hrId.substring(SPLIT_INDEX)); + var contains = new MatchField.QualifierMatch(Filter.Qualifier.CONTAINS, hrId.substring(SPLIT_INDEX, SPLIT_INDEX + SPLIT_INDEX)); + shouldMatchRecordByInstanceHridFieldAndQualifier(beginWith); + shouldMatchRecordByInstanceHridFieldAndQualifier(endWith); + shouldMatchRecordByInstanceHridFieldAndQualifier(contains); + } + + @Test + public void shouldNotMatchMarcBibRecordByInstanceIdFieldAndQualifier(){ + var externalId = RecordDaoUtil.getExternalId(existingRecord.getExternalIdsHolder(), existingRecord.getRecordType()); + var qualifier = new MatchField.QualifierMatch(Filter.Qualifier.CONTAINS, "ABC"); + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.valueOf(existingRecord.getRecordType().name())) + .withFilters(List.of(new Filter() + .withValues(List.of(externalId)) + .withField("999") + .withIndicator1("f") + .withIndicator2("f") + .withSubfield("i") + .withQualifier(qualifier.qualifier()) + .withQualifierValue(qualifier.value())))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(0)) + .body("identifiers.size()", is(0)); + } + @Test public void shouldMatchRecordByMultipleDataFields() { RestAssured.given() @@ -234,6 +332,29 @@ public void shouldMatchRecordByMultipleDataFields() { .body("identifiers[0].externalId", is(existingRecord.getExternalIdsHolder().getInstanceId())); } + public void shouldMatchRecordByMultipleDataFieldsAndQualifier(MatchField.QualifierMatch qualifier) { + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.MARC_BIB) + .withFilters(List.of(new Filter() + .withValues(List.of("12345", "oclc1234567")) + .withQualifier(qualifier.qualifier()) + .withQualifierValue(qualifier.value()) + .withField("035") + .withIndicator1("") + .withIndicator2("") + .withSubfield("a")))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(1)) + .body("identifiers.size()", is(1)) + .body("identifiers[0].recordId", is(existingRecord.getId())) + .body("identifiers[0].externalId", is(existingRecord.getExternalIdsHolder().getInstanceId())); + } + @Test public void shouldMatchRecordByMultipleControlledFields() { RestAssured.given() @@ -253,6 +374,87 @@ public void shouldMatchRecordByMultipleControlledFields() { .body("identifiers[0].externalId", is(existingRecord.getExternalIdsHolder().getInstanceId())); } + @Test + public void shouldMatchRecordByMultipleDataFieldsAndQualifier() { + var beginWith = new MatchField.QualifierMatch(Filter.Qualifier.BEGINS_WITH, FIELD_007.substring(0, SPLIT_INDEX)); + var endWith = new MatchField.QualifierMatch(Filter.Qualifier.ENDS_WITH, FIELD_007.substring(SPLIT_INDEX)); + var contains = new MatchField.QualifierMatch(Filter.Qualifier.CONTAINS, FIELD_007.substring(SPLIT_INDEX, SPLIT_INDEX + SPLIT_INDEX)); + shouldMatchRecordByMultipleDataFieldsAndQualifier(beginWith); + shouldMatchRecordByMultipleDataFieldsAndQualifier(endWith); + shouldMatchRecordByMultipleDataFieldsAndQualifier(contains); + } + + @Test + public void shouldNotMatchRecordByMultipleDataFieldsAndQualifier() { + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.MARC_BIB) + .withFilters(List.of(new Filter() + .withValues(List.of("12345", "oclc1234567")) + .withQualifier(Filter.Qualifier.BEGINS_WITH) + .withQualifierValue("ABC") + .withField("035") + .withIndicator1("") + .withIndicator2("") + .withSubfield("a")))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(0)) + .body("identifiers.size()", is(0)); + } + + private void shouldMatchRecordByMultipleControlledFieldsAndQualifier(MatchField.QualifierMatch qualifier){ + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.MARC_BIB) + .withFilters(List.of(new Filter() + .withValues(List.of("12569", "364345")) + .withQualifier(qualifier.qualifier()) + .withQualifierValue(qualifier.value()) + .withField("007")))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(1)) + .body("identifiers.size()", is(1)) + .body("identifiers[0].recordId", is(existingRecord.getId())) + .body("identifiers[0].externalId", is(existingRecord.getExternalIdsHolder().getInstanceId())); + } + + @Test + public void shouldMatchRecordByMultipleControlledFieldsAndQualifier() { + var beginWith = new MatchField.QualifierMatch(Filter.Qualifier.BEGINS_WITH, FIELD_035.substring(0, SPLIT_INDEX)); + var endWith = new MatchField.QualifierMatch(Filter.Qualifier.ENDS_WITH, FIELD_035.substring(SPLIT_INDEX)); + var contains = new MatchField.QualifierMatch(Filter.Qualifier.CONTAINS, FIELD_035.substring(SPLIT_INDEX, SPLIT_INDEX + SPLIT_INDEX)); + shouldMatchRecordByMultipleControlledFieldsAndQualifier(beginWith); + shouldMatchRecordByMultipleControlledFieldsAndQualifier(endWith); + shouldMatchRecordByMultipleControlledFieldsAndQualifier(contains); + } + + @Test + public void shouldNotMatchRecordByMultipleControlledFieldsAndQualifier(){ + RestAssured.given() + .spec(spec) + .when() + .body(new RecordMatchingDto() + .withRecordType(RecordMatchingDto.RecordType.MARC_BIB) + .withFilters(List.of(new Filter() + .withValues(List.of("12569", "364345")) + .withQualifier(Filter.Qualifier.BEGINS_WITH) + .withQualifierValue("ABC") + .withField("007")))) + .post(RECORDS_MATCHING_PATH) + .then() + .statusCode(HttpStatus.SC_OK) + .body("totalRecords", is(0)) + .body("identifiers.size()", is(0)); + } + @Test public void shouldMatchRecordByMultiple024FieldsWithWildcardsInd() { RestAssured.given() diff --git a/ramls/raml-storage b/ramls/raml-storage index 195de7f9f..1e4076bf5 160000 --- a/ramls/raml-storage +++ b/ramls/raml-storage @@ -1 +1 @@ -Subproject commit 195de7f9fd0f5ef76aac6542e11b0391a67e8af0 +Subproject commit 1e4076bf5e4eae9670cd005adea7a174ccfaf681