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

Migrate from Mariadb to Postgres #400

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions migrate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -o allexport
source ./migrate.env
set +o allexport
shift
node scripts/migrateMariadbToPostgresql.mjs
1,226 changes: 1,184 additions & 42 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
"jsonschema": "^1.4.1",
"mysql": "^2.18.1",
"node-cache": "^5.1.2",
"pg": "^8.8.0",
"pg-query-stream": "^4.2.4",
"pgsql-ast-parser": "^11.0.0",
"promise-mysql": "^5.2.0",
"slonik": "^31.2.1",
"slonik-interceptor-field-name-transformation": "^1.6.5",
"tslib": "^2.4.0",
"uuid": "^9.0.0",
"validate.js": "^0.13.1",
Expand All @@ -33,6 +38,7 @@
"@types/mysql": "^2.15.21",
"@types/node": "^18.7.16",
"@types/node-cache": "^4.2.5",
"@types/pg": "8.6.5",
"@types/triple-beam": "^1.3.2",
"@types/uuid": "^8.3.4",
"typescript": "^4.8.3"
Expand Down
85 changes: 40 additions & 45 deletions packages/core/src/database/contexts/appEventContext.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { AppEvent, AppEventFilter } from "../../types";
import { SubContext } from "./subContext";
import { sql } from "slonik";
import { AppEventFilter } from "../../types";
import { appEvent, AppEvent } from "../databaseTypes";
import { joinAnd, joinIdentifier } from "./helper";
import { QueryContext } from "./queryContext";

export class AppEventContext extends SubContext {
public async addAppEvent(event: AppEvent): Promise<AppEvent> {
const newEvent = await this.query(
"INSERT INTO app_events (`program`, `date`, `type`) VALUES (?,?,?) RETURNING `id`, `program`, `date`, `type`",
[event.program, event.date, event.type],
export class AppEventContext extends QueryContext {
public async addAppEvent(event: Readonly<AppEvent>): Promise<AppEvent> {
return this.con.one(
sql.type(appEvent)`
INSERT INTO app_events (program, date, type)
VALUES (${event.program},${sql.timestamp(event.date)},${event.type})
RETURNING id, program, date, type`,
);
return newEvent[0];
}

public async updateAppEvent(event: AppEvent): Promise<void> {
public async updateAppEvent(event: Readonly<AppEvent>): Promise<void> {
await this.update(
"app_events",
(updates, values) => {
updates.push("`date` = ?");
values.push(event.date);
() => {
return [sql`date = ${sql.timestamp(event.date)}`];
},
{
column: "id",
Expand All @@ -24,48 +27,40 @@ export class AppEventContext extends SubContext {
);
}

public async getAppEvents(filter: AppEventFilter = {}): Promise<AppEvent[]> {
let sort = "";

if (Array.isArray(filter.sortOrder)) {
sort = filter.sortOrder.join(",");
} else if (filter.sortOrder) {
sort = filter.sortOrder;
public async getAppEvents(filter: AppEventFilter = {}): Promise<readonly AppEvent[]> {
if (!Array.isArray(filter.sortOrder) && filter.sortOrder) {
filter.sortOrder = [filter.sortOrder];
}
const where = [];
const values = [];

if (Array.isArray(filter.program)) {
where.push("type IN (" + filter.program.map(() => "?").join(",") + ")");
values.push(...filter.program);
} else if (filter.program) {
where.push("program = ?");
values.push(filter.program);
if (!Array.isArray(filter.program) && filter.program) {
filter.program = [filter.program];
}

if (Array.isArray(filter.type)) {
where.push("type IN (" + filter.type.map(() => "?").join(",") + ")");
values.push(...filter.type);
} else if (filter.type) {
where.push("type = ?");
values.push(filter.type);
if (!Array.isArray(filter.type) && filter.type) {
filter.type = [filter.type];
}

const whereFilter = [];

if (filter.fromDate) {
where.push("date >= ?");
values.push(filter.fromDate);
whereFilter.push(sql`date >= ${sql.timestamp(filter.fromDate)}`);
}

if (filter.toDate) {
where.push("date <= ?");
values.push(filter.toDate);
whereFilter.push(sql`date >= ${sql.timestamp(filter.toDate)}`);
}
if (Array.isArray(filter.program)) {
const array = sql.array(filter.program, "text");
whereFilter.push(sql`program = ANY(${array})`);
}
if (Array.isArray(filter.type)) {
const array = sql.array(filter.type, "text");
whereFilter.push(sql`program = ANY(${array})`);
}

const whereClause = whereFilter.length ? sql` WHERE ${joinAnd(whereFilter)}` : sql``;
const orderClause = filter.sortOrder?.length ? sql` ORDER BY ${joinIdentifier(filter.sortOrder)}` : sql``;

return this.query(
`SELECT id, program, date, type FROM app_events${where.length ? " WHERE " + where.join(" AND ") : ""}${
sort ? " ORDER BY " + sort : ""
};`,
values,
return this.con.any(
sql.type(appEvent)`
SELECT id, program, date, type FROM app_events${whereClause}${orderClause};`,
);
}
}
68 changes: 31 additions & 37 deletions packages/core/src/database/contexts/customHookContext.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,55 @@
import { SubContext } from "./subContext";
import { isInvalidId } from "../../tools";
import { storeModifications } from "../sqlTools";
import { CustomHook } from "@/types";
import { ValidationError } from "../../error";
import { QueryContext } from "./queryContext";
import { customHook, entity, CustomHook } from "../databaseTypes";
import { sql } from "slonik";
import { isString } from "validate.js";

export class CustomHookContext extends SubContext {
public async addHook(value: CustomHook): Promise<CustomHook> {
export class CustomHookContext extends QueryContext {
public async addHook(value: Readonly<CustomHook>): Promise<CustomHook> {
if (value.id) {
throw new ValidationError("Cannot add Hook with id already defined");
}
if (typeof value.state === "object") {
value.state = JSON.stringify(value.state);
}

let result = await this.query(
"INSERT IGNORE INTO custom_hook (name, state, hookState, comment) VALUES (?,?,?,?);",
[value.name, value.state, value.hookState, value.comment],
const state = isString(value.state) ? JSON.parse(value.state as string) : value.enabled;

const id = await this.con.oneFirst(
sql.type(entity)`
INSERT INTO custom_hook (name, state, enabled, comment)
VALUES (${value.name},${sql.jsonb(state)},${value.enabled},${value.comment})
ON CONFLICT DO NOTHING RETURNING id;`,
);
if (!Number.isInteger(result.insertId) || result.insertId === 0) {
throw new ValidationError(`invalid ID ${result.insertId + ""}`);
}
storeModifications("custom_hook", "insert", result);

result = { ...value, id: result.insertId };
return result;
// FIXME: storeModifications("custom_hook", "insert", result);

return { ...value, id };
}

public async getHooks(): Promise<CustomHook[]> {
return this.query("SELECT id, name, state, updated_at, hookState, comment FROM custom_hook;");
public async getHooks(): Promise<readonly CustomHook[]> {
return this.con.any(
sql.type(customHook)`
SELECT id, name, state, updated_at, enabled, comment FROM custom_hook;`,
);
}

public async updateHook(value: CustomHook): Promise<CustomHook> {
if (isInvalidId(value.id)) {
throw new ValidationError(`Invalid id: '${value.id}'`);
}
const updateResult = await this.update(
customHook.parse(value);
await this.update(
"custom_hook",
(updates, values) => {
updates.push("comment = ?");
values.push(value.comment);

updates.push("hookState = ?");
values.push(value.hookState);

updates.push("name = ?");
values.push(value.name);

updates.push("state = ?");
values.push(value.state);
() => {
return [
sql`comment = ${value.comment}`,
sql`enabled = ${value.enabled}`,
sql`name = ${value.name}`,
sql`state = ${sql.jsonb(value.state)}`,
];
},
{
column: "id",
value: value.id,
},
);

storeModifications("custom_hook", "update", updateResult);
// FIXME storeModifications("custom_hook", "update", updateResult);

return value;
}
Expand Down
Loading