Skip to content

Commit c879923

Browse files
authored
Handle improperly formatted mysql version strings (#124)
Handled MySQL version checks better and enabled tests for MySQL 5.7
1 parent c209538 commit c879923

File tree

14 files changed

+94
-85
lines changed

14 files changed

+94
-85
lines changed

.changeset/gentle-icons-try.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/service-module-mysql': patch
3+
---
4+
5+
Fixed MySQL version checking to better handle non-semantic version strings

.github/workflows/test.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ jobs:
152152
strategy:
153153
fail-fast: false
154154
matrix:
155-
mysql-version: [8.0, 8.4]
155+
mysql-version: [5.7, 8.0, 8.4]
156156

157157
steps:
158158
- uses: actions/checkout@v4
@@ -167,7 +167,8 @@ jobs:
167167
-d mysql:${{ matrix.mysql-version }} \
168168
--log-bin=/var/lib/mysql/mysql-bin.log \
169169
--gtid_mode=ON \
170-
--enforce_gtid_consistency=ON
170+
--enforce_gtid_consistency=ON \
171+
--server-id=1
171172
172173
- name: Start MongoDB
173174
uses: supercharge/[email protected]

modules/module-mysql/src/api/MySQLRouteAPIAdapter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as sync_rules from '@powersync/service-sync-rules';
44
import * as service_types from '@powersync/service-types';
55
import mysql from 'mysql2/promise';
66
import * as common from '../common/common-index.js';
7-
import * as mysql_utils from '../utils/mysql_utils.js';
7+
import * as mysql_utils from '../utils/mysql-utils.js';
88
import * as types from '../types/types.js';
99
import { toExpressionTypeFromMySQLType } from '../common/common-index.js';
1010

@@ -326,7 +326,7 @@ export class MySQLRouteAPIAdapter implements api.RouteAPI {
326326
name: result.schema_name,
327327
tables: []
328328
});
329-
329+
330330
const columns = JSON.parse(result.columns).map((column: { data_type: string; column_name: string }) => ({
331331
name: column.column_name,
332332
type: column.data_type,

modules/module-mysql/src/common/ReplicatedGTID.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import mysql from 'mysql2/promise';
22
import * as uuid from 'uuid';
3-
import * as mysql_utils from '../utils/mysql_utils.js';
3+
import * as mysql_utils from '../utils/mysql-utils.js';
44

55
export type BinLogPosition = {
66
filename: string;

modules/module-mysql/src/common/check-source-configuration.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import mysqlPromise from 'mysql2/promise';
2-
import * as mysql_utils from '../utils/mysql_utils.js';
2+
import * as mysql_utils from '../utils/mysql-utils.js';
3+
4+
const MIN_SUPPORTED_VERSION = '5.7.0';
35

46
export async function checkSourceConfiguration(connection: mysqlPromise.Connection): Promise<string[]> {
57
const errors: string[] = [];
8+
9+
const version = await mysql_utils.getMySQLVersion(connection);
10+
if (!mysql_utils.isVersionAtLeast(version, MIN_SUPPORTED_VERSION)) {
11+
errors.push(`MySQL versions older than ${MIN_SUPPORTED_VERSION} are not supported. Your version is: ${version}.`);
12+
}
13+
614
const [[result]] = await mysql_utils.retriedQuery({
715
connection,
816
query: `
@@ -48,12 +56,3 @@ export async function checkSourceConfiguration(connection: mysqlPromise.Connecti
4856

4957
return errors;
5058
}
51-
52-
export async function getMySQLVersion(connection: mysqlPromise.Connection): Promise<string> {
53-
const [[versionResult]] = await mysql_utils.retriedQuery({
54-
connection,
55-
query: `SELECT VERSION() as version`
56-
});
57-
58-
return versionResult.version as string;
59-
}

modules/module-mysql/src/common/get-replication-columns.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { storage } from '@powersync/service-core';
22
import mysqlPromise from 'mysql2/promise';
3-
import * as mysql_utils from '../utils/mysql_utils.js';
3+
import * as mysql_utils from '../utils/mysql-utils.js';
44

55
export type GetReplicationColumnsOptions = {
66
connection: mysqlPromise.Connection;

modules/module-mysql/src/common/read-executed-gtid.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
import mysqlPromise from 'mysql2/promise';
2-
import * as mysql_utils from '../utils/mysql_utils.js';
3-
import { gte } from 'semver';
4-
2+
import * as mysql_utils from '../utils/mysql-utils.js';
53
import { ReplicatedGTID } from './ReplicatedGTID.js';
6-
import { getMySQLVersion } from './check-source-configuration.js';
74

85
/**
96
* Gets the current master HEAD GTID
107
*/
118
export async function readExecutedGtid(connection: mysqlPromise.Connection): Promise<ReplicatedGTID> {
12-
const version = await getMySQLVersion(connection);
9+
const version = await mysql_utils.getMySQLVersion(connection);
10+
1311
let binlogStatus: mysqlPromise.RowDataPacket;
14-
if (gte(version, '8.4.0')) {
15-
// Get the BinLog status
12+
if (mysql_utils.isVersionAtLeast(version, '8.4.0')) {
13+
// Syntax for the below query changed in 8.4.0
1614
const [[binLogResult]] = await mysql_utils.retriedQuery({
1715
connection,
1816
query: `SHOW BINARY LOG STATUS`
1917
});
2018
binlogStatus = binLogResult;
2119
} else {
22-
// TODO Check if this works for version 5.7
23-
// Get the BinLog status
2420
const [[binLogResult]] = await mysql_utils.retriedQuery({
2521
connection,
2622
query: `SHOW MASTER STATUS`

modules/module-mysql/src/replication/BinLogStream.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as zongji_utils from './zongji/zongji-utils.js';
1111
import { MySQLConnectionManager } from './MySQLConnectionManager.js';
1212
import { isBinlogStillAvailable, ReplicatedGTID, toColumnDescriptors } from '../common/common-index.js';
1313
import mysqlPromise from 'mysql2/promise';
14-
import { createRandomServerId } from '../utils/mysql_utils.js';
14+
import { createRandomServerId } from '../utils/mysql-utils.js';
1515

1616
export interface BinLogStreamOptions {
1717
connections: MySQLConnectionManager;

modules/module-mysql/src/replication/MySQLConnectionManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NormalizedMySQLConnectionConfig } from '../types/types.js';
22
import mysqlPromise from 'mysql2/promise';
33
import mysql, { FieldPacket, RowDataPacket } from 'mysql2';
4-
import * as mysql_utils from '../utils/mysql_utils.js';
4+
import * as mysql_utils from '../utils/mysql-utils.js';
55
import ZongJi from '@powersync/mysql-zongji';
66
import { logger } from '@powersync/lib-services-framework';
77

modules/module-mysql/src/utils/mysql_utils.ts renamed to modules/module-mysql/src/utils/mysql-utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { logger } from '@powersync/lib-services-framework';
22
import mysql from 'mysql2';
33
import mysqlPromise from 'mysql2/promise';
44
import * as types from '../types/types.js';
5+
import { coerce, gte } from 'semver';
56

67
export type RetriedQueryOptions = {
78
connection: mysqlPromise.Connection;
@@ -60,3 +61,24 @@ export function createPool(config: types.NormalizedMySQLConnectionConfig, option
6061
export function createRandomServerId(syncRuleId: number): number {
6162
return Number.parseInt(`${syncRuleId}00${Math.floor(Math.random() * 10000)}`);
6263
}
64+
65+
export async function getMySQLVersion(connection: mysqlPromise.Connection): Promise<string> {
66+
const [[versionResult]] = await retriedQuery({
67+
connection,
68+
query: `SELECT VERSION() as version`
69+
});
70+
71+
return versionResult.version as string;
72+
}
73+
74+
/**
75+
* Check if the current MySQL version is newer or equal to the target version.
76+
* @param version
77+
* @param minimumVersion
78+
*/
79+
export function isVersionAtLeast(version: string, minimumVersion: string): boolean {
80+
const coercedVersion = coerce(version);
81+
const coercedMinimumVersion = coerce(minimumVersion);
82+
83+
return gte(coercedVersion!, coercedMinimumVersion!, { loose: true });
84+
}

0 commit comments

Comments
 (0)