Skip to content

EditableTree: transaction-based editing#12182

Closed
sharptrip wants to merge 19 commits into
microsoft:mainfrom
sharptrip:edit-proxy
Closed

EditableTree: transaction-based editing#12182
sharptrip wants to merge 19 commits into
microsoft:mainfrom
sharptrip:edit-proxy

Conversation

@sharptrip
Copy link
Copy Markdown
Contributor

@sharptrip sharptrip commented Sep 29, 2022

An initial implementation of a transaction-based editing with EditableTree.

Maybe it was too much to put both editing and handling sequences here.

Everything in this PR may be discussed, changed, removed.

In general, supported editing scenarios so far:

  • insert/update/delete a single node with cursors using symbols and JS-like
    JS-like updates (proxy.name = "adam") work only for primitive values. Same for inserts i.e. assigning the field which does not yet exist.
  • append to an explicit array (a sequence under a primary key) and set value by index for primitives

If absolutely needed to change an array, one could remove it completely from the tree and insert a new one =)

Keep in mind that deleting a node with children will currently leave an "anchor trace" in a forest. This is how AnchorSet works right now.

@github-actions github-actions Bot added area: dds Issues related to distributed data structures public api change Changes to a public API labels Sep 29, 2022
@github-actions github-actions Bot added the base: main PRs targeted against main branch label Sep 29, 2022
});
});

it("can edit using editable-tree", async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Was removing this test intended? If so why? Seems like we could update it to use the new API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it was accidental, as my VS Code didn't list this change before I pushed. I'll put it back.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@CraigMacomber Restored, but without anchorSymbol, which seems to be not needed anymore as an editing is implemented here, to some extent at least.
However, as more I think about that, more I get to the point, that it might actually be very useful to be able to get a path for a node of EditableTree. What do you think?

Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTree.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeContext.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTree.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTree.ts Outdated
"Cannot resolve a field type, use 'insertNodeSymbol' instead");
const name = [...fieldSchema.types][0];
const type: NamedTreeSchema = { name, ...target.context.forest.schema.lookupTreeSchema(name) };
const jsonValue = isPrimitiveValue(value) ? value : value as object;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First I want to make sure I understand the editing contract that is implemented here: if I am following the code correctly, it looks like this adds support for three kinds of "set" operations:

  1. Overwriting the primitive value of an existing node.
  2. Inserting a new leaf node that represents the given primitive value where no such node exists currently.
  3. Inserting a new subtree where no such node exists currently.
    Is that correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, that's correct more or less. Currently it's limited with a functionality of TypedJsonCursor, e.g., one shouldn't use it if a schema contains polymorphic fields (same for the context of a subtree), or optional fields, etc.. Still a lot to be done here.

Copy link
Copy Markdown
Contributor

@yann-achard-MS yann-achard-MS Sep 30, 2022

Choose a reason for hiding this comment

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

You could also, in the case where there is a subtree present for the given key and the given value is not a primitive, replace the existing subtree with a combination of delete and insert.

I'm not demanding that you add that in this PR though, just pointing out the possibility.
Feel free to close this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I like the idea. I'll keep in mind.

Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTree.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeContext.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeContext.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeContext.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeSequence.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeSequence.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeSequence.ts Outdated
Comment thread packages/dds/tree/src/feature-libraries/editable-tree/editableTreeSequence.ts Outdated
@sharptrip sharptrip marked this pull request as draft October 5, 2022 15:44
assert.equal("zip" in person.address, true);
assert.throws(() => {
person.address[insertNodeSymbol]("zip", singleTextCursor({ value: 99038, type: int32Schema.name }));
}, /Insertion into a non-empty non-sequence field. Consider to use 'setValueSymbol' or delete the node first./);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like setValueSymbol is not supported yet, but its exposed in the API

const [provider, trees] = await createSharedTrees(fullSchemaData, personData, 2);
const person = trees[0].root as PersonType;
assert(isUnwrappedNode(person.address));
assert(isEditableFieldSequence(person.address.phones));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What happens if the property is not editable and you try to edit/delete it via the proxy, does it throw an exception ?
If yes, can we add a test case for that ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If it is not a sequence, you can read and change it as an object. If you mean the line above, in a negative case you get a primitive. Anyway, you can edit/delete it using its predecessor and symbols, for deletes even like delete person.address.phones.

* @returns the number of immediate children for the given key of the currently selected node.
*/
length(key: FieldKey): number;
length(key?: FieldKey): number;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you are going to change the API here, you need to update the documentation and tests accordingly.

If no key is provided, it looks like you are returning the number of siblings of the current node?
This seems like a reasonable change to make. You might also an extra feature in the new cursor API to handle whatever needed this change as well.

CraigMacomber pushed a commit that referenced this pull request Oct 11, 2022
This PR enables support of global fields in EditableTree and refactors
tests.

**Step 2** in a series to push code from #12182.

[Step 1](#12300)
CraigMacomber pushed a commit that referenced this pull request Oct 14, 2022
This PR provide a way to access fields of EditableTree without
unwrapping. Currently, this works only on "all or nothing" basis meaning
that (given we got an instance of EditableTreeContext with
`getEditableTreeContext()`):
- one can call `context.unwrappedRoot` to get root of EditableTree with
all children fields unwrapped into just values of their nodes if those
are primitives and the field has a non-sequence schema, nodes with a
sequence primary field ("arrays") unwrapped into arrays and so on.
- and one can call `context.root` to get root field typed as
`EditableField`, where no unwrapping happens, and then iterate over its
nodes, which is just an array of EditableTrees.

In turn, one can iterate over `EditableTree` to get its fields as
EditableFields.

Please, check the `"traverse a complete tree (generic)"` test case and
the corresponding `expectFieldEquals` and `expectNodeEquals` helper
functions to see how this works.

Opinions, comments, suggestions are very welcomed.

**Step 3** in a series to push code from #12182 to enable editing of
EditableTree.

[Step 2](#12306)
[Step 1](#12300)
CraigMacomber added a commit that referenced this pull request Oct 27, 2022
Same as
[#12488](#12512), but
using a new cursor API.

## Description

This PR implements `EditableField` as an array-like sequence of nodes
(`EditableTrees`), which
1) replaces handling of sequence fields with arrays,
2) allows to access nodes lazily by their indices with and without
unwrapping, and
3) is a required change to implement editing of `EditableTree`.

## Reviewer Guidance

Everything around "root" probably might be considered as a workaround as
currently `cursor.getPath()` and anchors do not support fields.

Also, it seems that "primaryField" property of `FieldProxyTarget` is
redundant, as `fieldKey` is also there, but it adds clarity, and we
might make use of it when implementing editing.

## Other information or known dependencies

**Step 4** in a series to push code from #12182 to enable editing of
EditableTree.

[Step 3](#12428)
[Step 2](#12306)
[Step 1](#12300)

Co-authored-by: Craig Macomber (Microsoft) <42876482+CraigMacomber@users.noreply.github.com>
sharptrip added a commit to sharptrip/FluidFramework that referenced this pull request Oct 28, 2022
)

Same as
[microsoft#12488](microsoft#12512), but
using a new cursor API.

## Description

This PR implements `EditableField` as an array-like sequence of nodes
(`EditableTrees`), which
1) replaces handling of sequence fields with arrays,
2) allows to access nodes lazily by their indices with and without
unwrapping, and
3) is a required change to implement editing of `EditableTree`.

## Reviewer Guidance

Everything around "root" probably might be considered as a workaround as
currently `cursor.getPath()` and anchors do not support fields.

Also, it seems that "primaryField" property of `FieldProxyTarget` is
redundant, as `fieldKey` is also there, but it adds clarity, and we
might make use of it when implementing editing.

## Other information or known dependencies

**Step 4** in a series to push code from microsoft#12182 to enable editing of
EditableTree.

[Step 3](microsoft#12428)
[Step 2](microsoft#12306)
[Step 1](microsoft#12300)

Co-authored-by: Craig Macomber (Microsoft) <42876482+CraigMacomber@users.noreply.github.com>
alexvy86 pushed a commit to alexvy86/FluidFramework that referenced this pull request Nov 1, 2022
)

Same as
[microsoft#12488](microsoft#12512), but
using a new cursor API.

## Description

This PR implements `EditableField` as an array-like sequence of nodes
(`EditableTrees`), which
1) replaces handling of sequence fields with arrays,
2) allows to access nodes lazily by their indices with and without
unwrapping, and
3) is a required change to implement editing of `EditableTree`.

## Reviewer Guidance

Everything around "root" probably might be considered as a workaround as
currently `cursor.getPath()` and anchors do not support fields.

Also, it seems that "primaryField" property of `FieldProxyTarget` is
redundant, as `fieldKey` is also there, but it adds clarity, and we
might make use of it when implementing editing.

## Other information or known dependencies

**Step 4** in a series to push code from microsoft#12182 to enable editing of
EditableTree.

[Step 3](microsoft#12428)
[Step 2](microsoft#12306)
[Step 1](microsoft#12300)

Co-authored-by: Craig Macomber (Microsoft) <42876482+CraigMacomber@users.noreply.github.com>
@sharptrip
Copy link
Copy Markdown
Contributor Author

done

@sharptrip sharptrip closed this Nov 9, 2022
@sharptrip sharptrip deleted the edit-proxy branch November 9, 2022 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: dds Issues related to distributed data structures base: main PRs targeted against main branch community-contribution public api change Changes to a public API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants