Skip to content

Commit 5904a6f

Browse files
authored
Add MySQL support for nested subpath (JSON) expressions (#321)
* Implement the new SQLDialect.nestedSubpathExpression(in:for:) method for MySQL syntax. * Fix CI, add API breakage allowlist
1 parent ba3dcb5 commit 5904a6f

File tree

4 files changed

+46
-43
lines changed

4 files changed

+46
-43
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
API breakage: func MySQLDialect.customDataType(for:) has return type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
2+
API breakage: var MySQLDialect.sharedSelectLockExpression has declared type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
3+
API breakage: accessor MySQLDialect.sharedSelectLockExpression.Get() has return type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
4+
API breakage: var MySQLDialect.exclusiveSelectLockExpression has declared type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
5+
API breakage: accessor MySQLDialect.exclusiveSelectLockExpression.Get() has return type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
6+

.github/workflows/test.yml

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
push: { branches: [ main ] }
88

99
env:
10-
LOG_LEVEL: debug
10+
LOG_LEVEL: info
1111
SWIFT_DETERMINISTIC_HASHING: 1
1212
MYSQL_HOSTNAME: 'mysql-a'
1313
MYSQL_HOSTNAME_A: 'mysql-a'
@@ -24,22 +24,17 @@ env:
2424

2525
jobs:
2626

27-
# Check for API breakage versus main
2827
api-breakage:
29-
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
28+
if: ${{ !(github.event.pull_request.draft || false) }}
3029
runs-on: ubuntu-latest
3130
container: swift:5.8-jammy
3231
steps:
33-
- name: Check out package
32+
- name: Check out code
3433
uses: actions/checkout@v3
35-
with: { fetch-depth: 0 }
36-
# https://github.com/actions/checkout/issues/766
37-
- name: Mark the workspace as safe
38-
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
39-
- name: Check for API breaking changes
40-
run: swift package diagnose-api-breaking-changes origin/main
34+
with: { 'fetch-depth': 0 }
35+
- name: Run API breakage check action
36+
uses: vapor/ci/.github/actions/ci-swift-check-api-breakage@reusable-workflows
4137

42-
# Test integration with downstream Fluent driver
4338
dependents:
4439
if: ${{ !(github.event.pull_request.draft || false) }}
4540
runs-on: ubuntu-latest
@@ -65,7 +60,7 @@ jobs:
6560
dbimage:
6661
- mysql:5.7
6762
- mysql:8.0
68-
- mariadb:10.11
63+
- mariadb:11
6964
- percona:8.0
7065
steps:
7166
- name: Check out package
@@ -91,15 +86,13 @@ jobs:
9186
- mysql:5.7
9287
- mysql:8.0
9388
- mariadb:10.4
94-
- mariadb:10.11
89+
- mariadb:11
9590
- percona:8.0
9691
runner:
9792
# List is deliberately incomplete; we want to avoid running 50 jobs on every commit
9893
- swift:5.6-focal
99-
#- swift:5.7-jammy
10094
- swift:5.8-jammy
10195
- swiftlang/swift:nightly-5.9-jammy
102-
#- swiftlang/swift:nightly-main-jammy
10396
container: ${{ matrix.runner }}
10497
runs-on: ubuntu-latest
10598
services:
@@ -114,19 +107,22 @@ jobs:
114107
- name: Save MySQL version to env
115108
run: |
116109
echo MYSQL_VERSION='${{ matrix.dbimage }}' >> $GITHUB_ENV
110+
- name: Display versions
111+
shell: bash
112+
run: |
113+
if [[ '${{ contains(matrix.container, 'nightly') }}' == 'true' ]]; then
114+
SWIFT_PLATFORM="$(source /etc/os-release && echo "${ID}${VERSION_ID}")" SWIFT_VERSION="$(cat /.swift_tag)"
115+
printf 'SWIFT_PLATFORM=%s\nSWIFT_VERSION=%s\n' "${SWIFT_PLATFORM}" "${SWIFT_VERSION}" >>"${GITHUB_ENV}"
116+
fi
117+
printf 'OS: %s\nTag: %s\nVersion:\n' "${SWIFT_PLATFORM}-${RUNNER_ARCH}" "${SWIFT_VERSION}" && swift --version
117118
- name: Check out package
118119
uses: actions/checkout@v3
119120
- name: Run local tests with coverage and TSan
120121
run: swift test --enable-code-coverage --sanitize=thread
121122
- name: Submit coverage report to Codecov.io
122-
if: ${{ !contains(matrix.runner, '5.8') }}
123123
uses: vapor/[email protected]
124124
with:
125-
cc_flags: 'unittests'
126125
cc_env_vars: 'SWIFT_VERSION,SWIFT_PLATFORM,RUNNER_OS,RUNNER_ARCH,MYSQL_VERSION'
127-
cc_fail_ci_if_error: true
128-
cc_verbose: true
129-
cc_dry_run: false
130126

131127
# Run unit tests (macOS). Don't bother with lots of variations, Linux will cover that.
132128
macos-unit:
@@ -135,7 +131,7 @@ jobs:
135131
fail-fast: false
136132
matrix:
137133
formula: [ '[email protected]' ]
138-
macos: [ 'macos-12' ]
134+
macos: [ 'macos-13' ]
139135
xcode: [ 'latest-stable' ]
140136
runs-on: ${{ matrix.macos }}
141137
steps:
@@ -162,15 +158,3 @@ jobs:
162158
- name: Run tests with Thread Sanitizer
163159
run: swift test --sanitize=thread
164160
env: { MYSQL_HOSTNAME: '127.0.0.1' }
165-
166-
test-exports:
167-
if: ${{ !(github.event.pull_request.draft || false) }}
168-
name: Test exports
169-
runs-on: ubuntu-latest
170-
container: swift:5.8-jammy
171-
steps:
172-
- name: Check out package
173-
uses: actions/checkout@v3
174-
with: { fetch-depth: 0 }
175-
- name: Build
176-
run: swift build -Xswiftc -DBUILDING_DOCC

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let package = Package(
1414
],
1515
dependencies: [
1616
.package(url: "https://github.com/vapor/mysql-nio.git", from: "1.0.0"),
17-
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.16.0"),
17+
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.28.0"),
1818
.package(url: "https://github.com/vapor/async-kit.git", from: "1.0.0"),
1919
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"),
2020
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),

Sources/MySQLKit/MySQLDialect.swift

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ public struct MySQLDialect: SQLDialect {
99
"mysql"
1010
}
1111

12-
public var identifierQuote: SQLExpression {
12+
public var identifierQuote: any SQLExpression {
1313
return SQLRaw("`")
1414
}
1515

16-
public var literalStringQuote: SQLExpression {
16+
public var literalStringQuote: any SQLExpression {
1717
return SQLRaw("'")
1818
}
1919

20-
public func bindPlaceholder(at position: Int) -> SQLExpression {
20+
public func bindPlaceholder(at position: Int) -> any SQLExpression {
2121
return SQLRaw("?")
2222
}
2323

24-
public func literalBoolean(_ value: Bool) -> SQLExpression {
24+
public func literalBoolean(_ value: Bool) -> any SQLExpression {
2525
switch value {
2626
case false:
2727
return SQLRaw("0")
@@ -30,7 +30,7 @@ public struct MySQLDialect: SQLDialect {
3030
}
3131
}
3232

33-
public var autoIncrementClause: SQLExpression {
33+
public var autoIncrementClause: any SQLExpression {
3434
return SQLRaw("AUTO_INCREMENT")
3535
}
3636

@@ -42,7 +42,7 @@ public struct MySQLDialect: SQLDialect {
4242
.inline
4343
}
4444

45-
public func customDataType(for dataType: SQLDataType) -> SQLExpression? {
45+
public func customDataType(for dataType: SQLDataType) -> (any SQLExpression)? {
4646
switch dataType {
4747
case .text:
4848
return SQLRaw("VARCHAR(255)")
@@ -58,7 +58,7 @@ public struct MySQLDialect: SQLDialect {
5858
)
5959
}
6060

61-
public func normalizeSQLConstraint(identifier: SQLExpression) -> SQLExpression {
61+
public func normalizeSQLConstraint(identifier: any SQLExpression) -> any SQLExpression {
6262
if let sqlIdentifier = identifier as? SQLIdentifier {
6363
return SQLIdentifier(Insecure.SHA1.hash(data: Data(sqlIdentifier.string.utf8)).hexRepresentation)
6464
} else {
@@ -78,13 +78,26 @@ public struct MySQLDialect: SQLDialect {
7878
[.union, .unionAll, .explicitDistinct, .parenthesizedSubqueries]
7979
}
8080

81-
public var sharedSelectLockExpression: SQLExpression? {
81+
public var sharedSelectLockExpression: (any SQLExpression)? {
8282
SQLRaw("LOCK IN SHARE MODE")
8383
}
8484

85-
public var exclusiveSelectLockExpression: SQLExpression? {
85+
public var exclusiveSelectLockExpression: (any SQLExpression)? {
8686
SQLRaw("FOR UPDATE")
8787
}
88+
89+
public func nestedSubpathExpression(in column: any SQLExpression, for path: [String]) -> (any SQLExpression)? {
90+
guard !path.isEmpty else { return nil }
91+
92+
// N.B.: While MySQL has had the `->` and `->>` operators since 5.7.13, there are still implementations with
93+
// which they do not work properly (most notably AWS's Aurora 2.x), so we use the legacy functions instead.
94+
return SQLFunction("json_unquote", args:
95+
SQLFunction("json_extract", args: [
96+
column,
97+
SQLLiteral.string("$.\(path.joined(separator: "."))")
98+
]
99+
))
100+
}
88101
}
89102

90103
fileprivate let hexTable: [UInt8] = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66]

0 commit comments

Comments
 (0)