From 27c1ce8f22e3f880d81315cd7d687802a9f25230 Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Wed, 22 Oct 2025 03:17:56 +0800 Subject: [PATCH 1/9] Add initSQLFilePath option documentation --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c68b0c..2b1a47f 100644 --- a/README.md +++ b/README.md @@ -180,9 +180,13 @@ Description: The number of times to try to download a MySQL binary before giving Default: "" -Description: A string with MySQL queries to run before the database starts to accept connections. This option can be used for things like initialising tables without having to first connect to the database to do that. The queries in the string get executed after ```mysql-memory-server```'s queries run. Uses the ```--init-file``` MySQL server option under the hood. Learn more at the [--init-file MySQL Documentation](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_init_file) +Description: A string with SQL queries to run before the database starts to accept connections. This option can be used for things like initialising tables without having to first connect to the database to do that. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. -The internal queries that are ran before the queries in ```initSQLString``` are creating the MySQL user with ```options.username``` username if the option's value is not ```root```, and creating a database with the ```options.dbName``` name. +- `initSQLFilePath: string` + +Default: "" + +Description: A path to a .sql file with SQL queries to run before the database starts to accept connections. This option can be used for things like importing an exported MySQL database. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails (like if the path does not exist), then the database creation will fail. - `arch: "arm64" | "x64"` @@ -195,3 +199,11 @@ Description: The MySQL binary architecture to execute. MySQL does not offer serv Default: "FORCE" Description: This option follows the convention set out by the [MySQL Documentation](https://dev.mysql.com/doc/refman/en/plugin-loading.html). If set to "OFF", the MySQL X Plugin will not initialise. If set to "FORCE", the MySQL Server will either start up with the MySQL X Plugin guaranteed to have successfully initialised, or if initialisation fails, the server will fail to start up. + +#### Init SQL file order of operations: + +There are some SQL queries executed on the database before the database is ready to be used. This is handled under the hood using the ```--init-file``` MySQL server option. Learn more at the [--init-file MySQL Documentation](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_init_file). The following is the order in which the SQL queries are executed in, ordered from first executed to last executed: + +1. ```mysql-memory-server``` internal SQL queries - The internal queries that are executed are creating the MySQL user with ```options.username``` username if the option's value is not ```root```, and creating a database with the ```options.dbName``` name. +2. The SQL queries provided to the ```initSQLString``` option +3. The SQL queries provided to the ```initSQLFilePath``` option From 929437e80bcfe81cd2c986ed0643a1d59c41f8bd Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:29:00 +0800 Subject: [PATCH 2/9] increase font size of heading in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b1a47f..6622d5b 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Default: "FORCE" Description: This option follows the convention set out by the [MySQL Documentation](https://dev.mysql.com/doc/refman/en/plugin-loading.html). If set to "OFF", the MySQL X Plugin will not initialise. If set to "FORCE", the MySQL Server will either start up with the MySQL X Plugin guaranteed to have successfully initialised, or if initialisation fails, the server will fail to start up. -#### Init SQL file order of operations: +### Init SQL file order of operations: There are some SQL queries executed on the database before the database is ready to be used. This is handled under the hood using the ```--init-file``` MySQL server option. Learn more at the [--init-file MySQL Documentation](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_init_file). The following is the order in which the SQL queries are executed in, ordered from first executed to last executed: From 43cf40e0a441cc5a8322bc494a21967b1e720248 Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Wed, 22 Oct 2025 16:55:50 +0800 Subject: [PATCH 3/9] add initSQLFilePath option functionality --- README.md | 2 +- src/constants.ts | 11 +++++++++-- src/libraries/Executor.ts | 34 +++++++++++++++++++++++++++++++++- types/index.ts | 6 ++++-- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6622d5b..ed79315 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Description: A string with SQL queries to run before the database starts to acce Default: "" -Description: A path to a .sql file with SQL queries to run before the database starts to accept connections. This option can be used for things like importing an exported MySQL database. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails (like if the path does not exist), then the database creation will fail. +Description: A path to a UTF-8 .sql file with SQL queries to run before the database starts to accept connections. This option can be used for things like importing an exported MySQL database. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails (like if the path does not exist), then the database creation will fail. - `arch: "arm64" | "x64"` diff --git a/src/constants.ts b/src/constants.ts index 619a363..568e335 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,6 +2,7 @@ import { InternalServerOptions, OptionTypeChecks } from "../types"; import {normalize as normalizePath} from 'path' import { tmpdir } from "os"; import { valid as validSemver, coerce as coerceSemver } from "semver"; +import { existsSync } from "fs"; export const DEFAULT_OPTIONS: InternalServerOptions = { version: undefined, @@ -18,7 +19,8 @@ export const DEFAULT_OPTIONS: InternalServerOptions = { downloadRetries: 10, initSQLString: '', arch: process.arch, - xEnabled: 'FORCE' + xEnabled: 'FORCE', + initSQLFilePath: '' } as const; export const DEFAULT_OPTIONS_KEYS = Object.freeze(Object.keys(DEFAULT_OPTIONS)) @@ -115,7 +117,12 @@ export const OPTION_TYPE_CHECKS: OptionTypeChecks = { check: (opt: any) => opt === undefined || pluginActivationStates.includes(opt), errorMessage: `xEnabled must be either undefined or one of the following: ${pluginActivationStates.join(', ')}`, definedType: 'boolean' - } + }, + initSQLFilePath: { + check: (opt: any) => opt === undefined || (typeof opt === 'string' && existsSync(opt)), + errorMessage: 'Option initSQLFilePath must be either undefined or a filepath string that points to a file that exists.', + definedType: 'string' + }, } as const; export const MIN_SUPPORTED_MYSQL = '5.7.19'; diff --git a/src/libraries/Executor.ts b/src/libraries/Executor.ts index 4e164fd..f233020 100644 --- a/src/libraries/Executor.ts +++ b/src/libraries/Executor.ts @@ -358,6 +358,29 @@ class Executor { }) } + #streamAppendToFile(readPath: string, writePath: string): Promise { + return new Promise((resolve, reject) => { + const rs = fs.createReadStream(readPath, {encoding: 'utf-8'}) + const ws = fs.createWriteStream(writePath, {flags: 'a', encoding: 'utf-8'}) + + rs.on('error', (e) => { + ws.end(); + this.logger.error('Received error from streamAppendToFile read stream:', e) + reject(e) + }) + + ws.on('error', (e) => { + rs.close(); + this.logger.error('Received error from streamAppendToFile write stream:', e) + reject(e) + }) + + rs.pipe(ws) + + ws.on('finish', () => resolve) + }) + } + async #setupDataDirectories(options: InternalServerOptions, binary: DownloadedMySQLVersion, datadir: string, retry: boolean): Promise { const binaryFilepath = binary.path this.logger.log('Created data directory for database at:', datadir) @@ -499,9 +522,18 @@ class Executor { this.logger.log('Writing init file') - await fsPromises.writeFile(`${this.databasePath}/init.sql`, initText, {encoding: 'utf8'}) + const initFilePath = `${this.databasePath}/init.sql` + await fsPromises.writeFile(initFilePath, initText, {encoding: 'utf8'}) this.logger.log('Finished writing init file') + + if (options.initSQLFilePath) { + this.logger.log('Appending init.sql file with the contents of the file at path provided by options.initSQLFilePath.') + + await this.#streamAppendToFile(options.initSQLFilePath, initFilePath) + + this.logger.log('Successfully appended init.sql file with the contents of the file at path provided by options.initSQLFilePath.') + } } async startMySQL(options: InternalServerOptions, installedMySQLBinary: DownloadedMySQLVersion): Promise { diff --git a/types/index.ts b/types/index.ts index 2bf8f74..00181fb 100644 --- a/types/index.ts +++ b/types/index.ts @@ -19,7 +19,8 @@ export type ServerOptions = { downloadRetries?: number | undefined, initSQLString?: string | undefined, arch?: "arm64" | "x64" | undefined, - xEnabled?: PluginActivationState | undefined + xEnabled?: PluginActivationState | undefined, + initSQLFilePath?: string | undefined } export type InternalServerOptions = { @@ -37,7 +38,8 @@ export type InternalServerOptions = { downloadRetries: number, initSQLString: string, arch: string, - xEnabled: PluginActivationState + xEnabled: PluginActivationState, + initSQLFilePath: string } export type ExecuteFileReturn = { From b38dbf04748ae5091bd56ee643491dee658094a8 Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:00:47 +0800 Subject: [PATCH 4/9] add test for initSQLFilePath option --- tests/versions.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/versions.test.ts b/tests/versions.test.ts index e6ed27e..63ac057 100644 --- a/tests/versions.test.ts +++ b/tests/versions.test.ts @@ -18,6 +18,9 @@ const arch = process.arch === 'x64' || (process.platform === 'win32' && process. const versionRequirement = process.env.VERSION_REQUIREMENT || '>0.0.0' console.log('Running versions test with versionRequirement:', versionRequirement) +const initSQLFilePath = `${os.tmpdir()}/mysqlmsn-init-file-${crypto.randomUUID()}` +fs.writeFileSync(initSQLFilePath, 'CREATE DATABASE initfromsqlfilepath;', 'utf-8') + for (const version of DOWNLOADABLE_MYSQL_VERSIONS.filter(v => satisfies(v, versionRequirement))) { try { getBinaryURL(version, arch) @@ -35,7 +38,8 @@ for (const version of DOWNLOADABLE_MYSQL_VERSIONS.filter(v => satisfies(v, versi logLevel: 'LOG', initSQLString: 'CREATE DATABASE mytestdb;', arch, - xEnabled: process.env.X_OFF === 'true' ? 'OFF' : 'FORCE' + xEnabled: process.env.X_OFF === 'true' ? 'OFF' : 'FORCE', + initSQLFilePath } const db = await createDB(options) @@ -49,6 +53,9 @@ for (const version of DOWNLOADABLE_MYSQL_VERSIONS.filter(v => satisfies(v, versi //If this does not fail, it means initSQLString works as expected and the database was successfully created. await connection.query('USE mytestdb;') + + //If this does not fail, it means initSQLFilePath works as expected and the database was successfully created. + await connection.query('USE initfromsqlfilepath;') await connection.end(); await db.stop(); From 0c152e676f6149b4d00508367ad2920721f8b7fc Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:02:17 +0800 Subject: [PATCH 5/9] change initSQLFilePath documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed79315..04f7c33 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Description: A string with SQL queries to run before the database starts to acce Default: "" -Description: A path to a UTF-8 .sql file with SQL queries to run before the database starts to accept connections. This option can be used for things like importing an exported MySQL database. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails (like if the path does not exist), then the database creation will fail. +Description: A path to a UTF-8 .sql file with SQL queries to run before the database starts to accept connections. This option can be used for things like importing an exported MySQL database. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails, then the database creation will fail. The database creation process will not begin if ```initSQLFilePath``` is defined but the path specified does not exist. - `arch: "arm64" | "x64"` From f8b345eec3b43e64ca648f234bdc157f03a7b07a Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:42:32 +0800 Subject: [PATCH 6/9] resolve streamAppendToFile when read stream emits 'end' --- src/libraries/Executor.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/Executor.ts b/src/libraries/Executor.ts index f233020..e0c2040 100644 --- a/src/libraries/Executor.ts +++ b/src/libraries/Executor.ts @@ -375,9 +375,13 @@ class Executor { reject(e) }) - rs.pipe(ws) + rs.on('end', () => { + rs.close(); + ws.close(); + resolve() + }) - ws.on('finish', () => resolve) + rs.pipe(ws) }) } From afe17ff4888594bac6b27c5994efcfe95bb7bdcc Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:05:59 +0800 Subject: [PATCH 7/9] import randomUUID from crypto for versions test --- tests/versions.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/versions.test.ts b/tests/versions.test.ts index 63ac057..7a75da1 100644 --- a/tests/versions.test.ts +++ b/tests/versions.test.ts @@ -8,6 +8,7 @@ import { DOWNLOADABLE_MYSQL_VERSIONS } from '../src/constants'; import fs from 'fs' import fsPromises from 'fs/promises' import os from 'os' +import { randomUUID } from 'crypto'; const usernames = ['root', 'dbuser'] @@ -18,7 +19,7 @@ const arch = process.arch === 'x64' || (process.platform === 'win32' && process. const versionRequirement = process.env.VERSION_REQUIREMENT || '>0.0.0' console.log('Running versions test with versionRequirement:', versionRequirement) -const initSQLFilePath = `${os.tmpdir()}/mysqlmsn-init-file-${crypto.randomUUID()}` +const initSQLFilePath = `${os.tmpdir()}/mysqlmsn-init-file-${randomUUID()}` fs.writeFileSync(initSQLFilePath, 'CREATE DATABASE initfromsqlfilepath;', 'utf-8') for (const version of DOWNLOADABLE_MYSQL_VERSIONS.filter(v => satisfies(v, versionRequirement))) { From a4855088ba967d4fb8941a7a2f650391a9d5ad51 Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:25:30 +0800 Subject: [PATCH 8/9] add new line after initSQLString option --- src/libraries/Executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Executor.ts b/src/libraries/Executor.ts index e0c2040..383bd43 100644 --- a/src/libraries/Executor.ts +++ b/src/libraries/Executor.ts @@ -521,7 +521,7 @@ class Executor { } if (options.initSQLString.length > 0) { - initText += `\n${options.initSQLString}` + initText += `\n${options.initSQLString}\n` } this.logger.log('Writing init file') From 21ea425e8e54e9885527e82e06d3a6bf638df4b1 Mon Sep 17 00:00:00 2001 From: Sebastian-Webster <84299475+Sebastian-Webster@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:52:59 +0800 Subject: [PATCH 9/9] update documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04f7c33..de1cc7e 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Description: A string with SQL queries to run before the database starts to acce Default: "" -Description: A path to a UTF-8 .sql file with SQL queries to run before the database starts to accept connections. This option can be used for things like importing an exported MySQL database. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails, then the database creation will fail. The database creation process will not begin if ```initSQLFilePath``` is defined but the path specified does not exist. +Description: A path to a UTF-8 .sql file with SQL queries to run before the database starts to accept connections. This option is like the ```initSQLString``` option, instead taking a filepath for SQL statements to execute rather than a string of SQL statements - great for when you need to execute more than just a few SQL statements. Check the [Init SQL file order of operations](#init-sql-file-order-of-operations) to learn more about what SQL queries are ran and in what order before the database starts accepting connections. If a filepath is defined and reading the file fails, then the database creation will fail. The database creation process will not begin if ```initSQLFilePath``` is defined but the path specified does not exist. - `arch: "arm64" | "x64"`