awesomemotive / easy-digital-downloads

Sell digital downloads through WordPress
https://easydigitaldownloads.com
GNU General Public License v2.0
868 stars 473 forks source link

3.0 - Allow order and discount IDs to be directly migrated, without the need for legacy IDs using auto_increment. #7210

Closed mintplugins closed 5 years ago

mintplugins commented 5 years ago

I am opening this as a place to have a conversation and record of the thoughts behind how the id column will operate in EDD3, what its purpose is, and what the expectations are for it going forward from EDD3.

Previously to EDD3.0, payment IDs were automatically assigned by WordPress, as they were stored in the wp_posts table. Any blog post or revision (or any other post_type) that would happen "in the middle" of payments, would cause their IDs to become non sequential.

For example, imagine this series of events:

  1. Payment comes in (ID is 1)
  2. Blog post is published (ID is 2)
  3. Another blog post is published (ID is 3) 4) Another payment comes in (ID is 4)

You now have 2 payments:

EDD 3.0 will migrate those payments to their own unique table, and also change their terminology from "payments" into "orders". That new table has an ID column which is sequential. So after migration, you will have 2 payments:

The problem here is that the ID of Payment/Order 4 has changed to 2. Since the ID has been used for the ID of the payment, the invoice, and receipts, customers have records of their payment with the id 4 attached. We do not know the legality of changing an order number in all countries and regions. Aside from legality, we do not know if this will cause issues in 3rd party book-keeping integrations like Xero and others.

For this reason, a second ID column is being proposed which would keep the "legacy" IDs exactly the same after migration as before. While the id column would still be incremental (1,2,3), the order_id column would be considered as the "official" id of the order. After migration, using the example above, the next payment that comes in would sequentially start where the payments were left off. So it would look like this:

The id column would be ignored by any code that intends to query post the ID of the payment.

mintplugins commented 5 years ago

A few other issues that could arise if IDs change (props @chriscct7):

pippinsplugins commented 5 years ago

I would really like to avoid adding another ID column. We already have three ID-type fields:

While I do feel the case for adding another column, introducing another ID will ultimately lead to too much confusion. It will cause questions like:

I think there are too many issues that would be introduced by an additional ID column to warrant it.

We cannot possibly know what all of the legal ramifications are for changing order IDs, but I do know that store owners change systems (be it their eCommerce or book keeping tool) and very few of them have extensive toolsets for keeping the previous order IDs in tact. If the legal ramifications were too great, store owners would never change systems, but we know that's not true because we see it all the time.

I propose that we:

  1. Not add an additional ID column
  2. Allow the sequential order numbers feature be the solution here for book keeping
  3. Continue to update all extensions and features (such as REST API) to use distinct parameters (order_id vs payment_id) for looking up records. The name of the parameter will tell us if we should look up by the new ID or the legacy ID.
JJJ commented 5 years ago

https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html

It appears that we can pass the old ID if we wanted to allow for that.

The Database API I wrote would need to be modified to support this, as right now it slices the ID column off on insert.

The safest thing to do would be to check if that ID exists before inserting, and fail the insert if it does. (Update uses that ID so it’s required there.) This brings some parity to update & insert, as they’ll both now be looking for an existing entry, just for different reasons.

Retaining the ID from table to table seems like it would absolve a lot of concerns.

@alexstandiford worked on a related PR for Affiliate WP that included auto_incrementor manipulators, and I think that would be a valuable addition to the Table class in our 3.0 database API. (That repo is private, so I won’t link to that here.)

I’ll put up a PR asap and reference this issue, so we can see how viable this approach is.

pippinsplugins commented 5 years ago

I had no idea auto increment could be done that way! That's awesome.

If that works the way described, that could potentially mean that all extensions will continue to work as is without changing any payment IDs. How freakin' cool.

JJJ commented 5 years ago

If that works the way described

Confirmed that it works. 🎉

In my InnoDB setup:

Both of the above scenarios match my expectations and the previously linked-to MySQL documentation. Heckin' cool.


A few things:

PR with the database class alterations imminent. I think the migration alterations might need some tag-teaming and deeper review when @cklosowski is back around. <3

JJJ commented 5 years ago

Addendum: it's also (thankfully) impossible to set the auto-increment value to anything lower than the highest ID in the relative column.

This means when we introduce a method to set it, it will be impossible for them to screw it up; MySQL internally prevents the table contents and the auto-increment value from being bass-ackwards.

For example:

Auto-increment will be 1006, without throwing any errors or notices. Pretty sweet.

mintplugins commented 5 years ago

Amazing

JJJ commented 5 years ago

Pull request #7216 is ready for a first-pass review.

I've added @cklosowski and @mintplugins as potential reviewers. 🎈

cklosowski commented 5 years ago

Thank you all for investigating this while I was out. I'll get a review on the PR.

This also means, as @mintplugins has already done, that the issues set to migrate these IDs in extensions are unnecessary...and added bonus we don't have to add a meta value.

team-chears

mintplugins commented 5 years ago

After adding in https://github.com/easydigitaldownloads/easy-digital-downloads/commit/cf03cc215bc3cec75f634bd4bd4545fa8716f9ea I was able to migrate successfully, and all order IDs remain the same. 🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉.

I'm going to do some searching around and see if I find any issues. But I think this concept is proven.

cklosowski commented 5 years ago

I just thought about something that might derail this whole thing...

Stores cannot be open to taking new orders while this migration happens with this method unless we can make sure any new orders that are inserted during the migration will have an ID greater than the highest Post ID in wp_posts that is an edd_payment

The scenario would be: 1) Start migration 2) Post ID 123 is migrated to order_id 123 (for this example, assume this is the first edd_payment post type in the wp_posts table. 3) Auto increment is bumped to 124 when this happens. 4) Post ID 128 is migrated to order_id 128 5) Auto increment is bumped to 129 6) A new order comes in from the live site and is now order_id 129 7) The next Post ID is migrated and is 129 8) :boom:

I believe the only way around this would to set the auto_increment upon table creation...to whatever the highest post ID value of edd_payments is + 1, and then starting the migration. The table will allow us to inset items with an ID lower than the auto_increment will it not? @JJJ @mintplugins

JJJ commented 5 years ago

I like the idea of setting the auto_increments as step 1 of the upgrade process.

It is a pretty graceful way of keeping the store open to new orders while the upgrader is chugging through the old ones.

We should do this for discounts also.

If we want to put more shine on this, continuing to do all of this under the guise of better reporting, we should “disable” a bunch of admin screens while the upgrade is happening so busy/impatient store owners don’t open a second browser tab and manually do anything that would unexpectedly write to the database.

We’d want to avoid them from changing anything, while still allowing them to navigate around the store and see the progress being made. People are going to want to make darn sure it’s working, and opening another tab and clicking around is the easiest way to do that.

JJJ commented 5 years ago

The database API does tie into roles and caps, and we could filter the capabilities arrays of the various tables while the upgrade is taking place, allowing reads and disallowing, updates, inserts, and deletes.

And we’d need to make sure the upgrade itself remains unfiltered.

cklosowski commented 5 years ago

So can we set the initial auto_increment and insert lower numbers after that without issue?

JJJ commented 5 years ago

Yup! We totally can. No issues.

New orders (that do not pass an ID in) will get new auto-increments values.

Old orders (that do pass an ID in) will use the ID that gets passed in.

The auto-increment is only used and bumped when no ID is passed in.

Your suggestion is a great way to “reserve” the space necessary for the migrated data to fit inside of. 👍

mintplugins commented 5 years ago

Sounds like the right approach to reserve space for all of the "legacy" posts. Good thought @cklosowski

cklosowski commented 5 years ago

Ok I've been looking through how we should do this today quite a bit. I think the most ideal situation is going to be to add a call to a method in the EDD\Database\Table class called something like post_creation_steps/actions.

Then we can use the create method as normal, and if the table is successfully created, run the post_creation_steps/actions method (which can be a stub of nothing in EDD\Database\Table class, but in specific table classes that extend it like orders and discounts we can create a method that handles this auto_increment step.

Does that seem like the best logic? I originally was thinking actions, but I don't want actions this deep into things...I like the idea of it being part of the class inheritance.

cklosowski commented 5 years ago

After some discussion with @jjj in Slack, we're going to avoid the approach above and just use the inheritance to have a create method that calls parent::create in the child class (like orders) that will do anything extra needed, like setting auto_increment after creation.

:+1:

cklosowski commented 5 years ago

@jjj @mintplugins ready for a look over.

cklosowski commented 5 years ago

Just updated this from release/3.0 to include #7236

cklosowski commented 5 years ago

I've pushed up some changes to do this same reservation for discounts as they are migrated to the adjustments table.