├── .gitignore ├── .jazzy.yaml ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── PerfectPostgreSQL │ ├── PerfectPostgreSQL.swift │ ├── PostgresCRUD.swift │ ├── PostgresCRUDInsert.swift │ └── PostgresCRUDUpdate.swift └── Tests ├── LinuxMain.swift └── PerfectPostgreSQLTests ├── PerfectPostgreSQLTests.swift └── XCTestManifests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | *.resolved 26 | *.pins 27 | *.xcode* 28 | .build* 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/screenshots 68 | 69 | Packages/ 70 | PostgreSQL.xcodeproj/ 71 | .DS_Store 72 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | module: PerfectPostgreSQL 2 | author: PerfectlySoft 3 | author_url: https://perfect.org 4 | github_url: https://github.com/PerfectlySoft/Perfect-PostgreSQL 5 | copyright: '© 2016 PerfectlySoft Inc. and the Perfect project authors' 6 | theme: fullwidth 7 | xcodebuild_arguments: [-target, PostgreSQL, -toolchain, org.swift.3020160620a] 8 | clean: true 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // Package.swift 3 | // Perfect-PostgreSQL 4 | // 5 | // Created by Kyle Jessup on 3/22/16. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PackageDescription 21 | 22 | #if os(macOS) 23 | let package = Package( 24 | name: "PerfectPostgreSQL", 25 | platforms: [ 26 | .macOS(.v10_15) 27 | ], 28 | products: [ 29 | .library(name: "PerfectPostgreSQL", targets: ["PerfectPostgreSQL"]) 30 | ], 31 | dependencies: [ 32 | .package(url: "https://github.com/PerfectlySoft/Perfect-CRUD.git", from: "2.0.0"), 33 | .package(url: "https://github.com/PerfectlySoft/Perfect-libpq.git", from: "2.0.0"), 34 | ], 35 | targets: [ 36 | .target(name: "PerfectPostgreSQL", dependencies: ["PerfectCRUD"]), 37 | .testTarget(name: "PerfectPostgreSQLTests", dependencies: ["PerfectPostgreSQL"]) 38 | ] 39 | ) 40 | #else 41 | let package = Package( 42 | name: "PerfectPostgreSQL", 43 | products: [ 44 | .library(name: "PerfectPostgreSQL", targets: ["PerfectPostgreSQL"]) 45 | ], 46 | dependencies: [ 47 | .package(url: "https://github.com/PerfectlySoft/Perfect-CRUD.git", from: "2.0.0"), 48 | .package(url: "https://github.com/PerfectlySoft/Perfect-libpq-linux.git", from: "2.0.0"), 49 | ], 50 | targets: [ 51 | .target(name: "PerfectPostgreSQL", dependencies: ["PerfectCRUD"]), 52 | .testTarget(name: "PerfectPostgreSQLTests", dependencies: ["PerfectPostgreSQL"]) 53 | ] 54 | ) 55 | #endif 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect - PostgreSQL Connector 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 4.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | 43 | This project provides a Swift wrapper around the libpq client library, enabling access to PostgreSQL servers. 44 | 45 | This package builds with Swift Package Manager and is part of the [Perfect](https://github.com/PerfectlySoft/Perfect) project. It was written to be stand-alone and so does not require PerfectLib or any other components. 46 | 47 | Ensure you have installed and activated the latest Swift 4.0 tool chain. 48 | 49 | ## macOS Build Notes 50 | 51 | This package requires the [Home Brew](http://brew.sh) build of PostgreSQL. 52 | 53 | To install Home Brew: 54 | 55 | ``` 56 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 57 | ``` 58 | 59 | To install postgres: 60 | 61 | ``` 62 | brew install postgres 63 | ``` 64 | 65 | ## Linux Build Notes 66 | 67 | Ensure that you have installed libpq-dev. 68 | 69 | ``` 70 | sudo apt-get install libpq-dev 71 | ``` 72 | 73 | ## Building 74 | 75 | Add this project as a dependency in your Package.swift file. 76 | 77 | ``` 78 | .Package(url: "https://github.com/PerfectlySoft/Perfect-PostgreSQL.git", majorVersion: 3) 79 | ``` 80 | 81 | ## Documentation 82 | 83 | For more information, please visit [perfect.org](http://www.perfect.org/docs/PostgreSQL.html). 84 | -------------------------------------------------------------------------------- /Sources/PerfectPostgreSQL/PerfectPostgreSQL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostgreSQL.swift 3 | // PostgreSQL 4 | // 5 | // Created by Kyle Jessup on 2015-07-29. 6 | // Copyright (C) 2015 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import Foundation 21 | import libpq 22 | 23 | /// This enum type indicates an exception when dealing with a PostgreSQL database 24 | public enum PostgreSQLError : Error { 25 | /// Error with detail message. 26 | case error(String) 27 | } 28 | 29 | /// result object 30 | public final class PGResult { 31 | 32 | /// Result Status enum 33 | public enum StatusType { 34 | case emptyQuery 35 | case commandOK 36 | case tuplesOK 37 | case badResponse 38 | case nonFatalError 39 | case fatalError 40 | case singleTuple 41 | case unknown 42 | } 43 | 44 | private var res: OpaquePointer? = OpaquePointer(bitPattern: 0) 45 | private var borrowed = false 46 | 47 | init(_ res: OpaquePointer?, isBorrowed: Bool = false) { 48 | self.res = res 49 | self.borrowed = isBorrowed 50 | } 51 | 52 | deinit { 53 | close() 54 | } 55 | 56 | func isValid() -> Bool { 57 | return res != nil 58 | } 59 | 60 | /// close result object 61 | public func close() { 62 | clear() 63 | } 64 | 65 | /// clear and disconnect result object 66 | public func clear() { 67 | if let res = self.res { 68 | if !self.borrowed { 69 | PQclear(res) 70 | } 71 | self.res = OpaquePointer(bitPattern: 0) 72 | } 73 | } 74 | 75 | /// Result Status number as Int 76 | public func statusInt() -> Int { 77 | guard let res = self.res else { 78 | return 0 79 | } 80 | let s = PQresultStatus(res) 81 | return Int(s.rawValue) 82 | } 83 | 84 | /// Result Status Value 85 | public func status() -> StatusType { 86 | guard let res = self.res else { 87 | return .unknown 88 | } 89 | let s = PQresultStatus(res) 90 | switch(s.rawValue) { 91 | case PGRES_EMPTY_QUERY.rawValue: 92 | return .emptyQuery 93 | case PGRES_COMMAND_OK.rawValue: 94 | return .commandOK 95 | case PGRES_TUPLES_OK.rawValue: 96 | return .tuplesOK 97 | case PGRES_BAD_RESPONSE.rawValue: 98 | return .badResponse 99 | case PGRES_NONFATAL_ERROR.rawValue: 100 | return .nonFatalError 101 | case PGRES_FATAL_ERROR.rawValue: 102 | return .fatalError 103 | case PGRES_SINGLE_TUPLE.rawValue: 104 | return .singleTuple 105 | default: 106 | print("Unhandled PQresult status type \(s.rawValue)") 107 | } 108 | return .unknown 109 | } 110 | 111 | /// Result Status Message 112 | public func errorMessage() -> String { 113 | guard let res = self.res else { 114 | return "" 115 | } 116 | return String(validatingUTF8: PQresultErrorMessage(res)) ?? "" 117 | } 118 | 119 | /// Result field count 120 | public func numFields() -> Int { 121 | guard let res = self.res else { 122 | return 0 123 | } 124 | return Int(PQnfields(res)) 125 | } 126 | 127 | /// Field name for index value 128 | public func fieldName(index: Int) -> String? { 129 | guard let res = self.res, 130 | let fn = PQfname(res, Int32(index)), 131 | let ret = String(validatingUTF8: fn) else { 132 | return nil 133 | } 134 | return ret 135 | } 136 | 137 | /// Field type for index value 138 | public func fieldType(index: Int) -> Oid? { 139 | guard let res = self.res else { 140 | return nil 141 | } 142 | let fn = PQftype(res, Int32(index)) 143 | return fn 144 | } 145 | 146 | /// number of rows (Tuples) returned in result 147 | public func numTuples() -> Int { 148 | guard let res = self.res else { 149 | return 0 150 | } 151 | return Int(PQntuples(res)) 152 | } 153 | 154 | /// test null field at row index for field index 155 | public func fieldIsNull(tupleIndex: Int, fieldIndex: Int) -> Bool { 156 | return 1 == PQgetisnull(res, Int32(tupleIndex), Int32(fieldIndex)) 157 | } 158 | 159 | /// return value for String field type with row and field indexes provided 160 | public func getFieldString(tupleIndex: Int, fieldIndex: Int) -> String? { 161 | guard !fieldIsNull(tupleIndex: tupleIndex, fieldIndex: fieldIndex), 162 | let v = PQgetvalue(res, Int32(tupleIndex), Int32(fieldIndex)) else { 163 | return nil 164 | } 165 | return String(validatingUTF8: v) 166 | } 167 | 168 | /// return value for Bool field type with row and field indexes provided 169 | public func getFieldBool(tupleIndex: Int, fieldIndex: Int) -> Bool? { 170 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 171 | return nil 172 | } 173 | return s == "t" 174 | } 175 | 176 | /// return value for Int field type with row and field indexes provided 177 | public func getFieldInt(tupleIndex: Int, fieldIndex: Int) -> Int? { 178 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 179 | return nil 180 | } 181 | return Int(s) 182 | } 183 | 184 | /// return value for Int8 field type with row and field indexes provided 185 | public func getFieldInt8(tupleIndex: Int, fieldIndex: Int) -> Int8? { 186 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 187 | return nil 188 | } 189 | return Int8(s) 190 | } 191 | 192 | /// return value for Int16 field type with row and field indexes provided 193 | public func getFieldInt16(tupleIndex: Int, fieldIndex: Int) -> Int16? { 194 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 195 | return nil 196 | } 197 | return Int16(s) 198 | } 199 | 200 | /// return value for Int32 field type with row and field indexes provided 201 | public func getFieldInt32(tupleIndex: Int, fieldIndex: Int) -> Int32? { 202 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 203 | return nil 204 | } 205 | return Int32(s) 206 | } 207 | 208 | /// return value for Int64 field type with row and field indexes provided 209 | public func getFieldInt64(tupleIndex: Int, fieldIndex: Int) -> Int64? { 210 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 211 | return nil 212 | } 213 | return Int64(s) 214 | } 215 | 216 | /// return value for Int field type with row and field indexes provided 217 | public func getFieldUInt(tupleIndex: Int, fieldIndex: Int) -> UInt? { 218 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 219 | return nil 220 | } 221 | return UInt(s) 222 | } 223 | 224 | /// return value for Int8 field type with row and field indexes provided 225 | public func getFieldUInt8(tupleIndex: Int, fieldIndex: Int) -> UInt8? { 226 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 227 | return nil 228 | } 229 | return UInt8(s) 230 | } 231 | 232 | /// return value for Int16 field type with row and field indexes provided 233 | public func getFieldUInt16(tupleIndex: Int, fieldIndex: Int) -> UInt16? { 234 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 235 | return nil 236 | } 237 | return UInt16(s) 238 | } 239 | 240 | /// return value for Int32 field type with row and field indexes provided 241 | public func getFieldUInt32(tupleIndex: Int, fieldIndex: Int) -> UInt32? { 242 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 243 | return nil 244 | } 245 | return UInt32(s) 246 | } 247 | 248 | /// return value for Int64 field type with row and field indexes provided 249 | public func getFieldUInt64(tupleIndex: Int, fieldIndex: Int) -> UInt64? { 250 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 251 | return nil 252 | } 253 | return UInt64(s) 254 | } 255 | 256 | /// return value for Double field type with row and field indexes provided 257 | public func getFieldDouble(tupleIndex: Int, fieldIndex: Int) -> Double? { 258 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 259 | return nil 260 | } 261 | return Double(s) 262 | } 263 | 264 | /// return value for Float field type with row and field indexes provided 265 | public func getFieldFloat(tupleIndex: Int, fieldIndex: Int) -> Float? { 266 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 267 | return nil 268 | } 269 | return Float(s) 270 | } 271 | 272 | /// return value for Blob field type with row and field indexes provided 273 | public func getFieldBlob(tupleIndex: Int, fieldIndex: Int) -> [Int8]? { 274 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 275 | return nil 276 | } 277 | let sc = s.utf8 278 | guard sc.count % 2 == 0, sc.count >= 2, s[s.startIndex] == "\\", s[s.index(after: s.startIndex)] == "x" else { 279 | return nil 280 | } 281 | var ret = [Int8]() 282 | var index = sc.index(sc.startIndex, offsetBy: 2) 283 | while index != sc.endIndex { 284 | let c1 = Int8(sc[index]) 285 | index = sc.index(after: index) 286 | let c2 = Int8(sc[index]) 287 | guard let byte = byteFromHexDigits(one: c1, two: c2) else { 288 | return nil 289 | } 290 | ret.append(byte) 291 | index = sc.index(after: index) 292 | } 293 | return ret 294 | } 295 | 296 | private func byteFromHexDigits(one c1v: Int8, two c2v: Int8) -> Int8? { 297 | 298 | let capA: Int8 = 65 299 | let capF: Int8 = 70 300 | let lowA: Int8 = 97 301 | let lowF: Int8 = 102 302 | let zero: Int8 = 48 303 | let nine: Int8 = 57 304 | 305 | var newChar = Int8(0) 306 | 307 | if c1v >= capA && c1v <= capF { 308 | newChar = c1v - capA + 10 309 | } else if c1v >= lowA && c1v <= lowF { 310 | newChar = c1v - lowA + 10 311 | } else if c1v >= zero && c1v <= nine { 312 | newChar = c1v - zero 313 | } else { 314 | return nil 315 | } 316 | 317 | newChar = newChar &* 16 318 | 319 | if c2v >= capA && c2v <= capF { 320 | newChar += c2v - capA + 10 321 | } else if c2v >= lowA && c2v <= lowF { 322 | newChar += c2v - lowA + 10 323 | } else if c2v >= zero && c2v <= nine { 324 | newChar += c2v - zero 325 | } else { 326 | return nil 327 | } 328 | return newChar 329 | } 330 | } 331 | 332 | /// connection management class 333 | public final class PGConnection { 334 | 335 | /// Connection Status enum 336 | public enum StatusType { 337 | case ok 338 | case bad 339 | } 340 | 341 | var conn = OpaquePointer(bitPattern: 0) 342 | var connectInfo: String = "" 343 | 344 | /// empty init 345 | public init() { 346 | 347 | } 348 | 349 | deinit { 350 | close() 351 | } 352 | 353 | /// Makes a new connection to the database server. 354 | public func connectdb(_ info: String) -> StatusType { 355 | conn = PQconnectdb(info) 356 | connectInfo = info 357 | return status() 358 | } 359 | 360 | /// Close db connection 361 | public func close() { 362 | finish() 363 | } 364 | 365 | /// Closes the connection to the server. Also frees memory used by the PGconn object. 366 | public func finish() { 367 | if conn != nil { 368 | PQfinish(conn) 369 | conn = OpaquePointer(bitPattern: 0) 370 | } 371 | } 372 | 373 | /// Returns the status of the connection. 374 | public func status() -> StatusType { 375 | let status = PQstatus(conn) 376 | return status == CONNECTION_OK ? .ok : .bad 377 | } 378 | 379 | /// Returns the error message most recently generated by an operation on the connection. 380 | public func errorMessage() -> String { 381 | return String(validatingUTF8: PQerrorMessage(conn)) ?? "" 382 | } 383 | 384 | /// Submits a command to the server and waits for the result. 385 | public func exec(statement: String) -> PGResult { 386 | return PGResult(PQexec(conn, statement)) 387 | } 388 | 389 | /// Sends data to the server during COPY_IN state. 390 | public func putCopyData(data: String) { 391 | PQputCopyData(self.conn, data, Int32(data.count)) 392 | } 393 | 394 | /// Sends end-of-data indication to the server during COPY_IN state. 395 | /// If withError is set, the copy is forced to fail with the error description supplied. 396 | public func putCopyEnd(withError: String? = nil) -> PGResult { 397 | PQputCopyEnd(self.conn, withError) 398 | let result = PGResult(PQgetResult(self.conn)) 399 | return result 400 | } 401 | 402 | // !FIX! does not handle binary data 403 | /// Submits a command to the server and waits for the result, with the ability to pass parameters separately from the SQL command text. 404 | public func exec(statement: String, params: [Any?]) -> PGResult { 405 | let count = params.count 406 | let values = UnsafeMutablePointer?>.allocate(capacity: count) 407 | let types = UnsafeMutablePointer.allocate(capacity: count) 408 | let lengths = UnsafeMutablePointer.allocate(capacity: count) 409 | let formats = UnsafeMutablePointer.allocate(capacity: count) 410 | defer { 411 | values.deinitialize(count: count) ; values.deallocate() 412 | types.deinitialize(count: count) ; types.deallocate() 413 | lengths.deinitialize(count: count) ; lengths.deallocate() 414 | formats.deinitialize(count: count) ; formats.deallocate() 415 | } 416 | var asStrings = [String]() 417 | var temps = [[UInt8]]() 418 | for idx in 0..(OpaquePointer(temps.last!)) 425 | types[idx] = 0 426 | lengths[idx] = 0 427 | formats[idx] = 0 428 | case let a as [UInt8]: 429 | let length = Int32(a.count) 430 | values[idx] = UnsafePointer(OpaquePointer(a)) 431 | types[idx] = 17 432 | lengths[idx] = length 433 | formats[idx] = 1 434 | case let a as [Int8]: 435 | let length = Int32(a.count) 436 | values[idx] = UnsafePointer(OpaquePointer(a)) 437 | types[idx] = 17 438 | lengths[idx] = length 439 | formats[idx] = 1 440 | case let d as Data: 441 | let a = d.map { $0 } 442 | let length = Int32(a.count) 443 | temps.append(a) 444 | values[idx] = UnsafePointer(OpaquePointer(temps.last!)) 445 | types[idx] = 17 446 | lengths[idx] = length 447 | formats[idx] = 1 448 | default: 449 | if let pm = params[idx] { 450 | asStrings.append("\(pm)") 451 | var aa = [UInt8](asStrings.last!.utf8) 452 | aa.append(0) 453 | temps.append(aa) 454 | values[idx] = UnsafePointer(OpaquePointer(temps.last!)) 455 | } else { 456 | values[idx] = nil 457 | }//end if 458 | types[idx] = 0 459 | lengths[idx] = 0 460 | formats[idx] = 0 461 | } 462 | } 463 | let r = PQexecParams(conn, statement, Int32(count), nil, values, lengths, formats, Int32(0)) 464 | return PGResult(r) 465 | } 466 | 467 | /// Assert that the connection status is OK 468 | /// 469 | /// - throws: If the connection status is bad 470 | public func ensureStatusIsOk() throws { 471 | switch status() { 472 | case .ok: 473 | // connection status is good 474 | return 475 | case .bad: 476 | throw PostgreSQLError.error("Connection status is bad") 477 | } 478 | } 479 | 480 | /// The binding values 481 | public typealias ExecuteParameterArray = [Any?] 482 | 483 | /// Execute the given statement. 484 | /// 485 | /// - parameter statement: String statement to be executed 486 | /// - parameter params: ExecuteParameterArray? optional bindings 487 | /// - throws: If the status is an error 488 | @discardableResult 489 | public func execute(statement: String, params: ExecuteParameterArray? = nil) throws -> PGResult { 490 | try ensureStatusIsOk() 491 | let res: PGResult 492 | if let params = params { 493 | res = exec(statement: statement, params: params) 494 | } else { 495 | res = exec(statement: statement) 496 | } 497 | let status: PGResult.StatusType = res.status() 498 | switch status { 499 | case .emptyQuery, .commandOK, .tuplesOK: 500 | return res 501 | case .badResponse, .nonFatalError, .fatalError, .singleTuple, .unknown: 502 | throw PostgreSQLError.error("Failed to execute statement. status: \(status)") 503 | } 504 | } 505 | 506 | /// Executes a BEGIN, calls the provided closure and executes a ROLLBACK if an exception occurs or a COMMIT if no exception occurs. 507 | /// 508 | /// - parameter closure: Block to be executed inside transaction 509 | /// - throws: If the provided closure fails 510 | /// - returns: If successful then the return value from the `closure` 511 | public func doWithTransaction(closure: () throws -> Result) throws -> Result { 512 | try ensureStatusIsOk() 513 | try execute(statement: "BEGIN") 514 | do { 515 | let result: Result = try closure() 516 | try execute(statement: "COMMIT") 517 | return result 518 | } catch { 519 | try execute(statement: "ROLLBACK") 520 | throw error 521 | } 522 | } 523 | 524 | /// Handler for receiving a PGResult 525 | public typealias ReceiverProc = (PGResult) -> Void 526 | 527 | /// Handler for processing a text message 528 | public typealias ProcessorProc = (String) -> Void 529 | 530 | /// internal callback for notice receiving 531 | internal var receiver: ReceiverProc = { _ in } 532 | 533 | /// internal callback for notice processing 534 | internal var processor: ProcessorProc = { _ in } 535 | 536 | /// Set a new notice receiver 537 | /// - parameter handler: a closure to handle the incoming notice 538 | /// - returns: a C convention function pointer; would be nil if failed to set. 539 | public func setReceiver(_ handler: @escaping ReceiverProc) -> PQnoticeReceiver? { 540 | guard let cn = self.conn else { 541 | return nil 542 | } 543 | self.receiver = handler 544 | let me = Unmanaged.passUnretained(self).toOpaque() 545 | return PQsetNoticeReceiver(cn, { arg, result in 546 | guard let pointer = arg, let res = result else { 547 | return 548 | } 549 | let this = Unmanaged.fromOpaque(pointer).takeUnretainedValue() 550 | let pgresult = PGResult(res, isBorrowed: true) 551 | this.receiver(pgresult) 552 | }, me) 553 | } 554 | 555 | /// Set a new notice processor 556 | /// - parameter handler: a closure to handle the incoming notice 557 | /// - returns: a C convention function pointer; would be nil if failed to set. 558 | public func setProcessor(_ handler: @escaping ProcessorProc) -> PQnoticeProcessor?{ 559 | guard let cn = self.conn else { 560 | return nil 561 | } 562 | self.processor = handler 563 | let me = Unmanaged.passUnretained(self).toOpaque() 564 | return PQsetNoticeProcessor(cn, {arg, msg in 565 | guard let pointer = arg, let message = msg else { 566 | return 567 | } 568 | let this = Unmanaged.fromOpaque(pointer).takeUnretainedValue() 569 | let strmsg = String(cString: message) 570 | this.processor(strmsg) 571 | }, me) 572 | } 573 | } 574 | 575 | #if !swift(>=4.1) 576 | // Added for Swift 4.0/4.1 compat 577 | extension UnsafeMutableRawBufferPointer { 578 | static func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawBufferPointer { 579 | return allocate(count: byteCount) 580 | } 581 | } 582 | extension UnsafeMutablePointer { 583 | func deallocate() { 584 | deallocate(capacity: 0) 585 | } 586 | } 587 | extension Collection { 588 | func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { 589 | return try flatMap(transform) 590 | } 591 | } 592 | #endif 593 | -------------------------------------------------------------------------------- /Sources/PerfectPostgreSQL/PostgresCRUD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostgresCRUD.swift 3 | // PerfectCRUD 4 | // 5 | // Created by Kyle Jessup on 2017-12-04. 6 | // 7 | 8 | import Foundation 9 | import PerfectCRUD 10 | 11 | public struct PostgresCRUDError: Error, CustomStringConvertible { 12 | public let description: String 13 | public init(_ msg: String) { 14 | description = msg 15 | CRUDLogging.log(.error, msg) 16 | } 17 | } 18 | 19 | extension PGResult { 20 | public func getFieldBlobUInt8(tupleIndex: Int, fieldIndex: Int) -> [UInt8]? { 21 | guard let s = getFieldString(tupleIndex: tupleIndex, fieldIndex: fieldIndex) else { 22 | return nil 23 | } 24 | let sc = s.utf8 25 | guard sc.count % 2 == 0, sc.count >= 2, s[s.startIndex] == "\\", s[s.index(after: s.startIndex)] == "x" else { 26 | return nil 27 | } 28 | var ret = [UInt8]() 29 | var index = sc.index(sc.startIndex, offsetBy: 2) 30 | while index != sc.endIndex { 31 | let c1 = UInt8(sc[index]) 32 | index = sc.index(after: index) 33 | let c2 = UInt8(sc[index]) 34 | guard let byte = byteFromHexDigits(one: c1, two: c2) else { 35 | return nil 36 | } 37 | ret.append(byte) 38 | index = sc.index(after: index) 39 | } 40 | return ret 41 | } 42 | 43 | private func byteFromHexDigits(one c1v: UInt8, two c2v: UInt8) -> UInt8? { 44 | let capA: UInt8 = 65 45 | let capF: UInt8 = 70 46 | let lowA: UInt8 = 97 47 | let lowF: UInt8 = 102 48 | let zero: UInt8 = 48 49 | let nine: UInt8 = 57 50 | var newChar = UInt8(0) 51 | if c1v >= capA && c1v <= capF { 52 | newChar = c1v - capA + 10 53 | } else if c1v >= lowA && c1v <= lowF { 54 | newChar = c1v - lowA + 10 55 | } else if c1v >= zero && c1v <= nine { 56 | newChar = c1v - zero 57 | } else { 58 | return nil 59 | } 60 | newChar *= 16 61 | if c2v >= capA && c2v <= capF { 62 | newChar += c2v - capA + 10 63 | } else if c2v >= lowA && c2v <= lowF { 64 | newChar += c2v - lowA + 10 65 | } else if c2v >= zero && c2v <= nine { 66 | newChar += c2v - zero 67 | } else { 68 | return nil 69 | } 70 | return newChar 71 | } 72 | } 73 | 74 | class PostgresCRUDRowReader: KeyedDecodingContainerProtocol { 75 | typealias Key = K 76 | var codingPath: [CodingKey] = [] 77 | var allKeys: [Key] = [] 78 | let results: PGResult 79 | let tupleIndex: Int 80 | let fieldNames: [String:Int] 81 | init(results r: PGResult, tupleIndex ti: Int, fieldNames fn: [String:Int]) { 82 | results = r 83 | tupleIndex = ti 84 | fieldNames = fn 85 | } 86 | func ensureIndex(forKey key: Key) throws -> Int { 87 | guard let index = fieldNames[key.stringValue.lowercased()] else { 88 | throw PostgresCRUDError("No index for column \(key.stringValue)") 89 | } 90 | return index 91 | } 92 | func contains(_ key: Key) -> Bool { 93 | return fieldNames[key.stringValue.lowercased()] != nil 94 | } 95 | func decodeNil(forKey key: Key) throws -> Bool { 96 | return results.fieldIsNull(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) 97 | } 98 | func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { 99 | return results.getFieldBool(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? false 100 | } 101 | func decode(_ type: Int.Type, forKey key: Key) throws -> Int { 102 | return results.getFieldInt(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 103 | } 104 | func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { 105 | return results.getFieldInt8(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 106 | } 107 | func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { 108 | return results.getFieldInt16(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 109 | } 110 | func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { 111 | return results.getFieldInt32(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 112 | } 113 | func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { 114 | return results.getFieldInt64(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 115 | } 116 | func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { 117 | return results.getFieldUInt(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 118 | } 119 | func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { 120 | return results.getFieldUInt8(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 121 | } 122 | func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { 123 | return results.getFieldUInt16(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 124 | } 125 | func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { 126 | return results.getFieldUInt32(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 127 | } 128 | func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { 129 | return results.getFieldUInt64(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 130 | } 131 | func decode(_ type: Float.Type, forKey key: Key) throws -> Float { 132 | return results.getFieldFloat(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 133 | } 134 | func decode(_ type: Double.Type, forKey key: Key) throws -> Double { 135 | return results.getFieldDouble(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? 0 136 | } 137 | func decode(_ type: String.Type, forKey key: Key) throws -> String { 138 | return results.getFieldString(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) ?? "" 139 | } 140 | func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { 141 | let index = try ensureIndex(forKey: key) 142 | guard let special = SpecialType(type) else { 143 | throw CRUDDecoderError("Unsupported type: \(type) for key: \(key.stringValue)") 144 | } 145 | switch special { 146 | case .uint8Array: 147 | let ret: [UInt8] = results.getFieldBlobUInt8(tupleIndex: tupleIndex, fieldIndex: index) ?? [] 148 | return ret as! T 149 | case .int8Array: 150 | let ret: [Int8] = results.getFieldBlob(tupleIndex: tupleIndex, fieldIndex: index) ?? [] 151 | return ret as! T 152 | case .data: 153 | let bytes: [UInt8] = results.getFieldBlobUInt8(tupleIndex: tupleIndex, fieldIndex: index) ?? [] 154 | return Data(bytes) as! T 155 | case .uuid: 156 | let str = results.getFieldString(tupleIndex: tupleIndex, fieldIndex: index) ?? "" 157 | guard let ret = UUID(uuidString: str) else { 158 | throw CRUDDecoderError("Invalid UUID string \(str).") 159 | } 160 | return ret as! T 161 | case .date: 162 | let str = results.getFieldString(tupleIndex: tupleIndex, fieldIndex: index) ?? "" 163 | guard let date = Date(fromISO8601: str) else { 164 | throw CRUDDecoderError("Invalid Date string \(str).") 165 | } 166 | return date as! T 167 | case .url: 168 | let str = results.getFieldString(tupleIndex: tupleIndex, fieldIndex: index) ?? "" 169 | guard let url = URL(string: str) else { 170 | throw CRUDDecoderError("Invalid URL string \(str).") 171 | } 172 | return url as! T 173 | case .codable: 174 | guard let data0 = results.getFieldString(tupleIndex: tupleIndex, fieldIndex: try ensureIndex(forKey: key)) else { 175 | throw CRUDDecoderError("Unsupported type: \(type) for key: \(key.stringValue)") 176 | } 177 | if type == String.self { 178 | return data0 as! T 179 | } 180 | let container = data0.count >= 1 && (data0[data0.startIndex] == "[" || data0[data0.startIndex] == "{") 181 | guard let data = data0.data(using: .utf8) else { 182 | throw CRUDDecoderError("Invalid data for type: \(type) for key: \(key.stringValue)") 183 | } 184 | if container { 185 | return try JSONDecoder().decode(type, from: data) 186 | } 187 | guard let obj = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? T else { 188 | throw CRUDDecoderError("Invalid data for type: \(type) for key: \(key.stringValue)") 189 | } 190 | return obj 191 | case .wrapped: 192 | let decoder = CRUDColumnValueDecoder(source: KeyedDecodingContainer(self), key: key) 193 | return try T(from: decoder) 194 | } 195 | } 196 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { 197 | throw CRUDDecoderError("Unimplimented nestedContainer") 198 | } 199 | func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { 200 | throw CRUDDecoderError("Unimplimented nestedUnkeyedContainer") 201 | } 202 | func superDecoder() throws -> Decoder { 203 | throw CRUDDecoderError("Unimplimented superDecoder") 204 | } 205 | func superDecoder(forKey key: Key) throws -> Decoder { 206 | throw CRUDDecoderError("Unimplimented superDecoder") 207 | } 208 | } 209 | 210 | struct PostgresColumnInfo: Codable { 211 | let column_name: String 212 | let data_type: String 213 | } 214 | 215 | class PostgresGenDelegate: SQLGenDelegate { 216 | let connection: PGConnection 217 | var parentTableStack: [TableStructure] = [] 218 | var bindings: Bindings = [] 219 | 220 | init(connection c: PGConnection) { 221 | connection = c 222 | } 223 | func getBinding(for expr: Expression) throws -> String { 224 | let id = "$\(bindings.count+1)" 225 | bindings.append((id, expr)) 226 | return id 227 | } 228 | func quote(identifier: String) throws -> String { 229 | return "\"\(identifier.lowercased())\"" 230 | } 231 | func getCreateTableSQL(forTable: TableStructure, policy: TableCreatePolicy) throws -> [String] { 232 | parentTableStack.append(forTable) 233 | defer { 234 | parentTableStack.removeLast() 235 | } 236 | var sub: [String] = [] 237 | if policy.contains(.dropTable) { 238 | sub += ["DROP TABLE IF EXISTS \(try quote(identifier: forTable.tableName)) CASCADE"] 239 | } 240 | if !policy.contains(.dropTable), 241 | policy.contains(.reconcileTable), 242 | let existingColumns = getExistingColumnData(forTable: forTable.tableName) { 243 | let existingColumnMap: [String:PostgresColumnInfo] = .init(uniqueKeysWithValues: existingColumns.map { ($0.column_name, $0) }) 244 | let newColumnMap: [String:TableStructure.Column] = .init(uniqueKeysWithValues: forTable.columns.map { ($0.name.lowercased(), $0) }) 245 | 246 | let addColumns = newColumnMap.keys.filter { existingColumnMap[$0] == nil } 247 | let removeColumns: [String] = existingColumnMap.keys.filter { newColumnMap[$0] == nil } 248 | 249 | sub += try removeColumns.map { 250 | return """ 251 | ALTER TABLE \(try quote(identifier: forTable.tableName)) DROP COLUMN \(try quote(identifier: $0)) 252 | """ 253 | } 254 | sub += try addColumns.compactMap { newColumnMap[$0] }.map { 255 | let nameType = try getColumnDefinition($0) 256 | return """ 257 | ALTER TABLE \(try quote(identifier: forTable.tableName)) ADD COLUMN \(nameType) 258 | """ 259 | } 260 | return sub 261 | } else { 262 | sub += [ 263 | """ 264 | CREATE TABLE IF NOT EXISTS \(try quote(identifier: forTable.tableName)) ( 265 | \(try forTable.columns.map { try getColumnDefinition($0) }.joined(separator: ",\n\t")) 266 | ) 267 | """] 268 | } 269 | if !policy.contains(.shallow) { 270 | sub += try forTable.subTables.flatMap { 271 | try getCreateTableSQL(forTable: $0, policy: policy) 272 | } 273 | } 274 | 275 | return sub 276 | } 277 | func getExistingColumnData(forTable: String) -> [PostgresColumnInfo]? { 278 | do { 279 | let statement = 280 | """ 281 | SELECT column_name, data_type 282 | FROM INFORMATION_SCHEMA.COLUMNS 283 | WHERE table_name = $1 284 | """ 285 | let exeDelegate = PostgresExeDelegate(connection: connection, sql: statement) 286 | exeDelegate.nextBindings = [("$1", .string(forTable.lowercased()))] 287 | var ret: [PostgresColumnInfo] = [] 288 | while try exeDelegate.hasNext() { 289 | let rowDecoder: CRUDRowDecoder = CRUDRowDecoder(delegate: exeDelegate) 290 | ret.append(try PostgresColumnInfo(from: rowDecoder)) 291 | } 292 | guard !ret.isEmpty else { 293 | return nil 294 | } 295 | return ret 296 | } catch { 297 | return nil 298 | } 299 | } 300 | private func getTypeName(_ type: Any.Type) throws -> String { 301 | let typeName: String 302 | switch type { 303 | case is Int.Type: 304 | typeName = "bigint" 305 | case is Int8.Type: 306 | typeName = "smallint" 307 | case is Int16.Type: 308 | typeName = "smallint" 309 | case is Int32.Type: 310 | typeName = "integer" 311 | case is Int64.Type: 312 | typeName = "bigint" 313 | case is UInt.Type: 314 | typeName = "bigint" 315 | case is UInt8.Type: 316 | typeName = "smallint" 317 | case is UInt16.Type: 318 | typeName = "integer" 319 | case is UInt32.Type: 320 | typeName = "bigint" 321 | case is UInt64.Type: 322 | typeName = "bigint" 323 | case is Double.Type: 324 | typeName = "double precision" 325 | case is Float.Type: 326 | typeName = "real" 327 | case is Bool.Type: 328 | typeName = "boolean" 329 | case is String.Type: 330 | typeName = "text" 331 | default: 332 | guard let special = SpecialType(type) else { 333 | throw PostgresCRUDError("Unsupported SQL column type \(type)") 334 | } 335 | switch special { 336 | case .uint8Array: 337 | typeName = "bytea" 338 | case .int8Array: 339 | typeName = "bytea" 340 | case .data: 341 | typeName = "bytea" 342 | case .uuid: 343 | typeName = "uuid" 344 | case .date: 345 | typeName = "timestamp with time zone" 346 | case .url: 347 | typeName = "text" 348 | case .codable: 349 | typeName = "jsonb" 350 | case .wrapped: 351 | guard let w = type as? WrappedCodableProvider.Type else { 352 | throw PostgresCRUDError("Unsupported SQL column type \(type)") 353 | } 354 | return try getTypeName(w) 355 | } 356 | } 357 | return typeName 358 | } 359 | func getColumnDefinition(_ column: TableStructure.Column) throws -> String { 360 | let name = column.name 361 | let type = column.type 362 | let typeName = try getTypeName(type) 363 | var addendum = "" 364 | if !column.properties.contains(.primaryKey) && !column.optional { 365 | addendum += " NOT NULL" 366 | } 367 | for prop in column.properties { 368 | switch prop { 369 | case .primaryKey: 370 | addendum += " PRIMARY KEY" 371 | case .foreignKey(let table, let column, let onDelete, let onUpdate): 372 | addendum += " REFERENCES \(try quote(identifier: table))(\(try quote(identifier: column)))" 373 | let scenarios = [(" ON DELETE ", onDelete), (" ON UPDATE ", onUpdate)] 374 | for (scenario, action) in scenarios { 375 | addendum += scenario 376 | switch action { 377 | case .ignore: 378 | addendum += "NO ACTION" 379 | case .restrict: 380 | addendum += "RESTRICT" 381 | case .setNull: 382 | addendum += "SET NULL" 383 | case .setDefault: 384 | addendum += "SET DEFAULT" 385 | case .cascade: 386 | addendum += "CASCADE" 387 | } 388 | } 389 | } 390 | } 391 | return "\(try quote(identifier: name)) \(typeName)\(addendum)" 392 | } 393 | func getCreateIndexSQL(forTable name: String, on columns: [String], unique: Bool) throws -> [String] { 394 | let stat = 395 | """ 396 | CREATE \(unique ? "UNIQUE " : "")INDEX IF NOT EXISTS \(try quote(identifier: "index_\(columns.joined(separator: "_"))")) 397 | ON \(try quote(identifier: name)) (\(try columns.map{try quote(identifier: $0)}.joined(separator: ","))) 398 | """ 399 | return [stat] 400 | } 401 | } 402 | 403 | class PostgresExeDelegate: SQLExeDelegate { 404 | var nextBindings: Bindings = [] 405 | let connection: PGConnection 406 | let sql: String 407 | var results: PGResult? 408 | var tupleIndex = -1 409 | var numTuples = 0 410 | var fieldNames: [String:Int] = [:] 411 | init(connection c: PGConnection, sql s: String) { 412 | connection = c 413 | sql = s 414 | } 415 | func bind(_ bindings: Bindings, skip: Int) throws { 416 | results = nil 417 | if skip == 0 { 418 | nextBindings = bindings 419 | } else { 420 | nextBindings = nextBindings[0.. Bool { 431 | tupleIndex += 1 432 | if nil == results { 433 | let r = try connection.exec(statement: sql, 434 | params: nextBindings.map { 435 | try bindOne(expr: $0.1) }) 436 | results = r 437 | guard r.isValid() else { 438 | switch connection.status() { 439 | case .ok: 440 | throw CRUDSQLExeError("Fatal error on SQL execution") 441 | case .bad: 442 | throw CRUDSQLExeError(connection.errorMessage()) 443 | } 444 | } 445 | let status = r.status() 446 | switch status { 447 | case .emptyQuery: 448 | return false 449 | case .commandOK, .tuplesOK, .singleTuple: 450 | numTuples = r.numTuples() 451 | for i in 0..() throws -> KeyedDecodingContainer? where A : CodingKey { 469 | guard let results = self.results else { 470 | return nil 471 | } 472 | let ret = KeyedDecodingContainer(PostgresCRUDRowReader(results: results, 473 | tupleIndex: tupleIndex, 474 | fieldNames: fieldNames)) 475 | return ret 476 | } 477 | 478 | private func bindOne(expr: CRUDExpression) throws -> Any? { 479 | switch expr { 480 | case .lazy(let e): 481 | return try bindOne(expr: e()) 482 | case .integer(let i): 483 | return i 484 | case .uinteger(let i): 485 | return i 486 | case .integer64(let i): 487 | return i 488 | case .uinteger64(let i): 489 | return i 490 | case .integer32(let i): 491 | return i 492 | case .uinteger32(let i): 493 | return i 494 | case .integer16(let i): 495 | return i 496 | case .uinteger16(let i): 497 | return i 498 | case .integer8(let i): 499 | return i 500 | case .uinteger8(let i): 501 | return i 502 | case .decimal(let d): 503 | return d 504 | case .float(let f): 505 | return f 506 | case .string(let s): 507 | return s 508 | case .blob(let b): 509 | return b 510 | case .sblob(let b): 511 | return b 512 | case .bool(let b): 513 | return b 514 | case .date(let d): 515 | return d.iso8601() 516 | case .url(let u): 517 | return u.absoluteString 518 | case .uuid(let u): 519 | return u.uuidString 520 | case .null: 521 | return nil as String? 522 | case .column(_), .and(_, _), .or(_, _), 523 | .equality(_, _), .inequality(_, _), 524 | .not(_), .lessThan(_, _), .lessThanEqual(_, _), 525 | .greaterThan(_, _), .greaterThanEqual(_, _), 526 | .keyPath(_), .in(_, _), .like(_, _, _, _): 527 | throw PostgresCRUDError("Asked to bind unsupported expression type: \(expr)") 528 | } 529 | } 530 | } 531 | 532 | public struct PostgresDatabaseConfiguration: DatabaseConfigurationProtocol { 533 | let connection: PGConnection 534 | 535 | public init(url: String?, 536 | name: String?, 537 | host: String?, 538 | port: Int?, 539 | user: String?, 540 | pass: String?) throws { 541 | if let connectionInfo = url { 542 | try self.init(connectionInfo) 543 | } else { 544 | guard let database = name, let host = host else { 545 | throw PostgresCRUDError("Database name and host must be provided.") 546 | } 547 | try self.init(database: database, host: host, port: port, username: user, password: pass) 548 | } 549 | } 550 | 551 | public init(database: String, host: String, port: Int? = nil, username: String? = nil, password: String? = nil) throws { 552 | var s = "host=\(host) dbname=\(database)" 553 | if let p = port { 554 | s += " port=\(p)" 555 | } 556 | if let u = username { 557 | s += " user=\(u)" 558 | } 559 | if let p = password { 560 | s += " password=\(p)" 561 | } 562 | try self.init(s) 563 | } 564 | public init(_ connectionInfo: String) throws { 565 | let con = PGConnection() 566 | guard case .ok = con.connectdb(connectionInfo) else { 567 | throw PostgresCRUDError("Could not connect. \(con.errorMessage())") 568 | } 569 | connection = con 570 | } 571 | public var sqlGenDelegate: SQLGenDelegate { 572 | return PostgresGenDelegate(connection: connection) 573 | } 574 | public func sqlExeDelegate(forSQL: String) throws -> SQLExeDelegate { 575 | return PostgresExeDelegate(connection: connection, sql: forSQL) 576 | } 577 | } 578 | -------------------------------------------------------------------------------- /Sources/PerfectPostgreSQL/PostgresCRUDInsert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostgresCRUDInsert.swift 3 | // PerfectPostgreSQL 4 | // 5 | // Created by Kyle Jessup on 2019-01-29. 6 | // 7 | 8 | import Foundation 9 | import PerfectCRUD 10 | 11 | // Promises that instances.count == returnValue.count on exit 12 | // otherwise it will throw 13 | private func _insert(fromTable ft: FromTableType, 14 | instances: [OverAllForm], 15 | includeKeys: [PartialKeyPath], 16 | excludeKeys: [PartialKeyPath]) throws -> [OverAllForm] { 17 | typealias OAF = OverAllForm 18 | let delegate = ft.databaseConfiguration.sqlGenDelegate 19 | var state = SQLGenState(delegate: delegate) 20 | state.command = .insert 21 | try ft.setState(state: &state) 22 | let td = state.tableData[0] 23 | let kpDecoder = td.keyPathDecoder 24 | guard let kpInstance = td.modelInstance else { 25 | throw CRUDSQLGenError("Could not get model instance for key path decoder \(OAF.self)") 26 | } 27 | guard let databaseConfiguration = ft.databaseConfiguration as? PostgresDatabaseConfiguration else { 28 | throw CRUDSQLGenError("This is for Postgres only.") 29 | } 30 | let includeNames: [String] 31 | if includeKeys.isEmpty { 32 | let columnDecoder = CRUDColumnNameDecoder() 33 | _ = try OverAllForm.init(from: columnDecoder) 34 | includeNames = columnDecoder.collectedKeys.map { $0.name } 35 | } else { 36 | includeNames = try includeKeys.map { 37 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 38 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 39 | } 40 | return n 41 | } 42 | } 43 | let excludeNames: [String] = try excludeKeys.map { 44 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 45 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 46 | } 47 | return n 48 | } 49 | 50 | let encoder = try CRUDBindingsEncoder(delegate: delegate) 51 | try instances[0].encode(to: encoder) 52 | 53 | let bindings = try encoder.completedBindings(allKeys: includeNames, ignoreKeys: Set(excludeNames)) 54 | let columnNames = try bindings.map { try delegate.quote(identifier: $0.column) } 55 | let bindIdentifiers = bindings.map { $0.identifier } 56 | 57 | let nameQ = try delegate.quote(identifier: "\(OAF.CRUDTableName)") 58 | let sqlStr = """ 59 | INSERT INTO \(nameQ) (\(columnNames.joined(separator: ", "))) 60 | VALUES (\(bindIdentifiers.joined(separator: ", "))) 61 | RETURNING * 62 | """ 63 | CRUDLogging.log(.query, sqlStr) 64 | let exeDelegate = PostgresExeDelegate(connection: databaseConfiguration.connection, sql: sqlStr) 65 | try exeDelegate.bind(delegate.bindings) 66 | guard try exeDelegate.hasNext() else { 67 | throw CRUDSQLGenError("Did not get return value from statement \(sqlStr).") 68 | } 69 | var ret: [OverAllForm] = [] 70 | ret.append(try OverAllForm(from: CRUDRowDecoder(delegate: exeDelegate))) 71 | for instance in instances[1...] { 72 | exeDelegate.resetResults() 73 | let delegate = databaseConfiguration.sqlGenDelegate 74 | let encoder = try CRUDBindingsEncoder(delegate: delegate) 75 | try instance.encode(to: encoder) 76 | _ = try encoder.completedBindings(allKeys: includeNames, ignoreKeys: Set(excludeNames)) 77 | try exeDelegate.bind(delegate.bindings) 78 | guard try exeDelegate.hasNext() else { 79 | throw CRUDSQLGenError("Did not get return value from statement \(sqlStr).") 80 | } 81 | ret.append(try OverAllForm(from: CRUDRowDecoder(delegate: exeDelegate))) 82 | } 83 | return ret 84 | } 85 | 86 | // Promises that instances.count == returnValue.count on exit 87 | // otherwise it will throw 88 | private func _insert(fromTable ft: FromTableType, 89 | instances: [OverAllForm], 90 | returning: KeyPath, 91 | includeKeys: [PartialKeyPath], 92 | excludeKeys: [PartialKeyPath]) throws -> [R] { 93 | typealias OAF = OverAllForm 94 | let delegate = ft.databaseConfiguration.sqlGenDelegate 95 | var state = SQLGenState(delegate: delegate) 96 | state.command = .insert 97 | try ft.setState(state: &state) 98 | let td = state.tableData[0] 99 | let kpDecoder = td.keyPathDecoder 100 | guard let kpInstance = td.modelInstance else { 101 | throw CRUDSQLGenError("Could not get model instance for key path decoder \(OAF.self)") 102 | } 103 | guard let returningName = try kpDecoder.getKeyPathName(kpInstance, keyPath: returning) else { 104 | throw CRUDSQLGenError("Could not get column name for `returning` key path \(returning).") 105 | } 106 | guard let databaseConfiguration = ft.databaseConfiguration as? PostgresDatabaseConfiguration else { 107 | throw CRUDSQLGenError("This is for Postgres only.") 108 | } 109 | let includeNames: [String] 110 | if includeKeys.isEmpty { 111 | let columnDecoder = CRUDColumnNameDecoder() 112 | _ = try OverAllForm.init(from: columnDecoder) 113 | includeNames = columnDecoder.collectedKeys.map { $0.name } 114 | } else { 115 | includeNames = try includeKeys.map { 116 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 117 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 118 | } 119 | return n 120 | } 121 | } 122 | let excludeNames: [String] = try excludeKeys.map { 123 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 124 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 125 | } 126 | return n 127 | } 128 | 129 | let encoder = try CRUDBindingsEncoder(delegate: delegate) 130 | try instances[0].encode(to: encoder) 131 | 132 | let bindings = try encoder.completedBindings(allKeys: includeNames, ignoreKeys: Set(excludeNames)) 133 | let columnNames = try bindings.map { try delegate.quote(identifier: $0.column) } 134 | let bindIdentifiers = bindings.map { $0.identifier } 135 | 136 | let nameQ = try delegate.quote(identifier: "\(OAF.CRUDTableName)") 137 | let sqlStr: String 138 | if columnNames.isEmpty { 139 | sqlStr = "INSERT INTO \(nameQ) DEFAULT VALUES RETURNING \(nameQ).\(try delegate.quote(identifier: returningName))" 140 | } else { 141 | sqlStr = """ 142 | INSERT INTO \(nameQ) (\(columnNames.joined(separator: ", "))) 143 | VALUES (\(bindIdentifiers.joined(separator: ", "))) 144 | RETURNING \(nameQ).\(try delegate.quote(identifier: returningName)) 145 | """ 146 | } 147 | CRUDLogging.log(.query, sqlStr) 148 | let exeDelegate = PostgresExeDelegate(connection: databaseConfiguration.connection, sql: sqlStr) 149 | try exeDelegate.bind(delegate.bindings) 150 | guard try exeDelegate.hasNext(), let next: KeyedDecodingContainer = try exeDelegate.next() else { 151 | throw CRUDSQLGenError("Did not get return value from statement \(sqlStr).") 152 | } 153 | var ret: [R] = [] 154 | let value = try next.decode(R.self, forKey: ColumnKey(stringValue: returningName)!) 155 | ret.append(value) 156 | for instance in instances[1...] { 157 | exeDelegate.resetResults() 158 | let delegate = databaseConfiguration.sqlGenDelegate 159 | let encoder = try CRUDBindingsEncoder(delegate: delegate) 160 | try instance.encode(to: encoder) 161 | _ = try encoder.completedBindings(allKeys: includeNames, ignoreKeys: Set(excludeNames)) 162 | try exeDelegate.bind(delegate.bindings) 163 | guard try exeDelegate.hasNext(), let next: KeyedDecodingContainer = try exeDelegate.next() else { 164 | throw CRUDSQLGenError("Did not get return value from statement \(sqlStr).") 165 | } 166 | let value = try next.decode(R.self, forKey: ColumnKey(stringValue: returningName)!) 167 | ret.append(value) 168 | } 169 | return ret 170 | } 171 | 172 | public extension Table where C.Configuration == PostgresDatabaseConfiguration { 173 | /// Insert the instance and return the new column value. 174 | func returning(_ returning: KeyPath, insert instance: Form) throws -> R { 175 | return try _insert(fromTable: self, instances: [instance], returning: returning, includeKeys: [], excludeKeys: []).first! 176 | } 177 | /// Insert the instance and return the new column value. 178 | func returning(_ returning: KeyPath, insert instance: Form, 179 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> R { 180 | return try _insert(fromTable: self, instances: [instance], returning: returning, includeKeys: [setKeys] + rest, excludeKeys: []).first! 181 | } 182 | /// Insert the instance and return the new column value. 183 | func returning(_ returning: KeyPath, insert instance: Form, 184 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> R { 185 | return try _insert(fromTable: self, instances: [instance], returning: returning, includeKeys: [], excludeKeys: [ignoreKeys] + rest).first! 186 | } 187 | /// Insert the instances and return the new column values. 188 | /// Guarantees that insert.count == returnValue.count. 189 | func returning(_ returning: KeyPath, insert instances: [Form]) throws -> [R] { 190 | return try _insert(fromTable: self, instances: instances, returning: returning, includeKeys: [], excludeKeys: []) 191 | } 192 | /// Insert the instances and return the new column values. 193 | /// Guarantees that insert.count == returnValue.count. 194 | func returning(_ returning: KeyPath, insert instances: [Form], 195 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [R] { 196 | return try _insert(fromTable: self, instances: instances, returning: returning, includeKeys: [setKeys] + rest, excludeKeys: []) 197 | } 198 | /// Insert the instances and return the new column values. 199 | /// Guarantees that insert.count == returnValue.count. 200 | func returning(_ returning: KeyPath, insert instances: [Form], 201 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [R] { 202 | return try _insert(fromTable: self, instances: instances, returning: returning, includeKeys: [], excludeKeys: [ignoreKeys] + rest) 203 | } 204 | } 205 | 206 | public extension Table where C.Configuration == PostgresDatabaseConfiguration { 207 | /// Insert the instance and return the new object value. 208 | func returning(insert instance: Form) throws -> OverAllForm { 209 | return try _insert(fromTable: self, instances: [instance], includeKeys: [], excludeKeys: []).first! 210 | } 211 | /// Insert the instance and return the new object value. 212 | func returning(insert instance: Form, 213 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> OverAllForm { 214 | return try _insert(fromTable: self, instances: [instance], includeKeys: [setKeys] + rest, excludeKeys: []).first! 215 | } 216 | /// Insert the instance and return the new object value. 217 | func returning(insert instance: Form, 218 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> OverAllForm { 219 | return try _insert(fromTable: self, instances: [instance], includeKeys: [], excludeKeys: [ignoreKeys] + rest).first! 220 | } 221 | /// Insert the instances and return the new object values. 222 | /// Guarantees that insert.count == returnValue.count. 223 | func returning(insert instances: [Form]) throws -> [OverAllForm] { 224 | return try _insert(fromTable: self, instances: instances, includeKeys: [], excludeKeys: []) 225 | } 226 | /// Insert the instances and return the new object values. 227 | /// Guarantees that insert.count == returnValue.count. 228 | func returning(insert instances: [Form], 229 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [OverAllForm] { 230 | return try _insert(fromTable: self, instances: instances, includeKeys: [setKeys] + rest, excludeKeys: []) 231 | } 232 | /// Insert the instances and return the new object values. 233 | /// Guarantees that insert.count == returnValue.count. 234 | func returning(insert instances: [Form], 235 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [OverAllForm] { 236 | return try _insert(fromTable: self, instances: instances, includeKeys: [], excludeKeys: [ignoreKeys] + rest) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Sources/PerfectPostgreSQL/PostgresCRUDUpdate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostgresCRUDUpdate.swift 3 | // PerfectPostgreSQL 4 | // 5 | // Created by Kyle Jessup on 2019-01-30. 6 | // 7 | 8 | import Foundation 9 | import PerfectCRUD 10 | 11 | // Promises that instances.count == returnValue.count on exit 12 | // otherwise it will throw 13 | private func _update(fromTable ft: FromTableType, 14 | instance: OverAllForm, 15 | includeKeys: [PartialKeyPath], 16 | excludeKeys: [PartialKeyPath]) throws -> [OverAllForm] { 17 | typealias OAF = OverAllForm 18 | let delegate = ft.databaseConfiguration.sqlGenDelegate 19 | var state = SQLGenState(delegate: delegate) 20 | state.command = .update 21 | try ft.setState(state: &state) 22 | let td = state.tableData[0] 23 | let kpDecoder = td.keyPathDecoder 24 | guard let kpInstance = td.modelInstance else { 25 | throw CRUDSQLGenError("Could not get model instance for key path decoder \(OAF.self)") 26 | } 27 | guard let databaseConfiguration = ft.databaseConfiguration as? PostgresDatabaseConfiguration else { 28 | throw CRUDSQLGenError("This is for Postgres only.") 29 | } 30 | let includeNames: [String] 31 | if includeKeys.isEmpty { 32 | let columnDecoder = CRUDColumnNameDecoder() 33 | _ = try OverAllForm.init(from: columnDecoder) 34 | includeNames = columnDecoder.collectedKeys.map { $0.name } 35 | } else { 36 | includeNames = try includeKeys.map { 37 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 38 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 39 | } 40 | return n 41 | } 42 | } 43 | let excludeNames: [String] = try excludeKeys.map { 44 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 45 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 46 | } 47 | return n 48 | } 49 | 50 | let encoder = try CRUDBindingsEncoder(delegate: delegate) 51 | try instance.encode(to: encoder) 52 | state.bindingsEncoder = encoder 53 | state.columnFilters = (include: includeNames, exclude: excludeNames) 54 | try ft.setSQL(state: &state) 55 | var ret: [OverAllForm] = [] 56 | if let stat = state.statements.first { // multi statements?! 57 | let sql = stat.sql + " RETURNING *" 58 | let exeDelegate = try databaseConfiguration.sqlExeDelegate(forSQL: sql) 59 | try exeDelegate.bind(stat.bindings) 60 | while try exeDelegate.hasNext() { 61 | ret.append(try OverAllForm(from: CRUDRowDecoder(delegate: exeDelegate))) 62 | } 63 | } 64 | return ret 65 | } 66 | 67 | // Promises that instances.count == returnValue.count on exit 68 | // otherwise it will throw 69 | private func _update(fromTable ft: FromTableType, 70 | instance: OverAllForm, 71 | returning: KeyPath, 72 | includeKeys: [PartialKeyPath], 73 | excludeKeys: [PartialKeyPath]) throws -> [R] { 74 | typealias OAF = OverAllForm 75 | let delegate = ft.databaseConfiguration.sqlGenDelegate 76 | var state = SQLGenState(delegate: delegate) 77 | state.command = .update 78 | try ft.setState(state: &state) 79 | let td = state.tableData[0] 80 | let kpDecoder = td.keyPathDecoder 81 | guard let kpInstance = td.modelInstance else { 82 | throw CRUDSQLGenError("Could not get model instance for key path decoder \(OAF.self)") 83 | } 84 | guard let returningName = try kpDecoder.getKeyPathName(kpInstance, keyPath: returning) else { 85 | throw CRUDSQLGenError("Could not get column name for `returning` key path \(returning).") 86 | } 87 | guard let databaseConfiguration = ft.databaseConfiguration as? PostgresDatabaseConfiguration else { 88 | throw CRUDSQLGenError("This is for Postgres only.") 89 | } 90 | let includeNames: [String] 91 | if includeKeys.isEmpty { 92 | let columnDecoder = CRUDColumnNameDecoder() 93 | _ = try OverAllForm.init(from: columnDecoder) 94 | includeNames = columnDecoder.collectedKeys.map { $0.name } 95 | } else { 96 | includeNames = try includeKeys.map { 97 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 98 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 99 | } 100 | return n 101 | } 102 | } 103 | let excludeNames: [String] = try excludeKeys.map { 104 | guard let n = try kpDecoder.getKeyPathName(kpInstance, keyPath: $0) else { 105 | throw CRUDSQLGenError("Could not get key path name for \(OAF.self) \($0)") 106 | } 107 | return n 108 | } 109 | 110 | let encoder = try CRUDBindingsEncoder(delegate: delegate) 111 | try instance.encode(to: encoder) 112 | state.bindingsEncoder = encoder 113 | state.columnFilters = (include: includeNames, exclude: excludeNames) 114 | try ft.setSQL(state: &state) 115 | var ret: [R] = [] 116 | if let stat = state.statements.first { // multi statements?! 117 | let nameQ = try delegate.quote(identifier: "\(OAF.CRUDTableName)") 118 | let sql = stat.sql + " RETURNING \(nameQ).\(try delegate.quote(identifier: returningName))" 119 | let exeDelegate = try databaseConfiguration.sqlExeDelegate(forSQL: sql) 120 | try exeDelegate.bind(stat.bindings) 121 | while try exeDelegate.hasNext(), let next: KeyedDecodingContainer = try exeDelegate.next() { 122 | let value = try next.decode(R.self, forKey: ColumnKey(stringValue: returningName)!) 123 | ret.append(value) 124 | } 125 | } 126 | return ret 127 | } 128 | 129 | public extension Table where C.Configuration == PostgresDatabaseConfiguration { 130 | /// Update the instance and return the new column value. 131 | func returning(_ returning: KeyPath, update instance: Form) throws -> [R] { 132 | return try _update(fromTable: self, instance: instance, returning: returning, includeKeys: [], excludeKeys: []) 133 | } 134 | /// Update the instance and return the new column value. 135 | func returning(_ returning: KeyPath, update instance: Form, 136 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [R] { 137 | return try _update(fromTable: self, instance: instance, returning: returning, includeKeys: [setKeys] + rest, excludeKeys: []) 138 | } 139 | /// Update the instance and return the new column value. 140 | func returning(_ returning: KeyPath, update instance: Form, 141 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [R] { 142 | return try _update(fromTable: self, instance: instance, returning: returning, includeKeys: [], excludeKeys: [ignoreKeys] + rest) 143 | } 144 | /// Update the instance and return the new object value. 145 | func returning(update instance: Form) throws -> [OverAllForm] { 146 | return try _update(fromTable: self, instance: instance, includeKeys: [], excludeKeys: []) 147 | } 148 | /// Update the instance and return the new object value. 149 | func returning(update instance: Form, 150 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [OverAllForm] { 151 | return try _update(fromTable: self, instance: instance, includeKeys: [setKeys] + rest, excludeKeys: []) 152 | } 153 | /// Update the instance and return the new object value. 154 | func returning(update instance: Form, 155 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [OverAllForm] { 156 | return try _update(fromTable: self, instance: instance, includeKeys: [], excludeKeys: [ignoreKeys] + rest) 157 | } 158 | } 159 | 160 | public extension Where where OverAllForm == FromTableType.Form { 161 | /// Update the instance and return the new column value. 162 | func returning(_ returning: KeyPath, update instance: Form) throws -> [R] { 163 | return try _update(fromTable: self, instance: instance, returning: returning, includeKeys: [], excludeKeys: []) 164 | } 165 | /// Update the instance and return the new column value. 166 | func returning(_ returning: KeyPath, update instance: Form, 167 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [R] { 168 | return try _update(fromTable: self, instance: instance, returning: returning, includeKeys: [setKeys] + rest, excludeKeys: []) 169 | } 170 | /// Update the instance and return the new column value. 171 | func returning(_ returning: KeyPath, update instance: Form, 172 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [R] { 173 | return try _update(fromTable: self, instance: instance, returning: returning, includeKeys: [], excludeKeys: [ignoreKeys] + rest) 174 | } 175 | /// Update the instance and return the new object value. 176 | func returning(update instance: Form) throws -> [OverAllForm] { 177 | return try _update(fromTable: self, instance: instance, includeKeys: [], excludeKeys: []) 178 | } 179 | /// Update the instance and return the new object value. 180 | func returning(update instance: Form, 181 | setKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [OverAllForm] { 182 | return try _update(fromTable: self, instance: instance, includeKeys: [setKeys] + rest, excludeKeys: []) 183 | } 184 | /// Update the instance and return the new object value. 185 | func returning(update instance: Form, 186 | ignoreKeys: KeyPath, _ rest: PartialKeyPath...) throws -> [OverAllForm] { 187 | return try _update(fromTable: self, instance: instance, includeKeys: [], excludeKeys: [ignoreKeys] + rest) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectPostgreSQLTests 3 | 4 | XCTMain([ 5 | testCase(PerfectPostgreSQLTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/PerfectPostgreSQLTests/PerfectPostgreSQLTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostgreSQLTests.swift 3 | // PostgreSQLTests 4 | // 5 | // Created by Kyle Jessup on 2015-10-19. 6 | // Copyright © 2015 PerfectlySoft. All rights reserved. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import Foundation 21 | import XCTest 22 | import PerfectCRUD 23 | @testable import PerfectPostgreSQL 24 | 25 | let testDBRowCount = 5 26 | let postgresTestDBName = "testing123" 27 | let postgresInitConnInfo = "host=localhost dbname=postgres" 28 | let postgresTestConnInfo = "host=localhost dbname=testing123" 29 | typealias DBConfiguration = PostgresDatabaseConfiguration 30 | func getDB(reset: Bool = true) throws -> Database { 31 | if reset { 32 | let db = Database(configuration: try DBConfiguration(postgresInitConnInfo)) 33 | try? db.sql("DROP DATABASE \(postgresTestDBName)") 34 | try db.sql("CREATE DATABASE \(postgresTestDBName)") 35 | } 36 | return Database(configuration: try DBConfiguration(postgresTestConnInfo)) 37 | } 38 | 39 | class PerfectPostgreSQLTests: XCTestCase { 40 | // copy + paste from here into other CRUD driver projects 41 | struct TestTable1: Codable, TableNameProvider { 42 | enum CodingKeys: String, CodingKey { 43 | case id, name, integer = "int", double = "doub", blob, subTables 44 | } 45 | static let tableName = "test_table_1" 46 | 47 | @PrimaryKey var id: Int 48 | let name: String? 49 | let integer: Int? 50 | let double: Double? 51 | let blob: [UInt8]? 52 | let subTables: [TestTable2]? 53 | init(id: Int, 54 | name: String? = nil, 55 | integer: Int? = nil, 56 | double: Double? = nil, 57 | blob: [UInt8]? = nil, 58 | subTables: [TestTable2]? = nil) { 59 | self.id = id 60 | self.name = name 61 | self.integer = integer 62 | self.double = double 63 | self.blob = blob 64 | self.subTables = subTables 65 | } 66 | } 67 | 68 | struct TestTable2: Codable { 69 | @PrimaryKey var id: UUID 70 | @ForeignKey(TestTable1.self, onDelete: cascade, onUpdate: cascade) var parentId: Int 71 | let date: Date 72 | let name: String? 73 | let int: Int? 74 | let doub: Double? 75 | let blob: [UInt8]? 76 | init(id: UUID, 77 | parentId: Int, 78 | date: Date, 79 | name: String? = nil, 80 | int: Int? = nil, 81 | doub: Double? = nil, 82 | blob: [UInt8]? = nil) { 83 | self.id = id 84 | self.date = date 85 | self.name = name 86 | self.int = int 87 | self.doub = doub 88 | self.blob = blob 89 | self.parentId = parentId 90 | } 91 | } 92 | 93 | override func setUp() { 94 | super.setUp() 95 | CRUDClearTableStructureCache() 96 | } 97 | override func tearDown() { 98 | CRUDLogging.flush() 99 | super.tearDown() 100 | } 101 | 102 | func testCreate1() { 103 | do { 104 | let db = try getDB() 105 | try db.create(TestTable1.self, policy: .dropTable) 106 | do { 107 | let t2 = db.table(TestTable2.self) 108 | try t2.index(\.parentId) 109 | } 110 | let t1 = db.table(TestTable1.self) 111 | let t2 = db.table(TestTable2.self) 112 | let subId = UUID() 113 | try db.transaction { 114 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 115 | try t1.insert(newOne) 116 | let newSub1 = TestTable2(id: subId, parentId: 2000, date: Date(), name: "Me") 117 | let newSub2 = TestTable2(id: UUID(), parentId: 2000, date: Date(), name: "Not Me") 118 | try t2.insert([newSub1, newSub2]) 119 | } 120 | let j21 = try t1.join(\.subTables, on: \.id, equals: \.parentId) 121 | let j2 = j21.where(\TestTable1.id == 2000 && \TestTable2.name == "Me") 122 | let j3 = j21.where(\TestTable1.id > 20 && 123 | !(\TestTable1.name == "Me" || \TestTable1.name == "You")) 124 | XCTAssertEqual(try j3.count(), 1) 125 | try db.transaction { 126 | let j2a = try j2.select().map { $0 } 127 | XCTAssertEqual(try j2.count(), 1) 128 | XCTAssertEqual(j2a.count, 1) 129 | guard j2a.count == 1 else { 130 | return 131 | } 132 | let obj = j2a[0] 133 | XCTAssertEqual(obj.id, 2000) 134 | XCTAssertNotNil(obj.subTables) 135 | let subTables = obj.subTables! 136 | XCTAssertEqual(subTables.count, 1) 137 | let obj2 = subTables[0] 138 | XCTAssertEqual(obj2.id, subId) 139 | } 140 | try db.create(TestTable1.self) 141 | do { 142 | let j2a = try j2.select().map { $0 } 143 | XCTAssertEqual(try j2.count(), 1) 144 | XCTAssertEqual(j2a[0].id, 2000) 145 | } 146 | try db.create(TestTable1.self, policy: .dropTable) 147 | do { 148 | let j2b = try j2.select().map { $0 } 149 | XCTAssertEqual(j2b.count, 0) 150 | } 151 | } catch { 152 | XCTFail("\(error)") 153 | } 154 | } 155 | 156 | func testCreate2() { 157 | do { 158 | let db = try getTestDB() 159 | try db.create(TestTable1.self, primaryKey: \.id, policy: .dropTable) 160 | do { 161 | let t2 = db.table(TestTable2.self) 162 | try t2.index(\.parentId, \.date) 163 | } 164 | let t1 = db.table(TestTable1.self) 165 | do { 166 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 167 | try t1.insert(newOne) 168 | } 169 | let j2 = try t1.where(\TestTable1.id == 2000).select() 170 | do { 171 | let j2a = j2.map { $0 } 172 | XCTAssertEqual(j2a.count, 1) 173 | XCTAssertEqual(j2a[0].id, 2000) 174 | } 175 | try db.create(TestTable1.self) 176 | do { 177 | let j2a = j2.map { $0 } 178 | XCTAssertEqual(j2a.count, 1) 179 | XCTAssertEqual(j2a[0].id, 2000) 180 | } 181 | try db.create(TestTable1.self, policy: .dropTable) 182 | do { 183 | let j2b = j2.map { $0 } 184 | XCTAssertEqual(j2b.count, 0) 185 | } 186 | } catch { 187 | XCTFail("\(error)") 188 | } 189 | } 190 | 191 | func testCreate3() { 192 | struct FakeTestTable1: Codable, TableNameProvider { 193 | enum CodingKeys: String, CodingKey { 194 | case id, name, double = "doub", double2 = "doub2", blob, subTables 195 | } 196 | static let tableName = "test_table_1" 197 | let id: Int 198 | let name: String? 199 | let double2: Double? 200 | let double: Double? 201 | let blob: [UInt8]? 202 | let subTables: [TestTable2]? 203 | } 204 | do { 205 | let db = try getTestDB() 206 | try db.create(TestTable1.self, policy: [.dropTable, .shallow]) 207 | 208 | do { 209 | let t1 = db.table(TestTable1.self) 210 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 211 | try t1.insert(newOne) 212 | } 213 | do { 214 | try db.create(FakeTestTable1.self, policy: [.reconcileTable, .shallow]) 215 | let t1 = db.table(FakeTestTable1.self) 216 | let j2 = try t1.where(\FakeTestTable1.id == 2000).select() 217 | do { 218 | let j2a = j2.map { $0 } 219 | XCTAssertEqual(j2a.count, 1) 220 | XCTAssertEqual(j2a[0].id, 2000) 221 | } 222 | } 223 | } catch { 224 | XCTFail("\(error)") 225 | } 226 | } 227 | 228 | func getTestDB() throws -> Database { 229 | do { 230 | let db = try getDB() 231 | try db.create(TestTable1.self, policy: .dropTable) 232 | try db.transaction { 233 | () -> () in 234 | try db.table(TestTable1.self) 235 | .insert((1...testDBRowCount).map { 236 | num -> TestTable1 in 237 | let n = UInt8(num) 238 | let blob: [UInt8]? = (num % 2 != 0) ? nil : [UInt8](arrayLiteral: n+1, n+2, n+3, n+4, n+5) 239 | return TestTable1(id: num, 240 | name: "This is name bind \(num)", 241 | integer: num, 242 | double: Double(num), 243 | blob: blob) 244 | }) 245 | } 246 | try db.transaction { 247 | () -> () in 248 | try db.table(TestTable2.self) 249 | .insert((1...testDBRowCount).flatMap { 250 | parentId -> [TestTable2] in 251 | return (1...testDBRowCount).map { 252 | num -> TestTable2 in 253 | let n = UInt8(num) 254 | let blob: [UInt8]? = [UInt8](arrayLiteral: n+1, n+2, n+3, n+4, n+5) 255 | return TestTable2(id: UUID(), 256 | parentId: parentId, 257 | date: Date(), 258 | name: num % 2 == 0 ? "This is name bind \(num)" : "me", 259 | int: num, 260 | doub: Double(num), 261 | blob: blob) 262 | } 263 | }) 264 | } 265 | } catch { 266 | XCTFail("\(error)") 267 | } 268 | return try getDB(reset: false) 269 | } 270 | 271 | func testSelectAll() { 272 | do { 273 | let db = try getTestDB() 274 | let j2 = db.table(TestTable1.self) 275 | for row in try j2.select() { 276 | XCTAssertNil(row.subTables) 277 | } 278 | } catch { 279 | XCTFail("\(error)") 280 | } 281 | } 282 | 283 | func testSelectIn() { 284 | do { 285 | let db = try getTestDB() 286 | let table = db.table(TestTable1.self) 287 | XCTAssertEqual(2, try table.where(\TestTable1.id ~ [2, 4]).count()) 288 | XCTAssertEqual(3, try table.where(\TestTable1.id !~ [2, 4]).count()) 289 | } catch { 290 | XCTFail("\(error)") 291 | } 292 | } 293 | 294 | func testSelectLikeString() { 295 | do { 296 | let db = try getTestDB() 297 | let table = db.table(TestTable2.self) 298 | XCTAssertEqual(25, try table.where(\TestTable2.name %=% "me").count()) 299 | XCTAssertEqual(15, try table.where(\TestTable2.name =% "me").count()) 300 | XCTAssertEqual(15, try table.where(\TestTable2.name %= "me").count()) 301 | XCTAssertEqual( 0, try table.where(\TestTable2.name %!=% "me").count()) 302 | XCTAssertEqual(10, try table.where(\TestTable2.name !=% "me").count()) 303 | XCTAssertEqual(10, try table.where(\TestTable2.name %!= "me").count()) 304 | } catch { 305 | XCTFail("\(error)") 306 | } 307 | } 308 | 309 | func testSelectJoin() { 310 | do { 311 | let db = try getTestDB() 312 | let j2 = try db.table(TestTable1.self) 313 | .order(by: \TestTable1.name) 314 | .join(\.subTables, on: \.id, equals: \.parentId) 315 | .order(by: \.id) 316 | .where(\TestTable2.name == "me") 317 | 318 | let j2c = try j2.count() 319 | let j2a = try j2.select().map{$0} 320 | let j2ac = j2a.count 321 | XCTAssertNotEqual(j2c, 0) 322 | XCTAssertEqual(j2c, j2ac) 323 | j2a.forEach { row in 324 | XCTAssertFalse(row.subTables?.isEmpty ?? true) 325 | } 326 | } catch { 327 | XCTFail("\(error)") 328 | } 329 | } 330 | 331 | func testInsert1() { 332 | do { 333 | let db = try getTestDB() 334 | let t1 = db.table(TestTable1.self) 335 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 336 | try t1.insert(newOne) 337 | let j1 = t1.where(\TestTable1.id == newOne.id) 338 | let j2 = try j1.select().map {$0} 339 | XCTAssertEqual(try j1.count(), 1) 340 | XCTAssertEqual(j2[0].id, 2000) 341 | } catch { 342 | XCTFail("\(error)") 343 | } 344 | } 345 | 346 | func testInsert2() { 347 | do { 348 | let db = try getTestDB() 349 | let t1 = db.table(TestTable1.self) 350 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 351 | try t1.insert(newOne, ignoreKeys: \TestTable1.integer) 352 | let j1 = t1.where(\TestTable1.id == newOne.id) 353 | let j2 = try j1.select().map {$0} 354 | XCTAssertEqual(try j1.count(), 1) 355 | XCTAssertEqual(j2[0].id, 2000) 356 | XCTAssertNil(j2[0].integer) 357 | } catch { 358 | XCTFail("\(error)") 359 | } 360 | } 361 | 362 | func testInsert3() { 363 | do { 364 | let db = try getTestDB() 365 | let t1 = db.table(TestTable1.self) 366 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 367 | let newTwo = TestTable1(id: 2001, name: "New One", integer: 40) 368 | try t1.insert([newOne, newTwo], setKeys: \TestTable1.id, \TestTable1.integer) 369 | let j1 = t1.where(\TestTable1.id == newOne.id) 370 | let j2 = try j1.select().map {$0} 371 | XCTAssertEqual(try j1.count(), 1) 372 | XCTAssertEqual(j2[0].id, 2000) 373 | XCTAssertEqual(j2[0].integer, 40) 374 | XCTAssertNil(j2[0].name) 375 | } catch { 376 | XCTFail("\(error)") 377 | } 378 | } 379 | 380 | func testUpdate() { 381 | do { 382 | let db = try getTestDB() 383 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 384 | let newId: Int = try db.transaction { 385 | try db.table(TestTable1.self).insert(newOne) 386 | let newOne2 = TestTable1(id: 2000, name: "New👻One Updated", integer: 41) 387 | try db.table(TestTable1.self) 388 | .where(\TestTable1.id == newOne.id) 389 | .update(newOne2, setKeys: \.name) 390 | return newOne2.id 391 | } 392 | let j2 = try db.table(TestTable1.self) 393 | .where(\TestTable1.id == newId) 394 | .select().map { $0 } 395 | XCTAssertEqual(1, j2.count) 396 | XCTAssertEqual(2000, j2[0].id) 397 | XCTAssertEqual("New👻One Updated", j2[0].name) 398 | XCTAssertEqual(40, j2[0].integer) 399 | } catch { 400 | XCTFail("\(error)") 401 | } 402 | } 403 | 404 | func testDelete() { 405 | do { 406 | let db = try getTestDB() 407 | let t1 = db.table(TestTable1.self) 408 | let newOne = TestTable1(id: 2000, name: "New One", integer: 40) 409 | try t1.insert(newOne) 410 | let query = t1.where(\TestTable1.id == newOne.id) 411 | let j1 = try query.select().map { $0 } 412 | XCTAssertEqual(j1.count, 1) 413 | try query.delete() 414 | let j2 = try query.select().map { $0 } 415 | XCTAssertEqual(j2.count, 0) 416 | } catch { 417 | XCTFail("\(error)") 418 | } 419 | } 420 | 421 | func testSelectLimit() { 422 | do { 423 | let db = try getTestDB() 424 | do { 425 | let j2 = db.table(TestTable1.self).limit(2, skip: 2) 426 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 427 | } 428 | do { 429 | let j2 = db.table(TestTable1.self).limit(2...3) 430 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 431 | } 432 | do { 433 | let j2 = db.table(TestTable1.self).limit(2..<4) 434 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 435 | } 436 | do { 437 | let j2 = db.table(TestTable1.self).limit(...1) 438 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 439 | } 440 | do { 441 | let j2 = db.table(TestTable1.self).limit(..<2) 442 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 443 | } 444 | do { 445 | let j2 = db.table(TestTable1.self).limit(3...) 446 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 447 | } 448 | do { 449 | let j2 = db.table(TestTable1.self).limit(2, skip: 2) 450 | XCTAssertEqual(try j2.count(), 2) 451 | } 452 | do { 453 | let j2 = db.table(TestTable1.self).limit(2...3) 454 | XCTAssertEqual(try j2.count(), 2) 455 | } 456 | do { 457 | let j2 = db.table(TestTable1.self).limit(2..<4) 458 | XCTAssertEqual(try j2.count(), 2) 459 | } 460 | do { 461 | let j2 = db.table(TestTable1.self).limit(...1) 462 | XCTAssertEqual(try j2.count(), 2) 463 | } 464 | do { 465 | let j2 = db.table(TestTable1.self).limit(..<2) 466 | XCTAssertEqual(try j2.count(), 2) 467 | } 468 | do { 469 | let j2 = db.table(TestTable1.self).limit(3...) 470 | XCTAssertEqual(try j2.count(), 2) 471 | } 472 | } catch { 473 | XCTFail("\(error)") 474 | } 475 | } 476 | 477 | func testSelectLimitWhere() { 478 | do { 479 | let db = try getTestDB() 480 | let j2 = db.table(TestTable1.self).limit(3).where(\TestTable1.id > 3) 481 | XCTAssertEqual(try j2.count(), 2) 482 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 483 | } catch { 484 | XCTFail("\(error)") 485 | } 486 | } 487 | 488 | func testSelectOrderLimitWhere() { 489 | do { 490 | let db = try getTestDB() 491 | let j2 = db.table(TestTable1.self).order(by: \TestTable1.id).limit(3).where(\TestTable1.id > 3) 492 | XCTAssertEqual(try j2.count(), 2) 493 | XCTAssertEqual(try j2.select().map{$0}.count, 2) 494 | } catch { 495 | XCTFail("\(error)") 496 | } 497 | } 498 | 499 | func testSelectWhereNULL() { 500 | do { 501 | let db = try getTestDB() 502 | let t1 = db.table(TestTable1.self) 503 | let j1 = t1.where(\TestTable1.blob == nil) 504 | XCTAssert(try j1.count() > 0) 505 | let j2 = t1.where(\TestTable1.blob != nil) 506 | XCTAssert(try j2.count() > 0) 507 | CRUDLogging.flush() 508 | } catch { 509 | XCTFail("\(error)") 510 | } 511 | } 512 | 513 | // this is the general-overview example used in the readme 514 | func testPersonThing() { 515 | do { 516 | // CRUD can work with most Codable types. 517 | struct PhoneNumber: Codable { 518 | let personId: UUID 519 | let planetCode: Int 520 | let number: String 521 | } 522 | struct Person: Codable { 523 | let id: UUID 524 | let firstName: String 525 | let lastName: String 526 | let phoneNumbers: [PhoneNumber]? 527 | } 528 | 529 | // CRUD usage begins by creating a database connection. 530 | // The inputs for connecting to a database will differ depending on your client library. 531 | // Create a `Database` object by providing a configuration. 532 | // All code would be identical regardless of the datasource type. 533 | let db = try getTestDB() 534 | 535 | // Create the table if it hasn't been done already. 536 | // Table creates are recursive by default, so "PhoneNumber" is also created here. 537 | try db.create(Person.self, policy: .reconcileTable) 538 | 539 | // Get a reference to the tables we will be inserting data into. 540 | let personTable = db.table(Person.self) 541 | let numbersTable = db.table(PhoneNumber.self) 542 | 543 | // Add an index for personId, if it does not already exist. 544 | try numbersTable.index(\.personId) 545 | 546 | // Insert some sample data. 547 | do { 548 | // Insert some sample data. 549 | let owen = Person(id: UUID(), firstName: "Owen", lastName: "Lars", phoneNumbers: nil) 550 | let beru = Person(id: UUID(), firstName: "Beru", lastName: "Lars", phoneNumbers: nil) 551 | 552 | // Insert the people 553 | try personTable.insert([owen, beru]) 554 | 555 | // Give them some phone numbers 556 | try numbersTable.insert([ 557 | PhoneNumber(personId: owen.id, planetCode: 12, number: "555-555-1212"), 558 | PhoneNumber(personId: owen.id, planetCode: 15, number: "555-555-2222"), 559 | PhoneNumber(personId: beru.id, planetCode: 12, number: "555-555-1212")]) 560 | } 561 | 562 | // Perform a query. 563 | // Let's find all people with the last name of Lars which have a phone number on planet 12. 564 | let query = try personTable 565 | .order(by: \.lastName, \.firstName) 566 | .join(\.phoneNumbers, on: \.id, equals: \.personId) 567 | .order(descending: \.planetCode) 568 | .where(\Person.lastName == "Lars" && \PhoneNumber.planetCode == 12) 569 | .select() 570 | 571 | // Loop through the results and print the names. 572 | for user in query { 573 | // We joined PhoneNumbers, so we should have values here. 574 | guard let numbers = user.phoneNumbers else { 575 | continue 576 | } 577 | for number in numbers { 578 | print(number.number) 579 | } 580 | } 581 | CRUDLogging.flush() 582 | } catch { 583 | XCTFail("\(error)") 584 | } 585 | } 586 | 587 | func testStandardJoin() { 588 | do { 589 | let db = try getTestDB() 590 | struct Parent: Codable { 591 | let id: Int 592 | let children: [Child]? 593 | init(id i: Int) { 594 | id = i 595 | children = nil 596 | } 597 | } 598 | struct Child: Codable { 599 | let id: Int 600 | let parentId: Int 601 | } 602 | try db.transaction { 603 | try db.create(Parent.self, policy: [.shallow, .dropTable]).insert( 604 | Parent(id: 1)) 605 | try db.create(Child.self, policy: [.shallow, .dropTable]).insert( 606 | [Child(id: 1, parentId: 1), 607 | Child(id: 2, parentId: 1), 608 | Child(id: 3, parentId: 1)]) 609 | } 610 | let join = try db.table(Parent.self) 611 | .join(\.children, 612 | on: \.id, 613 | equals: \.parentId) 614 | .where(\Parent.id == 1) 615 | 616 | guard let parent = try join.first() else { 617 | return XCTFail("Failed to find parent id: 1") 618 | } 619 | guard let children = parent.children else { 620 | return XCTFail("Parent had no children") 621 | } 622 | XCTAssertEqual(3, children.count) 623 | for child in children { 624 | XCTAssertEqual(child.parentId, parent.id) 625 | } 626 | CRUDLogging.flush() 627 | } catch { 628 | XCTFail("\(error)") 629 | } 630 | } 631 | 632 | func testJunctionJoin() { 633 | do { 634 | struct Student: Codable { 635 | let id: Int 636 | let classes: [Class]? 637 | init(id i: Int) { 638 | id = i 639 | classes = nil 640 | } 641 | } 642 | struct Class: Codable { 643 | let id: Int 644 | let students: [Student]? 645 | init(id i: Int) { 646 | id = i 647 | students = nil 648 | } 649 | } 650 | struct StudentClasses: Codable { 651 | let studentId: Int 652 | let classId: Int 653 | } 654 | let db = try getTestDB() 655 | try db.transaction { 656 | try db.create(Student.self, policy: [.dropTable, .shallow]).insert( 657 | Student(id: 1)) 658 | try db.create(Class.self, policy: [.dropTable, .shallow]).insert([ 659 | Class(id: 1), 660 | Class(id: 2), 661 | Class(id: 3)]) 662 | try db.create(StudentClasses.self, policy: [.dropTable, .shallow]).insert([ 663 | StudentClasses(studentId: 1, classId: 1), 664 | StudentClasses(studentId: 1, classId: 2), 665 | StudentClasses(studentId: 1, classId: 3)]) 666 | } 667 | let join = try db.table(Student.self) 668 | .join(\.classes, 669 | with: StudentClasses.self, 670 | on: \.id, 671 | equals: \.studentId, 672 | and: \.id, 673 | is: \.classId) 674 | .where(\Student.id == 1) 675 | guard let student = try join.first() else { 676 | return XCTFail("Failed to find student id: 1") 677 | } 678 | guard let classes = student.classes else { 679 | return XCTFail("Student had no classes") 680 | } 681 | XCTAssertEqual(3, classes.count) 682 | for aClass in classes { 683 | let join = try db.table(Class.self) 684 | .join(\.students, 685 | with: StudentClasses.self, 686 | on: \.id, 687 | equals: \.classId, 688 | and: \.id, 689 | is: \.studentId) 690 | .where(\Class.id == aClass.id) 691 | guard let found = try join.first() else { 692 | XCTFail("Class with no students") 693 | continue 694 | } 695 | guard nil != found.students?.first(where: { $0.id == student.id }) else { 696 | XCTFail("Student not found in class") 697 | continue 698 | } 699 | } 700 | CRUDLogging.flush() 701 | } catch { 702 | XCTFail("\(error)") 703 | } 704 | } 705 | 706 | func testSelfJoin() { 707 | do { 708 | struct Me: Codable { 709 | let id: Int 710 | let parentId: Int 711 | let mes: [Me]? 712 | init(id i: Int, parentId p: Int) { 713 | id = i 714 | parentId = p 715 | mes = nil 716 | } 717 | } 718 | let db = try getTestDB() 719 | try db.transaction { 720 | () -> () in 721 | try db.create(Me.self, policy: .dropTable).insert([ 722 | Me(id: 1, parentId: 0), 723 | Me(id: 2, parentId: 1), 724 | Me(id: 3, parentId: 1), 725 | Me(id: 4, parentId: 1), 726 | Me(id: 5, parentId: 1) 727 | ]) 728 | } 729 | let join = try db.table(Me.self) 730 | .join(\.mes, on: \.id, equals: \.parentId) 731 | .where(\Me.id == 1) 732 | guard let me = try join.first() else { 733 | return XCTFail("Unable to find me.") 734 | } 735 | guard let mes = me.mes else { 736 | return XCTFail("Unable to find meesa.") 737 | } 738 | XCTAssertEqual(mes.count, 4) 739 | } catch { 740 | XCTFail("\(error)") 741 | } 742 | } 743 | 744 | func testSelfJunctionJoin() { 745 | do { 746 | struct Me: Codable { 747 | let id: Int 748 | let us: [Me]? 749 | init(id i: Int) { 750 | id = i 751 | us = nil 752 | } 753 | } 754 | struct Us: Codable { 755 | let you: Int 756 | let them: Int 757 | } 758 | let db = try getTestDB() 759 | try db.transaction { 760 | () -> () in 761 | try db.create(Me.self, policy: .dropTable) 762 | .insert((1...5).map { .init(id: $0) }) 763 | try db.create(Us.self, policy: .dropTable) 764 | .insert((2...5).map { .init(you: 1, them: $0) }) 765 | } 766 | let join = try db.table(Me.self) 767 | .join(\.us, 768 | with: Us.self, 769 | on: \.id, 770 | equals: \.you, 771 | and: \.id, 772 | is: \.them) 773 | .where(\Me.id == 1) 774 | guard let me = try join.first() else { 775 | return XCTFail("Unable to find me.") 776 | } 777 | guard let us = me.us else { 778 | return XCTFail("Unable to find us.") 779 | } 780 | XCTAssertEqual(us.count, 4) 781 | } catch { 782 | XCTFail("\(error)") 783 | } 784 | } 785 | 786 | func testCodableProperty() { 787 | do { 788 | struct Sub: Codable { 789 | let id: Int 790 | } 791 | struct Top: Codable { 792 | let id: Int 793 | let sub: Sub? 794 | } 795 | let db = try getTestDB() 796 | try db.create(Sub.self) 797 | try db.create(Top.self) 798 | let t1 = Top(id: 1, sub: Sub(id: 1)) 799 | try db.table(Top.self).insert(t1) 800 | guard let top = try db.table(Top.self).where(\Top.id == 1).first() else { 801 | return XCTFail("Unable to find top.") 802 | } 803 | XCTAssertEqual(top.sub?.id, t1.sub?.id) 804 | } catch { 805 | XCTFail("\(error)") 806 | } 807 | } 808 | 809 | func testBadDecoding() { 810 | do { 811 | struct Top: Codable, TableNameProvider { 812 | static var tableName = "Top" 813 | let id: Int 814 | } 815 | struct NTop: Codable, TableNameProvider { 816 | static var tableName = "Top" 817 | let nid: Int 818 | } 819 | let db = try getTestDB() 820 | try db.create(Top.self, policy: .dropTable) 821 | let t1 = Top(id: 1) 822 | try db.table(Top.self).insert(t1) 823 | _ = try db.table(NTop.self).first() 824 | XCTFail("Should not have a valid object.") 825 | } catch {} 826 | } 827 | 828 | func testAllPrimTypes1() { 829 | struct AllTypes: Codable { 830 | let int: Int 831 | let uint: UInt 832 | let int64: Int64 833 | let uint64: UInt64 834 | let int32: Int32? 835 | let uint32: UInt32? 836 | let int16: Int16 837 | let uint16: UInt16 838 | let int8: Int8? 839 | let uint8: UInt8? 840 | let double: Double 841 | let float: Float 842 | let string: String 843 | let bytes: [Int8] 844 | let ubytes: [UInt8]? 845 | let b: Bool 846 | } 847 | do { 848 | let db = try getTestDB() 849 | try db.create(AllTypes.self, policy: .dropTable) 850 | let model = AllTypes(int: 1, uint: 2, int64: 3, uint64: 4, int32: 5, uint32: 6, int16: 7, uint16: 8, int8: 9, uint8: 10, double: 11, float: 12, string: "13", bytes: [1, 4], ubytes: [1, 4], b: true) 851 | try db.table(AllTypes.self).insert(model) 852 | 853 | guard let f = try db.table(AllTypes.self).where(\AllTypes.int == 1).first() else { 854 | return XCTFail("Nil result.") 855 | } 856 | XCTAssertEqual(model.int, f.int) 857 | XCTAssertEqual(model.uint, f.uint) 858 | XCTAssertEqual(model.int64, f.int64) 859 | XCTAssertEqual(model.uint64, f.uint64) 860 | XCTAssertEqual(model.int32, f.int32) 861 | XCTAssertEqual(model.uint32, f.uint32) 862 | XCTAssertEqual(model.int16, f.int16) 863 | XCTAssertEqual(model.uint16, f.uint16) 864 | XCTAssertEqual(model.int8, f.int8) 865 | XCTAssertEqual(model.uint8, f.uint8) 866 | XCTAssertEqual(model.double, f.double) 867 | XCTAssertEqual(model.float, f.float) 868 | XCTAssertEqual(model.string, f.string) 869 | XCTAssertEqual(model.bytes, f.bytes) 870 | XCTAssertEqual(model.ubytes!, f.ubytes!) 871 | XCTAssertEqual(model.b, f.b) 872 | } catch { 873 | XCTFail("\(error)") 874 | } 875 | do { 876 | let db = try getTestDB() 877 | try db.create(AllTypes.self, policy: .dropTable) 878 | let model = AllTypes(int: 1, uint: 2, int64: -3, uint64: 4, int32: nil, uint32: nil, int16: -7, uint16: 8, int8: nil, uint8: nil, double: -11, float: -12, string: "13", bytes: [1, 4], ubytes: nil, b: true) 879 | try db.table(AllTypes.self).insert(model) 880 | 881 | guard let f = try db.table(AllTypes.self) 882 | .where(\AllTypes.int == 1).first() else { 883 | return XCTFail("Nil result.") 884 | } 885 | XCTAssertEqual(model.int, f.int) 886 | XCTAssertEqual(model.uint, f.uint) 887 | XCTAssertEqual(model.int64, f.int64) 888 | XCTAssertEqual(model.uint64, f.uint64) 889 | XCTAssertEqual(model.int32, f.int32) 890 | XCTAssertEqual(model.uint32, f.uint32) 891 | XCTAssertEqual(model.int16, f.int16) 892 | XCTAssertEqual(model.uint16, f.uint16) 893 | XCTAssertEqual(model.int8, f.int8) 894 | XCTAssertEqual(model.uint8, f.uint8) 895 | XCTAssertEqual(model.double, f.double) 896 | XCTAssertEqual(model.float, f.float) 897 | XCTAssertEqual(model.string, f.string) 898 | XCTAssertEqual(model.bytes, f.bytes) 899 | XCTAssertNil(f.ubytes) 900 | XCTAssertEqual(model.b, f.b) 901 | } catch { 902 | XCTFail("\(error)") 903 | } 904 | } 905 | 906 | func testAllPrimTypes2() { 907 | struct AllTypes2: Codable { 908 | func equals(rhs: AllTypes2) -> Bool { 909 | guard int == rhs.int && uint == rhs.uint && 910 | int64 == rhs.int64 && uint64 == rhs.uint64 && 911 | int32 == rhs.int32 && uint32 == rhs.uint32 && 912 | int16 == rhs.int16 && uint16 == rhs.uint16 && 913 | int8 == rhs.int8 && uint8 == rhs.uint8 else { 914 | return false 915 | } 916 | guard double == rhs.double && float == rhs.float && 917 | string == rhs.string && 918 | b == rhs.b else { 919 | return false 920 | } 921 | guard (bytes == nil && rhs.bytes == nil) || (bytes != nil && rhs.bytes != nil) else { 922 | return false 923 | } 924 | guard (ubytes == nil && rhs.ubytes == nil) || (ubytes != nil && rhs.ubytes != nil) else { 925 | return false 926 | } 927 | if let lhsb = bytes { 928 | guard lhsb == rhs.bytes! else { 929 | return false 930 | } 931 | } 932 | if let lhsb = ubytes { 933 | guard lhsb == rhs.ubytes! else { 934 | return false 935 | } 936 | } 937 | return true 938 | } 939 | let int: Int? 940 | let uint: UInt? 941 | let int64: Int64? 942 | let uint64: UInt64? 943 | let int32: Int32? 944 | let uint32: UInt32? 945 | let int16: Int16? 946 | let uint16: UInt16? 947 | let int8: Int8? 948 | let uint8: UInt8? 949 | let double: Double? 950 | let float: Float? 951 | let string: String? 952 | let bytes: [Int8]? 953 | let ubytes: [UInt8]? 954 | let b: Bool? 955 | } 956 | 957 | do { 958 | let db = try getTestDB() 959 | try db.create(AllTypes2.self, policy: .dropTable) 960 | let model = AllTypes2(int: 1, uint: 2, int64: -3, uint64: 4, int32: 5, uint32: 6, 961 | int16: 7, uint16: 8, int8: 9, uint8: 10, 962 | double: 11.2, float: 12.3, string: "13", 963 | bytes: [1, 4], ubytes: [1, 4], b: true) 964 | try db.table(AllTypes2.self).insert(model) 965 | do { 966 | guard let f = try db.table(AllTypes2.self) 967 | .where(\AllTypes2.int == 1 && 968 | \AllTypes2.uint == 2 && 969 | \AllTypes2.int64 == -3).first() else { 970 | return XCTFail("Nil result.") 971 | } 972 | XCTAssert(model.equals(rhs: f), "\(model) != \(f)") 973 | XCTAssertEqual(try db.table(AllTypes2.self) 974 | .where(\AllTypes2.int != 1 && 975 | \AllTypes2.uint != 2 && 976 | \AllTypes2.int64 != -3).count(), 0) 977 | } 978 | do { 979 | guard let f = try db.table(AllTypes2.self) 980 | .where(\AllTypes2.uint64 == 4 && 981 | \AllTypes2.int32 == 5 && 982 | \AllTypes2.uint32 == 6).first() else { 983 | return XCTFail("Nil result.") 984 | } 985 | XCTAssert(model.equals(rhs: f), "\(model) != \(f)") 986 | XCTAssertEqual(try db.table(AllTypes2.self) 987 | .where(\AllTypes2.uint64 != 4 && 988 | \AllTypes2.int32 != 5 && 989 | \AllTypes2.uint32 != 6).count(), 0) 990 | } 991 | do { 992 | guard let f = try db.table(AllTypes2.self) 993 | .where(\AllTypes2.int16 == 7 && 994 | \AllTypes2.uint16 == 8 && 995 | \AllTypes2.int8 == 9 && 996 | \AllTypes2.uint8 == 10).first() else { 997 | return XCTFail("Nil result.") 998 | } 999 | XCTAssert(model.equals(rhs: f), "\(model) != \(f)") 1000 | XCTAssertEqual(try db.table(AllTypes2.self) 1001 | .where(\AllTypes2.int16 != 7 && 1002 | \AllTypes2.uint16 != 8 && 1003 | \AllTypes2.int8 != 9 && 1004 | \AllTypes2.uint8 != 10).count(), 0) 1005 | } 1006 | do { 1007 | guard let f = try db.table(AllTypes2.self) 1008 | .where(\AllTypes2.double == 11.2 && 1009 | \AllTypes2.float == Float(12.3) && 1010 | \AllTypes2.string == "13").first() else { 1011 | return XCTFail("Nil result.") 1012 | } 1013 | XCTAssert(model.equals(rhs: f), "\(model) != \(f)") 1014 | XCTAssertEqual(try db.table(AllTypes2.self) 1015 | .where(\AllTypes2.double != 11.2 && 1016 | \AllTypes2.float != Float(12.3) && 1017 | \AllTypes2.string != "13").count(), 0) 1018 | } 1019 | do { 1020 | guard let f = try db.table(AllTypes2.self) 1021 | .where(\AllTypes2.bytes == [1, 4] as [Int8] && 1022 | \AllTypes2.ubytes == [1, 4] as [UInt8] && 1023 | \AllTypes2.b == true).first() else { 1024 | return XCTFail("Nil result.") 1025 | } 1026 | XCTAssert(model.equals(rhs: f), "\(model) != \(f)") 1027 | XCTAssertEqual(try db.table(AllTypes2.self) 1028 | .where(\AllTypes2.bytes != [1, 4] as [Int8] && 1029 | \AllTypes2.ubytes != [1, 4] as [UInt8] && 1030 | \AllTypes2.b != true).count(), 0) 1031 | } 1032 | } catch { 1033 | XCTFail("\(error)") 1034 | } 1035 | } 1036 | 1037 | func testBespokeSQL() { 1038 | do { 1039 | let db = try getTestDB() 1040 | do { 1041 | let r = try db.sql("SELECT * FROM \(TestTable1.CRUDTableName) WHERE id = 2", TestTable1.self) 1042 | XCTAssertEqual(r.count, 1) 1043 | } 1044 | do { 1045 | let r = try db.sql("SELECT * FROM \(TestTable1.CRUDTableName)", TestTable1.self) 1046 | XCTAssertEqual(r.count, 5) 1047 | } 1048 | } catch { 1049 | XCTFail("\(error)") 1050 | } 1051 | } 1052 | 1053 | func testModelClasses() { 1054 | class BaseClass: Codable { 1055 | let id: Int 1056 | let name: String 1057 | private enum CodingKeys: String, CodingKey { 1058 | case id, name 1059 | } 1060 | init(id: Int, name: String) { 1061 | self.id = id 1062 | self.name = name 1063 | } 1064 | required init(from decoder: Decoder) throws { 1065 | let container = try decoder.container(keyedBy: CodingKeys.self) 1066 | id = try container.decode(Int.self, forKey: .id) 1067 | name = try container.decode(String.self, forKey: .name) 1068 | } 1069 | func encode(to encoder: Encoder) throws { 1070 | var container = encoder.container(keyedBy: CodingKeys.self) 1071 | try container.encode(id, forKey: .id) 1072 | try container.encode(name, forKey: .name) 1073 | } 1074 | } 1075 | 1076 | class SubClass: BaseClass { 1077 | let another: String 1078 | private enum CodingKeys: String, CodingKey { 1079 | case another 1080 | } 1081 | init(id: Int, name: String, another: String) { 1082 | self.another = another 1083 | super.init(id: id, name: name) 1084 | } 1085 | required init(from decoder: Decoder) throws { 1086 | let container = try decoder.container(keyedBy: CodingKeys.self) 1087 | another = try container.decode(String.self, forKey: .another) 1088 | try super.init(from: decoder) 1089 | } 1090 | override func encode(to encoder: Encoder) throws { 1091 | var container = encoder.container(keyedBy: CodingKeys.self) 1092 | try container.encode(another, forKey: .another) 1093 | try super.encode(to: encoder) 1094 | } 1095 | } 1096 | 1097 | do { 1098 | let db = try getTestDB() 1099 | try db.create(SubClass.self) 1100 | let table = db.table(SubClass.self) 1101 | let obj = SubClass(id: 1, name: "The name", another: "And another thing") 1102 | try table.insert(obj) 1103 | 1104 | guard let found = try table.where(\SubClass.id == 1).first() else { 1105 | return XCTFail("Did not find SubClass") 1106 | } 1107 | XCTAssertEqual(found.another, obj.another) 1108 | XCTAssertEqual(found.name, obj.name) 1109 | } catch { 1110 | XCTFail("\(error)") 1111 | } 1112 | } 1113 | 1114 | func testURL() { 1115 | do { 1116 | let db = try getTestDB() 1117 | struct TableWithURL: Codable { 1118 | let id: Int 1119 | let url: URL 1120 | } 1121 | try db.create(TableWithURL.self) 1122 | let t1 = db.table(TableWithURL.self) 1123 | let newOne = TableWithURL(id: 2000, url: URL(string: "http://localhost/")!) 1124 | try t1.insert(newOne) 1125 | let j1 = t1.where(\TableWithURL.id == newOne.id) 1126 | let j2 = try j1.select().map {$0} 1127 | XCTAssertEqual(try j1.count(), 1) 1128 | XCTAssertEqual(j2[0].id, 2000) 1129 | XCTAssertEqual(j2[0].url.absoluteString, "http://localhost/") 1130 | } catch { 1131 | XCTFail("\(error)") 1132 | } 1133 | } 1134 | 1135 | func testManyJoins() { 1136 | do { 1137 | let db = try getTestDB() 1138 | struct Person2 : Codable{ 1139 | var id : UUID 1140 | var name : String 1141 | let cars : [Car]? 1142 | let boats : [Boat]? 1143 | let houses : [House]? 1144 | } 1145 | struct Car : Codable{ 1146 | var id : UUID 1147 | var owner : UUID 1148 | } 1149 | struct Boat : Codable{ 1150 | var id : UUID 1151 | var owner : UUID 1152 | } 1153 | struct House : Codable{ 1154 | var id : UUID 1155 | var owner : UUID 1156 | } 1157 | try db.create(Person2.self) 1158 | try db.table(Car.self).index(\.owner) 1159 | try db.table(Boat.self).index(\.owner) 1160 | try db.table(House.self).index(\.owner) 1161 | 1162 | let t1 = db.table(Person2.self) 1163 | let parentId = UUID() 1164 | let person = Person2(id: parentId, name: "The Person", cars: nil, boats: nil, houses: nil) 1165 | try t1.insert(person) 1166 | 1167 | for _ in 0..<5 { 1168 | try db.table(Car.self).insert(.init(id: UUID(), owner: parentId)) 1169 | try db.table(Boat.self).insert(.init(id: UUID(), owner: parentId)) 1170 | try db.table(House.self).insert(.init(id: UUID(), owner: parentId)) 1171 | } 1172 | 1173 | let j1 = try t1.join(\.cars, on: \.id, equals: \.owner) 1174 | .join(\.boats, on: \.id, equals: \.owner) 1175 | .join(\.houses, on: \.id, equals: \.owner) 1176 | .where(\Person2.id == parentId) 1177 | guard let j2 = try j1.first() else { 1178 | return XCTFail() 1179 | } 1180 | XCTAssertEqual(5, j2.cars?.count) 1181 | XCTAssertEqual(5, j2.boats?.count) 1182 | XCTAssertEqual(5, j2.houses?.count) 1183 | } catch { 1184 | XCTFail("\(error)") 1185 | } 1186 | } 1187 | 1188 | func testDateFormat() { 1189 | let fmt = Date(fromISO8601: "2018-08-18 08:10:51-04") 1190 | XCTAssertNotNil(fmt) 1191 | let fmt1 = Date(fromISO8601: "2018-08-18 08:10:51.32-04:00") 1192 | XCTAssertNotNil(fmt1) 1193 | let fmt2 = Date(fromISO8601: "2018-08-18 08:10:51Z") 1194 | XCTAssertNotNil(fmt2) 1195 | let fmt3 = Date(fromISO8601: "2018-08-18 08:10:51.43Z") 1196 | XCTAssertNotNil(fmt3) 1197 | } 1198 | 1199 | func testAssets() { 1200 | struct Asset: Codable { 1201 | let id: UUID 1202 | let name: String? 1203 | let assetLog: [AssetLog]? 1204 | init(id i: UUID, 1205 | name n: String? = nil, 1206 | assetLog log: [AssetLog]? = nil) { 1207 | id = i 1208 | name = n 1209 | assetLog = log 1210 | } 1211 | } 1212 | 1213 | struct AssetLog: Codable { 1214 | let assetId: UUID 1215 | let userId: UUID 1216 | let taken: Double 1217 | let returned: Double? 1218 | init(assetId: UUID, userId: UUID, taken: Double, returned: Double? = nil) { 1219 | self.assetId = assetId 1220 | self.userId = userId 1221 | self.taken = taken 1222 | self.returned = returned 1223 | } 1224 | } 1225 | 1226 | do { 1227 | let db = try getTestDB() 1228 | try db.create(Asset.self, policy: .dropTable) 1229 | let id = UUID() 1230 | let userId = UUID() 1231 | do { 1232 | let asset = Asset(id: id, name: "name") 1233 | try db.table(Asset.self).insert(asset) 1234 | let assetLogs = [AssetLog(assetId: id, userId: userId, taken: 1.0), 1235 | AssetLog(assetId: id, userId: userId, taken: 2.0)] 1236 | try db.table(AssetLog.self).insert(assetLogs) 1237 | } 1238 | let assetTable = db.table(Asset.self) 1239 | let asset = try assetTable.join(\.assetLog, on: \.id, equals: \.assetId) 1240 | .where(\AssetLog.userId == userId && \AssetLog.returned == nil).first() 1241 | XCTAssertNotNil(asset?.assetLog) 1242 | XCTAssertEqual(asset?.id, id) 1243 | XCTAssertEqual(asset?.assetLog?.count, 2) 1244 | } catch { 1245 | XCTFail("\(error)") 1246 | } 1247 | 1248 | } 1249 | 1250 | func testReturningInsert() { 1251 | do { 1252 | let db = try getTestDB() 1253 | struct ReturningItem: Codable, Equatable { 1254 | let id: UUID 1255 | let def: Int? 1256 | init(id: UUID, def: Int? = nil) { 1257 | self.id = id 1258 | self.def = def 1259 | } 1260 | } 1261 | try db.sql("DROP TABLE IF EXISTS \(ReturningItem.CRUDTableName)") 1262 | try db.sql("CREATE TABLE \(ReturningItem.CRUDTableName) (id UUID PRIMARY KEY, def int DEFAULT 42)") 1263 | let table = db.table(ReturningItem.self) 1264 | do { 1265 | let item = ReturningItem(id: UUID()) 1266 | let def = try table.returning(\.def, insert: item, ignoreKeys: \.def) 1267 | XCTAssertEqual(def, 42) 1268 | } 1269 | do { 1270 | let items = [ReturningItem(id: UUID()), 1271 | ReturningItem(id: UUID()), 1272 | ReturningItem(id: UUID())] 1273 | let defs = try table.returning(\.def, insert: items, ignoreKeys: \.def) 1274 | XCTAssertEqual(defs, [42, 42, 42]) 1275 | } 1276 | do { 1277 | let id = UUID() 1278 | let item = ReturningItem(id: id, def: 42) 1279 | let id0 = try table.returning(\.id, insert: item) 1280 | XCTAssertEqual(id0, id) 1281 | } 1282 | do { 1283 | let items = [ReturningItem(id: UUID()), 1284 | ReturningItem(id: UUID()), 1285 | ReturningItem(id: UUID())] 1286 | let defs = try table.returning(insert: items, ignoreKeys: \.def) 1287 | XCTAssertEqual(defs.map{$0.id}, items.map{$0.id}) 1288 | XCTAssertEqual(defs.compactMap{$0.def}.count, defs.count) 1289 | } 1290 | } catch { 1291 | XCTFail("\(error)") 1292 | } 1293 | } 1294 | 1295 | func testReturningUpdate() { 1296 | do { 1297 | let db = try getTestDB() 1298 | struct ReturningItem: Codable, Equatable { 1299 | let id: UUID 1300 | var def: Int? 1301 | init(id: UUID, def: Int? = nil) { 1302 | self.id = id 1303 | self.def = def 1304 | } 1305 | } 1306 | try db.sql("DROP TABLE IF EXISTS \(ReturningItem.CRUDTableName)") 1307 | try db.sql("CREATE TABLE \(ReturningItem.CRUDTableName) (id UUID PRIMARY KEY, def int DEFAULT 42)") 1308 | let table = db.table(ReturningItem.self) 1309 | let id = UUID() 1310 | var item = ReturningItem(id: id) 1311 | try table.insert(item, ignoreKeys: \.def) 1312 | item.def = 300 1313 | let item0 = try table 1314 | .where(\ReturningItem.id == id) 1315 | .returning(\.def, update: item, ignoreKeys: \.id) 1316 | XCTAssertEqual(item0.count, 1) 1317 | XCTAssertEqual(item.def, item0.first) 1318 | } catch { 1319 | XCTFail("\(error)") 1320 | } 1321 | } 1322 | 1323 | func testEmptyInsert() { 1324 | do { 1325 | let db = try getTestDB() 1326 | struct ReturningItem: Codable, Equatable { 1327 | let id: Int? 1328 | var def: Int? 1329 | init(id: Int, def: Int? = nil) { 1330 | self.id = id 1331 | self.def = def 1332 | } 1333 | } 1334 | try db.sql("DROP TABLE IF EXISTS \(ReturningItem.CRUDTableName)") 1335 | try db.sql("CREATE TABLE \(ReturningItem.CRUDTableName) (id SERIAL PRIMARY KEY, def INT DEFAULT 42)") 1336 | let table = db.table(ReturningItem.self) 1337 | 1338 | try table 1339 | .insert(ReturningItem(id: 0, def: 0), 1340 | ignoreKeys: \ReturningItem.id, \ReturningItem.def) 1341 | _ = try table 1342 | .returning(\.def, insert: ReturningItem(id: 0, def: 0), 1343 | ignoreKeys: \ReturningItem.id, \ReturningItem.def) 1344 | } catch { 1345 | XCTFail("\(error)") 1346 | } 1347 | } 1348 | 1349 | static var allTests = [ 1350 | ("testCreate1", testCreate1), 1351 | ("testCreate2", testCreate2), 1352 | ("testCreate3", testCreate3), 1353 | ("testSelectAll", testSelectAll), 1354 | ("testSelectIn", testSelectIn), 1355 | ("testSelectLikeString", testSelectLikeString), 1356 | ("testSelectJoin", testSelectJoin), 1357 | ("testInsert1", testInsert1), 1358 | ("testInsert2", testInsert2), 1359 | ("testInsert3", testInsert3), 1360 | ("testUpdate", testUpdate), 1361 | ("testDelete", testDelete), 1362 | ("testSelectLimit", testSelectLimit), 1363 | ("testSelectLimitWhere", testSelectLimitWhere), 1364 | ("testSelectOrderLimitWhere", testSelectOrderLimitWhere), 1365 | ("testSelectWhereNULL", testSelectWhereNULL), 1366 | ("testPersonThing", testPersonThing), 1367 | ("testStandardJoin", testStandardJoin), 1368 | ("testJunctionJoin", testJunctionJoin), 1369 | ("testSelfJoin", testSelfJoin), 1370 | ("testSelfJunctionJoin", testSelfJunctionJoin), 1371 | ("testCodableProperty", testCodableProperty), 1372 | ("testBadDecoding", testBadDecoding), 1373 | ("testAllPrimTypes1", testAllPrimTypes1), 1374 | ("testAllPrimTypes2", testAllPrimTypes2), 1375 | ("testBespokeSQL", testBespokeSQL), 1376 | ("testModelClasses", testModelClasses), 1377 | ("testURL", testURL), 1378 | ("testManyJoins", testManyJoins), 1379 | ("testDateFormat", testDateFormat), 1380 | ("testAssets", testAssets), 1381 | ("testReturningInsert", testReturningInsert), 1382 | ("testReturningUpdate", testReturningUpdate), 1383 | ("testEmptyInsert", testEmptyInsert) 1384 | ] 1385 | } 1386 | 1387 | -------------------------------------------------------------------------------- /Tests/PerfectPostgreSQLTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestManifests.swift 3 | // 4 | // Created by Kyle Jessup on 2015-10-19. 5 | // Copyright © 2015 PerfectlySoft. All rights reserved. 6 | // 7 | //===----------------------------------------------------------------------===// 8 | // 9 | // This source file is part of the Perfect.org open source project 10 | // 11 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 12 | // Licensed under Apache License v2.0 13 | // 14 | // See http://perfect.org/licensing.html for license information 15 | // 16 | //===----------------------------------------------------------------------===// 17 | // 18 | 19 | import XCTest 20 | 21 | #if !os(OSX) 22 | public func allTests() -> [XCTestCaseEntry] { 23 | return [ 24 | testCase(PerfectPostgreSQLTests.allTests) 25 | ] 26 | } 27 | #endif 28 | --------------------------------------------------------------------------------