{"action":"closed","number":5120,"pull_request":{"url":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/5120","id":231849092,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMxODQ5MDky","html_url":"https://github.com/ankidroid/Anki-Android/pull/5120","diff_url":"https://github.com/ankidroid/Anki-Android/pull/5120.diff","patch_url":"https://github.com/ankidroid/Anki-Android/pull/5120.patch","issue_url":"https://api.github.com/repos/ankidroid/Anki-Android/issues/5120","number":5120,"state":"closed","locked":false,"title":"database versioning / migration support","user":{"login":"mikehardy","id":782704,"node_id":"MDQ6VXNlcjc4MjcwNA==","avatar_url":"https://avatars1.githubusercontent.com/u/782704?v=4","gravatar_id":"","url":"https://api.github.com/users/mikehardy","html_url":"https://github.com/mikehardy","followers_url":"https://api.github.com/users/mikehardy/followers","following_url":"https://api.github.com/users/mikehardy/following{/other_user}","gists_url":"https://api.github.com/users/mikehardy/gists{/gist_id}","starred_url":"https://api.github.com/users/mikehardy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mikehardy/subscriptions","organizations_url":"https://api.github.com/users/mikehardy/orgs","repos_url":"https://api.github.com/users/mikehardy/repos","events_url":"https://api.github.com/users/mikehardy/events{/privacy}","received_events_url":"https://api.github.com/users/mikehardy/received_events","type":"User","site_admin":false},"body":"## Pull Request template\r\n\r\n## Purpose / Description\r\nAnkiDroid relies on app preferences for migration state, but app preferences could be restored by google backup/restore prior to app startup, meaning we can't rely on preferences to indicate if a database has been updated or not - the database itself has to store this state.\r\n\r\nThis patch implements versioning stored in the database, and can handle upgrades based on changes between the app version in the database and the current app version.\r\n\r\n## Fixes\r\n#5106 - fresh install treated as an upgrade\r\n\r\n## Approach\r\nThe upgrade logic is currently in DeckPicker and Storage, and handles preference data, database SQL data and database JSON data - it has a lot to handle potentially but is scattered about. Per #5106 a move to a standard migration tool (like Flyway) is possible, but isn't wanted yet.\r\n\r\nThose tools all have the general idea that versioning info is stored in the database, and before anyone uses the database, the versioning tool discovers and applies migrations with callbacks, then you get a connection.\r\n\r\nThis patch doesn't integrate a tool but does move to that style - it make sure first database access happens in a controlled location, it uses the underlying Android SQL Framework APIs to trigger migrations first thing, and uses callbacks so we can communicate API status.\r\n\r\n## How Has This Been Tested?\r\n\r\nI extended the new DBTest to show that database migrations do work correctly and are very easy to use, then the rest of the test was via an emulator with a collection from prior to this change. It correctly ran the integrity check on startup, then did not run it the second time\r\n\r\n## Learning (optional, can help others)\r\n- I looked at how flywaydb.org organizes things - with migrations and callbacks\r\n- I noticed how the app needs UI ability during migrations, with DeckPicker doing it now, but migrations are done outside of Activities, meaning callbacks really would be necessary\r\n- I examined adding our own version tables but using the framework APIs seemed much easier since they do all the work for us (which rely on a special 'user_version' byte in the database header, accessible with a `PRAGMA user_version;`\r\n\r\n\r\n\r\n## Checklist\r\n_Please, go through these checks before submitting the PR._\r\n\r\n- [x] You have not changed whitespace unnecessarily (it makes diffs hard to read)\r\n- [x] You have a descriptive commit message with a short title (first line, max 50 chars).\r\n- [x] Your code follows the style of the project (e.g. never omit braces in `if` statements) \r\n- [x] You have commented your code, particularly in hard-to-understand areas\r\n- [x] You have performed a self-review of your own code\r\n","created_at":"2018-11-19T04:17:34Z","updated_at":"2018-12-05T01:47:06Z","closed_at":"2018-12-05T01:47:06Z","merged_at":null,"merge_commit_sha":"ff0c756a8c829a042e2e70a6f8611372e7e9a37d","assignee":null,"assignees":[],"requested_reviewers":[{"login":"timrae","id":2818274,"node_id":"MDQ6VXNlcjI4MTgyNzQ=","avatar_url":"https://avatars1.githubusercontent.com/u/2818274?v=4","gravatar_id":"","url":"https://api.github.com/users/timrae","html_url":"https://github.com/timrae","followers_url":"https://api.github.com/users/timrae/followers","following_url":"https://api.github.com/users/timrae/following{/other_user}","gists_url":"https://api.github.com/users/timrae/gists{/gist_id}","starred_url":"https://api.github.com/users/timrae/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timrae/subscriptions","organizations_url":"https://api.github.com/users/timrae/orgs","repos_url":"https://api.github.com/users/timrae/repos","events_url":"https://api.github.com/users/timrae/events{/privacy}","received_events_url":"https://api.github.com/users/timrae/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":{"url":"https://api.github.com/repos/ankidroid/Anki-Android/milestones/11","html_url":"https://github.com/ankidroid/Anki-Android/milestone/11","labels_url":"https://api.github.com/repos/ankidroid/Anki-Android/milestones/11/labels","id":3653591,"node_id":"MDk6TWlsZXN0b25lMzY1MzU5MQ==","number":11,"title":"v2.9 release","description":"","creator":{"login":"timrae","id":2818274,"node_id":"MDQ6VXNlcjI4MTgyNzQ=","avatar_url":"https://avatars1.githubusercontent.com/u/2818274?v=4","gravatar_id":"","url":"https://api.github.com/users/timrae","html_url":"https://github.com/timrae","followers_url":"https://api.github.com/users/timrae/followers","following_url":"https://api.github.com/users/timrae/following{/other_user}","gists_url":"https://api.github.com/users/timrae/gists{/gist_id}","starred_url":"https://api.github.com/users/timrae/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timrae/subscriptions","organizations_url":"https://api.github.com/users/timrae/orgs","repos_url":"https://api.github.com/users/timrae/repos","events_url":"https://api.github.com/users/timrae/events{/privacy}","received_events_url":"https://api.github.com/users/timrae/received_events","type":"User","site_admin":false},"open_issues":9,"closed_issues":31,"state":"open","created_at":"2018-09-14T02:44:57Z","updated_at":"2018-12-05T01:47:06Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/5120/commits","review_comments_url":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/5120/comments","review_comment_url":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ankidroid/Anki-Android/issues/5120/comments","statuses_url":"https://api.github.com/repos/ankidroid/Anki-Android/statuses/9aca26dbc13244c3b7a501302807d5a5c349722c","head":{"label":"mikehardy:issue5106/database-migrations","ref":"issue5106/database-migrations","sha":"9aca26dbc13244c3b7a501302807d5a5c349722c","user":{"login":"mikehardy","id":782704,"node_id":"MDQ6VXNlcjc4MjcwNA==","avatar_url":"https://avatars1.githubusercontent.com/u/782704?v=4","gravatar_id":"","url":"https://api.github.com/users/mikehardy","html_url":"https://github.com/mikehardy","followers_url":"https://api.github.com/users/mikehardy/followers","following_url":"https://api.github.com/users/mikehardy/following{/other_user}","gists_url":"https://api.github.com/users/mikehardy/gists{/gist_id}","starred_url":"https://api.github.com/users/mikehardy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mikehardy/subscriptions","organizations_url":"https://api.github.com/users/mikehardy/orgs","repos_url":"https://api.github.com/users/mikehardy/repos","events_url":"https://api.github.com/users/mikehardy/events{/privacy}","received_events_url":"https://api.github.com/users/mikehardy/received_events","type":"User","site_admin":false},"repo":{"id":113939187,"node_id":"MDEwOlJlcG9zaXRvcnkxMTM5MzkxODc=","name":"Anki-Android","full_name":"mikehardy/Anki-Android","private":false,"owner":{"login":"mikehardy","id":782704,"node_id":"MDQ6VXNlcjc4MjcwNA==","avatar_url":"https://avatars1.githubusercontent.com/u/782704?v=4","gravatar_id":"","url":"https://api.github.com/users/mikehardy","html_url":"https://github.com/mikehardy","followers_url":"https://api.github.com/users/mikehardy/followers","following_url":"https://api.github.com/users/mikehardy/following{/other_user}","gists_url":"https://api.github.com/users/mikehardy/gists{/gist_id}","starred_url":"https://api.github.com/users/mikehardy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mikehardy/subscriptions","organizations_url":"https://api.github.com/users/mikehardy/orgs","repos_url":"https://api.github.com/users/mikehardy/repos","events_url":"https://api.github.com/users/mikehardy/events{/privacy}","received_events_url":"https://api.github.com/users/mikehardy/received_events","type":"User","site_admin":false},"html_url":"https://github.com/mikehardy/Anki-Android","description":"AnkiDroid: Anki on Android","fork":true,"url":"https://api.github.com/repos/mikehardy/Anki-Android","forks_url":"https://api.github.com/repos/mikehardy/Anki-Android/forks","keys_url":"https://api.github.com/repos/mikehardy/Anki-Android/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mikehardy/Anki-Android/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mikehardy/Anki-Android/teams","hooks_url":"https://api.github.com/repos/mikehardy/Anki-Android/hooks","issue_events_url":"https://api.github.com/repos/mikehardy/Anki-Android/issues/events{/number}","events_url":"https://api.github.com/repos/mikehardy/Anki-Android/events","assignees_url":"https://api.github.com/repos/mikehardy/Anki-Android/assignees{/user}","branches_url":"https://api.github.com/repos/mikehardy/Anki-Android/branches{/branch}","tags_url":"https://api.github.com/repos/mikehardy/Anki-Android/tags","blobs_url":"https://api.github.com/repos/mikehardy/Anki-Android/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mikehardy/Anki-Android/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mikehardy/Anki-Android/git/refs{/sha}","trees_url":"https://api.github.com/repos/mikehardy/Anki-Android/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mikehardy/Anki-Android/statuses/{sha}","languages_url":"https://api.github.com/repos/mikehardy/Anki-Android/languages","stargazers_url":"https://api.github.com/repos/mikehardy/Anki-Android/stargazers","contributors_url":"https://api.github.com/repos/mikehardy/Anki-Android/contributors","subscribers_url":"https://api.github.com/repos/mikehardy/Anki-Android/subscribers","subscription_url":"https://api.github.com/repos/mikehardy/Anki-Android/subscription","commits_url":"https://api.github.com/repos/mikehardy/Anki-Android/commits{/sha}","git_commits_url":"https://api.github.com/repos/mikehardy/Anki-Android/git/commits{/sha}","comments_url":"https://api.github.com/repos/mikehardy/Anki-Android/comments{/number}","issue_comment_url":"https://api.github.com/repos/mikehardy/Anki-Android/issues/comments{/number}","contents_url":"https://api.github.com/repos/mikehardy/Anki-Android/contents/{+path}","compare_url":"https://api.github.com/repos/mikehardy/Anki-Android/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mikehardy/Anki-Android/merges","archive_url":"https://api.github.com/repos/mikehardy/Anki-Android/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mikehardy/Anki-Android/downloads","issues_url":"https://api.github.com/repos/mikehardy/Anki-Android/issues{/number}","pulls_url":"https://api.github.com/repos/mikehardy/Anki-Android/pulls{/number}","milestones_url":"https://api.github.com/repos/mikehardy/Anki-Android/milestones{/number}","notifications_url":"https://api.github.com/repos/mikehardy/Anki-Android/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mikehardy/Anki-Android/labels{/name}","releases_url":"https://api.github.com/repos/mikehardy/Anki-Android/releases{/id}","deployments_url":"https://api.github.com/repos/mikehardy/Anki-Android/deployments","created_at":"2017-12-12T03:35:26Z","updated_at":"2018-12-04T23:27:37Z","pushed_at":"2018-12-05T01:28:12Z","git_url":"git://github.com/mikehardy/Anki-Android.git","ssh_url":"git@github.com:mikehardy/Anki-Android.git","clone_url":"https://github.com/mikehardy/Anki-Android.git","svn_url":"https://github.com/mikehardy/Anki-Android","homepage":"","size":108920,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"ankidroid:master","ref":"master","sha":"5c7ef543d2ac9233ba6818e43b1d1a097f7332a8","user":{"login":"ankidroid","id":3320903,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMzMjA5MDM=","avatar_url":"https://avatars0.githubusercontent.com/u/3320903?v=4","gravatar_id":"","url":"https://api.github.com/users/ankidroid","html_url":"https://github.com/ankidroid","followers_url":"https://api.github.com/users/ankidroid/followers","following_url":"https://api.github.com/users/ankidroid/following{/other_user}","gists_url":"https://api.github.com/users/ankidroid/gists{/gist_id}","starred_url":"https://api.github.com/users/ankidroid/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ankidroid/subscriptions","organizations_url":"https://api.github.com/users/ankidroid/orgs","repos_url":"https://api.github.com/users/ankidroid/repos","events_url":"https://api.github.com/users/ankidroid/events{/privacy}","received_events_url":"https://api.github.com/users/ankidroid/received_events","type":"Organization","site_admin":false},"repo":{"id":7716883,"node_id":"MDEwOlJlcG9zaXRvcnk3NzE2ODgz","name":"Anki-Android","full_name":"ankidroid/Anki-Android","private":false,"owner":{"login":"ankidroid","id":3320903,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMzMjA5MDM=","avatar_url":"https://avatars0.githubusercontent.com/u/3320903?v=4","gravatar_id":"","url":"https://api.github.com/users/ankidroid","html_url":"https://github.com/ankidroid","followers_url":"https://api.github.com/users/ankidroid/followers","following_url":"https://api.github.com/users/ankidroid/following{/other_user}","gists_url":"https://api.github.com/users/ankidroid/gists{/gist_id}","starred_url":"https://api.github.com/users/ankidroid/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ankidroid/subscriptions","organizations_url":"https://api.github.com/users/ankidroid/orgs","repos_url":"https://api.github.com/users/ankidroid/repos","events_url":"https://api.github.com/users/ankidroid/events{/privacy}","received_events_url":"https://api.github.com/users/ankidroid/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/ankidroid/Anki-Android","description":"AnkiDroid: Anki on Android","fork":false,"url":"https://api.github.com/repos/ankidroid/Anki-Android","forks_url":"https://api.github.com/repos/ankidroid/Anki-Android/forks","keys_url":"https://api.github.com/repos/ankidroid/Anki-Android/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ankidroid/Anki-Android/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ankidroid/Anki-Android/teams","hooks_url":"https://api.github.com/repos/ankidroid/Anki-Android/hooks","issue_events_url":"https://api.github.com/repos/ankidroid/Anki-Android/issues/events{/number}","events_url":"https://api.github.com/repos/ankidroid/Anki-Android/events","assignees_url":"https://api.github.com/repos/ankidroid/Anki-Android/assignees{/user}","branches_url":"https://api.github.com/repos/ankidroid/Anki-Android/branches{/branch}","tags_url":"https://api.github.com/repos/ankidroid/Anki-Android/tags","blobs_url":"https://api.github.com/repos/ankidroid/Anki-Android/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ankidroid/Anki-Android/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ankidroid/Anki-Android/git/refs{/sha}","trees_url":"https://api.github.com/repos/ankidroid/Anki-Android/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ankidroid/Anki-Android/statuses/{sha}","languages_url":"https://api.github.com/repos/ankidroid/Anki-Android/languages","stargazers_url":"https://api.github.com/repos/ankidroid/Anki-Android/stargazers","contributors_url":"https://api.github.com/repos/ankidroid/Anki-Android/contributors","subscribers_url":"https://api.github.com/repos/ankidroid/Anki-Android/subscribers","subscription_url":"https://api.github.com/repos/ankidroid/Anki-Android/subscription","commits_url":"https://api.github.com/repos/ankidroid/Anki-Android/commits{/sha}","git_commits_url":"https://api.github.com/repos/ankidroid/Anki-Android/git/commits{/sha}","comments_url":"https://api.github.com/repos/ankidroid/Anki-Android/comments{/number}","issue_comment_url":"https://api.github.com/repos/ankidroid/Anki-Android/issues/comments{/number}","contents_url":"https://api.github.com/repos/ankidroid/Anki-Android/contents/{+path}","compare_url":"https://api.github.com/repos/ankidroid/Anki-Android/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ankidroid/Anki-Android/merges","archive_url":"https://api.github.com/repos/ankidroid/Anki-Android/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ankidroid/Anki-Android/downloads","issues_url":"https://api.github.com/repos/ankidroid/Anki-Android/issues{/number}","pulls_url":"https://api.github.com/repos/ankidroid/Anki-Android/pulls{/number}","milestones_url":"https://api.github.com/repos/ankidroid/Anki-Android/milestones{/number}","notifications_url":"https://api.github.com/repos/ankidroid/Anki-Android/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ankidroid/Anki-Android/labels{/name}","releases_url":"https://api.github.com/repos/ankidroid/Anki-Android/releases{/id}","deployments_url":"https://api.github.com/repos/ankidroid/Anki-Android/deployments","created_at":"2013-01-20T13:55:53Z","updated_at":"2018-12-05T01:28:04Z","pushed_at":"2018-12-05T01:28:01Z","git_url":"git://github.com/ankidroid/Anki-Android.git","ssh_url":"git@github.com:ankidroid/Anki-Android.git","clone_url":"https://github.com/ankidroid/Anki-Android.git","svn_url":"https://github.com/ankidroid/Anki-Android","homepage":"","size":108812,"stargazers_count":1578,"watchers_count":1578,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":false,"has_wiki":true,"has_pages":false,"forks_count":690,"mirror_url":null,"archived":false,"open_issues_count":342,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":690,"open_issues":342,"watchers":1578,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/5120"},"html":{"href":"https://github.com/ankidroid/Anki-Android/pull/5120"},"issue":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/issues/5120"},"comments":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/issues/5120/comments"},"review_comments":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/5120/comments"},"review_comment":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/pulls/5120/commits"},"statuses":{"href":"https://api.github.com/repos/ankidroid/Anki-Android/statuses/9aca26dbc13244c3b7a501302807d5a5c349722c"}},"author_association":"MEMBER","merged":false,"mergeable":true,"rebaseable":true,"mergeable_state":"clean","merged_by":null,"comments":12,"review_comments":2,"maintainer_can_modify":false,"commits":1,"additions":119,"deletions":16,"changed_files":5}}
{"actor":{"display_login":"mikehardy"}}