Merge branch 'v1.3-dev'
[websub-hub] / src / db / schema-version-helper.js
1 'use strict';
2
3 const fs = require('fs');
4 const path = require('path');
5
6 /**
7 * Utility functions for wrangling schema migrations.
8 */
9
10 /**
11 * @typedef {object} SchemaVersionObject
12 * @property {number} major semver major
13 * @property {number} minor semver minor
14 * @property {number} patch semver patch
15 */
16
17
18 /**
19 * Split a dotted version string into parts.
20 * @param {string} v version
21 * @returns {SchemaVersionObject} version
22 */
23 function schemaVersionStringToObject(v) {
24 const [ major, minor, patch ] = v.split('.', 3).map((x) => parseInt(x, 10));
25 return { major, minor, patch };
26 }
27
28
29 /**
30 * Render a version object numerically.
31 * @param {SchemaVersionObject} v version
32 * @returns {number} version number
33 */
34 function schemaVersionObjectToNumber(v) {
35 const vScale = 1000;
36 return parseInt(v.major) * vScale * vScale + parseInt(v.minor) * vScale + parseInt(v.patch);
37 }
38
39
40 /**
41 * Convert dotted version string into number.
42 * @param {string} v version
43 * @returns {number} version number
44 */
45 function schemaVersionStringToNumber(v) {
46 return schemaVersionObjectToNumber(schemaVersionStringToObject(v));
47 }
48
49
50 /**
51 * Version string comparison, for sorting.
52 * @param {string} a version
53 * @param {string} b version
54 * @returns {number} difference
55 */
56 function schemaVersionStringCmp(a, b) {
57 return schemaVersionStringToNumber(a) - schemaVersionStringToNumber(b);
58 }
59
60
61 /**
62 * Check if an entry in a directory is a directory containing a migration file.
63 * @param {string} schemaDir path to schema version directories
64 * @param {string} name schema version
65 * @returns {boolean} is valid schema version
66 */
67 function isSchemaMigrationDirectory(schemaDir, name) {
68 // eslint-disable-next-line security/detect-non-literal-fs-filename
69 const nameStat = fs.statSync(path.join(schemaDir, name));
70 if (nameStat.isDirectory()) {
71 let applyStat;
72 try {
73 // eslint-disable-next-line security/detect-non-literal-fs-filename
74 applyStat = fs.statSync(path.join(schemaDir, name, 'apply.sql'));
75 return applyStat.isFile();
76 } catch (e) { // eslint-disable-line no-unused-vars
77 return false;
78 }
79 }
80 return false;
81 }
82
83
84 /**
85 * Return an array of schema migration directory names within engineDir.
86 * @param {string} engineDir path to engine implementation
87 * @returns {string[]} schema versions
88 */
89 function allSchemaVersions(engineDir) {
90 const schemaDir = path.join(engineDir, 'sql', 'schema');
91 // eslint-disable-next-line security/detect-non-literal-fs-filename
92 const availableVersions = fs.readdirSync(schemaDir).filter((d) => isSchemaMigrationDirectory(schemaDir, d));
93 availableVersions.sort(schemaVersionStringCmp);
94 return availableVersions;
95 }
96
97
98 /**
99 * Return an array of schema migration directory names within engineDir,
100 * which are within supported range, and are greater than the current
101 * @param {string} engineDir path to engine implementation
102 * @param {SchemaVersionObject} current curernt version
103 * @param {object} supported supported version range
104 * @param {SchemaVersionObject} supported.min min version
105 * @param {SchemaVersionObject} supported.max max version
106 * @returns {string[]} unapplied versions
107 */
108 function unappliedSchemaVersions(engineDir, current, supported) {
109 const min = schemaVersionObjectToNumber(supported.min);
110 const max = schemaVersionObjectToNumber(supported.max);
111 const cur = schemaVersionObjectToNumber(current);
112 const available = allSchemaVersions(engineDir);
113 return available.filter((a) => {
114 a = schemaVersionStringToNumber(a);
115 return a >= min && a <= max && a > cur;
116 });
117 }
118
119
120 module.exports = {
121 schemaVersionStringToObject,
122 schemaVersionObjectToNumber,
123 schemaVersionStringToNumber,
124 schemaVersionStringCmp,
125 isSchemaMigrationDirectory,
126 allSchemaVersions,
127 unappliedSchemaVersions,
128 };