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

[WIP] Rocks condition with update #22

Open
wants to merge 89 commits into
base: rocks-condition-expr-rebase
Choose a base branch
from

Conversation

stevelorddremio
Copy link
Owner

@stevelorddremio stevelorddremio commented Mar 1, 2021

This is the RocksDB implementation for update().
Ready for review are implementations for:
L1,
L2,
L3,
Value,
CommitMetadata
Fragment
Ref - Tag
Ref - Branch (missing a new tests on KeyMutations)

switch (updateFunction.getOperator()) {
case REMOVE:
if (childPath == null || !fieldIsList(segment, childPath)) {
throw new UnsupportedOperationException();
Copy link
Owner Author

Choose a reason for hiding this comment

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

A comment you will probably get anyway is to provide more information in the exception about what is failing.

);
break;
case KEY_MUTATIONS_KEY:
if (!childPath.getChild().get().getChild().isPresent()) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Just think we may want some utility methods as there is a lot of commonality in drilling through the PathSegment hierarchy.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The Optionals and path structure do make this a bit ugly. I don't have any good ideas for cleaning this up though. Do you have any suggestions?

set(segment, childPath, setFunction.getValue());
} else if (fieldIsList(segment, childPath) != (setFunction.getValue().getType() == Entity.EntityType.LIST)) {
throw new UnsupportedOperationException();
} else if (ID.equals(segment)) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

Should we handle set equals for dt field here also from BaseValue?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I added it now, but I doubt it would actually be used.

Copy link
Owner Author

Choose a reason for hiding this comment

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

They did actually use this in their GC prototyping. Subsequently they dropped it, but shows they sometimes have a need.

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK. It's implemented now, so we should be safe if they start updating date time.

Copy link
Owner Author

Choose a reason for hiding this comment

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

I may have missed it, but can you add some tests too?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is tested in the test classes for the implementations, such as TestUpdateFunctionRocksL1.

.builder(RocksL1.KEY_MUTATIONS)
.position(0)
.name(RocksL1.KEY_MUTATIONS_MUTATION_TYPE)
.build(), Entity.ofNumber(1)));
Copy link
Owner Author

Choose a reason for hiding this comment

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

Avoid magic numbers, particularly as it is input and output data.


@Test
void childrenSetEquals() {
final UpdateExpression updateExpression =
Copy link
Owner Author

Choose a reason for hiding this comment

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

This is very similar to the childrenSetEquals() test for RocksL1. Could we have a generic test?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see what you are thinking here, but it isn't quite as simple as it looks. RocksL1 and RocksL2 do not get the children from a common base class.
Another idea here is to parameterize the Update test classes, move the list tests into the base class and then add parameters for accessing the actual lists. This would get a little ugly with the parameters but would ensure that all lists are tested the same way.
What do you think?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Yes, I'm leaning that way to parameterize the tests. I've been moving that way in TestUpdateFunctionRocksRef for branch in rocks-update-branch. Although I haven't moved anything into the base class as it is Branch specific.

set(segment, childPath, setFunction.getValue());
} else if (fieldIsList(segment, childPath) != (setFunction.getValue().getType() == Entity.EntityType.LIST)) {
throw new UnsupportedOperationException();
} else if (ID.equals(segment)) {
Copy link
Owner Author

Choose a reason for hiding this comment

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

I may have missed it, but can you add some tests too?


@Test
void childrenSetEquals() {
final UpdateExpression updateExpression =
Copy link
Owner Author

Choose a reason for hiding this comment

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

Yes, I'm leaning that way to parameterize the tests. I've been moving that way in TestUpdateFunctionRocksRef for branch in rocks-update-branch. Although I haven't moved anything into the base class as it is Branch specific.


import org.projectnessie.versioned.store.Entity;

public class EntityConverter {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why public? JavaDocs?

Copy link
Collaborator

Choose a reason for hiding this comment

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

No reason for public. Added JavaDocs.

* @param entity the entity to convert
* @return the object converted from the entity
*/
public static ValueProtos.Commit entityToCommit(Entity entity) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why public?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Changed to package.

if (!entity.getType().equals(Entity.EntityType.MAP)) {
throw new UnsupportedOperationException();
}
ValueProtos.Commit.Builder builder = ValueProtos.Commit.newBuilder();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Style should be that we append final to everything, unless you have a reason to do so otherwise.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated.

if (!level0.getValue().getType().equals(Entity.EntityType.LIST)) {
throw new UnsupportedOperationException();
}
for (Entity level1 : level0.getValue().getList()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Won't this throw if it's not a list already?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Etc. for all other checks of this sort.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You are correct. Removed the checks.

}
ValueProtos.Commit.Builder builder = ValueProtos.Commit.newBuilder();

for (Map.Entry<String, Entity> level0 : entity.getMap().entrySet()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Won't this throw already if it's not a map?

appendToList(segment, childPath, Collections.singletonList(setFunction.getValue()));
}
} else {
throw new UnsupportedOperationException(String.format("The append to list operation is not valid for \"%s\"", segment));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer stylistically to not have the throws in the else, but to have them in the if. This way you don't even need the else, remove nesting, and don't need to refer to above in the code to find out why the exception is triggered.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated what I could to remove some of the nesting and to move the throws up higher.

*/
class RocksFragment extends RocksBaseValue<Fragment> implements Fragment {
static final String KEY_LIST = "keys";

private final ValueProtos.Fragment.Builder builder = ValueProtos.Fragment.newBuilder();
private final ValueProtos.Fragment.Builder fragmentBuilder = ValueProtos.Fragment.newBuilder();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why the rename?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In my opinion, builder is an overly generic name. This gets problematic if we have a scope that also has another type of Builder, such as a Key.Builder.
Is there a reason to keep the generic name?

.newBuilder()
.addAllElements(v.getList().stream().map(Entity::getString).collect(Collectors.toList()))
.build()
).collect(Collectors.toList()));
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should be able to avoid intermediate list creation by adding incrementally and not using addAllKeys.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please apply this to all applicable locations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated.


@Override
protected void appendToList(String fieldName, ExpressionPath.PathSegment childPath, List<Entity> valuesToAdd) {
if (new PathPattern().matches(childPath)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks odd, why isn't matches a static?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the PathPattern class needs to be rethought a bit, it looks like it always creates a new one.

Copy link
Collaborator

@normanj-bitquill normanj-bitquill Mar 10, 2021

Choose a reason for hiding this comment

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

I can see that you would like to remove the PathPattern class as you don't see any benefit to it.
If I remove it, then I will be dealing with ExpressionPath.PathSegment and I will receive a lot of feedback about how difficult it is to understand what is going on.

new PathPattern().nameEquals("foo").anyPosition()
is equivalent to:
if path != null && path.isName() && "foo".equals(path.asName().getName()) && path.getChild().isPresent() && path.getChild().get().isPosition())

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not that I want to have it removed, just that it looks odd to have to new it every time it's used. I wonder if it wouldn't be better to have a static builder or something?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This could be improved, and we could have the path patterns stored static in the classes. I'll leave as is for now, since you likely have a different approach in mind.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The PathPattern has been updated. It is still created new each time it is used. The next step would be to extract the PathPatterns into static variables.

@Override
protected void remove(String fieldName, ExpressionPath.PathSegment path) {
if (TREE.equals(fieldName)) {
List<ByteString> updatedChildren = new ArrayList<>(l2Builder.getTreeList());
Copy link
Collaborator

Choose a reason for hiding this comment

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

final

Copy link
Collaborator

Choose a reason for hiding this comment

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

Updated.

Norman Jordan and others added 27 commits March 9, 2021 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants