Skip to content

Commit

Permalink
improvement(core): use typeorm migrations instead of auto-synchronize
Browse files Browse the repository at this point in the history
This fixes concurrency issues on init (lock errors) and avoids any
potential data loss during schema synchronization.
  • Loading branch information
edvald committed Sep 9, 2020
1 parent 90ab9d0 commit b771949
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 23 deletions.
14 changes: 14 additions & 0 deletions core/ormconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { homedir } = require("os")
const { resolve } = require("path")

module.exports = {
type: "better-sqlite3",
database: resolve(homedir(), ".garden", "db"),
entities: [
resolve(__dirname, "build", "src", "db", "entities", "*.js"),
],
cli: {
entitiesDir: "build/src/db/entities",
migrationsDir: "src/db/migrations",
}
}
2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,12 @@
},
"scripts": {
"build": "tsc -p . && gulp pegjs",
"create-migration": "typeorm migration:create --config ../static/ormconfig.json -n",
"check-package-lock": "git diff-index --quiet HEAD -- yarn.lock || (echo 'yarn.lock is dirty!' && exit 1)",
"clean": "shx rm -rf build",
"dev": "yarn run watch",
"fix-format": "prettier --write \"{src,test}/**/*.ts\"",
"lint": "tslint -p .",
"migration:generate": "typeorm migration:generate --config ormconfig.js -n",
"integ": "mocha --opts test/mocha.integ.opts",
"integ-local": "GARDEN_INTEG_TEST_MODE=local GARDEN_SKIP_TESTS=remote-only mocha --opts test/mocha.integ.opts",
"integ-remote": "GARDEN_INTEG_TEST_MODE=remote GARDEN_SKIP_TESTS=local-only mocha --opts test/mocha.integ.opts",
Expand Down
6 changes: 3 additions & 3 deletions core/src/db/base-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import {
PrimaryGeneratedColumn,
} from "typeorm-with-better-sqlite3"

import { getConnection } from "./connection"

export class GardenEntity extends BaseEntity {
// Add these auto-populated columns on every entity
@PrimaryGeneratedColumn()
Expand All @@ -38,14 +36,16 @@ export class GardenEntity extends BaseEntity {
* Overriding this to make sure our connection parameters are correctly set.
*/
static getRepository<T extends BaseEntity>(this: ObjectType<T>): Repository<T> {
const { getConnection } = require("./connection")
const connection = getConnection()
return connection.getRepository<T>(this)
return connection.getRepository(this)
}

/**
* Helper method to avoid circular import issues.
*/
static getConnection() {
const { getConnection } = require("./connection")
return getConnection()
}
}
5 changes: 2 additions & 3 deletions core/src/db/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function getConnection(): Connection {
const { ClientAuthToken } = require("./entities/client-auth-token")
const { GardenProcess } = require("./entities/garden-process")
const { Warning } = require("./entities/warning")
const { Init1599658427984 } = require("./migrations/1599658427984-Init")

// Prepare the connection (the ormconfig.json in the static dir is only used for the typeorm CLI during dev)
const options: ConnectionOptions = {
Expand All @@ -29,9 +30,7 @@ export function getConnection(): Connection {
// IMPORTANT: All entities and migrations need to be manually referenced here because of how we
// package the garden binary
entities: [LocalAddress, ClientAuthToken, GardenProcess, Warning],
migrations: [],
// Auto-create new tables on init
synchronize: true,
migrations: [Init1599658427984],
// Auto-run migrations on init
migrationsRun: true,
}
Expand Down
45 changes: 45 additions & 0 deletions core/src/db/migrations/1599658427984-Init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2018-2020 Garden Technologies, Inc. <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { MigrationInterface, QueryRunner } from "typeorm-with-better-sqlite3"

export class Init1599658427984 implements MigrationInterface {
name = "Init1599658427984"

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "client_auth_token" ("_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "_createdAt" datetime NOT NULL DEFAULT (datetime('now')), "_updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "_version" integer NOT NULL, "token" varchar NOT NULL)`
)
await queryRunner.query(
`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_3f2902720f10884e413933e582" ON "client_auth_token" ("token") `
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "garden_process" ("_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "_createdAt" datetime NOT NULL DEFAULT (datetime('now')), "_updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "_version" integer NOT NULL, "pid" integer NOT NULL, "startedAt" datetime NOT NULL, "arguments" varchar NOT NULL, "sessionId" varchar, "projectRoot" varchar, "projectName" varchar, "environmentName" varchar, "namespace" varchar, "persistent" boolean NOT NULL DEFAULT (0), "serverHost" varchar, "serverAuthKey" varchar, "command" varchar)`
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "local_address" ("_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "_createdAt" datetime NOT NULL DEFAULT (datetime('now')), "_updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "_version" integer NOT NULL, "projectName" varchar NOT NULL, "moduleName" varchar NOT NULL, "serviceName" varchar NOT NULL, "hostname" varchar NOT NULL)`
)
await queryRunner.query(
`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_ccb1b3de9e2a1bd39c4619d516" ON "local_address" ("projectName", "moduleName", "serviceName", "hostname") `
)
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "warning" ("_id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "_createdAt" datetime NOT NULL DEFAULT (datetime('now')), "_updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "_version" integer NOT NULL, "key" varchar NOT NULL, "hidden" boolean NOT NULL)`
)
await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_e32e342758d4c83273d405698e" ON "warning" ("key") `)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_e32e342758d4c83273d405698e"`)
await queryRunner.query(`DROP TABLE "warning"`)
await queryRunner.query(`DROP INDEX "IDX_ccb1b3de9e2a1bd39c4619d516"`)
await queryRunner.query(`DROP TABLE "local_address"`)
await queryRunner.query(`DROP TABLE "garden_process"`)
await queryRunner.query(`DROP INDEX "IDX_3f2902720f10884e413933e582"`)
await queryRunner.query(`DROP TABLE "client_auth_token"`)
}
}
6 changes: 2 additions & 4 deletions core/src/db/migrations/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# Migrations

A migration needs to be created every time an entity is updated (columns added or modified). Do this by running the `create-migration` script with the name of the entity in question as a parameter:
A migration needs to be created every time an entity is added or updated (columns added or modified). Do this by running the `migrations:generate` script with the name of the entity in question as a parameter, after you've made your changes to the entity (or entities) in question:

```console
yarn run create-migration -- SomeEntity
yarn migration:generate SomeEntity
```

You then need to explicitly import the migration and reference in the `migrations` array in `src/db/connection.ts`.

This is not needed for _new_ entities, only after modifying an entity after it has been previously released and used.
12 changes: 0 additions & 12 deletions static/ormconfig.json

This file was deleted.

0 comments on commit b771949

Please sign in to comment.