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

Refactor scan with filtering #1715

Merged
merged 8 commits into from
May 14, 2024
Merged

Refactor scan with filtering #1715

merged 8 commits into from
May 14, 2024

Conversation

jnmt
Copy link
Contributor

@jnmt jnmt commented May 8, 2024

Description

This PR refactors ScanAll with a where clause so that we can support Get, GetWithIndex Scan, and ScanWithIndex with a where clause in a consistent way in the upcoming updates. After careful discussion, we decided to introduce a few backward-incompatible changes since cross-partition scan with filtering is a relatively new feature and we expected their users to be none or less.

Related issues and/or PRs

Changes made

  • Move Conjunction from Scan to Selection
  • Move ConditionSetBuilder and {And,Or}ConditionSet from ScanBuilder to api
  • Refactor ScanBuilder
    • Rename BuildableScanAllFromExistingWithX to BuildableScanFromExistingWithX so that it can be commonly used for Scan, ScanWithIndex and ScanAll
    • Consolidate member variables in BuildableScanFromExistingWithWhere using a composite buildable
    • Introduce OngoingWhere for the common internal condition management

Checklist

  • I have commented my code, particularly in hard-to-understand areas.
  • I have updated the documentation to reflect the changes.
  • Any remaining open issues linked to this PR are documented and up-to-date (Jira, GitHub, etc.).
  • Tests (unit, integration, etc.) have been added for the changes.
  • My changes generate no new warnings.
  • Any dependent changes in other PRs have been merged and published.

Additional notes (optional)

The following PRs will be upcoming in this order.

Release notes

Refactored scan with filtering.

@jnmt jnmt changed the title Refactor scan filtering Refactor scan with filtering May 8, 2024
AndConditionSet other = (AndConditionSet) o;
return conditions.equals(other.conditions);
}
private static class OngoingWhere {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is introduced for simply and commonly managing the internal condition state for both BuildableScanAllWithWhere and BuildableScanFromExistingWithXX. Since it can also be used for GetBuilder, this class may be moved to somewhere commonplace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, I avoided using just Where to distinguish the Where interface, but the name might be a bit weird since it will also be used after closing the where clause. InternalWhere, WhereClause, or WhereBuilder? What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

WhereBuilder sounds to me that it has build() method. So, it might be worth adding build() that returns Collection<Conjunction> or something.

WhereClause itself sounds good to me, but I noticed the term Clause isn't used in this file...

I think WhereCondition might be an option as the class contains only conditions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. I changed my mind a bit. Since the interface Where is only used twice in this file, using it with the modifier (i.e., OperationBuilder.Where) is not so bad looking. I'm going with this if no further concerns.

@@ -621,9 +591,9 @@ public static class BuildableScanOrScanAllFromExisting extends BuildableScan
OperationBuilder.Table<BuildableScanOrScanAllFromExisting>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since BuildableScanOrScanAllFromExisting is also used for changing an existing ScanWithIndex, it might be better BuildableScanAllFromExisting. But BuildableScanOrScanAllFromExisting is older than the cross-partition scan with filtering, so I think breaking it would be a bit aggressive. Please let me know if you have any opinions.

@jnmt jnmt self-assigned this May 8, 2024
@jnmt jnmt added the bugfix label May 8, 2024
@jnmt jnmt marked this pull request as ready for review May 8, 2024 09:15
@jnmt
Copy link
Contributor Author

jnmt commented May 8, 2024

Sorry, I found some null checks were accidentally deleted when refactoring. Fixed in 797b95e.

*/
public BuildableAndConditionSet and(ConditionalExpression condition) {
conditions.add(condition);
return new BuildableAndConditionSet(conditions);
Copy link
Contributor

Choose a reason for hiding this comment

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

How about copying conditions or using immutable conditions to prevent the shared mutable collection from unexpectedly being changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, that's well-mannered. Is the following expected?

  @Immutable
  public static class BuildableAndConditionSet {

    private final Set<ConditionalExpression> conditions;

    BuildableAndConditionSet(Set<ConditionalExpression> conditions) {
      this.conditions = ImmutableSet.copyOf(conditions);
    }

    public BuildableAndConditionSet and(ConditionalExpression condition) {
      return new BuildableAndConditionSet(
          new ImmutableSet.Builder<ConditionalExpression>()
              .addAll(conditions)
              .add(condition)
              .build());
    }

Copy link
Contributor

Choose a reason for hiding this comment

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

How about copying conditions or using immutable conditions to prevent the shared mutable collection from unexpectedly being changed?

I think my comment was a bit confusing. You could simply return just this if you choose the mutate builder pattern. You can also take immutable approach. Mutable builder pattern is still common, so either would be fine.

For immutable way, probably you can declare conditions as ImmutableSet while it's not an interface. This might be controversial, but I think it's acceptable since Java doesn't have immutable collection interface.

@Immutable
public static class Conjunction {
private final ImmutableSet<ConditionalExpression> conditions;
private Conjunction(ImmutableSet<ConditionalExpression> conditions) {
this.conditions = conditions;
}
would be a good example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Unfortunately, we cannot just return this here since we need to prevent users from calling or once they start with and. So, recreating a set without affecting the current set seems the best solution here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, you're right. I thought it as a usual builder pattern...

Also, I think I was thinking of defensive copy and immutability when I writing the first comment, but I forgot it when writing the second comment. Sorry for the confusion.

As we talked on another chat, immutable approach is basically better as long as there is no memory memory-pressure concerns.

this.orderings.addAll(buildable.orderings);
this.projections.addAll(buildable.projections);
this.consistency = buildable.consistency;
BuildableScanOrScanAllFromExisting buildableScanFromExisting;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
BuildableScanOrScanAllFromExisting buildableScanFromExisting;
private BuildableScanOrScanAllFromExisting buildableScanFromExisting;

This can be private?

this.projections.addAll(buildable.projections);
this.consistency = buildable.consistency;
BuildableScanOrScanAllFromExisting buildableScanFromExisting;
protected final OngoingWhere where;
Copy link
Contributor

Choose a reason for hiding this comment

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

IntelliJ shows Class 'OngoingWhere' is exposed outside its defined visibility scope. So, this should be package-private?

Suggested change
protected final OngoingWhere where;
final OngoingWhere where;

AndConditionSet other = (AndConditionSet) o;
return conditions.equals(other.conditions);
}
private static class OngoingWhere {
Copy link
Contributor

Choose a reason for hiding this comment

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

WhereBuilder sounds to me that it has build() method. So, it might be worth adding build() that returns Collection<Conjunction> or something.

WhereClause itself sounds good to me, but I noticed the term Clause isn't used in this file...

I think WhereCondition might be an option as the class contains only conditions.

@jnmt
Copy link
Contributor Author

jnmt commented May 9, 2024

@komamitsu Thank you for a lot of valuable feedback. I fixed them in a23097e. Also, I've added a little further refactoring in
1a0d6e5. PTAL when you get a chance.

@jnmt jnmt requested a review from komamitsu May 9, 2024 10:56
protected final List<Scan.Ordering> orderings = new ArrayList<>();
protected int limit;
@Nullable protected com.scalar.db.api.Consistency consistency;
protected BuildableScanAll buildableScanAll;
Copy link
Contributor

Choose a reason for hiding this comment

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

[trivial] BuildableScanAll is mutable, so you can also make this variable final and simply use it like this.

    @Override
    public BuildableScanAllWithWhere projection(String projection) {
      buildableScanAll.projection(projection);
      return this;
    }

The current code can be used as-is if BuildableScanAll changes into immutable in the future, so I don't have a strong opinion on it , though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks again. Definitely. Fixed them in 8320ae8 as you mentioned due to no plan for changing them to immutable so far.

@jnmt jnmt requested a review from komamitsu May 10, 2024 05:11
Copy link
Contributor

@komamitsu komamitsu left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you!

Copy link
Collaborator

@brfrn169 brfrn169 left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you!

Copy link
Contributor

@Torch3333 Torch3333 left a comment

Choose a reason for hiding this comment

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

LGTM, thank you!

Copy link
Contributor

@feeblefakie feeblefakie left a comment

Choose a reason for hiding this comment

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

LGTM! Thank you!
(Very sorry for the late review 🙇 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants