├── .gitmodules ├── .swift-version ├── Tests ├── SwiftKueryTests │ ├── port.txt │ ├── host.txt │ ├── password.txt │ ├── username.txt │ ├── TestRaw.swift │ ├── TestAlias.swift │ ├── TestParameters.swift │ ├── VerifyLinuxTestCount.swift │ ├── TestJoin.swift │ ├── TestSubquery.swift │ └── TestAggregateFunctions.swift └── LinuxMain.swift ├── .gitignore ├── docs ├── img │ ├── dash.png │ ├── gh.png │ ├── carat.png │ └── spinner.gif ├── docsets │ ├── SwiftKuery.tgz │ └── SwiftKuery.docset │ │ └── Contents │ │ ├── Resources │ │ ├── docSet.dsidx │ │ └── Documents │ │ │ ├── img │ │ │ ├── dash.png │ │ │ ├── gh.png │ │ │ ├── carat.png │ │ │ └── spinner.gif │ │ │ ├── js │ │ │ ├── jazzy.js │ │ │ └── jazzy.search.js │ │ │ └── css │ │ │ └── highlight.css │ │ └── Info.plist ├── badge.svg ├── js │ ├── jazzy.js │ └── jazzy.search.js ├── undocumented.json └── css │ └── highlight.css ├── docker.compose.yml ├── .jazzy.yaml ├── Sources └── SwiftKuery │ ├── PreparedStatement.swift │ ├── QuerySuffixProtocol.swift │ ├── IndexColumn.swift │ ├── Buildable.swift │ ├── String+Buildable.swift │ ├── OrderBy.swift │ ├── ResultFetcher.swift │ ├── Union.swift │ ├── Field.swift │ ├── RawField.swift │ ├── ConnectionPoolOptions.swift │ ├── FilterAndHaving_Extensions.swift │ ├── Query.swift │ ├── QueryError.swift │ ├── Raw.swift │ ├── Join.swift │ ├── Parameter.swift │ ├── IndexColumnOrdered.swift │ ├── With.swift │ ├── Condition.swift │ ├── ColumnCreator.swift │ ├── ResultSet.swift │ ├── Filter.swift │ ├── Having.swift │ ├── Index.swift │ ├── Predicate.swift │ ├── ForeignKey.swift │ ├── Delete.swift │ ├── Utils.swift │ ├── Update.swift │ ├── AuxiliaryTable.swift │ ├── Migration.swift │ ├── ConditionalClause.swift │ └── SQLDataType.swift ├── Scripts ├── TestAggregate.txt ├── TestAggregateScalar.txt ├── runScripts.sh ├── ColumnExtensionClauses.txt ├── ColumnExtensionOperands.txt ├── FilterAndHavingBoolOperators.txt ├── SimpleOperators.txt ├── TestLike.txt ├── FilterAndHavingBoolTypes.txt ├── InSelectTypes.txt ├── filterAndHavingExtensions.sh ├── SubqueriesTypes.txt ├── TestBetweenAndIn.txt ├── simpleOperators.sh ├── subqueries.sh ├── testSimpleOperators.sh ├── testAggregate.sh └── columnExtensions.sh ├── .travis.yml └── Package.swift /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.1 2 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/port.txt: -------------------------------------------------------------------------------- 1 | 5432 2 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/host.txt: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/password.txt: -------------------------------------------------------------------------------- 1 | kitura 2 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/username.txt: -------------------------------------------------------------------------------- 1 | postgres 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcodeproj/ 2 | .build 3 | build 4 | Package.resolved 5 | .DS_Store -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/docsets/SwiftKuery.tgz -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/docsets/SwiftKuery.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docker.compose.yml: -------------------------------------------------------------------------------- 1 | test: 2 | image: ibmcom/swift-ubuntu:4.0.3 3 | volumes: 4 | - .:/Swift-Kuery 5 | command: bash -c "cd /Swift-Kuery && swift package clean && swift build" 6 | -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitura/Swift-Kuery/HEAD/docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | module: SwiftKuery 2 | author: IBM & Kitura project authors 3 | github_url: https://github.com/Kitura/Swift-Kuery/ 4 | 5 | theme: fullwidth 6 | clean: true 7 | exclude: [Packages] 8 | 9 | readme: README.md 10 | 11 | skip_undocumented: false 12 | hide_documentation_coverage: false 13 | -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.swiftkuery 7 | CFBundleName 8 | SwiftKuery 9 | DocSetPlatformFamily 10 | swiftkuery 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/PreparedStatement.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Prepared statement 18 | 19 | /// A protocol for database specific prepared statements to implement. 20 | public protocol PreparedStatement {} 21 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/QuerySuffixProtocol.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: QuerySuffixProtocol 18 | 19 | /// Defines the protocol which should be used for all suffix clauses. 20 | public protocol QuerySuffixProtocol: Buildable { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Scripts/TestAggregate.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # Aggregate functions to test. Used in testAggregate.sh. The resulting test file is TestAggregateFunctions.swift. 18 | avg AVG 19 | max MAX 20 | min MIN 21 | sum SUM 22 | last LAST 23 | first FIRST 24 | count COUNT 25 | 26 | -------------------------------------------------------------------------------- /Scripts/TestAggregateScalar.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # Scalar functions with aggregate function as an argument to test. 18 | # Used in testAggregate.sh. The resulting test file is TestAggregateFunctions.swift. 19 | 20 | ucase UCASE 21 | lcase LCASE 22 | len LEN 23 | 24 | -------------------------------------------------------------------------------- /Scripts/runScripts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright IBM Corporation 2016 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 18 | cd "$SCRIPT_DIR" 19 | 20 | ./columnExtensions.sh 21 | ./simpleOperators.sh 22 | ./specialOperators.sh 23 | ./subqueries.sh 24 | ./filterAndHavingExtensions.sh 25 | 26 | ./testSimpleOperators.sh 27 | ./testAggregate.sh 28 | ./testSpecialOperators.sh 29 | ./testSubqueries.sh 30 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/IndexColumn.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: IndexColumn 18 | 19 | /// A protocol for columns used in indices. 20 | public protocol IndexColumn { 21 | /// Build the index column using `QueryBuilder`. 22 | /// 23 | /// - Parameter queryBuilder: The QueryBuilder to use. 24 | /// - Returns: A String representation of the index column. 25 | func buildIndex(queryBuilder: QueryBuilder) -> String 26 | 27 | var table: Table { get } 28 | } 29 | -------------------------------------------------------------------------------- /Scripts/ColumnExtensionClauses.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for columnExtension.sh that generates extensions for Column, ScalarColumnExpression 18 | # and AggregateColumnExpression for (NOT)LIKE, (NOT)BETWEEN, and (NOT)IN operators. 19 | # This file contains the pairs of (Filter/Having, type to extend). 20 | # The generated code is in Sources/SwiftKuery/ColumnAndExpressions_Extensions.swift. 21 | 22 | Filter ScalarColumnExpression 23 | Filter Column 24 | Having AggregateColumnExpression 25 | Having Column 26 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 99% 23 | 24 | 25 | 99% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Buildable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Buildable protocol 18 | 19 | /// Defines the protocol which all query components must implement. 20 | /// Each query component should be able to return its representation as a String. 21 | public protocol Buildable { 22 | /// Build the query component using `QueryBuilder`. 23 | /// 24 | /// - Parameter queryBuilder: The QueryBuilder to use. 25 | /// - Returns: A String representation of the query component. 26 | /// - Throws: QueryError.syntaxError if query build fails. 27 | func build(queryBuilder: QueryBuilder) throws -> String 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Scripts/ColumnExtensionOperands.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for columnExtension.sh that generates extensions for Column, ScalarColumnExpression, 18 | # and AggregateColumnExpression for (NOT)LIKE, (NOT)BETWEEN, and (NOT)IN operators. 19 | # This file contains the types of the operands. 20 | # The generated code is in Sources/SwiftKuery/ColumnAndExpressions_Extensions.swift. 21 | 22 | # It is also an input for specialOperators.sh that generates extensions for the types listed in this file for the same operators. 23 | # The generated code is in Sources/SwiftKuery/SpecialOperators_Extensions.swift. 24 | 25 | Bool 26 | String 27 | Int 28 | Float 29 | Double 30 | Parameter 31 | Date 32 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/String+Buildable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: String extensions 18 | 19 | /// The String extension with `QueryFilterProtocol` and `QueryHavingProtocol`. 20 | /// Enables the use if `String` in query as filtering clauses. 21 | extension String: QueryFilterProtocol, QueryHavingProtocol, QuerySuffixProtocol { 22 | 23 | /// Process `String` as raw SQL using `QueryBuilder`. 24 | /// 25 | /// - Parameter queryBuilder: The QueryBuilder to use. 26 | /// - Returns: A String representation of the query. 27 | /// - Throws: QueryError.syntaxError if query build fails. 28 | public func build(queryBuilder: QueryBuilder) throws -> String { 29 | return self 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Scripts/FilterAndHavingBoolOperators.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for simpleOperators.sh that generates global operators for simple conditions that return Filter and Having clauses. 18 | # This file contains both the operators and the types for conditions with Bool as a type of one of the operands. 19 | # The last entry is ignored. 20 | # The generated code is in Sources/SwiftKuery/FilterAndHaving_GlobalFunctions.swift. 21 | 22 | # This is also an input file for testSimpleOperators.sh that generates the tests for the code created by simpleOperators.sh. 23 | # The format is (operator in query creation, Condition case (ignored), SQL operator). 24 | # The generated tests are in TestFilterAndHaving.swift 25 | == equal = 26 | != notEqual <> 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI build file. 2 | 3 | # whitelist (branches that should be built) 4 | branches: 5 | only: 6 | - master 7 | - next 8 | - /^issue.*$/ 9 | 10 | # the matrix of builds should cover each combination of Swift version 11 | # and platform that is supported. The version of Swift used is specified 12 | # by .swift-version, unless SWIFT_SNAPSHOT is specified. 13 | matrix: 14 | include: 15 | - os: linux 16 | dist: xenial 17 | sudo: required 18 | services: docker 19 | env: DOCKER_IMAGE=docker.kitura.net/kitura/swift-ci-ubuntu16.04:5.1.5 20 | - os: linux 21 | dist: bionic 22 | sudo: required 23 | services: docker 24 | env: DOCKER_IMAGE=docker.kitura.net/kitura/swift-ci-ubuntu18.04:5.4 25 | - os: linux 26 | dist: xenial 27 | sudo: required 28 | services: docker 29 | env: DOCKER_IMAGE=docker.kitura.net/kitura/swift-ci-ubuntu18.04:latest USE_SWIFT_DEVELOPMENT_SNAPSHOT=1 30 | - os: osx 31 | osx_image: xcode11 32 | sudo: required 33 | env: SWIFT_SNAPSHOT=5.1.5 JAZZY_ELIGIBLE=true 34 | - os: osx 35 | osx_image: xcode12.2 36 | sudo: required 37 | - os: osx 38 | osx_image: xcode12.5 39 | sudo: required 40 | env: USE_SWIFT_DEVELOPMENT_SNAPSHOT=1 41 | 42 | before_install: 43 | - git clone https://github.com/Kitura/Package-Builder.git 44 | 45 | script: 46 | - ./Package-Builder/build-package.sh -projectDir $TRAVIS_BUILD_DIR 47 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/OrderBy.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: OrderBy 18 | 19 | /// An SQL ORDER BY clause. 20 | public enum OrderBy: Buildable { 21 | /// Order ascending. 22 | case ASC(Field) 23 | /// Order descending. 24 | case DESC(Field) 25 | 26 | /// Build the query component using `QueryBuilder`. 27 | /// 28 | /// - Parameter queryBuilder: The QueryBuilder to use. 29 | /// - Returns: A String representation of the query component. 30 | /// - Throws: QueryError.syntaxError if query build fails. 31 | public func build(queryBuilder: QueryBuilder) throws -> String { 32 | switch self { 33 | case .ASC(let field): 34 | return try field.build(queryBuilder: queryBuilder) + " ASC" 35 | case .DESC(let field): 36 | return try field.build(queryBuilder: queryBuilder) + " DESC" 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/ResultFetcher.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: ResultFetcher protocol 18 | 19 | /// A protocol for retrieving query results. All database plugins must implement this protocol. 20 | public protocol ResultFetcher { 21 | 22 | /// Fetch the next row of the query result. This function is non-blocking. 23 | /// 24 | /// - Parameter callback: A callback to call when the next row of the query result is ready. 25 | func fetchNext(callback: @escaping (([Any?]?, Error?)) ->()) 26 | 27 | /// Fetch the titles of the query result. This function is non-blocking. 28 | /// 29 | /// - Parameter callback: A callback to call when the column titles of the query result are ready. 30 | func fetchTitles(callback: @escaping (([String]?, Error?)) -> ()) 31 | 32 | /// Closes any underlying database connections and releases system resources synchronously 33 | /// 34 | func done() 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Union.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Union 18 | 19 | /// The SQL SELECT UNION/UNION ALL statement. 20 | public enum Union: Buildable { 21 | /// The SQL UNION statement. 22 | case union(Select) 23 | /// The SQL UNION ALL statement. 24 | case unionAll(Select) 25 | 26 | /// Build the query component using `QueryBuilder`. 27 | /// 28 | /// - Parameter queryBuilder: The QueryBuilder to use. 29 | /// - Returns: A String representation of the query component. 30 | /// - Throws: QueryError.syntaxError if query build fails. 31 | public func build(queryBuilder: QueryBuilder) throws -> String { 32 | switch self { 33 | case .union(let query): 34 | return try " UNION " + query.build(queryBuilder: queryBuilder) 35 | case .unionAll(let query): 36 | return try " UNION ALL " + query.build(queryBuilder: queryBuilder) 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Field.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 18 | // MARK: Field protocol 19 | 20 | /// Defines the protocol for columns, and aggregate and scalar functions on columns. 21 | public protocol Field: Buildable { 22 | 23 | /// The alias of the field. 24 | var alias: String? { get set } 25 | 26 | /// Add an alias to the field, i.e., implement the SQL AS operator. 27 | /// 28 | /// - Parameter newName: A String containing the alias for the field. 29 | /// - Returns: A new Field instance with the alias. 30 | func `as`(_ newName: String) -> Field 31 | } 32 | 33 | extension Field { 34 | /// Add an alias to the field, i.e., implement the SQL AS operator. 35 | /// 36 | /// - Parameter newName: A String containing the alias for the field. 37 | /// - Returns: A new Field instance with the alias. 38 | public func `as`(_ newName: String) -> Field { 39 | var new = self 40 | new.alias = newName 41 | return new 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/RawField.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: RawField 18 | 19 | /// An arbitrary field represented by a String. 20 | public struct RawField: Field { 21 | /// A String containg the field. 22 | public let field: String 23 | 24 | /// The alias of the field. 25 | public var alias: String? 26 | 27 | /// Initialize an instance of RawField. 28 | /// 29 | /// - Parameter field: A String containing the field. 30 | public init(_ field: String) { 31 | self.field = field 32 | } 33 | 34 | /// Build the field using `QueryBuilder`. 35 | /// 36 | /// - Parameter queryBuilder: The QueryBuilder to use. 37 | /// - Returns: A String representation of the field. 38 | public func build(queryBuilder: QueryBuilder) -> String { 39 | var result = field 40 | if let alias = alias { 41 | result += " AS " + Utils.packName(alias, queryBuilder: queryBuilder) 42 | } 43 | return result 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Scripts/SimpleOperators.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for simpleOperators.sh that generates global operators for simple conditions that return Filter and Having clauses. 18 | # This file contains the operators. 19 | # The generated code is in Sources/SwiftKuery/FilterAndHaving_GlobalFunctions.swift. 20 | 21 | # An input file for subqueries.sh that generates global operators with subqueries (i.e. 'expression operator ANY/ALL(subquery)'). 22 | # The generated code is in Sources/SwiftKuery/Subqueries_GlobalFunctionsAndExtensions.swift. 23 | 24 | # This is also an input file for testSimpleOperators.sh that generates the tests for the code created by simpleOperators.sh. 25 | # The generated tests are in TestFilterAndHaving.swift 26 | 27 | # This is also an input file for testSubquries.sh that generates the tests for the code created by subqueries.sh. 28 | # The generated tests are in TestSubqueries.swift 29 | 30 | == equal = 31 | >= greaterThanOrEqual >= 32 | > greaterThan > 33 | <= lessThanOrEqual <= 34 | < lessThan < 35 | != notEqual <> 36 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/ConnectionPoolOptions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 18 | // MARK: ConnectionPoolOptions 19 | 20 | /// Options for `ConectionPool` configuration. 21 | public struct ConnectionPoolOptions { 22 | /// The initial number of connections in the pool. 23 | public let initialCapacity: Int 24 | 25 | /// The maximum number of connections in the pool. The pool is allowed to grow from `initialCapacity` up to 26 | /// this limit. If not specified, or `maxCapacity` <= `initialCapacity`, the pool cannot grow. 27 | public let maxCapacity: Int 28 | 29 | /// Initialize an instance of `ConnectionPoolOptions`. 30 | /// 31 | /// - Parameter initialCapacity: The initial number of connections in the pool. 32 | /// - Parameter maxCapacity: The maximum number of connections in the pool 33 | /// - Parameter timeout: Maximum wait (in milliseconds) to receive a connection before returning nil. 34 | public init(initialCapacity: Int, maxCapacity: Int = 0) { 35 | self.initialCapacity = initialCapacity 36 | self.maxCapacity = maxCapacity 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Scripts/TestLike.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # Arguments of the LIKE operator: operand.like(argument). 18 | # The format of each line is (where/having, operand in Swift, operand in SQL, argument in Swift, argument in SQL). 19 | # Used in testSpecialOperators.sh. The resulting test file is TestSpecialOperators.swift. 20 | 21 | where t.a table.a "%kuery%" '%kuery%' 22 | having t.a table.a "%kuery%" '%kuery%' 23 | where t.a table.a Parameter() \?1 24 | having t.a table.a Parameter() \?1 25 | where ucase(t.a) UCASE(table.a) "%kuery%" '%kuery%' 26 | having first(t.a) FIRST(table.a) "%kuery%" '%kuery%' 27 | where ucase(t.a) UCASE(table.a) Parameter() \?1 28 | having first(t.a) FIRST(table.a) Parameter() \?1 29 | where "swift-kuery" 'swift-kuery' "%kuery%" '%kuery%' 30 | having "swift-kuery" 'swift-kuery' "%kuery%" '%kuery%' 31 | where "swift-kuery" 'swift-kuery' Parameter() \?1 32 | having "swift-kuery" 'swift-kuery' Parameter() \?1 33 | where Parameter() \?1 "%kuery%" '%kuery%' 34 | having Parameter("first") @first "%kuery%" '%kuery%' 35 | where Parameter("first") @first Parameter() \?1 36 | having Parameter() \?1 Parameter() \?2 37 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/TestRaw.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | 19 | @testable import SwiftKuery 20 | class TestRaw: XCTestCase { 21 | 22 | static var allTests: [(String, (TestRaw) -> () throws -> Void)] { 23 | return [ 24 | ("testRaw", testRaw), 25 | ] 26 | } 27 | 28 | class MyTable : Table { 29 | let a = Column("a") 30 | let b = Column("b") 31 | 32 | let tableName = "tableRaw" 33 | } 34 | 35 | func testRaw () { 36 | let t = MyTable() 37 | let connection = createConnection() 38 | 39 | var r = Raw(query: "DROP TABLE", table: t) 40 | var kuery = connection.descriptionOf(query: r) 41 | var query = "DROP TABLE \"tableRaw\"" 42 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 43 | 44 | r = Raw(query: "DROP TABLE", tables: [t]) 45 | kuery = connection.descriptionOf(query: r) 46 | query = "DROP TABLE \"tableRaw\"" 47 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/FilterAndHaving_Extensions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016, 2017 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | extension Filter { 18 | 19 | /// Create a `Filter` clause using the isNull operator. 20 | /// 21 | /// - Returns: A `Filter` containing the clause. 22 | public func isNull() -> Filter { 23 | return Filter(lhs: .clause(self), condition: .isNull) 24 | } 25 | 26 | /// Create a `Filter` clause using the isNotNull operator. 27 | /// 28 | /// - Returns: A `Filter` containing the clause. 29 | public func isNotNull() -> Filter { 30 | return Filter(lhs: .clause(self), condition: .isNotNull) 31 | } 32 | } 33 | extension Having { 34 | 35 | /// Create a `Having` clause using the isNull operator. 36 | /// 37 | /// - Returns: A `Having` containing the clause. 38 | public func isNull() -> Having { 39 | return Having(lhs: .clause(self), condition: .isNull) 40 | } 41 | 42 | /// Create a `Having` clause using the isNotNull operator. 43 | /// 44 | /// - Returns: A `Having` containing the clause. 45 | public func isNotNull() -> Having { 46 | return Having(lhs: .clause(self), condition: .isNotNull) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Query.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Query protocol 18 | 19 | /// Defines the protocol for queries. 20 | public protocol Query: Buildable { 21 | } 22 | 23 | extension Query { 24 | /// Execute the query. 25 | /// 26 | /// - Parameter connection: The plugin that implements the Connection protocol and executes the query. 27 | /// - Parameter onCompletion: The function to be called when the execution of the query has completed. 28 | public func execute(_ connection: Connection, onCompletion: @escaping ((QueryResult) -> ())) { 29 | connection.execute(query: self, onCompletion: onCompletion) 30 | } 31 | 32 | /// Execute the query with parameters. 33 | /// 34 | /// - Parameter connection: The plugin that implements the Connection protocol and executes the query. 35 | /// - Parameter parameters: An array of the query parameters. 36 | /// - Parameter onCompletion: The function to be called when the execution of the query has completed. 37 | public func execute(_ connection: Connection, parameters: [Any], onCompletion: @escaping ((QueryResult) -> ())) { 38 | connection.execute(query: self, parameters: parameters, onCompletion: onCompletion) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | /** 5 | * Copyright IBM Corporation and the Kitura project authors 2016-2020 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | **/ 19 | 20 | import PackageDescription 21 | 22 | let package = Package( 23 | name: "SwiftKuery", 24 | products: [ 25 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 26 | .library( 27 | name: "SwiftKuery", 28 | targets: ["SwiftKuery"]), 29 | ], 30 | dependencies: [ 31 | // Dependencies declare other packages that this package depends on. 32 | // .package(url: /* package url */, from: "1.0.0"), 33 | .package(url: "https://github.com/Kitura/LoggerAPI.git", from: "1.9.200"), 34 | ], 35 | targets: [ 36 | // Targets are the basic building blocks of a package. A target defines a module or a test suite. 37 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 38 | .target( 39 | name: "SwiftKuery", 40 | dependencies: ["LoggerAPI"]), 41 | .testTarget( 42 | name: "SwiftKueryTests", 43 | dependencies: ["SwiftKuery"]), 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/QueryError.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: QueryError 18 | 19 | /// An enumeration of errors that may occur during query construction and execution. 20 | public enum QueryError: Error { 21 | /// An error connecting to the database. 22 | case connection(String) 23 | /// No result was received from the query execution. 24 | case noResult(String) 25 | /// The database generated an error for the query execution. 26 | case databaseError(String) 27 | /// A syntax error occurred while constructing the query. 28 | case syntaxError(String) 29 | /// The query or its execution is not supported. 30 | case unsupported(String) 31 | /// An error in transaction. 32 | case transactionError(String) 33 | } 34 | 35 | extension QueryError: CustomStringConvertible { 36 | /// A String representation of the error. 37 | public var description: String { 38 | switch self { 39 | case .connection(let error): 40 | return error 41 | case .noResult(let error): 42 | return error 43 | case .databaseError(let error): 44 | return error 45 | case .syntaxError(let error): 46 | return error 47 | case .unsupported(let error): 48 | return error 49 | case .transactionError(let error): 50 | return error 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Scripts/FilterAndHavingBoolTypes.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for simpleOperators.sh that generates global operators with Bool operands 18 | # for simple conditions that return Filter and Having clauses. 19 | # This file contains the list of all the types: each line is (Filter/Having, LHS type, RHS type) - the rest is ignored. 20 | # The generated code is in Sources/SwiftKuery/FilterAndHaving_GlobalFunctions.swift. 21 | 22 | # This is also an input file for testSimpleOperators.sh that generates the tests for the code created by simpleOperators.sh. 23 | # The first three entries in each line are ignored. The format of the rest of the line is 24 | # (where/having, first operand in Swift, second operand in Swift, first operand in SQL, second operand in SQL). 25 | # The generated tests are in TestFilterAndHaving.swift 26 | 27 | # The operators are in FilterAndHavingBoolOperators.txt 28 | 29 | Filter ScalarColumnExpression Bool where lcase(t.a) true LCASE(table.a) true 30 | Filter Bool ScalarColumnExpression where false ucase(t.a) false UCASE(table.a) 31 | Filter Column Bool where t.a true table.a true 32 | Filter Bool Column where true t.a true table.a 33 | Having AggregateColumnExpression Bool having first(t.a) true FIRST(table.a) true 34 | Having Bool AggregateColumnExpression having false last(t.a) false LAST(table.a) 35 | Having Column Bool having t.a true table.a true 36 | Having Bool Column having true t.a true table.a 37 | -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Raw.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Raw 18 | 19 | /// An arbitrary query represented by a String. The generated query will be a concatenation of the 20 | /// supplied query string and tables. 21 | public struct Raw: Query { 22 | /// A String containing the query. 23 | public let query: String 24 | 25 | /// The table to apply the query on. 26 | public let tables: [Table] 27 | 28 | /// Initialize an instance of Raw. 29 | /// 30 | /// - Parameter query: A String containing the query. 31 | /// - Parameter table: The table(s) to apply the query on. 32 | public init(query: String, table: Table...) { 33 | self.init(query: query, tables: table) 34 | } 35 | 36 | /// Initialize an instance of Raw. 37 | /// 38 | /// - Parameter query: A String containing the query. 39 | /// - Parameter tables: An array of tables to apply the query on. 40 | public init(query: String, tables: [Table]) { 41 | self.query = query 42 | self.tables = tables 43 | } 44 | 45 | /// Build the query using `QueryBuilder`. 46 | /// 47 | /// - Parameter queryBuilder: The QueryBuilder to use. 48 | /// - Returns: A String representation of the query. 49 | /// - Throws: QueryError.syntaxError if query build fails. 50 | public func build(queryBuilder: QueryBuilder) throws -> String { 51 | return try query + " " + "\(tables.map { try $0.build(queryBuilder: queryBuilder) }.joined(separator: ", "))" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Join.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Join 18 | 19 | /// The SQL SELECT JOIN statement. 20 | public enum Join: Buildable { 21 | /// The SQL INNER JOIN statement. 22 | case join(Table) 23 | /// The SQL LEFT OUTER JOIN statement. 24 | case left(Table) 25 | /// The SQL CROSS JOIN statement. 26 | case cross(Table) 27 | /// The SQL NATURAL INNER JOIN statement. 28 | case natural(Table) 29 | /// A String with a join statement. 30 | case raw(String, Table) 31 | 32 | /// Build the query component using `QueryBuilder`. 33 | /// 34 | /// - Parameter queryBuilder: The QueryBuilder to use. 35 | /// - Returns: A String representation of the query component. 36 | /// - Throws: QueryError.syntaxError if query build fails. 37 | public func build(queryBuilder: QueryBuilder) throws -> String { 38 | switch self { 39 | case .join(let table): 40 | return try " JOIN " + table.build(queryBuilder: queryBuilder) 41 | case .left(let table): 42 | return try " LEFT JOIN " + table.build(queryBuilder: queryBuilder) 43 | case .cross(let table): 44 | return try " CROSS JOIN " + table.build(queryBuilder: queryBuilder) 45 | case .natural(let table): 46 | return try " NATURAL JOIN " + table.build(queryBuilder: queryBuilder) 47 | case .raw(let raw, let table): 48 | return try " " + raw + " " + table.build(queryBuilder: queryBuilder) 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Scripts/InSelectTypes.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for subqueries.sh that generates extensions for (NOT)IN operator with a subquery. 18 | # This file contains the list of all the types for IN and NOT IN operators (the last three entries are ignored). 19 | # The generated code is in Sources/SwiftKuery/Subqueries_GlobalFunctionsAndExtensions.swift. 20 | 21 | # This is also an input file for testSubqueries.sh that generates the tests for the code created by subqueries.sh. 22 | # The first two entries in each line are ignored. The format of the rest of the line is 23 | # (where/having, first operand in Swift, first operand in SQL). 24 | # The generated tests are in TestSubqueries.swift 25 | 26 | Filter ScalarColumnExpression where lcase(t.a) LCASE(table.a) 27 | Filter String where "plum" 'plum' 28 | Filter Column where t.a table.a 29 | Filter Int where 7 7 30 | Filter Float where Float(7.8) 7.8 31 | Filter Double where (-0.9) -0.9 32 | Filter Parameter where Parameter() \?1 33 | Filter Bool where true true 34 | Filter Date where Date(timeIntervalSince1970:_0) '1970-01-01_00:00:00_+0000' 35 | Having AggregateColumnExpression having min(t.a) MIN(table.a) 36 | Having String having "plum" 'plum' 37 | Having Column having t.a table.a 38 | Having Int having 7 7 39 | Having Float having Float(7.8) 7.8 40 | Having Double having (-0.9) -0.9 41 | Having Parameter having Parameter() \?1 42 | Having Bool having false false 43 | Having Date having Date(timeIntervalSince1970:_0) '1970-01-01_00:00:00_+0000' 44 | -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/TestAlias.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | 19 | @testable import SwiftKuery 20 | 21 | class TestAlias: XCTestCase { 22 | 23 | static var allTests: [(String, (TestAlias) -> () throws -> Void)] { 24 | return [ 25 | ("testAlias", testAlias), 26 | ] 27 | } 28 | 29 | class MyTable : Table { 30 | let a = Column("a") 31 | let b = Column("b") 32 | 33 | let tableName = "tableAlias" 34 | } 35 | 36 | func testAlias() { 37 | let t = MyTable() 38 | let connection = createConnection() 39 | 40 | var s = Select(t.a.as("\"fruit name\""), t.b.as("number"), from: t) 41 | var kuery = connection.descriptionOf(query: s) 42 | var query = "SELECT \"tableAlias\".\"a\" AS \"fruit name\", \"tableAlias\".\"b\" AS \"number\" FROM \"tableAlias\"" 43 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 44 | 45 | s = Select(from: t.as("new")) 46 | kuery = connection.descriptionOf(query: s) 47 | query = "SELECT * FROM \"tableAlias\" AS \"new\"" 48 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 49 | 50 | let t2 = t.as("\"t 2\"") 51 | s = Select(t2.a, from: t2) 52 | kuery = connection.descriptionOf(query: s) 53 | query = "SELECT \"t 2\".\"a\" FROM \"tableAlias\" AS \"t 2\"" 54 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | { 4 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/Connection.swift", 5 | "line": 162, 6 | "symbol": "Connection.execute(query:parameters:onCompletion:)", 7 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 8 | "warning": "undocumented" 9 | }, 10 | { 11 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/Connection.swift", 12 | "line": 194, 13 | "symbol": "Connection.runCompletionHandler(_:onCompletion:)", 14 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 15 | "warning": "undocumented" 16 | }, 17 | { 18 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/Connection.swift", 19 | "line": 200, 20 | "symbol": "Connection.runCompletionHandlerRetainingConnection(result:onCompletion:)", 21 | "symbol_kind": "source.lang.swift.decl.function.method.instance", 22 | "warning": "undocumented" 23 | }, 24 | { 25 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/ConnectionPool.swift", 26 | "line": 73, 27 | "symbol": "ConnectionPool.connectionPoolTask", 28 | "symbol_kind": "source.lang.swift.decl.typealias", 29 | "warning": "undocumented" 30 | }, 31 | { 32 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/ConnectionPoolConnection.swift", 33 | "line": 397, 34 | "symbol": "DummyColumBuilder", 35 | "symbol_kind": "source.lang.swift.decl.class", 36 | "warning": "undocumented" 37 | }, 38 | { 39 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/IndexColumn.swift", 40 | "line": 27, 41 | "symbol": "IndexColumn.table", 42 | "symbol_kind": "source.lang.swift.decl.var.instance", 43 | "warning": "undocumented" 44 | }, 45 | { 46 | "file": "/Users/dannys/projects/kitura/Swift-Kuery/Sources/SwiftKuery/IndexColumnOrdered.swift", 47 | "line": 21, 48 | "symbol": "IndexColumnOrdered.table", 49 | "symbol_kind": "source.lang.swift.decl.var.instance", 50 | "warning": "undocumented" 51 | } 52 | ], 53 | "source_directory": "/Users/dannys/projects/kitura/Swift-Kuery" 54 | } -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Parameter.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Parameter 18 | 19 | /// Query parameter, either numbered, or named, or just marked with a marker. 20 | public struct Parameter: Buildable { 21 | /// An optional name of the parameter. 22 | public private (set) var name: String? 23 | 24 | /// Initialize an instance of Parameter. 25 | /// 26 | /// - Parameter name: An optional name of the parameter. 27 | public init(_ name: String?=nil) { 28 | self.name = name 29 | } 30 | 31 | static var numberedParameterMarker = "\u{00}\u{01}\u{01}\u{00}" 32 | static var namedParameterStart = "\u{01}\u{02}\u{02}\u{01}" 33 | static var namedParameterEnd = "\u{02}\u{03}\u{03}\u{02}" 34 | 35 | /// Build the parameter using `QueryBuilder`. If the parameter's name is set, 36 | /// return it along with the named parameter marker in `QueryBuilder`. Otherwise, 37 | /// return the numbered parameter marker in `QueryBuilder`. 38 | /// 39 | /// - Parameter queryBuilder: The QueryBuilder to use. 40 | /// - Returns: A String representation of the parameter. 41 | /// - Throws: QueryError.syntaxError if query build fails. 42 | public func build(queryBuilder: QueryBuilder) throws -> String { 43 | if let name = name { 44 | let marker = queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.namedParameter.rawValue] 45 | if marker == "" { 46 | return Parameter.namedParameterStart + name + Parameter.namedParameterEnd 47 | } 48 | else { 49 | return marker + name 50 | } 51 | } 52 | else { 53 | return Parameter.numberedParameterMarker 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/IndexColumnOrdered.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: IndexColumnOrdered 18 | 19 | /// An enumeration for columns used in indices along with their sort order. 20 | public enum IndexColumnOrdered: IndexColumn { 21 | public var table: Table { 22 | switch self { 23 | case .ASC(let column): 24 | return column.table 25 | case .DESC(let column): 26 | return column.table 27 | } 28 | } 29 | 30 | /// Order ascending. 31 | case ASC(Column) 32 | /// Order descending. 33 | case DESC(Column) 34 | 35 | /// Build the index column using `QueryBuilder`. 36 | /// 37 | /// - Parameter queryBuilder: The QueryBuilder to use. 38 | /// - Returns: A String representation of the index column. 39 | public func buildIndex(queryBuilder: QueryBuilder) -> String { 40 | switch self { 41 | case .ASC(let column): 42 | return column.buildIndex(queryBuilder: queryBuilder) + " ASC" 43 | case .DESC(let column): 44 | return column.buildIndex(queryBuilder: queryBuilder) + " DESC" 45 | } 46 | } 47 | } 48 | 49 | 50 | /// Create a IndexColumnOrdered.ASC for the column. 51 | /// 52 | /// - Parameter column: The column to apply IndexColumnOrdered.ASC. 53 | /// - Returns: IndexColumnOrdered.ASC for the column. 54 | public func asc(_ column: Column) -> IndexColumnOrdered { 55 | return IndexColumnOrdered.ASC(column) 56 | } 57 | 58 | /// Create a IndexColumnOrdered.DESC for the column. 59 | /// 60 | /// - Parameter column: The column to apply IndexColumnOrdered.ASC. 61 | /// - Returns: IndexColumnOrdered.DESC for the column. 62 | public func desc(_ column: Column) -> IndexColumnOrdered { 63 | return IndexColumnOrdered.DESC(column) 64 | } 65 | -------------------------------------------------------------------------------- /Scripts/filterAndHavingExtensions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/** 4 | #* Copyright IBM Corporation 2016, 2017 5 | #* 6 | #* Licensed under the Apache License, Version 2.0 (the "License"); 7 | #* you may not use this file except in compliance with the License. 8 | #* You may obtain a copy of the License at 9 | #* 10 | #* http://www.apache.org/licenses/LICENSE-2.0 11 | #* 12 | #* Unless required by applicable law or agreed to in writing, software 13 | #* distributed under the License is distributed on an "AS IS" BASIS, 14 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | #* See the License for the specific language governing permissions and 16 | #* limitations under the License. 17 | #**/ 18 | 19 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 20 | cd "$SCRIPT_DIR" 21 | CUR_DIR=$(pwd) 22 | 23 | temp=$(dirname "${CUR_DIR}") 24 | temp=$(dirname "${temp}") 25 | PKG_DIR=$(dirname "${CUR_DIR}") 26 | 27 | shopt -s nullglob 28 | 29 | if ! [ -d "${PKG_DIR}/Sources/SwiftKuery" ]; then 30 | echo "Failed to find ${PKG_DIR}/Sources/SwiftKuery" 31 | exit 1 32 | fi 33 | 34 | OUTPUT_FILE="${PKG_DIR}/Sources/SwiftKuery/FilterAndHaving_Extensions.swift" 35 | 36 | echo "--- Generating ${OUTPUT_FILE}" 37 | 38 | cat <<'EOF' > ${OUTPUT_FILE} 39 | /** 40 | * Copyright IBM Corporation 2016, 2017 41 | * 42 | * Licensed under the Apache License, Version 2.0 (the "License"); 43 | * you may not use this file except in compliance with the License. 44 | * You may obtain a copy of the License at 45 | * 46 | * http://www.apache.org/licenses/LICENSE-2.0 47 | * 48 | * Unless required by applicable law or agreed to in writing, software 49 | * distributed under the License is distributed on an "AS IS" BASIS, 50 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 51 | * See the License for the specific language governing permissions and 52 | * limitations under the License. 53 | **/ 54 | 55 | EOF 56 | 57 | # Generate extensions for Filter and Having for IS (NOT) NULL operators 58 | 59 | for CLAUSE_TYPE in Filter Having; do 60 | cat <> ${OUTPUT_FILE} 61 | public extension $CLAUSE_TYPE { 62 | EOF 63 | for OPERATOR in isNull isNotNull; do 64 | 65 | cat <> ${OUTPUT_FILE} 66 | 67 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator. 68 | /// 69 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 70 | public func $OPERATOR() -> $CLAUSE_TYPE { 71 | return $CLAUSE_TYPE(lhs: .clause(self), condition: .$OPERATOR) 72 | } 73 | EOF 74 | done 75 | echo "}" >> ${OUTPUT_FILE} 76 | done 77 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2017 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | import XCTest 18 | import Glibc 19 | @testable import SwiftKueryTests 20 | 21 | // http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift 22 | #if swift(>=4) 23 | 24 | extension MutableCollection { 25 | mutating func shuffle() { 26 | let c = count 27 | guard c > 1 else { return } 28 | 29 | srand(UInt32(time(nil))) 30 | for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { 31 | let d: IndexDistance = numericCast(random() % numericCast(unshuffledCount)) 32 | guard d != 0 else { continue } 33 | let i = index(firstUnshuffled, offsetBy: d) 34 | swapAt(firstUnshuffled, i) 35 | } 36 | } 37 | } 38 | 39 | #else 40 | 41 | extension MutableCollection where Indices.Iterator.Element == Index { 42 | mutating func shuffle() { 43 | let c = count 44 | guard c > 1 else { return } 45 | 46 | srand(UInt32(time(nil))) 47 | for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { 48 | let d: IndexDistance = numericCast(random() % numericCast(unshuffledCount)) 49 | guard d != 0 else { continue } 50 | let i = index(firstUnshuffled, offsetBy: d) 51 | swap(&self[firstUnshuffled], &self[i]) 52 | } 53 | } 54 | } 55 | 56 | #endif 57 | 58 | extension Sequence { 59 | func shuffled() -> [Iterator.Element] { 60 | var result = Array(self) 61 | result.shuffle() 62 | return result 63 | } 64 | } 65 | 66 | XCTMain([ 67 | testCase(TestAggregateFunctions.allTests.shuffled()), 68 | testCase(TestAlias.allTests.shuffled()), 69 | testCase(TestFilterAndHaving.allTests.shuffled()), 70 | testCase(TestInsert.allTests.shuffled()), 71 | testCase(TestJoin.allTests.shuffled()), 72 | testCase(TestParameters.allTests.shuffled()), 73 | testCase(TestQueryResult.allTests.shuffled()), 74 | testCase(TestRaw.allTests.shuffled()), 75 | testCase(TestSchema.allTests.shuffled()), 76 | testCase(TestSelect.allTests.shuffled()), 77 | testCase(TestSpecialOperators.allTests.shuffled()), 78 | testCase(TestSubqueries.allTests.shuffled()), 79 | testCase(TestSubquery.allTests.shuffled()), 80 | testCase(TestSyntaxError.allTests.shuffled()), 81 | testCase(TestUpdate.allTests.shuffled()), 82 | ]) 83 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/With.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: With 18 | 19 | 20 | /// Create a query with a WITH clause. 21 | /// 22 | /// - Parameter table: A table for a WITH clause. 23 | /// - Parameter query: A SELECT query that will use the table from the WITH clause. 24 | public func with(_ table: AuxiliaryTable, _ query: Select) -> Select { 25 | return with([table], query) 26 | } 27 | 28 | /// Create a query with a WITH clause. 29 | /// 30 | /// - Parameter tables: An array of tables for a WITH clause. 31 | /// - Parameter query: A SELECT query that will use the table from the WITH clause. 32 | public func with(_ tables: [AuxiliaryTable], _ query: Select) -> Select { 33 | return query.with(tables) 34 | } 35 | 36 | 37 | /// Create a query with a WITH clause. 38 | /// 39 | /// - Parameter table: A table for a WITH clause. 40 | /// - Parameter query: An INSERT query that will use the table from the WITH clause. 41 | public func with(_ table: AuxiliaryTable, _ query: Insert) -> Insert { 42 | return with([table], query) 43 | } 44 | 45 | /// Create a query with a WITH clause. 46 | /// 47 | /// - Parameter tables: An array of tables for a WITH clause. 48 | /// - Parameter query: An INSERT query that will use the table from the WITH clause. 49 | public func with(_ tables: [AuxiliaryTable], _ query: Insert) -> Insert { 50 | return query.with(tables) 51 | } 52 | 53 | 54 | /// Create a query with a WITH clause. 55 | /// 56 | /// - Parameter table: A table for a WITH clause. 57 | /// - Parameter query: An UPDATE query that will use the table from the WITH clause. 58 | public func with(_ table: AuxiliaryTable, _ query: Update) -> Update { 59 | return with([table], query) 60 | } 61 | 62 | /// Create a query with a WITH clause. 63 | /// 64 | /// - Parameter tables: An array of tables for a WITH clause. 65 | /// - Parameter query: An UPDATE query that will use the table from the WITH clause. 66 | public func with(_ tables: [AuxiliaryTable], _ query: Update) -> Update { 67 | return query.with(tables) 68 | } 69 | 70 | 71 | /// Create a query with a WITH clause. 72 | /// 73 | /// - Parameter table: A table for a WITH clause. 74 | /// - Parameter query: A DELETE query that will use the table from the WITH clause. 75 | public func with(_ table: AuxiliaryTable, _ query: Delete) -> Delete { 76 | return with([table], query) 77 | } 78 | 79 | /// Create a query with a WITH clause. 80 | /// 81 | /// - Parameter tables: An array of tables for a WITH clause. 82 | /// - Parameter query: A DELETE query that will use the table from the WITH clause. 83 | public func with(_ tables: [AuxiliaryTable], _ query: Delete) -> Delete { 84 | return query.with(tables) 85 | } 86 | 87 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Condition.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Condition 18 | 19 | /// An enumeration of condition operators supported in `Filter` and `Having`. 20 | public enum Condition: Buildable { 21 | /// The SQL == operator. 22 | case equal 23 | /// The SQL != operator. 24 | case notEqual 25 | /// The SQL > operator. 26 | case greaterThan 27 | /// The SQL < operator. 28 | case lessThan 29 | /// The SQL >= operator. 30 | case greaterThanOrEqual 31 | /// The SQL <= operator. 32 | case lessThanOrEqual 33 | /// The SQL BETWEEN operator. 34 | case between 35 | /// The SQL NOT BETWEEN operator. 36 | case notBetween 37 | /// The SQL LIKE operator. 38 | case like 39 | /// The SQL LIKE operator. 40 | case notLike 41 | /// The SQL IN operator. 42 | case `in` 43 | /// The SQL NOT IN operator. 44 | case notIn 45 | /// The SQL EXISTS operator. 46 | case exists 47 | /// The SQL NOT EXISTS operator. 48 | case notExists 49 | /// The SQL IS NULL operator. 50 | case isNull 51 | /// The SQL IS NOT NULL operator. 52 | case isNotNull 53 | /// The SQL AND operator. 54 | case and 55 | /// The SQL OR operator. 56 | case or 57 | 58 | /// Build the condition, i.e., return its representation as a String. 59 | /// 60 | /// - Parameter queryBuilder: The QueryBuilder to use. 61 | /// - Returns: A String representation of the condition. 62 | public func build(queryBuilder: QueryBuilder) -> String { 63 | switch self { 64 | case .equal: 65 | return "=" 66 | case .notEqual: 67 | return "<>" 68 | case .greaterThan: 69 | return ">" 70 | case .lessThan: 71 | return "<" 72 | case .greaterThanOrEqual: 73 | return ">=" 74 | case .lessThanOrEqual: 75 | return "<=" 76 | case .between: 77 | return "BETWEEN" 78 | case .notBetween: 79 | return "NOT BETWEEN" 80 | case .like: 81 | return "LIKE" 82 | case .notLike: 83 | return "NOT LIKE" 84 | case .in: 85 | return "IN" 86 | case .notIn: 87 | return "NOT IN" 88 | case .exists: 89 | return "EXISTS" 90 | case .notExists: 91 | return "NOT EXISTS" 92 | case .isNull: 93 | return "IS NULL" 94 | case .isNotNull: 95 | return "IS NOT NULL" 96 | case .and: 97 | return "AND" 98 | case .or: 99 | return "OR" 100 | } 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/ColumnCreator.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2018, 2019 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // MARK: ColumnCreator protocol 20 | 21 | /// A protocol defining functions database plugins must implement to build a string representation of a Column 22 | public protocol ColumnCreator { 23 | 24 | /// Build a string representation of the column 25 | /// 26 | /// - Parameter column: The column being built 27 | /// - Returns: A string representation of the column for the implementing database or nil if it cannot be built 28 | func buildColumn(for column: Column, using queryBuilder: QueryBuilder) -> String? 29 | 30 | /// Build an appropriate representation of a passed value for the database 31 | /// A default implementation is provided that can be overriden should a 32 | /// plugin require non-common behaviour 33 | /// 34 | /// - Parameter item: The value to convert 35 | /// - Returns: A string representing the value that can be passed into the database 36 | func packType(_ item: Any, queryBuilder: QueryBuilder) throws -> String 37 | 38 | /// Get the default value for a column 39 | /// 40 | /// - Parameter for: The Column to get the default value for 41 | /// - Parameter queryBuilder: The plugin specific queryBuilder 42 | /// - Returns: A string representing the default value for the column or nil if no default value is set 43 | func getDefaultValue(for column: Column, queryBuilder: QueryBuilder) throws -> String? 44 | } 45 | 46 | public extension ColumnCreator { 47 | 48 | func getDefaultValue(for column: Column, queryBuilder: QueryBuilder) -> String? { 49 | guard let defaultValue = column.defaultValue else { 50 | if column.nullDefaultValue { 51 | return "NULL" 52 | } 53 | return nil 54 | } 55 | do { 56 | return try packType(defaultValue, queryBuilder: queryBuilder) 57 | } catch { 58 | return nil 59 | } 60 | } 61 | 62 | func packType(_ item: Any, queryBuilder: QueryBuilder) throws -> String { 63 | switch item { 64 | case let val as String: 65 | return "'\(val)'" 66 | case let val as Bool: 67 | return val ? queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.booleanTrue.rawValue] 68 | : queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.booleanFalse.rawValue] 69 | case let val as Parameter: 70 | return try val.build(queryBuilder: queryBuilder) 71 | case let value as Date: 72 | if let dateFormatter = queryBuilder.dateFormatter { 73 | return dateFormatter.string(from: value) 74 | } 75 | return "'\(String(describing: value))'" 76 | default: 77 | return String(describing: item) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Scripts/SubqueriesTypes.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # An input file for subqueries.sh that generates global operators with subqueries (i.e. 'expression operator ANY/ALL(subquery)'). 18 | # This file contains the list of all the types for the operators: (the result type (Filter or Having), LHS, RHS (the subquery type)). 19 | # The generated code is in Sources/SwiftKuery/Subqueries_GlobalFunctionsAndExtensions.swift. 20 | 21 | # This is also an input file for testSubqueries.sh that generates the tests for the code created by subqueries.sh. 22 | # The first three entries in each line are ignored. The format of the rest of the line is 23 | # (where/having, first operand in Swift, second operand in Swift, first operand in SQL, second operand in SQL). 24 | # The generated tests are in TestSubqueries.swift 25 | 26 | # The operators are in SimpleOperators.txt 27 | 28 | Filter ScalarColumnExpression Predicate where ucase(t.a) any(Select(t2.c,_from:_t2)) UCASE(table.a) ANY_(SELECT_table2.c_FROM_table2) 29 | Filter String Predicate where "peach" all(Select(t2.c,_from:_t2)) 'peach' ALL_(SELECT_table2.c_FROM_table2) 30 | Filter Column Predicate where t.a any(Select(t2.c,_from:_t2)) table.a ANY_(SELECT_table2.c_FROM_table2) 31 | Filter Int Predicate where 3 any(Select(t2.c,_from:_t2)) 3 ANY_(SELECT_table2.c_FROM_table2) 32 | Filter Float Predicate where Float(3.5) any(Select(t2.c,_from:_t2)) 3.5 ANY_(SELECT_table2.c_FROM_table2) 33 | Filter Double Predicate where -8.1 any(Select(t2.c,_from:_t2)) -8.1 ANY_(SELECT_table2.c_FROM_table2) 34 | Filter Parameter Predicate where Parameter("fruit") any(Select(t2.c,_from:_t2)) @fruit ANY_(SELECT_table2.c_FROM_table2) 35 | Filter Bool Predicate where true all(Select(t2.c,_from:_t2)) true ALL_(SELECT_table2.c_FROM_table2) 36 | Having AggregateColumnExpression Predicate having sum(t.a) any(Select(t2.c,_from:_t2)) SUM(table.a) ANY_(SELECT_table2.c_FROM_table2) 37 | Having String Predicate having "peach" all(Select(t2.c,_from:_t2)) 'peach' ALL_(SELECT_table2.c_FROM_table2) 38 | Having Column Predicate having t.a any(Select(t2.c,_from:_t2)) table.a ANY_(SELECT_table2.c_FROM_table2) 39 | Having Int Predicate having 3 any(Select(t2.c,_from:_t2)) 3 ANY_(SELECT_table2.c_FROM_table2) 40 | Having Float Predicate having Float(3.5) any(Select(t2.c,_from:_t2)) 3.5 ANY_(SELECT_table2.c_FROM_table2) 41 | Having Double Predicate having -8.1 any(Select(t2.c,_from:_t2)) -8.1 ANY_(SELECT_table2.c_FROM_table2) 42 | Having Parameter Predicate having Parameter() any(Select(t2.c,_from:_t2)) \?1 ANY_(SELECT_table2.c_FROM_table2) 43 | Having Bool Predicate having true all(Select(t2.c,_from:_t2)) true ALL_(SELECT_table2.c_FROM_table2) 44 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/ResultSet.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: ResultSet 18 | 19 | /// Represents a query result set. The rows are accessable either in a blocking fashion using a `RowSequence` or in a non-blocking fashion using nextRow() function. 20 | public class ResultSet { 21 | private var resultFetcher: ResultFetcher 22 | 23 | var connection: Connection? = nil 24 | 25 | /// Instantiate an instance of ResultSet. 26 | /// 27 | /// - Parameter resultFetcher: An implementation of `ResultFetcher` protocol to fetch the query results. 28 | public init(_ resultFetcher: ResultFetcher, connection: Connection) { 29 | self.resultFetcher = resultFetcher 30 | self.connection = connection 31 | } 32 | 33 | /// Fetch the next row of the query result. This function is non-blocking. 34 | /// 35 | /// - Parameter callback: A callback to call when the next row of the query result is ready. 36 | public func nextRow(callback: @escaping (([Any?]?, Error?)) ->()) { 37 | resultFetcher.fetchNext { row, error in 38 | callback((row, error)) 39 | } 40 | } 41 | 42 | /// Fetch the column titles of the query result. This function is non-blocking 43 | public func getColumnTitles( callback: @escaping (([String]?, Error?)) -> ()) { 44 | resultFetcher.fetchTitles(callback: callback) 45 | } 46 | 47 | /// Called to indicate no further operations will be called on the result set. 48 | /// A ResultSet will keep a connection alive until this method is called. 49 | /// When called this method enables the underlying connection to be released and in the case where a connection pool is used, returned to the pool for reuse. 50 | public func done() { 51 | // Nil connection reference once result fetcher cleanup is complete. 52 | resultFetcher.done() 53 | self.connection = nil 54 | } 55 | 56 | /// Type alias for closures to be passed to the forEach method 57 | public typealias RowOperation = ([Any?]?, Error?) -> Void 58 | 59 | /// Type alias for closures to be passed to the forEach method which allows asynchronous opertions. 60 | public typealias RowOperationWithNext = ([Any?]?, Error?, () -> Void) -> Void 61 | 62 | /// Execute the supplied RowOperation against each row returned from the database 63 | /// 64 | /// - Parameter operation: A callback to be executed against each row in the result set. 65 | public func forEach(operation: @escaping RowOperation) { 66 | resultFetcher.fetchNext { row, error in 67 | operation(row, error) 68 | if row != nil { 69 | self.forEach(operation: operation) 70 | } 71 | } 72 | } 73 | 74 | /// Execute the supplied RowOperation against each row returned from the database 75 | /// 76 | /// - Parameter operation: A callback to be executed against each row in the result set. 77 | public func forEach(operation: @escaping RowOperationWithNext) { 78 | resultFetcher.fetchNext { row, error in 79 | operation(row, error, { 80 | self.forEach(operation: operation) 81 | }) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/TestParameters.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | 19 | @testable import SwiftKuery 20 | 21 | class TestParameters: XCTestCase { 22 | 23 | static var allTests: [(String, (TestParameters) -> () throws -> Void)] { 24 | return [ 25 | ("testParameters", testParameters), 26 | ] 27 | } 28 | 29 | class MyTable : Table { 30 | let a = Column("a") 31 | let b = Column("b") 32 | 33 | let tableName = "tableParameters" 34 | } 35 | 36 | func testParameters() { 37 | let t = MyTable() 38 | let connection = createConnection() 39 | 40 | let i = Insert(into: t, rows: [[Parameter(), 10], ["apricot", Parameter()], [Parameter(), Parameter()]]) 41 | var kuery = connection.descriptionOf(query: i) 42 | var query = "INSERT INTO \"tableParameters\" VALUES (?1, 10), ('apricot', ?2), (?3, ?4)" 43 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 44 | 45 | var u = Update(t, set: [(t.a, Parameter()), (t.b, Parameter())], where: t.a == "banana") 46 | kuery = connection.descriptionOf(query: u) 47 | query = "UPDATE \"tableParameters\" SET \"a\" = ?1, \"b\" = ?2 WHERE \"tableParameters\".\"a\" = 'banana'" 48 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 49 | 50 | u = Update(t, set: [(t.a, Parameter("name")), (t.b, Parameter())], where: t.a == "banana") 51 | kuery = connection.descriptionOf(query: u) 52 | query = "UPDATE \"tableParameters\" SET \"a\" = @name, \"b\" = ?1 WHERE \"tableParameters\".\"a\" = 'banana'" 53 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 54 | 55 | let d = Delete(from: t) 56 | .where(t.b == Parameter()) 57 | kuery = connection.descriptionOf(query: d) 58 | query = "DELETE FROM \"tableParameters\" WHERE \"tableParameters\".\"b\" = ?1" 59 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 60 | 61 | var s = Select(t.a, from: t) 62 | .where(t.b.notBetween(Parameter(), and: Parameter())) 63 | .group(by: t.a) 64 | .order(by: .DESC(t.a)) 65 | .having(sum(t.b) < Parameter()) 66 | kuery = connection.descriptionOf(query: s) 67 | query = "SELECT \"tableParameters\".\"a\" FROM \"tableParameters\" WHERE \"tableParameters\".\"b\" NOT BETWEEN ?1 AND ?2 GROUP BY \"tableParameters\".\"a\" HAVING SUM(\"tableParameters\".\"b\") < ?3 ORDER BY \"tableParameters\".\"a\" DESC" 68 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 69 | 70 | s = Select(t.a, from: t) 71 | .where(t.a.like(Parameter())) 72 | kuery = connection.descriptionOf(query: s) 73 | query = "SELECT \"tableParameters\".\"a\" FROM \"tableParameters\" WHERE \"tableParameters\".\"a\" LIKE ?1" 74 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Filter.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Filter 18 | 19 | /// A condition used in an SQL WHERE or ON clause. 20 | public struct Filter: ConditionalClause, QueryFilterProtocol { 21 | public typealias ClauseType = Filter 22 | public typealias ColumnExpressionType = ScalarColumnExpression 23 | /// The left hand side of the conditional clause. 24 | public let lhs: Predicate? 25 | /// The right hand side of the conditional clause. 26 | public let rhs: Predicate? 27 | /// The operator of the conditional clause. 28 | public let condition: Condition 29 | 30 | init(lhs: Predicate?=nil, rhs: Predicate?=nil, condition: Condition) { 31 | self.lhs = lhs 32 | self.rhs = rhs 33 | self.condition = condition 34 | } 35 | } 36 | 37 | // MARK: QueryFilterProtocol 38 | 39 | /// Defines the protocol which should be used for all filtering clauses. 40 | /// Represents a filter as String value. 41 | public protocol QueryFilterProtocol: Buildable { 42 | 43 | } 44 | 45 | // MARK Global functions 46 | 47 | /// Create a `Filter` clause using the OR operator. 48 | /// 49 | /// - Parameter lhs: A `Filter` - the left hand side of the clause. 50 | /// - Parameter rhs: A `Filter` - the right hand side of the clause. 51 | /// - Returns: A `Filter` containing the clause. 52 | public func || (lhs: Filter, rhs: Filter) -> Filter { 53 | return Filter(lhs: .clause(lhs), rhs: .clause(rhs), condition: .or) 54 | } 55 | 56 | /// Create a `Filter` clause using the AND operator. 57 | /// 58 | /// - Parameter lhs: A `Filter` - the left hand side of the clause. 59 | /// - Parameter rhs: A `Filter` - the right hand side of the clause. 60 | /// - Returns: A `Filter` containing the clause. 61 | public func && (lhs: Filter, rhs: Filter) -> Filter { 62 | return Filter(lhs: .clause(lhs), rhs: .clause(rhs), condition: .and) 63 | } 64 | 65 | /// Create a `Filter` clause using the EXISTS operator. 66 | /// 67 | /// - Parameter query: The `Select` query to apply EXISTS on. 68 | /// - Returns: A `Filter` containing the clause. 69 | public func exists(_ query: Select) -> Filter { 70 | return Filter(rhs: .select(query), condition: .exists) 71 | } 72 | 73 | /// Create a `Filter` clause using the NOT EXISTS operator. 74 | /// 75 | /// - Parameter query: The `Select` query to apply NOT EXISTS on. 76 | /// - Returns: A `Filter` containing the clause. 77 | public func notExists(_ query: Select) -> Filter { 78 | return Filter(rhs: .select(query), condition: .notExists) 79 | } 80 | 81 | /// Create a `FilterPredicate` using the ANY operator. 82 | /// 83 | /// - Parameter query: The `Select` query to apply ANY on. 84 | /// - Returns: A `Predicate` containing the anySubquery. 85 | public func any(_ query: Select) -> Predicate { 86 | return .anySubquery(query) 87 | } 88 | 89 | /// Create a `FilterPredicate` using the ALL operator. 90 | /// 91 | /// - Parameter query: The `Select` query to apply ALL on. 92 | /// - Returns: A `Predicate` containing the allSubquery. 93 | public func all(_ query: Select) -> Predicate { 94 | return .allSubquery(query) 95 | } 96 | 97 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Having.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Having 18 | 19 | /// An SQL HAVING clause. 20 | public struct Having: ConditionalClause, QueryHavingProtocol { 21 | public typealias ClauseType = Having 22 | public typealias ColumnExpressionType = AggregateColumnExpression 23 | /// The left hand side of the conditional clause. 24 | public let lhs: Predicate? 25 | /// The right hand side of the conditional clause. 26 | public let rhs: Predicate? 27 | /// The operator of the conditional clause. 28 | public let condition: Condition 29 | 30 | init(lhs: Predicate?=nil, rhs: Predicate?=nil, condition: Condition) { 31 | self.lhs = lhs 32 | self.rhs = rhs 33 | self.condition = condition 34 | } 35 | } 36 | 37 | // MARK: QueryHavingProtocol 38 | 39 | /// Defines the protocol which should be used for all HAVING clauses. 40 | /// Represents a HAVING clause as String value. 41 | public protocol QueryHavingProtocol: Buildable { 42 | 43 | } 44 | 45 | // MARK Global functions 46 | 47 | /// Create a `Having` clause using the OR operator. 48 | /// 49 | /// - Parameter lhs: A `Having` - the left hand side of the clause. 50 | /// - Parameter rhs: A `Having` - the right hand side of the clause. 51 | /// - Returns: A `Having` containing the clause. 52 | public func || (lhs: Having, rhs: Having) -> Having { 53 | return Having(lhs: .clause(lhs), rhs: .clause(rhs), condition: .or) 54 | } 55 | 56 | /// Create a `Having` clause using the AND operator. 57 | /// 58 | /// - Parameter lhs: A `Having` - the left hand side of the clause. 59 | /// - Parameter rhs: A `Having` - the right hand side of the clause. 60 | /// - Returns: A `Having` containing the clause. 61 | public func && (lhs: Having, rhs: Having) -> Having { 62 | return Having(lhs: .clause(lhs), rhs: .clause(rhs), condition: .and) 63 | } 64 | 65 | /// Create a `Having` clause using the EXISTS operator. 66 | /// 67 | /// - Parameter query: The `Select` query to apply EXISTS on. 68 | /// - Returns: A `Having` containing the clause. 69 | public func exists(_ query: Select) -> Having { 70 | return Having(rhs: .select(query), condition: .exists) 71 | } 72 | 73 | /// Create a `Having` clause using the NOT EXISTS operator. 74 | /// 75 | /// - Parameter query: The `Select` query to apply NOT EXISTS on. 76 | /// - Returns: A `Having` containing the clause. 77 | public func notExists(_ query: Select) -> Having { 78 | return Having(rhs: .select(query), condition: .notExists) 79 | } 80 | 81 | /// Create a `HavingPredicate` using the ANY operator. 82 | /// 83 | /// - Parameter query: The `Select` query to apply ANY on. 84 | /// - Returns: A `Predicate` containing the anySubquery. 85 | public func any(_ query: Select) -> Predicate { 86 | return .anySubquery(query) 87 | } 88 | 89 | /// Create a `HavingPredicate` using the ALL operator. 90 | /// 91 | /// - Parameter query: The `Select` query to apply ALL on. 92 | /// - Returns: A `Predicate` containing the allSubquery. 93 | public func all(_ query: Select) -> Predicate { 94 | return .allSubquery(query) 95 | } 96 | -------------------------------------------------------------------------------- /Scripts/TestBetweenAndIn.txt: -------------------------------------------------------------------------------- 1 | #/** 2 | #* Copyright IBM Corporation 2016, 2017 3 | #* 4 | #* Licensed under the Apache License, Version 2.0 (the "License"); 5 | #* you may not use this file except in compliance with the License. 6 | #* You may obtain a copy of the License at 7 | #* 8 | #* http://www.apache.org/licenses/LICENSE-2.0 9 | #* 10 | #* Unless required by applicable law or agreed to in writing, software 11 | #* distributed under the License is distributed on an "AS IS" BASIS, 12 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | #* See the License for the specific language governing permissions and 14 | #* limitations under the License. 15 | #**/ 16 | 17 | # Arguments of the BETWEEN and IN operators: operand.in/between(arguments) 18 | # The line format is (where/having, operand in Swift, operand in SQL, argument1 in Swift, argument2 in Swift, argument1 in SQL, argument2 in SQL) 19 | # Used in testSpecialOperators.sh. The resulting test file is TestSpecialOperators.swift. 20 | 21 | where t.a table.a true false true false 22 | having t.a table.a true false true false 23 | where t.a table.a "apple" "peach" 'apple' 'peach' 24 | having t.a table.a "apple" "peach" 'apple' 'peach' 25 | where t.a table.a 3 6 3 6 26 | having t.a table.a 3 6 3 6 27 | where t.a table.a 2.71828 3.14159 2.71828 3.14159 28 | having t.a table.a 2.71828 3.14159 2.71828 3.14159 29 | where t.a table.a Float(2.71828) Float(3.14159) 2.71828 3.14159 30 | having t.a table.a 2.71828 3.14159 2.71828 3.14159 31 | where t.a table.a Parameter() Parameter() \?1 \?2 32 | having t.a table.a Parameter() Parameter() \?1 \?2 33 | where ucase(t.a) UCASE(table.a) true false true false 34 | having last(t.a) LAST(table.a) true false true false 35 | where ucase(t.a) UCASE(table.a) "apple" "peach" 'apple' 'peach' 36 | having last(t.a) LAST(table.a) "apple" "peach" 'apple' 'peach' 37 | where ucase(t.a) UCASE(table.a) 3 6 3 6 38 | having last(t.a) LAST(table.a) 3 6 3 6 39 | where ucase(t.a) UCASE(table.a) 2.71828 3.14159 2.71828 3.14159 40 | having last(t.a) LAST(table.a) 2.71828 3.14159 2.71828 3.14159 41 | where ucase(t.a) UCASE(table.a) Float(2.71828) Float(3.14159) 2.71828 3.14159 42 | having last(t.a) LAST(table.a) Float(2.71828) Float(3.14159) 2.71828 3.14159 43 | where ucase(t.a) UCASE(table.a) Parameter() Parameter() \?1 \?2 44 | having last(t.a) LAST(table.a) Parameter() Parameter() \?1 \?2 45 | where true true true false true false 46 | having true true true false true false 47 | where true true Parameter() Parameter() \?1 \?2 48 | having true true Parameter() Parameter() \?1 \?2 49 | where "banana" 'banana' "apple" "peach" 'apple' 'peach' 50 | having "banana" 'banana' "apple" "peach" 'apple' 'peach' 51 | where "banana" 'banana' Parameter("first") Parameter("second") @first @second 52 | having "banana" 'banana' Parameter("first") Parameter("second") @first @second 53 | where 4 4 3 6 3 6 54 | having 4 4 3 6 3 6 55 | where 4 4 Parameter() Parameter() \?1 \?2 56 | having 4 4 Parameter("first") Parameter("second") @first @second 57 | where Float(2.71828) 2.71828 Float(2.71828) Float(3.14159) 2.71828 3.14159 58 | having Float(2.71828) 2.71828 Float(2.71828) Float(3.14159) 2.71828 3.14159 59 | where 3.001 3.001 3.14159 2.71828 3.14159 2.71828 60 | having 3.008 3.008 3.14159 2.71828 3.14159 2.71828 61 | where Float(2.71828) 2.71828 Parameter() Parameter() \?1 \?2 62 | having Float(2.71828) 2.71828 Parameter("first") Parameter("second") @first @second 63 | where 3.001 3.001 Parameter("first") Parameter("second") @first @second 64 | having 3.008 3.008 Parameter() Parameter() \?1 \?2 65 | where Parameter("first") @first Parameter("second") Parameter("third") @second @third 66 | having Parameter("first") @first Parameter("second") Parameter("third") @second @third 67 | where Date(timeIntervalSince1970:_50000) '1970-01-01_13:53:20_+0000' Date(timeIntervalSince1970:_50000) Date(timeIntervalSince1970:_0) '1970-01-01_13:53:20_+0000' '1970-01-01_00:00:00_+0000' 68 | having Date(timeIntervalSince1970:_50000) '1970-01-01_13:53:20_+0000' Date(timeIntervalSince1970:_50000) Date(timeIntervalSince1970:_0) '1970-01-01_13:53:20_+0000' '1970-01-01_00:00:00_+0000' 69 | 70 | -------------------------------------------------------------------------------- /Scripts/simpleOperators.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/** 4 | #* Copyright IBM Corporation 2016, 2017 5 | #* 6 | #* Licensed under the Apache License, Version 2.0 (the "License"); 7 | #* you may not use this file except in compliance with the License. 8 | #* You may obtain a copy of the License at 9 | #* 10 | #* http://www.apache.org/licenses/LICENSE-2.0 11 | #* 12 | #* Unless required by applicable law or agreed to in writing, software 13 | #* distributed under the License is distributed on an "AS IS" BASIS, 14 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | #* See the License for the specific language governing permissions and 16 | #* limitations under the License. 17 | #**/ 18 | 19 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 20 | cd "$SCRIPT_DIR" 21 | CUR_DIR=$(pwd) 22 | 23 | temp=$(dirname "${CUR_DIR}") 24 | temp=$(dirname "${temp}") 25 | PKG_DIR=$(dirname "${CUR_DIR}") 26 | 27 | shopt -s nullglob 28 | 29 | if ! [ -d "${PKG_DIR}/Sources/SwiftKuery" ]; then 30 | echo "Failed to find ${PKG_DIR}/Sources/SwiftKuery" 31 | exit 1 32 | fi 33 | 34 | INPUT_OPERATORS_FILE="${PKG_DIR}/Scripts/SimpleOperators.txt" 35 | INPUT_TYPES_FILE="${PKG_DIR}/Scripts/FilterAndHavingTypes.txt" 36 | INPUT_BOOL_TYPES_FILE="${PKG_DIR}/Scripts/FilterAndHavingBoolTypes.txt" 37 | INPUT_BOOL_OPERATORS_FILE="${PKG_DIR}/Scripts/FilterAndHavingBoolOperators.txt" 38 | 39 | OUTPUT_FILE="${PKG_DIR}/Sources/SwiftKuery/FilterAndHaving_GlobalFunctions.swift" 40 | 41 | echo "--- Generating ${OUTPUT_FILE}" 42 | 43 | cat <<'EOF' > ${OUTPUT_FILE} 44 | /** 45 | * Copyright IBM Corporation 2016, 2017 46 | * 47 | * Licensed under the Apache License, Version 2.0 (the "License"); 48 | * you may not use this file except in compliance with the License. 49 | * You may obtain a copy of the License at 50 | * 51 | * http://www.apache.org/licenses/LICENSE-2.0 52 | * 53 | * Unless required by applicable law or agreed to in writing, software 54 | * distributed under the License is distributed on an "AS IS" BASIS, 55 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 56 | * See the License for the specific language governing permissions and 57 | * limitations under the License. 58 | **/ 59 | 60 | import Foundation 61 | 62 | EOF 63 | 64 | # Generate operators for simple conditions that return Filter and Having 65 | for INPUT_TYPES in $INPUT_TYPES_FILE $INPUT_BOOL_TYPES_FILE; do 66 | if [[ $INPUT_TYPES == *"Bool"* ]] 67 | then 68 | INPUT_OPERATORS=$INPUT_BOOL_OPERATORS_FILE 69 | else 70 | INPUT_OPERATORS=$INPUT_OPERATORS_FILE 71 | fi 72 | 73 | while read -r LINE; do 74 | [ -z "$LINE" ] && continue 75 | [[ "$LINE" =~ ^#.*$ ]] && continue 76 | stringarray=($LINE) 77 | OPERATOR=${stringarray[0]} 78 | CASE=${stringarray[1]} 79 | while read -r LINE; do 80 | [ -z "$LINE" ] && continue 81 | [[ "$LINE" =~ ^#.*$ ]] && continue 82 | stringarray=($LINE) 83 | TYPE=${stringarray[0]} 84 | LHS_TYPE=${stringarray[1]} 85 | RHS_TYPE=${stringarray[2]} 86 | LHS_TYPE_LOWER="$(tr '[:upper:]' '[:lower:]' <<< ${LHS_TYPE:0:1})${LHS_TYPE:1}" 87 | RHS_TYPE_LOWER="$(tr '[:upper:]' '[:lower:]' <<< ${RHS_TYPE:0:1})${RHS_TYPE:1}" 88 | if [[ $LHS_TYPE_LOWER == *"ColumnExpression" ]] 89 | then 90 | LHS_TYPE_LOWER="columnExpression" 91 | fi 92 | if [[ $RHS_TYPE_LOWER == *"ColumnExpression" ]] 93 | then 94 | RHS_TYPE_LOWER="columnExpression" 95 | fi 96 | 97 | cat <> ${OUTPUT_FILE} 98 | /// Create a \`$TYPE\` clause using the operator $OPERATOR for $LHS_TYPE 99 | /// and $RHS_TYPE. 100 | /// 101 | /// - Parameter lhs: The left hand side of the clause. 102 | /// - Parameter rhs: The right hand side of the clause. 103 | /// - Returns: A \`$TYPE\` containing the clause. 104 | public func $OPERATOR(lhs: $LHS_TYPE, rhs: $RHS_TYPE) -> $TYPE { 105 | return $TYPE(lhs: .$LHS_TYPE_LOWER(lhs), rhs: .$RHS_TYPE_LOWER(rhs), condition: .$CASE) 106 | } 107 | 108 | EOF 109 | 110 | done < $INPUT_TYPES 111 | done < $INPUT_OPERATORS 112 | done 113 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Index.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Index 18 | 19 | /// The SQL INDEX. 20 | public struct Index { 21 | private var name: String 22 | private var isUnique: Bool 23 | private var table: Table 24 | private var columns: [IndexColumn] 25 | 26 | 27 | /// Initialize an instance of Index. 28 | /// 29 | /// - Parameter name: The name of the index. 30 | /// - Parameter unique: An indication whether the index has to be unique. 31 | /// - Parameter on table: The table of the index. 32 | /// - Parameter columns: An array of columns of the index. 33 | public init(_ name: String, unique: Bool = false, on table: Table, columns: [IndexColumn]) { 34 | self.name = name 35 | self.isUnique = unique 36 | self.table = table 37 | self.columns = columns 38 | } 39 | 40 | /// Initialize an instance of Index. 41 | /// 42 | /// - Parameter name: The name of the index. 43 | /// - Parameter unique: An indication whether the index has to be unique. 44 | /// - Parameter on table: The table of the index. 45 | /// - Parameter columns: A list of columns of the index. 46 | public init(_ name: String, unique: Bool = false, on table: Table, columns: IndexColumn...) { 47 | self.init(name, unique: unique, on: table, columns: columns) 48 | } 49 | 50 | /// Create the index in the database. 51 | /// 52 | /// - Parameter connection: The connection to the database. 53 | /// - Parameter onCompletion: The function to be called when the execution of the query has completed. 54 | public func create(connection: Connection, onCompletion: @escaping ((QueryResult) -> ())) { 55 | do { 56 | let query = try description(connection: connection) 57 | connection.execute(query, onCompletion: onCompletion) 58 | } 59 | catch { 60 | onCompletion(.error(error)) 61 | } 62 | } 63 | 64 | /// Drop the index from the database. 65 | /// 66 | /// - Parameter connection: The connection to the database. 67 | /// - Parameter onCompletion: The function to be called when the execution of the query has completed. 68 | public func drop(connection: Connection, onCompletion: @escaping ((QueryResult) -> ())) { 69 | let queryBuilder = connection.queryBuilder 70 | var query = "DROP INDEX " + Utils.packName(name, queryBuilder: queryBuilder) 71 | if queryBuilder.dropIndexRequiresOnTableName { 72 | query += " ON " + Utils.packName(table._name, queryBuilder: queryBuilder) 73 | } 74 | connection.execute(query, onCompletion: onCompletion) 75 | } 76 | 77 | /// Return a String representation of the index create statement. 78 | /// 79 | /// - Returns: A String representation of the index create statement. 80 | /// - Throws: QueryError.syntaxError if statement build fails. 81 | public func description(connection: Connection) throws -> String { 82 | for column in columns { 83 | if column.table._name != table._name { 84 | throw QueryError.syntaxError("Index contains columns that do not belong to its table.") 85 | } 86 | } 87 | 88 | var query = "CREATE \(isUnique ? "UNIQUE" : "") INDEX " 89 | 90 | let queryBuilder = connection.queryBuilder 91 | query += Utils.packName(name, queryBuilder: queryBuilder) + " ON " + Utils.packName(table._name, queryBuilder: queryBuilder) + " (" 92 | query += columns.map { $0.buildIndex(queryBuilder: queryBuilder) }.joined(separator: ", ") + ")" 93 | 94 | return query 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Predicate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // MARK Predicate 20 | 21 | /// An operand of a `ConditionalClause`. 22 | public indirect enum Predicate: Buildable { 23 | /// A `Having` or a `Filter` clause. 24 | case clause(ClauseType) 25 | /// A String. 26 | case string(String) 27 | /// An integer. 28 | case int(Int) 29 | /// A float. 30 | case float(Float) 31 | /// A double. 32 | case double(Double) 33 | /// A boolean. 34 | case bool(Bool) 35 | /// A date. 36 | case date(Date) 37 | /// An array of String. 38 | case arrayOfString([String]) 39 | /// An array of Int. 40 | case arrayOfInt([Int]) 41 | /// An array of Float. 42 | case arrayOfFloat([Float]) 43 | /// An array of Double. 44 | case arrayOfDouble([Double]) 45 | /// An array of Bool. 46 | case arrayOfBool([Bool]) 47 | /// An array of Date. 48 | case arrayOfDate([Date]) 49 | /// A `Column`. 50 | case column(Column) 51 | /// An `AggregateColumnExpression` or a `ScalarColumnExpression`. 52 | case columnExpression(ColumnExpressionType) 53 | /// A parameter. 54 | case parameter(Parameter) 55 | /// An array of Parameter. 56 | case arrayOfParameter([Parameter]) 57 | /// A `Select` query. 58 | case select(Select) 59 | /// ANY applied on a `Select` query. 60 | case anySubquery(Select) 61 | /// ALL applied on a `Select` query. 62 | case allSubquery(Select) 63 | 64 | /// Build the having predicate using `QueryBuilder`. 65 | /// 66 | /// - Parameter queryBuilder: The QueryBuilder to use. 67 | /// - Returns: A String representation of the havinh predicate. 68 | /// - Throws: QueryError.syntaxError if query build fails. 69 | public func build(queryBuilder: QueryBuilder) throws -> String { 70 | switch self { 71 | case .clause(let clause): 72 | return try "(" + clause.build(queryBuilder: queryBuilder) + ")" 73 | case .string(let string): 74 | return Utils.packType(string) 75 | case .int(let value): 76 | return Utils.packType(value) 77 | case .float(let value): 78 | return Utils.packType(value) 79 | case .double(let value): 80 | return Utils.packType(value) 81 | case .bool(let value): 82 | return try Utils.packType(value, queryBuilder: queryBuilder) 83 | case .date(let value): 84 | return try Utils.packType(value, queryBuilder: queryBuilder) 85 | case .column(let column): 86 | return try column.build(queryBuilder: queryBuilder) 87 | case .columnExpression(let columnExpression): 88 | return try columnExpression.build(queryBuilder: queryBuilder) 89 | case .parameter(let parameter): 90 | return try parameter.build(queryBuilder: queryBuilder) 91 | case .select(let subquery): 92 | return try "(" + subquery.build(queryBuilder: queryBuilder) + ")" 93 | case .anySubquery(let subquery): 94 | if queryBuilder.anyOnSubquerySupported { 95 | return try "ANY (" + subquery.build(queryBuilder: queryBuilder) + ")" 96 | } 97 | throw QueryError.syntaxError("ANY on subquery is not supported. ") 98 | case .allSubquery(let subquery): 99 | return try queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.all.rawValue] + " (" + subquery.build(queryBuilder: queryBuilder) + ")" 100 | default: 101 | return "" 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/ForeignKey.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017, 2018, 2019 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | struct ForeignKey: Buildable { 20 | var keyColumns: [Column] 21 | var refColumns: [Column] 22 | var keyNames: [String] 23 | var refNames: [String] 24 | 25 | public init?(keys: [Column], refs: [Column],_ tableName: String, _ errorString: inout String) { 26 | if !ForeignKey.validKey(keys, refs, tableName, &errorString) { 27 | return nil 28 | } 29 | keyColumns = keys 30 | refColumns = refs 31 | keyNames = keyColumns.map { "\($0._table._name).\($0.name)" } 32 | refNames = refColumns.map { "\($0._table._name).\($0.name)" } 33 | } 34 | 35 | static func validKey(_ keys: [Column], _ refs: [Column],_ tableName: String, _ errorString: inout String) -> Bool { 36 | if keys.count == 0 || refs.count == 0 || keys.count != refs.count { 37 | errorString = "Invalid definition of foreign key. " 38 | return false 39 | } 40 | else if !columnsBelongToSameTable(columns: keys, tableName: tableName) { 41 | errorString = "Foreign key contains columns from another table. " 42 | return false 43 | } 44 | else if !columnsBelongToSameTable(columns: refs, tableName: refs[0]._table._name) { 45 | errorString = "Foreign key references columns from more than one table. " 46 | return false 47 | } 48 | return true 49 | } 50 | 51 | static func columnsBelongToSameTable(columns: [Column], tableName: String) -> Bool { 52 | for column in columns { 53 | if column.table._name != tableName { 54 | return false 55 | } 56 | } 57 | return true 58 | } 59 | 60 | static public func == (lhs: ForeignKey, rhs: ForeignKey) -> Bool { 61 | // Foreign keys cannot span databases and we do not currently support temporary tables therefore checking key name and ref name sets match is sufficient to establish equality 62 | if !(Set(lhs.keyNames) == Set(rhs.keyNames)) { 63 | return false 64 | } 65 | if !(Set(lhs.refNames) == Set(rhs.refNames)) { 66 | return false 67 | } 68 | return true 69 | } 70 | 71 | func build(queryBuilder: QueryBuilder) -> String { 72 | var append = ", FOREIGN KEY (" 73 | append += keyColumns.map { Utils.packName($0.name, queryBuilder: queryBuilder) }.joined(separator: ", ") 74 | append += ") REFERENCES " 75 | append += Utils.packName(refColumns[0].table._name, queryBuilder: queryBuilder) 76 | append += "(" 77 | append += refColumns.map { Utils.packName($0.name, queryBuilder: queryBuilder) }.joined(separator: ", ") 78 | append += ")" 79 | return append 80 | } 81 | } 82 | 83 | extension ForeignKey: Hashable { 84 | 85 | #if swift(>=4.2) 86 | func hash(into hasher: inout Hasher) { 87 | let baseHash = "foreignKey".hashValue 88 | hasher.combine(baseHash) 89 | for key in keyNames { 90 | hasher.combine(baseHash ^ key.hashValue) 91 | } 92 | for ref in refNames { 93 | hasher.combine(baseHash ^ ref.hashValue) 94 | } 95 | } 96 | #else 97 | public var hashValue: Int { 98 | var hashvalue = "foreignKey".hashValue 99 | for key in keyNames { 100 | hashvalue = hashvalue ^ key.hashValue 101 | } 102 | for ref in refNames { 103 | hashvalue = hashvalue ^ ref.hashValue 104 | } 105 | return hashvalue 106 | } 107 | #endif 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Scripts/subqueries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/** 4 | #* Copyright IBM Corporation 2016, 2017 5 | #* 6 | #* Licensed under the Apache License, Version 2.0 (the "License"); 7 | #* you may not use this file except in compliance with the License. 8 | #* You may obtain a copy of the License at 9 | #* 10 | #* http://www.apache.org/licenses/LICENSE-2.0 11 | #* 12 | #* Unless required by applicable law or agreed to in writing, software 13 | #* distributed under the License is distributed on an "AS IS" BASIS, 14 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | #* See the License for the specific language governing permissions and 16 | #* limitations under the License. 17 | #**/ 18 | 19 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 20 | cd "$SCRIPT_DIR" 21 | CUR_DIR=$(pwd) 22 | 23 | temp=$(dirname "${CUR_DIR}") 24 | temp=$(dirname "${temp}") 25 | PKG_DIR=$(dirname "${CUR_DIR}") 26 | 27 | shopt -s nullglob 28 | 29 | if ! [ -d "${PKG_DIR}/Sources/SwiftKuery" ]; then 30 | echo "Failed to find ${PKG_DIR}/Sources/SwiftKuery" 31 | exit 1 32 | fi 33 | 34 | INPUT_SUBQUERIES_OPERATORS_FILE="${PKG_DIR}/Scripts/SimpleOperators.txt" 35 | INPUT_SUBQUERIES_TYPES_FILE="${PKG_DIR}/Scripts/SubqueriesTypes.txt" 36 | INPUT_IN_SUBQUERY_TYPES_FILE="${PKG_DIR}/Scripts/InSelectTypes.txt" 37 | 38 | OUTPUT_FILE="${PKG_DIR}/Sources/SwiftKuery/Subqueries_GlobalFunctionsAndExtensions.swift" 39 | 40 | echo "--- Generating ${OUTPUT_FILE}" 41 | 42 | cat <<'EOF' > ${OUTPUT_FILE} 43 | /** 44 | * Copyright IBM Corporation 2016, 2017 45 | * 46 | * Licensed under the Apache License, Version 2.0 (the "License"); 47 | * you may not use this file except in compliance with the License. 48 | * You may obtain a copy of the License at 49 | * 50 | * http://www.apache.org/licenses/LICENSE-2.0 51 | * 52 | * Unless required by applicable law or agreed to in writing, software 53 | * distributed under the License is distributed on an "AS IS" BASIS, 54 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | * See the License for the specific language governing permissions and 56 | * limitations under the License. 57 | **/ 58 | 59 | import Foundation 60 | 61 | EOF 62 | 63 | # Generate operators with subqueries, i.e. 'expression operator ANY/ALL(subquery)' 64 | while read -r LINE; do 65 | [ -z "$LINE" ] && continue 66 | [[ "$LINE" =~ ^#.*$ ]] && continue 67 | stringarray=($LINE) 68 | OPERATOR=${stringarray[0]} 69 | CASE=${stringarray[1]} 70 | while read -r LINE; do 71 | [ -z "$LINE" ] && continue 72 | [[ "$LINE" =~ ^#.*$ ]] && continue 73 | stringarray=($LINE) 74 | TYPE=${stringarray[0]} 75 | LHS_TYPE=${stringarray[1]} 76 | RHS_TYPE=${stringarray[2]} 77 | LHS_TYPE_LOWER="$(tr '[:upper:]' '[:lower:]' <<< ${LHS_TYPE:0:1})${LHS_TYPE:1}" 78 | if [[ $LHS_TYPE_LOWER == *"ColumnExpression" ]] 79 | then 80 | LHS_TYPE_LOWER="columnExpression" 81 | fi 82 | 83 | cat <> ${OUTPUT_FILE} 84 | /// Create a \`$TYPE\` clause using the operator $OPERATOR for $LHS_TYPE 85 | /// and subquery. 86 | /// 87 | /// - Parameter lhs: The left hand side of the clause. 88 | /// - Parameter rhs: The right hand side of the clause. 89 | /// - Returns: A \`$TYPE\` containing the clause. 90 | public func $OPERATOR(lhs: $LHS_TYPE, rhs: $RHS_TYPE) -> $TYPE { 91 | return $TYPE(lhs: .$LHS_TYPE_LOWER(lhs), rhs: rhs, condition: .$CASE) 92 | } 93 | 94 | EOF 95 | 96 | done < $INPUT_SUBQUERIES_TYPES_FILE 97 | done < $INPUT_SUBQUERIES_OPERATORS_FILE 98 | 99 | # Generate extensions for IN, NOT IN with subquery 100 | 101 | while read -r LINE; do 102 | [ -z "$LINE" ] && continue 103 | [[ "$LINE" =~ ^#.*$ ]] && continue 104 | stringarray=($LINE) 105 | CLAUSE=${stringarray[0]} 106 | TYPE=${stringarray[1]} 107 | TYPE_LOWER="$(tr '[:upper:]' '[:lower:]' <<< ${TYPE:0:1})${TYPE:1}" 108 | if [[ $TYPE_LOWER == *"ColumnExpression" ]] 109 | then 110 | TYPE_LOWER="columnExpression" 111 | fi 112 | 113 | echo "public extension $TYPE {" >> ${OUTPUT_FILE} 114 | 115 | for OPERATOR in \`in\` notIn; do 116 | 117 | cat <> ${OUTPUT_FILE} 118 | 119 | /// Create a \`$CLAUSE\` clause using the $OPERATOR operator for subquery. 120 | /// 121 | /// - Parameter query: The subquery. 122 | /// - Returns: A \`$CLAUSE\` containing the clause. 123 | public func $OPERATOR(_ query: Select) -> $CLAUSE { 124 | return $CLAUSE(lhs: .$TYPE_LOWER(self), rhs: .select(query), condition: .$OPERATOR) 125 | } 126 | EOF 127 | done 128 | echo "}" >> ${OUTPUT_FILE} 129 | done < $INPUT_IN_SUBQUERY_TYPES_FILE 130 | 131 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Delete.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Delete 18 | 19 | /// The SQL DELETE statement. 20 | public struct Delete: Query { 21 | /// The table to delete rows from. 22 | public let table: Table 23 | 24 | /// The SQL WHERE clause containing the filter for rows to delete. 25 | /// Could be represented with a `Filter` clause or a `String` containing raw SQL. 26 | public private (set) var whereClause: QueryFilterProtocol? 27 | 28 | /// A String with a clause to be appended to the end of the query. 29 | public private (set) var suffix: QuerySuffixProtocol? 30 | 31 | /// An array of `AuxiliaryTable` which will be used in a query with a WITH clause. 32 | public private (set) var with: [AuxiliaryTable]? 33 | 34 | private var syntaxError = "" 35 | 36 | /// Initialize an instance of Delete. 37 | /// 38 | /// - Parameter from: The table to delete rows from. 39 | /// - Parameter conditions: An optional where clause to apply. 40 | public init(from table: Table, where conditions: QueryFilterProtocol?=nil) { 41 | self.table = table 42 | self.whereClause = conditions 43 | } 44 | 45 | /// Build the query using `QueryBuilder`. 46 | /// 47 | /// - Parameter queryBuilder: The QueryBuilder to use. 48 | /// - Returns: A String representation of the query. 49 | /// - Throws: QueryError.syntaxError if query build fails. 50 | public func build(queryBuilder: QueryBuilder) throws -> String { 51 | if syntaxError != "" { 52 | throw QueryError.syntaxError(syntaxError) 53 | } 54 | 55 | var result = "" 56 | 57 | if let with = with { 58 | result += "WITH " 59 | + "\(try with.map { try $0.buildWith(queryBuilder: queryBuilder) }.joined(separator: ", "))" 60 | + " " 61 | } 62 | 63 | result += "DELETE FROM " 64 | 65 | result += try table.build(queryBuilder: queryBuilder) 66 | 67 | if let with = with, 68 | queryBuilder.withDeleteRequiresUsing { 69 | result += try " USING " + with.map { try $0.build(queryBuilder: queryBuilder) }.joined(separator: ", ") 70 | } 71 | 72 | if let whereClause = whereClause { 73 | result += try " WHERE " + whereClause.build(queryBuilder: queryBuilder) 74 | } 75 | if let suffix = suffix { 76 | result += try " " + suffix.build(queryBuilder: queryBuilder) 77 | } 78 | result = Utils.updateParameterNumbers(query: result, queryBuilder: queryBuilder) 79 | return result 80 | } 81 | 82 | /// Add an SQL WHERE clause to the delete statement. 83 | /// 84 | /// - Parameter conditions: The `Filter` clause or a `String` containing SQL WHERE clause to apply. 85 | /// - Returns: A new instance of Delete. 86 | public func `where`(_ conditions: QueryFilterProtocol) -> Delete { 87 | var new = self 88 | if whereClause != nil { 89 | new.syntaxError += "Multiple where clauses. " 90 | } 91 | else { 92 | new.whereClause = conditions 93 | } 94 | return new 95 | } 96 | 97 | /// Add a raw suffix to the delete statement. 98 | /// 99 | /// - Parameter raw: A String with a clause to be appended to the end of the query. 100 | /// - Returns: A new instance of Delete. 101 | public func suffix(_ raw: String) -> Delete { 102 | var new = self 103 | if suffix != nil { 104 | new.syntaxError += "Multiple suffixes. " 105 | } 106 | else { 107 | new.suffix = raw 108 | } 109 | return new 110 | } 111 | 112 | /// Set tables to be used for WITH clause. 113 | /// 114 | /// - Parameter tables: A list of the `AuxiliaryTable` to apply. 115 | /// - Returns: A new instance of Delete with tables for WITH clause. 116 | func with(_ tables: [AuxiliaryTable]) -> Delete { 117 | var new = self 118 | if new.with != nil { 119 | new.syntaxError += "Multiple with clauses. " 120 | } 121 | else { 122 | new.with = tables 123 | } 124 | return new 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Utils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | struct Utils { 20 | 21 | static func packType(_ item: Any) -> String { 22 | switch item { 23 | case let val as String: 24 | return "'\(val)'" 25 | case let value as Date: 26 | return "'\(String(describing: value))'" 27 | default: 28 | return String(describing: item) 29 | } 30 | } 31 | 32 | static func packType(_ item: Any, queryBuilder: QueryBuilder) throws -> String { 33 | switch item { 34 | case let val as String: 35 | return "'\(val)'" 36 | case let val as Bool: 37 | return val ? queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.booleanTrue.rawValue] 38 | : queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.booleanFalse.rawValue] 39 | case let val as Parameter: 40 | return try val.build(queryBuilder: queryBuilder) 41 | case let value as Date: 42 | if let dateFormatter = queryBuilder.dateFormatter { 43 | return dateFormatter.string(from: value) 44 | } 45 | return "'\(String(describing: value))'" 46 | default: 47 | let val = String(describing: item) 48 | return val == "nil" ? "NULL" : val 49 | } 50 | } 51 | 52 | static func packName(_ name: String, queryBuilder: QueryBuilder) -> String { 53 | var result = name 54 | let identifierQuoteCharacter = queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.identifierQuoteCharacter.rawValue] 55 | if !result.hasPrefix(identifierQuoteCharacter) { 56 | result = identifierQuoteCharacter + result + identifierQuoteCharacter 57 | } 58 | return result 59 | } 60 | 61 | static func updateParameterNumbers(query: String, queryBuilder: QueryBuilder) -> String { 62 | let marker = queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.numberedParameter.rawValue] 63 | if !queryBuilder.addNumbersToParameters { 64 | return query.replacingOccurrences(of: Parameter.numberedParameterMarker, with: marker) 65 | } 66 | var resultQuery = "" 67 | var inputQuery = query 68 | var range = inputQuery.range(of: Parameter.numberedParameterMarker) 69 | var index = 1 70 | while let _range = range { 71 | resultQuery += inputQuery[..<_range.lowerBound] + marker 72 | resultQuery += "\(index)" 73 | inputQuery = String(inputQuery[_range.upperBound...]) 74 | 75 | index += 1 76 | 77 | range = inputQuery.range(of: Parameter.numberedParameterMarker) 78 | } 79 | resultQuery += inputQuery 80 | return resultQuery 81 | } 82 | 83 | static func convertNamedParametersToNumbered(query: String, queryBuilder: QueryBuilder) -> (String, [String:[Int]], Int) { 84 | var resultQuery = "" 85 | var nameToNumber = [String:[Int]]() 86 | var index = 1 87 | let marker = queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.numberedParameter.rawValue] 88 | var inputQuery = query 89 | var startRange = inputQuery.range(of: Parameter.namedParameterStart) 90 | var endRange = inputQuery.range(of: Parameter.namedParameterEnd) 91 | while let _startRange = startRange, let _endRange = endRange { 92 | resultQuery += inputQuery[..<_startRange.lowerBound] + marker 93 | 94 | if queryBuilder.addNumbersToParameters { 95 | resultQuery += "\(index)" 96 | } 97 | 98 | let name = String(inputQuery[_startRange.upperBound..<_endRange.lowerBound]) 99 | 100 | if let _ = nameToNumber[name] { 101 | nameToNumber[name]!.append(index) 102 | } 103 | else { 104 | nameToNumber[name] = [index] 105 | } 106 | 107 | index += 1 108 | 109 | inputQuery = String(inputQuery[_endRange.upperBound...]) 110 | 111 | startRange = inputQuery.range(of: Parameter.namedParameterStart) 112 | endRange = inputQuery.range(of: Parameter.namedParameterEnd) 113 | } 114 | resultQuery += inputQuery 115 | return (resultQuery, nameToNumber, index - 1) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/VerifyLinuxTestCount.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2017 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | #if os(OSX) && !swift(>=3.2) 18 | import XCTest 19 | 20 | class VerifyLinuxTestCount: XCTestCase { 21 | func testVerifyLinuxTestCount() { 22 | var linuxCount: Int = 0 23 | var darwinCount: Int = 0 24 | 25 | linuxCount = TestAggregateFunctions.allTests.count 26 | darwinCount = Int(TestAggregateFunctions.defaultTestSuite().testCaseCount) 27 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestAggregateFunctions.allTests") 28 | 29 | linuxCount = TestAlias.allTests.count 30 | darwinCount = Int(TestAlias.defaultTestSuite().testCaseCount) 31 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestAlias.allTests") 32 | 33 | linuxCount = TestFilterAndHaving.allTests.count 34 | darwinCount = Int(TestFilterAndHaving.defaultTestSuite().testCaseCount) 35 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestFilterAndHaving.allTests") 36 | 37 | linuxCount = TestInsert.allTests.count 38 | darwinCount = Int(TestInsert.defaultTestSuite().testCaseCount) 39 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestInsert.allTests") 40 | 41 | linuxCount = TestJoin.allTests.count 42 | darwinCount = Int(TestJoin.defaultTestSuite().testCaseCount) 43 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestJoin.allTests") 44 | 45 | linuxCount = TestParameters.allTests.count 46 | darwinCount = Int(TestParameters.defaultTestSuite().testCaseCount) 47 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestParameters.allTests") 48 | 49 | linuxCount = TestQueryResult.allTests.count 50 | darwinCount = Int(TestQueryResult.defaultTestSuite().testCaseCount) 51 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestQueryResult.allTests") 52 | 53 | linuxCount = TestRaw.allTests.count 54 | darwinCount = Int(TestRaw.defaultTestSuite().testCaseCount) 55 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestRaw.allTests") 56 | 57 | linuxCount = TestSchema.allTests.count 58 | darwinCount = Int(TestSchema.defaultTestSuite().testCaseCount) 59 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestSchema.allTests") 60 | 61 | linuxCount = TestSelect.allTests.count 62 | darwinCount = Int(TestSelect.defaultTestSuite().testCaseCount) 63 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestSelect.allTests") 64 | 65 | linuxCount = TestSpecialOperators.allTests.count 66 | darwinCount = Int(TestSpecialOperators.defaultTestSuite().testCaseCount) 67 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestSpecialOperators.allTests") 68 | 69 | linuxCount = TestSubqueries.allTests.count 70 | darwinCount = Int(TestSubqueries.defaultTestSuite().testCaseCount) 71 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestSubqueries.allTests") 72 | 73 | linuxCount = TestSubquery.allTests.count 74 | darwinCount = Int(TestSubquery.defaultTestSuite().testCaseCount) 75 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestSubquery.allTests") 76 | 77 | linuxCount = TestSyntaxError.allTests.count 78 | darwinCount = Int(TestSyntaxError.defaultTestSuite().testCaseCount) 79 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestSyntaxError.allTests") 80 | 81 | linuxCount = TestUpdate.allTests.count 82 | darwinCount = Int(TestUpdate.defaultTestSuite().testCaseCount) 83 | XCTAssertEqual(linuxCount, darwinCount, "\(darwinCount - linuxCount) tests are missing from TestUpdate.allTests") 84 | } 85 | } 86 | #endif 87 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Update.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Update 18 | 19 | /// The SQL UPDATE statement. 20 | public struct Update: Query { 21 | /// The table to update. 22 | public let table: Table 23 | 24 | /// The SQL WHERE clause containing the filter for the rows to update. 25 | /// Could be represented with a `Filter` clause or a `String` containing raw SQL. 26 | public private (set) var whereClause: QueryFilterProtocol? 27 | 28 | /// A String with a clause to be appended to the end of the query. 29 | public private (set) var suffix: QuerySuffixProtocol? 30 | 31 | private let valueTuples: [(column: Column, value: Any)] 32 | 33 | /// An array of `AuxiliaryTable` which will be used in a query with a WITH clause. 34 | public private (set) var with: [AuxiliaryTable]? 35 | 36 | private var syntaxError = "" 37 | 38 | /// Initialize an instance of Update. 39 | /// 40 | /// - Parameter table: The table to update. 41 | /// - Parameter set: An array of (column, value) tuples to set. 42 | /// - Parameter conditions: An optional where clause to apply. 43 | public init(_ table: Table, set: [(Column, Any)], where conditions: QueryFilterProtocol?=nil) { 44 | self.table = table 45 | self.valueTuples = set 46 | self.whereClause = conditions 47 | if set.count == 0 { 48 | syntaxError = "An empty set of (column, value) tuples. " 49 | } 50 | } 51 | 52 | /// Build the query using `QueryBuilder`. 53 | /// 54 | /// - Parameter queryBuilder: The QueryBuilder to use. 55 | /// - Returns: A String representation of the query. 56 | /// - Throws: QueryError.syntaxError if query build fails. 57 | public func build(queryBuilder: QueryBuilder) throws -> String { 58 | if syntaxError != "" { 59 | throw QueryError.syntaxError(syntaxError) 60 | } 61 | 62 | var result = "" 63 | 64 | if let with = with { 65 | result += "WITH " 66 | + "\(try with.map { try $0.buildWith(queryBuilder: queryBuilder) }.joined(separator: ", "))" 67 | + " " 68 | } 69 | 70 | result += "UPDATE " 71 | 72 | result += try table.build(queryBuilder: queryBuilder) 73 | 74 | result += try " SET " + valueTuples.map { 75 | "\(Utils.packName($0.column.name, queryBuilder: queryBuilder)) = \(try Utils.packType($0.value, queryBuilder: queryBuilder))" 76 | }.joined(separator: ", ") 77 | 78 | if let with = with, 79 | queryBuilder.withDeleteRequiresUsing { 80 | result += try " FROM " + with.map { try $0.build(queryBuilder: queryBuilder) }.joined(separator: ", ") 81 | } 82 | 83 | if let whereClause = whereClause { 84 | result += try " WHERE " + whereClause.build(queryBuilder: queryBuilder) 85 | } 86 | 87 | if let suffix = suffix { 88 | result += try " " + suffix.build(queryBuilder: queryBuilder) 89 | } 90 | 91 | result = Utils.updateParameterNumbers(query: result, queryBuilder: queryBuilder) 92 | 93 | return result 94 | } 95 | 96 | /// Add an SQL WHERE clause to the update statement. 97 | /// 98 | /// - Parameter conditions: The `Filter` clause or a `String` containing the SQL WHERE to apply. 99 | /// - Returns: A new instance of Update. 100 | public func `where`(_ conditions: QueryFilterProtocol) -> Update { 101 | var new = self 102 | if whereClause != nil { 103 | new.syntaxError += "Multiple where clauses. " 104 | } 105 | else { 106 | new.whereClause = conditions 107 | } 108 | return new 109 | } 110 | 111 | /// Add a raw suffix to the update statement. 112 | /// 113 | /// - Parameter raw: A String with a clause to be appended to the end of the query. 114 | /// - Returns: A new instance of Update. 115 | public func suffix(_ raw: String) -> Update { 116 | var new = self 117 | if suffix != nil { 118 | new.syntaxError += "Multiple suffixes. " 119 | } 120 | else { 121 | new.suffix = raw 122 | } 123 | return new 124 | } 125 | 126 | /// Set tables to be used for WITH clause. 127 | /// 128 | /// - Parameter tables: A list of the `AuxiliaryTable` to apply. 129 | /// - Returns: A new instance of Update with tables for WITH clause. 130 | func with(_ tables: [AuxiliaryTable]) -> Update { 131 | var new = self 132 | if new.with != nil { 133 | new.syntaxError += "Multiple with clauses. " 134 | } 135 | else { 136 | new.with = tables 137 | } 138 | return new 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Scripts/testSimpleOperators.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/** 4 | #* Copyright IBM Corporation 2016, 2017 5 | #* 6 | #* Licensed under the Apache License, Version 2.0 (the "License"); 7 | #* you may not use this file except in compliance with the License. 8 | #* You may obtain a copy of the License at 9 | #* 10 | #* http://www.apache.org/licenses/LICENSE-2.0 11 | #* 12 | #* Unless required by applicable law or agreed to in writing, software 13 | #* distributed under the License is distributed on an "AS IS" BASIS, 14 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | #* See the License for the specific language governing permissions and 16 | #* limitations under the License. 17 | #**/ 18 | 19 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 20 | cd "$SCRIPT_DIR" 21 | CUR_DIR=$(pwd) 22 | 23 | temp=$(dirname "${CUR_DIR}") 24 | temp=$(dirname "${temp}") 25 | PKG_DIR=$(dirname "${CUR_DIR}") 26 | 27 | shopt -s nullglob 28 | 29 | if ! [ -d "${PKG_DIR}/Tests/SwiftKueryTests" ]; then 30 | echo "Failed to find ${PKG_DIR}/Tests/SwiftKueryTests" 31 | exit 1 32 | fi 33 | 34 | INPUT_OPERATORS_FILE="${PKG_DIR}/Scripts/SimpleOperators.txt" 35 | INPUT_TYPES_FILE="${PKG_DIR}/Scripts/FilterAndHavingTypes.txt" 36 | 37 | INPUT_BOOL_OPERATORS_FILE="${PKG_DIR}/Scripts/FilterAndHavingBoolOperators.txt" 38 | INPUT_BOOL_TYPES_FILE="${PKG_DIR}/Scripts/FilterAndHavingBoolTypes.txt" 39 | 40 | OUTPUT_FILE="${PKG_DIR}/Tests/SwiftKueryTests/TestFilterAndHaving.swift" 41 | 42 | echo "--- Generating ${OUTPUT_FILE}" 43 | 44 | cat <<'EOF' > ${OUTPUT_FILE} 45 | /** 46 | * Copyright IBM Corporation 2016, 2017 47 | * 48 | * Licensed under the Apache License, Version 2.0 (the "License"); 49 | * you may not use this file except in compliance with the License. 50 | * You may obtain a copy of the License at 51 | * 52 | * http://www.apache.org/licenses/LICENSE-2.0 53 | * 54 | * Unless required by applicable law or agreed to in writing, software 55 | * distributed under the License is distributed on an "AS IS" BASIS, 56 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 57 | * See the License for the specific language governing permissions and 58 | * limitations under the License. 59 | **/ 60 | 61 | import Foundation 62 | 63 | import XCTest 64 | 65 | @testable import SwiftKuery 66 | 67 | // This test was generated by Scripts/testSimpleOperators.sh. 68 | class TestFilterAndHaving: XCTestCase { 69 | 70 | static var allTests: [(String, (TestFilterAndHaving) -> () throws -> Void)] { 71 | return [ 72 | ("testSimpleOperators", testSimpleOperators), 73 | ] 74 | } 75 | 76 | class MyTable: Table { 77 | let a = Column("a") 78 | let b = Column("b") 79 | 80 | let tableName = "table" 81 | } 82 | 83 | class MyTable2: Table { 84 | let c = Column("c") 85 | let tableName = "table2" 86 | } 87 | 88 | func testSimpleOperators() { 89 | let t = MyTable() 90 | let t2 = MyTable2() 91 | let connection = createConnection() 92 | var s = Select(from: t) 93 | var kuery = "" 94 | var queryWhere = "" 95 | var queryHaving = "" 96 | 97 | EOF 98 | 99 | 100 | # Generate test for operators for simple conditions that return Filter and Having 101 | 102 | for INPUT_TYPES in $INPUT_TYPES_FILE $INPUT_BOOL_TYPES_FILE; do 103 | if [[ $INPUT_TYPES == *"Bool"* ]] 104 | then 105 | INPUT_OPERATORS=$INPUT_BOOL_OPERATORS_FILE 106 | else 107 | INPUT_OPERATORS=$INPUT_OPERATORS_FILE 108 | fi 109 | 110 | 111 | while read -r LINE; do 112 | [ -z "$LINE" ] && continue 113 | [[ "$LINE" =~ ^#.*$ ]] && continue 114 | stringarray=($LINE) 115 | OPERATOR=${stringarray[0]} 116 | SQL_OPERATOR=${stringarray[2]} 117 | while read -r LINE; do 118 | [ -z "$LINE" ] && continue 119 | [[ "$LINE" =~ ^#.*$ ]] && continue 120 | stringarray=($LINE) 121 | CLAUSE=${stringarray[3]} 122 | OPERAND1=${stringarray[4]} 123 | OPERAND2=${stringarray[5]} 124 | SQL_OPERAND1=${stringarray[6]} 125 | SQL_OPERAND2=${stringarray[7]} 126 | 127 | CLAUSE_UPPER="$(tr '[:lower:]' '[:upper:]' <<< $CLAUSE)" 128 | # Remove backslashes. 129 | SQL_OPERAND1=${SQL_OPERAND1/\\} 130 | SQL_OPERAND2=${SQL_OPERAND2/\\} 131 | # Replace underscores with whitespaces. 132 | OPERAND1=${OPERAND1//_/" "} 133 | OPERAND2=${OPERAND2//_/" "} 134 | SQL_OPERAND1=${SQL_OPERAND1//_/" "} 135 | SQL_OPERAND2=${SQL_OPERAND2//_/" "} 136 | 137 | cat <> ${OUTPUT_FILE} 138 | 139 | s = Select(t.a, from: t) 140 | .group(by: t.a) 141 | .$CLAUSE($OPERAND1 $OPERATOR $OPERAND2) 142 | kuery = connection.descriptionOf(query: s) 143 | queryWhere = "SELECT table.a FROM table $CLAUSE_UPPER $SQL_OPERAND1 $SQL_OPERATOR $SQL_OPERAND2 GROUP BY table.a" 144 | queryHaving = "SELECT table.a FROM table GROUP BY table.a $CLAUSE_UPPER $SQL_OPERAND1 $SQL_OPERATOR $SQL_OPERAND2" 145 | XCTAssert(kuery == queryWhere || kuery == queryHaving, 146 | "\nError in query construction: \n\(kuery) \ninstead of \n\(queryWhere) \nor instead of \n\(queryHaving)") 147 | EOF 148 | 149 | done < $INPUT_TYPES 150 | done < $INPUT_OPERATORS 151 | 152 | done 153 | 154 | echo " }" >> ${OUTPUT_FILE} 155 | echo "}" >> ${OUTPUT_FILE} 156 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/SwiftKuery.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Scripts/testAggregate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/** 4 | #* Copyright IBM Corporation 2016 5 | #* 6 | #* Licensed under the Apache License, Version 2.0 (the "License"); 7 | #* you may not use this file except in compliance with the License. 8 | #* You may obtain a copy of the License at 9 | #* 10 | #* http://www.apache.org/licenses/LICENSE-2.0 11 | #* 12 | #* Unless required by applicable law or agreed to in writing, software 13 | #* distributed under the License is distributed on an "AS IS" BASIS, 14 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | #* See the License for the specific language governing permissions and 16 | #* limitations under the License. 17 | #**/ 18 | 19 | # Test AggregateColumnExpressions and ScalarColumnExpressions with Aggregate as their argument. 20 | 21 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 22 | cd "$SCRIPT_DIR" 23 | CUR_DIR=$(pwd) 24 | 25 | temp=$(dirname "${CUR_DIR}") 26 | temp=$(dirname "${temp}") 27 | PKG_DIR=$(dirname "${CUR_DIR}") 28 | 29 | shopt -s nullglob 30 | 31 | if ! [ -d "${PKG_DIR}/Tests/SwiftKueryTests" ]; then 32 | echo "Failed to find ${PKG_DIR}/Tests/SwiftKueryTests" 33 | exit 1 34 | fi 35 | 36 | INPUT_FILE="${PKG_DIR}/Scripts/TestAggregate.txt" 37 | INPUT_SCALAR_FILE="${PKG_DIR}/Scripts/TestAggregateScalar.txt" 38 | OUTPUT_FILE="${PKG_DIR}/Tests/SwiftKueryTests/TestAggregateFunctions.swift" 39 | 40 | echo "--- Generating ${OUTPUT_FILE}" 41 | 42 | cat <<'EOF' > ${OUTPUT_FILE} 43 | /** 44 | * Copyright IBM Corporation 2016 45 | * 46 | * Licensed under the Apache License, Version 2.0 (the "License"); 47 | * you may not use this file except in compliance with the License. 48 | * You may obtain a copy of the License at 49 | * 50 | * http://www.apache.org/licenses/LICENSE-2.0 51 | * 52 | * Unless required by applicable law or agreed to in writing, software 53 | * distributed under the License is distributed on an "AS IS" BASIS, 54 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | * See the License for the specific language governing permissions and 56 | * limitations under the License. 57 | **/ 58 | 59 | import XCTest 60 | 61 | @testable import SwiftKuery 62 | 63 | // This test was generated by Scripts/testAggregate.sh. 64 | class TestAggregateFunctions: XCTestCase { 65 | 66 | static var allTests: [(String, (TestAggregateFunctions) -> () throws -> Void)] { 67 | return [ 68 | ("testFunctions", testFunctions), 69 | ] 70 | } 71 | 72 | class MyTable: Table { 73 | let a = Column("a") 74 | let b = Column("b") 75 | 76 | let tableName = "table" 77 | } 78 | 79 | func testFunctions() { 80 | let t = MyTable() 81 | let connection = createConnection() 82 | var s = Select(from: t) 83 | var kuery = "" 84 | var query = "" 85 | 86 | EOF 87 | 88 | while read -r LINE; do 89 | [ -z "$LINE" ] && continue 90 | [[ "$LINE" =~ ^#.*$ ]] && continue 91 | stringarray=($LINE) 92 | FUNCTION=${stringarray[0]} 93 | SQL=${stringarray[1]} 94 | 95 | cat <> ${OUTPUT_FILE} 96 | 97 | s = Select(t.a, from: t) 98 | .group(by: t.a) 99 | .having($FUNCTION(t.b) > 3) 100 | kuery = connection.descriptionOf(query: s) 101 | query = "SELECT table.a FROM table GROUP BY table.a HAVING $SQL(table.b) > 3" 102 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 103 | EOF 104 | 105 | done < $INPUT_FILE 106 | 107 | cat <> ${OUTPUT_FILE} 108 | 109 | s = Select(t.a, from: t) 110 | .group(by: t.a) 111 | .having(countDistinct(t.b) != 0) 112 | kuery = connection.descriptionOf(query: s) 113 | query = "SELECT table.a FROM table GROUP BY table.a HAVING COUNT(DISTINCT(table.b)) <> 0" 114 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 115 | EOF 116 | 117 | while read -r LINE; do 118 | [ -z "$LINE" ] && continue 119 | [[ "$LINE" =~ ^#.*$ ]] && continue 120 | stringarray=($LINE) 121 | FUNCTION=${stringarray[0]} 122 | SQL=${stringarray[1]} 123 | 124 | cat <> ${OUTPUT_FILE} 125 | 126 | s = Select(t.a, from: t) 127 | .group(by: t.a) 128 | .having($FUNCTION(sum(t.b)) <= -1) 129 | kuery = connection.descriptionOf(query: s) 130 | query = "SELECT table.a FROM table GROUP BY table.a HAVING $SQL(SUM(table.b)) <= -1" 131 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 132 | EOF 133 | done < $INPUT_SCALAR_FILE 134 | 135 | cat <> ${OUTPUT_FILE} 136 | s = Select(t.a, from: t) 137 | .group(by: t.a) 138 | .having(round(sum(t.b), to: 2) >= 9.08) 139 | kuery = connection.descriptionOf(query: s) 140 | query = "SELECT table.a FROM table GROUP BY table.a HAVING ROUND(SUM(table.b), 2) >= 9.08" 141 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 142 | 143 | s = Select(t.a, from: t) 144 | .group(by: t.a) 145 | .having(mid(sum(t.b), start: 3, length: 6) <= -1) 146 | kuery = connection.descriptionOf(query: s) 147 | query = "SELECT table.a FROM table GROUP BY table.a HAVING MID(SUM(table.b), 3, 6) <= -1" 148 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 149 | EOF 150 | echo " }" >> ${OUTPUT_FILE} 151 | echo "}" >> ${OUTPUT_FILE} 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/AuxiliaryTable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: AuxiliaryTable 18 | 19 | /** 20 | Subclasses of the `AuxiliaryTable` class are describing a `Table` that is used in WITH clauses. 21 | ### Usage Example: ### 22 | In this example, an `AuxiliaryTable` class is defined containing two columns. 23 | A `ToDoTable` (as defined in `Table`), a `PersonTable` (as defined in `Column`) and a connection instance are initialized. 24 | An instance of the `AuxiliaryTable` is then initialised from `Column` instances in "todotable". 25 | This `AuxiliaryTable` is then used to create an SQL WITH query. 26 | A description of the created query is then printed. 27 | ```swift 28 | class AuxTable: AuxiliaryTable { 29 | let tableName = "aux_table" 30 | let name = Column("name") 31 | let finished = Column("finished") 32 | } 33 | 34 | let todotable = ToDoTable() // ToDoTable() is a previously defined `Table` class 35 | let persontable = PersonTable() // PersonTable() is a previously defined `Table` class 36 | let connection = PostgreSQLConnection(host: "localhost", port: 5432, options: [.databaseName("ToDoDatabase")]) 37 | 38 | let withTable = AuxTable(as: Select(todotable.toDo_completed.as("finished"), todotable.toDo_title.as("name"), from: todotable)) 39 | let withQuery = with(withTable, Select(withTable.finished, persontable.monthlyPay, from: persontable).join(withTable).on(persontable.name == withTable.name)) 40 | let stringQuery = try connection.descriptionOf(query: withQuery) 41 | print(stringQuery) 42 | // Prints WITH aux_table AS (SELECT toDoTable.toDo_completed AS finished, toDoTable.toDo_title AS name FROM toDoTable) SELECT aux_table.finished, personTable.monthlyPay FROM personTable JOIN aux_table ON personTable.firstName = aux_table.name 43 | ``` 44 | */ 45 | open class AuxiliaryTable: Table { 46 | 47 | /// A query used to build table in WITH clause 48 | private var query: Query? 49 | 50 | // MARK: Initializer 51 | /** 52 | Initialize an instance of `AuxiliaryTable`. 53 | ### Usage Example: ### 54 | In this example, an `AuxiliaryTable` class is defined containing two columns. 55 | A `ToDoTable` (as defined in `Table`) instance, and a connection instance are initialized. 56 | An instance of this `AuxiliaryTable` is then initialised from the `Column` instances in "todotable". 57 | ```swift 58 | class AuxTable: AuxiliaryTable { 59 | let tableName = "aux_table" 60 | let name = Column("name") 61 | let finished = Column("finished") 62 | } 63 | 64 | let todotable = ToDoTable() // ToDoTable() is a previously defined `Table` class 65 | let withTable = AuxTable(as: Select(todotable.toDo_completed.as("finished"), todotable.toDo_title.as("name"), from: todotable)) 66 | ``` 67 | 68 | - Parameter query: A query that will be used in a WITH clause. 69 | */ 70 | public convenience init(as query: Query) { 71 | self.init() 72 | self.query = query 73 | } 74 | 75 | // MARK: Build Query 76 | /** 77 | Build a String representation of the WITH clause used to create the `AuxiliaryTable` instance, using `QueryBuilder` to account for the various databases. 78 | ### Usage Example: ### 79 | In this example, an `AuxiliaryTable` class is defined containing two columns. 80 | A `ToDoTable` (as defined in `Table`) instance and queryBuilder instance are initialized. 81 | An instance of this `AuxiliaryTable` is then initialised from the `Column` instances in "todotable". 82 | The `buildWith` function is then called on this `AuxiliaryTable` instance, with the resulting String being printed out. 83 | ```swift 84 | class AuxTable: AuxiliaryTable { 85 | let tableName = "aux_table" 86 | let name = Column("name") 87 | let finished = Column("finished") 88 | } 89 | 90 | let todotable = ToDoTable() // ToDoTable() is a previously defined `Table` class 91 | let queryBuilder = QueryBuilder() 92 | 93 | let withTable = AuxTable(as: Select(todotable.toDo_completed.as("finished"), todotable.toDo_title.as("name"), from: todotable)) 94 | let withString = try withTable.buildWith(queryBuilder: queryBuilder) 95 | print(withString) 96 | // Prints aux_table AS (SELECT toDoTable.toDo_completed AS finished, toDoTable.toDo_title AS name FROM toDoTable) 97 | ``` 98 | 99 | - Parameter queryBuilder: The QueryBuilder to use. 100 | - Returns: A String representation of the query. 101 | - Throws: QueryError.syntaxError if query build fails. 102 | */ 103 | public func buildWith(queryBuilder: QueryBuilder) throws -> String { 104 | guard let query = query else { 105 | throw QueryError.syntaxError("With query was not specified. ") 106 | } 107 | return Utils.packName(nameInQuery, queryBuilder: queryBuilder) + " AS " + "(" + (try query.build(queryBuilder: queryBuilder)) + ")" 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/Migration.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: Migration 18 | 19 | /** 20 | A class to help with migration between two versions of a table. 21 | 22 | ### Usage Example: ### 23 | The suggested usage is to keep versions of the table classes somewhere in the application code: 24 | ```swift 25 | public class MyTable_v0: Table { 26 | let a = Column("a", ...) 27 | let b = Column("b", ...) 28 | let tableName = "MyTable" 29 | } 30 | public class MyTable_v1: Table { 31 | let b = Column("b", ...) 32 | let c = Column("c", ...) 33 | let tableName = "MyTable" 34 | } 35 | ``` 36 | And use a typealias to refer to the current version of the table class in the application: 37 | ```swift 38 | typealias MyTable = MyTable_v0 39 | let t = MyTable() 40 | let q = Select(from: t) 41 | ... 42 | ``` 43 | The migration code from v0 to v1 should be something like this: 44 | ```swift 45 | let t0 = MyTable_v0() 46 | let t1 = MyTable_v1() 47 | let migration0 = Migration(from: t0, to: t1, using: connection) 48 | migration0.alterTableAdd(column: t1.c) { result in ... } 49 | ``` 50 | And raw alternations, if needed: 51 | ```swift 52 | let dropColumnQuery = "ALTER TABLE " + t1.tableName + " DROP COLUMN " + t0.a.name 53 | connection.execute(dropColumnQuery) { result in ... } 54 | ``` 55 | */ 56 | public class Migration { 57 | 58 | private let connection: Connection 59 | private let table1: Table 60 | private let table2: Table 61 | private var renamed = false 62 | 63 | // MARK: Initializer 64 | /** 65 | Initialize an instance of `Migration` with the tables you are migrating between 66 | and the connection to the database, where the tables are located. 67 | ### Usage Example: ### 68 | 69 | ```swift 70 | let connection = getConnection() 71 | let oldTable = MyTable_v0() 72 | let newTable = MyTable_v1() 73 | let migration = Migration(from: oldTable, to: newTable, using: connection) 74 | ``` 75 | 76 | - Parameter from: The version of the table to migrate from. 77 | - Parameter to: The version of the table to migrate to. 78 | - Parameter using conenction: The connection to the database to use. 79 | */ 80 | public init(from: Table, to: Table, using connection: Connection) { 81 | self.connection = connection 82 | table1 = from 83 | table2 = to 84 | } 85 | // MARK: Table Alteration 86 | 87 | /** 88 | Builds an executes an SQL query to alter the name of the old table to the name in the new version of the table. 89 | ### Usage Example: ### 90 | In this example, a `Migration` instance is initialized. 91 | The alterTableName function is called to initiate the SQL query, which changes the name. 92 | The "responseHandler" is a closure which handles the response from the database. 93 | ```swift 94 | let migration = Migration(from: oldTable, to: newTable, using: connection) 95 | migration.alterTableName(responseHandler) 96 | ``` 97 | 98 | - Parameter onCompletion: The function to be called when the execution of the query has completed. 99 | */ 100 | public func alterTableName(onCompletion: @escaping ((QueryResult) -> ())) { 101 | let query = "ALTER TABLE " + Utils.packName(table1._name, queryBuilder: connection.queryBuilder) + " RENAME TO " + Utils.packName(table2._name, queryBuilder: connection.queryBuilder) 102 | renamed = true 103 | connection.execute(query, onCompletion: onCompletion) 104 | } 105 | 106 | /** 107 | Create and execute an SQL query to add a `Column` to the new table in the `Migration` instance. 108 | ### Usage Example: ### 109 | In this example, `Migration` and `Column` instances are initialized. 110 | The `alterTableAdd` function is called to add a new column to `newTable` within the `Migration` instance and a closure `responseHandler` is passed in to handle the response from the database. 111 | ```swift 112 | let migration = Migration(from: oldTable, to: newTable, using: connection) 113 | let toDo_title = Column("toDo_title", String.self, notNull: true) 114 | migration.alterTableAdd(column: toDo_title, onCompletion: responseHandler) 115 | ``` 116 | 117 | - Parameter column: The column to add. This should be a column in the new version of the table. 118 | - Parameter onCompletion: The function to be called when the execution of the query has completed. 119 | */ 120 | public func alterTableAdd(column: Column, onCompletion: @escaping ((QueryResult) -> ())) { 121 | let tableName = renamed ? table2._name : table1._name 122 | do { 123 | let query = try "ALTER TABLE " + Utils.packName(tableName, queryBuilder: connection.queryBuilder) + " ADD COLUMN " + column.create(queryBuilder: connection.queryBuilder) 124 | connection.execute(query, onCompletion: onCompletion) 125 | } 126 | catch { 127 | onCompletion(.error(QueryError.syntaxError("Failed to alter table: \(error)"))) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/TestJoin.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | 19 | @testable import SwiftKuery 20 | 21 | class TestJoin: XCTestCase { 22 | 23 | static var allTests: [(String, (TestJoin) -> () throws -> Void)] { 24 | return [ 25 | ("testJoin", testJoin), 26 | ] 27 | } 28 | 29 | class MyTable1: Table { 30 | let a = Column("a") 31 | let b = Column("b") 32 | 33 | let tableName = "table1Join" 34 | } 35 | 36 | class MyTable2: Table { 37 | let c = Column("c") 38 | let b = Column("b") 39 | 40 | let tableName = "table2Join" 41 | } 42 | 43 | class MyTable3: Table { 44 | let d = Column("d") 45 | let b = Column("b") 46 | 47 | let tableName = "table3Join" 48 | } 49 | 50 | func testJoin() { 51 | let myTable1 = MyTable1() 52 | let myTable2 = MyTable2() 53 | let myTable3 = MyTable3() 54 | let connection = createConnection() 55 | 56 | var s = Select(from: myTable1) 57 | .join(myTable2) 58 | .on(myTable1.b == myTable2.b) 59 | var kuery = connection.descriptionOf(query: s) 60 | var query = "SELECT * FROM \"table1Join\" JOIN \"table2Join\" ON \"table1Join\".\"b\" = \"table2Join\".\"b\"" 61 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 62 | 63 | s = Select(from: myTable1) 64 | .naturalJoin(myTable2) 65 | .on(myTable1.b == myTable2.b) 66 | .rawJoin("NATURAL LEFT JOIN", myTable3) 67 | .on(myTable1.b == myTable3.b) 68 | kuery = connection.descriptionOf(query: s) 69 | query = "SELECT * FROM \"table1Join\" NATURAL JOIN \"table2Join\" ON \"table1Join\".\"b\" = \"table2Join\".\"b\" NATURAL LEFT JOIN \"table3Join\" ON \"table1Join\".\"b\" = \"table3Join\".\"b\"" 70 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 71 | 72 | let t1 = myTable1.as("t1") 73 | let t2 = myTable2.as("t2") 74 | let t3 = myTable3.as("t3") 75 | s = Select(from: t1) 76 | .crossJoin(t2) 77 | .on(t1.b == t2.b) 78 | kuery = connection.descriptionOf(query: s) 79 | query = "SELECT * FROM \"table1Join\" AS \"t1\" CROSS JOIN \"table2Join\" AS \"t2\" ON \"t1\".\"b\" = \"t2\".\"b\"" 80 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 81 | 82 | s = Select(from: t1) 83 | .join(t2) 84 | .on(t1.b == t2.b) 85 | .join(t3) 86 | .on(t1.b == t3.b) 87 | kuery = connection.descriptionOf(query: s) 88 | query = "SELECT * FROM \"table1Join\" AS \"t1\" JOIN \"table2Join\" AS \"t2\" ON \"t1\".\"b\" = \"t2\".\"b\" JOIN \"table3Join\" AS \"t3\" ON \"t1\".\"b\" = \"t3\".\"b\"" 89 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 90 | 91 | s = Select(from: t1) 92 | .join(t2) 93 | .on(t1.b == t2.b) 94 | .rawJoin("NATURAL FULL JOIN", t3) 95 | .on(t1.b == t3.b) 96 | kuery = connection.descriptionOf(query: s) 97 | query = "SELECT * FROM \"table1Join\" AS \"t1\" JOIN \"table2Join\" AS \"t2\" ON \"t1\".\"b\" = \"t2\".\"b\" NATURAL FULL JOIN \"table3Join\" AS \"t3\" ON \"t1\".\"b\" = \"t3\".\"b\"" 98 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 99 | 100 | s = Select(from: myTable1) 101 | .leftJoin(myTable2) 102 | .on(myTable1.a == myTable2.c) 103 | kuery = connection.descriptionOf(query: s) 104 | query = "SELECT * FROM \"table1Join\" LEFT JOIN \"table2Join\" ON \"table1Join\".\"a\" = \"table2Join\".\"c\"" 105 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 106 | 107 | s = Select(from: t1) 108 | .rawJoin("FULL JOIN", t2) 109 | .using(t1.b) 110 | kuery = connection.descriptionOf(query: s) 111 | query = "SELECT * FROM \"table1Join\" AS \"t1\" FULL JOIN \"table2Join\" AS \"t2\" USING (\"b\")" 112 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 113 | 114 | s = Select(from: t1) 115 | .rawJoin("FULL JOIN", t2) 116 | .using([t1.b]) 117 | kuery = connection.descriptionOf(query: s) 118 | query = "SELECT * FROM \"table1Join\" AS \"t1\" FULL JOIN \"table2Join\" AS \"t2\" USING (\"b\")" 119 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 120 | 121 | s = Select(myTable1.a, from: myTable1) 122 | .union(Select(myTable2.c, from: myTable2)) 123 | .unionAll(Select(myTable3.d, from: myTable3)) 124 | kuery = connection.descriptionOf(query: s) 125 | query = "SELECT \"table1Join\".\"a\" FROM \"table1Join\" UNION SELECT \"table2Join\".\"c\" FROM \"table2Join\" UNION ALL SELECT \"table3Join\".\"d\" FROM \"table3Join\"" 126 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/TestSubquery.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import XCTest 18 | 19 | @testable import SwiftKuery 20 | class TestSubquery: XCTestCase { 21 | 22 | static var allTests: [(String, (TestSubquery) -> () throws -> Void)] { 23 | return [ 24 | ("testSubquery", testSubquery), 25 | ] 26 | } 27 | 28 | class MyTable : Table { 29 | let a = Column("a") 30 | let b = Column("b") 31 | 32 | let tableName = "tableSubquery" 33 | } 34 | 35 | func testSubquery() { 36 | let t = MyTable() 37 | let connection = createConnection() 38 | 39 | var s = Select(from: t) 40 | .where(t.b == any(Select(t.b, from: t).where(t.b == 2))) 41 | var kuery = connection.descriptionOf(query: s) 42 | var query = "SELECT * FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = ANY (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 2)" 43 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 44 | 45 | s = Select(t.a, from: t) 46 | .group(by: t.a) 47 | .having(sum(t.b) == any(Select(t.b, from: t).where(t.b == 2))) 48 | kuery = connection.descriptionOf(query: s) 49 | query = "SELECT \"tableSubquery\".\"a\" FROM \"tableSubquery\" GROUP BY \"tableSubquery\".\"a\" HAVING SUM(\"tableSubquery\".\"b\") = ANY (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 2)" 50 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 51 | 52 | s = Select(from: t) 53 | .where(exists(Select(t.b, from: t).where(t.b == 2))) 54 | kuery = connection.descriptionOf(query: s) 55 | query = "SELECT * FROM \"tableSubquery\" WHERE EXISTS (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 2)" 56 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 57 | 58 | s = Select(from: t) 59 | .where(8.in(1,6,8)) 60 | kuery = connection.descriptionOf(query: s) 61 | query = "SELECT * FROM \"tableSubquery\" WHERE 8 IN (1, 6, 8)" 62 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 63 | 64 | s = Select(from: t) 65 | .group(by: t.a) 66 | .having("apple".in("plum")) 67 | kuery = connection.descriptionOf(query: s) 68 | query = "SELECT * FROM \"tableSubquery\" GROUP BY \"tableSubquery\".\"a\" HAVING 'apple' IN ('plum')" 69 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 70 | 71 | s = Select(from: t) 72 | .where(8.in(Select(t.b, from: t).where(t.b == 8))) 73 | kuery = connection.descriptionOf(query: s) 74 | query = "SELECT * FROM \"tableSubquery\" WHERE 8 IN (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 8)" 75 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 76 | 77 | s = Select(from: t) 78 | .group(by: t.a) 79 | .having(exists(Select(t.b, from: t).where(t.b == 8))) 80 | kuery = connection.descriptionOf(query: s) 81 | query = "SELECT * FROM \"tableSubquery\" GROUP BY \"tableSubquery\".\"a\" HAVING EXISTS (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 8)" 82 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 83 | 84 | s = Select(from: t) 85 | .group(by: t.a) 86 | .having(notExists(Select(t.b, from: t).where(t.b == 8)) && sum(t.b) > 0) 87 | kuery = connection.descriptionOf(query: s) 88 | query = "SELECT * FROM \"tableSubquery\" GROUP BY \"tableSubquery\".\"a\" HAVING (NOT EXISTS (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 8)) AND (SUM(\"tableSubquery\".\"b\") > 0)" 89 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 90 | 91 | s = Select(from: t) 92 | .where(notExists(Select(t.b, from: t).where(t.b == 8))) 93 | kuery = connection.descriptionOf(query: s) 94 | query = "SELECT * FROM \"tableSubquery\" WHERE NOT EXISTS (SELECT \"tableSubquery\".\"b\" FROM \"tableSubquery\" WHERE \"tableSubquery\".\"b\" = 8)" 95 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 96 | 97 | s = Select(from: t) 98 | .group(by: t.a) 99 | .having(Parameter().in(Parameter(), Parameter())) 100 | kuery = connection.descriptionOf(query: s) 101 | query = "SELECT * FROM \"tableSubquery\" GROUP BY \"tableSubquery\".\"a\" HAVING ?1 IN (?2, ?3)" 102 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 103 | 104 | s = Select(from: t) 105 | .where(false.notIn(Parameter(), Parameter())) 106 | kuery = connection.descriptionOf(query: s) 107 | query = "SELECT * FROM \"tableSubquery\" WHERE false NOT IN (?1, ?2)" 108 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Scripts/columnExtensions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #/** 4 | #* Copyright IBM Corporation 2016, 2017 5 | #* 6 | #* Licensed under the Apache License, Version 2.0 (the "License"); 7 | #* you may not use this file except in compliance with the License. 8 | #* You may obtain a copy of the License at 9 | #* 10 | #* http://www.apache.org/licenses/LICENSE-2.0 11 | #* 12 | #* Unless required by applicable law or agreed to in writing, software 13 | #* distributed under the License is distributed on an "AS IS" BASIS, 14 | #* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | #* See the License for the specific language governing permissions and 16 | #* limitations under the License. 17 | #**/ 18 | 19 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 20 | cd "$SCRIPT_DIR" 21 | CUR_DIR=$(pwd) 22 | 23 | temp=$(dirname "${CUR_DIR}") 24 | temp=$(dirname "${temp}") 25 | PKG_DIR=$(dirname "${CUR_DIR}") 26 | 27 | shopt -s nullglob 28 | 29 | if ! [ -d "${PKG_DIR}/Sources/SwiftKuery" ]; then 30 | echo "Failed to find ${PKG_DIR}/Sources/SwiftKuery" 31 | exit 1 32 | fi 33 | 34 | INPUT_CLAUSES_FILE="${PKG_DIR}/Scripts/ColumnExtensionClauses.txt" 35 | INPUT_OPERANDS_FILE="${PKG_DIR}/Scripts/ColumnExtensionOperands.txt" 36 | 37 | OUTPUT_FILE="${PKG_DIR}/Sources/SwiftKuery/ColumnAndExpressions_Extensions.swift" 38 | 39 | echo "--- Generating ${OUTPUT_FILE}" 40 | 41 | cat <<'EOF' > ${OUTPUT_FILE} 42 | /** 43 | * Copyright IBM Corporation 2016, 2017 44 | * 45 | * Licensed under the Apache License, Version 2.0 (the "License"); 46 | * you may not use this file except in compliance with the License. 47 | * You may obtain a copy of the License at 48 | * 49 | * http://www.apache.org/licenses/LICENSE-2.0 50 | * 51 | * Unless required by applicable law or agreed to in writing, software 52 | * distributed under the License is distributed on an "AS IS" BASIS, 53 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 54 | * See the License for the specific language governing permissions and 55 | * limitations under the License. 56 | **/ 57 | 58 | import Foundation 59 | 60 | EOF 61 | 62 | # Generate extensions for Scalar/AggregateColumnExpression and Column for (NOT)LIKE, (NOT)BETWEEN, (NOT)IN, and IS (NOT) NULL operators 63 | while read -r LINE; do 64 | [ -z "$LINE" ] && continue 65 | [[ "$LINE" =~ ^#.*$ ]] && continue 66 | stringarray=($LINE) 67 | CLAUSE_TYPE=${stringarray[0]} 68 | TYPE=${stringarray[1]} 69 | TYPE_LOWER="$(tr '[:upper:]' '[:lower:]' <<< ${TYPE:0:1})${TYPE:1}" 70 | if [[ $TYPE_LOWER == *"ColumnExpression" ]] 71 | then 72 | TYPE_LOWER="columnExpression" 73 | fi 74 | 75 | cat <> ${OUTPUT_FILE} 76 | 77 | public extension $TYPE { 78 | EOF 79 | for OPERATOR in like notLike; do 80 | 81 | cat <> ${OUTPUT_FILE} 82 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator. 83 | /// 84 | /// - Parameter pattern: The pattern to use in the $OPERATOR expression. 85 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 86 | public func $OPERATOR(_ pattern: String) -> $CLAUSE_TYPE { 87 | return $CLAUSE_TYPE(lhs: .$TYPE_LOWER(self), rhs: .string(pattern), condition: .$OPERATOR) 88 | } 89 | 90 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator with \`Parameter\`. 91 | /// 92 | /// - Parameter pattern: The pattern to use in the $OPERATOR expression as \`Parameter\`. 93 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 94 | public func $OPERATOR(_ pattern: Parameter) -> $CLAUSE_TYPE { 95 | return $CLAUSE_TYPE(lhs: .$TYPE_LOWER(self), rhs: .parameter(pattern), condition: .$OPERATOR) 96 | } 97 | 98 | EOF 99 | done 100 | for PARAM_TYPE in `sed '/^$/d' ${INPUT_OPERANDS_FILE} | sed '/^#/d'`; do 101 | PARAM_TYPE_LOWER="$(tr '[:upper:]' '[:lower:]' <<< ${PARAM_TYPE:0:1})${PARAM_TYPE:1}" 102 | 103 | for OPERATOR in between notBetween; do 104 | 105 | cat <> ${OUTPUT_FILE} 106 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator for $PARAM_TYPE. 107 | /// 108 | /// - Parameter value1: The left hand side of the $OPERATOR expression. 109 | /// - Parameter and value2: The right hand side of the $OPERATOR expression. 110 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 111 | public func $OPERATOR(_ value1: $PARAM_TYPE, and value2: $PARAM_TYPE) -> $CLAUSE_TYPE { 112 | var array = [$PARAM_TYPE]() 113 | array.append(value1) 114 | array.append(value2) 115 | return $CLAUSE_TYPE(lhs: .$TYPE_LOWER(self), rhs: .arrayOf$PARAM_TYPE(array), condition: .$OPERATOR) 116 | } 117 | EOF 118 | done 119 | for OPERATOR in \`in\` notIn; do 120 | 121 | cat <> ${OUTPUT_FILE} 122 | 123 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator for $PARAM_TYPE. 124 | /// 125 | /// - Parameter values: The list of values for the $OPERATOR expression. 126 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 127 | public func $OPERATOR(_ values: $PARAM_TYPE...) -> $CLAUSE_TYPE { 128 | return $OPERATOR(values) 129 | } 130 | 131 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator for $PARAM_TYPE. 132 | /// 133 | /// - Parameter values: An array of values for the $OPERATOR expression. 134 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 135 | public func $OPERATOR(_ values: [$PARAM_TYPE]) -> $CLAUSE_TYPE { 136 | return $CLAUSE_TYPE(lhs: .$TYPE_LOWER(self), rhs: .arrayOf$PARAM_TYPE(values), condition: .$OPERATOR) 137 | } 138 | EOF 139 | done 140 | done 141 | 142 | for OPERATOR in isNull isNotNull; do 143 | 144 | cat <> ${OUTPUT_FILE} 145 | 146 | /// Create a \`$CLAUSE_TYPE\` clause using the $OPERATOR operator. 147 | /// 148 | /// - Returns: A \`$CLAUSE_TYPE\` containing the clause. 149 | public func $OPERATOR() -> $CLAUSE_TYPE { 150 | return $CLAUSE_TYPE(lhs: .$TYPE_LOWER(self), condition: .$OPERATOR) 151 | } 152 | 153 | EOF 154 | done 155 | 156 | echo "}" >> ${OUTPUT_FILE} 157 | done < $INPUT_CLAUSES_FILE 158 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/ConditionalClause.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2016, 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // MARK: ConditionalClause 18 | 19 | /// A protocol for conditions used in SQL WHERE, ON and HAVING clauses. 20 | public protocol ConditionalClause: Buildable { 21 | /// The type of the clause: either `Filter` or `Having`. 22 | associatedtype ClauseType: Buildable 23 | /// The type of the column expression used in the clause: either `ScalarColumnExpression` or `AggregateColumnExpression`. 24 | associatedtype ColumnExpressionType: Field 25 | /// The left hand side of the conditional clause. 26 | var lhs: Predicate? { get } 27 | /// The right hand side of the conditional clause. 28 | var rhs: Predicate? { get } 29 | /// The operator of the conditional clause. 30 | var condition: Condition { get } 31 | 32 | /// Build the clause using `QueryBuilder`. 33 | /// 34 | /// - Parameter queryBuilder: The QueryBuilder to use. 35 | /// - Returns: A String representation of the clause. 36 | /// - Throws: QueryError.syntaxError if query build fails. 37 | func build(queryBuilder: QueryBuilder) throws -> String 38 | } 39 | 40 | /// An extension of `ConditionalClause` with implementation of `Buildable` protocol. 41 | extension ConditionalClause { 42 | /// Build the clause using `QueryBuilder`. 43 | /// 44 | /// - Parameter queryBuilder: The QueryBuilder to use. 45 | /// - Returns: A String representation of the clause. 46 | /// - Throws: QueryError.syntaxError if query build fails. 47 | public func build(queryBuilder: QueryBuilder) throws -> String { 48 | guard let _lhs = lhs else { 49 | if condition == .exists || condition == .notExists { 50 | guard let _rhs = rhs else { 51 | throw QueryError.syntaxError("No right hand side operand in conditional clause.") 52 | } 53 | 54 | return try condition.build(queryBuilder: queryBuilder) + " " + _rhs.build(queryBuilder: queryBuilder) 55 | } 56 | throw QueryError.syntaxError("No left hand side operand in conditional clause.") 57 | } 58 | 59 | guard let _rhs = rhs else { 60 | if condition == .isNull || condition == .isNotNull { 61 | return try _lhs.build(queryBuilder: queryBuilder) + " " + condition.build(queryBuilder: queryBuilder) 62 | } 63 | throw QueryError.syntaxError("No right hand side operand in conditional clause.") 64 | } 65 | 66 | let lhsBuilt = try _lhs.build(queryBuilder: queryBuilder) 67 | let conditionBuilt = condition.build(queryBuilder: queryBuilder) 68 | var rhsBuilt = "" 69 | if condition == .between || condition == .notBetween { 70 | switch _rhs { 71 | case .arrayOfString(let array): 72 | rhsBuilt = Utils.packType(array[0]) + " AND " + Utils.packType(array[1]) 73 | case .arrayOfInt(let array): 74 | rhsBuilt = Utils.packType(array[0]) + " AND " + Utils.packType(array[1]) 75 | case .arrayOfFloat(let array): 76 | rhsBuilt = Utils.packType(array[0]) + " AND " + Utils.packType(array[1]) 77 | case .arrayOfDouble(let array): 78 | rhsBuilt = Utils.packType(array[0]) + " AND " + Utils.packType(array[1]) 79 | case .arrayOfBool(let array): 80 | rhsBuilt = try Utils.packType(array[0], queryBuilder: queryBuilder) + " AND " + Utils.packType(array[1], queryBuilder: queryBuilder) 81 | case .arrayOfDate(let array): 82 | rhsBuilt = try Utils.packType(array[0], queryBuilder: queryBuilder) + " AND " + Utils.packType(array[1], queryBuilder: queryBuilder) 83 | case .arrayOfParameter(let array): 84 | rhsBuilt = try Utils.packType(array[0], queryBuilder: queryBuilder) + " AND " + Utils.packType(array[1], queryBuilder: queryBuilder) 85 | default: 86 | throw QueryError.syntaxError("Wrong type for right hand side operand in \(conditionBuilt) expression.") 87 | } 88 | } 89 | else if condition == .in || condition == .notIn { 90 | switch _rhs { 91 | case .arrayOfString(let array): 92 | rhsBuilt = "(\(array.map { Utils.packType($0) }.joined(separator: ", ")))" 93 | case .arrayOfInt(let array): 94 | rhsBuilt = "(\(array.map { Utils.packType($0) }.joined(separator: ", ")))" 95 | case .arrayOfFloat(let array): 96 | rhsBuilt = "(\(array.map { Utils.packType($0) }.joined(separator: ", ")))" 97 | case .arrayOfDouble(let array): 98 | rhsBuilt = "(\(array.map { Utils.packType($0) }.joined(separator: ", ")))" 99 | case .arrayOfBool(let array): 100 | rhsBuilt = try "(\(array.map { try Utils.packType($0, queryBuilder: queryBuilder) }.joined(separator: ", ")))" 101 | case .arrayOfDate(let array): 102 | rhsBuilt = try "(\(array.map { try Utils.packType($0, queryBuilder: queryBuilder) }.joined(separator: ", ")))" 103 | case .arrayOfParameter(let array): 104 | rhsBuilt = try "(\(array.map { try Utils.packType($0, queryBuilder: queryBuilder) }.joined(separator: ", ")))" 105 | case .select(let query): 106 | rhsBuilt = try "(" + query.build(queryBuilder: queryBuilder) + ")" 107 | default: 108 | throw QueryError.syntaxError("Wrong type for right hand side operand in \(conditionBuilt) expression.") 109 | } 110 | } 111 | else { 112 | rhsBuilt = try _rhs.build(queryBuilder: queryBuilder) 113 | } 114 | return lhsBuilt + " " + conditionBuilt + " " + rhsBuilt 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Tests/SwiftKueryTests/TestAggregateFunctions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright IBM Corporation 2016 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | import XCTest 18 | 19 | @testable import SwiftKuery 20 | 21 | // This test was generated by Scripts/testAggregate.sh. 22 | class TestAggregateFunctions: XCTestCase { 23 | 24 | static var allTests: [(String, (TestAggregateFunctions) -> () throws -> Void)] { 25 | return [ 26 | ("testFunctions", testFunctions), 27 | ] 28 | } 29 | 30 | class MyTable: Table { 31 | let a = Column("a") 32 | let b = Column("b") 33 | 34 | let tableName = "table" 35 | } 36 | 37 | func testFunctions() { 38 | let t = MyTable() 39 | let connection = createConnection() 40 | var s = Select(from: t) 41 | var kuery = "" 42 | var query = "" 43 | 44 | 45 | s = Select(t.a, from: t) 46 | .group(by: t.a) 47 | .having(avg(t.b) > 3) 48 | kuery = connection.descriptionOf(query: s) 49 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING AVG(\"table\".\"b\") > 3" 50 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 51 | 52 | s = Select(t.a, from: t) 53 | .group(by: t.a) 54 | .having(max(t.b) > 3) 55 | kuery = connection.descriptionOf(query: s) 56 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING MAX(\"table\".\"b\") > 3" 57 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 58 | 59 | s = Select(t.a, from: t) 60 | .group(by: t.a) 61 | .having(min(t.b) > 3) 62 | kuery = connection.descriptionOf(query: s) 63 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING MIN(\"table\".\"b\") > 3" 64 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 65 | 66 | s = Select(t.a, from: t) 67 | .group(by: t.a) 68 | .having(sum(t.b) > 3) 69 | kuery = connection.descriptionOf(query: s) 70 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING SUM(\"table\".\"b\") > 3" 71 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 72 | 73 | s = Select(t.a, from: t) 74 | .group(by: t.a) 75 | .having(last(t.b) > 3) 76 | kuery = connection.descriptionOf(query: s) 77 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING LAST(\"table\".\"b\") > 3" 78 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 79 | 80 | s = Select(t.a, from: t) 81 | .group(by: t.a) 82 | .having(first(t.b) > 3) 83 | kuery = connection.descriptionOf(query: s) 84 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING FIRST(\"table\".\"b\") > 3" 85 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 86 | 87 | s = Select(t.a, from: t) 88 | .group(by: t.a) 89 | .having(count(t.b) > 3) 90 | kuery = connection.descriptionOf(query: s) 91 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING COUNT(\"table\".\"b\") > 3" 92 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 93 | 94 | s = Select(t.a, from: t) 95 | .group(by: t.a) 96 | .having(countDistinct(t.b) != 0) 97 | kuery = connection.descriptionOf(query: s) 98 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING COUNT(DISTINCT(\"table\".\"b\")) <> 0" 99 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 100 | 101 | s = Select(t.a, from: t) 102 | .group(by: t.a) 103 | .having(ucase(sum(t.b)) <= -1) 104 | kuery = connection.descriptionOf(query: s) 105 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING UCASE(SUM(\"table\".\"b\")) <= -1" 106 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 107 | 108 | s = Select(t.a, from: t) 109 | .group(by: t.a) 110 | .having(lcase(sum(t.b)) <= -1) 111 | kuery = connection.descriptionOf(query: s) 112 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING LCASE(SUM(\"table\".\"b\")) <= -1" 113 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 114 | 115 | s = Select(t.a, from: t) 116 | .group(by: t.a) 117 | .having(len(sum(t.b)) <= -1) 118 | kuery = connection.descriptionOf(query: s) 119 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING LEN(SUM(\"table\".\"b\")) <= -1" 120 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 121 | s = Select(t.a, from: t) 122 | .group(by: t.a) 123 | .having(round(sum(t.b), to: 2) >= 9.08) 124 | kuery = connection.descriptionOf(query: s) 125 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING ROUND(SUM(\"table\".\"b\"), 2) >= 9.08" 126 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 127 | 128 | s = Select(t.a, from: t) 129 | .group(by: t.a) 130 | .having(mid(sum(t.b), start: 3, length: 6) <= -1) 131 | kuery = connection.descriptionOf(query: s) 132 | query = "SELECT \"table\".\"a\" FROM \"table\" GROUP BY \"table\".\"a\" HAVING MID(SUM(\"table\".\"b\"), 3, 6) <= -1" 133 | XCTAssertEqual(kuery, query, "\nError in query construction: \n\(kuery) \ninstead of \n\(query)") 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/SwiftKuery/SQLDataType.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright IBM Corporation 2017 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | // MARK: SQLDataType protocol 20 | 21 | /// Defines the protocol for data types to be used as table column types. 22 | public protocol SQLDataType { 23 | /// Return database specific representation of the type using `QueryBuilder`. 24 | /// 25 | /// - Parameter queryBuilder: The QueryBuilder to use. 26 | /// - Returns: A String representation of the type. 27 | static func create(queryBuilder: QueryBuilder) -> String 28 | } 29 | 30 | /// SQL varchar type. 31 | public struct Varchar: SQLDataType { 32 | /// Return database specific representation of the varchar type using `QueryBuilder`. 33 | /// 34 | /// - Parameter queryBuilder: The QueryBuilder to use. 35 | /// - Returns: A String representation of the type. 36 | public static func create(queryBuilder: QueryBuilder) -> String { 37 | return "varchar" 38 | } 39 | } 40 | 41 | /// SQL char/character type. 42 | public struct Char: SQLDataType { 43 | /// Return database specific representation of the char type using `QueryBuilder`. 44 | /// 45 | /// - Parameter queryBuilder: The QueryBuilder to use. 46 | /// - Returns: A String representation of the type. 47 | public static func create(queryBuilder: QueryBuilder) -> String { 48 | return queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.char.rawValue] 49 | } 50 | } 51 | 52 | /// SQL date type. 53 | public struct SQLDate: SQLDataType { 54 | /// Return database specific representation of the date type using `QueryBuilder`. 55 | /// 56 | /// - Parameter queryBuilder: The QueryBuilder to use. 57 | /// - Returns: A String representation of the type. 58 | public static func create(queryBuilder: QueryBuilder) -> String { 59 | return "date" 60 | } 61 | } 62 | 63 | /// SQL time type. 64 | public struct Time: SQLDataType { 65 | /// Return database specific representation of the time type using `QueryBuilder`. 66 | /// 67 | /// - Parameter queryBuilder: The QueryBuilder to use. 68 | /// - Returns: A String representation of the type. 69 | public static func create(queryBuilder: QueryBuilder) -> String { 70 | return "time" 71 | } 72 | } 73 | 74 | /// SQL timestamp type. 75 | public struct Timestamp: SQLDataType { 76 | /// Return database specific representation of the timestamp type using `QueryBuilder`. 77 | /// 78 | /// - Parameter queryBuilder: The QueryBuilder to use. 79 | /// - Returns: A String representation of the type. 80 | public static func create(queryBuilder: QueryBuilder) -> String { 81 | return "timestamp" 82 | } 83 | } 84 | 85 | extension Int16: SQLDataType { 86 | /// Return database specific representation of the int16 type using `QueryBuilder`. 87 | /// 88 | /// - Parameter queryBuilder: The QueryBuilder to use. 89 | /// - Returns: A String representation of the type. 90 | public static func create(queryBuilder: QueryBuilder) -> String { 91 | return "smallint" 92 | } 93 | } 94 | 95 | extension Int32: SQLDataType { 96 | /// Return database specific representation of the int32 type using `QueryBuilder`. 97 | /// 98 | /// - Parameter queryBuilder: The QueryBuilder to use. 99 | /// - Returns: A String representation of the type. 100 | public static func create(queryBuilder: QueryBuilder) -> String { 101 | return queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.int32.rawValue] 102 | } 103 | } 104 | 105 | extension Int64: SQLDataType { 106 | /// Return database specific representation of the int32 type using `QueryBuilder`. 107 | /// 108 | /// - Parameter queryBuilder: The QueryBuilder to use. 109 | /// - Returns: A String representation of the type. 110 | public static func create(queryBuilder: QueryBuilder) -> String { 111 | return "bigint" 112 | } 113 | } 114 | 115 | extension String: SQLDataType { 116 | /// Return database specific representation of the string type using `QueryBuilder`. 117 | /// 118 | /// - Parameter queryBuilder: The QueryBuilder to use. 119 | /// - Returns: A String representation of the type. 120 | public static func create(queryBuilder: QueryBuilder) -> String { 121 | return "text" 122 | } 123 | } 124 | 125 | extension Float: SQLDataType { 126 | /// Return database specific representation of the float type using `QueryBuilder`. 127 | /// 128 | /// - Parameter queryBuilder: The QueryBuilder to use. 129 | /// - Returns: A String representation of the type. 130 | public static func create(queryBuilder: QueryBuilder) -> String { 131 | return queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.float.rawValue] 132 | } 133 | } 134 | 135 | extension Double: SQLDataType { 136 | /// Return database specific representation of the double type using `QueryBuilder`. 137 | /// 138 | /// - Parameter queryBuilder: The QueryBuilder to use. 139 | /// - Returns: A String representation of the type. 140 | public static func create(queryBuilder: QueryBuilder) -> String { 141 | return queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.double.rawValue] 142 | } 143 | } 144 | 145 | extension Bool: SQLDataType { 146 | /// Return database specific representation of the boolean type using `QueryBuilder`. 147 | /// 148 | /// - Parameter queryBuilder: The QueryBuilder to use. 149 | /// - Returns: A String representation of the type. 150 | public static func create(queryBuilder: QueryBuilder) -> String { 151 | return "boolean" 152 | } 153 | } 154 | 155 | extension UUID: SQLDataType { 156 | /// Return database specific representation of the UUID type using `QueryBuilder`. 157 | /// 158 | /// - Parameter queryBuilder: The QueryBuilder to use. 159 | /// - Returns: A String representation of the type. 160 | public static func create(queryBuilder: QueryBuilder) -> String { 161 | return queryBuilder.substitutions[QueryBuilder.QuerySubstitutionNames.uuid.rawValue] 162 | } 163 | } 164 | 165 | 166 | 167 | --------------------------------------------------------------------------------