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
Rollbacks by start time #901
Conversation
@rquadling this is simply the rebase from I still have to act on your #745 (comment) |
regarding your comment:
ok, but I'm not sure how to highlight the column
makes sense to me, shall I do it?
hmm so you're saying like, get the versions properly ordered by creation or execution time from
So no |
That sounds about perfect. No For highlighting, https://github.com/robmorgan/phinx/blob/0.6.x-dev/src/Phinx/Migration/Manager.php#L112 contains the column headings. A simple variant could be to wrap the name with Probably though the |
on it! quick question: for the "version order" references, I believe it's much cleaner to have the valid values (strings // bad
if ('start-time' === $versionOrder) Vs // good
if (VERSION_ORDER_START_TIME === $versionOrder) My question is, where shall I store these values? They are gonna be needed (I think) in the |
Another question: for passing the version order from the Maybe I can simply read it from the Config class? The only other thing I'd need to do is set the key to its default value (creation-time) in the |
I would put the constants in Config, as they are configuration constants. With an appropriate getter(). Which will also include the validation and throw an exception if the value is incorrect/unknown. If not set then supply the default which would preserve the current status quo of creation date order. So, then, in both status and rollback, something like |
perfect, looks good. What about the rollback command's
It looks like we don't need a |
there is a problem with:
which is that whereas it is impossible to have two versions with the same create time (right?), it might happen that two versions are executed in bulk at the same time and end up with the same start time, meaning we can't really index the versions/migrations by start time without ensuring we won't ignore entries with the same start time (ie. array key)... (sorry for all the questions, it's just nice to finally have someone in charge listening and advising hehe, so I might as well take advantage of that and ensure I do the best PR possible) |
I double-checked since I didn't remember seeing any checks for this in the code, but the PRIMARY key in the |
I could index the migrations (returned in This would allow for having several versions with the same start time, but it wouldn't be able to ensure any definitive version order for the same start time, which could lead to unexpected behaviour. Maybe I can sort the versions by start time and creation time (aka ID)? |
just updated the initial PR description (copied from the old PR) with the new stuff from this PR |
If they are ordered by With regard to the output for the status, try running your code with |
Is there no way to merge It just grates having 4 methods that all do the same work. In essence. I may need to take a closer look at the workings of these. |
And I'm happy to be helping here. A lot of the features people are working on I will be actively using anyway. |
on it.
the output is the same with
I will try to merge them now - the only problem I had was the ordering, now that you replied I'm good to try it. How about the |
As for 2016 -> 20160101000000 / Midnight Jan 1st 2016 Basically, allowing me to use a point in time between 2 runs of the migration. It would be nice to have a --dry-run option that would show the status page with the current up/down and the new up/down, taking into account any breakpoints. #906 |
Indeed, with As for the Let me just summarize how I think the rollback mechanism would work, to make sure we are on the same page: Premise: Depending on the configuration's version order, the existing versions (ie. the previously executed migrations) will be sorted by creation time (aka name) or start time (aka execution time). Behaviour with a
Behaviour with a
Behaviour with no
Is that it? (note that this description is ignoring breakpoints, the implementation of which is quite simple: if at any point a migration is about to be rolled back and it has a breakpoint set for it, the mechanism stops right there without rolling back that migration and prints the |
Maybe changing the As for In both cases, date and target operate on the sorted results. Depending upon the ordering, the status page would look like ...
or
Now lets got through the some "targets" for rollback.
For 'By version' supplying a target would rollback the following migrations.
For 'By execution' supplying a target would rollback the following migrations.
I see "target" and "date" being merged into a "time point", which has automatic expansion to a full datetime depending upon what parts are not present. And that datetime is then used against the ordering. As the ordering is set at a config level, I don't see a need to differentiate once the migrations to rollback have been determined. All migrations after the timepoint are rolled back, depending upon the sort order. |
easy peasy.
I'm with you, but doesn't that mean that
without distinguishing between a target version via |
It is just painful having -t 20160105121314 AND a -d "2016-01-05 12:13:14" If someone needs a "strict" point, they will enter one. But it is also about enforcing a strict behaviour. Fair enough. So, because of the 'strict' nature of MUST having a matched ...
If the timepoint is from If the timepoint is from How does that sound. And of course, that may be exactly what we do already! Ha ha! (ahem - I'm off home - sorry for wasting everyone's time!!!). |
That was precisely my point :-) I wasn't arguing for having the As for the strict nature, I wasn't trying to suggest that that was how it should work, I was saying it's what I thought was wanted.
I think it kinda is :-) I just got confused when you mentioned that both target versions and target dates were a time point, pure and simple. Anyway, me personally I think the So the way I see it we have 2 options:
What shall it be? |
I do feel that -d and -t are essentially the same. The one issue is the 'strictness' of -t. The requested target MUST exist. So. I think if we can support all the features with minimal code (i.e. not 4 methods that nearly all do the same thing, then I think that will be best. Keeping both -t and -d will be easier for end users as they already have that. I think the code should do the following steps. Get the migrations in the appropriate order. Keyed by version ID. If -d, expand to a full time point. Rollback migrations in order until migration timepoint (depending upon the version ordering). All handled by 1 method. |
@rquadling done :-) let me know what you think |
I see some failed tests in AppVeyor, although in Travis CI (and locally) everything passes. What specifically is AppVeyor testing, how dos it differ from Travis and how can I run that environment locally? |
AppVeyor runs Windows + MSSQL. I can re-run the tests locally and give some feedback. I run Windows and can test MySQL, Microsoft SQL Server, SQLite and PostgreSQL. Possibly one of the good points about Windows is that it can run MS servers AND most other SQL engines. Yay for windows! |
You've changed the tests and have embedded unix line endings in the regex which will fail on Windows.
The above is an example patch. |
indeed I have! will get rid of that |
💥 |
I'll add the new features to the docs now |
To be honest, I was getting very confused with names like But maybe it's just me, so for the sake of consistency, I'll rename the variables. In the meantime, I think I found a bug in the Status command, so hold off on the merge ;-) |
bugs fixed, ready for more scrutiny! |
4 => '0101000000', | ||
); | ||
|
||
$format = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused?
Rebase and squash as we are getting a lot of commits here. Everything is going in the right direction. |
Rebase is important as you are missing tests (and maybe code) related to breakpoints. |
|
||
if (!isset($versionOrder)) { | ||
// if the configuration value is missing we use the default version order (creation time) | ||
$this->values['version_order'] = self::VERSION_ORDER_CREATION_TIME; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh dear. I led you the wrong way. I forgot that the config class is, essentially, read only, no setters() needed. All guards are in the getters().
And looking at the rest of config, it doesn't really guard against much!
I think the getter can be as simple as:
return $this->values['version_order'] ?: self::VERSION_ORDER_CREATION_TIME;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I should remove the setter and replace the getter with that? Where should the guard go to? Having an invalid configuration version order (because of a typo, for instance) might lead to unexpected behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily. If you add the default:
clauses, the the system will use that for an incorrect value. I think there are a limited number of places where the value is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so you mean, wherever where I do a getVersionOrder
, like say in the Manager
class, I do a switch
clause with cases for creation and start time and default to raising an exception for an unknown order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or it actually sounds like you're suggesting something like:
switch($this->getConfig()->getVersionOrder()) {
case \Phinx\Config\Config::VERSION_ORDER_START_TIME:
// handle start time version order
break;
case \Phinx\Config\Config::VERSION_ORDER_CREATION_TIME:
default:
// handle creation time version order
}
The problem I see here is that an invalid version order (eg. a typo like starttime
) would be invisibly handled as a creation-time, which could lead to unexpected behaviour (since phinx would rollback the "invalid" commit, at least in the eyes of the user). I believe we should guard against this, if not on the Config getter/setter, then at least in the switch's default
clause, by raising an exception. If we do so, the only thing left to do is to return the default version order (creation-time) in the getter if none was set in the configuration file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use default to be the default. So the current behaviour is in creation time order, so that's the default behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use default to be the default. So the current behaviour is in creation time order, so that's the default behaviour.
even if the user writes something else in the config file? that seems to go beyond the scope of "default"
rebased and squashed |
{ | ||
if (!preg_match('/^\d{4,14}$/', $date)) { | ||
throw new \InvalidArgumentException('Invalid date. Format is YYYYmmddHHiiss (with no mandatory '. | ||
'part, but the missing part(s) must all be in the end of the date string)'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rquadling maybe could I make this message more nice and concise like your other comment:
Invalid date. Format is Y[m[d[H[i[s]]]]].
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format is an acceptable way of describing a parameter. It is not uncommon to see nested optional parts. Unusual to see many as this, probably, not not unusual enough to need anything more complex.
I'd recommend Invalid value. Format is CCYY[MM[DD[HH[II[SS]]]]]
. I'd also like to have the --help page show a few examples as well as mention it is not JUST date.
It is a bit confusing when -t
is target (or timestamp) and -d
is date (or datetime but as a timestamp).
All a bit samey.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all good, but, why CCYY
and not YYYY
? What does the C
stand for? Century? Afaik, date formats simply refers to Y
for years
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CCYY or YYYY, but never YY or Y.
so, I think currently we only have these open points:
|
I prefer the shorter form. The guard for the current config options is based upon the consumer, not in the config class. By having it in the config class, potentially we are restricting subclassing. Maybe. For the guard, the default value is assigned during config and then using a switch, the default case could either be the same as the default value or an exception could be thrown. Thinking ... Exception. Basically, if someone has gone to the trouble of editing the config and done it wrong, BOOM! If no config changes, then everything operates exactly as it is at the moment. |
done! :-) anything else missing?? |
That's all looking very good!!! I'll be doing a thorough review tomorrow (and testing too. Hopefully unit tests have increased coverage. One thing I've found in my own code, especially where there are many options all working together, generating the test data for unit testing can be as troublesome as the code itself. So (doing this blind without looking at the tests), the following options all play a part in rollback.
So, just taking into account those options, potentially, 432 combinations. Some won't be valid (0 migrations for example), but the code still needs to respond appropriately. Having a rollbackProvider that can present all these tests with their expected results or exceptions, and, ideally, a message indicative of the test data (Version order undefined, migrations in order, no breakpoint settings, target after all migrations with 0 migrations failed). It is the large combinations of options that cover all bases that reveal those broken edge cases. If need be, write a script (and commit it) that generates the tests. That way, if the fundamentals change, we can change the generator and generate the tests again. |
HI. Can you rebase to 0.7.x-dev please. This will be the new breaking changes branch (new interface methods or signature changes). |
The old testRollbacksByDate test was renamed to testRollbackToDateTime and turned into a unit test, ensuring the rollback method is called with the right version. A new testRollback was added which ensures the correct output of the rollback operation (as was being done in the old testRollbacksByDate test). More test cases were added.
* A new version_order configuration option was added. * A new Config.getVersionOrder() and Config.isVersionOrderCreationTime() were added. * The getVersionLog() method now returns the versions indexed by version creation time but already in the proper order, according to the configuration version order. * The Manage's "rollback" and "rollbackToDateTime" methods were merged into one (simply named "rollback"). * The new Manager.rollback method was adapted so that it serves any combination of start/creation time ordering and target version/date. * The Rollback command now "fills" in missing parts of target dates. * Tests improved: now using test input/output from setUp. * Tests improved: the expectations for the underlying Manager calls now include the expected arguments.
* It now highlights the column the migrations are being ordered by. * Down migrations are now showing up in the right place.
Code that uses the version order value now also has a default clause to raise an exception in case of invalid values.
replaced by #926 |
A new Configuration option named
version_order
was added. Its possible values are:creation-time
(the default): causes the Rollback and Status commands to order the executed migrations by their creation times (aka Migration IDs or Names).start-time
: causes the Rollback and Status commands to order the executed migrations by their start times (aka Execution Times).This means that when invoking the Rollback command:
start-time
version order, the last executed migration will be rollbacked.start-time
version order and a-d (date)
option, the migration start times will be used to determine the target version to rollback to. In other words, all migrations which were executed after that date will be rollbacked.start-time
version order and a-t (target version)
option, all migrations which were executed after the target version was executed will be rollbacked.In terms of testing:
testRollbacksByDate
test was renamed totestRollbackToDateTime
and turned into a unit test, ensuring therollback
method is called with the right target version. A newtestRollback
was added which ensures the correct output of the rollback operation (as was being done in the oldtestRollbacksByDate
test).RollbackTest
andStatusTest
.Other notes:
rollback
androllbackToDateTime
methods were merged into a singlerollback
method that handles all cases and version orders.