Repository: novi/mysql-swift
Branch: master
Commit: 07574370c94c
Files: 42
Total size: 133.4 KB
Directory structure:
gitextract_zidf83u4/
├── .circleci/
│ └── config.yml
├── .gitignore
├── LICENSE.md
├── Package.swift
├── Package@swift-5.swift
├── README.md
├── Sources/
│ ├── MySQL/
│ │ ├── AutoincrementID.swift
│ │ ├── Blob.swift
│ │ ├── Connection.swift
│ │ ├── ConnectionPool.swift
│ │ ├── Date.swift
│ │ ├── Error.swift
│ │ ├── IDType.swift
│ │ ├── Query.swift
│ │ ├── QueryParameter-Data.swift
│ │ ├── QueryParameterType.swift
│ │ ├── RawRepresentableParameter.swift
│ │ ├── Result-SQLRawStringDecodable.swift
│ │ ├── Result.swift
│ │ ├── Sync.swift
│ │ └── Transaction.swift
│ ├── SQLFormatter/
│ │ ├── Error.swift
│ │ └── QueryFormatter.swift
│ └── cmysql/
│ ├── macos.pc
│ ├── module.modulemap
│ └── shim.h
└── Tests/
├── LinuxMain.swift
├── MySQLTests/
│ ├── BlobTests.swift
│ ├── ConnectionPoolTests.swift
│ ├── ConnectionTests.swift
│ ├── DateTests.swift
│ ├── EscapeTests.swift
│ ├── Model.swift
│ ├── MySQLConnection.swift
│ ├── QueryDecimalTypeTests.swift
│ ├── QueryFormatterTests.swift
│ ├── QueryParameterTests.swift
│ ├── QueryTests.swift
│ ├── QueryURLTypeTests.swift
│ └── XCTestManifests.swift
└── SQLFormatterTests/
├── SQLFormatterTests.swift
└── XCTestManifests.swift
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
mac-swift5.3:
macos:
xcode: "12.3.0"
steps:
- checkout
- run: brew tap novi/tap && brew install novi/tap/cmysqlmariadb
- run: mkdir -p /usr/local/etc/my.cnf.d /usr/local/var/mysql #workaround
- run: sh -c "mysqld_safe --skip-grant-tables & sleep 5"
- run: mysql -u root -e "create database IF NOT EXISTS test;"
- run: swift build
- run: swift test
mac-swift5.3-mysql:
macos:
xcode: "12.3.0"
steps:
- checkout
- run: brew tap novi/tap && brew install novi/tap/cmysql
- run: mkdir -p /usr/local/var/mysql/data && echo "datadir=/usr/local/var/mysql/data" >> /usr/local/etc/my.cnf && mysqld --initialize-insecure
- run: mysql.server start
- run: mysql -u root -e "create database IF NOT EXISTS test;"
- run: swift build
- run: swift test
linux-swift5.0:
docker:
- image: yusukeito/swift-basic:swift5.0
- image: mariadb:10.3
environment:
MYSQL_USER: root
MYSQL_DATABASE: "test"
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_ROOT_HOST: "%"
DATABASE_HOST: "%"
steps:
- checkout
- run: sleep 10 #wait for mysql
- run: swift build
- run: swift test
linux-swift5.1:
docker:
- image: yusukeito/swift-basic:swift5.1-cgrpc
- image: mariadb:10.3
environment:
MYSQL_USER: root
MYSQL_DATABASE: "test"
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_ROOT_HOST: "%"
DATABASE_HOST: "%"
steps:
- checkout
- run: sleep 10 #wait for mysql
- run: swift build
- run: swift test
linux-swift5.2:
docker:
- image: yusukeito/swift-basic:swift5.2-grpc-1
- image: mariadb:10.3
environment:
MYSQL_USER: root
MYSQL_DATABASE: "test"
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_ROOT_HOST: "%"
DATABASE_HOST: "%"
steps:
- checkout
- run: sleep 10 #wait for mysql
- run: swift build
- run: swift test
linux-swift5.3:
docker:
- image: yusukeito/swift-basic:swift5.3-grpc-1
- image: mariadb:10.3
environment:
MYSQL_USER: root
MYSQL_DATABASE: "test"
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_ROOT_HOST: "%"
DATABASE_HOST: "%"
steps:
- checkout
- run: sleep 10 #wait for mysql
- run: swift build
- run: swift test
workflows:
version: 2
build_and_test:
jobs:
- mac-swift5.3
- mac-swift5.3-mysql
- linux-swift5.0
- linux-swift5.1
- linux-swift5.2
- linux-swift5.3
================================================
FILE: .gitignore
================================================
# OS X Finder
.DS_Store
# Xcode per-user config
*.mode1
*.mode1v3
*.mode2v3
*.perspective
*.perspectivev3
*.pbxuser
#*.xcworkspace
xcuserdata
# Build products
build/
*.o
*.LinkFileList
*.hmap
# Automatic backup files
*~.nib/
*.swp
*~
*.dat
*.dep
docs/
Cartfile.resolved
Carthage/
.build/
Constants.swift
/*.xcodeproj
.swiftpm/
================================================
FILE: LICENSE.md
================================================
Copyright (c) 2015-18 Yusuke Ito
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Package.swift
================================================
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "MySQL",
products: [
.library(name: "MySQL", targets: ["MySQL"])
],
dependencies: [
.package(url: "https://github.com/novi/cmysql.git", from: "2.0.0"),
],
targets: [
.target(
name: "SQLFormatter"
),
.target(
name: "MySQL",
dependencies: [
"SQLFormatter",
]
),
.testTarget(
name: "MySQLTests",
dependencies: [
"MySQL"
]
),
.testTarget(
name: "SQLFormatterTests",
dependencies: [
"MySQL"
]
)
],
swiftLanguageVersions: [4]
)
================================================
FILE: Package@swift-5.swift
================================================
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "mysql-swift",
products: [
.library(name: "MySQL", targets: ["MySQL"])
],
targets: [
.systemLibrary(
name: "CMySQL",
path: "Sources/cmysql",
pkgConfig: "cmysql",
providers: [
.brew(["cmysql"]),
.apt(["libmysqlclient-dev"])
]
),
.target(
name: "SQLFormatter"
),
.target(
name: "MySQL",
dependencies: [
"CMySQL",
"SQLFormatter",
]
),
.testTarget(
name: "MySQLTests",
dependencies: [
"MySQL"
]
),
.testTarget(
name: "SQLFormatterTests",
dependencies: [
"MySQL"
]
)
]
)
================================================
FILE: README.md
================================================
mysql-swift
===========
**This library is obsolete and not maintained. Use [MySQLNIO](https://github.com/vapor/mysql-nio) instead.**

[](https://circleci.com/gh/novi/mysql-swift)
MySQL client library for Swift.
This is inspired by Node.js' [mysql](https://github.com/mysqljs/mysql).
* Based on libmysqlclient
* Raw SQL query
* Simple query formatting and escaping (same as Node's)
* Mapping queried results to `Codable` structs or classes
_Note:_ No asynchronous I/O support currently. It depends libmysqlclient.
```swift
// Declare a model
struct User: Codable, QueryParameter {
let id: Int
let userName: String
let age: Int?
let status: Status
let createdAt: Date
enum Status: String, Codable {
case created = "created"
case verified = "verified"
}
private enum CodingKeys: String, CodingKey {
case id
case userName = "user_name"
case age
case status = "status"
case createdAt = "created_at"
}
}
// Selecting
let nameParam = "some one"
let ids: [QueryParameter] = [1, 2, 3, 4, 5, 6]
let optionalInt: Int? = nil
let rows: [User] = try conn.query("SELECT id,user_name,status,status,created_at FROM `user` WHERE (age > ? OR age is ?) OR name = ? OR id IN (?)", [50, optionalInt, nameParam, QueryArray(ids)] ])
// Inserting
let age: Int? = 26
let user = User(id: 0, userName: "novi", age: age, status: .created, createdAt: Date())
let status = try conn.query("INSERT INTO `user` SET ?", [user]) as QueryStatus
let newId = status.insertedId
// Updating
let tableName = "user"
let defaultAge = 30
try conn.query("UPDATE ?? SET age = ? WHERE age is NULL;", [tableName, defaultAge])
```
# Requirements
* Swift 5.0 or later
* MariaDB or MySQL Connector/C (libmysqlclient) 2.2.3 or later
## macOS
Install pkg-config `.pc` file in [cmysql](https://github.com/vapor-community/cmysql) or [cmysql-mariadb](https://github.com/novi/cmysql-mariadb/tree/mariadb).
```sh
# cmysql
$ brew tap novi/tap
$ brew install novi/tap/cmysql
# cmysql-mariadb
$ brew tap novi/tap
$ brew install novi/tap/cmysqlmariadb
```
## Ubuntu
* Install `libmariadbclient`
* Follow [Setting up MariaDB Repositories](https://downloads.mariadb.org/mariadb/repositories/#mirror=yamagata-university) and set up your repository.
```sh
$ sudo apt-get install libmariadbclient-dev
```
# Installation
## Swift Package Manager
* Add `mysql-swift` to `Package.swift` of your project.
```swift
// swift-tools-version:5.2
import PackageDescription
let package = Package(
...,
dependencies: [
.package(url: "https://github.com/novi/mysql-swift.git", .upToNextMajor(from: "0.9.0"))
],
targets: [
.target(
name: "YourAppOrLibrary",
dependencies: [
// add a dependency
.product(name: "MySQL", package: "mysql-swift")
]
)
]
)
```
# Usage
## Connection & Querying
1. Create a pool with options (hostname, port, password,...).
2. Use `ConnectionPool.execute()`. It automatically get and release a connection.
```swift
let option = Option(host: "your.mysql.host"...) // Define and create your option type
let pool = ConnectionPool(option: option) // Create a pool with the option
let rows: [User] = try pool.execute { conn in
// The connection `conn` is held in this block
try conn.query("SELECT * FROM users;") // And it returns result to outside execute block
}
```
## Transaction
```swift
let wholeStaus: QueryStatus = try pool.transaction { conn in
let status = try conn.query("INSERT INTO users SET ?;", [user]) as QueryStatus // Create a user
let userId = status.insertedId // the user's id
try conn.query("UPDATE info SET some_value = ? WHERE some_key = 'latest_user_id' ", [userId]) // Store user's id that we have created the above
}
wholeStaus.affectedRows == 1 // true
```
# License
MIT
================================================
FILE: Sources/MySQL/AutoincrementID.swift
================================================
//
// AutoincrementID.swift
// MySQL
//
// Created by Yusuke Ito on 6/27/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import SQLFormatter
public enum AutoincrementID<I: IDType> {
case noID
case ID(I)
public var id: I {
switch self {
case .noID: fatalError("\(self) has no ID")
case .ID(let id): return id
}
}
}
extension AutoincrementID: Equatable {
static public func ==<I>(lhs: AutoincrementID<I>, rhs: AutoincrementID<I>) -> Bool {
switch (lhs, rhs) {
case (.noID, .noID): return true
case (.ID(let lhs), .ID(let rhs) ): return lhs.id == rhs.id
default: return false
}
}
}
extension AutoincrementID: CustomStringConvertible {
// where I: CustomStringConvertible
// is not supported yet
public var description: String {
switch self {
case .noID: return "noID"
case .ID(let id):
if let idVal = id as? CustomStringConvertible {
return idVal.description
} else {
return "\(id)"
}
}
}
}
extension AutoincrementID {
public init(_ id: I) {
self = .ID(id)
}
}
/*
extension AutoincrementID where I: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> AutoincrementID<I> {
return AutoincrementID(try I.fromSQLValue(string: string))
}
}*/
extension AutoincrementID: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
switch self {
case .noID: return ""
case .ID(let id): return try id.queryParameter(option: option)
}
}
public var omitOnQueryParameter: Bool {
switch self {
case .noID: return true
case .ID: return false
}
}
}
/// MARK: Codable support
extension AutoincrementID: Decodable where I: Decodable {
public init(from decoder: Decoder) throws {
self = .ID(try I(from: decoder))
}
}
extension AutoincrementID: Encodable where I: Encodable {
public func encode(to encoder: Encoder) throws {
switch self {
case .noID:
break // nothing to encode
case .ID(let id):
try id.encode(to: encoder)
}
}
}
================================================
FILE: Sources/MySQL/Blob.swift
================================================
//
// Blob.swift
// MySQL
//
// Created by Yusuke Ito on 4/22/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import Foundation
import SQLFormatter
extension Data: SQLRawStringDecodable {
public static func fromSQLValue(string: String) throws -> Data {
fatalError("logic error, construct via init(:)")
}
}
fileprivate let HexTable: [Character] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]
extension Data: QueryParameterType {
public func escaped() -> String {
var buffer = [Character](["x", "'"])
buffer.reserveCapacity( self.count * 2 + 3 ) // 3 stands for "x''", 2 stands for 2 characters per a byte data
for byte in self {
buffer.append(HexTable[Int((byte >> 4) & 0x0f)])
buffer.append(HexTable[Int(byte & 0x0f)])
}
buffer.append("'")
return String(buffer)
}
public func escapedForID() -> String? {
return nil // Data can not be used for ID(?? placeholder).
}
}
extension Data: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return self
}
}
internal struct Blob: QueryParameter {
let data: Data
let dataType: QueryCustomDataParameterDataType
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return self
}
}
extension Blob: QueryParameterType {
public func escaped() -> String {
switch dataType {
case .blob: return data.escaped()
case .json:
return "CONVERT(" + data.escaped() + " using utf8mb4)"
}
}
public func escapedForID() -> String? {
return nil // Data can not be used for ID(?? placeholder).
}
}
================================================
FILE: Sources/MySQL/Connection.swift
================================================
//
// Database.swift
// MySQL
//
// Created by ito on 2015/10/24.
// Copyright © 2015年 Yusuke Ito. All rights reserved.
//
import CMySQL
import CoreFoundation
import Foundation
internal struct MySQLUtil {
internal static func getMySQLError(_ mysql: UnsafeMutablePointer<MYSQL>) -> String {
guard let strPtr = mysql_error(mysql) else {
return "unknown error. could not get error with `mysql_error()`."
}
guard let errorString = String(validatingUTF8: strPtr) else {
return "unknown error. could not get error as string."
}
return errorString
}
}
public protocol ConnectionOption {
var host: String { get }
var port: Int { get }
var user: String { get }
var password: String { get }
var database: String { get }
var timeZone: TimeZone { get }
var encoding: Connection.Encoding { get }
var timeout: Int { get }
var reconnect: Bool { get }
var omitDetailsOnError: Bool { get }
}
fileprivate let defaultTimeZone = TimeZone(identifier: "UTC")!
public extension ConnectionOption {
// Provide default options
var timeZone: TimeZone {
return defaultTimeZone
}
var encoding: Connection.Encoding {
return .UTF8MB4
}
var timeout: Int {
return 10
}
var reconnect: Bool {
return true
}
var omitDetailsOnError: Bool {
return true
}
}
extension Connection {
public enum Encoding: String {
case UTF8 = "utf8"
case UTF8MB4 = "utf8mb4"
}
}
public enum ConnectionError: Error {
case connectionError(String)
case connectionPoolGetConnectionTimeoutError
}
public final class Connection {
internal var isInUse: Bool = false
private var mysql_: UnsafeMutablePointer<MYSQL>?
internal let pool: ConnectionPool
public let option: ConnectionOption
internal init(option: ConnectionOption, pool: ConnectionPool) {
self.option = option
self.pool = pool
self.mysql_ = nil
}
internal func release() {
pool.releaseConnection(self)
}
internal static func setReconnect(_ reconnect: Bool, mysql: UnsafeMutablePointer<MYSQL>) {
let reconnectPtr = UnsafeMutablePointer<my_bool>.allocate(capacity: 1)
reconnectPtr.pointee = reconnect == false ? 0 : 1
mysql_options(mysql, MYSQL_OPT_RECONNECT, reconnectPtr)
reconnectPtr.deallocate()
}
func setReconnect(_ reconnect: Bool) {
if let mysql = mysql {
Connection.setReconnect(reconnect, mysql: mysql)
}
}
internal func connect() throws -> UnsafeMutablePointer<MYSQL> {
dispose()
guard let mysql = mysql_init(nil) else {
fatalError("mysql_init() failed.")
}
do {
let timeoutPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
timeoutPtr.pointee = option.timeout
mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, timeoutPtr)
timeoutPtr.deallocate()
}
Connection.setReconnect(option.reconnect, mysql: mysql)
if mysql_real_connect(mysql,
option.host,
option.user,
option.password,
option.database,
UInt32(option.port), nil, 0) == nil {
// error
throw ConnectionError.connectionError(MySQLUtil.getMySQLError(mysql))
}
mysql_set_character_set(mysql, option.encoding.rawValue)
self.mysql_ = mysql
return mysql
}
internal func connectIfNeeded() throws -> UnsafeMutablePointer<MYSQL> {
guard let mysql = self.mysql_ else {
return try connect()
}
return mysql
}
private var mysql: UnsafeMutablePointer<MYSQL>? {
guard mysql_ != nil else {
return nil
}
return mysql_
}
internal func ping() -> Bool {
_ = try? connectIfNeeded()
guard let mysql = mysql else {
return false
}
return mysql_ping(mysql) == 0
}
private func dispose() {
guard let mysql = mysql else {
return
}
mysql_close(mysql)
self.mysql_ = nil
}
deinit {
dispose()
}
}
================================================
FILE: Sources/MySQL/ConnectionPool.swift
================================================
//
// ConnectionPool.swift
// MySQL
//
// Created by ito on 12/24/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import Dispatch
#if os(Linux)
import Glibc
#endif
import CMySQL
fileprivate var LibraryInitialized: Atomic<Bool> = Atomic(false)
fileprivate func InitializeMySQLLibrary() {
LibraryInitialized.syncWriting {
guard $0 == false else {
return
}
if mysql_server_init(0, nil, nil) != 0 { // mysql_library_init
fatalError("could not initialize MySQL library with `mysql_server_init`.")
}
$0 = true
}
}
extension Array where Element == Connection {
mutating func preparedNewConnection(option: ConnectionOption, pool: ConnectionPool) -> Connection {
let newConn = Connection(option: option, pool: pool)
_ = try? newConn.connect()
append(newConn)
return newConn
}
func getUsableConnection() -> Connection? {
for c in self {
if c.isInUse == false && c.ping() {
c.isInUse = true
return c
}
}
return nil
}
internal var inUseConnections: Int {
var count: Int = 0
for c in self {
if c.isInUse {
count += 1
}
}
return count
}
}
final public class ConnectionPool: CustomStringConvertible {
private var initialConnections_: Atomic<Int> = Atomic(1)
public var initialConnections: Int {
get {
return initialConnections_.sync { $0 }
}
set {
initialConnections_.syncWriting {
$0 = newValue
}
pool.syncWriting {
while $0.count < newValue {
_ = $0.preparedNewConnection(option: self.option, pool: self)
}
}
}
}
public var maxConnections: Int {
get {
return maxConnections_.sync { $0 }
}
set {
maxConnections_.syncWriting {
$0 = newValue
}
}
}
private var maxConnections_: Atomic<Int> = Atomic(10)
internal private(set) var pool: Atomic<[Connection]> = Atomic([])
@available(*, deprecated, renamed: "option")
public var options: ConnectionOption {
return option
}
public let option: ConnectionOption
@available(*, deprecated, renamed: "init(option:)")
public convenience init(options: ConnectionOption) {
self.init(option: options)
}
public init(option: ConnectionOption) {
self.option = option
InitializeMySQLLibrary()
for _ in 0..<initialConnections {
pool.syncWriting {
_ = $0.preparedNewConnection(option: option, pool: self)
}
}
}
public var timeoutForGetConnection: Int {
get {
return timeoutForGetConnection_.sync { $0 }
}
set {
timeoutForGetConnection_.syncWriting {
$0 = newValue
}
}
}
private var timeoutForGetConnection_: Atomic<Int> = Atomic(60)
internal func getConnection() throws -> Connection {
var connection: Connection? =
pool.syncWriting {
if let conn = $0.getUsableConnection() {
return conn
}
if $0.count < maxConnections {
let conn = $0.preparedNewConnection(option: option, pool: self)
conn.isInUse = true
return conn
}
return nil
}
if let conn = connection {
return conn
}
let tickInMs = 50 // ms
var timeoutCount = (timeoutForGetConnection*1000)/tickInMs
while timeoutCount > 0 {
usleep(useconds_t(1000*tickInMs))
connection = pool.sync {
$0.getUsableConnection()
}
if connection != nil {
break
}
timeoutCount -= 1
}
guard let conn = connection else {
throw ConnectionError.connectionPoolGetConnectionTimeoutError
}
return conn
}
internal func releaseConnection(_ conn: Connection) {
pool.sync { _ in
conn.isInUse = false
}
}
public var description: String {
let inUseConnections = pool.sync {
$0.inUseConnections
}
return "connections:\n\tinitial:\(initialConnections), max:\(maxConnections), in-use:\(inUseConnections)"
}
}
extension ConnectionPool {
public func execute<T>( _ block: (_ conn: Connection) throws -> T ) throws -> T {
let conn = try getConnection()
defer {
releaseConnection(conn)
}
return try block(conn)
}
}
================================================
FILE: Sources/MySQL/Date.swift
================================================
//
// Date.swift
// MySQL
//
// Created by ito on 12/16/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import Foundation
import SQLFormatter
internal final class SQLDateCalendar {
private static var calendars: Atomic<[TimeZone:Calendar]> = Atomic([:])
internal static func calendar<T>(forTimezone timeZone: TimeZone, _ block: (_ calendar: Calendar) -> T) -> T {
return calendars.syncWriting {
if let calendar = $0[timeZone] {
return block(calendar)
}
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
$0[timeZone] = calendar
return block(calendar)
}
}
}
fileprivate func pad(num: Int, digits: Int = 2) -> String {
var str = String(num)
if num < 0 {
return str
}
while str.count < digits {
str = "0" + str
}
return str
}
extension Date {
internal init(sqlDate: String, timeZone: TimeZone) throws {
switch sqlDate.count {
case 19:
let chars: [Character] = Array(sqlDate)
if let year = Int(String(chars[0...3])),
let month = Int(String(chars[5...6])),
let day = Int(String(chars[8...9])),
let hour = Int(String(chars[11...12])),
let minute = Int(String(chars[14...15])),
let second = Int(String(chars[17...18])), year > 0 && day > 0 && month > 0 {
var comps = DateComponents()
comps.year = year
comps.month = month
comps.day = day
comps.hour = hour
comps.minute = minute
comps.second = second
let parsedDate: Date? = SQLDateCalendar.calendar(forTimezone: timeZone) { calendar in
calendar.date(from :comps)
}
if let date = parsedDate {
self = date
return
}
}
default: break
}
throw QueryError.SQLDateStringError(sqlDate)
}
}
extension Date: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
let comp: DateComponents = SQLDateCalendar.calendar(forTimezone: option.timeZone) { calendar in
calendar.dateComponents([ .year, .month, .day, .hour, .minute, .second], from: self)
}
// YYYY-MM-DD HH:MM:SS
return EscapedQueryParameter( "'\(pad(num: comp.year ?? 0, digits: 4))-\(pad(num: comp.month ?? 0))-\(pad(num: comp.day ?? 0)) \(pad(num: comp.hour ?? 0)):\(pad(num: comp.minute ?? 0)):\(pad(num: comp.second ?? 0))'" )
}
}
fileprivate func nanosecondsToString(_ nanosec: Int) -> String {
let nanosecSecond = Double(nanosec % 1_000_000_000)/1_000_000_000.0
var nanosecStr = String(format: "%.6f", nanosecSecond)
nanosecStr.removeFirst()
return String(nanosecStr)
}
extension DateComponents: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
if let year = self.year, let month = self.month, let day = self.day, let hour = self.hour, let minute = self.minute, let second = self.second {
var string = "'\(pad(num: year, digits: 4))-\(pad(num: month))-\(pad(num: day)) \(pad(num: hour)):\(pad(num: minute)):\(pad(num: second))"
if let nanosec = self.nanosecond {
string += nanosecondsToString(nanosec)
}
return EscapedQueryParameter(string + "'")
}
if let hour = self.hour, let minute = self.minute, let second = self.second {
var string = "'\(pad(num: hour)):\(pad(num: minute)):\(pad(num: second))"
if let nanosec = self.nanosecond {
string += nanosecondsToString(nanosec)
}
return EscapedQueryParameter(string + "'")
}
if let year = self.year {
return EscapedQueryParameter("'\(pad(num: year))'")
}
throw QueryParameterError.dateComponentsError(self.description)
}
}
fileprivate let DateTimeRegex: NSRegularExpression = {
return try! NSRegularExpression(pattern: "^(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})\\.?(\\d*)$", options: [])
}()
fileprivate let TimeRegex: NSRegularExpression = {
return try! NSRegularExpression(pattern: "^(\\-?\\d{1,3}):(\\d{2}):(\\d{2})\\.?(\\d*)$", options: [])
}()
fileprivate let DateRegex: NSRegularExpression = {
return try! NSRegularExpression(pattern: "^(\\d{4})-(\\d{2})-(\\d{2})$", options: [])
}()
fileprivate func stringToNanoseconds<S: StringProtocol>(_ string: S) -> Int? {
guard string.count > 0 else {
return nil
}
guard let doubleValue = Double("0.\(string)") else {
return nil
}
return Int(doubleValue * 1_000_000_000.0)
}
extension DateComponents: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> DateComponents {
if string.count == 4 {
// YEAR type
return DateComponents(year: Int(string))
}
let wholeRange = NSRange(string.startIndex..<string.endIndex, in: string)
// count of the string will be at least 19...
// "2000-01-23 01:23:45"
if string.count >= 19, let match = DateTimeRegex.firstMatch(in: string, options: [], range: wholeRange) {
let year = Int(string[Range(match.range(at: 1), in: string)!])
let month = Int(string[Range(match.range(at: 2), in: string)!])
let day = Int(string[Range(match.range(at: 3), in: string)!])
let hour = Int(string[Range(match.range(at: 4), in: string)!])
let minute = Int(string[Range(match.range(at: 5), in: string)!])
let second = Int(string[Range(match.range(at: 6), in: string)!])
let nanosecond = String(string[Range(match.range(at: 7), in: string)!])
return DateComponents(
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
nanosecond: stringToNanoseconds(nanosecond)
)
}
// "1:23:45"
if string.count >= 7, let match = TimeRegex.firstMatch(in: string, options: [], range: wholeRange) {
let hour = Int(string[Range(match.range(at: 1), in: string)!])
let minute = Int(string[Range(match.range(at: 2), in: string)!])
let second = Int(string[Range(match.range(at: 3), in: string)!])
let nanosecond = string[Range(match.range(at: 4), in: string)!]
return DateComponents(
hour: hour,
minute: minute,
second: second,
nanosecond: stringToNanoseconds(nanosecond)
)
}
// "2000-01-23"
if string.count == 10, let match = DateRegex.firstMatch(in: string, options: [], range: wholeRange) {
let year = Int(string[Range(match.range(at: 1), in: string)!])
let month = Int(string[Range(match.range(at: 2), in: string)!])
let day = Int(string[Range(match.range(at: 3), in: string)!])
return DateComponents(
year: year,
month: month,
day: day
)
}
throw QueryError.SQLDateStringError(string)
}
}
================================================
FILE: Sources/MySQL/Error.swift
================================================
//
// Error.swift
// MySQL
//
// Created by Yusuke Ito on 12/14/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
public enum QueryError: Error {
case queryExecutionError(message: String, query: String)
case resultFetchError(message: String, query: String)
case resultNoFieldError(query: String)
case resultRowFetchError(query: String)
case resultFieldFetchError(query: String)
case resultParseError(message: String, result: String)
case resultCastError(actualValue: String, expectedType: String, forField: String)
case resultDecodeError(rawSQLValue: String, forType: Any)
case resultDecodeErrorMessage(message: String)
case SQLDateStringError(String)
case SQLRawStringDecodeError(error: Error, actualValue: String, expectedType: String, forField: String)
case missingField(String)
}
public enum QueryParameterError: Error {
case dateComponentsError(String)
}
================================================
FILE: Sources/MySQL/IDType.swift
================================================
//
// IDType.swift
// MySQL
//
// Created by Yusuke Ito on 6/27/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import SQLFormatter
public protocol IDType: QueryParameter, Hashable, Codable {
associatedtype T: QueryParameter, Hashable, Codable
var id: T { get }
init(_ id: T)
}
public extension IDType {
func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return try id.queryParameter(option: option)
}
#if swift(>=4.2)
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
#else
var hashValue: Int {
return id.hashValue
}
#endif
}
extension IDType where Self: SQLRawStringDecodable, Self.T: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Self {
return Self(try T.fromSQLValue(string: string))
}
}
extension IDType {
public static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
}
// MARK: Codable type
extension IDType {
public init(from decoder: Decoder) throws {
if T.self == Int.self {
self.init(try decoder.singleValueContainer().decode(Int.self) as! T)
} else if T.self == Int64.self {
self.init(try decoder.singleValueContainer().decode(Int64.self) as! T)
} else if T.self == UInt.self {
self.init(try decoder.singleValueContainer().decode(UInt.self) as! T)
} else if T.self == UInt64.self {
self.init(try decoder.singleValueContainer().decode(UInt64.self) as! T)
} else if T.self == String.self {
self.init(try decoder.singleValueContainer().decode(String.self) as! T)
} else {
fatalError("`init(from:)` of \(Self.self) is not implemented")
}
}
public func encode(to encoder: Encoder) throws {
if T.self == Int.self {
var container = encoder.singleValueContainer()
try container.encode(id as! Int)
} else if T.self == Int64.self {
var container = encoder.singleValueContainer()
try container.encode(id as! Int64)
} else if T.self == UInt.self {
var container = encoder.singleValueContainer()
try container.encode(id as! UInt)
} else if T.self == UInt64.self {
var container = encoder.singleValueContainer()
try container.encode(id as! UInt64)
} else if T.self == String.self {
var container = encoder.singleValueContainer()
try container.encode(id as! String)
} else {
fatalError("`encode(to:)` of \(Self.self) is not implemented")
}
}
}
// TODO: this implementation does not work in release build, Swift 4.1
/*
extension IDType {
public init(from decoder: Decoder) throws {
fatalError("`init(from:)` of \(Self.self) is not implemented")
}
public func encode(to encoder: Encoder) throws {
fatalError("`encode(to:)` of \(Self.self) is not implemented")
}
}
extension IDType where T == Int {
public init(from decoder: Decoder) throws {
self.init(try decoder.singleValueContainer().decode(Int.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
extension IDType where T == Int64 {
public init(from decoder: Decoder) throws {
self.init(try decoder.singleValueContainer().decode(Int64.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
extension IDType where T == UInt {
public init(from decoder: Decoder) throws {
self.init(try decoder.singleValueContainer().decode(UInt.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
extension IDType where T == UInt64 {
public init(from decoder: Decoder) throws {
self.init(try decoder.singleValueContainer().decode(UInt64.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
extension IDType where T == String {
public init(from decoder: Decoder) throws {
self.init(try decoder.singleValueContainer().decode(String.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
}
}
*/
================================================
FILE: Sources/MySQL/Query.swift
================================================
//
// Connection.swift
// MySQL
//
// Created by ito on 2015/10/24.
// Copyright © 2015年 Yusuke Ito. All rights reserved.
//
import CMySQL
import SQLFormatter
import Foundation
public struct QueryStatus: CustomStringConvertible {
public let affectedRows: UInt64?
public let insertedID: UInt64
init(mysql: UnsafeMutablePointer<MYSQL>) {
self.insertedID = mysql_insert_id(mysql)
let arows = mysql_affected_rows(mysql)
if arows == (~0) {
self.affectedRows = nil // error or select statement
} else {
self.affectedRows = arows
}
}
public var description: String {
return "insertedID:\(insertedID), affectedRows:" + (affectedRows != nil ? ("\(affectedRows!)") : "nil")
}
}
internal extension String {
func subString(max: Int) -> String {
guard let r = index(startIndex, offsetBy: max, limitedBy: endIndex) else {
return self
}
return String(self[startIndex..<r])
}
}
extension Connection {
internal struct NullValue {
static let null = NullValue()
}
internal struct EmptyRowResult: Decodable {
static func decodeRow(r: QueryRowResult) throws -> EmptyRowResult {
return EmptyRowResult()
}
}
internal struct Field {
let name: String
let type: enum_field_types
init?(f: MYSQL_FIELD) {
if f.name == nil {
return nil
}
guard let fs = String(validatingUTF8: f.name) else {
return nil
}
self.name = fs
self.type = f.type
}
var isDate: Bool {
return type == MYSQL_TYPE_DATE ||
type == MYSQL_TYPE_DATETIME ||
type == MYSQL_TYPE_TIME ||
type == MYSQL_TYPE_TIMESTAMP
}
}
internal enum FieldValue {
case null
case binary(Data)
case date(dateString: String, timezone: TimeZone)
static func makeBinary(ptr: UnsafeMutablePointer<Int8>, length: UInt) -> FieldValue {
let data = Data(bytes: UnsafeRawPointer(ptr), count: Int(length))
return FieldValue.binary(data)
}
func string() throws -> String {
switch self {
case .null:
throw QueryError.resultParseError(message: "the field is not string.", result: "null")
case .date(let string, _):
return string
case .binary(let data):
guard let string = String(data: data, encoding: .utf8) else {
throw QueryError.resultParseError(message: "invalid utf8 string bytes.", result: "")
}
return string
}
}
}
fileprivate func query<T: Decodable>(query formattedQuery: String, option: QueryParameterOption) throws -> ([T], QueryStatus) {
let (rows, status) = try self.query(query: formattedQuery, option: option)
return try (rows.map({ try T(from: QueryRowResultDecoder(row: $0))}), status)
}
fileprivate func query(query formattedQuery: String, option: QueryParameterOption) throws -> ([QueryRowResult], QueryStatus) {
let mysql = try connectIfNeeded()
func queryPrefix() -> String {
if self.option.omitDetailsOnError {
return ""
}
return formattedQuery.subString(max: 1000)
}
guard mysql_real_query(mysql, formattedQuery, UInt(formattedQuery.utf8.count)) == 0 else {
throw QueryError.queryExecutionError(message: MySQLUtil.getMySQLError(mysql), query: queryPrefix())
}
let status = QueryStatus(mysql: mysql)
let res = mysql_use_result(mysql)
guard res != nil else {
if mysql_field_count(mysql) == 0 {
// actual no result
return ([], status)
}
throw QueryError.resultFetchError(message: MySQLUtil.getMySQLError(mysql), query: queryPrefix())
}
defer {
mysql_free_result(res)
}
let fieldCount = Int(mysql_num_fields(res))
guard fieldCount > 0 else {
throw QueryError.resultNoFieldError(query: queryPrefix())
}
// fetch field info
guard let fieldDef = mysql_fetch_fields(res) else {
throw QueryError.resultFieldFetchError(query: queryPrefix())
}
var fields:[Field] = []
for i in 0..<fieldCount {
guard let f = Field(f: fieldDef[i]) else {
throw QueryError.resultFieldFetchError(query: queryPrefix())
}
fields.append(f)
}
// fetch rows
var rows:[QueryRowResult] = []
var rowCount: Int = 0
while true {
guard let row = mysql_fetch_row(res) else {
break // end of rows
}
guard let lengths = mysql_fetch_lengths(res) else {
throw QueryError.resultRowFetchError(query: queryPrefix())
}
var fieldValues: [FieldValue] = []
for i in 0..<fieldCount {
let field = fields[i]
if let valf = row[i], row[i] != nil {
let binary = FieldValue.makeBinary(ptr: valf, length: lengths[i])
if field.isDate {
fieldValues.append(FieldValue.date(dateString: try binary.string(), timezone: option.timeZone))
} else {
fieldValues.append(binary)
}
} else {
fieldValues.append(FieldValue.null)
}
}
rowCount += 1
if fields.count != fieldValues.count {
throw QueryError.resultParseError(message: "invalid fetched column count", result: "")
}
rows.append(QueryRowResult(fields: fields, fieldValues: fieldValues))
}
return (rows, status)
}
}
fileprivate struct QueryParameterDefaultOption: QueryParameterOption {
let timeZone: TimeZone
}
extension Connection {
internal static func buildParameters(_ params: [QueryParameter], option: QueryParameterOption) throws -> [QueryParameterType] {
return try params.map { try $0.queryParameter(option: option) }
}
public func query<R: Decodable>(_ query: String, _ params: [QueryParameter] = []) throws -> ([R], QueryStatus) {
let option = QueryParameterDefaultOption(
timeZone: self.option.timeZone
)
let queryString = try QueryFormatter.format(query: query, parameters: type(of: self).buildParameters(params, option: option))
return try self.query(query: queryString, option: option)
}
public func query<R: Decodable>(_ query: String, _ params: [QueryParameter] = [], option: QueryParameterOption) throws -> ([R], QueryStatus) {
let queryString = try QueryFormatter.format(query: query, parameters: type(of: self).buildParameters(params, option: option))
return try self.query(query: queryString, option: option)
}
public func query<R: Decodable>(_ query: String, _ params: [QueryParameter] = []) throws -> [R] {
let (rows, _) = try self.query(query, params) as ([R], QueryStatus)
return rows
}
public func query<R: Decodable>(_ query: String, _ params: [QueryParameter] = [], option: QueryParameterOption) throws -> [R] {
let (rows, _) = try self.query(query, params, option: option) as ([R], QueryStatus)
return rows
}
public func query(_ query: String, _ params: [QueryParameter] = []) throws -> QueryStatus {
let (_, status) = try self.query(query, params) as ([EmptyRowResult], QueryStatus)
return status
}
public func query(_ query: String, _ params: [QueryParameter] = [], option: QueryParameterOption) throws -> QueryStatus {
let (_, status) = try self.query(query, params, option: option) as ([EmptyRowResult], QueryStatus)
return status
}
}
================================================
FILE: Sources/MySQL/QueryParameter-Data.swift
================================================
//
// QueryParameter-Data.swift
// MySQL
//
// Created by Yusuke Ito on 4/29/18.
//
import Foundation
public protocol QueryRowResultCustomData {
static func decode(fromRowData data: Data) throws -> Self
}
public enum QueryCustomDataParameterDataType {
case blob
case json
}
public protocol QueryCustomDataParameter {
func encodeForQueryParameter() throws -> Data
var queryParameterDataType: QueryCustomDataParameterDataType { get }
}
public extension QueryCustomDataParameter {
var queryParameterDataType: QueryCustomDataParameterDataType {
return .blob
}
}
================================================
FILE: Sources/MySQL/QueryParameterType.swift
================================================
//
// QueryParameterType.swift
// MySQL
//
// Created by Yusuke Ito on 12/28/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import SQLFormatter
import Foundation
public protocol QueryParameter {
func queryParameter(option: QueryParameterOption) throws -> QueryParameterType
var omitOnQueryParameter: Bool { get }
}
public extension QueryParameter {
var omitOnQueryParameter: Bool {
return false
}
}
public protocol QueryParameterOption {
var timeZone: TimeZone { get }
}
internal struct QueryParameterNull: QueryParameter {
private init() {
}
static let null = QueryParameterNull()
func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( "NULL" )
}
}
@available(*, renamed: "QueryParameterDictionary")
typealias QueryDictionary = QueryParameterDictionary
public struct QueryParameterDictionary: QueryParameter {
private let dict: [String: QueryParameter?]
public init(_ dict: [String: QueryParameter?]) {
self.dict = dict
}
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
var keyVals: [String] = []
for (k, v) in dict {
if v == nil || v?.omitOnQueryParameter == false {
keyVals.append("\(SQLString.escapeForID(k)) = \(try (v ?? QueryParameterNull.null).queryParameter(option: option).escaped())")
}
}
return EscapedQueryParameter( keyVals.joined(separator: ", ") )
}
}
fileprivate protocol QueryParameterArrayType: QueryParameter {
}
@available(*, renamed: "QueryParameterArray")
typealias QueryArray = QueryParameterArray
public struct QueryParameterArray: QueryParameter, QueryParameterArrayType {
private let arr: [QueryParameter?]
public init(_ arr: [QueryParameter?]) {
self.arr = arr
}
public init(_ arr: [QueryParameter]) {
self.arr = arr.map { Optional($0) }
}
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return EscapedQueryParameter( try arr.filter({ val in
if let valid = val {
return valid.omitOnQueryParameter == false
}
return true
}).map({
if let val = $0 as? QueryParameterArrayType {
return "(" + (try val.queryParameter(option: option).escaped()) + ")"
}
return try ($0 ?? QueryParameterNull.null).queryParameter(option: option).escaped()
}).joined(separator: ", ") )
}
}
extension Optional: QueryParameter where Wrapped: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
switch self {
case .none:
return QueryParameterNull.null.queryParameter(option: option)
case .some(let value):
return try value.queryParameter(option: option)
}
}
public var omitOnQueryParameter: Bool {
switch self {
case .none:
return false
case .some(let value):
return value.omitOnQueryParameter
}
}
}
internal struct EscapedQueryParameter: QueryParameterType {
private let value: String
private let idParameter: String?
init(_ val: String, idParameter: String? = nil) {
self.value = val
self.idParameter = idParameter
}
func escaped() -> String {
return value
}
func escapedForID() -> String? {
return idParameter
}
}
extension String: QueryParameterType {
public func escaped() -> String {
return SQLString.escape(self)
}
public func escapedForID() -> String? {
return SQLString.escapeForID(self)
}
}
extension String: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return self
}
}
extension Int: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension UInt: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Int64: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Int32: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Int16: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Int8: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension UInt64: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension UInt32: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension UInt16: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension UInt8: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Double: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Float: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(self) )
}
}
extension Bool: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( self ? "true" : "false" )
}
}
extension Decimal: QueryParameter {
public func queryParameter(option: QueryParameterOption) -> QueryParameterType {
return EscapedQueryParameter( String(describing: self) )
}
}
/// MARK: Codable support
fileprivate final class QueryParameterEncoder: Encoder {
let codingPath = [CodingKey]()
let userInfo = [CodingUserInfoKey : Any]()
var dict: [String: QueryParameter?] = [:]
var singleValue: QueryParameter? = nil
enum StorageType {
case single
case dictionary
}
var storageType: StorageType = .dictionary
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
return KeyedEncodingContainer(QueryParameterKeyedEncodingContainer<Key>(encoder: self))
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
fatalError("not supported unkeyedContainer in QueryParameter")
}
func singleValueContainer() -> SingleValueEncodingContainer {
self.storageType = .single
return QueryParameterSingleValueEncodingContainer(encoder: self)
}
}
fileprivate struct QueryParameterSingleValueEncodingContainer: SingleValueEncodingContainer {
let codingPath = [CodingKey]()
var encoder: QueryParameterEncoder
mutating func encodeNil() throws {
encoder.singleValue = nil
}
mutating func encode(_ value: Bool) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Int) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Int8) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Int16) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Int32) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Int64) throws {
encoder.singleValue = value
}
mutating func encode(_ value: UInt) throws {
encoder.singleValue = value
}
mutating func encode(_ value: UInt8) throws {
encoder.singleValue = value
}
mutating func encode(_ value: UInt16) throws {
encoder.singleValue = value
}
mutating func encode(_ value: UInt32) throws {
encoder.singleValue = value
}
mutating func encode(_ value: UInt64) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Float) throws {
encoder.singleValue = value
}
mutating func encode(_ value: Double) throws {
encoder.singleValue = value
}
mutating func encode(_ value: String) throws {
encoder.singleValue = value
}
mutating func encode<T>(_ value: T) throws where T : Encodable {
encoder.singleValue = value as? QueryParameter
}
}
fileprivate struct QueryParameterKeyedEncodingContainer<Key : CodingKey> : KeyedEncodingContainerProtocol {
fileprivate let codingPath = [CodingKey]()
fileprivate let encoder: QueryParameterEncoder
mutating func encodeNil(forKey key: Key) throws {
encoder.dict[key.stringValue] = nil
}
mutating func encode(_ value: Bool, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Int, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Int8, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Int16, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Int32, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Int64, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: UInt, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: UInt8, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: UInt16, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: UInt32, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: UInt64, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Float, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: Double, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode(_ value: String, forKey key: Key) throws {
encoder.dict[key.stringValue] = value
}
mutating func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
if T.self == Date.self {
encoder.dict[key.stringValue] = value as! Date
} else if T.self == DateComponents.self {
encoder.dict[key.stringValue] = value as! DateComponents
} else if T.self == Data.self {
encoder.dict[key.stringValue] = value as! Data
} else if T.self == URL.self {
// encode absoluteString same as JSONEncoder
// https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/JSONEncoder.swift
encoder.dict[key.stringValue] = (value as! URL).absoluteString
} else if T.self == Decimal.self {
encoder.dict[key.stringValue] = (value as! Decimal).description
} else if let custom = value as? QueryCustomDataParameter {
if let param = value as? QueryParameter {
if !param.omitOnQueryParameter {
let data = try custom.encodeForQueryParameter()
encoder.dict[key.stringValue] = Blob(data: data, dataType: custom.queryParameterDataType)
}
} else {
let data = try custom.encodeForQueryParameter()
encoder.dict[key.stringValue] = Blob(data: data, dataType: custom.queryParameterDataType)
}
} else {
let singleValueEncoder = QueryParameterEncoder()
try value.encode(to: singleValueEncoder)
if let param = value as? QueryParameter {
if !param.omitOnQueryParameter {
encoder.dict[key.stringValue] = singleValueEncoder.singleValue
}
} else {
encoder.dict[key.stringValue] = singleValueEncoder.singleValue
}
}
//fatalError("not supported type \(T.self)")
}
mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
fatalError("nestedContainer in query parameter is not supported.")
}
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
fatalError("nestedUnkeyedContainer in query parameter is not supported.")
}
mutating func superEncoder() -> Encoder {
fatalError("superEncoder in query parameter is not supported.")
}
mutating func superEncoder(forKey key: Key) -> Encoder {
fatalError("superEncoder(forKey:) in query parameter is not supported.")
}
}
extension Encodable where Self: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
let encoder = QueryParameterEncoder()
try self.encode(to: encoder)
switch encoder.storageType {
case .dictionary:
return try QueryParameterDictionary(encoder.dict).queryParameter(option: option)
case .single:
return try (encoder.singleValue ?? QueryParameterNull.null).queryParameter(option: option)
}
}
}
================================================
FILE: Sources/MySQL/RawRepresentableParameter.swift
================================================
//
// RawRepresentableParameter.swift
// MySQL
//
// Created by Yusuke Ito on 4/21/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import SQLFormatter
import Foundation
public protocol QueryRawRepresentableParameter: RawRepresentable, QueryParameter {
}
extension QueryRawRepresentableParameter where RawValue: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return try rawValue.queryParameter(option: option)
}
}
/*
extension RawRepresentable where RawValue: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return try rawValue.queryParameter(option: option)
}
}*/
================================================
FILE: Sources/MySQL/Result-SQLRawStringDecodable.swift
================================================
//
// ResultTypes.swift
// MySQL
//
// Created by Yusuke Ito on 12/28/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import Foundation
extension Int: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Int {
guard let val = Int(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension UInt: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> UInt {
guard let val = UInt(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Int64: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Int64 {
guard let val = Int64(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Int32: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Int32 {
guard let val = Int32(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Int16: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Int16 {
guard let val = Int16(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Int8: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Int8 {
guard let val = Int8(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension UInt64: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> UInt64 {
guard let val = UInt64(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension UInt32: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> UInt32 {
guard let val = UInt32(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension UInt16: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> UInt16 {
guard let val = UInt16(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension UInt8: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> UInt8 {
guard let val = UInt8(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Float: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Float {
guard let val = Float(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Double: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Double {
guard let val = Double(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension String: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> String {
return string
}
}
extension Bool: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Bool {
guard let val = Int(string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return Bool(val == 0 ? false : true )
}
}
extension Decimal: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Decimal {
guard let val = Decimal(string: string) else {
throw QueryError.resultDecodeError(rawSQLValue: string, forType: self)
}
return val
}
}
extension Date: SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Date {
fatalError("invalid constructor (use init instead)")
}
}
================================================
FILE: Sources/MySQL/Result.swift
================================================
//
// Result.swift
// MySQL
//
// Created by ito on 12/10/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import Foundation
internal protocol SQLRawStringDecodable {
static func fromSQLValue(string: String) throws -> Self
}
internal struct QueryRowResult {
private let fields: [Connection.Field]
private let fieldValues: [Connection.FieldValue]
internal let columnMap: [String: Connection.FieldValue] // the key is field name
init(fields: [Connection.Field], fieldValues: [Connection.FieldValue]) {
self.fields = fields
self.fieldValues = fieldValues
var map:[String: Connection.FieldValue] = [:]
for i in 0..<fieldValues.count {
map[fields[i].name] = fieldValues[i]
}
self.columnMap = map
}
func isNull(forField field: String) -> Bool {
guard let fieldValue = columnMap[field] else {
return false
}
switch fieldValue {
case .null:
return true
case .binary, .date:
return false
}
}
private func castOrFail<T: SQLRawStringDecodable>(_ obj: String, field: String) throws -> T {
//print("casting val \(obj) to \(T.self)")
do {
return try T.fromSQLValue(string: obj)
} catch {
throw QueryError.SQLRawStringDecodeError(error: error, actualValue: obj, expectedType: "\(T.self)", forField: field)
}
}
private func getValue<T: SQLRawStringDecodable>(fieldValue: Connection.FieldValue, field: String) throws -> T {
switch fieldValue {
case .null:
throw QueryError.resultCastError(actualValue: "NULL", expectedType: "\(T.self)", forField: field)
case .date(let string, let timezone):
if T.self == Date.self {
return try Date(sqlDate: string, timeZone: timezone) as! T
} else if T.self == DateComponents.self {
return try DateComponents.fromSQLValue(string: string) as! T
}
throw QueryError.resultCastError(actualValue: "\(string)", expectedType: "\(T.self)", forField: field)
case .binary(let data):
//print("T is \(T.self)")
if let bin = data as? T {
return bin
}
return try castOrFail(fieldValue.string(), field: field)
}
}
func getValue<T: SQLRawStringDecodable>(forField field: String) throws -> T {
guard let fieldValue = columnMap[field] else {
throw QueryError.missingField(field)
}
return try getValue(fieldValue: fieldValue, field: field)
}
}
internal struct QueryRowResultDecoder : Decoder {
let codingPath = [CodingKey]()
let userInfo = [CodingUserInfoKey : Any]()
let row: QueryRowResult
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
return KeyedDecodingContainer(RowKeyedDecodingContainer<Key>(decoder: self))
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
throw QueryError.resultDecodeErrorMessage(message: "Decoder unkeyedContainer not implemented")
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
throw QueryError.resultDecodeErrorMessage(message: "Decoder singleValueContainer not implemented")
}
}
fileprivate struct SQLStringDecoder: Decoder {
let codingPath = [CodingKey]()
let userInfo = [CodingUserInfoKey : Any]()
let sqlString: String
struct SingleValue: SingleValueDecodingContainer {
let codingPath = [CodingKey]()
let sqlString: String
func decodeNil() -> Bool {
fatalError()
}
func decode(_ type: Bool.Type) throws -> Bool {
fatalError()
}
func decode(_ type: Int.Type) throws -> Int {
return try Int.fromSQLValue(string: sqlString)
}
func decode(_ type: Int8.Type) throws -> Int8 {
return try Int8.fromSQLValue(string: sqlString)
}
func decode(_ type: Int16.Type) throws -> Int16 {
return try Int16.fromSQLValue(string: sqlString)
}
func decode(_ type: Int32.Type) throws -> Int32 {
return try Int32.fromSQLValue(string: sqlString)
}
func decode(_ type: Int64.Type) throws -> Int64 {
return try Int64.fromSQLValue(string: sqlString)
}
func decode(_ type: UInt.Type) throws -> UInt {
return try UInt.fromSQLValue(string: sqlString)
}
func decode(_ type: UInt8.Type) throws -> UInt8 {
return try UInt8.fromSQLValue(string: sqlString)
}
func decode(_ type: UInt16.Type) throws -> UInt16 {
return try UInt16.fromSQLValue(string: sqlString)
}
func decode(_ type: UInt32.Type) throws -> UInt32 {
return try UInt32.fromSQLValue(string: sqlString)
}
func decode(_ type: UInt64.Type) throws -> UInt64 {
return try UInt64.fromSQLValue(string: sqlString)
}
func decode(_ type: Float.Type) throws -> Float {
fatalError()
}
func decode(_ type: Double.Type) throws -> Double {
fatalError()
}
func decode(_ type: String.Type) throws -> String {
return sqlString
}
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
fatalError()
}
}
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
throw QueryError.resultDecodeErrorMessage(message: "RawTypeDecoder container(keyedBy:) not implemented, you could implement `QueryRowResultCustomData` ")
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
throw QueryError.resultDecodeErrorMessage(message: "RawTypeDecoder unkeyedContainer not implemented")
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
return SingleValue(sqlString: sqlString)
}
}
fileprivate struct RowKeyedDecodingContainer<K : CodingKey> : KeyedDecodingContainerProtocol {
typealias Key = K
let decoder : QueryRowResultDecoder
let allKeys = [Key]()
let codingPath = [CodingKey]()
func decodeNil(forKey key: K) throws -> Bool {
return false
}
func contains(_ key: K) -> Bool {
return decoder.row.columnMap[key.stringValue] != nil && !decoder.row.isNull(forField: key.stringValue)
}
func decode(_ type: Bool.Type, forKey key: K) throws -> Bool {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Int.Type, forKey key: K) throws -> Int {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: UInt.Type, forKey key: K) throws -> UInt {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Float.Type, forKey key: K) throws -> Float {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: Double.Type, forKey key: K) throws -> Double {
return try decoder.row.getValue(forField: key.stringValue)
}
func decode(_ type: String.Type, forKey key: K) throws -> String {
return try decoder.row.getValue(forField: key.stringValue) as String
}
func decode<T>(_ t: T.Type, forKey key: K) throws -> T where T : Decodable {
if t == Data.self {
return try decoder.row.getValue(forField: key.stringValue) as Data as! T
} else if t == Date.self {
return try decoder.row.getValue(forField: key.stringValue) as Date as! T
} else if t == DateComponents.self {
return try decoder.row.getValue(forField: key.stringValue) as DateComponents as! T
} else if t == URL.self {
let urlString = try decoder.row.getValue(forField: key.stringValue) as String
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return url as! T
} else if t == Decimal.self {
return try decoder.row.getValue(forField: key.stringValue) as Decimal as! T
} else if let customType = t as? QueryRowResultCustomData.Type {
let data = try decoder.row.getValue(forField: key.stringValue) as Data
return try customType.decode(fromRowData: data) as! T
}
guard let columnValue = decoder.row.columnMap[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: [key], debugDescription: ""))
}
let d = SQLStringDecoder(sqlString: try columnValue.string())
return try T(from: d)
}
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> {
throw QueryError.resultDecodeErrorMessage(message: "KeyedDecodingContainer nestedContainer not implemented")
}
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
throw QueryError.resultDecodeErrorMessage(message: "KeyedDecodingContainer nestedContainer not implemented")
}
func superDecoder() throws -> Decoder {
throw QueryError.resultDecodeErrorMessage(message: "KeyedDecodingContainer superDecoder not implemented")
}
func superDecoder(forKey key: K) throws -> Decoder {
throw QueryError.resultDecodeErrorMessage(message: "KeyedDecodingContainer superDecoder(forKey) not implemented")
}
}
================================================
FILE: Sources/MySQL/Sync.swift
================================================
//
// Sync.swift
// MySQL
//
// Created by Yusuke Ito on 1/12/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
#if os(Linux)
import Glibc
#elseif os(OSX)
import Darwin.C
#endif
fileprivate final class Mutex {
private var mutex = pthread_mutex_t()
init() {
pthread_mutex_init(&mutex, nil)
}
func lock() {
pthread_mutex_lock(&mutex)
}
func unlock() {
pthread_mutex_unlock(&mutex)
}
deinit {
pthread_mutex_destroy(&mutex)
}
}
internal struct Atomic<T> {
private var value: T
private let mutex = Mutex()
init(_ value: T) {
self.value = value
}
mutating func syncWriting<R>( _ block: (inout T) throws -> R) rethrows -> R {
mutex.lock()
defer {
mutex.unlock()
}
let result = try block(&value)
return result
}
func sync<R>( _ block: (T) throws -> R) rethrows -> R {
mutex.lock()
defer {
mutex.unlock()
}
let result = try block(value)
return result
}
}
================================================
FILE: Sources/MySQL/Transaction.swift
================================================
//
// Connection-Transaction.swift
// MySQL
//
// Created by ito on 12/24/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
extension Connection {
func beginTransaction() throws {
_ = try query("START TRANSACTION;")
}
func commit() throws {
_ = try query("COMMIT;")
}
func rollback() throws {
_ = try query("ROLLBACK;")
}
}
extension ConnectionPool {
public func transaction<T>( _ block: (_ conn: Connection) throws -> T ) throws -> T {
let conn = try getConnection()
defer {
if option.reconnect {
conn.setReconnect(true)
}
releaseConnection(conn)
}
// disable reconnect option of MySQL while transaction
conn.setReconnect(false)
try conn.beginTransaction()
do {
let result = try block(conn)
try conn.commit()
return result
} catch {
do {
try conn.rollback()
} catch {
print("error while `ROLLBACK`.", error)
}
throw error
}
}
}
================================================
FILE: Sources/SQLFormatter/Error.swift
================================================
//
// Error.swift
// SQLFormatter
//
// Created by Yusuke Ito on 4/5/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
public enum QueryFormatError: Error {
case placeholderCountMismatch(query: String)
case parameterIDTypeError(givenValue: String, query: String)
}
================================================
FILE: Sources/SQLFormatter/QueryFormatter.swift
================================================
//
// Query.swift
// MySQL
//
// Created by Yusuke Ito on 12/14/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import Foundation
public protocol QueryParameterType {
func escaped() -> String
func escapedForID() -> String? // returns nil if not supported for query id parameter
}
public struct SQLString {
public static func escapeForID(_ str: String) -> String {
var step1 = ""
for c in str {
switch c {
case "`":
step1 += "``"
default:
step1.append(c)
}
}
var out = ""
for c in step1 {
switch c {
case ".":
out += "`.`"
default:
out.append(c)
}
}
return "`\(out)`"
}
public static func escape(_ str: String) -> String {
var out = "'"
for c in str.unicodeScalars {
switch c {
case "\0":
out += "\\0"
case "\n":
out += "\\n"
case "\r":
out += "\\r"
case "\u{8}":
out += "\\b"
case "\t":
out += "\\t"
case "\\":
out += "\\\\"
case "'":
out += "\\'"
case "\"":
out += "\\\""
case "\u{1A}":
out += "\\Z"
default:
out.append(Character(c))
}
}
out.append("'")
return out
}
public static func escapeForLike(_ str: String, escapingWith: Character = "\\") -> String {
var out = ""
for c in str.unicodeScalars {
switch c {
case "%", "_":
out.append(escapingWith)
default: break
}
out.append(Character(c))
}
return out
}
}
public struct QueryFormatter {
public static func format(query: String, parameters: [QueryParameterType]) throws -> String {
var placeHolderCount = 0
var formatted = query + ""
var valArgs: [QueryParameterType] = []
var scanRange = formatted.startIndex..<formatted.endIndex
// format ??
while true {
// TODO: use function in Swift.String
let r1 = formatted.range(of: "??", options: [], range: scanRange, locale: nil)
let r2 = formatted.range(of: "?", options: [], range: scanRange, locale: nil)
let r: Range<String.Index>
if let r1 = r1, let r2 = r2 {
r = r1.lowerBound <= r2.lowerBound ? r1 : r2
} else if let rr = r1 ?? r2 {
r = rr
} else {
break
}
switch formatted[r] {
case "??":
if placeHolderCount >= parameters.count {
throw QueryFormatError.placeholderCountMismatch(query: query)
}
guard let escapedVal = parameters[placeHolderCount].escapedForID() else {
throw QueryFormatError.parameterIDTypeError(givenValue: "\(parameters[placeHolderCount])", query: query)
}
formatted.replaceSubrange(r, with: escapedVal)
scanRange = r.upperBound..<formatted.endIndex
case "?":
if placeHolderCount >= parameters.count {
throw QueryFormatError.placeholderCountMismatch(query: query)
}
valArgs.append(parameters[placeHolderCount])
scanRange = r.upperBound..<formatted.endIndex
default: break
}
placeHolderCount += 1
if placeHolderCount >= parameters.count {
break
}
}
//print(formatted, valArgs)
placeHolderCount = 0
var formattedChars = Array(formatted)
var index = 0
while index < formattedChars.count {
if formattedChars[index] == "?" {
if placeHolderCount >= valArgs.count {
throw QueryFormatError.placeholderCountMismatch(query: query)
}
let val = valArgs[placeHolderCount]
formattedChars.remove(at: index)
let valStr = val.escaped()
formattedChars.insert(contentsOf: valStr, at: index)
index += valStr.count - 1
placeHolderCount += 1
} else {
index += 1
}
}
return String(formattedChars)
}
}
================================================
FILE: Sources/cmysql/macos.pc
================================================
prefix=/usr/local/opt/mysql
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: MySQL
Description: MySQL client library
Version: 2.0
Cflags: -I${includedir}
Libs: -L${libdir} -lmysqlclient
================================================
FILE: Sources/cmysql/module.modulemap
================================================
module CMySQL [system] {
header "shim.h"
link "mysqlclient"
export *
}
================================================
FILE: Sources/cmysql/shim.h
================================================
#ifndef __CMYSQL_SHIM_H__
#define __CMYSQL_SHIM_H__
#include <mysql/mysql.h>
#if LIBMYSQL_VERSION_ID >= 80000
typedef int my_bool;
#endif
#endif
================================================
FILE: Tests/LinuxMain.swift
================================================
import XCTest
import MySQLTests
import SQLFormatterTests
var tests = [XCTestCaseEntry]()
tests += MySQLTests.__allTests()
tests += SQLFormatterTests.__allTests()
XCTMain(tests)
================================================
FILE: Tests/MySQLTests/BlobTests.swift
================================================
//
// SQLTypeTests.swift
// MySQL
//
// Created by Yusuke Ito on 4/21/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import XCTest
@testable import MySQL
import Foundation
extension Row {
fileprivate struct BlobTextRow: Codable, QueryParameter {
let id: AutoincrementID<BlobTextID>
let text1: String
let binary1: Data
}
fileprivate struct JSONDataUser: Codable, Equatable, QueryCustomDataParameter, QueryRowResultCustomData {
func encodeForQueryParameter() throws -> Data {
let encoder = JSONEncoder()
return try encoder.encode(self)
}
var queryParameterDataType: QueryCustomDataParameterDataType {
return .json
}
static func decode(fromRowData data: Data) throws -> Row.JSONDataUser {
let decoder = JSONDecoder()
return try decoder.decode(self, from: data)
}
// this type decoded from and encoded to Data, like JSON, Protobuf...
let name: String
}
fileprivate struct JSONColumnUser: Codable, QueryParameter, Equatable {
let userName: String
let jsonValue_blob: JSONDataUser
let jsonValue_json: JSONDataUser
}
}
final class BlobQueryTests: XCTestCase, QueryTestType {
var constants: TestConstantsType!
var pool: ConnectionPool!
override func setUp() {
super.setUp()
prepare()
try! createBlobTable()
}
func createBinaryBlobTable() throws {
try dropTestTable()
let conn = try pool.getConnection()
let query = "CREATE TABLE `\(constants.tableName)` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`text1` mediumtext NOT NULL," +
"`binary1` mediumblob NOT NULL," +
"PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;"
_ = try conn.query(query)
}
func createBlobTable() throws {
try dropTestTable()
let conn = try pool.getConnection()
let query = "CREATE TABLE `\(constants.tableName)` (" +
"`id` int(11) unsigned NOT NULL AUTO_INCREMENT," +
"`text1` mediumtext NOT NULL," +
"`binary1` mediumblob NOT NULL," +
"PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
_ = try conn.query(query)
}
func testInsertForCombinedUnicodeCharacter() throws {
let str = "'゙ and áäèëî , ¥"
let obj = Row.BlobTextRow(id: .noID, text1: str, binary1: Data() )
let status: QueryStatus = try pool.execute { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, obj])
}
XCTAssertEqual(status.insertedID, 1)
}
func testBlobAndTextOnBinCollation() throws {
try createBinaryBlobTable()
let testBinary: [UInt8] = Array( (UInt8(0)...UInt8(255)) )
let obj = Row.BlobTextRow(id: .noID, text1: "", binary1: Data(testBinary) )
let status: QueryStatus = try pool.execute { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, obj])
}
XCTAssertEqual(status.insertedID, 1)
let rows: [Row.BlobTextRow] = try pool.execute{ conn in
try conn.query("SELECT * FROM ??", [constants.tableName])
}
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0].binary1.count, 256)
XCTAssertEqual(rows[0].binary1, Data(testBinary))
print(rows[0].binary1, testBinary)
}
func testEscapeBlob() throws {
do {
let testBinary: [UInt8] = [0, 0x1, 0x9, 0x10, 0x1f, 0x99, 0xff, 0x00, 0x0a]
let str = try Data(testBinary).queryParameter(option: queryOption).escaped()
XCTAssertEqual(str, "x'000109101f99ff000a'")
}
}
private func createJSONValueTable() throws {
try dropTestTable()
let conn = try pool.getConnection()
let query = """
CREATE TABLE `\(constants.tableName)` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`userName` mediumtext NOT NULL,
`jsonValue_blob` mediumblob NOT NULL,
`jsonValue_json` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
_ = try conn.query(query)
}
func testJSONColumnValue() throws {
try createJSONValueTable()
let jsonValue = Row.JSONDataUser(name: "name in json value")
let user = Row.JSONColumnUser(userName: "john", jsonValue_blob: jsonValue, jsonValue_json: jsonValue)
let status: QueryStatus = try pool.execute { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, user])
}
XCTAssertEqual(status.insertedID, 1)
let rows: [Row.JSONColumnUser] = try pool.execute{ conn in
try conn.query("SELECT * FROM ??", [constants.tableName])
}
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0], user)
}
}
================================================
FILE: Tests/MySQLTests/ConnectionPoolTests.swift
================================================
//
// ConnectionPoolTests.swift
// MySQL
//
// Created by ito on 12/24/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import Foundation
import Dispatch
import XCTest
@testable import MySQL
final class ConnectionPoolTests: XCTestCase, MySQLTestType {
var constants: TestConstantsType!
var pool: ConnectionPool!
override func setUp() {
super.setUp()
prepare()
}
func testGetConnection() throws {
let initialConnection = 1
XCTAssertEqual(pool.pool.sync { $0.count }, initialConnection)
XCTAssertEqual(pool.pool.sync { $0.inUseConnections }, 0)
var connections: [Connection] = []
for _ in 0..<initialConnection {
let con = try pool.getConnection()
connections.append(con)
}
XCTAssertEqual(connections.count, initialConnection)
XCTAssertEqual(pool.pool.sync { $0.inUseConnections }, initialConnection)
// increse initial connections
pool.initialConnections = 7
XCTAssertEqual(pool.pool.sync { $0.count }, 7)
// get connection while max connections count
while connections.count < pool.maxConnections {
let con = try pool.getConnection()
connections.append(con)
}
XCTAssertEqual(connections.count, pool.maxConnections)
XCTAssertEqual(pool.pool.sync { $0.count }, pool.maxConnections)
XCTAssertEqual(pool.pool.sync { $0.inUseConnections }, pool.maxConnections)
// this connection getting failure
pool.timeoutForGetConnection = 2
XCTAssertThrowsError(try pool.getConnection())
for c in connections {
// release connections that we have got
c.release()
}
connections.removeAll()
XCTAssertEqual(pool.pool.sync { $0.inUseConnections }, 0)
XCTAssertEqual(pool.pool.sync { $0.count }, pool.maxConnections)
}
func testExecutionBlock() throws {
var thisConn: Connection!
try pool.execute { conn in
thisConn = conn
XCTAssertEqual(conn.isInUse, true)
_ = try conn.query("SELECT 1 + 2;")
}
XCTAssertEqual(thisConn.isInUse, false)
}
private var errors: [Error?] = []
private var errorSemaphore = DispatchSemaphore(value: 0)
func testThreadingConnectionPool() throws {
pool.maxConnections = 3
pool.initialConnections = 3
if #available(OSX 10.12, *) {
let THREAD_COUNT = 10
errors = [Error?](repeating: nil, count: THREAD_COUNT)
let semaphore = DispatchSemaphore(value: 0)
for i in 0..<THREAD_COUNT {
Thread.detachNewThread {
print(Thread.current)
do {
try self.pool.execute { conn in
_ = try conn.query("SELECT 1 + 2;")
sleep(1)
}
print("done", Thread.current)
} catch {
print("error while executing", error)
self.errors[i] = error
}
semaphore.signal()
}
}
print("waiting until thread is done.")
for _ in 0..<THREAD_COUNT {
semaphore.wait()
}
print("thread done", errors)
for i in 0..<THREAD_COUNT {
if let error = errors[i] {
XCTFail("\(error)")
}
}
} else {
fatalError()
}
}
}
================================================
FILE: Tests/MySQLTests/ConnectionTests.swift
================================================
//
// ConnectionTest.swift
// MySQL
//
// Created by ito on 12/20/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import XCTest
@testable import MySQL
final class ConnectionTests: XCTestCase, MySQLTestType {
var constants: TestConstantsType!
var pool: ConnectionPool!
override func setUp() {
super.setUp()
prepare()
}
func testConnect() throws {
let conn = try pool.getConnection()
XCTAssertTrue(conn.ping())
}
func testConnect2() throws {
let conn = try pool.getConnection()
_ = try conn.query("SELECT 1;" as String)
XCTAssertTrue(conn.ping())
}
struct Option: ConnectionOption {
let host: String = "dummy"
let port: Int = 3306
let user: String = "dummy"
let password: String = "dummy"
let database: String = "dummy"
}
func testDefaultConnectionOption() {
let option = Option()
XCTAssertNotNil(option.timeZone)
}
}
================================================
FILE: Tests/MySQLTests/DateTests.swift
================================================
//
// DateTests.swift
// MySQL
//
// Created by ito on 12/20/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import XCTest
import CoreFoundation
import Foundation
@testable import MySQL
struct QueryParameterTestOption: QueryParameterOption {
let timeZone: TimeZone
}
extension XCTestCase {
var queryOption: QueryParameterOption {
return QueryParameterTestOption(timeZone: TimeZone(abbreviation: "UTC")!)
}
}
final class DateTests : XCTestCase {
func testSQLDate() throws {
let gmt = QueryParameterTestOption(timeZone: TimeZone(abbreviation: "UTC")!)
let losAngeles = QueryParameterTestOption(timeZone: TimeZone(identifier: "America/Los_Angeles")!)
let expected = "2003-01-02 03:04:05" // no timezone
let date = Date(timeIntervalSince1970: 1041476645) // "2003-01-02 03:04:05" at GMT
XCTAssertEqual(date.queryParameter(option: gmt).escaped(), "'\(expected)'")
let sqlDate = try Date(sqlDate: expected, timeZone: losAngeles.timeZone)
let dateAtLos = Date(timeIntervalSince1970: 1041476645 + 3600*8)
XCTAssertEqual(sqlDate, dateAtLos, "create date from sql string")
XCTAssertEqual(sqlDate.queryParameter(option: losAngeles).escaped(), "'\(expected)'")
XCTAssertEqual(sqlDate, dateAtLos)
XCTAssertNotEqual(try Date(sqlDate: expected, timeZone: losAngeles.timeZone),
try Date(sqlDate: expected, timeZone: gmt.timeZone))
XCTAssertEqual(try Date(sqlDate: expected, timeZone: losAngeles.timeZone),
try Date(sqlDate: expected, timeZone: losAngeles.timeZone))
}
func testSQLCalendar() {
let timeZone = TimeZone(abbreviation: "PDT")!
let cal1 = SQLDateCalendar.calendar(forTimezone: timeZone, { $0 })
let cal2 = SQLDateCalendar.calendar(forTimezone: timeZone, { $0 })
XCTAssertEqual(cal1, cal2)
XCTAssertEqual(cal1.hashValue, cal2.hashValue)
}
func testDateComponents() throws {
do {
// YEAR
let comps = try DateComponents.fromSQLValue(string: "9999")
XCTAssertEqual(comps.year, 9999)
}
do {
// DATETIME
// with nanoseconds
let comps = try DateComponents.fromSQLValue(string: "9999-12-31 23:59:58.123456")
XCTAssertEqual(comps.year, 9999)
XCTAssertEqual(comps.month, 12)
XCTAssertEqual(comps.day, 31)
XCTAssertEqual(comps.hour, 23)
XCTAssertEqual(comps.minute, 59)
XCTAssertEqual(comps.second, 58)
XCTAssertEqual(comps.nanosecond, 123456_000)
}
do {
// DATETIME
let comps = try DateComponents.fromSQLValue(string: "9999-12-31 23:59:58")
XCTAssertEqual(comps.year, 9999)
XCTAssertEqual(comps.month, 12)
XCTAssertEqual(comps.day, 31)
XCTAssertEqual(comps.hour, 23)
XCTAssertEqual(comps.minute, 59)
XCTAssertEqual(comps.second, 58)
}
do {
// TIME
// negative hours
let comps = try DateComponents.fromSQLValue(string: "-123:59:58")
XCTAssertEqual(comps.hour, -123)
XCTAssertEqual(comps.minute, 59)
XCTAssertEqual(comps.second, 58)
}
do {
// TIME
// with nanoseconds
let comps = try DateComponents.fromSQLValue(string: "893:59:58.123456")
XCTAssertEqual(comps.hour, 893)
XCTAssertEqual(comps.minute, 59)
XCTAssertEqual(comps.second, 58)
XCTAssertEqual(comps.nanosecond, 123456_000)
}
do {
// DATE
let comps = try DateComponents.fromSQLValue(string: "9999-12-31")
XCTAssertEqual(comps.year, 9999)
XCTAssertEqual(comps.month, 12)
XCTAssertEqual(comps.day, 31)
}
}
}
================================================
FILE: Tests/MySQLTests/EscapeTests.swift
================================================
//
// EscapeTests.swift
// MySQL
//
// Created by ito on 12/20/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import XCTest
@testable import MySQL
import SQLFormatter
final class EscapeTests: XCTestCase {
// https://github.com/mysqljs/mysql/blob/master/test/unit/protocol/test-SqlString.js
func testStringEscape() {
XCTAssertEqual(SQLString.escape("Sup'er"), "'Sup\\'er'")
XCTAssertEqual(SQLString.escape("\u{00A5}"), "'¥'")
XCTAssertEqual(SQLString.escape("\\"), "'\\\\'")
// escape combined character
XCTAssertEqual(SQLString.escape("'゙"), "'\\'゙'")
}
func testBasicTypes() throws {
let strVal: String = "Sup'er"
let strValOptional: String? = "Sup'er Super"
let strValOptionalNone: String? = nil
XCTAssertEqual(strVal.queryParameter(option: queryOption).escaped(), "'Sup\\'er'")
XCTAssertEqual(try strValOptional.queryParameter(option: queryOption).escaped(), "'Sup\\'er Super'")
XCTAssertEqual(try ((strValOptionalNone ?? QueryParameterNull.null) as QueryParameter).queryParameter(option: queryOption).escaped(), "NULL")
}
func testArrayType() throws {
let strVal: String = "Sup'er"
let strValOptional: String? = "Sup'er Super"
let strValOptionalNone: String? = nil
let strs: [QueryParameter] = [strVal, strVal]
XCTAssertEqual(try QueryParameterArray(strs).queryParameter(option: queryOption).escaped(), "'Sup\\'er', 'Sup\\'er'")
let strsOptional1: [QueryParameter?] = [strVal, strValOptional]
XCTAssertEqual(try QueryParameterArray(strsOptional1).queryParameter(option: queryOption).escaped(), "'Sup\\'er', 'Sup\\'er Super'")
let strsOptional2: [QueryParameter?] = [strVal, strValOptionalNone]
XCTAssertEqual(try QueryParameterArray(strsOptional2).queryParameter(option: queryOption).escaped(), "'Sup\\'er', NULL")
let arr = QueryParameterArray(strs)
let arrayOfArr = QueryParameterArray( [arr] )
XCTAssertEqual(try arrayOfArr.queryParameter(option: queryOption).escaped(), "('Sup\\'er', 'Sup\\'er')")
let strInt:[QueryParameter] = [strVal, 271]
XCTAssertEqual(try QueryParameterArray(strInt).queryParameter(option: queryOption).escaped(), "'Sup\\'er', 271")
let strOptionalAndInt:[QueryParameter] = [strValOptional, 3.14]
XCTAssertEqual(try QueryParameterArray(strOptionalAndInt).queryParameter(option: queryOption).escaped(), "'Sup\\'er Super', 3.14")
}
func testNestedArray() throws {
let strVal: String = "Sup'er"
let strValOptionalNone: String? = nil
let child: [QueryParameter] = [strVal, strValOptionalNone]
let strs: [QueryParameter] = [strVal, strValOptionalNone, QueryParameterArray(child)]
XCTAssertEqual(try QueryParameterArray(strs).queryParameter(option: queryOption).escaped(), "\'Sup\\\'er\', NULL, (\'Sup\\\'er\', NULL)")
}
func testAutoincrement() throws {
let strVal: String = "Sup'er"
do {
let userID: AutoincrementID<UserID> = .ID(UserID(333))
XCTAssertEqual(try userID.queryParameter(option: queryOption).escaped(), "333")
let arr: [QueryParameter] = [strVal, userID]
XCTAssertEqual(try QueryParameterArray(arr).queryParameter(option: queryOption).escaped(), "\'Sup\\\'er\', 333")
}
do {
let stringID: AutoincrementID<SomeStringID> = .ID(SomeStringID("id-555@"))
XCTAssertEqual(try stringID.queryParameter(option: queryOption).escaped(), "\'id-555@\'")
let arr: [QueryParameter] = [strVal, stringID]
XCTAssertEqual(try QueryParameterArray(arr).queryParameter(option: queryOption).escaped(), "\'Sup\\\'er\', \'id-555@\'")
}
do {
let noID: AutoincrementID<UserID> = .noID
XCTAssertEqual(try noID.queryParameter(option: queryOption).escaped(), "\'\'")
let arr: [QueryParameter] = [strVal, noID]
XCTAssertEqual(try QueryParameterArray(arr).queryParameter(option: queryOption).escaped(), "\'Sup\\\'er\'")
}
}
func testDictionary() {
let strVal: String = "Sup'er"
let strValOptional: String? = "Sup'er Super"
let strValOptionalNone: String? = nil
let dict = QueryParameterDictionary([
"string": strVal,
"stringOptional": strValOptional,
"stringNone" : strValOptionalNone
])
let expectedResult = Set(arrayLiteral: "`string` = 'Sup\\'er'", "`stringOptional` = 'Sup\\'er Super'", "`stringNone` = NULL")
let escaped = try! dict.queryParameter(option: queryOption).escaped()
XCTAssertEqual(Set(escaped.split(separator: ",").map(String.init).map({ $0.trimmingCharacters(in: .whitespaces) })), expectedResult)
}
}
================================================
FILE: Tests/MySQLTests/Model.swift
================================================
//
// Model.swift
// MySQL
//
// Created by ito on 12/20/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import MySQL
import Foundation
struct UserID: IDType {
let id: Int
init(_ id: Int) {
self.id = id
}
}
struct BlobTextID: IDType {
let id: Int
init(_ id: Int) {
self.id = id
}
}
struct SomeStringID: IDType {
let id: String
init(_ id: String) {
self.id = id
}
}
final class Row {
private init() { }
}
================================================
FILE: Tests/MySQLTests/MySQLConnection.swift
================================================
//
// MySQLTests.swift
// MySQLTests
//
// Created by ito on 2015/10/24.
// Copyright © 2015年 Yusuke Ito. All rights reserved.
//
import XCTest
import MySQL
import Foundation
/*
struct TestConstants: TestConstantsType {
let host: String = ""
let port: Int = 3306
let user: String = ""
let password: String = ""
let database: String = "test"
let tableName: String = "unit_test_db_3894" // Unit test creates a table
let encoding: Connection.Encoding = .UTF8MB4
let timeZone: Connection.TimeZone = Connection.TimeZone(GMTOffset: 60 * 60 * 9) // JST
}
*/
struct DummyConstants: TestConstantsType {
let host: String = "127.0.0.1"
let port: Int = 3306
let user: String = "root"
let password: String = ""
let database: String = "test"
let tableName: String = "unit_test_db_3894"
let encoding: Connection.Encoding = .UTF8MB4
let timeZone: TimeZone = TimeZone(abbreviation: "JST")! // JST
let reconnect: Bool = true
}
protocol TestConstantsType: ConnectionOption {
var tableName: String { get }
}
protocol MySQLTestType: class {
var constants: TestConstantsType! { get set }
var pool: ConnectionPool! { get set }
}
extension MySQLTestType {
func prepare() {
self.constants = DummyConstants() // !!! Replace with your MySQL connection !!!
self.pool = ConnectionPool(option: constants)
XCTAssertEqual(constants.timeZone, TimeZone(abbreviation: "JST"), "test MySQL's timezone should be JST")
}
}
================================================
FILE: Tests/MySQLTests/QueryDecimalTypeTests.swift
================================================
//
// QueryDecimalTypeTests.swift
// MySQLTests
//
// Created by Yusuke Ito on 5/1/18.
//
import XCTest
@testable import MySQL
import Foundation
extension Row {
fileprivate struct DecimalRow: Codable, QueryParameter, Equatable {
let valueDoubleColumn: Decimal
let valueTextColumn: Decimal
private enum CodingKeys: String, CodingKey {
case valueDoubleColumn = "value_double_col"
case valueTextColumn = "value_text_col"
}
}
}
final class QueryDecimalTypeTests: XCTestCase, QueryTestType {
var constants: TestConstantsType!
var pool: ConnectionPool!
override func setUp() {
super.setUp()
prepare()
try! createDecimalTestTable()
}
private func createDecimalTestTable() throws {
try dropTestTable()
let conn = try pool.getConnection()
let query = """
CREATE TABLE `\(constants.tableName)` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`value_double_col` DOUBLE NOT NULL DEFAULT 0,
`value_text_col` MEDIUMTEXT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
_ = try conn.query(query)
}
func testDecimalType() throws {
let value = Decimal(string: "1.549")!
let row = Row.DecimalRow(valueDoubleColumn: value, valueTextColumn: value)
try pool.execute { conn in
_ = try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, row])
}
let rows: [Row.DecimalRow] = try pool.execute {
try $0.query("SELECT * FROM ?? ORDER BY id ASC", [constants.tableName])
}
XCTAssertEqual(rows[0], row)
}
func testDecimalType_largerValue() throws {
let value = Decimal(string: "1.23e100")!
let row = Row.DecimalRow(valueDoubleColumn: value, valueTextColumn: value)
try pool.execute { conn in
_ = try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, row])
}
let rows: [Row.DecimalRow] = try pool.execute {
try $0.query("SELECT * FROM ?? ORDER BY id ASC", [constants.tableName])
}
XCTAssertEqual(rows[0], row)
}
}
================================================
FILE: Tests/MySQLTests/QueryFormatterTests.swift
================================================
//
// QueryFormatterTests.swift
// MySQL
//
// Created by ito on 12/20/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import XCTest
@testable import MySQL
import SQLFormatter
final class QueryFormatterTests: XCTestCase {
fileprivate enum TableName: String, QueryRawRepresentableParameter {
case user = "user"
}
func testBasicFormatting() throws {
let params: (String, TableName, String, Int, String, Int?) = (
"i.d",
TableName.user,
"id",
1,
"user's",
nil
)
let args: [QueryParameter] = [
params.0,
params.1,
params.2,
params.3,
params.4,
params.5,
]
let formatted = try QueryFormatter.format(query: "SELECT name,??,id FROM ?? WHERE ?? = ? OR name = ? OR age is ?;", parameters: Connection.buildParameters(args, option: queryOption) )
XCTAssertEqual(formatted, "SELECT name,`i`.`d`,id FROM `user` WHERE `id` = 1 OR name = 'user\\'s' OR age is NULL;")
}
func testLikeEscape() {
XCTAssertEqual(SQLString.escapeForLike("ap%ple_"), "ap\\%ple\\_")
XCTAssertEqual(SQLString.escapeForLike("ap\\%ple_"), "ap\\\\%ple\\_")
XCTAssertEqual(SQLString.escapeForLike("ap%ple_", escapingWith: "$"), "ap$%ple$_")
XCTAssertEqual(SQLString.escapeForLike("ap\\%ple_", escapingWith: "$"), "ap\\$%ple$_")
}
func testPlaceholder() throws {
let params: [QueryParameter] = ["name", "message??", "col", "hello??", "hello?"]
let formatted = try QueryFormatter.format(query: "SELECT ??, ?, ??, ?, ?", parameters: Connection.buildParameters(params, option: queryOption))
XCTAssertEqual(formatted, "SELECT `name`, 'message??', `col`, 'hello??', 'hello?'")
}
func testStringUtil() {
let someString = "abcdefghijklmn12345"
XCTAssertEqual(someString.subString(max: 10), "abcdefghij")
XCTAssertEqual(someString.subString(max: 1000), someString)
XCTAssertEqual("".subString(max: 10), "")
}
}
================================================
FILE: Tests/MySQLTests/QueryParameterTests.swift
================================================
//
// QueryParameterTests.swift
// MySQL
//
// Created by Yusuke Ito on 4/21/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import XCTest
import MySQL
import SQLFormatter
// the URL as QueryParameter should be
extension URL: QueryParameter {
public func queryParameter(option: QueryParameterOption) throws -> QueryParameterType {
return self.absoluteString.queryParameter(option: option)
}
}
extension QueryParameterTests {
static var allTests : [(String, (QueryParameterTests) -> () throws -> Void)] {
return [
("testIDType", testIDType),
("testIDTypeInContainer", testIDTypeInContainer),
("testEnumType", testEnumType),
("testAutoincrementType", testAutoincrementType),
("testDateComponentsType", testDateComponentsType),
("testDataAndURLType", testDataAndURLType),
("testDecimalType", testDecimalType),
("testCodableIDType", testCodableIDType),
("testCodableIDType_AutoincrementNoID", testCodableIDType_AutoincrementNoID)
]
}
}
final class QueryParameterTests: XCTestCase {
private struct IDInt: IDType {
let id: Int
init(_ id: Int) {
self.id = id
}
}
private struct IDString: IDType {
let id: String
init(_ id: String) {
self.id = id
}
}
private struct ModelWithIDType_StringAutoincrement: Encodable, QueryParameter {
let idStringAutoincrement: AutoincrementID<IDString>
}
private struct ModelWithIDType_IntAutoincrement: Encodable, QueryParameter {
let idIntAutoincrement: AutoincrementID<IDInt>
}
private enum SomeEnumParameter: String, QueryRawRepresentableParameter {
case first = "first 1"
case second = "second' 2"
}
private enum SomeEnumCodable: String, Codable, QueryParameter {
case first = "first 1"
case second = "second' 2"
}
// https://developer.apple.com/documentation/swift/optionset
private struct ShippingOptions: OptionSet, QueryRawRepresentableParameter {
let rawValue: Int
static let nextDay = ShippingOptions(rawValue: 1 << 0)
static let secondDay = ShippingOptions(rawValue: 1 << 1)
static let priority = ShippingOptions(rawValue: 1 << 2)
static let standard = ShippingOptions(rawValue: 1 << 3)
static let express: ShippingOptions = [.nextDay, .secondDay]
static let all: ShippingOptions = [.express, .priority, .standard]
}
private struct ModelWithData: Encodable, QueryParameter {
let data: Data
}
private struct ModelWithDate: Encodable, QueryParameter {
let date: Date
}
private struct ModelWithDateComponents: Encodable, QueryParameter {
let dateComponents: DateComponents
}
private struct ModelWithURL: Encodable, QueryParameter {
let url: URL
}
private struct ModelWithDecimal: Encodable, QueryParameter {
let value: Decimal
}
func testIDType() throws {
let idInt: QueryParameter = IDInt(1234)
XCTAssertEqual(try idInt.queryParameter(option: queryOption).escaped(), "1234")
//let id: SomeID = try SomeID.fromSQLValue(string: "5678")
//XCTAssertEqual(id.id, 5678)
let idString: QueryParameter = IDString("123abc")
XCTAssertEqual(try idString.queryParameter(option: queryOption).escaped(), "'123abc'")
let idIntAutoincrement: QueryParameter = AutoincrementID(IDInt(1234))
XCTAssertEqual(try idIntAutoincrement.queryParameter(option: queryOption).escaped(), "1234")
let idStringAutoincrement: QueryParameter = AutoincrementID(IDString("123abc"))
XCTAssertEqual(try idStringAutoincrement.queryParameter(option: queryOption).escaped(), "'123abc'")
}
func testIDTypeInContainer() throws {
do {
let param: QueryParameter = ModelWithIDType_IntAutoincrement(idIntAutoincrement: .ID(IDInt(1234)))
XCTAssertEqual(try param.queryParameter(option: queryOption).escaped(), "`idIntAutoincrement` = 1234")
}
do {
let param: QueryParameter = ModelWithIDType_StringAutoincrement(idStringAutoincrement: .ID(IDString("123abc")))
XCTAssertEqual(try param.queryParameter(option: queryOption).escaped(), "`idStringAutoincrement` = '123abc'")
}
}
func testEnumType() throws {
do {
let someVal: QueryParameter = SomeEnumParameter.second
let escaped = "second' 2".escaped()
XCTAssertEqual(try someVal.queryParameter(option: queryOption).escaped() , escaped)
}
do {
let someVal: QueryParameter = SomeEnumCodable.second
let escaped = "second' 2".escaped()
XCTAssertEqual(try someVal.queryParameter(option: queryOption).escaped() , escaped)
}
do {
let someOption: QueryParameter = ShippingOptions.all
XCTAssertEqual(try someOption.queryParameter(option: queryOption).escaped() , "\(ShippingOptions.all.rawValue)")
}
}
func testAutoincrementType() throws {
let userID: AutoincrementID<UserID> = .ID(UserID(333))
XCTAssertEqual(userID, AutoincrementID.ID(UserID(333)))
let someStringID: AutoincrementID<SomeStringID> = .ID(SomeStringID("id678@"))
XCTAssertEqual(someStringID, AutoincrementID.ID(SomeStringID("id678@")))
let noID: AutoincrementID<UserID> = .noID
XCTAssertEqual(noID, AutoincrementID.noID)
}
func testDateComponentsType() throws {
do {
let compsEmpty = DateComponents()
let model = ModelWithDateComponents(dateComponents: compsEmpty)
let _ = try model.queryParameter(option: queryOption).escaped()
XCTFail("this should be throws an error")
} catch {
// OK
}
do {
// MySQL YEAR type
var comps = DateComponents()
comps.year = 2155
let model = ModelWithDateComponents(dateComponents: comps)
let queryString = try model.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString, "`dateComponents` = '2155'")
}
do {
// MySQL TIME type
var comps = DateComponents()
comps.hour = -838
comps.minute = 59
comps.second = 59
let model = ModelWithDateComponents(dateComponents: comps)
let queryString = try model.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString, "`dateComponents` = '-838:59:59'")
}
do {
// MySQL TIME type
// with nanosecond
var comps = DateComponents()
comps.hour = -838
comps.minute = 59
comps.second = 59
comps.nanosecond = 1234567
let model = ModelWithDateComponents(dateComponents: comps)
let queryString = try model.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString, "`dateComponents` = '-838:59:59.001235'")
}
do {
// MySQL DATETIME, TIMESTAMP type
var comps = DateComponents()
comps.year = 9999
comps.month = 12
comps.day = 31
comps.hour = 23
comps.minute = 59
comps.second = 59
let model = ModelWithDateComponents(dateComponents: comps)
let queryString = try model.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString, "`dateComponents` = '9999-12-31 23:59:59'")
}
do {
// MySQL DATETIME, TIMESTAMP type
// with nanosecond
var comps = DateComponents()
comps.year = 9999
comps.month = 12
comps.day = 31
comps.hour = 23
comps.minute = 59
comps.second = 59
comps.nanosecond = 1234567
let model = ModelWithDateComponents(dateComponents: comps)
let queryString = try model.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString, "`dateComponents` = '9999-12-31 23:59:59.001235'")
}
}
func testDataAndURLType() throws {
do {
let dataModel = ModelWithData(data: Data([0x12, 0x34, 0x56, 0xff, 0x00]))
let queryString = try dataModel.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString,
"`data` = x'123456ff00'")
}
do {
let urlModel = ModelWithURL(url: URL(string: "https://apple.com/iphone")!)
let queryString = try urlModel.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString,
"`url` = 'https://apple.com/iphone'")
}
do {
let param: QueryParameter = URL(string: "https://apple.com/iphone")!
let queryString = try param.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString,
"'https://apple.com/iphone'")
}
}
func testDecimalType() throws {
let decimalModel = ModelWithDecimal(value: Decimal(1.2345e100))
let queryString = try decimalModel.queryParameter(option: queryOption).escaped()
XCTAssertEqual(queryString,
"`value` = '12345000000000010240000000000000000000000000000000000000000000000000000000000000000000000000000000000'")
}
private enum UserType: String, Codable {
case user = "user"
case admin = "admin"
}
private struct CodableModel: Codable, QueryParameter {
let id: UserID
let name: String
let userType: UserType
}
private struct CodableModelWithAutoincrement: Codable, QueryParameter {
let id: AutoincrementID<UserID>
let name: String
let userType: UserType
}
func testCodableIDType() throws {
let expectedResult = Set(arrayLiteral: "`id` = 123", "`name` = 'test4456'", "`userType` = 'user'")
do {
let parameter: QueryParameter = CodableModel(id: UserID(123),
name: "test4456",
userType: .user)
let result = try parameter.queryParameter(option: queryOption).escaped()
XCTAssertEqual(Set(result.split(separator: ",").map(String.init).map({ $0.trimmingCharacters(in: .whitespaces) })), expectedResult)
}
do {
let parameter: QueryParameter = CodableModelWithAutoincrement(id: AutoincrementID(UserID(123)),
name: "test4456",
userType: .user)
let result = try parameter.queryParameter(option: queryOption).escaped()
XCTAssertEqual(Set(result.split(separator: ",").map(String.init).map({ $0.trimmingCharacters(in: .whitespaces) })), expectedResult)
}
}
func testCodableIDType_AutoincrementNoID() throws {
let expectedResult = Set(arrayLiteral: "`name` = 'test4456'", "`userType` = 'user'")
let parameter: QueryParameter = CodableModelWithAutoincrement(id: .noID,
name: "test4456",
userType: .user)
let result = try parameter.queryParameter(option: queryOption).escaped()
XCTAssertEqual(Set(result.split(separator: ",").map(String.init).map({ $0.trimmingCharacters(in: .whitespaces) })), expectedResult)
}
}
================================================
FILE: Tests/MySQLTests/QueryTests.swift
================================================
//
// QueryTests.swift
// MySQL
//
// Created by ito on 12/20/15.
// Copyright © 2015 Yusuke Ito. All rights reserved.
//
import XCTest
@testable import MySQL
import Foundation
protocol QueryTestType: MySQLTestType {
func dropTestTable() throws
}
extension QueryTestType {
func dropTestTable() throws {
let conn = try pool.getConnection()
_ = try conn.query("DROP TABLE IF EXISTS \(constants.tableName)")
}
}
extension Row {
fileprivate struct SimpleUser: Codable, Equatable {
let id: UInt
let name: String
let age: Int
}
fileprivate enum UserType: String, Codable {
case user = "user"
case admin = "admin"
}
fileprivate struct User: Codable, QueryParameter, Equatable {
let id: AutoincrementID<UserID>
let name: String
let age: Int
let createdAt: Date
let createdAtDateComponentsOptional: DateComponents?
let nameOptional: String?
let ageOptional: Int?
let createdAtOptional: Date?
let done: Bool
let doneOptional: Bool?
let userType: UserType
private enum CodingKeys: String, CodingKey {
case id
case name
case age
case createdAt = "created_at"
case createdAtDateComponentsOptional = "created_at_datecomponents_Optional"
case nameOptional = "name_Optional"
case ageOptional = "age_Optional"
case createdAtOptional = "created_at_Optional"
case done
case doneOptional = "done_Optional"
case userType = "user_type"
}
}
}
final class QueryTests: XCTestCase, QueryTestType {
var constants: TestConstantsType!
var pool: ConnectionPool!
override func setUp() {
super.setUp()
prepare()
try! createTestTable()
}
func createTestTable() throws {
try dropTestTable()
let conn = try pool.getConnection()
let query = """
CREATE TABLE `\(constants.tableName)` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`age` int(11) NOT NULL,
`created_at` datetime NOT NULL DEFAULT '2001-01-01 00:00:00',
`created_at_datecomponents_Optional` datetime(6) DEFAULT NULL,
`name_Optional` varchar(50) DEFAULT NULL,
`age_Optional` int(11) DEFAULT NULL,
`created_at_Optional` datetime DEFAULT NULL,
`done` tinyint(1) NOT NULL DEFAULT 0,
`done_Optional` tinyint(1) DEFAULT NULL,
`user_type` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
_ = try conn.query(query)
}
private var someDate: Date {
return try! Date(sqlDate: "2015-12-27 16:54:00", timeZone: pool.option.timeZone)
}
private var anotherDate: Date {
return Date(timeIntervalSinceReferenceDate: 60*60*24*67)
}
func testInsertRowCodable() throws {
typealias User = Row.User
let name = "name 's"
let age = 25
let dateComponents = DateComponents(year: 2012, month: 3, day: 4, hour: 5, minute: 6, second: 7, nanosecond: 890_000_000)
let userNil = User(id: .noID, name: name, age: age, createdAt: someDate, createdAtDateComponentsOptional: dateComponents, nameOptional: nil, ageOptional: nil, createdAtOptional: nil, done: false, doneOptional: nil, userType: .user)
let status: QueryStatus = try pool.execute { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, userNil])
}
XCTAssertEqual(status.insertedID, 1)
let userFill = User(id: .ID(UserID(134)), name: name, age: age, createdAt: someDate, createdAtDateComponentsOptional: dateComponents, nameOptional: "fuga", ageOptional: 50, createdAtOptional: anotherDate, done: true, doneOptional: false, userType: .admin)
let status2: QueryStatus = try pool.execute { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, userFill])
}
XCTAssertEqual(status2.insertedID, 134)
let rows:[User] = try pool.execute { conn in
try conn.query("SELECT id,name,age,created_at,created_at_datecomponents_Optional,name_Optional,age_Optional,created_at_Optional,done,done_Optional,user_type FROM ?? ORDER BY id ASC", [constants.tableName])
}
XCTAssertEqual(rows.count, 2)
// first row
XCTAssertEqual(rows[0].id.id, UserID(Int(status.insertedID)))
XCTAssertEqual(rows[0].name, name)
XCTAssertEqual(rows[0].age, age)
XCTAssertEqual(rows[0].createdAt, someDate)
XCTAssertNil(rows[0].nameOptional)
XCTAssertNil(rows[0].ageOptional)
XCTAssertNil(rows[0].createdAtOptional)
XCTAssertFalse(rows[0].done)
XCTAssertNil(rows[0].doneOptional)
XCTAssertEqual(rows[0].userType, .user)
XCTAssertEqual(rows[1], userFill)
}
func testTransaction() throws {
let user = Row.User(id: .noID, name: "name", age: 11, createdAt: someDate, createdAtDateComponentsOptional: nil, nameOptional: nil, ageOptional: nil, createdAtOptional: nil, done: false, doneOptional: nil, userType: .user)
let status: QueryStatus = try pool.transaction { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, user])
}
XCTAssertEqual(status.insertedID, 1)
}
func testEmojiInserting() throws {
typealias User = Row.User
let now = Date()
let user = User(id: .noID, name: "日本語123🍣🍺あいう", age: 123, createdAt: now, createdAtDateComponentsOptional: nil, nameOptional: nil, ageOptional: nil, createdAtOptional: nil, done: false, doneOptional: nil, userType: .user)
let status: QueryStatus = try pool.execute { conn in
try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, user])
}
let rows: [User] = try pool.execute{ conn in
try conn.query("SELECT id,name,age,created_at,name_Optional,age_Optional,created_at_Optional,done,done_Optional,user_type FROM ?? WHERE id = ?", [constants.tableName, status.insertedID])
}
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0].name, user.name)
XCTAssertEqual(rows[0].age, user.age)
}
func testBulkInsert() throws {
//let now = Date()
let users = (1...3).map({ row in
Row.SimpleUser(id: UInt(10+row), name: "name\(row)", age: row)
})
let usersParam: [QueryParameterArray] = users.map { user in
QueryParameterArray([user.id, user.name, user.age])
}
_ = try pool.execute { conn in
try conn.query("INSERT INTO ??(id,name,age) VALUES ? ", [constants.tableName, QueryParameterArray(usersParam)])
}
let fetchedUsers: [Row.SimpleUser] = try pool.execute { conn in
try conn.query("SELECT id,name,age FROM ?? ORDER BY id DESC", [constants.tableName])
}
XCTAssertEqual(fetchedUsers.count, 3)
for index in (0..<3) {
XCTAssertEqual(fetchedUsers[index], users.reversed()[index])
}
}
}
================================================
FILE: Tests/MySQLTests/QueryURLTypeTests.swift
================================================
//
// QueryURLTypeTests.swift
// MySQLTests
//
// Created by Yusuke Ito on 5/1/18.
//
import XCTest
@testable import MySQL
import Foundation
extension Row {
fileprivate struct URLRow: Codable, QueryParameter, Equatable {
let url: URL
let urlOptional: URL?
private enum CodingKeys: String, CodingKey {
case url = "url"
case urlOptional = "url_Optional"
}
}
}
final class QueryURLTypeTests: XCTestCase, QueryTestType {
var constants: TestConstantsType!
var pool: ConnectionPool!
override func setUp() {
super.setUp()
prepare()
try! createURLTestTable()
}
private func createURLTestTable() throws {
try dropTestTable()
let conn = try pool.getConnection()
let query = """
CREATE TABLE `\(constants.tableName)` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`url` mediumtext NOT NULL,
`url_Optional` mediumtext,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
_ = try conn.query(query)
}
func testURLType() throws {
let urlRow1 = Row.URLRow(url: URL(string: "https://apple.com/iphone")!, urlOptional: nil)
let urlRow2 = Row.URLRow(url: URL(string: "https://apple.com/iphone")!, urlOptional: URL(string: "https://apple.com/ipad")!)
try pool.execute { conn in
_ = try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, urlRow1])
_ = try conn.query("INSERT INTO ?? SET ? ", [constants.tableName, urlRow2])
}
let rows: [Row.URLRow] = try pool.execute {
try $0.query("SELECT * FROM ?? ORDER BY id ASC", [constants.tableName])
}
XCTAssertEqual(rows[0], urlRow1)
XCTAssertEqual(rows[1], urlRow2)
}
func testURLInvalid() throws {
try pool.execute { conn in
_ = try conn.query("INSERT INTO ?? SET `url` = ''", [constants.tableName])
}
do {
let _: [Row.URLRow] = try pool.execute {
try $0.query("SELECT * FROM ?? ORDER BY id ASC", [constants.tableName])
}
} catch let error as DecodingError {
switch error {
case .dataCorrupted(let context):
print(context)
// expected error
default:
XCTFail("unexpected error \(error)")
}
} catch {
XCTFail("unexpected error \(error)")
}
}
}
================================================
FILE: Tests/MySQLTests/XCTestManifests.swift
================================================
import XCTest
extension BlobQueryTests {
static let __allTests = [
("testBlobAndTextOnBinCollation", testBlobAndTextOnBinCollation),
("testEscapeBlob", testEscapeBlob),
("testInsertForCombinedUnicodeCharacter", testInsertForCombinedUnicodeCharacter),
("testJSONColumnValue", testJSONColumnValue),
]
}
extension ConnectionPoolTests {
static let __allTests = [
("testExecutionBlock", testExecutionBlock),
("testGetConnection", testGetConnection),
("testThreadingConnectionPool", testThreadingConnectionPool),
]
}
extension ConnectionTests {
static let __allTests = [
("testConnect2", testConnect2),
("testConnect", testConnect),
("testDefaultConnectionOption", testDefaultConnectionOption),
]
}
extension DateTests {
static let __allTests = [
("testDateComponents", testDateComponents),
("testSQLCalendar", testSQLCalendar),
("testSQLDate", testSQLDate),
]
}
extension EscapeTests {
static let __allTests = [
("testArrayType", testArrayType),
("testAutoincrement", testAutoincrement),
("testBasicTypes", testBasicTypes),
("testDictionary", testDictionary),
("testNestedArray", testNestedArray),
("testStringEscape", testStringEscape),
]
}
extension QueryDecimalTypeTests {
static let __allTests = [
("testDecimalType_largerValue", testDecimalType_largerValue),
("testDecimalType", testDecimalType),
]
}
extension QueryFormatterTests {
static let __allTests = [
("testBasicFormatting", testBasicFormatting),
("testLikeEscape", testLikeEscape),
("testPlaceholder", testPlaceholder),
("testStringUtil", testStringUtil),
]
}
extension QueryParameterTests {
static let __allTests = [
("testAutoincrementType", testAutoincrementType),
("testCodableIDType_AutoincrementNoID", testCodableIDType_AutoincrementNoID),
("testCodableIDType", testCodableIDType),
("testDataAndURLType", testDataAndURLType),
("testDateComponentsType", testDateComponentsType),
("testDecimalType", testDecimalType),
("testEnumType", testEnumType),
("testIDType", testIDType),
("testIDTypeInContainer", testIDTypeInContainer),
]
}
extension QueryTests {
static let __allTests = [
("testBulkInsert", testBulkInsert),
("testEmojiInserting", testEmojiInserting),
("testInsertRowCodable", testInsertRowCodable),
("testTransaction", testTransaction),
]
}
extension QueryURLTypeTests {
static let __allTests = [
("testURLInvalid", testURLInvalid),
("testURLType", testURLType),
]
}
#if !os(macOS)
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(BlobQueryTests.__allTests),
testCase(ConnectionPoolTests.__allTests),
testCase(ConnectionTests.__allTests),
testCase(DateTests.__allTests),
testCase(EscapeTests.__allTests),
testCase(QueryDecimalTypeTests.__allTests),
testCase(QueryFormatterTests.__allTests),
testCase(QueryParameterTests.__allTests),
testCase(QueryTests.__allTests),
testCase(QueryURLTypeTests.__allTests),
]
}
#endif
================================================
FILE: Tests/SQLFormatterTests/SQLFormatterTests.swift
================================================
//
// SQLFormatterTests.swift
// SQLFormatterTests
//
// Created by Yusuke Ito on 4/5/16.
// Copyright © 2016 Yusuke Ito. All rights reserved.
//
import XCTest
@testable import SQLFormatter
class SQLFormattingTests: XCTestCase {
func testDummy() throws {
XCTAssertTrue(true)
}
}
================================================
FILE: Tests/SQLFormatterTests/XCTestManifests.swift
================================================
import XCTest
extension SQLFormattingTests {
static let __allTests = [
("testDummy", testDummy),
]
}
#if !os(macOS)
public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(SQLFormattingTests.__allTests),
]
}
#endif
gitextract_zidf83u4/
├── .circleci/
│ └── config.yml
├── .gitignore
├── LICENSE.md
├── Package.swift
├── Package@swift-5.swift
├── README.md
├── Sources/
│ ├── MySQL/
│ │ ├── AutoincrementID.swift
│ │ ├── Blob.swift
│ │ ├── Connection.swift
│ │ ├── ConnectionPool.swift
│ │ ├── Date.swift
│ │ ├── Error.swift
│ │ ├── IDType.swift
│ │ ├── Query.swift
│ │ ├── QueryParameter-Data.swift
│ │ ├── QueryParameterType.swift
│ │ ├── RawRepresentableParameter.swift
│ │ ├── Result-SQLRawStringDecodable.swift
│ │ ├── Result.swift
│ │ ├── Sync.swift
│ │ └── Transaction.swift
│ ├── SQLFormatter/
│ │ ├── Error.swift
│ │ └── QueryFormatter.swift
│ └── cmysql/
│ ├── macos.pc
│ ├── module.modulemap
│ └── shim.h
└── Tests/
├── LinuxMain.swift
├── MySQLTests/
│ ├── BlobTests.swift
│ ├── ConnectionPoolTests.swift
│ ├── ConnectionTests.swift
│ ├── DateTests.swift
│ ├── EscapeTests.swift
│ ├── Model.swift
│ ├── MySQLConnection.swift
│ ├── QueryDecimalTypeTests.swift
│ ├── QueryFormatterTests.swift
│ ├── QueryParameterTests.swift
│ ├── QueryTests.swift
│ ├── QueryURLTypeTests.swift
│ └── XCTestManifests.swift
└── SQLFormatterTests/
├── SQLFormatterTests.swift
└── XCTestManifests.swift
SYMBOL INDEX (1 symbols across 1 files) FILE: Sources/cmysql/shim.h type my_bool (line 7) | typedef int my_bool;
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (145K chars).
[
{
"path": ".circleci/config.yml",
"chars": 2693,
"preview": "version: 2\n\n\njobs:\n mac-swift5.3:\n macos:\n xcode: \"12.3.0\"\n steps:\n - checkout\n - run: brew tap no"
},
{
"path": ".gitignore",
"chars": 332,
"preview": "# OS X Finder\n.DS_Store\n\n# Xcode per-user config\n*.mode1\n*.mode1v3\n*.mode2v3\n*.perspective\n*.perspectivev3\n*.pbxuser\n#*."
},
{
"path": "LICENSE.md",
"chars": 1057,
"preview": "Copyright (c) 2015-18 Yusuke Ito\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this s"
},
{
"path": "Package.swift",
"chars": 785,
"preview": "// swift-tools-version:4.0\nimport PackageDescription\n\nlet package = Package(\n name: \"MySQL\",\n products: [\n "
},
{
"path": "Package@swift-5.swift",
"chars": 933,
"preview": "// swift-tools-version:5.0\nimport PackageDescription\n\nlet package = Package(\n name: \"mysql-swift\",\n products: [\n "
},
{
"path": "README.md",
"chars": 4062,
"preview": "mysql-swift\n===========\n\n**This library is obsolete and not maintained. Use [MySQLNIO](https://github.com/vapor/mysql-ni"
},
{
"path": "Sources/MySQL/AutoincrementID.swift",
"chars": 2305,
"preview": "//\n// AutoincrementID.swift\n// MySQL\n//\n// Created by Yusuke Ito on 6/27/16.\n// Copyright © 2016 Yusuke Ito. All rig"
},
{
"path": "Sources/MySQL/Blob.swift",
"chars": 1796,
"preview": "//\n// Blob.swift\n// MySQL\n//\n// Created by Yusuke Ito on 4/22/16.\n// Copyright © 2016 Yusuke Ito. All rights reserve"
},
{
"path": "Sources/MySQL/Connection.swift",
"chars": 4362,
"preview": "//\n// Database.swift\n// MySQL\n//\n// Created by ito on 2015/10/24.\n// Copyright © 2015年 Yusuke Ito. All rights reserv"
},
{
"path": "Sources/MySQL/ConnectionPool.swift",
"chars": 4965,
"preview": "//\n// ConnectionPool.swift\n// MySQL\n//\n// Created by ito on 12/24/15.\n// Copyright © 2015 Yusuke Ito. All rights res"
},
{
"path": "Sources/MySQL/Date.swift",
"chars": 7599,
"preview": "//\n// Date.swift\n// MySQL\n//\n// Created by ito on 12/16/15.\n// Copyright © 2015 Yusuke Ito. All rights reserved.\n//\n"
},
{
"path": "Sources/MySQL/Error.swift",
"chars": 943,
"preview": "//\n// Error.swift\n// MySQL\n//\n// Created by Yusuke Ito on 12/14/15.\n// Copyright © 2015 Yusuke Ito. All rights reser"
},
{
"path": "Sources/MySQL/IDType.swift",
"chars": 4654,
"preview": "//\n// IDType.swift\n// MySQL\n//\n// Created by Yusuke Ito on 6/27/16.\n// Copyright © 2016 Yusuke Ito. All rights reser"
},
{
"path": "Sources/MySQL/Query.swift",
"chars": 8335,
"preview": "//\n// Connection.swift\n// MySQL\n//\n// Created by ito on 2015/10/24.\n// Copyright © 2015年 Yusuke Ito. All rights rese"
},
{
"path": "Sources/MySQL/QueryParameter-Data.swift",
"chars": 603,
"preview": "//\n// QueryParameter-Data.swift\n// MySQL\n//\n// Created by Yusuke Ito on 4/29/18.\n//\n\nimport Foundation\n\npublic protoc"
},
{
"path": "Sources/MySQL/QueryParameterType.swift",
"chars": 14380,
"preview": "//\n// QueryParameterType.swift\n// MySQL\n//\n// Created by Yusuke Ito on 12/28/15.\n// Copyright © 2015 Yusuke Ito. All"
},
{
"path": "Sources/MySQL/RawRepresentableParameter.swift",
"chars": 739,
"preview": "//\n// RawRepresentableParameter.swift\n// MySQL\n//\n// Created by Yusuke Ito on 4/21/16.\n// Copyright © 2016 Yusuke It"
},
{
"path": "Sources/MySQL/Result-SQLRawStringDecodable.swift",
"chars": 4279,
"preview": "//\n// ResultTypes.swift\n// MySQL\n//\n// Created by Yusuke Ito on 12/28/15.\n// Copyright © 2015 Yusuke Ito. All rights"
},
{
"path": "Sources/MySQL/Result.swift",
"chars": 11234,
"preview": "//\n// Result.swift\n// MySQL\n//\n// Created by ito on 12/10/15.\n// Copyright © 2015 Yusuke Ito. All rights reserved.\n/"
},
{
"path": "Sources/MySQL/Sync.swift",
"chars": 1103,
"preview": "//\n// Sync.swift\n// MySQL\n//\n// Created by Yusuke Ito on 1/12/16.\n// Copyright © 2016 Yusuke Ito. All rights reserve"
},
{
"path": "Sources/MySQL/Transaction.swift",
"chars": 1177,
"preview": "//\n// Connection-Transaction.swift\n// MySQL\n//\n// Created by ito on 12/24/15.\n// Copyright © 2015 Yusuke Ito. All ri"
},
{
"path": "Sources/SQLFormatter/Error.swift",
"chars": 288,
"preview": "//\n// Error.swift\n// SQLFormatter\n//\n// Created by Yusuke Ito on 4/5/16.\n// Copyright © 2016 Yusuke Ito. All rights "
},
{
"path": "Sources/SQLFormatter/QueryFormatter.swift",
"chars": 4744,
"preview": "//\n// Query.swift\n// MySQL\n//\n// Created by Yusuke Ito on 12/14/15.\n// Copyright © 2015 Yusuke Ito. All rights reser"
},
{
"path": "Sources/cmysql/macos.pc",
"chars": 220,
"preview": "prefix=/usr/local/opt/mysql\nexec_prefix=${prefix}\nlibdir=${exec_prefix}/lib\nincludedir=${prefix}/include\nName: MySQL\nDes"
},
{
"path": "Sources/cmysql/module.modulemap",
"chars": 83,
"preview": "module CMySQL [system] {\n header \"shim.h\"\n link \"mysqlclient\"\n export *\n}\n"
},
{
"path": "Sources/cmysql/shim.h",
"chars": 149,
"preview": "#ifndef __CMYSQL_SHIM_H__\n#define __CMYSQL_SHIM_H__\n\n#include <mysql/mysql.h>\n\n#if LIBMYSQL_VERSION_ID >= 80000\ntypedef "
},
{
"path": "Tests/LinuxMain.swift",
"chars": 180,
"preview": "import XCTest\n\nimport MySQLTests\nimport SQLFormatterTests\n\nvar tests = [XCTestCaseEntry]()\ntests += MySQLTests.__allTest"
},
{
"path": "Tests/MySQLTests/BlobTests.swift",
"chars": 5289,
"preview": "//\n// SQLTypeTests.swift\n// MySQL\n//\n// Created by Yusuke Ito on 4/21/16.\n// Copyright © 2016 Yusuke Ito. All rights"
},
{
"path": "Tests/MySQLTests/ConnectionPoolTests.swift",
"chars": 3908,
"preview": "//\n// ConnectionPoolTests.swift\n// MySQL\n//\n// Created by ito on 12/24/15.\n// Copyright © 2015 Yusuke Ito. All right"
},
{
"path": "Tests/MySQLTests/ConnectionTests.swift",
"chars": 1047,
"preview": "//\n// ConnectionTest.swift\n// MySQL\n//\n// Created by ito on 12/20/15.\n// Copyright © 2015 Yusuke Ito. All rights res"
},
{
"path": "Tests/MySQLTests/DateTests.swift",
"chars": 4147,
"preview": "//\n// DateTests.swift\n// MySQL\n//\n// Created by ito on 12/20/15.\n// Copyright © 2015 Yusuke Ito. All rights reserved"
},
{
"path": "Tests/MySQLTests/EscapeTests.swift",
"chars": 5127,
"preview": "//\n// EscapeTests.swift\n// MySQL\n//\n// Created by ito on 12/20/15.\n// Copyright © 2015 Yusuke Ito. All rights reserv"
},
{
"path": "Tests/MySQLTests/Model.swift",
"chars": 490,
"preview": "//\n// Model.swift\n// MySQL\n//\n// Created by ito on 12/20/15.\n// Copyright © 2015 Yusuke Ito. All rights reserved.\n//"
},
{
"path": "Tests/MySQLTests/MySQLConnection.swift",
"chars": 1520,
"preview": "//\n// MySQLTests.swift\n// MySQLTests\n//\n// Created by ito on 2015/10/24.\n// Copyright © 2015年 Yusuke Ito. All rights"
},
{
"path": "Tests/MySQLTests/QueryDecimalTypeTests.swift",
"chars": 2300,
"preview": "//\n// QueryDecimalTypeTests.swift\n// MySQLTests\n//\n// Created by Yusuke Ito on 5/1/18.\n//\n\nimport XCTest\n@testable im"
},
{
"path": "Tests/MySQLTests/QueryFormatterTests.swift",
"chars": 2175,
"preview": "//\n// QueryFormatterTests.swift\n// MySQL\n//\n// Created by ito on 12/20/15.\n// Copyright © 2015 Yusuke Ito. All right"
},
{
"path": "Tests/MySQLTests/QueryParameterTests.swift",
"chars": 12456,
"preview": "//\n// QueryParameterTests.swift\n// MySQL\n//\n// Created by Yusuke Ito on 4/21/16.\n// Copyright © 2016 Yusuke Ito. All"
},
{
"path": "Tests/MySQLTests/QueryTests.swift",
"chars": 7646,
"preview": "//\n// QueryTests.swift\n// MySQL\n//\n// Created by ito on 12/20/15.\n// Copyright © 2015 Yusuke Ito. All rights reserve"
},
{
"path": "Tests/MySQLTests/QueryURLTypeTests.swift",
"chars": 2629,
"preview": "//\n// QueryURLTypeTests.swift\n// MySQLTests\n//\n// Created by Yusuke Ito on 5/1/18.\n//\n\nimport XCTest\n@testable import"
},
{
"path": "Tests/MySQLTests/XCTestManifests.swift",
"chars": 3306,
"preview": "import XCTest\n\nextension BlobQueryTests {\n static let __allTests = [\n (\"testBlobAndTextOnBinCollation\", testBl"
},
{
"path": "Tests/SQLFormatterTests/SQLFormatterTests.swift",
"chars": 307,
"preview": "//\n// SQLFormatterTests.swift\n// SQLFormatterTests\n//\n// Created by Yusuke Ito on 4/5/16.\n// Copyright © 2016 Yusuke"
},
{
"path": "Tests/SQLFormatterTests/XCTestManifests.swift",
"chars": 259,
"preview": "import XCTest\n\nextension SQLFormattingTests {\n static let __allTests = [\n (\"testDummy\", testDummy),\n ]\n}\n\n#"
}
]
About this extraction
This page contains the full source code of the novi/mysql-swift GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (133.4 KB), approximately 32.6k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.