Skip to content

Commit

Permalink
Add MySQL support for nested subpath (JSON) expressions (#321)
Browse files Browse the repository at this point in the history
* Implement the new SQLDialect.nestedSubpathExpression(in:for:) method for MySQL syntax.
* Fix CI, add API breakage allowlist
  • Loading branch information
gwynne authored Jul 11, 2023
1 parent ba3dcb5 commit 5904a6f
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 43 deletions.
6 changes: 6 additions & 0 deletions .api-breakage/allowlist-branch-nested-subpath-expressions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
API breakage: func MySQLDialect.customDataType(for:) has return type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
API breakage: var MySQLDialect.sharedSelectLockExpression has declared type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
API breakage: accessor MySQLDialect.sharedSelectLockExpression.Get() has return type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
API breakage: var MySQLDialect.exclusiveSelectLockExpression has declared type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?
API breakage: accessor MySQLDialect.exclusiveSelectLockExpression.Get() has return type change from SQLKit.SQLExpression? to (SQLKit.SQLExpression)?

50 changes: 17 additions & 33 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
push: { branches: [ main ] }

env:
LOG_LEVEL: debug
LOG_LEVEL: info
SWIFT_DETERMINISTIC_HASHING: 1
MYSQL_HOSTNAME: 'mysql-a'
MYSQL_HOSTNAME_A: 'mysql-a'
Expand All @@ -24,22 +24,17 @@ env:

jobs:

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

# Test integration with downstream Fluent driver
dependents:
if: ${{ !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
Expand All @@ -65,7 +60,7 @@ jobs:
dbimage:
- mysql:5.7
- mysql:8.0
- mariadb:10.11
- mariadb:11
- percona:8.0
steps:
- name: Check out package
Expand All @@ -91,15 +86,13 @@ jobs:
- mysql:5.7
- mysql:8.0
- mariadb:10.4
- mariadb:10.11
- mariadb:11
- percona:8.0
runner:
# List is deliberately incomplete; we want to avoid running 50 jobs on every commit
- swift:5.6-focal
#- swift:5.7-jammy
- swift:5.8-jammy
- swiftlang/swift:nightly-5.9-jammy
#- swiftlang/swift:nightly-main-jammy
container: ${{ matrix.runner }}
runs-on: ubuntu-latest
services:
Expand All @@ -114,19 +107,22 @@ jobs:
- name: Save MySQL version to env
run: |
echo MYSQL_VERSION='${{ matrix.dbimage }}' >> $GITHUB_ENV
- name: Display versions
shell: bash
run: |
if [[ '${{ contains(matrix.container, 'nightly') }}' == 'true' ]]; then
SWIFT_PLATFORM="$(source /etc/os-release && echo "${ID}${VERSION_ID}")" SWIFT_VERSION="$(cat /.swift_tag)"
printf 'SWIFT_PLATFORM=%s\nSWIFT_VERSION=%s\n' "${SWIFT_PLATFORM}" "${SWIFT_VERSION}" >>"${GITHUB_ENV}"
fi
printf 'OS: %s\nTag: %s\nVersion:\n' "${SWIFT_PLATFORM}-${RUNNER_ARCH}" "${SWIFT_VERSION}" && swift --version
- name: Check out package
uses: actions/checkout@v3
- name: Run local tests with coverage and TSan
run: swift test --enable-code-coverage --sanitize=thread
- name: Submit coverage report to Codecov.io
if: ${{ !contains(matrix.runner, '5.8') }}
uses: vapor/[email protected]
with:
cc_flags: 'unittests'
cc_env_vars: 'SWIFT_VERSION,SWIFT_PLATFORM,RUNNER_OS,RUNNER_ARCH,MYSQL_VERSION'
cc_fail_ci_if_error: true
cc_verbose: true
cc_dry_run: false

# Run unit tests (macOS). Don't bother with lots of variations, Linux will cover that.
macos-unit:
Expand All @@ -135,7 +131,7 @@ jobs:
fail-fast: false
matrix:
formula: [ '[email protected]' ]
macos: [ 'macos-12' ]
macos: [ 'macos-13' ]
xcode: [ 'latest-stable' ]
runs-on: ${{ matrix.macos }}
steps:
Expand All @@ -162,15 +158,3 @@ jobs:
- name: Run tests with Thread Sanitizer
run: swift test --sanitize=thread
env: { MYSQL_HOSTNAME: '127.0.0.1' }

test-exports:
if: ${{ !(github.event.pull_request.draft || false) }}
name: Test exports
runs-on: ubuntu-latest
container: swift:5.8-jammy
steps:
- name: Check out package
uses: actions/checkout@v3
with: { fetch-depth: 0 }
- name: Build
run: swift build -Xswiftc -DBUILDING_DOCC
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/vapor/mysql-nio.git", from: "1.0.0"),
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.16.0"),
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.28.0"),
.package(url: "https://github.com/vapor/async-kit.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
Expand Down
31 changes: 22 additions & 9 deletions Sources/MySQLKit/MySQLDialect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ public struct MySQLDialect: SQLDialect {
"mysql"
}

public var identifierQuote: SQLExpression {
public var identifierQuote: any SQLExpression {
return SQLRaw("`")
}

public var literalStringQuote: SQLExpression {
public var literalStringQuote: any SQLExpression {
return SQLRaw("'")
}

public func bindPlaceholder(at position: Int) -> SQLExpression {
public func bindPlaceholder(at position: Int) -> any SQLExpression {
return SQLRaw("?")
}

public func literalBoolean(_ value: Bool) -> SQLExpression {
public func literalBoolean(_ value: Bool) -> any SQLExpression {
switch value {
case false:
return SQLRaw("0")
Expand All @@ -30,7 +30,7 @@ public struct MySQLDialect: SQLDialect {
}
}

public var autoIncrementClause: SQLExpression {
public var autoIncrementClause: any SQLExpression {
return SQLRaw("AUTO_INCREMENT")
}

Expand All @@ -42,7 +42,7 @@ public struct MySQLDialect: SQLDialect {
.inline
}

public func customDataType(for dataType: SQLDataType) -> SQLExpression? {
public func customDataType(for dataType: SQLDataType) -> (any SQLExpression)? {
switch dataType {
case .text:
return SQLRaw("VARCHAR(255)")
Expand All @@ -58,7 +58,7 @@ public struct MySQLDialect: SQLDialect {
)
}

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

public var sharedSelectLockExpression: SQLExpression? {
public var sharedSelectLockExpression: (any SQLExpression)? {
SQLRaw("LOCK IN SHARE MODE")
}

public var exclusiveSelectLockExpression: SQLExpression? {
public var exclusiveSelectLockExpression: (any SQLExpression)? {
SQLRaw("FOR UPDATE")
}

public func nestedSubpathExpression(in column: any SQLExpression, for path: [String]) -> (any SQLExpression)? {
guard !path.isEmpty else { return nil }

// N.B.: While MySQL has had the `->` and `->>` operators since 5.7.13, there are still implementations with
// which they do not work properly (most notably AWS's Aurora 2.x), so we use the legacy functions instead.
return SQLFunction("json_unquote", args:
SQLFunction("json_extract", args: [
column,
SQLLiteral.string("$.\(path.joined(separator: "."))")
]
))
}
}

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

0 comments on commit 5904a6f

Please sign in to comment.