Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Currsor get error #108

Merged
merged 2 commits into from
Nov 28, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.here.naksha.lib.core.util.json.Json;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import java.io.IOException;
import javax.annotation.concurrent.NotThreadSafe;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -209,6 +210,10 @@ public abstract class FeatureCodec<FEATURE, SELF extends FeatureCodec<FEATURE, S
* The JTS geometry build from the {@link #wkb}.
*/
protected @Nullable Geometry geometry;
/**
* The JSON of the error.
*/
protected @Nullable String errorJson;

/**
* Sets the given geometry and clears the WKB.
Expand Down Expand Up @@ -330,6 +335,22 @@ public abstract class FeatureCodec<FEATURE, SELF extends FeatureCodec<FEATURE, S
return self();
}

/**
* Sets the JSON of the error.
*
* @param json The JSON to set.
*/
public void setRawError(@Nullable String json) {
this.errorJson = json;
if (json != null) {
try (final Json jp = Json.get()) {
this.err = jp.reader().readValue(errorJson, CodecError.class);
} catch (IOException e) {
throw unchecked(e);
}
}
}

/**
* Returns the {@code id} of the feature; if it has any.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,22 @@ public boolean next() {
return feature;
}

/**
* Returns true if current row has error, otherwise false.
* @return
*/
public boolean hasError() {
return currentRow.codec.hasError();
}

/**
* Returns current row error or null.
* @return
*/
public @Nullable CodecError getError() {
return currentRow.codec.getError();
}

public @NotNull MutableCursor<FEATURE, CODEC> toMutableCursor(long limit, boolean reOrder) {
throw new UnsupportedOperationException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ protected boolean loadNextRow(@NotNull Row row) {
final String r_ptype = rs.getString(5); // may be null
final String r_feature = rs.getString(6); // may be null
final byte[] r_geo = rs.getBytes(7); // may be null
final String r_err = rs.getString(8); // may be null
// Note: Only r_ptype, r_feature and r_geo may be null!
assert r_op != null && r_id != null && r_uuid != null && r_type != null;

Expand All @@ -73,6 +74,7 @@ protected boolean loadNextRow(@NotNull Row row) {
row.codec.setPropertiesType(r_ptype);
row.codec.setJson(r_feature);
row.codec.setWkb(r_geo);
row.codec.setRawError(r_err);
row.valid = true;
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
package com.here.naksha.lib.psql;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.Mockito.when;

import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature;
import com.here.naksha.lib.core.models.storage.ForwardCursor;
Expand All @@ -45,13 +47,41 @@ void testCursorCodecChange() throws SQLException {
PsqlCursor<XyzFeature, XyzFeatureCodec> cursor = new PsqlCursor<>(xyzFeatureCodecFactory, null, statement, rs);

// when
Mockito.when(rs.next()).thenReturn(true);
Mockito.when(rs.getString(anyInt())).thenReturn("feature");
when(rs.next()).thenReturn(true);

when(rs.getString(intThat(i -> i < 6))).thenReturn("id");
when(rs.getString(6)).thenReturn("feature");
when(rs.getString(8)).thenReturn(null);
ForwardCursor<String, StringCodec> forwardCursor = cursor.withCodecFactory(stringCodecFactory, true);

// expect
assertEquals(stringCodecFactory, cursor.getCodecFactory());
assertTrue(forwardCursor.next());
assertEquals("feature", forwardCursor.getFeature());
}

@Test
void testErrorRead() throws SQLException {
// given
Statement statement = Mockito.mock(Statement.class);
ResultSet rs = Mockito.mock(ResultSet.class);
XyzFeatureCodecFactory xyzFeatureCodecFactory = XyzFeatureCodecFactory.get();

PsqlCursor<XyzFeature, XyzFeatureCodec> cursor = new PsqlCursor<>(xyzFeatureCodecFactory, null, statement, rs);

// when
when(rs.next()).thenReturn(true);

when(rs.getString(intThat(i -> i < 6))).thenReturn("id");
when(rs.getString(6)).thenReturn("{}");
when(rs.getString(8)).thenReturn("{\"err\": \"23505\", \"msg\": \"Error Message\"}");

// then
assertTrue(cursor.next());
assertTrue(cursor.hasError());
assertNotNull(cursor.getError());
assertEquals("23505", cursor.getError().err.value());
assertEquals("Error Message", cursor.getError().msg);
assertEquals("{}", cursor.getJson());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.here.naksha.lib.psql;

import static com.spatial4j.core.io.GeohashUtils.encodeLatLon;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
Expand All @@ -42,6 +43,7 @@
import com.here.naksha.lib.core.models.geojson.implementation.namespaces.XyzNamespace;
import com.here.naksha.lib.core.models.naksha.NakshaFeature;
import com.here.naksha.lib.core.models.naksha.XyzCollection;
import com.here.naksha.lib.core.models.storage.CodecError;
import com.here.naksha.lib.core.models.storage.EExecutedOp;
import com.here.naksha.lib.core.models.storage.EWriteOp;
import com.here.naksha.lib.core.models.storage.ErrorResult;
Expand Down Expand Up @@ -348,6 +350,51 @@ void testDuplicateFeatureId() throws NoCursor {
}
}

@Test
@Order(61)
@EnabledIf("runTest")
void testMultiOperationPartialFail() throws NoCursor {
assertNotNull(storage);
assertNotNull(session);

final String UNIQUE_VIOLATION_CODE = "23505";
final String UNIQUE_VIOLATION_MSG =
format("The feature with the id '%s' does exist already", SINGLE_FEATURE_ID);

// given
final WriteXyzFeatures request = new WriteXyzFeatures(collectionId());
final XyzFeature featureToSucceed = new XyzFeature("123");
request.add(EWriteOp.CREATE, featureToSucceed);
final XyzFeature featureToFail = new XyzFeature(SINGLE_FEATURE_ID);
request.add(EWriteOp.CREATE, featureToFail);

// when
final Result result = session.execute(request);

// then
assertInstanceOf(ErrorResult.class, result);
ErrorResult errorResult = (ErrorResult) result;
assertEquals(UNIQUE_VIOLATION_CODE, errorResult.reason.value());
assertEquals(UNIQUE_VIOLATION_MSG, errorResult.message);
try (ForwardCursor<XyzFeature, XyzFeatureCodec> cursor = result.getXyzFeatureCursor()) {
assertTrue(cursor.next());
assertEquals(featureToSucceed.getId(), cursor.getFeature().getId());

// error row check
assertTrue(cursor.next());
assertTrue(cursor.hasError());
assertSame(EExecutedOp.ERROR, cursor.getOp());
CodecError rowError = cursor.getError();
assertNotNull(rowError);
assertEquals(UNIQUE_VIOLATION_CODE, rowError.err.value());
Copy link
Member

@hirenkp2000 hirenkp2000 Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on how @xeus2001 explained, this was expected to return XyzErr.CONFLICT . If we have now decided to return actual Postgres error back, that is also fine as we haven't started with psql alignment yet. But we need to ensure there is consistent agreed error handling going forward (and so the next question will be, how will no-data-found error will be returned for Update scenario? .. and same question for UUID conflict error for Update operation)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware, I will change it to CONFLICT then.
Can UUID conflict be the same CONFLICT?
I will cover other cases by tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree to this, but maybe we can make this a two step fix. So lets first approve this change, as it improves the situation and then ensure that we somehow translate the unique violation code into a CONFLICT.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so we then continue with XyzErr specific codes. Yes, it makes sense not to hold this PR, as we haven't started with psql alignment yet, so no impact.

assertEquals(UNIQUE_VIOLATION_MSG, rowError.msg);
// we still should be able to read the feature
assertEquals(featureToFail.getId(), cursor.getFeature().getId());
} finally {
session.commit(true);
}
}

@Test
@Order(64)
@EnabledIf("runTest")
Expand Down