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

Support added for cascading row removal #561

Merged
merged 16 commits into from
Oct 31, 2014
Merged

Conversation

kspangsege
Copy link
Contributor

The documentation of Descriptor::set_link_type() explains what this is all about, so please start by reading that.

Ari, Thomas, please pay special attention to this paragraph as it may apply to the binding too, i.e., the binding may be vulnerable to rows disappearing while they are modified.

Unit testing is still missing, but please go ahead with your review anyway.

I have added pseudo code below for the non-trivial parts, since I wrote it, and since there is a chance that it can help you understand my intentions.

@finnschiermer @astigsen @rrrlasse

@alazier @tgoyne (especially the API and the documentation)

public Table::erase_row(row_ndx):
    stop_on_table_ndx = npos
    rows = set() // Set of pairs (table_ndx, row_ndx)
    rows.insert((this->table_ndx, row_ndx))
    this->find_erase_cascade(row_ndx, stop_on_table_ndx, rows)
    this->erase_rowset(this->group, rows)

public Table::clear():
    rows = set() // Set of pairs (table_ndx, row_ndx)
    for each col in this->columns:
        col->find_clear_cascade(this->table_ndx, this->num_rows, rows)
    this->erase_rowset(this->group, rows)
    # Clear the table
    # ...
    this->repl->clear_table(this)

public Table::set_link(col_ndx, row_ndx, new_target_row_ndx):
    rows = set() // Set of pairs (table_ndx, row_ndx)
    col = this->columns[col_ndx]
    if col.link_type != weak && new_target_row_ndx != col->get(row_ndx):
        stop_on_table_ndx = npos
        col->find_erase_cascade(row_ndx, stop_on_table_ndx, rows)
    # Set the link
    # ...
    this->repl->set_link(this, col_ndx, row_ndx, new_target_row_ndx)
    # Target row may be removed below (possible suicide)
    this->erase_rowset(this->group, rows)

public Table::set_link_in_list(col_ndx, row_ndx, link_ndx, new_target_row_ndx):
    rows = set() // Set of pairs (table_ndx, row_ndx)
    col = this->columns[col_ndx]
    if col.link_type != weak && new_target_row_ndx != col->get(row_ndx)->get(link_ndx):
        col->find_erase_cascade_single(row_ndx, link_ndx, rows)
    # Set the the link in the list
    # ...
    this->repl->set_link_in_list(this, col_ndx, row_ndx, link_ndx, new_target_row_ndx)
    # Target row may be removed below (possible suicide)
    this->erase_rowset(this->group, rows)

public Table::remove_link_from_list(col_ndx, row_ndx, link_ndx):
    rows = set() // Set of pairs (table_ndx, row_ndx)
    col = this->columns[col_ndx]
    if col.link_type != weak:
        this->erase_rowset(this->group, rows)
    # Remove the link from the list
    # ...
    this->repl->remove_link_from_list(this, col_ndx, row_ndx, link_ndx)
    # Target row may be removed below (possible suicide)
    this->erase_rowset(this->group, rows)

public Table::clear_link_list(col_ndx, row_ndx, link_ndx):
    rows = set() // Set of pairs (table_ndx, row_ndx)
    col = this->columns[col_ndx]
    if col.link_type != weak:
        stop_on_table_ndx = npos
        col->find_erase_cascade(row_ndx, stop_on_table_ndx, rows)
    # Clear the link list
    # ...
    this->repl->clear_link_list(this, col_ndx, row_ndx)
    # Target row may be removed below (possible suicide)
    this->erase_rowset(this->group, rows)


private Table::find_erase_cascade(row_ndx, stop_on_table_ndx, rows):
    for each col in this->columns:
        col->find_erase_cascade(row_ndx, stop_on_table_ndx, rows)

private static Table::erase_rowset(group, rows):
    for each (table_ndx, row_ndx) in (sort rows by table_ndx, then descendingly by row_ndx):
        group->get_table(table_ndx)->low_level_erase_row(row_ndx)

private Table::low_level_erase_row(row_ndx):
    # Erase the row by 'move last over'. Assuming that all group-level tables are unordered.
    # ...
    this->repl->erase_row(this, row_ndx)


public virtual ColumnBase::find_erase_cascade(row_ndx, stop_on_table_ndx, rows):
    pass

public virtual ColumnBase::find_clear_cascade(table_ndx, num_rows, rows):
    pass


public ColumnLink::find_erase_cascade(row_ndx, stop_on_table_ndx, rows) override:
    if this->link_type == weak || this->is_null(row_ndx):
        pass
    if this->target_table->table_ndx == stop_on_table_ndx:
        pass
    target_row_ndx = this->get(row_ndx)
    if this->target_table->get_num_strong_backlinks(target_row_ndx) > 1:
        pass
    if (this->target_table->table_ndx, target_row_ndx) in rows:
        pass
    rows.insert((this->target_table->table_ndx, target_row_ndx))
    this->target_table->find_erase_cascade(target_row_ndx, stop_on_table_ndx, rows)

public ColumnLink::find_clear_cascade(table_ndx, num_rows, rows) override:
    if this->link_type == weak:
        pass
    if this->target_table->table_ndx == table_ndx:
        pass
    for row_ndx in range(num_rows):
        if this->is_null(row_ndx):
            continue
        target_row_ndx = this->get(row_ndx)
        if this->target_table->get_num_strong_backlinks(target_row_ndx) > 1:
            continue
        if (this->target_table->table_ndx, target_row_ndx) in rows:
            continue
        rows.insert((this->target_table->table_ndx, target_row_ndx))
        stop_on_table_ndx = table_ndx
        this->target_table->find_erase_cascade(target_row_ndx, stop_on_table_ndx, rows)


public ColumnLinkList::find_erase_cascade(row_ndx, stop_on_table_ndx, rows) override:
    if this->link_type == weak:
        pass
    if this->target_table->table_ndx == stop_on_table_ndx:
        pass
    link_list = this->get(row_ndx)
    for each target_row_ndx in link_list:
        if this->target_table->get_num_strong_backlinks(target_row_ndx) > 1:
            continue
        if (this->target_table->table_ndx, target_row_ndx) in rows:
            continue
        rows.insert((this->target_table->table_ndx, target_row_ndx))
        this->target_table->find_erase_cascade(target_row_ndx, stop_on_table_ndx, rows)

public ColumnLinkList::find_erase_cascade_single(row_ndx, link_ndx, rows):
    if this->link_type == weak:
        pass
    link_list = this->get(row_ndx)
    target_row_ndx = link_list->get(link_ndx):
    if this->target_table->get_num_strong_backlinks(target_row_ndx) > 1:
        pass
    if (this->target_table->table_ndx, target_row_ndx) in rows:
        pass
    rows.insert((this->target_table->table_ndx, target_row_ndx))
    stop_on_table_ndx = npos
    this->target_table->find_erase_cascade(target_row_ndx, stop_on_table_ndx, rows)

public ColumnLinkList::find_clear_cascade(table_ndx, num_rows, rows) override:
    if this->link_type == weak:
        pass
    if this->target_table->table_ndx == table_ndx:
        pass
    for row_ndx in range(num_rows):
        link_list = this->get(row_ndx)
        for each target_row_ndx in link_list:
            if this->target_table->get_num_strong_backlinks(target_row_ndx) > 1:
                continue
            if (this->target_table->table_ndx, target_row_ndx) in rows:
                continue
            rows.insert((this->target_table->table_ndx, target_row_ndx))
            stop_on_table_ndx = table_ndx
            this->target_table->find_erase_cascade(target_row_ndx, stop_on_table_ndx, rows)

///
/// Note that if a link is replaced by itself (a link to the same target
/// row), then the link is *not* considered broken, and no rows will be
/// cascade-removed by such an operation.
Copy link
Contributor

Choose a reason for hiding this comment

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

We might also want to add a method to "detach" a link. For those cases where you have a strong link to an object, but you want to explicitly remove the link, while keeping the object (kinda as a manual override to circumvent the cascade rules).

This could especially be needed if you need to move sub-objects from one parent object to another.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see your point, I would however prefer to postpone the addition of the 'detach' operation, until after I have presented my full GC idea, as it would render that operation meaningless.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By the way, it is already easy to move a subobject from one parent to another. For example, if you want to move A from B to C:

table.set_link(col_ndx, row_ndx_c, row_ndx_a);
table.nullify_link(col_ndx, row_ndx_b);

The point is, that you have to "clone" the link, before removing it, just as you would have to do in a regular reference counting situation.

To a very large extent, the simple cascade removal scheme, that is now implemented, behaves just like regular reference counting. And, to an almost equally large extent, my full GC idea behaves just like a regular tracing garbage collector. The only exception is that objects are removed immediately, rather than eventually.

@finnschiermer
Copy link
Contributor

Here are my suggestions. Mostly they reflect which part of the code I found hard to grasp:

  1. A brief explanation put in a reasonable place, highlighting that we first collect rows and only later erase them.
  2. Naming improvement: from erase_xxx to collect_dead_rows_xxx or something similar which in a more obvious way distinguishes between collecting rows and erasing them
  3. Naming improvement: there are some cascade, cascade2, cascade3 in columnlinklist, which might benefit from a having more telling names indicating their purpose
  4. Naming improvement: from cascade_rows to something like set_of_rows, perhaps
    otherwise +1

@kspangsege
Copy link
Contributor Author

Finn, I now renamed vector typedef from cascade_rows to cascade_rowset, all the erase_cascade_xxx() methods to find_erase_cascade_xxx(), and Table::erase_rows__w_repl__wo_cascade() to Table::erase_rowset(). I also expanded/improved the documentation in various places.

@alazier
Copy link
Contributor

alazier commented Oct 10, 2014

Overall this looks good. It's a bit different than what we originally had in mind for the binding api, but the more I think about it the more I think this model may work better.

The biggest concern I have though is that users will run into errors when changing link values (unsetting before re-setting). It would be very nice if we could delay deletion in the case there is an outstanding object accessor until the transaction is committed. That way a user can pull an object out, unset the link, and then have the choice to set another link with that object before the transaction ends. This model would also allow us to add another method to allow a user to explicitly preserve an object which was unset from a link if so desired.

@kspangsege
Copy link
Contributor Author

The implementation of cascading row removal in this branch has undergone a major update to fix a severe design flaw in the first version.

The new version breaks backlinks as it recurses into the link graph such that it knows the true resulting link counts. It postpones the breaking of forward links until the final row removals, which effectively means that the breaking of forward links can be skipped entirely. For a more detailed description, see the documentation of Table::cascade_break_backlinks_to()

There are a few other significant changes at this point:

  1. There is no more crossover between Table::remove() and Table::move_last_over(). This was necessary to avoid also having to add the cascading mechanism to Table::remove(), where it would be mostly superfluous. Please also read the note under api-breaking-changes.
  2. BacklinkColumn::remove_all_backlinks() was introduced for more efficient table clearing.

Also, there is a new general pattern for API functions that do replication and also cascade removal. I expect that it will apply well to other replicated functions too. The general idea is that for each operation foo(), there is a do_foo(), that does not do replication, and does not do cascade removal. This lower level function is called from foo(), and it is also called directly when applying replication logs. This works well with the current design, because each row removal of the cascade is replicated as an individual stand-alone non-cascading row-removal operation. For example,

Table::foo()
{
    repl->foo();
    do_foo();
}

Table::do_foo()
{
    // Do foo...
    bump_version();
}

@kspangsege
Copy link
Contributor Author

Again, there is Python-like pseudo code, if you are interested:

public Table::move_last_over(row_ndx):
     if this->is_group_level:
         state = {}
         state.rows = [] // Set of pairs (table_ndx, row_ndx)
         state.rows += [ (this->ndx_in_group, row_ndx) ]
         this->cascade_break_backlinks_to(row_ndx, state)
         this->remove_backlink_broken_rows(state.rows)
     else:
         this->repl->move_last_over(this, row_ndx)
         broken_reciprocal_backlinks = false
         this->do_move_last_over(row_ndx, broken_reciprocal_backlinks)

# Also called by replication with broken_reciprocal_backlinks=false
public Table::do_move_last_over(row_ndx, broken_reciprocal_backlinks):
    # ...


public Table::clear():
    this->repl->clear_table(this)
    if this->is_group_level:
        state = {}
        state.rows = [] // Set of pairs (table_ndx, row_ndx)
        state.stop_on_table = this
        this->cascade_break_backlinks_to_all_rows(state)
        broken_reciprocal_backlinks = true
        this->do_clear(broken_reciprocal_backlinks)
        this->remove_backlink_broken_rows(rows)
    else:
        broken_reciprocal_backlinks = false
        this->do_clear(broken_reciprocal_backlinks)

# Also called by replication with broken_reciprocal_backlinks=false
public Table::do_clear(broken_reciprocal_backlinks):
    # ...


# Also handles nullification by passing null as new_target_row_ndx
public Table::set_link(col_ndx, row_ndx, new_target_row_ndx):
    this->repl->set_link(this, col_ndx, row_ndx, new_target_row_ndx)
    old_target_row_ndx = this->do_set_link(col_ndx, row_ndx, new_target_row_ndx)
    if old_target_row_ndx is null:
        return
    if col->link_type is not strong:
        return
    if col->target_table->get_num_strong_backlinks(old_target_row_ndx) > 0:
        return
    state = {}
    state.rows = [] // Set of pairs (table_ndx, row_ndx)
    state.rows += [ (col->target_table->ndx_in_group, old_target_row_ndx) ]
    col->target_table->cascade_break_backlinks_to(old_target_row_ndx, state)
    this->remove_backlink_broken_rows(state.rows)

# Also called by replication
public Table::do_set_link(col_ndx, row_ndx, new_target_row_ndx):
    col = this->columns[col_ndx]
    old_target_row_ndx = col->get(row_ndx)
    col->set(row_ndx, new_target_row_ndx)
    # Add a backlink at new_target_row_ndx that points to row_ndx
    if new_target_row_ndx is not null:
        col->backlink_column->add_backlink(new_target_row_ndx, row_ndx)
    # Remove the backlink at old_target_row_ndx that points to row_ndx
    if old_target_row_ndx is not null:
        col->backlink_column->remove_one_backlink(old_target_row_ndx, row_ndx)
    return old_target_row_ndx


public LinkList::set(link_ndx, new_target_row_ndx):
    this->repl->set(this, link_ndx, new_target_row_ndx)
    old_target_row_ndx = this->do_set(link_ndx, new_target_row_ndx)
    if this->origin_column->link_type is not strong:
        return
    if this->target_table->get_num_strong_backlinks(old_target_row_ndx) > 0:
        return
    state = {}
    state.rows = [] // Set of pairs (table_ndx, row_ndx)
    state.rows += [ (this->target_table->ndx_in_group, old_target_row_ndx) ]
    this->target_table->cascade_break_backlinks_to(old_target_row_ndx, state)
    this->target_table->remove_backlink_broken_rows(state.rows)

# Also called by replication
public LinkList::do_set(link_ndx, new_target_row_ndx):
    old_target_row_ndx = this->get(link_ndx)
    this->set(link_ndx, new_target_row_ndx)
    this->backlink_column->add_backlink(new_target_row_ndx, this->origin_row_ndx)
    this->backlink_column->remove_one_backlink(old_target_row_ndx, this->origin_row_ndx)
    return old_target_row_ndx


public LinkList::remove(link_ndx):
    this->repl->remove(this, link_ndx)
    target_row_ndx = this->do_remove(link_ndx)
    if this->origin_column->link_type is not strong:
        return
    if this->target_table->get_num_strong_backlinks(target_row_ndx) > 0:
        return
    state = {}
    state.rows = [] // Set of pairs (table_ndx, row_ndx)
    state.rows += [ (this->target_table->ndx_in_group, target_row_ndx) ]
    this->target_table->cascade_break_backlinks_to(target_row_ndx, state)
    this->target_table->remove_backlink_broken_rows(state.rows)

# Also called by replication
public LinkList::do_remove(link_ndx):
    target_row_ndx = this->get(link_ndx)
    this->remove(link_ndx)
    this->backlink_column->remove_one_backlink(target_row_ndx, this->origin_row_ndx)
    return target_row_ndx


public LinkList::clear():
    this->repl->clear(this, link_ndx)
    if this->origin_column->link_type is not strong:
        broken_reciprocal_backlinks=false
        this->do_clear(broken_reciprocal_backlinks=false)
        return
    state = {}
    state.rows = [] // Set of pairs (table_ndx, row_ndx)
    state.stop_on_link_list_column  = this->origin_column
    state.stop_on_link_list_row_ndx = this->origin_row_ndx
    for link_ndx in range(this->num_links):
        target_row_ndx = this->get(link_ndx)
        this->backlink_column->remove_one_backlink(target_row_ndx, this->origin_row_ndx)
        if this->target_table->get_num_strong_backlinks(target_row_ndx) > 0:
            continue
        state.rows += [ (this->target_table->ndx_in_group, target_row_ndx) ]
        this->target_table->cascade_break_backlinks_to(target_row_ndx, state)
    broken_reciprocal_backlinks=true
    this->do_clear(broken_reciprocal_backlinks=false)
    this->target_table->remove_backlink_broken_rows(state.rows)

# Also called by replication with broken_reciprocal_backlinks=false
public LinkList::do_clear(broken_reciprocal_backlinks):
    # ...


private Table::cascade_break_backlinks_to(row_ndx, state):
    for each col in this->columns:
        col->cascade_break_backlinks_to(row_ndx, state)

# state.stop_on_table must be set to the origin table (origin table of
# corresponding forward links). state.stop_on_link_list_column must be null.
# Has the same affect as calling cascade_break_backlinks_to() once for each row.
private Table::cascade_break_backlinks_to_all_rows(state):
    for each col in this->columns:
        col->cascade_break_backlinks_to_all_rows(state)

private Table::remove_backlink_broken_rows(rows):
    for each (table_ndx, row_ndx) in (sort rows by table_ndx, then descendingly by row_ndx):
        table = this->group->get_table(table_ndx)
        table->repl->move_last_over(row_ndx)
        broken_reciprocal_backlinks = true
        table->do_move_last_over(row_ndx, broken_reciprocal_backlinks)


public virtual ColumnBase::cascade_break_backlinks_to(row_ndx, state):
    pass

public virtual ColumnBase::cascade_break_backlinks_to_all_rows(state):
    pass


public ColumnLink::cascade_break_backlinks_to(row_ndx, state) override:
    target_row_ndx = this->get(row_ndx)
    if target_row_ndx is null:
            return
    # Remove the backlink at target_row_ndx that points to row_ndx
    this->backlink_column->remove_one_backlink(target_row_ndx, row_ndx)
    if this->link_type is not strong:
        return
    if this->target_table == state.stop_on_table:
        return
    this->check_cascade_break_backlinks_to(target_row_ndx, state)

public ColumnLink::cascade_break_backlinks_to_all_rows(state) override:
    this->backlink_column->remove_all_backlinks()
    if this->link_type is not strong:
        return
    if this->target_table == state.stop_on_table:
        return
    for row_ndx in range(this->num_rows):
        target_row_ndx = this->get(row_ndx)
        if target_row_ndx is null:
            continue
        this->check_cascade_break_backlinks_to(target_row_ndx, state)


public ColumnLinkList::cascade_break_backlinks_to(row_ndx, state) override:
    if row_ndx == state.stop_on_link_list_row_ndx and this == state.stop_on_link_list_column:
        return
    link_list = this->get(row_ndx)
    for each target_row_ndx in link_list:
        # Remove one backlink at target_row_ndx that points to row_ndx
        this->backlink_column->remove_one_backlink(target_row_ndx, row_ndx)
        if this->link_type is not strong:
            continue
        if this->target_table == state.stop_on_table:
            continue
        this->check_cascade_break_backlinks_to(target_row_ndx, state)

public ColumnLinkList::cascade_break_backlinks_to_all_rows(num_rows, state) override:
    this->backlink_column->remove_all_backlinks()
    if this->link_type is not strong:
        return
    if this->target_table == state.stop_on_table:
        return
    for row_ndx in range(num_rows):
        link_list = this->get(row_ndx)
        for each target_row_ndx in link_list:
            this->check_cascade_break_backlinks_to(target_row_ndx, state)


public ColumnLinkBase::check_cascade_break_backlinks_to(target_row_ndx, state) override:
    if (this->target_table->ndx_in_group, target_row_ndx) in state.rows:
        return
    if this->target_table->get_num_strong_backlinks(target_row_ndx) > 0:
        return
    state.rows.insert((this->target_table->ndx_in_group, target_row_ndx))
    this->target_table->cascade_break_backlinks_to(target_row_ndx, state)

@kspangsege
Copy link
Contributor Author

Testing is incomplete, but please go ahead with the review.

@alazier
Copy link
Contributor

alazier commented Oct 28, 2014

These new changes sound good, but my previous concerns still stand - ie it is very odd from the user's perspective to have an object get deleted in the case they are holding a valid accessor to that object.

For example lets say I want to move an object to a new index in a LinkView where the LinkView holds the only strong link. I would expect to be able to get a reference to an object, remove that object from the LinkView, then add it back to the LinkView without issue. Unfortunately with the current design such an object would get deleted. In this case you can order your operations to prevent this from occurring, but expecting all users to understand these nuances may be too much to ask, and there may be other scenarios where there is no sane way to prevent an object from being deleted without creating 'fake' temporary links to the object.

Users will without a doubt run into this issue unless we provide a way to allow users to prevent object from being deleted. Counting outstanding row accessors as strong links to objects would seem to be a natural way to do this, as by holding onto a reference a user is denoting interest in that object, and most likely intends to use it sometime in the future. If we can delay deletion until the accessor is freed this would give the user an opportunity to create a new link if desired, and would avoid this issue altogether.

@astigsen
Copy link
Contributor

I see you point Ari, and I agree that we need a way to 'detach' an object
without deleting it. Especially for the case where you want to move objects
between locations. But I don't think that accessors are the right place for
that functionality. The problem with accessors in that context, is that
they span transaction boundaries (which is kinda one of their main reasons
of existence). So if you have an accessor pointing to an object, which then
gets removed from another object in a way that should get it deleted, when
do you then do the actual deletion? At some point the accessor may go out
of scope or be explicitly freed, but that may not be in a write
transaction, so how do you do you apply the changes?

I think that the right way would be to offer specific methods to detach
objects (i.e. remove the link to them) without deleting them. In that way
you can explicitly choose to ignore the cascade rules.

/a

On Tue, Oct 28, 2014 at 12:21 PM, Ari Lazier [email protected]
wrote:

These new changes sound good, but my previous concerns still stand - ie it
is very odd from the user's perspective to have an object get deleted in
the case they are holding a valid accessor to that object.

For example lets say I want to move an object to a new index in a LinkView
where the LinkView holds the only strong link. I would expect to be able to
get a reference to an object, remove that object from the LinkView, then
add it back to the LinkView without issue. Unfortunately with the current
design such an object would get deleted. In this case you can order your
operations to prevent this from occurring, but expecting all users to
understand these nuances may be too much to ask, and there may be other
scenarios where there is no sane way to prevent an object from being
deleted without creating 'fake' temporary links to the object.

Users will without a doubt run into this issue unless we provide a way to
allow users to prevent object from being deleted. Counting outstanding row
accessors as strong links to objects would seem to be a natural way to do
this, as by holding onto a reference a user is denoting interest in that
object, and most likely intends to use it sometime in the future. If we can
delay deletion until the accessor is freed this would give the user an
opportunity to create a new link if desired, and would avoid this issue
altogether.


Reply to this email directly or view it on GitHub
#561 (comment).

@alazier
Copy link
Contributor

alazier commented Oct 28, 2014

An alternative would be to delay any cascading deletes until the end of a transaction. Object could go into a 'pending delete' state would be finalized when the transaction is committed. Until deletion is finalized existing accessors would remain attached and it would be possible to create new strong links to those objects to prevent them from being deleted.

I think asking the user to explicitly hold on to an object wont work from an api perspective, as most users wont understand when they need to do this. That being said, its may be possible to hide some of the complexity in the binding if we go that route, although that may have negative impacts on performance.

@astigsen
Copy link
Contributor

The key problem here is the confusion over what happens when you remove an
object from an array. The user can have (at least) three different intents:

  1. The object should not be deleted (it should just be detached), since I
    want to use it later.
  2. The object should definitely be deleted.
  3. The object should be deleted if there are no other strong links to it.

With the current setup removing an object will have behavior #3. By just
deleting the object itself you can get #2. The problem comes if what they
want is #1. The easiest way to solve this is to give them an explicit
method to detach an object, but maybe we can find some other way?

/a

On Tue, Oct 28, 2014 at 1:07 PM, Ari Lazier [email protected]
wrote:

An alternative would be to delay any cascading deletes until the end of a
transaction. Object could go into a 'pending delete' state would be
finalized when the transaction is committed. Until deletion is finalized
existing accessors would remain attached and it would be possible to create
new strong links to those objects to prevent them from being deleted.

I think asking the user to explicitly hold on to an object wont work from
an api perspective, as most users wont understand when they need to do
this. That being said, its may be possible to hide some of the complexity
in the binding if we go that route, although that may have negative impacts
on performance.


Reply to this email directly or view it on GitHub
#561 (comment).

@kspangsege
Copy link
Contributor Author

Ari, Alexander, I'll get back to you later on this. I have already been
thinking at length about these issues. Altogether, the current model is
rather crude, and suffers from a number of issues, such as the one you
mentioned. I have already proposed what I think is a far better model,
namely a 'garbage collection' model. It solves all the problems I am aware
of, and it is very simple and elegant too. So far, I have not been able to
convince you guys, but then again, I have not yet presented the idea
properly, and shown you all its virtues.

For the next couple of weeks, I will focus on sync, but then I will get
back to these issues.

On Tue, Oct 28, 2014 at 11:50 PM, astigsen [email protected] wrote:

The key problem here is the confusion over what happens when you remove an
object from an array. The user can have (at least) three different
intents:

  1. The object should not be deleted (it should just be detached), since I
    want to use it later.
  2. The object should definitely be deleted.
  3. The object should be deleted if there are no other strong links to it.

With the current setup removing an object will have behavior #3. By just
deleting the object itself you can get #2. The problem comes if what they
want is #1. The easiest way to solve this is to give them an explicit
method to detach an object, but maybe we can find some other way?

/a

On Tue, Oct 28, 2014 at 1:07 PM, Ari Lazier [email protected]
wrote:

An alternative would be to delay any cascading deletes until the end of
a
transaction. Object could go into a 'pending delete' state would be
finalized when the transaction is committed. Until deletion is finalized
existing accessors would remain attached and it would be possible to
create
new strong links to those objects to prevent them from being deleted.

I think asking the user to explicitly hold on to an object wont work
from
an api perspective, as most users wont understand when they need to do
this. That being said, its may be possible to hide some of the
complexity
in the binding if we go that route, although that may have negative
impacts
on performance.


Reply to this email directly or view it on GitHub
#561 (comment).


Reply to this email directly or view it on GitHub
#561 (comment).

@finnschiermer
Copy link
Contributor

The convention of having a xxx and a do_xxx matches to some degree similar choices in other portions of the code. But in the long term, I'd prefer a more meaningful prefix instead of "do_".

kspangsege pushed a commit that referenced this pull request Oct 31, 2014
Support added for cascading row removal
@kspangsege kspangsege merged commit 63253e0 into master Oct 31, 2014
@kspangsege kspangsege deleted the ks-cascading-row-removal branch February 23, 2015 21:58
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants