From 4896eae274b4ee0c422a6a2ef058221693375ad4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 13 Oct 2014 23:47:44 -0700 Subject: [PATCH 0001/1046] Add type-safe SQL expression builder A bit more declarative sugar for building queries, e.g.: // given let users = db["users"] let admin = Expression("admin") let age = Expression("age") users.filter(admin && age >= 30).order(age.desc) // SELECT * FROM users WHERE (admin) AND (age >= 30) ORDER BY age DESC users.group(age, having: count(age) == 1) // SELECT * FROM users GROUP BY age HAVING count(age) = 1 Everything is an expression now, and depending on the type can interact differently. This means we can now use bindings everywhere with little effort. Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 423 +++++++++++++++++ SQLite Common Tests/QueryTests.swift | 266 +++++------ SQLite Common Tests/StatementTests.swift | 2 +- SQLite Common Tests/TestHelper.swift | 1 + SQLite Common/Database.swift | 38 +- SQLite Common/Expression.swift | 420 ++++++++++++++++ SQLite Common/Query.swift | 449 ++++++------------ SQLite Common/Statement.swift | 32 +- SQLite Common/{Datatype.swift => Value.swift} | 22 +- SQLite.xcodeproj/project.pbxproj | 34 +- 10 files changed, 1193 insertions(+), 494 deletions(-) create mode 100644 SQLite Common Tests/ExpressionTests.swift create mode 100644 SQLite Common/Expression.swift rename SQLite Common/{Datatype.swift => Value.swift} (84%) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift new file mode 100644 index 00000000..159edfdb --- /dev/null +++ b/SQLite Common Tests/ExpressionTests.swift @@ -0,0 +1,423 @@ +import XCTest +import SQLite + +class ExpressionTests: XCTestCase { + + let db = Database() + var users: Query { return db["users"] } + + override func setUp() { + super.setUp() + + CreateUsersTable(db) + } + + func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { + let string = Expression(value: "Hello") + ExpectExecutions(db, ["SELECT 'Hello' || 'Hello' FROM users": 3]) { _ in + for _ in self.users.select(string + string) {} + for _ in self.users.select(string + "Hello") {} + for _ in self.users.select("Hello" + string) {} + } + } + + func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 + 2 FROM users": 3]) { _ in + for _ in self.users.select(int + int) {} + for _ in self.users.select(int + 2) {} + for _ in self.users.select(2 + int) {} + } + } + + func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() { + let double = Expression(value: 2.0) + ExpectExecutions(db, ["SELECT 2.0 + 2.0 FROM users": 3]) { _ in + for _ in self.users.select(double + double) {} + for _ in self.users.select(double + 2.0) {} + for _ in self.users.select(2.0 + double) {} + } + } + + func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 - 2 FROM users": 3]) { _ in + for _ in self.users.select(int - int) {} + for _ in self.users.select(int - 2) {} + for _ in self.users.select(2 - int) {} + } + } + + func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() { + let double = Expression(value: 2.0) + ExpectExecutions(db, ["SELECT 2.0 - 2.0 FROM users": 3]) { _ in + for _ in self.users.select(double - double) {} + for _ in self.users.select(double - 2.0) {} + for _ in self.users.select(2.0 - double) {} + } + } + + func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 * 2 FROM users": 3]) { _ in + for _ in self.users.select(int * int) {} + for _ in self.users.select(int * 2) {} + for _ in self.users.select(2 * int) {} + } + } + + func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() { + let double = Expression(value: 2.0) + ExpectExecutions(db, ["SELECT 2.0 * 2.0 FROM users": 3]) { _ in + for _ in self.users.select(double * double) {} + for _ in self.users.select(double * 2.0) {} + for _ in self.users.select(2.0 * double) {} + } + } + + func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 / 2 FROM users": 3]) { _ in + for _ in self.users.select(int / int) {} + for _ in self.users.select(int / 2) {} + for _ in self.users.select(2 / int) {} + } + } + + func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() { + let double = Expression(value: 2.0) + ExpectExecutions(db, ["SELECT 2.0 / 2.0 FROM users": 3]) { _ in + for _ in self.users.select(double / double) {} + for _ in self.users.select(double / 2.0) {} + for _ in self.users.select(2.0 / double) {} + } + } + + func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 % 2 FROM users": 3]) { _ in + for _ in self.users.select(int % int) {} + for _ in self.users.select(int % 2) {} + for _ in self.users.select(2 % int) {} + } + } + + func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 << 2 FROM users": 3]) { _ in + for _ in self.users.select(int << int) {} + for _ in self.users.select(int << 2) {} + for _ in self.users.select(2 << int) {} + } + } + + func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 >> 2 FROM users": 3]) { _ in + for _ in self.users.select(int >> int) {} + for _ in self.users.select(int >> 2) {} + for _ in self.users.select(2 >> int) {} + } + } + + func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 & 2 FROM users": 3]) { _ in + for _ in self.users.select(int & int) {} + for _ in self.users.select(int & 2) {} + for _ in self.users.select(2 & int) {} + } + } + + func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 | 2 FROM users": 3]) { _ in + for _ in self.users.select(int | int) {} + for _ in self.users.select(int | 2) {} + for _ in self.users.select(2 | int) {} + } + } + + func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { + let int = Expression(value: 2) + let query = users.select(~int) + ExpectExecutions(db, ["SELECT ~(2) FROM users": 1]) { _ in for _ in query {} } + } + + func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { + let bool = Expression(value: true) + ExpectExecutions(db, ["SELECT 1 = 1 FROM users": 3]) { _ in + for _ in self.users.select(bool == bool) {} + for _ in self.users.select(bool == true) {} + for _ in self.users.select(true == bool) {} + } + } + + func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { + let bool = Expression(value: true) + ExpectExecutions(db, ["SELECT 1 != 1 FROM users": 3]) { _ in + for _ in self.users.select(bool != bool) {} + for _ in self.users.select(bool != true) {} + for _ in self.users.select(true != bool) {} + } + } + + func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 > 2 FROM users": 3]) { _ in + for _ in self.users.select(int > int) {} + for _ in self.users.select(int > 2) {} + for _ in self.users.select(2 > int) {} + } + } + + func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 >= 2 FROM users": 3]) { _ in + for _ in self.users.select(int >= int) {} + for _ in self.users.select(int >= 2) {} + for _ in self.users.select(2 >= int) {} + } + } + + func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 < 2 FROM users": 3]) { _ in + for _ in self.users.select(int < int) {} + for _ in self.users.select(int < 2) {} + for _ in self.users.select(2 < int) {} + } + } + + func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { + let int = Expression(value: 2) + ExpectExecutions(db, ["SELECT 2 <= 2 FROM users": 3]) { _ in + for _ in self.users.select(int <= int) {} + for _ in self.users.select(int <= 2) {} + for _ in self.users.select(2 <= int) {} + } + } + + func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() { + let int = Expression(value: 2) + let query = users.select(-int) + ExpectExecution(db, query, "-(2)") + } + + func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() { + let double = Expression(value: 2.0) + let query = users.select(-double) + ExpectExecution(db, query, "-(2.0)") + } + + func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() { + let int = Expression(value: 2) + let query = users.select(0...5 ~= int) + ExpectExecution(db, query, "2 BETWEEN 0 AND 5") + } + + func test_likeOperator_withStringExpression_buildsLikeExpression() { + let string = Expression(value: "Hello") + let query = users.select(like("%ello", string)) + ExpectExecution(db, query, "'Hello' LIKE '%ello'") + } + + func test_globOperator_withStringExpression_buildsGlobExpression() { + let string = Expression(value: "Hello") + let query = users.select(glob("*ello", string)) + ExpectExecution(db, query, "'Hello' GLOB '*ello'") + } + + func test_matchOperator_withStringExpression_buildsMatchExpression() { + let string = Expression(value: "Hello") + let query = users.select(match("ello", string)) + ExpectExecution(db, query, "'Hello' MATCH 'ello'") + } + + func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { + let bool = Expression(value: true) + let query = users.select(bool && bool) + ExpectExecution(db, query, "(1) AND (1)") + } + + func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { + let bool = Expression(value: true) + let query = users.select(bool || bool) + ExpectExecution(db, query, "(1) OR (1)") + } + + func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { + let bool = Expression(value: true) + let query = users.select(!bool) + ExpectExecution(db, query, "NOT (1)") + } + + func test_absFunction_withNumberExpressions_buildsAbsExpression() { + let int = Expression(value: -2) + let query = users.select(abs(int)) + ExpectExecution(db, query, "abs(-2)") + } + + func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { + let int1 = Expression(value: nil) + let int2 = Expression(value: nil) + let int3 = Expression(value: 3) + let query = users.select(coalesce(int1, int2, int3)) + ExpectExecution(db, query, "coalesce(NULL, NULL, 3)") + } + + func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { + let int = Expression(value: nil) + ExpectExecution(db, users.select(ifnull(int, 1)), "ifnull(NULL, 1)") + ExpectExecution(db, users.select(int ?? 1), "ifnull(NULL, 1)") + } + + func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { + let string = Expression(value: "Hello") + let query = users.select(length(string)) + ExpectExecution(db, query, "length('Hello')") + } + + func test_lowerFunction_withStringExpression_buildsLowerStringExpression() { + let string = Expression(value: "Hello") + let query = users.select(lower(string)) + ExpectExecution(db, query, "lower('Hello')") + } + + func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() { + let string = Expression(value: " Hello") + let query = users.select(ltrim(string)) + ExpectExecution(db, query, "ltrim(' Hello')") + } + + func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { + let string = Expression(value: "Hello") + let query = users.select(ltrim(string, "H")) + ExpectExecution(db, query, "ltrim('Hello', 'H')") + } + + func test_randomFunction_buildsRandomIntExpression() { + let query = users.select(random) + ExpectExecution(db, query, "random()") + } + + func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() { + let string = Expression(value: "Hello") + let query = users.select(replace(string, "He", "E")) + ExpectExecution(db, query, "replace('Hello', 'He', 'E')") + } + + func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() { + let double = Expression(value: 3.14159) + let query = users.select(round(double)) + ExpectExecution(db, query, "round(3.14159)") + } + + func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() { + let double = Expression(value: 3.14159) + let query = users.select(round(double, 2)) + ExpectExecution(db, query, "round(3.14159, 2)") + } + + func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() { + let string = Expression(value: "Hello ") + let query = users.select(rtrim(string)) + ExpectExecution(db, query, "rtrim('Hello ')") + } + + func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { + let string = Expression(value: "Hello") + let query = users.select(rtrim(string, "lo")) + ExpectExecution(db, query, "rtrim('Hello', 'lo')") + } + + func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() { + let string = Expression(value: "Hello") + let query = users.select(substr(string, 1)) + ExpectExecution(db, query, "substr('Hello', 1)") + } + + func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() { + let string = Expression(value: "Hello") + let query = users.select(substr(string, 1, 2)) + ExpectExecution(db, query, "substr('Hello', 1, 2)") + } + + func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() { + let string = Expression(value: "Hello") + let query = users.select(substr(string, 1..<3)) + ExpectExecution(db, query, "substr('Hello', 1, 2)") + } + + func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() { + let string = Expression(value: " Hello ") + let query = users.select(trim(string)) + ExpectExecution(db, query, "trim(' Hello ')") + } + + func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { + let string = Expression(value: "Hello") + let query = users.select(trim(string, "lo")) + ExpectExecution(db, query, "trim('Hello', 'lo')") + } + + func test_upperFunction_withStringExpression_buildsLowerStringExpression() { + let string = Expression(value: "Hello") + let query = users.select(upper(string)) + ExpectExecution(db, query, "upper('Hello')") + } + + let id = Expression("id") + let email = Expression("email") + let salary = Expression("salary") + let admin = Expression("admin") + + func test_countFunction_withExpression_buildsCountExpression() { + ExpectExecution(db, users.select(count(id)), "count(id)") + ExpectExecution(db, users.select(count(email)), "count(email)") + ExpectExecution(db, users.select(count(salary)), "count(salary)") + ExpectExecution(db, users.select(count(admin)), "count(admin)") + } + + func test_countFunction_withStar_buildsCountExpression() { + ExpectExecution(db, users.select(count(*)), "count(*)") + } + + func test_maxFunction_withExpression_buildsMaxExpression() { + ExpectExecution(db, users.select(max(id)), "max(id)") + ExpectExecution(db, users.select(max(email)), "max(email)") + ExpectExecution(db, users.select(max(salary)), "max(salary)") + ExpectExecution(db, users.select(max(admin)), "max(admin)") + } + + func test_minFunction_withExpression_buildsMinExpression() { + ExpectExecution(db, users.select(min(id)), "min(id)") + ExpectExecution(db, users.select(min(email)), "min(email)") + ExpectExecution(db, users.select(min(salary)), "min(salary)") + ExpectExecution(db, users.select(min(admin)), "min(admin)") + } + + func test_averageFunction_withExpression_buildsAverageExpression() { + ExpectExecution(db, users.select(average(id)), "avg(id)") + ExpectExecution(db, users.select(average(salary)), "avg(salary)") + } + + func test_sumFunction_withExpression_buildsSumExpression() { + ExpectExecution(db, users.select(sum(id)), "sum(id)") + ExpectExecution(db, users.select(sum(salary)), "sum(salary)") + } + + func test_totalFunction_withExpression_buildsTotalExpression() { + ExpectExecution(db, users.select(total(id)), "total(id)") + ExpectExecution(db, users.select(total(salary)), "total(salary)") + } + + func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { + ExpectExecution(db, users.select(contains([1, 2, 3], id)), "id IN (1, 2, 3)") + } + +} + +func ExpectExecution(db: Database, query: Query, SQL: String) { + ExpectExecutions(db, ["SELECT \(SQL) FROM users": 1]) { _ in for _ in query {} } +} diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index d501a6a7..9a67fa52 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -6,181 +6,162 @@ class QueryTests: XCTestCase { let db = Database() var users: Query { return db["users"] } + let id = Expression("id") + let email = Expression("email") + let age = Expression("age") + let admin = Expression("admin") + let manager_id = Expression("manager_id") + override func setUp() { super.setUp() CreateUsersTable(db) } - func test_select_withString_compilesSelectClause() { - let query = users.select("email") + func test_select_withExpression_compilesSelectClause() { + let query = users.select(email) let SQL = "SELECT email FROM users" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_select_withVariadicStrings_compilesSelectClause() { - let query = users.select("email", "count(*)") + func test_select_withVariadicExpressions_compilesSelectClause() { + let query = users.select(email, count(*)) let SQL = "SELECT email, count(*) FROM users" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_join_compilesJoinClause() { - let query = users.join("users AS managers", on: "users.manager_id = managers.id") + func test_select_withStar_resetsSelectClause() { + let query = users.select(email) - let SQL = "SELECT * FROM users INNER JOIN users AS managers ON users.manager_id = managers.id" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + let SQL = "SELECT * FROM users" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(*) {} } } - func test_join_withExplicityType_compilesJoinClauseWithType() { - let query = users.join(.LeftOuter, "users AS managers", on: "users.manager_id = managers.id") + func test_join_compilesJoinClause() { + let managers = db["users AS managers"] + let managers_id = Expression("managers.id") + let users_manager_id = Expression("users.manager_id") - let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON users.manager_id = managers.id" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - } + let query = users.join(managers, on: managers_id == users_manager_id) - func test_join_whenChained_compilesAggregateJoinClause() { - let query = users - .join("users AS managers", on: "users.manager_id = managers.id") - .join("users AS managed", on: "managed.manager_id = users.id") - - let SQL = "SELECT * FROM users " + - "INNER JOIN users AS managers ON users.manager_id = managers.id " + - "INNER JOIN users AS managed ON managed.manager_id = users.id" + let SQL = "SELECT * FROM users INNER JOIN users AS managers ON managers.id = users.manager_id" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_filter_withoutBindings_compilesWhereClause() { - let query = users.filter("admin = 1") + func test_join_withExplicitType_compilesJoinClauseWithType() { + let managers = db["users AS managers"] + let managers_id = Expression("managers.id") + let users_manager_id = Expression("users.manager_id") - let SQL = "SELECT * FROM users WHERE admin = 1" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - } + let query = users.join(.LeftOuter, managers, on: managers_id == users_manager_id) - func test_filter_withExplicitBindings_compilesWhereClause() { - let query = users.filter("admin = ?", true) - - let SQL = "SELECT * FROM users WHERE admin = 1" + let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON managers.id = users.manager_id" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_filter_withImplicitBindingsDictionary_compilesWhereClause() { - let query = users.filter(["email": "alice@example.com", "age": 30]) + func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { + let admin = Expression("managers.admin") + let managers = db["users AS managers"].filter(admin) + let managers_id = Expression("managers.id") + let users_manager_id = Expression("users.manager_id") + + let query = users.join(managers, on: managers_id == users_manager_id) let SQL = "SELECT * FROM users " + - "WHERE email = 'alice@example.com' " + - "AND age = 30" + "INNER JOIN users AS managers " + + "ON (managers.id = users.manager_id) " + + "AND (managers.admin)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_filter_withArrayBindings_compilesWhereClause() { - let query = users.filter(["id": [1, 2]]) + func test_join_whenChained_compilesAggregateJoinClause() { + let managers = db["users AS managers"] + let managers_id = Expression("managers.id") + let users_manager_id = Expression("users.manager_id") + + let managed = db["users AS managed"] + let managed_manager_id = Expression("users.id") + let users_id = Expression("managed.manager_id") + + let query = users + .join(managers, on: managers_id == users_manager_id) + .join(managed, on: managed_manager_id == users_id) - let SQL = "SELECT * FROM users WHERE id IN (1, 2)" + let SQL = "SELECT * FROM users " + + "INNER JOIN users AS managers ON managers.id = users.manager_id " + + "INNER JOIN users AS managed ON users.id = managed.manager_id" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_filter_withRangeBindings_compilesWhereClause() { - let query = users.filter(["age": 20..<30]) + func test_filter_compilesWhereClause() { + let query = users.filter(admin == true) - let SQL = "SELECT * FROM users WHERE age BETWEEN 20 AND 30" + let SQL = "SELECT * FROM users WHERE admin = 1" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_filter_whenChained_compilesAggregateWhereClause() { let query = users - .filter("email = ?", "alice@example.com") - .filter("age >= ?", 21) + .filter(email == "alice@example.com") + .filter(age >= 21) let SQL = "SELECT * FROM users " + - "WHERE email = 'alice@example.com' " + - "AND age >= 21" + "WHERE (email = 'alice@example.com') " + + "AND (age >= 21)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_group_withSingleColumnName_compilesGroupClause() { - let query = users.group("age") + func test_group_withSingleExpressionName_compilesGroupClause() { + let query = users.group(age) let SQL = "SELECT * FROM users GROUP BY age" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_group_withVariadicColumnNames_compilesGroupClause() { - let query = users.group("age", "admin") + func test_group_withVariadicExpressionNames_compilesGroupClause() { + let query = users.group(age, admin) let SQL = "SELECT * FROM users GROUP BY age, admin" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_group_withColumnNameAndHavingBindings_compilesGroupClause() { - let query = users.group("age", having: "age >= ?", 30) + func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { + let query = users.group(age, having: age >= 30) let SQL = "SELECT * FROM users GROUP BY age HAVING age >= 30" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_group_withColumnNamesAndHavingBindings_compilesGroupClause() { - let query = users.group(["age", "admin"], having: "age >= ?", 30) + func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { + let query = users.group([age, admin], having: age >= 30) let SQL = "SELECT * FROM users GROUP BY age, admin HAVING age >= 30" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_order_withSingleColumnName_compilesOrderClause() { - let query = users.order("age") + func test_order_withSingleExpressionName_compilesOrderClause() { + let query = users.order(age) - let SQL = "SELECT * FROM users ORDER BY age ASC" + let SQL = "SELECT * FROM users ORDER BY age" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_order_withVariadicColumnNames_compilesOrderClause() { - let query = users.order("age", "email") + func test_order_withVariadicExpressionNames_compilesOrderClause() { + let query = users.order(age, email) - let SQL = "SELECT * FROM users ORDER BY age ASC, email ASC" + let SQL = "SELECT * FROM users ORDER BY age, email" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_order_withColumnAndSortDirection_compilesOrderClause() { - let query = users.order("age", .Desc) - - let SQL = "SELECT * FROM users ORDER BY age DESC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - } - - func test_order_withColumnSortDirectionTuple_compilesOrderClause() { - let query = users.order(("age", .Desc)) - - let SQL = "SELECT * FROM users ORDER BY age DESC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - } - - func test_order_withVariadicColumnSortDirectionTuples_compilesOrderClause() { - let query = users.order(("age", .Desc), ("email", .Asc)) + func test_order_withExpressionAndSortDirection_compilesOrderClause() { + let query = users.order(age.desc, email.asc) let SQL = "SELECT * FROM users ORDER BY age DESC, email ASC" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - func test_order_whenChained_compilesAggregateOrderClause() { - let query = users.order("age").order("email") - - let SQL = "SELECT * FROM users ORDER BY age ASC, email ASC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - } - - func test_reverse_reversesOrder() { - let query = users.order(("age", .Desc), ("email", .Asc)) - - let SQL = "SELECT * FROM users ORDER BY age ASC, email DESC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in reverse(query) {} } - } - - func test_reverse_withoutOrder_reversesOrderByRowID() { - let SQL = "SELECT * FROM users ORDER BY users.ROWID DESC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in reverse(self.users) {} } - } - func test_limit_compilesLimitClause() { let query = users.limit(5) @@ -216,17 +197,26 @@ class QueryTests: XCTestCase { } func test_SQL_compilesProperly() { + let admin = Expression("managers.admin") + let managers = db["users AS managers"].filter(admin == true) + let managers_id = Expression("managers.id") + let users_manager_id = Expression("users.manager_id") + let email = Expression("users.email") + let age = Expression("users.age") + let query = users - .select("email", "count(email) AS count") - .filter("age >= ?", 21) - .group("age", having: "count > ?", 1) - .order("email", .Desc) + .select(email, count(email)) + .join(.LeftOuter, managers, on: managers_id == users_manager_id) + .filter(21..<32 ~= age) + .group(age, having: count(email) > 1) + .order(email.desc) .limit(1, offset: 2) - let SQL = "SELECT email, count(email) AS count FROM users " + - "WHERE age >= 21 " + - "GROUP BY age HAVING count > 1 " + - "ORDER BY email DESC " + + let SQL = "SELECT users.email, count(users.email) FROM users " + + "LEFT OUTER JOIN users AS managers ON (managers.id = users.manager_id) AND (managers.admin = 1) " + + "WHERE users.age BETWEEN 21 AND 32 " + + "GROUP BY users.age HAVING count(users.email) > 1 " + + "ORDER BY users.email DESC " + "LIMIT 1 " + "OFFSET 2" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } @@ -243,24 +233,6 @@ class QueryTests: XCTestCase { } } - func test_last_withAnEmptyQuery_returnsNil() { - XCTAssert(users.last == nil) - } - - func test_last_returnsTheLastRow() { - InsertUsers(db, "alice", "betsy") - ExpectExecutions(db, ["SELECT * FROM users ORDER BY users.ROWID DESC LIMIT 1": 1]) { _ in - XCTAssertEqual(2, self.users.last!["id"] as Int) - } - } - - func test_last_withAnOrderedQuery_reversesOrder() { - ExpectExecutions(db, ["SELECT * FROM users ORDER BY age DESC LIMIT 1": 1]) { _ in - self.users.order("age").last - return - } - } - func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() { ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 2]) { _ in XCTAssertTrue(self.users.isEmpty) @@ -272,23 +244,35 @@ class QueryTests: XCTestCase { func test_insert_insertsRows() { let SQL = "INSERT INTO users (email, age) VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in - XCTAssertEqual(1, self.users.insert(["email": "alice@example.com", "age": 30]).ID!) + XCTAssertEqual(1, self.users.insert { u in + u.set(self.email, "alice@example.com") + u.set(self.age, 30) + }.ID!) } - XCTAssert(users.insert(["email": "alice@example.com", "age": 30]).ID == nil) + XCTAssert(users.insert { u in + u.set(self.email, "alice@example.com") + u.set(self.age, 30) + }.ID == nil) } func test_update_updatesRows() { InsertUsers(db, "alice", "betsy") InsertUser(db, "dolly", admin: true) - XCTAssertEqual(2, users.filter(["admin": false]).update(["age": 30, "admin": true]).changes) - XCTAssertEqual(0, users.filter(["admin": false]).update(["age": 30, "admin": true]).changes) + XCTAssertEqual(2, users.filter(!admin).update { u in + u.set(self.age, 30) + u.set(self.admin, true) + }.changes) + XCTAssertEqual(0, users.filter(!admin).update { u in + u.set(self.age, 30) + u.set(self.admin, true) + }.changes) } func test_delete_deletesRows() { InsertUser(db, "alice", age: 20) - XCTAssertEqual(0, users.filter(["email": "betsy@example.com"]).delete().changes) + XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes) InsertUser(db, "betsy", age: 30) XCTAssertEqual(2, users.delete().changes) @@ -300,56 +284,56 @@ class QueryTests: XCTestCase { InsertUser(db, "alice") XCTAssertEqual(1, users.count) - XCTAssertEqual(0, users.filter("age IS NOT NULL").count) + XCTAssertEqual(0, users.filter(age != nil).count) } - func test_count_withColumn_returnsCount() { + func test_count_withExpression_returnsCount() { InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 20) InsertUser(db, "cindy") - XCTAssertEqual(2, users.count("age")) - XCTAssertEqual(1, users.count("DISTINCT age")) + XCTAssertEqual(2, users.count(age)) + XCTAssertEqual(1, users.count(Expression("DISTINCT age"))) } - func test_max_returnsMaximum() { - XCTAssert(users.max("age") == nil) + func test_max_withInt_returnsMaximumInt() { + XCTAssert(users.max(age) == nil) InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(30, users.max("age") as Int) + XCTAssertEqual(30, users.max(age)!) } - func test_min_returnsMinimum() { - XCTAssert(users.min("age") == nil) + func test_min_withInt_returnsMinimumInt() { + XCTAssert(users.min(age) == nil) InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(20, users.min("age") as Int) + XCTAssertEqual(20, users.min(age)!) } - func test_average_returnsAverage() { - XCTAssert(users.average("age") == nil) + func test_averageWithInt_returnsDouble() { + XCTAssert(users.average(age) == nil) InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(25.0, users.average("age")!) + XCTAssertEqual(25.0, users.average(age)!) } func test_sum_returnsSum() { - XCTAssert(users.sum("age") == nil) + XCTAssert(users.sum(age) == nil) InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(50, users.sum("age") as Int) + XCTAssertEqual(50, users.sum(age)!) } func test_total_returnsTotal() { - XCTAssertEqual(0.0, users.total("age")) + XCTAssertEqual(0.0, users.total(age)) InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(50.0, users.total("age")) + XCTAssertEqual(50.0, users.total(age)) } } diff --git a/SQLite Common Tests/StatementTests.swift b/SQLite Common Tests/StatementTests.swift index d74208f9..453f8e19 100644 --- a/SQLite Common Tests/StatementTests.swift +++ b/SQLite Common Tests/StatementTests.swift @@ -171,7 +171,7 @@ class StatementTests: XCTestCase { XCTAssertEqual("alice@example.com", row[1] as String) } - func test_values_returnsDictionaryOfColumnsToValues() { + func test_values_returnsDictionaryOfExpressionsToValues() { InsertUser(db, "alice") let stmt = db.prepare("SELECT id, \"email\" FROM users") stmt.next() diff --git a/SQLite Common Tests/TestHelper.swift b/SQLite Common Tests/TestHelper.swift index 89ab014b..ef80a310 100644 --- a/SQLite Common Tests/TestHelper.swift +++ b/SQLite Common Tests/TestHelper.swift @@ -11,6 +11,7 @@ func CreateUsersTable(db: Database) { "id INTEGER PRIMARY KEY, " + "email TEXT NOT NULL UNIQUE, " + "age INTEGER, " + + "salary REAL, " + "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + "manager_id INTEGER, " + "FOREIGN KEY(manager_id) REFERENCES users(id)" + diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index e0778ea3..5ce16cb7 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -1,3 +1,4 @@ + // // SQLite.Database // Copyright (c) 2014 Stephen Celis. @@ -21,6 +22,11 @@ // THE SOFTWARE. // +func quote(#literal: String) -> String { + let escaped = join("''", split(literal) { $0 == "'" }) + return "'\(escaped)'" +} + /// A connection (handle) to a SQLite database. public final class Database { @@ -85,7 +91,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: Datatype?...) -> Statement { + public func prepare(statement: String, _ bindings: Value?...) -> Statement { if !bindings.isEmpty { return prepare(statement, bindings) } var statementHandle: COpaquePointer = nil @@ -100,7 +106,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [Datatype?]) -> Statement { + public func prepare(statement: String, _ bindings: [Value?]) -> Statement { return prepare(statement).bind(bindings) } @@ -112,7 +118,7 @@ public final class Database { /// statement. /// /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [String: Datatype?]) -> Statement { + public func prepare(statement: String, _ bindings: [String: Value?]) -> Statement { return prepare(statement).bind(bindings) } @@ -125,7 +131,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement. - public func run(statement: String, _ bindings: Datatype?...) -> Statement { + public func run(statement: String, _ bindings: Value?...) -> Statement { return run(statement, bindings) } @@ -136,7 +142,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement. - public func run(statement: String, _ bindings: [Datatype?]) -> Statement { + public func run(statement: String, _ bindings: [Value?]) -> Statement { return prepare(statement).run(bindings) } @@ -148,7 +154,7 @@ public final class Database { /// statement. /// /// :returns: The statement. - public func run(statement: String, _ bindings: [String: Datatype?]) -> Statement { + public func run(statement: String, _ bindings: [String: Value?]) -> Statement { return prepare(statement).run(bindings) } @@ -162,7 +168,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: Datatype?...) -> Datatype? { + public func scalar(statement: String, _ bindings: Value?...) -> Value? { return scalar(statement, bindings) } @@ -174,7 +180,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [Datatype?]) -> Datatype? { + public func scalar(statement: String, _ bindings: [Value?]) -> Value? { return prepare(statement).scalar(bindings) } @@ -187,7 +193,7 @@ public final class Database { /// statement. /// /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [String: Datatype?]) -> Datatype? { + public func scalar(statement: String, _ bindings: [String: Value?]) -> Value? { return prepare(statement).scalar(bindings) } @@ -272,12 +278,12 @@ public final class Database { } private func savepoint(name: String, _ statements: [@autoclosure () -> Statement]) -> Statement { - let quotedName = join("''", split(name) { $0 == "'" }) - var savepoint = run("SAVEPOINT '\(quotedName)'") + let quotedName = quote(literal: name) + var savepoint = run("SAVEPOINT \(quotedName)") // FIXME: rdar://18479820 // for statement in statements { savepoint = savepoint && statement() } for idx in 0.. Query { - return Query(self, tableName) - } - } extension Database: DebugPrintable { diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift new file mode 100644 index 00000000..6443bf8f --- /dev/null +++ b/SQLite Common/Expression.swift @@ -0,0 +1,420 @@ +// +// SQLite.Expression +// Copyright (c) 2014 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct Expression { + + internal var SQL: String + internal var bindings: [Value?] + + public init(_ SQL: String = "", _ bindings: [Value?] = []) { + (self.SQL, self.bindings) = (SQL, bindings) + } + + public init(value: Expression) { + self.init(value.SQL, value.bindings) + } + + public init(value: Value?) { + self.init("?", [value]) + } + +} + +public protocol Expressible { + + var expression: Expression<()> { get } + +} + +extension Bool: Expressible { + + public var expression: Expression<()> { + return Expression(value: self) + } + +} + +extension Double: Expressible { + + public var expression: Expression<()> { + return Expression(value: self) + } + +} + +extension Int: Expressible { + + public var expression: Expression<()> { + return Expression(value: self) + } + +} + +extension String: Expressible { + + public var expression: Expression<()> { + return Expression(value: self) + } + +} + +extension Query: Expressible { + + public var expression: Expression<()> { + return Expression(tableName) + } + +} + +extension Expression: Expressible { + + public var expression: Expression<()> { + return Expression<()>(SQL, bindings) + } + + public var asc: Expression<()> { + return join(" ", [self, Expression("ASC")]) + } + + public var desc: Expression<()> { + return join(" ", [self, Expression("DESC")]) + } + +} + +// MARK: - Expressions + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix("||", lhs, rhs) +} +public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(value: rhs) } +public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func +(lhs: Expression, rhs: T) -> Expression { return lhs + Expression(value: rhs) } +public func +(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } + +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func -(lhs: Expression, rhs: T) -> Expression { return lhs - Expression(value: rhs) } +public func -(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } + +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func *(lhs: Expression, rhs: T) -> Expression { return lhs * Expression(value: rhs) } +public func *(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } + +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func /(lhs: Expression, rhs: T) -> Expression { return lhs / Expression(value: rhs) } +public func /(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } + +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(value: rhs) } +public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } + +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(value: rhs) } +public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } + +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return Expression("\(lhs.SQL) \(__FUNCTION__) \(rhs.SQL)", lhs.bindings + rhs.bindings) +} +public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) } +public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } + +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(value: rhs) } +public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } + +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(value: rhs) } +public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } + +public prefix func ~(rhs: Expression) -> Expression { + return wrap(__FUNCTION__, rhs) +} + +// MARK: - Predicates + +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return infix("=", lhs, rhs) +} +public func ==(lhs: Expression, rhs: T?) -> Expression { + if let rhs = rhs { return lhs == Expression(value: rhs) } + return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) +} +public func ==(lhs: T?, rhs: Expression) -> Expression { + if let lhs = lhs { return Expression(value: lhs) == rhs } + return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) +} + +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func !=(lhs: Expression, rhs: T?) -> Expression { + if let rhs = rhs { return lhs != Expression(value: rhs) } + return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) +} +public func !=(lhs: T?, rhs: Expression) -> Expression { + if let lhs = lhs { return Expression(value: lhs) != rhs } + return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) +} + +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >(lhs: Expression, rhs: T) -> Expression { + return lhs > Expression(value: rhs) +} +public func >(lhs: T, rhs: Expression) -> Expression { + return Expression(value: lhs) > rhs +} + +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >=(lhs: Expression, rhs: T) -> Expression { + return lhs >= Expression(value: rhs) +} +public func >=(lhs: T, rhs: Expression) -> Expression { + return Expression(value: lhs) >= rhs +} + +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <(lhs: Expression, rhs: T) -> Expression { + return lhs < Expression(value: rhs) +} +public func <(lhs: T, rhs: Expression) -> Expression { + return Expression(value: lhs) < rhs +} + +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <=(lhs: Expression, rhs: T) -> Expression { + return lhs <= Expression(value: rhs) +} +public func <=(lhs: T, rhs: Expression) -> Expression { + return Expression(value: lhs) <= rhs +} + +public prefix func -(rhs: Expression) -> Expression { + return wrap(__FUNCTION__, rhs) +} + +public func ~=(lhs: I, rhs: Expression) -> Expression { + return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +} + +// MARK: Operators + +public func like(string: String, expression: Expression) -> Expression { + return infix("LIKE", expression, Expression(value: string)) +} + +public func glob(string: String, expression: Expression) -> Expression { + return infix("GLOB", expression, Expression(value: string)) +} + +public func match(string: String, expression: Expression) -> Expression { + return infix("MATCH", expression, Expression(value: string)) +} + +// MARK: Compound + +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return Expression("(\(lhs.SQL)) AND (\(rhs.SQL))", lhs.bindings + rhs.bindings) +} +public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } +public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } + +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return Expression("(\(lhs.SQL)) OR (\(rhs.SQL))", lhs.bindings + rhs.bindings) +} +public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } +public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } + +public prefix func !(rhs: Expression) -> Expression { + return wrap("NOT ", rhs) +} + +// MARK: - Core Functions + +public func abs(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func coalesce(expressions: Expression...) -> Expression { + return wrap(__FUNCTION__, join(", ", expressions.map { $0 })) +} + +public func ifnull(expression: Expression, defaultValue: T) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, defaultValue])) +} + +public func ??(expression: Expression, defaultValue: T) -> Expression { + return ifnull(expression, defaultValue) +} + +public func length(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func lower(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func ltrim(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func ltrim(expression: Expression, characters: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, characters])) +} + +public var random: Expression { + return wrap(__FUNCTION__, Expression<()>()) +} + +public func replace(expression: Expression, match: String, subtitute: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, match, subtitute])) +} + +public func round(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func round(expression: Expression, precision: Int) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, precision])) +} + +public func rtrim(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func rtrim(expression: Expression, characters: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, characters])) +} + +public func substr(expression: Expression, startIndex: Int) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, startIndex])) +} + +public func substr(expression: Expression, position: Int, length: Int) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, position, length])) +} + +public func substr(expression: Expression, subRange: Range) -> Expression { + return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) +} + +public func trim(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func trim(expression: Expression, characters: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, characters])) +} + +public func upper(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +// MARK: - Aggregate Functions + +public func count(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func count(star: Star) -> Expression { + return count(star(nil, nil)) +} + +public func max(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func min(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func average(expression: Expression) -> Expression { + return wrap("avg", expression) +} + +public func sum(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +public func total(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} + +// MARK: - Helper + +public typealias Star = (Expression?, Expression?) -> Expression<()> + +public func *(Expression?, Expression?) -> Expression<()> { + return Expression<()>("*") +} + +public func contains(values: [T?], column: Expression) -> Expression { + let templates = join(", ", [String](count: values.count, repeatedValue: "?")) + return infix("IN", column, Expression("(\(templates))", values.map { $0 })) +} + +// MARK: - Internal + +internal func join(separator: String, expressions: [Expressible]) -> Expression<()> { + var (SQL, bindings) = ([String](), [Value?]()) + for expressible in expressions { + let expression = expressible.expression + SQL.append(expression.SQL) + bindings.extend(expression.bindings) + } + return Expression(Swift.join(separator, SQL), bindings) +} + +private func wrap(function: String, expression: Expression) -> Expression { + return Expression("\(function)(\(expression.SQL))", expression.bindings) +} + +private func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { + return Expression("\(lhs.SQL) \(function) \(rhs.SQL)", lhs.bindings + rhs.bindings) +} diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index c3c48771..7c834618 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -22,7 +22,7 @@ // /// A dictionary mapping column names to values. -public typealias Values = [String: Datatype?] +public typealias Values = [String: Value?] /// A query object. Used to build SQL statements with a collection of chainable /// helper functions. @@ -33,7 +33,6 @@ public struct Query { internal init(_ database: Database, _ tableName: String) { self.database = database self.tableName = tableName - self.columnNames = ["*"] } // MARK: - Keywords @@ -64,213 +63,63 @@ public struct Query { } - private var columnNames: [String] - private var tableName: String - private var joins = [JoinType, String, String]() - private var conditions: String? - private var bindings = [Datatype?]() - private var groupByHaving: ([String], String?, [Datatype?])? - private var order = [String, SortDirection]() - private var limit: Int? - private var offset: Int? + private var columns: [Expressible] = [Expression<()>("*")] + internal var tableName: String + private var joins = [Expressible]() + private var filter: Expression? + private var group: Expressible? + private var order = [Expressible]() + private var limit: (to: Int, offset: Int?)? = nil - // MARK: - - - /// Sets a SELECT clause on the query. - /// - /// :param: columnNames A list of columns to retrieve. - /// - /// :returns: A query with the given SELECT clause applied. - public func select(columnNames: String...) -> Query { + public func select(columns: Expressible...) -> Query { var query = self - query.columnNames = columnNames + query.columns = columns return query } - /// Adds an INNER JOIN clause to the query. - /// - /// :param: tableName The table being joined. - /// - /// :param: constraint The condition in which the table is joined. - /// - /// :returns: A query with the given INNER JOIN clause applied. - public func join(tableName: String, on constraint: String) -> Query { - return join(.Inner, tableName, on: constraint) - } - - /// Adds a JOIN clause to the query. - /// - /// :param: type The JOIN operator used. - /// - /// :param: tableName The table being joined. - /// - /// :param: constraint The condition in which the table is joined. - /// - /// :returns: A query with the given JOIN clause applied. - public func join(type: JoinType, _ tableName: String, on constraint: String) -> Query { + public func select(star: Star) -> Query { var query = self - query.joins.append(type, tableName, constraint) + query.columns = [star(nil, nil)] return query } - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A dictionary of conditions where the keys map to - /// column names and the columns must equal the given - /// values. - /// - /// :returns: A query with the given condition applied. - public func filter(condition: [String: Datatype?]) -> Query { - var query = self - for (column, value) in condition { - if let value = value { - query = query.filter("\(column) = ?", value) - } else { - query = query.filter("\(column) IS NULL") - } - } - return query + public func join(table: Query, on: Expression) -> Query { + return join(.Inner, table, on: on) } - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A dictionary of conditions where the keys map to - /// column names and the columns must be in the lists of - /// given values. - /// - /// :returns: A query with the given condition applied. - public func filter(condition: [String: [Datatype?]]) -> Query { + public func join(type: JoinType, _ table: Query, on: Expression) -> Query { var query = self - for (column, values) in condition { - let templates = Swift.join(", ", [String](count: values.count, repeatedValue: "?")) - query = query.filter("\(column) IN (\(templates))", values) - } + let condition = table.filter.map { on && $0 } ?? on + let expression = Expression<()>("\(type.rawValue) JOIN \(table.tableName) ON \(condition.SQL)", condition.bindings) + query.joins.append(expression) return query } - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A dictionary of conditions where the keys map to - /// column names and the columns must be in the ranges of - /// given values. - /// - /// :returns: A query with the given condition applied. - public func filter(condition: [String: Range]) -> Query { - var query = self - for (column, value) in condition { - query = query.filter("\(column) BETWEEN ? AND ?", value.startIndex, value.endIndex) - } - return query - } - - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A string condition with optional "?"-parameterized - /// bindings. - /// - /// :param: bindings A list of parameters to bind to the WHERE clause. - /// - /// :returns: A query with the given condition applied. - public func filter(condition: String, _ bindings: Datatype?...) -> Query { - return filter(condition, bindings) - } - - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A string condition with optional "?"-parameterized - /// bindings. - /// - /// :param: bindings A list of parameters to bind to the WHERE clause. - /// - /// :returns: A query with the given condition applied. - public func filter(condition: String, _ bindings: [Datatype?]) -> Query { + public func filter(condition: Expression) -> Query { var query = self - if let conditions = query.conditions { - query.conditions = "\(conditions) AND \(condition)" - } else { - query.conditions = condition - } - query.bindings += bindings + query.filter = filter.map { $0 && condition } ?? condition return query } - /// Sets a GROUP BY clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: String...) -> Query { - return group(by, []) + public func group(by: Expressible...) -> Query { + return group(by) } - /// Sets a GROUP BY-HAVING clause on the query. - /// - /// :param: by A column to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :param: bindings A list of parameters to bind to the HAVING clause. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: String, having: String, _ bindings: Datatype?...) -> Query { - return group([by], having: having, bindings) + public func group(by: Expressible, having: Expression) -> Query { + return group([by], having: having) } - /// Sets a GROUP BY-HAVING clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :param: bindings A list of parameters to bind to the HAVING clause. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: [String], having: String, _ bindings: Datatype?...) -> Query { - return group(by, having: having, bindings) - } - - private func group(by: [String], having: String? = nil, _ bindings: [Datatype?]) -> Query { + public func group(by: [Expressible], having: Expression? = nil) -> Query { var query = self - query.groupByHaving = (by, having, bindings) + var group = SQLite.join(" ", [Expression<()>("GROUP BY"), SQLite.join(", ", by)]) + if let having = having { group = SQLite.join(" ", [group, Expression<()>("HAVING"), having]) } + query.group = group return query } - /// Adds sorting instructions to the query’s ORDER BY clause. - /// - /// :param: by A list of columns to order by with an ascending sort. - /// - /// :returns: A query with the given sorting instructions applied. - public func order(by: String...) -> Query { - return order(by) - } - - private func order(by: [String]) -> Query { - return order(by.map { ($0, .Asc) }) - } - - /// Adds sorting instructions to the query’s ORDER BY clause. - /// - /// :param: by A column to order by. - /// - /// :param: direction The direction in which the column is ordered. - /// - /// :returns: A query with the given sorting instructions applied. - public func order(by: String, _ direction: SortDirection) -> Query { - return order([(by, direction)]) - } - - /// Adds sorting instructions to the query’s ORDER BY clause. - /// - /// :param: by An ordered list of columns and directions to sort them by. - /// - /// :returns: A query with the given sorting instructions applied. - public func order(by: (String, SortDirection)...) -> Query { - return order(by) - } - - private func order(by: [(String, SortDirection)]) -> Query { + public func order(by: Expressible...) -> Query { var query = self - query.order += by + query.order = by return query } @@ -297,91 +146,83 @@ public struct Query { // prevent limit(nil, offset: 5) private func limit(#to: Int?, offset: Int? = nil) -> Query { var query = self - (query.limit, query.offset) = (to, offset) + if let to = to { + query.limit = (to, offset) + } else { + query.limit = nil + } return query } // MARK: - Compiling Statements private var selectStatement: Statement { - let columnNames = Swift.join(", ", self.columnNames) - - var parts = ["SELECT \(columnNames) FROM \(tableName)"] - joinClause.map(parts.append) - whereClause.map(parts.append) - groupClause.map(parts.append) - orderClause.map(parts.append) - limitClause.map(parts.append) - - var bindings = self.bindings - if let (_, _, values) = groupByHaving { bindings += values } - - return database.prepare(Swift.join(" ", parts), bindings) + var expressions = [selectClause] + joinClause.map(expressions.append) + whereClause.map(expressions.append) + group.map(expressions.append) + orderClause.map(expressions.append) + limitClause.map(expressions.append) + let expression = SQLite.join(" ", expressions) + return database.prepare(expression.SQL, expression.bindings) } - private func insertStatement(values: Values) -> Statement { - var (parts, bindings) = (["INSERT INTO \(tableName)"], self.bindings) - let valuesClause = Swift.join(", ", map(values) { columnName, value in - bindings.append(value) - return columnName - }) - let templates = Swift.join(", ", [String](count: values.count, repeatedValue: "?")) - parts.append("(\(valuesClause)) VALUES (\(templates))") - return database.prepare(Swift.join(" ", parts), bindings) + private func insertStatement(values: [(Expressible, Expressible)]) -> Statement { + var expressions: [Expressible] = [Expression<()>("INSERT INTO \(tableName)")] + let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) + expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) + whereClause.map(expressions.append) + let expression = SQLite.join(" ", expressions) + return database.prepare(expression.SQL, expression.bindings) } - private func updateStatement(values: Values) -> Statement { - var (parts, bindings) = (["UPDATE \(tableName)"], [Datatype?]()) - let valuesClause = Swift.join(", ", map(values) { columnName, value in - bindings.append(value) - return "\(columnName) = ?" - }) - parts.append("SET \(valuesClause)") - whereClause.map(parts.append) - return database.prepare(Swift.join(" ", parts), bindings + self.bindings) + private func updateStatement(values: [(Expressible, Expressible)]) -> Statement { + var expressions: [Expressible] = [Expression<()>("UPDATE \(tableName) SET")] + expressions.append(SQLite.join(", ", values.map { SQLite.join(" = ", [$0, $1]) })) + whereClause.map(expressions.append) + let expression = SQLite.join(" ", expressions) + return database.prepare(expression.SQL, expression.bindings) } private var deleteStatement: Statement { - var parts = ["DELETE FROM \(tableName)"] - whereClause.map(parts.append) - return database.prepare(Swift.join(" ", parts), bindings) + var expressions: [Expressible] = [Expression<()>("DELETE FROM \(tableName)")] + whereClause.map(expressions.append) + let expression = SQLite.join(" ", expressions) + return database.prepare(expression.SQL, expression.bindings) } // MARK: - - private var joinClause: String? { - if joins.count == 0 { return nil } - let clause = joins.map { "\($0.rawValue) JOIN \($1) ON \($2)" } - return Swift.join(" ", clause) + private var selectClause: Expressible { + let select = SQLite.join(", ", columns) + return SQLite.join(" ", [Expression<()>("SELECT"), select, Expression<()>("FROM \(tableName)")]) } - private var whereClause: String? { - if let conditions = conditions { return "WHERE \(conditions)" } - return nil + private var joinClause: Expressible? { + if joins.count == 0 { return nil } + return SQLite.join(" ", joins) } - private var groupClause: String? { - if let (groupBy, having, _) = groupByHaving { - let groups = Swift.join(", ", groupBy) - var clause = ["GROUP BY \(groups)"] - having.map { clause.append("HAVING \($0)") } - return Swift.join(" ", clause) + private var whereClause: Expressible? { + if let filter = filter { + return Expression<()>("WHERE \(filter.SQL)", filter.bindings) } return nil } - private var orderClause: String? { + private var orderClause: Expressible? { if order.count == 0 { return nil } - let mapped = order.map { "\($0.0) \($0.1.rawValue)" } - let joined = Swift.join(", ", mapped) - return "ORDER BY \(joined)" + let clause = SQLite.join(", ", order) + return Expression<()>("ORDER BY \(clause.SQL)", clause.bindings) } - private var limitClause: String? { - if let to = limit { - var clause = ["LIMIT \(to)"] - offset.map { clause.append("OFFSET \($0)") } - return Swift.join(" ", clause) + private var limitClause: Expressible? { + if let limit = limit { + var clause = Expression<()>("LIMIT \(limit.to)") + if let offset = limit.offset { + clause = SQLite.join(" ", [clause, Expression<()>("OFFSET \(offset)")]) + } + return clause } return nil } @@ -391,73 +232,93 @@ public struct Query { /// The first result (or nil if the query has no results). public var first: Values? { return limit(1).generate().next() } - /// The last result (or nil if the query has no results). - public var last: Values? { return reverse(self).first } - /// Returns true if the query has no results. public var isEmpty: Bool { return first == nil } // MARK: - Modifying - /// Runs an INSERT statement with the given row of values. + public final class ValuesBuilder { + + private let query: Query + + private var values = [(Expressible, Expressible)]() + + private init(_ query: Query, _ builder: ValuesBuilder -> ()) { + self.query = query + builder(self) + } + + public func set(column: Expression, _ value: T?) { + values.append((column, Expression<()>(value: value))) + } + + } + + /// Runs an INSERT statement against the query. /// - /// :param: values A dictionary of column names to values. + /// :param: builder A block with a ValuesBuilder, used for aggregating + /// values for the INSERT. /// /// :returns: The statement. - public func insert(values: Values) -> Statement { return insert(values).statement } + public func insert(builder: ValuesBuilder -> ()) -> Statement { return insert(builder).statement } - /// Runs an INSERT statement with the given row of values. + /// Runs an INSERT statement against the query. /// - /// :param: values A dictionary of column names to values. + /// :param: builder A block with a ValuesBuilder, used for aggregating + /// values for the INSERT. /// /// :returns: The row ID. - public func insert(values: Values) -> Int? { return insert(values).ID } + public func insert(builder: ValuesBuilder -> ()) -> Int? { return insert(builder).ID } - /// Runs an INSERT statement with the given row of values. + /// Runs an INSERT statement against the query. /// - /// :param: values A dictionary of column names to values. + /// :param: builder A block with a ValuesBuilder, used for aggregating + /// values for the INSERT. /// /// :returns: The row ID and statement. - public func insert(values: Values) -> (ID: Int?, statement: Statement) { - let statement = insertStatement(values).run() + public func insert(builder: ValuesBuilder -> ()) -> (ID: Int?, statement: Statement) { + let statement = insertStatement(ValuesBuilder(self, builder).values).run() return (statement.failed ? nil : database.lastID, statement) } - /// Runs an UPDATE statement against the query with the given values. + /// Runs an UPDATE statement against the query. /// - /// :param: values A dictionary of column names to values. + /// :param: builder A block with a ValuesBuilder, used for aggregating + /// values for the UPDATE. /// /// :returns: The statement. - public func update(values: Values) -> Statement { return update(values).statement } + public func update(builder: ValuesBuilder -> ()) -> Statement { return update(builder).statement } - /// Runs an UPDATE statement against the query with the given values. + /// Runs an UPDATE statement against the query. /// - /// :param: values A dictionary of column names to values. + /// :param: builder A block with a ValuesBuilder, used for aggregating + /// values for the UPDATE. /// /// :returns: The number of updated rows. - public func update(values: Values) -> Int { return update(values).changes } + public func update(builder: ValuesBuilder -> ()) -> Int { return update(builder).changes } - /// Runs an UPDATE statement against the query with the given values. + /// Runs an UPDATE statement against the query. /// - /// :param: values A dictionary of column names to values. + /// :param: builder A block with a ValuesBuilder, used for aggregating + /// values for the UPDATE. /// /// :returns: The number of updated rows and statement. - public func update(values: Values) -> (changes: Int, statement: Statement) { - let statement = updateStatement(values).run() + public func update(builder: ValuesBuilder -> ()) -> (changes: Int, statement: Statement) { + let statement = updateStatement(ValuesBuilder(self, builder).values).run() return (statement.failed ? 0 : database.lastChanges ?? 0, statement) } - /// Runs an DELETE statement against the query. + /// Runs a DELETE statement against the query. /// /// :returns: The statement. public func delete() -> Statement { return delete().statement } - /// Runs an DELETE statement against the query. + /// Runs a DELETE statement against the query. /// /// :returns: The number of deleted rows. public func delete() -> Int { return delete().changes } - /// Runs an DELETE statement against the query. + /// Runs a DELETE statement against the query. /// /// :returns: The number of deleted rows and statement. public func delete() -> (changes: Int, statement: Statement) { @@ -468,63 +329,64 @@ public struct Query { // MARK: - Aggregate Functions /// Runs count(*) against the query and returns it. - public var count: Int { return count("*") } + public var count: Int { return count(Expression<()>("*")) } /// Runs count() against the query. /// - /// :param: columnName The column used for the calculation. + /// :param: column The column used for the calculation. /// /// :returns: The number of rows matching the given column. - public func count(columnName: String) -> Int { - return calculate("count", columnName) as Int + public func count(column: Expression) -> Int { + return calculate(SQLite.count(column))! } /// Runs max() against the query. /// - /// :param: columnName The column used for the calculation. + /// :param: column The column used for the calculation. /// /// :returns: The largest value of the given column. - public func max(columnName: String) -> Datatype? { - return calculate("max", columnName) + public func max(column: Expression) -> T? { + return calculate(SQLite.max(column)) } /// Runs min() against the query. /// - /// :param: columnName The column used for the calculation. + /// :param: column The column used for the calculation. /// /// :returns: The smallest value of the given column. - public func min(columnName: String) -> Datatype? { - return calculate("min", columnName) + public func min(column: Expression) -> T? { + return calculate(SQLite.min(column)) } /// Runs avg() against the query. /// - /// :param: columnName The column used for the calculation. + /// :param: column The column used for the calculation. + /// /// :returns: The average value of the given column. - public func average(columnName: String) -> Double? { - return calculate("avg", columnName) as? Double + public func average(column: Expression) -> Double? { + return calculate(SQLite.average(column)) } /// Runs sum() against the query. /// - /// :param: columnName The column used for the calculation. + /// :param: column The column used for the calculation. /// /// :returns: The sum of the given column’s values. - public func sum(columnName: String) -> Datatype? { - return calculate("sum", columnName) + public func sum(column: Expression) -> T? { + return calculate(SQLite.sum(column)) } /// Runs total() against the query. /// - /// :param: columnName The column used for the calculation. + /// :param: column The column used for the calculation. /// /// :returns: The total of the given column’s values. - public func total(columnName: String) -> Double { - return calculate("total", columnName) as Double + public func total(column: Expression) -> Double { + return calculate(SQLite.total(column))! } - private func calculate(function: String, _ columnName: String) -> Datatype? { - return select("\(function)(\(columnName))").selectStatement.scalar() + private func calculate(expression: Expression) -> U? { + return select(expression).selectStatement.scalar() as? U } } @@ -552,15 +414,10 @@ public struct QueryGenerator: GeneratorType { } -/// Reverses the order of a given query. -/// -/// :param: query A query object to reverse. -/// -/// :returns: A reversed query. -public func reverse(query: Query) -> Query { - if query.order.count == 0 { return query.order("\(query.tableName).ROWID", .Desc) } - - var reversed = query - reversed.order = query.order.map { ($0, $1 == .Asc ? .Desc : .Asc) } - return reversed +extension Database { + + public subscript(tableName: String) -> Query { + return Query(self, tableName) + } + } diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index d6e5da23..d6f57bf9 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -47,7 +47,7 @@ public final class Statement { /// :param: values A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func bind(values: Datatype?...) -> Statement { + public func bind(values: Value?...) -> Statement { return bind(values) } @@ -56,7 +56,7 @@ public final class Statement { /// :param: values A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func bind(values: [Datatype?]) -> Statement { + public func bind(values: [Value?]) -> Statement { if values.isEmpty { return self } reset() assert(values.count == Int(sqlite3_bind_parameter_count(handle)), "\(Int(sqlite3_bind_parameter_count(handle))) values expected, \(values.count) passed") @@ -70,7 +70,7 @@ public final class Statement { /// statement. /// /// :returns: The statement object (useful for chaining). - public func bind(values: [String: Datatype?]) -> Statement { + public func bind(values: [String: Value?]) -> Statement { reset() for (name, value) in values { let idx = sqlite3_bind_parameter_index(handle, name) @@ -96,9 +96,9 @@ public final class Statement { try(sqlite3_bind_text(handle, Int32(idx), text, -1, SQLITE_TRANSIENT)) } - private func bind(value: Datatype?, atIndex idx: Int) { - if let datatype = value { - datatype.bindTo(self, atIndex: idx) + private func bind(value: Value?, atIndex idx: Int) { + if let Value = value { + Value.bindTo(self, atIndex: idx) } else { try(sqlite3_bind_null(handle, Int32(idx))) } @@ -109,7 +109,7 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func run(bindings: Datatype?...) -> Statement { + public func run(bindings: Value?...) -> Statement { if !bindings.isEmpty { return run(bindings) } reset(clearBindings: false) for _ in self {} @@ -119,7 +119,7 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func run(bindings: [Datatype?]) -> Statement { + public func run(bindings: [Value?]) -> Statement { return bind(bindings).run() } @@ -127,7 +127,7 @@ public final class Statement { /// statement. /// /// :returns: The statement object (useful for chaining). - public func run(bindings: [String: Datatype?]) -> Statement { + public func run(bindings: [String: Value?]) -> Statement { return bind(bindings).run() } @@ -136,10 +136,10 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(bindings: Datatype?...) -> Datatype? { + public func scalar(bindings: Value?...) -> Value? { if !bindings.isEmpty { return scalar(bindings) } reset(clearBindings: false) - let value: Datatype? = next()?[0] + let value: Value? = next()?[0] for _ in self {} return value } @@ -147,7 +147,7 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(bindings: [Datatype?]) -> Datatype? { + public func scalar(bindings: [Value?]) -> Value? { return bind(bindings).scalar() } @@ -155,7 +155,7 @@ public final class Statement { /// statement. /// /// :returns: The first value of the first row returned. - public func scalar(bindings: [String: Datatype?]) -> Datatype? { + public func scalar(bindings: [String: Value?]) -> Value? { return bind(bindings).scalar() } @@ -200,7 +200,7 @@ extension Statement: SequenceType { extension Statement: GeneratorType { /// A single row. - public typealias Element = [Datatype?] + public typealias Element = [Value?] /// :returns: The next row from the result set (or nil). public func next() -> Element? { @@ -236,9 +236,9 @@ extension Statement: GeneratorType { } /// :returns: A dictionary of column name to row value. - public var values: [String: Datatype?]? { + public var values: [String: Value?]? { if let row = row { - var values = [String: Datatype?]() + var values = [String: Value?]() for idx in 0.. Date: Tue, 21 Oct 2014 09:50:07 -0700 Subject: [PATCH 0002/1046] Update playground with type-safe query builder Signed-off-by: Stephen Celis --- .../Documentation/fragment-10.html | 2 +- .../Documentation/fragment-11.html | 5 +---- .../Documentation/fragment-12.html | 5 ++++- .../Documentation/fragment-13.html | 5 +---- .../Documentation/fragment-14.html | 17 ++++------------- .../Documentation/fragment-15.html | 14 +++++++++++++- .../Documentation/fragment-16.html | 2 +- .../Documentation/fragment-17.html | 17 +---------------- .../Documentation/fragment-18.html | 17 ++++++++++++++++- .../Documentation/fragment-19.html | 5 +---- .../Documentation/fragment-20.html | 5 ++--- .../Documentation/fragment-21.html | 13 +++++++++++++ SQLite.playground/Documentation/fragment-9.html | 2 +- SQLite.playground/contents.xcplayground | 2 ++ SQLite.playground/section-20.swift | 6 +++++- SQLite.playground/section-22.swift | 4 +--- SQLite.playground/section-24.swift | 12 ++++++++---- SQLite.playground/section-26.swift | 8 ++++---- SQLite.playground/section-28.swift | 13 ++++--------- SQLite.playground/section-30.swift | 10 +++++++++- SQLite.playground/section-32.swift | 3 +-- SQLite.playground/section-34.swift | 8 ++------ SQLite.playground/section-36.swift | 8 +++++--- SQLite.playground/section-38.swift | 6 ++++-- SQLite.playground/section-40.swift | 3 ++- SQLite.playground/section-42.swift | 2 ++ 26 files changed, 108 insertions(+), 86 deletions(-) create mode 100644 SQLite.playground/Documentation/fragment-21.html create mode 100644 SQLite.playground/section-42.swift diff --git a/SQLite.playground/Documentation/fragment-10.html b/SQLite.playground/Documentation/fragment-10.html index 1370f6ee..449e18b4 100644 --- a/SQLite.playground/Documentation/fragment-10.html +++ b/SQLite.playground/Documentation/fragment-10.html @@ -4,6 +4,6 @@

- From here, we can build a variety of queries. For example, we can build and run an INSERT statement by passing a dictionary of values to the query’s insert function. Let’s add a few new rows this way. + The query-building interface is provided via the Query struct. We can access this interface by subscripting our database connection with a table name.

diff --git a/SQLite.playground/Documentation/fragment-11.html b/SQLite.playground/Documentation/fragment-11.html index 7d2267ab..f9196504 100644 --- a/SQLite.playground/Documentation/fragment-11.html +++ b/SQLite.playground/Documentation/fragment-11.html @@ -4,9 +4,6 @@

- Barely any room for syntax errors! -

-

- The insert function can return an ID (which will be nil in the case of failure) and the just-run statement. It can also return a Statement object directly, making it easy to run in a transaction. + From here, we can build a variety of queries. For example, we can build and run an INSERT statement by calling the query’s insert function. Let’s add a few new rows this way.

diff --git a/SQLite.playground/Documentation/fragment-12.html b/SQLite.playground/Documentation/fragment-12.html index 0341e633..4b0633fe 100644 --- a/SQLite.playground/Documentation/fragment-12.html +++ b/SQLite.playground/Documentation/fragment-12.html @@ -4,6 +4,9 @@

- Query objects can also build SELECT statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement. + No room for syntax errors! Try changing an input to the wrong type and see what happens. +

+

+ The insert function can return an ID (which will be nil in the case of failure) and the just-run statement. It can also return a Statement object directly, making it easy to run in a transaction.

diff --git a/SQLite.playground/Documentation/fragment-13.html b/SQLite.playground/Documentation/fragment-13.html index 1cbf1f92..0341e633 100644 --- a/SQLite.playground/Documentation/fragment-13.html +++ b/SQLite.playground/Documentation/fragment-13.html @@ -4,9 +4,6 @@

- You may notice that iteration works a little differently here. Rather than arrays of rows, we are given dictionaries of column names to row values. This gives us a little more powerful of a mapping to work with and pass around. -

-

- Queries can be used and reused, and can quickly return rows, counts and other aggregate values. + Query objects can also build SELECT statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement.

diff --git a/SQLite.playground/Documentation/fragment-14.html b/SQLite.playground/Documentation/fragment-14.html index 75e5e396..1cbf1f92 100644 --- a/SQLite.playground/Documentation/fragment-14.html +++ b/SQLite.playground/Documentation/fragment-14.html @@ -3,19 +3,10 @@
-

- Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset. + You may notice that iteration works a little differently here. Rather than arrays of rows, we are given dictionaries of column names to row values. This gives us a little more powerful of a mapping to work with and pass around. +

+

+ Queries can be used and reused, and can quickly return rows, counts and other aggregate values.

diff --git a/SQLite.playground/Documentation/fragment-15.html b/SQLite.playground/Documentation/fragment-15.html index b19d01d4..850f2bf8 100644 --- a/SQLite.playground/Documentation/fragment-15.html +++ b/SQLite.playground/Documentation/fragment-15.html @@ -3,7 +3,19 @@
+

- Filtered queries will in turn filter their aggregate functions. + Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset.

diff --git a/SQLite.playground/Documentation/fragment-16.html b/SQLite.playground/Documentation/fragment-16.html index 177081a3..b19d01d4 100644 --- a/SQLite.playground/Documentation/fragment-16.html +++ b/SQLite.playground/Documentation/fragment-16.html @@ -4,6 +4,6 @@

- Alongside filter, we can use the select, join, group, order, and limit functions to compose rich queries with relative safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows. + Filtered queries will in turn filter their aggregate functions.

diff --git a/SQLite.playground/Documentation/fragment-17.html b/SQLite.playground/Documentation/fragment-17.html index 2ba198d7..9b4e1bde 100644 --- a/SQLite.playground/Documentation/fragment-17.html +++ b/SQLite.playground/Documentation/fragment-17.html @@ -3,22 +3,7 @@
-

- We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages. + Alongside filter, we can use the select, join, group, order, and limit functions to compose rich queries with safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows.

diff --git a/SQLite.playground/Documentation/fragment-18.html b/SQLite.playground/Documentation/fragment-18.html index c6724957..967cf580 100644 --- a/SQLite.playground/Documentation/fragment-18.html +++ b/SQLite.playground/Documentation/fragment-18.html @@ -3,7 +3,22 @@
+

- Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s update interface to (temporarily) revoke their privileges while we wait for them to update their profiles. + We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages.

diff --git a/SQLite.playground/Documentation/fragment-19.html b/SQLite.playground/Documentation/fragment-19.html index c3150e7b..c6724957 100644 --- a/SQLite.playground/Documentation/fragment-19.html +++ b/SQLite.playground/Documentation/fragment-19.html @@ -4,9 +4,6 @@

- If we ever need to remove rows from our database, we can use the delete function, which will be scoped to a query’s filters. Be careful! You may just want to archive the records, instead. -

-

- We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, carefully scope a query to match her and delete her record. + Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s update interface to (temporarily) revoke their privileges while we wait for them to update their profiles.

diff --git a/SQLite.playground/Documentation/fragment-20.html b/SQLite.playground/Documentation/fragment-20.html index bea27da9..c3150e7b 100644 --- a/SQLite.playground/Documentation/fragment-20.html +++ b/SQLite.playground/Documentation/fragment-20.html @@ -4,10 +4,9 @@

- And that’s that. + If we ever need to remove rows from our database, we can use the delete function, which will be scoped to a query’s filters. Be careful! You may just want to archive the records, instead.

-

& More…

- We’ve only explored the surface to SQLite.swift. Dive into the code to discover more! + We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, carefully scope a query to match her and delete her record.

diff --git a/SQLite.playground/Documentation/fragment-21.html b/SQLite.playground/Documentation/fragment-21.html new file mode 100644 index 00000000..bea27da9 --- /dev/null +++ b/SQLite.playground/Documentation/fragment-21.html @@ -0,0 +1,13 @@ + + + + +
+

+ And that’s that. +

+

& More…

+

+ We’ve only explored the surface to SQLite.swift. Dive into the code to discover more! +

+
diff --git a/SQLite.playground/Documentation/fragment-9.html b/SQLite.playground/Documentation/fragment-9.html index 56a46575..2f90332f 100644 --- a/SQLite.playground/Documentation/fragment-9.html +++ b/SQLite.playground/Documentation/fragment-9.html @@ -14,6 +14,6 @@

Experiment

Query Building

- In order to both minimize the risk of SQL syntax errors and maximize our ability to quickly access data, SQLite.swift exposes a query-building interface via the Query struct. We can access this interface by subscripting our database connection with a table name. + SQLite.swift provides a powerful, type-safe query builder. With only a small amount of boilerplate to map our columns to types, we can ensure the queries we build are valid upon compilation.

diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index 22659c1e..e16a107d 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -42,6 +42,8 @@ + + \ No newline at end of file diff --git a/SQLite.playground/section-20.swift b/SQLite.playground/section-20.swift index 624a7098..4357de4a 100644 --- a/SQLite.playground/section-20.swift +++ b/SQLite.playground/section-20.swift @@ -1 +1,5 @@ -let users = db["users"] +let id = Expression("id") +let email = Expression("email") +let age = Expression("age") +let admin = Expression("admin") +let manager_id = Expression("manager_id") diff --git a/SQLite.playground/section-22.swift b/SQLite.playground/section-22.swift index 08b03fa3..624a7098 100644 --- a/SQLite.playground/section-22.swift +++ b/SQLite.playground/section-22.swift @@ -1,3 +1 @@ -users.insert(["email": "giles@acme.com", "age": 42, "admin": true]).ID -users.insert(["email": "haley@acme.com", "age": 30, "admin": true]).ID -users.insert(["email": "inigo@acme.com", "age": 24, "admin": false]).ID +let users = db["users"] diff --git a/SQLite.playground/section-24.swift b/SQLite.playground/section-24.swift index e5be28c9..f1e84a81 100644 --- a/SQLite.playground/section-24.swift +++ b/SQLite.playground/section-24.swift @@ -1,4 +1,8 @@ -db.transaction( - users.insert(["email": "julie@acme.com"]), - users.insert(["email": "kelly@acme.com", "manager_id": db.lastID]) -) +users.insert { u in + u.set(email, "giles@acme.com") + u.set(age, 42) + u.set(admin, true) +}.ID + +users.insert { $0.set(email, "haley@acme.com"); $0.set(age, 30) }.ID +users.insert { $0.set(email, "inigo@acme.com"); $0.set(age, 24) }.ID diff --git a/SQLite.playground/section-26.swift b/SQLite.playground/section-26.swift index 104bdcb3..656a7227 100644 --- a/SQLite.playground/section-26.swift +++ b/SQLite.playground/section-26.swift @@ -1,4 +1,4 @@ -// SELECT * FROM users -for user in users { - println(user["email"]) -} +db.transaction( + users.insert { $0.set(email, "julie@acme.com") }, + users.insert { $0.set(email, "kelly@acme.com"); $0.set(manager_id, db.lastID) } +) diff --git a/SQLite.playground/section-28.swift b/SQLite.playground/section-28.swift index 273a695d..104bdcb3 100644 --- a/SQLite.playground/section-28.swift +++ b/SQLite.playground/section-28.swift @@ -1,9 +1,4 @@ -// SELECT * FROM users LIMIT 1 -users.first - -// SELECT count(*) FROM users -users.count - -users.min("age") -users.max("age") -users.average("age") +// SELECT * FROM users +for user in users { + println(user["email"]) +} diff --git a/SQLite.playground/section-30.swift b/SQLite.playground/section-30.swift index 113d778a..618e1e08 100644 --- a/SQLite.playground/section-30.swift +++ b/SQLite.playground/section-30.swift @@ -1 +1,9 @@ -let admins = users.filter(["admin": true]) +// SELECT * FROM users LIMIT 1 +users.first + +// SELECT count(*) FROM users +users.count + +users.min(age) +users.max(age) +users.average(age) diff --git a/SQLite.playground/section-32.swift b/SQLite.playground/section-32.swift index 86b2bf20..d6d1597c 100644 --- a/SQLite.playground/section-32.swift +++ b/SQLite.playground/section-32.swift @@ -1,2 +1 @@ -// SELECT count(*) FROM users WHERE admin = 1 -admins.count +let admins = users.filter(admin) diff --git a/SQLite.playground/section-34.swift b/SQLite.playground/section-34.swift index 9f4c2743..e6e582f0 100644 --- a/SQLite.playground/section-34.swift +++ b/SQLite.playground/section-34.swift @@ -1,6 +1,2 @@ -let ordered = admins.order("email", "age").limit(3) - -// SELECT * FROM users WHERE admin = 1 ORDER BY email ASC, age ASC LIMIT 3 -for admin in ordered { - println(admin) -} +// SELECT count(*) FROM users WHERE admin +admins.count diff --git a/SQLite.playground/section-36.swift b/SQLite.playground/section-36.swift index 469e6ce6..587a6693 100644 --- a/SQLite.playground/section-36.swift +++ b/SQLite.playground/section-36.swift @@ -1,4 +1,6 @@ -let agelessAdmins = admins.filter(["age": nil]) +let ordered = admins.order(email.asc, age.asc).limit(3) -// SELECT count(*) FROM users WHERE admin = 1 AND age IS NULL -agelessAdmins.count +// SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3 +for admin in ordered { + println(admin) +} diff --git a/SQLite.playground/section-38.swift b/SQLite.playground/section-38.swift index fafcc454..90d8900d 100644 --- a/SQLite.playground/section-38.swift +++ b/SQLite.playground/section-38.swift @@ -1,2 +1,4 @@ -// UPDATE users SET admin = 0 WHERE admin = 1 AND age IS NULL -agelessAdmins.update(["admin": false]).changes +let agelessAdmins = admins.filter(age == nil) + +// SELECT count(*) FROM users WHERE admin AND age IS NULL +agelessAdmins.count diff --git a/SQLite.playground/section-40.swift b/SQLite.playground/section-40.swift index 24b77750..555ddb03 100644 --- a/SQLite.playground/section-40.swift +++ b/SQLite.playground/section-40.swift @@ -1 +1,2 @@ -users.filter(["email": "alice@acme.com"]).delete().changes +// UPDATE users SET admin = 0 WHERE admin AND age IS NULL +agelessAdmins.update { $0.set(admin, false) }.changes diff --git a/SQLite.playground/section-42.swift b/SQLite.playground/section-42.swift new file mode 100644 index 00000000..db4ce0e2 --- /dev/null +++ b/SQLite.playground/section-42.swift @@ -0,0 +1,2 @@ +// DELETE FROM users WHERE email = 'alice@acme.com' +users.filter(email == "alice@acme.com").delete().changes From a0db7e624d32e088f84518b5ca7b12674a0d78d6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 21 Oct 2014 10:03:44 -0700 Subject: [PATCH 0003/1046] Update README with basic, type-safe query-building example Signed-off-by: Stephen Celis --- README.md | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 51be4759..255d1a15 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A pure [Swift][1.1] framework wrapping [SQLite3][1.2]. ## Features - A lightweight, uncomplicated query and parameter binding interface - - A flexible, chainable query builder + - A flexible, chainable, type-safe query builder - Safe, automatically-typed data access - Transactions with implicit commit/rollback - Developer-friendly error handling and debugging @@ -64,30 +64,32 @@ db.transaction( ) ``` -SQLite.swift also exposes a powerful query building interface. +SQLite.swift also exposes a powerful, type-safe query building interface. ``` swift -let products = db["products"] +let users = db["users"] +let email = Expression("email") +let admin = Expression("admin") +let age = Expression("age") -let available = products.filter("quantity > ?", 0).order("name").limit(5) -for product in available { - println(product["name"]) -} -// SELECT * FROM products WHERE quantity > 0 ORDER BY name LIMIT 5 +for user in users.filter(admin && age >= 30).order(age.desc) { /* ... */ } +// SELECT * FROM users WHERE (admin) AND (age >= 30) ORDER BY age DESC -products.count // SELECT count(*) FROM products -products.average("price") // SELECT avg(price) FROM products +for user in users.group(age, having: count(age) == 1) { /* ... */ } +// SELECT * FROM users GROUP BY age HAVING count(age) = 1 -if let id = products.insert(["name": "Sprocket", "price": 19.99]) { - println("inserted \(id)") -} -// INSERT INTO products (name, price) VALUES ('Sprocket', 19.99) +users.count +// SELECT count(*) FROM users + +users.filter(admin).average(age) +// SELECT average(age) FROM users WHERE admin -let updates: Int = products.filter(["id": id]).update(["quantity": 20]) -// UPDATE products SET quantity = 20 WHERE id = 42 +if let id = users.insert { u in u.set(email, "fiona@example.com") } { /* ... */ } +// INSERT INTO users (email) VALUES ('fiona@example.com') -let deletes: Int = products.filter(["quantity": 0]).delete() -// DELETE FROM products WHERE quantity = 0 +let ageless = users.filter(admin && age == nil) +let updates: Int = ageless.update { u in u.set(admin, false) } +// UPDATE users SET admin = 0 WHERE (admin) AND (age IS NULL) ``` From 26bfbdffaa4cd752ce5a9dee27487d335eb1f7f0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 25 Oct 2014 09:46:28 -0700 Subject: [PATCH 0004/1046] Improved INSERT/UPDATE syntax E.g., users.insert(name <- "Alice", admin <- true) I know, I know, this goes beyond operator overloading into custom operator territory, but hear me out! You don't NEED to use the operator. I think it works particularly well here, but if you protest: users.insert(set(name, "Alice"), set(admin, true)) In any case, this is an improvement on the existing state of things. A block-using builder just to set a bunch of values is a cumbersome affair. We needn't use blocks where we can avoid it. Suggested by Thomas Denney . Signed-off-by: Stephen Celis --- README.md | 4 +- SQLite Common Tests/QueryTests.swift | 21 ++--- SQLite Common/Query.swift | 81 ++++++++++--------- SQLite.playground/section-24.swift | 11 +-- SQLite.playground/section-26.swift | 4 +- SQLite.playground/section-40.swift | 2 +- .../xcshareddata/SQLite.xccheckout | 41 ++++++++++ 7 files changed, 97 insertions(+), 67 deletions(-) create mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout diff --git a/README.md b/README.md index 255d1a15..dc25a7b0 100644 --- a/README.md +++ b/README.md @@ -84,11 +84,11 @@ users.count users.filter(admin).average(age) // SELECT average(age) FROM users WHERE admin -if let id = users.insert { u in u.set(email, "fiona@example.com") } { /* ... */ } +if let id = users.insert(email <- "fiona@example.com") { /* ... */ } // INSERT INTO users (email) VALUES ('fiona@example.com') let ageless = users.filter(admin && age == nil) -let updates: Int = ageless.update { u in u.set(admin, false) } +let updates: Int = ageless.update(admin <- false) // UPDATE users SET admin = 0 WHERE (admin) AND (age IS NULL) ``` diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 9a67fa52..f35c6ec3 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -243,31 +243,20 @@ class QueryTests: XCTestCase { func test_insert_insertsRows() { let SQL = "INSERT INTO users (email, age) VALUES ('alice@example.com', 30)" + ExpectExecutions(db, [SQL: 1]) { _ in - XCTAssertEqual(1, self.users.insert { u in - u.set(self.email, "alice@example.com") - u.set(self.age, 30) - }.ID!) + XCTAssertEqual(1, self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID!) } - XCTAssert(users.insert { u in - u.set(self.email, "alice@example.com") - u.set(self.age, 30) - }.ID == nil) + XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID == nil) } func test_update_updatesRows() { InsertUsers(db, "alice", "betsy") InsertUser(db, "dolly", admin: true) - XCTAssertEqual(2, users.filter(!admin).update { u in - u.set(self.age, 30) - u.set(self.admin, true) - }.changes) - XCTAssertEqual(0, users.filter(!admin).update { u in - u.set(self.age, 30) - u.set(self.admin, true) - }.changes) + XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes) + XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes) } func test_delete_deletesRows() { diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 7c834618..45bc4a2e 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -167,7 +167,7 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - private func insertStatement(values: [(Expressible, Expressible)]) -> Statement { + private func insertStatement(values: [ValueSetter]) -> Statement { var expressions: [Expressible] = [Expression<()>("INSERT INTO \(tableName)")] let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) @@ -176,7 +176,7 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - private func updateStatement(values: [(Expressible, Expressible)]) -> Statement { + private func updateStatement(values: [ValueSetter]) -> Statement { var expressions: [Expressible] = [Expression<()>("UPDATE \(tableName) SET")] expressions.append(SQLite.join(", ", values.map { SQLite.join(" = ", [$0, $1]) })) whereClause.map(expressions.append) @@ -237,74 +237,61 @@ public struct Query { // MARK: - Modifying - public final class ValuesBuilder { - - private let query: Query - - private var values = [(Expressible, Expressible)]() - - private init(_ query: Query, _ builder: ValuesBuilder -> ()) { - self.query = query - builder(self) - } - - public func set(column: Expression, _ value: T?) { - values.append((column, Expression<()>(value: value))) - } - - } + public typealias ValueSetter = (Expressible, Expressible) /// Runs an INSERT statement against the query. /// - /// :param: builder A block with a ValuesBuilder, used for aggregating - /// values for the INSERT. + /// :param: values A list of values to set. /// /// :returns: The statement. - public func insert(builder: ValuesBuilder -> ()) -> Statement { return insert(builder).statement } + public func insert(values: ValueSetter...) -> Statement { return insert(values).statement } /// Runs an INSERT statement against the query. /// - /// :param: builder A block with a ValuesBuilder, used for aggregating - /// values for the INSERT. + /// :param: values A list of values to set. /// /// :returns: The row ID. - public func insert(builder: ValuesBuilder -> ()) -> Int? { return insert(builder).ID } + public func insert(values: ValueSetter...) -> Int? { return insert(values).ID } /// Runs an INSERT statement against the query. /// - /// :param: builder A block with a ValuesBuilder, used for aggregating - /// values for the INSERT. + /// :param: values A list of values to set. /// /// :returns: The row ID and statement. - public func insert(builder: ValuesBuilder -> ()) -> (ID: Int?, statement: Statement) { - let statement = insertStatement(ValuesBuilder(self, builder).values).run() + public func insert(values: ValueSetter...) -> (ID: Int?, statement: Statement) { + return insert(values) + } + + private func insert(values: [ValueSetter]) -> (ID: Int?, statement: Statement) { + let statement = insertStatement(values).run() return (statement.failed ? nil : database.lastID, statement) } /// Runs an UPDATE statement against the query. /// - /// :param: builder A block with a ValuesBuilder, used for aggregating - /// values for the UPDATE. + /// :param: values A list of values to set. /// /// :returns: The statement. - public func update(builder: ValuesBuilder -> ()) -> Statement { return update(builder).statement } + public func update(values: ValueSetter...) -> Statement { return update(values).statement } /// Runs an UPDATE statement against the query. /// - /// :param: builder A block with a ValuesBuilder, used for aggregating - /// values for the UPDATE. + /// :param: values A list of values to set. /// /// :returns: The number of updated rows. - public func update(builder: ValuesBuilder -> ()) -> Int { return update(builder).changes } + public func update(values: ValueSetter...) -> Int { return update(values).changes } /// Runs an UPDATE statement against the query. /// - /// :param: builder A block with a ValuesBuilder, used for aggregating - /// values for the UPDATE. + /// :param: values A list of values to set. /// /// :returns: The number of updated rows and statement. - public func update(builder: ValuesBuilder -> ()) -> (changes: Int, statement: Statement) { - let statement = updateStatement(ValuesBuilder(self, builder).values).run() + public func update(values: ValueSetter...) -> (changes: Int, statement: Statement) { + return update(values) + } + + private func update(values: [ValueSetter]) -> (changes: Int, statement: Statement) { + let statement = updateStatement(values).run() return (statement.failed ? 0 : database.lastChanges ?? 0, statement) } @@ -421,3 +408,21 @@ extension Database { } } + +/// Returns a setter to be used with INSERT and UPDATE statements. +/// +/// :param: column The column being set. +/// +/// :param: value The value the column is being set to. +/// +/// :returns: A setter that can be used in a Query's insert and update +/// functions. +public func set(column: Expression, value: T?) -> Query.ValueSetter { + return (column, Expression<()>(value: value)) +} + +infix operator <- { associativity left precedence 140 } + +public func <-(column: Expression, value: T?) -> Query.ValueSetter { + return set(column, value) +} diff --git a/SQLite.playground/section-24.swift b/SQLite.playground/section-24.swift index f1e84a81..3b4bf65b 100644 --- a/SQLite.playground/section-24.swift +++ b/SQLite.playground/section-24.swift @@ -1,8 +1,3 @@ -users.insert { u in - u.set(email, "giles@acme.com") - u.set(age, 42) - u.set(admin, true) -}.ID - -users.insert { $0.set(email, "haley@acme.com"); $0.set(age, 30) }.ID -users.insert { $0.set(email, "inigo@acme.com"); $0.set(age, 24) }.ID +users.insert(email <- "giles@acme.com", age <- 42, admin <- true).ID +users.insert(email <- "haley@acme.com", age <- 30, admin <- true).ID +users.insert(email <- "inigo@acme.com", age <- 24).ID diff --git a/SQLite.playground/section-26.swift b/SQLite.playground/section-26.swift index 656a7227..cb3f3704 100644 --- a/SQLite.playground/section-26.swift +++ b/SQLite.playground/section-26.swift @@ -1,4 +1,4 @@ db.transaction( - users.insert { $0.set(email, "julie@acme.com") }, - users.insert { $0.set(email, "kelly@acme.com"); $0.set(manager_id, db.lastID) } + users.insert(email <- "julie@acme.com"), + users.insert(email <- "kelly@acme.com", manager_id <- db.lastID) ) diff --git a/SQLite.playground/section-40.swift b/SQLite.playground/section-40.swift index 555ddb03..a61552fe 100644 --- a/SQLite.playground/section-40.swift +++ b/SQLite.playground/section-40.swift @@ -1,2 +1,2 @@ // UPDATE users SET admin = 0 WHERE admin AND age IS NULL -agelessAdmins.update { $0.set(admin, false) }.changes +agelessAdmins.update(admin <- false).changes diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout new file mode 100644 index 00000000..3cabf16c --- /dev/null +++ b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + E427014D-8915-4E3B-859D-5EB72D455321 + IDESourceControlProjectName + SQLite + IDESourceControlProjectOriginsDictionary + + 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD + github.com:stephencelis/SQLite.swift.git + + IDESourceControlProjectPath + SQLite.xcodeproj + IDESourceControlProjectRelativeInstallPathDictionary + + 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD + ../.. + + IDESourceControlProjectURL + github.com:stephencelis/SQLite.swift.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD + IDESourceControlWCCName + SQLite + + + + From b609ab0be4606ace0cc26871df310c3306226bf6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 25 Oct 2014 09:54:01 -0700 Subject: [PATCH 0005/1046] Add documentation to Query methods They were lost in the Expression refactor shuffle. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 45bc4a2e..d5823f11 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -71,22 +71,48 @@ public struct Query { private var order = [Expressible]() private var limit: (to: Int, offset: Int?)? = nil + /// Sets the SELECT clause on the query. + /// + /// :param: columns A list of expressions to select. + /// + /// :returns A query with the given SELECT clause applied. public func select(columns: Expressible...) -> Query { var query = self query.columns = columns return query } + /// Sets the SELECT clause on the query. + /// + /// :param: star A literal *. + /// + /// :returns A query with SELECT * applied. public func select(star: Star) -> Query { var query = self query.columns = [star(nil, nil)] return query } + /// Adds an INNER JOIN clause to the query. + /// + /// :param: table A query representing the other table. + /// + /// :param: on A boolean expression describing the join condition. + /// + /// :returns A query with the given INNER JOIN clause applied. public func join(table: Query, on: Expression) -> Query { return join(.Inner, table, on: on) } + /// Adds a JOIN clause to the query. + /// + /// :param: type The JOIN operator. + /// + /// :param: table A query representing the other table. + /// + /// :param: on A boolean expression describing the join condition. + /// + /// :returns A query with the given JOIN clause applied. public func join(type: JoinType, _ table: Query, on: Expression) -> Query { var query = self let condition = table.filter.map { on && $0 } ?? on @@ -95,20 +121,44 @@ public struct Query { return query } + /// Adds a condition the the query’s WHERE clause. + /// + /// :param: condition A boolean expression to filter on. + /// + /// :returns A query with the given WHERE clause applied. public func filter(condition: Expression) -> Query { var query = self query.filter = filter.map { $0 && condition } ?? condition return query } + /// Sets a GROUP BY clause on the query. + /// + /// :param: by A list of columns to group by. + /// + /// :returns: A query with the given GROUP BY clause applied. public func group(by: Expressible...) -> Query { return group(by) } + /// Sets a GROUP BY clause (with optional HAVING) on the query. + /// + /// :param: by A column to group by. + /// + /// :param: having A condition determining which groups are returned. + /// + /// :returns: A query with the given GROUP BY clause applied. public func group(by: Expressible, having: Expression) -> Query { return group([by], having: having) } + /// Sets a GROUP BY-HAVING clause on the query. + /// + /// :param: by A list of columns to group by. + /// + /// :param: having A condition determining which groups are returned. + /// + /// :returns: A query with the given GROUP BY clause applied. public func group(by: [Expressible], having: Expression? = nil) -> Query { var query = self var group = SQLite.join(" ", [Expression<()>("GROUP BY"), SQLite.join(", ", by)]) @@ -117,6 +167,11 @@ public struct Query { return query } + /// Sets an ORDER BY clause on the query. + /// + /// :param: by An ordered list of columns and directions to sort by. + /// + /// :returns: A query with the given ORDER BY clause applied. public func order(by: Expressible...) -> Query { var query = self query.order = by From 29973e965c87cb7d927a9867065c2240a2c318ea Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 25 Oct 2014 09:56:24 -0700 Subject: [PATCH 0006/1046] Comments can be more descriptive with even one letter Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index d5823f11..385e6dd8 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -198,7 +198,7 @@ public struct Query { return limit(to: to, offset: offset) } - // prevent limit(nil, offset: 5) + // prevents limit(nil, offset: 5) private func limit(#to: Int?, offset: Int? = nil) -> Query { var query = self if let to = to { From c076184afee39f0917888b7ebab8534d439dd29c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 25 Oct 2014 11:32:52 -0700 Subject: [PATCH 0007/1046] Reorganize Signed-off-by: Stephen Celis --- SQLite Common/Database.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index 5ce16cb7..60e2ad60 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -1,4 +1,3 @@ - // // SQLite.Database // Copyright (c) 2014 Stephen Celis. @@ -22,11 +21,6 @@ // THE SOFTWARE. // -func quote(#literal: String) -> String { - let escaped = join("''", split(literal) { $0 == "'" }) - return "'\(escaped)'" -} - /// A connection (handle) to a SQLite database. public final class Database { @@ -345,3 +339,8 @@ extension Database: DebugPrintable { } } + +private func quote(#literal: String) -> String { + let escaped = join("''", split(literal) { $0 == "'" }) + return "'\(escaped)'" +} From 479140cc61d784e3673fe597aca96a4c5ebe8127 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 25 Oct 2014 14:49:10 -0700 Subject: [PATCH 0008/1046] Remove SortDirection No longer used after Expression.{asc,desc} was implemented. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 385e6dd8..68558523 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -51,18 +51,6 @@ public struct Query { } - /// Determines the direction in which rows are returned for a query’s ORDER - /// BY clause. - public enum SortDirection: String { - - /// Ascending order (smaller to larger values). - case Asc = "ASC" - - /// Descending order (larger to smaller values). - case Desc = "DESC" - - } - private var columns: [Expressible] = [Expression<()>("*")] internal var tableName: String private var joins = [Expressible]() From f0c677698c526338ca73f4ef33ff6270c90d0989 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 19:38:09 -0700 Subject: [PATCH 0009/1046] Add enhanced setter functions Firstly, we now support set(Expression, Expression), for mutations that involve database state. With this, we also create a bunch of operator-equals overrides. Now this is possible: let amount = 100.0 db.transaction( checking.update(balance -= amount), savings.update(balance += amount) ) // BEGIN DEFERRED TRANSACTION // UPDATE accounts SET balance = balance - 100.0 WHERE ... // UPDATE accounts SET balance = balance + 100.0 WHERE ... // COMMIT TRANSACTION Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 196 ++++++++++++++++------ SQLite Common Tests/TestHelper.swift | 11 ++ SQLite Common/Expression.swift | 107 ++++++++++++ SQLite Common/Query.swift | 40 ++--- 4 files changed, 271 insertions(+), 83 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 159edfdb..b81aa03b 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -140,8 +140,7 @@ class ExpressionTests: XCTestCase { func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { let int = Expression(value: 2) - let query = users.select(~int) - ExpectExecutions(db, ["SELECT ~(2) FROM users": 1]) { _ in for _ in query {} } + ExpectExecutionMatches(db, "~(2)", users.select(~int)) } func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { @@ -201,61 +200,61 @@ class ExpressionTests: XCTestCase { func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() { let int = Expression(value: 2) let query = users.select(-int) - ExpectExecution(db, query, "-(2)") + ExpectExecutionMatches(db, "-(2)", query) } func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() { let double = Expression(value: 2.0) let query = users.select(-double) - ExpectExecution(db, query, "-(2.0)") + ExpectExecutionMatches(db, "-(2.0)", query) } func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() { let int = Expression(value: 2) let query = users.select(0...5 ~= int) - ExpectExecution(db, query, "2 BETWEEN 0 AND 5") + ExpectExecutionMatches(db, "2 BETWEEN 0 AND 5", query) } func test_likeOperator_withStringExpression_buildsLikeExpression() { let string = Expression(value: "Hello") let query = users.select(like("%ello", string)) - ExpectExecution(db, query, "'Hello' LIKE '%ello'") + ExpectExecutionMatches(db, "'Hello' LIKE '%ello'", query) } func test_globOperator_withStringExpression_buildsGlobExpression() { let string = Expression(value: "Hello") let query = users.select(glob("*ello", string)) - ExpectExecution(db, query, "'Hello' GLOB '*ello'") + ExpectExecutionMatches(db, "'Hello' GLOB '*ello'", query) } func test_matchOperator_withStringExpression_buildsMatchExpression() { let string = Expression(value: "Hello") let query = users.select(match("ello", string)) - ExpectExecution(db, query, "'Hello' MATCH 'ello'") + ExpectExecutionMatches(db, "'Hello' MATCH 'ello'", query) } func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { let bool = Expression(value: true) let query = users.select(bool && bool) - ExpectExecution(db, query, "(1) AND (1)") + ExpectExecutionMatches(db, "(1) AND (1)", query) } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { let bool = Expression(value: true) let query = users.select(bool || bool) - ExpectExecution(db, query, "(1) OR (1)") + ExpectExecutionMatches(db, "(1) OR (1)", query) } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { let bool = Expression(value: true) let query = users.select(!bool) - ExpectExecution(db, query, "NOT (1)") + ExpectExecutionMatches(db, "NOT (1)", query) } func test_absFunction_withNumberExpressions_buildsAbsExpression() { let int = Expression(value: -2) let query = users.select(abs(int)) - ExpectExecution(db, query, "abs(-2)") + ExpectExecutionMatches(db, "abs(-2)", query) } func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { @@ -263,161 +262,252 @@ class ExpressionTests: XCTestCase { let int2 = Expression(value: nil) let int3 = Expression(value: 3) let query = users.select(coalesce(int1, int2, int3)) - ExpectExecution(db, query, "coalesce(NULL, NULL, 3)") + ExpectExecutionMatches(db, "coalesce(NULL, NULL, 3)", query) } func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { let int = Expression(value: nil) - ExpectExecution(db, users.select(ifnull(int, 1)), "ifnull(NULL, 1)") - ExpectExecution(db, users.select(int ?? 1), "ifnull(NULL, 1)") + ExpectExecutionMatches(db, "ifnull(NULL, 1)", users.select(ifnull(int, 1))) + ExpectExecutionMatches(db, "ifnull(NULL, 1)", users.select(int ?? 1)) } func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { let string = Expression(value: "Hello") let query = users.select(length(string)) - ExpectExecution(db, query, "length('Hello')") + ExpectExecutionMatches(db, "length('Hello')", query) } func test_lowerFunction_withStringExpression_buildsLowerStringExpression() { let string = Expression(value: "Hello") let query = users.select(lower(string)) - ExpectExecution(db, query, "lower('Hello')") + ExpectExecutionMatches(db, "lower('Hello')", query) } func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() { let string = Expression(value: " Hello") let query = users.select(ltrim(string)) - ExpectExecution(db, query, "ltrim(' Hello')") + ExpectExecutionMatches(db, "ltrim(' Hello')", query) } func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { let string = Expression(value: "Hello") let query = users.select(ltrim(string, "H")) - ExpectExecution(db, query, "ltrim('Hello', 'H')") + ExpectExecutionMatches(db, "ltrim('Hello', 'H')", query) } func test_randomFunction_buildsRandomIntExpression() { let query = users.select(random) - ExpectExecution(db, query, "random()") + ExpectExecutionMatches(db, "random()", query) } func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() { let string = Expression(value: "Hello") let query = users.select(replace(string, "He", "E")) - ExpectExecution(db, query, "replace('Hello', 'He', 'E')") + ExpectExecutionMatches(db, "replace('Hello', 'He', 'E')", query) } func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() { let double = Expression(value: 3.14159) let query = users.select(round(double)) - ExpectExecution(db, query, "round(3.14159)") + ExpectExecutionMatches(db, "round(3.14159)", query) } func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() { let double = Expression(value: 3.14159) let query = users.select(round(double, 2)) - ExpectExecution(db, query, "round(3.14159, 2)") + ExpectExecutionMatches(db, "round(3.14159, 2)", query) } func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() { let string = Expression(value: "Hello ") let query = users.select(rtrim(string)) - ExpectExecution(db, query, "rtrim('Hello ')") + ExpectExecutionMatches(db, "rtrim('Hello ')", query) } func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { let string = Expression(value: "Hello") let query = users.select(rtrim(string, "lo")) - ExpectExecution(db, query, "rtrim('Hello', 'lo')") + ExpectExecutionMatches(db, "rtrim('Hello', 'lo')", query) } func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() { let string = Expression(value: "Hello") let query = users.select(substr(string, 1)) - ExpectExecution(db, query, "substr('Hello', 1)") + ExpectExecutionMatches(db, "substr('Hello', 1)", query) } func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() { let string = Expression(value: "Hello") let query = users.select(substr(string, 1, 2)) - ExpectExecution(db, query, "substr('Hello', 1, 2)") + ExpectExecutionMatches(db, "substr('Hello', 1, 2)", query) } func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() { let string = Expression(value: "Hello") let query = users.select(substr(string, 1..<3)) - ExpectExecution(db, query, "substr('Hello', 1, 2)") + ExpectExecutionMatches(db, "substr('Hello', 1, 2)", query) } func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() { let string = Expression(value: " Hello ") let query = users.select(trim(string)) - ExpectExecution(db, query, "trim(' Hello ')") + ExpectExecutionMatches(db, "trim(' Hello ')", query) } func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { let string = Expression(value: "Hello") let query = users.select(trim(string, "lo")) - ExpectExecution(db, query, "trim('Hello', 'lo')") + ExpectExecutionMatches(db, "trim('Hello', 'lo')", query) } func test_upperFunction_withStringExpression_buildsLowerStringExpression() { let string = Expression(value: "Hello") let query = users.select(upper(string)) - ExpectExecution(db, query, "upper('Hello')") + ExpectExecutionMatches(db, "upper('Hello')", query) } let id = Expression("id") + let age = Expression("age") let email = Expression("email") let salary = Expression("salary") let admin = Expression("admin") func test_countFunction_withExpression_buildsCountExpression() { - ExpectExecution(db, users.select(count(id)), "count(id)") - ExpectExecution(db, users.select(count(email)), "count(email)") - ExpectExecution(db, users.select(count(salary)), "count(salary)") - ExpectExecution(db, users.select(count(admin)), "count(admin)") + ExpectExecutionMatches(db, "count(id)", users.select(count(id))) + ExpectExecutionMatches(db, "count(email)", users.select(count(email))) + ExpectExecutionMatches(db, "count(salary)", users.select(count(salary))) + ExpectExecutionMatches(db, "count(admin)", users.select(count(admin))) } func test_countFunction_withStar_buildsCountExpression() { - ExpectExecution(db, users.select(count(*)), "count(*)") + ExpectExecutionMatches(db, "count(*)", users.select(count(*))) } func test_maxFunction_withExpression_buildsMaxExpression() { - ExpectExecution(db, users.select(max(id)), "max(id)") - ExpectExecution(db, users.select(max(email)), "max(email)") - ExpectExecution(db, users.select(max(salary)), "max(salary)") - ExpectExecution(db, users.select(max(admin)), "max(admin)") + ExpectExecutionMatches(db, "max(id)", users.select(max(id))) + ExpectExecutionMatches(db, "max(email)", users.select(max(email))) + ExpectExecutionMatches(db, "max(salary)", users.select(max(salary))) + ExpectExecutionMatches(db, "max(admin)", users.select(max(admin))) } func test_minFunction_withExpression_buildsMinExpression() { - ExpectExecution(db, users.select(min(id)), "min(id)") - ExpectExecution(db, users.select(min(email)), "min(email)") - ExpectExecution(db, users.select(min(salary)), "min(salary)") - ExpectExecution(db, users.select(min(admin)), "min(admin)") + ExpectExecutionMatches(db, "min(id)", users.select(min(id))) + ExpectExecutionMatches(db, "min(email)", users.select(min(email))) + ExpectExecutionMatches(db, "min(salary)", users.select(min(salary))) + ExpectExecutionMatches(db, "min(admin)", users.select(min(admin))) } func test_averageFunction_withExpression_buildsAverageExpression() { - ExpectExecution(db, users.select(average(id)), "avg(id)") - ExpectExecution(db, users.select(average(salary)), "avg(salary)") + ExpectExecutionMatches(db, "avg(id)", users.select(average(id))) + ExpectExecutionMatches(db, "avg(salary)", users.select(average(salary))) } func test_sumFunction_withExpression_buildsSumExpression() { - ExpectExecution(db, users.select(sum(id)), "sum(id)") - ExpectExecution(db, users.select(sum(salary)), "sum(salary)") + ExpectExecutionMatches(db, "sum(id)", users.select(sum(id))) + ExpectExecutionMatches(db, "sum(salary)", users.select(sum(salary))) } func test_totalFunction_withExpression_buildsTotalExpression() { - ExpectExecution(db, users.select(total(id)), "total(id)") - ExpectExecution(db, users.select(total(salary)), "total(salary)") + ExpectExecutionMatches(db, "total(id)", users.select(total(id))) + ExpectExecutionMatches(db, "total(salary)", users.select(total(salary))) } func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - ExpectExecution(db, users.select(contains([1, 2, 3], id)), "id IN (1, 2, 3)") + ExpectExecutionMatches(db, "id IN (1, 2, 3)", users.select(contains([1, 2, 3], id))) + } + + func test_plusEquals_withStringExpression_buildsSetter() { + let SQL = "UPDATE users SET email = email || email" + ExpectExecution(db, SQL, users.update(email += email)) + } + + func test_plusEquals_withStringValue_buildsSetter() { + let SQL = "UPDATE users SET email = email || '.com'" + ExpectExecution(db, SQL, users.update(email += ".com")) + } + + func test_plusEquals_withNumberExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age + age", users.update(age += age)) + ExpectExecution(db, "UPDATE users SET salary = salary + salary", users.update(salary += salary)) + } + + func test_plusEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age + 1", users.update(age += 1)) + ExpectExecution(db, "UPDATE users SET salary = salary + 100.0", users.update(salary += 100)) + } + + func test_minusEquals_withNumberExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age - age", users.update(age -= age)) + ExpectExecution(db, "UPDATE users SET salary = salary - salary", users.update(salary -= salary)) + } + + func test_minusEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age - 1", users.update(age -= 1)) + ExpectExecution(db, "UPDATE users SET salary = salary - 100.0", users.update(salary -= 100)) + } + + func test_timesEquals_withNumberExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age * age", users.update(age *= age)) + ExpectExecution(db, "UPDATE users SET salary = salary * salary", users.update(salary *= salary)) + } + + func test_timesEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age * 1", users.update(age *= 1)) + ExpectExecution(db, "UPDATE users SET salary = salary * 100.0", users.update(salary *= 100)) + } + + func test_divideEquals_withNumberExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age / age", users.update(age /= age)) + ExpectExecution(db, "UPDATE users SET salary = salary / salary", users.update(salary /= salary)) + } + + func test_divideEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age / 1", users.update(age /= 1)) + ExpectExecution(db, "UPDATE users SET salary = salary / 100.0", users.update(salary /= 100)) + } + + func test_moduloEquals_withIntegerExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age % age", users.update(age %= age)) + } + + func test_moduloEquals_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age % 10", users.update(age %= 10)) + } + + func test_rightShiftEquals_withIntegerExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age >> age", users.update(age >>= age)) + } + + func test_rightShiftEquals_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age >> 1", users.update(age >>= 1)) + } + + func test_leftShiftEquals_withIntegerExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age << age", users.update(age <<= age)) + } + + func test_leftShiftEquals_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age << 1", users.update(age <<= 1)) + } + + func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age & age", users.update(age &= age)) + } + + func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age & 1", users.update(age &= 1)) + } + + func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age | age", users.update(age |= age)) + } + + func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age | 1", users.update(age |= 1)) } } -func ExpectExecution(db: Database, query: Query, SQL: String) { - ExpectExecutions(db, ["SELECT \(SQL) FROM users": 1]) { _ in for _ in query {} } +func ExpectExecutionMatches(db: Database, SQL: String, query: Query) { + ExpectExecution(db, "SELECT \(SQL) FROM users", query) } diff --git a/SQLite Common Tests/TestHelper.swift b/SQLite Common Tests/TestHelper.swift index ef80a310..b5e1a03d 100644 --- a/SQLite Common Tests/TestHelper.swift +++ b/SQLite Common Tests/TestHelper.swift @@ -44,3 +44,14 @@ func ExpectExecutions(db: Database, statements: [String: Int], block: Database - XCTAssertEqual(statements, fulfilled) } + +func ExpectExecution(db: Database, SQL: String, statement: @autoclosure () -> Statement) { + ExpectExecutions(db, [SQL: 1]) { _ in + statement() + return + } +} + +func ExpectExecution(db: Database, SQL: String, query: Query) { + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } +} diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 6443bf8f..c0bdec78 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -399,6 +399,113 @@ public func contains(values: [T?], column: Expression) -> Expressio return infix("IN", column, Expression("(\(templates))", values.map { $0 })) } +// MARK: - Modifying + +/// A pair of expressions used to set values in INSERT and UPDATE statements. +public typealias Setter = (Expressible, Expressible) + +/// Returns a setter to be used with INSERT and UPDATE statements. +/// +/// :param: column The column being set. +/// +/// :param: value The value the column is being set to. +/// +/// :returns: A setter that can be used in a Query's insert and update +/// functions. +public func set(column: Expression, value: T?) -> Setter { + return (column, Expression<()>(value: value)) +} + +/// Returns a setter to be used with INSERT and UPDATE statements. +/// +/// :param: column The column being set. +/// +/// :param: value The value the column is being set to. +/// +/// :returns: A setter that can be used in a Query's insert and update +/// functions. +public func set(column: Expression, value: Expression) -> Setter { + return (column, value) +} + +infix operator <- { associativity left precedence 140 } +public func <-(column: Expression, value: T?) -> Setter { + return set(column, value) +} +public func <-(column: Expression, value: Expression) -> Setter { + return set(column, value) +} + +public func +=(column: Expression, value: String) -> Setter { + return set(column, column + value) +} +public func +=(column: Expression, value: Expression) -> Setter { + return set(column, column + value) +} + +public func +=(column: Expression, value: T) -> Setter { + return set(column, column + value) +} +public func +=(column: Expression, value: Expression) -> Setter { + return set(column, column + value) +} + +public func -=(column: Expression, value: T) -> Setter { + return set(column, column - value) +} +public func -=(column: Expression, value: Expression) -> Setter { + return set(column, column - value) +} + +public func *=(column: Expression, value: T) -> Setter { + return set(column, column * value) +} +public func *=(column: Expression, value: Expression) -> Setter { + return set(column, column * value) +} + +public func /=(column: Expression, value: T) -> Setter { + return set(column, column / value) +} +public func /=(column: Expression, value: Expression) -> Setter { + return set(column, column / value) +} + +public func %=(column: Expression, value: Int) -> Setter { + return set(column, column % value) +} +public func %=(column: Expression, value: Expression) -> Setter { + return set(column, column % value) +} + +public func <<=(column: Expression, value: Int) -> Setter { + return set(column, column << value) +} +public func <<=(column: Expression, value: Expression) -> Setter { + return set(column, column << value) +} + +public func >>=(column: Expression, value: Int) -> Setter { + return set(column, column >> value) +} +public func >>=(column: Expression, value: Expression) -> Setter { + return set(column, column >> value) +} + +public func &=(column: Expression, value: Int) -> Setter { + return set(column, column & value) +} +public func &=(column: Expression, value: Expression) -> Setter { + return set(column, column & value) +} + +public func |=(column: Expression, value: Int) -> Setter { + return set(column, column | value) +} +public func |=(column: Expression, value: Expression) -> Setter { + return set(column, column | value) +} + // MARK: - Internal internal func join(separator: String, expressions: [Expressible]) -> Expression<()> { diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 68558523..5f750956 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -210,7 +210,7 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - private func insertStatement(values: [ValueSetter]) -> Statement { + private func insertStatement(values: [Setter]) -> Statement { var expressions: [Expressible] = [Expression<()>("INSERT INTO \(tableName)")] let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) @@ -219,7 +219,7 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - private func updateStatement(values: [ValueSetter]) -> Statement { + private func updateStatement(values: [Setter]) -> Statement { var expressions: [Expressible] = [Expression<()>("UPDATE \(tableName) SET")] expressions.append(SQLite.join(", ", values.map { SQLite.join(" = ", [$0, $1]) })) whereClause.map(expressions.append) @@ -280,32 +280,30 @@ public struct Query { // MARK: - Modifying - public typealias ValueSetter = (Expressible, Expressible) - /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The statement. - public func insert(values: ValueSetter...) -> Statement { return insert(values).statement } + public func insert(values: Setter...) -> Statement { return insert(values).statement } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The row ID. - public func insert(values: ValueSetter...) -> Int? { return insert(values).ID } + public func insert(values: Setter...) -> Int? { return insert(values).ID } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The row ID and statement. - public func insert(values: ValueSetter...) -> (ID: Int?, statement: Statement) { + public func insert(values: Setter...) -> (ID: Int?, statement: Statement) { return insert(values) } - private func insert(values: [ValueSetter]) -> (ID: Int?, statement: Statement) { + private func insert(values: [Setter]) -> (ID: Int?, statement: Statement) { let statement = insertStatement(values).run() return (statement.failed ? nil : database.lastID, statement) } @@ -315,25 +313,25 @@ public struct Query { /// :param: values A list of values to set. /// /// :returns: The statement. - public func update(values: ValueSetter...) -> Statement { return update(values).statement } + public func update(values: Setter...) -> Statement { return update(values).statement } /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The number of updated rows. - public func update(values: ValueSetter...) -> Int { return update(values).changes } + public func update(values: Setter...) -> Int { return update(values).changes } /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The number of updated rows and statement. - public func update(values: ValueSetter...) -> (changes: Int, statement: Statement) { + public func update(values: Setter...) -> (changes: Int, statement: Statement) { return update(values) } - private func update(values: [ValueSetter]) -> (changes: Int, statement: Statement) { + private func update(values: [Setter]) -> (changes: Int, statement: Statement) { let statement = updateStatement(values).run() return (statement.failed ? 0 : database.lastChanges ?? 0, statement) } @@ -451,21 +449,3 @@ extension Database { } } - -/// Returns a setter to be used with INSERT and UPDATE statements. -/// -/// :param: column The column being set. -/// -/// :param: value The value the column is being set to. -/// -/// :returns: A setter that can be used in a Query's insert and update -/// functions. -public func set(column: Expression, value: T?) -> Query.ValueSetter { - return (column, Expression<()>(value: value)) -} - -infix operator <- { associativity left precedence 140 } - -public func <-(column: Expression, value: T?) -> Query.ValueSetter { - return set(column, value) -} From da03002cbd4679814405c3f5524d7e4e36434bf2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 19:43:18 -0700 Subject: [PATCH 0010/1046] Let's not use `where` where we can avoid it Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index c0bdec78..17347015 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -170,67 +170,67 @@ public prefix func ~(rhs: Expression) -> Expression { // MARK: - Predicates -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==>(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: T?) -> Expression { +public func ==>(lhs: Expression, rhs: T?) -> Expression { if let rhs = rhs { return lhs == Expression(value: rhs) } return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) } -public func ==(lhs: T?, rhs: Expression) -> Expression { +public func ==>(lhs: T?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs) == rhs } return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: T?) -> Expression { +public func !=>(lhs: Expression, rhs: T?) -> Expression { if let rhs = rhs { return lhs != Expression(value: rhs) } return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) } -public func !=(lhs: T?, rhs: Expression) -> Expression { +public func !=>(lhs: T?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs) != rhs } return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: T) -> Expression { +public func >>(lhs: Expression, rhs: T) -> Expression { return lhs > Expression(value: rhs) } -public func >(lhs: T, rhs: Expression) -> Expression { +public func >>(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) > rhs } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: T) -> Expression { +public func >=>(lhs: Expression, rhs: T) -> Expression { return lhs >= Expression(value: rhs) } -public func >=(lhs: T, rhs: Expression) -> Expression { +public func >=>(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) >= rhs } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: T) -> Expression { +public func <>(lhs: Expression, rhs: T) -> Expression { return lhs < Expression(value: rhs) } -public func <(lhs: T, rhs: Expression) -> Expression { +public func <>(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) < rhs } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: T) -> Expression { +public func <=>(lhs: Expression, rhs: T) -> Expression { return lhs <= Expression(value: rhs) } -public func <=(lhs: T, rhs: Expression) -> Expression { +public func <=>(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) <= rhs } @@ -238,7 +238,7 @@ public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public func ~=(lhs: I, rhs: Expression) -> Expression { +public func ~= where T == I.Bound>(lhs: I, rhs: Expression) -> Expression { return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) } From 9c83be672c060232cfd83363d49d1863016c5b5c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 21:20:30 -0700 Subject: [PATCH 0011/1046] Add ++/-- postfix Setter operators Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 8 ++++++++ SQLite Common/Expression.swift | 3 +++ 2 files changed, 11 insertions(+) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index b81aa03b..78bd8b41 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -506,6 +506,14 @@ class ExpressionTests: XCTestCase { ExpectExecution(db, "UPDATE users SET age = age | 1", users.update(age |= 1)) } + func test_postfixPlus_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age + 1", users.update(age++)) + } + + func test_postfixMinus_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = age - 1", users.update(age--)) + } + } func ExpectExecutionMatches(db: Database, SQL: String, query: Query) { diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 17347015..1048e389 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -506,6 +506,9 @@ public func |=(column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public postfix func ++(rhs: Expression) -> Setter { return rhs += 1 } +public postfix func --(rhs: Expression) -> Setter { return rhs -= 1 } + // MARK: - Internal internal func join(separator: String, expressions: [Expressible]) -> Expression<()> { From ec29abd2bc3c6b3970a4d58cfafca00abe572863 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 21:54:30 -0700 Subject: [PATCH 0012/1046] Maintain expression precedence We need to surround expressions with parentheses to guarantee that Swift precedence is preserved. Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 108 +++++++++++----------- SQLite Common Tests/QueryTests.swift | 16 ++-- SQLite Common/Expression.swift | 14 ++- 3 files changed, 71 insertions(+), 67 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 78bd8b41..702da9a2 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -14,7 +14,7 @@ class ExpressionTests: XCTestCase { func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { let string = Expression(value: "Hello") - ExpectExecutions(db, ["SELECT 'Hello' || 'Hello' FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT ('Hello' || 'Hello') FROM users": 3]) { _ in for _ in self.users.select(string + string) {} for _ in self.users.select(string + "Hello") {} for _ in self.users.select("Hello" + string) {} @@ -23,7 +23,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 + 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 + 2) FROM users": 3]) { _ in for _ in self.users.select(int + int) {} for _ in self.users.select(int + 2) {} for _ in self.users.select(2 + int) {} @@ -32,7 +32,7 @@ class ExpressionTests: XCTestCase { func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() { let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT 2.0 + 2.0 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2.0 + 2.0) FROM users": 3]) { _ in for _ in self.users.select(double + double) {} for _ in self.users.select(double + 2.0) {} for _ in self.users.select(2.0 + double) {} @@ -41,7 +41,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 - 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 - 2) FROM users": 3]) { _ in for _ in self.users.select(int - int) {} for _ in self.users.select(int - 2) {} for _ in self.users.select(2 - int) {} @@ -50,7 +50,7 @@ class ExpressionTests: XCTestCase { func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() { let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT 2.0 - 2.0 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2.0 - 2.0) FROM users": 3]) { _ in for _ in self.users.select(double - double) {} for _ in self.users.select(double - 2.0) {} for _ in self.users.select(2.0 - double) {} @@ -59,7 +59,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 * 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 * 2) FROM users": 3]) { _ in for _ in self.users.select(int * int) {} for _ in self.users.select(int * 2) {} for _ in self.users.select(2 * int) {} @@ -68,7 +68,7 @@ class ExpressionTests: XCTestCase { func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() { let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT 2.0 * 2.0 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2.0 * 2.0) FROM users": 3]) { _ in for _ in self.users.select(double * double) {} for _ in self.users.select(double * 2.0) {} for _ in self.users.select(2.0 * double) {} @@ -77,7 +77,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 / 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 / 2) FROM users": 3]) { _ in for _ in self.users.select(int / int) {} for _ in self.users.select(int / 2) {} for _ in self.users.select(2 / int) {} @@ -86,7 +86,7 @@ class ExpressionTests: XCTestCase { func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() { let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT 2.0 / 2.0 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2.0 / 2.0) FROM users": 3]) { _ in for _ in self.users.select(double / double) {} for _ in self.users.select(double / 2.0) {} for _ in self.users.select(2.0 / double) {} @@ -95,7 +95,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 % 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 % 2) FROM users": 3]) { _ in for _ in self.users.select(int % int) {} for _ in self.users.select(int % 2) {} for _ in self.users.select(2 % int) {} @@ -104,7 +104,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 << 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 << 2) FROM users": 3]) { _ in for _ in self.users.select(int << int) {} for _ in self.users.select(int << 2) {} for _ in self.users.select(2 << int) {} @@ -113,7 +113,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 >> 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 >> 2) FROM users": 3]) { _ in for _ in self.users.select(int >> int) {} for _ in self.users.select(int >> 2) {} for _ in self.users.select(2 >> int) {} @@ -122,7 +122,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 & 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 & 2) FROM users": 3]) { _ in for _ in self.users.select(int & int) {} for _ in self.users.select(int & 2) {} for _ in self.users.select(2 & int) {} @@ -131,7 +131,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 | 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 | 2) FROM users": 3]) { _ in for _ in self.users.select(int | int) {} for _ in self.users.select(int | 2) {} for _ in self.users.select(2 | int) {} @@ -145,7 +145,7 @@ class ExpressionTests: XCTestCase { func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { let bool = Expression(value: true) - ExpectExecutions(db, ["SELECT 1 = 1 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (1 = 1) FROM users": 3]) { _ in for _ in self.users.select(bool == bool) {} for _ in self.users.select(bool == true) {} for _ in self.users.select(true == bool) {} @@ -154,7 +154,7 @@ class ExpressionTests: XCTestCase { func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { let bool = Expression(value: true) - ExpectExecutions(db, ["SELECT 1 != 1 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (1 != 1) FROM users": 3]) { _ in for _ in self.users.select(bool != bool) {} for _ in self.users.select(bool != true) {} for _ in self.users.select(true != bool) {} @@ -163,7 +163,7 @@ class ExpressionTests: XCTestCase { func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 > 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 > 2) FROM users": 3]) { _ in for _ in self.users.select(int > int) {} for _ in self.users.select(int > 2) {} for _ in self.users.select(2 > int) {} @@ -172,7 +172,7 @@ class ExpressionTests: XCTestCase { func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 >= 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 >= 2) FROM users": 3]) { _ in for _ in self.users.select(int >= int) {} for _ in self.users.select(int >= 2) {} for _ in self.users.select(2 >= int) {} @@ -181,7 +181,7 @@ class ExpressionTests: XCTestCase { func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 < 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 < 2) FROM users": 3]) { _ in for _ in self.users.select(int < int) {} for _ in self.users.select(int < 2) {} for _ in self.users.select(2 < int) {} @@ -190,7 +190,7 @@ class ExpressionTests: XCTestCase { func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT 2 <= 2 FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (2 <= 2) FROM users": 3]) { _ in for _ in self.users.select(int <= int) {} for _ in self.users.select(int <= 2) {} for _ in self.users.select(2 <= int) {} @@ -218,19 +218,19 @@ class ExpressionTests: XCTestCase { func test_likeOperator_withStringExpression_buildsLikeExpression() { let string = Expression(value: "Hello") let query = users.select(like("%ello", string)) - ExpectExecutionMatches(db, "'Hello' LIKE '%ello'", query) + ExpectExecutionMatches(db, "('Hello' LIKE '%ello')", query) } func test_globOperator_withStringExpression_buildsGlobExpression() { let string = Expression(value: "Hello") let query = users.select(glob("*ello", string)) - ExpectExecutionMatches(db, "'Hello' GLOB '*ello'", query) + ExpectExecutionMatches(db, "('Hello' GLOB '*ello')", query) } func test_matchOperator_withStringExpression_buildsMatchExpression() { let string = Expression(value: "Hello") let query = users.select(match("ello", string)) - ExpectExecutionMatches(db, "'Hello' MATCH 'ello'", query) + ExpectExecutionMatches(db, "('Hello' MATCH 'ello')", query) } func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { @@ -413,105 +413,105 @@ class ExpressionTests: XCTestCase { } func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - ExpectExecutionMatches(db, "id IN (1, 2, 3)", users.select(contains([1, 2, 3], id))) + ExpectExecutionMatches(db, "(id IN (1, 2, 3))", users.select(contains([1, 2, 3], id))) } func test_plusEquals_withStringExpression_buildsSetter() { - let SQL = "UPDATE users SET email = email || email" + let SQL = "UPDATE users SET email = (email || email)" ExpectExecution(db, SQL, users.update(email += email)) } func test_plusEquals_withStringValue_buildsSetter() { - let SQL = "UPDATE users SET email = email || '.com'" + let SQL = "UPDATE users SET email = (email || '.com')" ExpectExecution(db, SQL, users.update(email += ".com")) } func test_plusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age + age", users.update(age += age)) - ExpectExecution(db, "UPDATE users SET salary = salary + salary", users.update(salary += salary)) + ExpectExecution(db, "UPDATE users SET age = (age + age)", users.update(age += age)) + ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary += salary)) } func test_plusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age + 1", users.update(age += 1)) - ExpectExecution(db, "UPDATE users SET salary = salary + 100.0", users.update(salary += 100)) + ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age += 1)) + ExpectExecution(db, "UPDATE users SET salary = (salary + 100.0)", users.update(salary += 100)) } func test_minusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age - age", users.update(age -= age)) - ExpectExecution(db, "UPDATE users SET salary = salary - salary", users.update(salary -= salary)) + ExpectExecution(db, "UPDATE users SET age = (age - age)", users.update(age -= age)) + ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary -= salary)) } func test_minusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age - 1", users.update(age -= 1)) - ExpectExecution(db, "UPDATE users SET salary = salary - 100.0", users.update(salary -= 100)) + ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age -= 1)) + ExpectExecution(db, "UPDATE users SET salary = (salary - 100.0)", users.update(salary -= 100)) } func test_timesEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age * age", users.update(age *= age)) - ExpectExecution(db, "UPDATE users SET salary = salary * salary", users.update(salary *= salary)) + ExpectExecution(db, "UPDATE users SET age = (age * age)", users.update(age *= age)) + ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary *= salary)) } func test_timesEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age * 1", users.update(age *= 1)) - ExpectExecution(db, "UPDATE users SET salary = salary * 100.0", users.update(salary *= 100)) + ExpectExecution(db, "UPDATE users SET age = (age * 1)", users.update(age *= 1)) + ExpectExecution(db, "UPDATE users SET salary = (salary * 100.0)", users.update(salary *= 100)) } func test_divideEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age / age", users.update(age /= age)) - ExpectExecution(db, "UPDATE users SET salary = salary / salary", users.update(salary /= salary)) + ExpectExecution(db, "UPDATE users SET age = (age / age)", users.update(age /= age)) + ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary /= salary)) } func test_divideEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age / 1", users.update(age /= 1)) - ExpectExecution(db, "UPDATE users SET salary = salary / 100.0", users.update(salary /= 100)) + ExpectExecution(db, "UPDATE users SET age = (age / 1)", users.update(age /= 1)) + ExpectExecution(db, "UPDATE users SET salary = (salary / 100.0)", users.update(salary /= 100)) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age % age", users.update(age %= age)) + ExpectExecution(db, "UPDATE users SET age = (age % age)", users.update(age %= age)) } func test_moduloEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age % 10", users.update(age %= 10)) + ExpectExecution(db, "UPDATE users SET age = (age % 10)", users.update(age %= 10)) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age >> age", users.update(age >>= age)) + ExpectExecution(db, "UPDATE users SET age = (age >> age)", users.update(age >>= age)) } func test_rightShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age >> 1", users.update(age >>= 1)) + ExpectExecution(db, "UPDATE users SET age = (age >> 1)", users.update(age >>= 1)) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age << age", users.update(age <<= age)) + ExpectExecution(db, "UPDATE users SET age = (age << age)", users.update(age <<= age)) } func test_leftShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age << 1", users.update(age <<= 1)) + ExpectExecution(db, "UPDATE users SET age = (age << 1)", users.update(age <<= 1)) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age & age", users.update(age &= age)) + ExpectExecution(db, "UPDATE users SET age = (age & age)", users.update(age &= age)) } func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age & 1", users.update(age &= 1)) + ExpectExecution(db, "UPDATE users SET age = (age & 1)", users.update(age &= 1)) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age | age", users.update(age |= age)) + ExpectExecution(db, "UPDATE users SET age = (age | age)", users.update(age |= age)) } func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age | 1", users.update(age |= 1)) + ExpectExecution(db, "UPDATE users SET age = (age | 1)", users.update(age |= 1)) } func test_postfixPlus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age + 1", users.update(age++)) + ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age++)) } func test_postfixMinus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = age - 1", users.update(age--)) + ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age--)) } } diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index f35c6ec3..0fb13961 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -46,7 +46,7 @@ class QueryTests: XCTestCase { let query = users.join(managers, on: managers_id == users_manager_id) - let SQL = "SELECT * FROM users INNER JOIN users AS managers ON managers.id = users.manager_id" + let SQL = "SELECT * FROM users INNER JOIN users AS managers ON (managers.id = users.manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -57,7 +57,7 @@ class QueryTests: XCTestCase { let query = users.join(.LeftOuter, managers, on: managers_id == users_manager_id) - let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON managers.id = users.manager_id" + let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON (managers.id = users.manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -90,15 +90,15 @@ class QueryTests: XCTestCase { .join(managed, on: managed_manager_id == users_id) let SQL = "SELECT * FROM users " + - "INNER JOIN users AS managers ON managers.id = users.manager_id " + - "INNER JOIN users AS managed ON users.id = managed.manager_id" + "INNER JOIN users AS managers ON (managers.id = users.manager_id) " + + "INNER JOIN users AS managed ON (users.id = managed.manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_filter_compilesWhereClause() { let query = users.filter(admin == true) - let SQL = "SELECT * FROM users WHERE admin = 1" + let SQL = "SELECT * FROM users WHERE (admin = 1)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -130,14 +130,14 @@ class QueryTests: XCTestCase { func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { let query = users.group(age, having: age >= 30) - let SQL = "SELECT * FROM users GROUP BY age HAVING age >= 30" + let SQL = "SELECT * FROM users GROUP BY age HAVING (age >= 30)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { let query = users.group([age, admin], having: age >= 30) - let SQL = "SELECT * FROM users GROUP BY age, admin HAVING age >= 30" + let SQL = "SELECT * FROM users GROUP BY age, admin HAVING (age >= 30)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -215,7 +215,7 @@ class QueryTests: XCTestCase { let SQL = "SELECT users.email, count(users.email) FROM users " + "LEFT OUTER JOIN users AS managers ON (managers.id = users.manager_id) AND (managers.admin = 1) " + "WHERE users.age BETWEEN 21 AND 32 " + - "GROUP BY users.age HAVING count(users.email) > 1 " + + "GROUP BY users.age HAVING (count(users.email) > 1) " + "ORDER BY users.email DESC " + "LIMIT 1 " + "OFFSET 2" diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 1048e389..848972ca 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -147,7 +147,7 @@ public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs < public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } public func >>(lhs: Expression, rhs: Expression) -> Expression { - return Expression("\(lhs.SQL) \(__FUNCTION__) \(rhs.SQL)", lhs.bindings + rhs.bindings) + return infix(__FUNCTION__, lhs, rhs) } public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) } public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } @@ -259,13 +259,13 @@ public func match(string: String, expression: Expression) -> Expression< // MARK: Compound public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Expression("(\(lhs.SQL)) AND (\(rhs.SQL))", lhs.bindings + rhs.bindings) + return Expression("\(surround(lhs.SQL)) AND \(surround(rhs.SQL))", lhs.bindings + rhs.bindings) } public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Expression("(\(lhs.SQL)) OR (\(rhs.SQL))", lhs.bindings + rhs.bindings) + return Expression("\(surround(lhs.SQL)) OR \(surround(rhs.SQL))", lhs.bindings + rhs.bindings) } public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } @@ -522,9 +522,13 @@ internal func join(separator: String, expressions: [Expressible]) -> Expression< } private func wrap(function: String, expression: Expression) -> Expression { - return Expression("\(function)(\(expression.SQL))", expression.bindings) + return Expression("\(function)\(surround(expression.SQL))", expression.bindings) } private func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { - return Expression("\(lhs.SQL) \(function) \(rhs.SQL)", lhs.bindings + rhs.bindings) + return Expression(surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) +} + +private func surround(expression: String) -> String { + return expression.hasPrefix("(") && expression.hasSuffix(")") ? expression : "(\(expression))" } From 7d98891ba6d043d033afa5174aab4a7fe18a7c4e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 21:55:40 -0700 Subject: [PATCH 0013/1046] Support XOR SQLite may not, but we can. Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 18 ++++++++++++++++++ SQLite Common/Expression.swift | 13 +++++++++++++ 2 files changed, 31 insertions(+) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 702da9a2..7783e2e9 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -138,6 +138,16 @@ class ExpressionTests: XCTestCase { } } + func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { + let int1 = Expression(value: 1) + let int2 = Expression(value: 2) + ExpectExecutions(db, ["SELECT (~(1 & 2) & (1 | 2)) FROM users": 3]) { _ in + for _ in self.users.select(int1 ^ int2) {} + for _ in self.users.select(int1 ^ 2) {} + for _ in self.users.select(1 ^ int2) {} + } + } + func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { let int = Expression(value: 2) ExpectExecutionMatches(db, "~(2)", users.select(~int)) @@ -506,6 +516,14 @@ class ExpressionTests: XCTestCase { ExpectExecution(db, "UPDATE users SET age = (age | 1)", users.update(age |= 1)) } + func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = (~(age & age) & (age | age))", users.update(age ^= age)) + } + + func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET age = (~(age & 1) & (age | 1))", users.update(age ^= 1)) + } + func test_postfixPlus_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age++)) } diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 848972ca..15afea64 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -164,6 +164,12 @@ public func |(lhs: Expression, rhs: Expression) -> Expression { public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(value: rhs) } public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(value: rhs) } +public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } + public prefix func ~(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } @@ -506,6 +512,13 @@ public func |=(column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func ^=(column: Expression, value: Int) -> Setter { + return set(column, column ^ value) +} +public func ^=(column: Expression, value: Expression) -> Setter { + return set(column, column ^ value) +} + public postfix func ++(rhs: Expression) -> Setter { return rhs += 1 } public postfix func --(rhs: Expression) -> Setter { return rhs -= 1 } From b44ea4db4a520b33ca936e47c448a99ace6bb8a5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 23:41:10 -0700 Subject: [PATCH 0014/1046] Share schemes (for CI, eventually) Signed-off-by: Stephen Celis --- .../xcschemes/SQLite Mac.xcscheme | 110 ++++++++++++++++++ .../xcschemes/SQLite iOS.xcscheme | 110 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme new file mode 100644 index 00000000..2eb851f7 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme new file mode 100644 index 00000000..cb5a6c96 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a6e974120a4ce2eb457ea471f1b8a96c3ff412ab Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 26 Oct 2014 23:41:39 -0700 Subject: [PATCH 0015/1046] Use parentheses consistently to group expressions It makes the compiled statements slightly muddier, but precedence should always be retained now. Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 12 +++++++++--- SQLite Common Tests/QueryTests.swift | 8 ++++---- SQLite Common/Expression.swift | 4 +--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 7783e2e9..67b25fc0 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -141,7 +141,7 @@ class ExpressionTests: XCTestCase { func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { let int1 = Expression(value: 1) let int2 = Expression(value: 2) - ExpectExecutions(db, ["SELECT (~(1 & 2) & (1 | 2)) FROM users": 3]) { _ in + ExpectExecutions(db, ["SELECT (~((1 & 2)) & (1 | 2)) FROM users": 3]) { _ in for _ in self.users.select(int1 ^ int2) {} for _ in self.users.select(int1 ^ 2) {} for _ in self.users.select(1 ^ int2) {} @@ -517,11 +517,11 @@ class ExpressionTests: XCTestCase { } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (~(age & age) & (age | age))", users.update(age ^= age)) + ExpectExecution(db, "UPDATE users SET age = (~((age & age)) & (age | age))", users.update(age ^= age)) } func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (~(age & 1) & (age | 1))", users.update(age ^= 1)) + ExpectExecution(db, "UPDATE users SET age = (~((age & 1)) & (age | 1))", users.update(age ^= 1)) } func test_postfixPlus_withIntegerValue_buildsSetter() { @@ -532,6 +532,12 @@ class ExpressionTests: XCTestCase { ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age--)) } + func test_precedencePreserved() { + let n = Expression(value: 1) + ExpectExecutionMatches(db, "(((1 = 1)) AND ((1 = 1))) OR ((1 = 1))", users.select((n == n && n == n) || n == n)) + ExpectExecutionMatches(db, "((1 = 1)) AND (((1 = 1)) OR ((1 = 1)))", users.select(n == n && (n == n || n == n))) + } + } func ExpectExecutionMatches(db: Database, SQL: String, query: Query) { diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 0fb13961..f4865636 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -71,7 +71,7 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM users " + "INNER JOIN users AS managers " + - "ON (managers.id = users.manager_id) " + + "ON ((managers.id = users.manager_id)) " + "AND (managers.admin)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -108,8 +108,8 @@ class QueryTests: XCTestCase { .filter(age >= 21) let SQL = "SELECT * FROM users " + - "WHERE (email = 'alice@example.com') " + - "AND (age >= 21)" + "WHERE ((email = 'alice@example.com')) " + + "AND ((age >= 21))" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -213,7 +213,7 @@ class QueryTests: XCTestCase { .limit(1, offset: 2) let SQL = "SELECT users.email, count(users.email) FROM users " + - "LEFT OUTER JOIN users AS managers ON (managers.id = users.manager_id) AND (managers.admin = 1) " + + "LEFT OUTER JOIN users AS managers ON ((managers.id = users.manager_id)) AND ((managers.admin = 1)) " + "WHERE users.age BETWEEN 21 AND 32 " + "GROUP BY users.age HAVING (count(users.email) > 1) " + "ORDER BY users.email DESC " + diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 15afea64..5f9974d6 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -542,6 +542,4 @@ private func infix(function: String, lhs: Expression, rhs: Expression String { - return expression.hasPrefix("(") && expression.hasSuffix(")") ? expression : "(\(expression))" -} +private func surround(expression: String) -> String { return "(\(expression))" } From c5f822ff2460e0f162eda6c77ecb69d57908dbaf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Oct 2014 00:33:30 -0700 Subject: [PATCH 0016/1046] Surround boolean expressions with parentheses Better fidelity. Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 8 ++++---- SQLite Common Tests/QueryTests.swift | 10 +++++----- SQLite Common/Expression.swift | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 67b25fc0..ca6aca90 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -246,13 +246,13 @@ class ExpressionTests: XCTestCase { func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { let bool = Expression(value: true) let query = users.select(bool && bool) - ExpectExecutionMatches(db, "(1) AND (1)", query) + ExpectExecutionMatches(db, "(1 AND 1)", query) } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { let bool = Expression(value: true) let query = users.select(bool || bool) - ExpectExecutionMatches(db, "(1) OR (1)", query) + ExpectExecutionMatches(db, "(1 OR 1)", query) } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { @@ -534,8 +534,8 @@ class ExpressionTests: XCTestCase { func test_precedencePreserved() { let n = Expression(value: 1) - ExpectExecutionMatches(db, "(((1 = 1)) AND ((1 = 1))) OR ((1 = 1))", users.select((n == n && n == n) || n == n)) - ExpectExecutionMatches(db, "((1 = 1)) AND (((1 = 1)) OR ((1 = 1)))", users.select(n == n && (n == n || n == n))) + ExpectExecutionMatches(db, "(((1 = 1) AND (1 = 1)) OR (1 = 1))", users.select((n == n && n == n) || n == n)) + ExpectExecutionMatches(db, "((1 = 1) AND ((1 = 1) OR (1 = 1)))", users.select(n == n && (n == n || n == n))) } } diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index f4865636..a3641e4a 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -71,8 +71,8 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM users " + "INNER JOIN users AS managers " + - "ON ((managers.id = users.manager_id)) " + - "AND (managers.admin)" + "ON ((managers.id = users.manager_id) " + + "AND managers.admin)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -108,8 +108,8 @@ class QueryTests: XCTestCase { .filter(age >= 21) let SQL = "SELECT * FROM users " + - "WHERE ((email = 'alice@example.com')) " + - "AND ((age >= 21))" + "WHERE ((email = 'alice@example.com') " + + "AND (age >= 21))" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -213,7 +213,7 @@ class QueryTests: XCTestCase { .limit(1, offset: 2) let SQL = "SELECT users.email, count(users.email) FROM users " + - "LEFT OUTER JOIN users AS managers ON ((managers.id = users.manager_id)) AND ((managers.admin = 1)) " + + "LEFT OUTER JOIN users AS managers ON ((managers.id = users.manager_id) AND (managers.admin = 1)) " + "WHERE users.age BETWEEN 21 AND 32 " + "GROUP BY users.age HAVING (count(users.email) > 1) " + "ORDER BY users.email DESC " + diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 5f9974d6..759bc5ba 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -265,13 +265,13 @@ public func match(string: String, expression: Expression) -> Expression< // MARK: Compound public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Expression("\(surround(lhs.SQL)) AND \(surround(rhs.SQL))", lhs.bindings + rhs.bindings) + return Expression(surround("\(lhs.SQL) AND \(rhs.SQL)"), lhs.bindings + rhs.bindings) } public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Expression("\(surround(lhs.SQL)) OR \(surround(rhs.SQL))", lhs.bindings + rhs.bindings) + return Expression(surround("\(lhs.SQL) OR \(rhs.SQL)"), lhs.bindings + rhs.bindings) } public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } From 38602e69f448a32d47b345cb3c2022da071f1390 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Oct 2014 00:34:09 -0700 Subject: [PATCH 0017/1046] Add SELECT DISTINCT helper Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 7 +++++++ SQLite Common/Query.swift | 22 +++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index a3641e4a..878ef8df 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -39,6 +39,13 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(*) {} } } + func test_selectDistinct_withExpression_compilesSelectClause() { + let query = users.select(distinct: age) + + let SQL = "SELECT DISTINCT age FROM users" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + } + func test_join_compilesJoinClause() { let managers = db["users AS managers"] let managers_id = Expression("managers.id") diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 5f750956..4b2765f3 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -51,7 +51,7 @@ public struct Query { } - private var columns: [Expressible] = [Expression<()>("*")] + private var columns: Expressible = Expression<()>("*") internal var tableName: String private var joins = [Expressible]() private var filter: Expression? @@ -66,7 +66,7 @@ public struct Query { /// :returns A query with the given SELECT clause applied. public func select(columns: Expressible...) -> Query { var query = self - query.columns = columns + query.columns = SQLite.join(", ", columns) return query } @@ -77,10 +77,23 @@ public struct Query { /// :returns A query with SELECT * applied. public func select(star: Star) -> Query { var query = self - query.columns = [star(nil, nil)] + query.columns = star(nil, nil) return query } + /// Sets the SELECT DISTINCT clause on the query. + /// + /// :param: columns A list of expressions to select. + /// + /// :returns A query with the given SELECT DISTINCT clause applied. + public func select(distinct columns: Expressible...) -> Query { + var query = self + query.columns = SQLite.join(" ", [Expression<()>("DISTINCT"), SQLite.join(", ", columns)]) + return query + } + + // rdar://18778670 causes select(distinct: *) to make select(*) ambiguous + /// Adds an INNER JOIN clause to the query. /// /// :param: table A query representing the other table. @@ -237,8 +250,7 @@ public struct Query { // MARK: - private var selectClause: Expressible { - let select = SQLite.join(", ", columns) - return SQLite.join(" ", [Expression<()>("SELECT"), select, Expression<()>("FROM \(tableName)")]) + return SQLite.join(" ", [Expression<()>("SELECT"), columns, Expression<()>("FROM \(tableName)")]) } private var joinClause: Expressible? { From 0f36bc165f815c5fb0a4d8aa1a7b5c5aa7d34bee Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Oct 2014 00:34:31 -0700 Subject: [PATCH 0018/1046] Reorganize ASC/DESC They're main computer variables, not Expressible extension material. Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 759bc5ba..bd2e398e 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -38,6 +38,14 @@ public struct Expression { self.init("?", [value]) } + public var asc: Expression<()> { + return join(" ", [self, Expression("ASC")]) + } + + public var desc: Expression<()> { + return join(" ", [self, Expression("DESC")]) + } + } public protocol Expressible { @@ -92,14 +100,6 @@ extension Expression: Expressible { return Expression<()>(SQL, bindings) } - public var asc: Expression<()> { - return join(" ", [self, Expression("ASC")]) - } - - public var desc: Expression<()> { - return join(" ", [self, Expression("DESC")]) - } - } // MARK: - Expressions From d8b80a80892433124cc4634a277d5b374e720228 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Oct 2014 00:35:59 -0700 Subject: [PATCH 0019/1046] Use helpers wherever possible Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index bd2e398e..e899fff1 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -265,13 +265,13 @@ public func match(string: String, expression: Expression) -> Expression< // MARK: Compound public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Expression(surround("\(lhs.SQL) AND \(rhs.SQL)"), lhs.bindings + rhs.bindings) + return infix("AND", lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Expression(surround("\(lhs.SQL) OR \(rhs.SQL)"), lhs.bindings + rhs.bindings) + return infix("OR", lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } From 7969c13933fb62c8d276cdad15281c1c928fb5d4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Oct 2014 07:49:59 -0700 Subject: [PATCH 0020/1046] Support select(distinct: *) It requires changing select(*) to select(all: *), but this is a small concession to make when calling `distinct: *` is going to be much more common (`all: *` is technically the default). Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 9 +++++++- SQLite Common/Query.swift | 34 ++++++++++++++++------------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 878ef8df..81564ab2 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -36,7 +36,7 @@ class QueryTests: XCTestCase { let query = users.select(email) let SQL = "SELECT * FROM users" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(*) {} } + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(all: *) {} } } func test_selectDistinct_withExpression_compilesSelectClause() { @@ -46,6 +46,13 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } + func test_selectDistinct_withStar_compilesSelectClause() { + let query = users.select(distinct: *) + + let SQL = "SELECT DISTINCT * FROM users" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + } + func test_join_compilesJoinClause() { let managers = db["users AS managers"] let managers_id = Expression("managers.id") diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 4b2765f3..987262ea 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -61,23 +61,12 @@ public struct Query { /// Sets the SELECT clause on the query. /// - /// :param: columns A list of expressions to select. + /// :param: all A list of expressions to select. /// /// :returns A query with the given SELECT clause applied. - public func select(columns: Expressible...) -> Query { - var query = self - query.columns = SQLite.join(", ", columns) - return query - } - - /// Sets the SELECT clause on the query. - /// - /// :param: star A literal *. - /// - /// :returns A query with SELECT * applied. - public func select(star: Star) -> Query { + public func select(all: Expressible...) -> Query { var query = self - query.columns = star(nil, nil) + query.columns = SQLite.join(", ", all) return query } @@ -92,7 +81,24 @@ public struct Query { return query } + /// Sets the SELECT clause on the query. + /// + /// :param: star A literal *. + /// + /// :returns A query with SELECT * applied. // rdar://18778670 causes select(distinct: *) to make select(*) ambiguous + public func select(all star: Star) -> Query { + return select(star(nil, nil)) + } + + /// Sets the SELECT clause on the query. + /// + /// :param: star A literal *. + /// + /// :returns A query with SELECT * applied. + public func select(distinct star: Star) -> Query { + return select(distinct: star(nil, nil)) + } /// Adds an INNER JOIN clause to the query. /// From 162ee29058445369fe75884254f8756576db7ba6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Oct 2014 07:51:19 -0700 Subject: [PATCH 0021/1046] From: Apple Developer Relations > Engineering has determined that your bug report is a duplicate of > another issue and will be closed. > The open or closed status of the original bug report your issue was > duplicated to appears in the yellow "Duplicate of XXXXXXXX" section of > the bug reporter user interface. This section appears near the top of > the right column's bug detail view just under the bug number, title, > state, product and rank. Signed-off-by: Stephen Celis --- SQLite Common/Database.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index 60e2ad60..811cd03f 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -234,7 +234,7 @@ public final class Database { private func transaction(mode: TransactionMode, _ statements: [@autoclosure () -> Statement]) -> Statement { var transaction = run("BEGIN \(mode.rawValue) TRANSACTION") - // FIXME: rdar://18479820 // for statement in statements { transaction = transaction && statement() } + // FIXME: rdar://15217242 // for statement in statements { transaction = transaction && statement() } for idx in 0.. Statement]) -> Statement { let quotedName = quote(literal: name) var savepoint = run("SAVEPOINT \(quotedName)") - // FIXME: rdar://18479820 // for statement in statements { savepoint = savepoint && statement() } + // FIXME: rdar://15217242 // for statement in statements { savepoint = savepoint && statement() } for idx in 0.. Date: Wed, 29 Oct 2014 16:39:57 -0700 Subject: [PATCH 0022/1046] Add collation operator Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 7 +++++++ SQLite Common/Expression.swift | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index ca6aca90..5b505e29 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -243,6 +243,13 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches(db, "('Hello' MATCH 'ello')", query) } + func test_collateOperator_withStringExpression_buildsCollationExpression() { + let string = Expression(value: "Hello ") + ExpectExecutionMatches(db, "('Hello ' COLLATE BINARY)", users.select(collate(.Binary, string))) + ExpectExecutionMatches(db, "('Hello ' COLLATE NOCASE)", users.select(collate(.NoCase, string))) + ExpectExecutionMatches(db, "('Hello ' COLLATE RTRIM)", users.select(collate(.RTrim, string))) + } + func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { let bool = Expression(value: true) let query = users.select(bool && bool) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index e899fff1..76119540 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -174,6 +174,20 @@ public prefix func ~(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public enum Collation: String { + + case Binary = "BINARY" + + case NoCase = "NOCASE" + + case RTrim = "RTRIM" + +} + +public func collate(collation: Collation, expression: Expression) -> Expression { + return infix("COLLATE", expression, Expression(collation.rawValue)) +} + // MARK: - Predicates public func ==>(lhs: Expression, rhs: Expression) -> Expression { From 53909e4cdbeadb90f498062a6c7926dde584a17c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 30 Oct 2014 09:34:23 -0700 Subject: [PATCH 0023/1046] Workaround for segfault MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Archive currently triggers a segfault with the addition of these two postfix operators. The bug has been reported to Apple with and this commit acts as a less-than-DRY workaround in the meantime. Reported-by: Ferran Vilà Conesa <@fnva> Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 76119540..ec63ec01 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -533,8 +533,14 @@ public func ^=(column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public postfix func ++(rhs: Expression) -> Setter { return rhs += 1 } -public postfix func --(rhs: Expression) -> Setter { return rhs -= 1 } +public postfix func ++(column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column += 1 + return (column, Expression("(\(column.SQL) + 1)", column.bindings)) +} +public postfix func --(column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column -= 1 + return (column, Expression("(\(column.SQL) - 1)", column.bindings)) +} // MARK: - Internal From ffaa1ddf530c2e275bf1a7f8806a15a1bdf309c2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 09:36:33 -0700 Subject: [PATCH 0024/1046] Documentation fixes Copy-paste an error several times and you have more than several errors. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 987262ea..c3b47d22 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -63,7 +63,7 @@ public struct Query { /// /// :param: all A list of expressions to select. /// - /// :returns A query with the given SELECT clause applied. + /// :returns: A query with the given SELECT clause applied. public func select(all: Expressible...) -> Query { var query = self query.columns = SQLite.join(", ", all) @@ -74,7 +74,7 @@ public struct Query { /// /// :param: columns A list of expressions to select. /// - /// :returns A query with the given SELECT DISTINCT clause applied. + /// :returns: A query with the given SELECT DISTINCT clause applied. public func select(distinct columns: Expressible...) -> Query { var query = self query.columns = SQLite.join(" ", [Expression<()>("DISTINCT"), SQLite.join(", ", columns)]) @@ -85,7 +85,7 @@ public struct Query { /// /// :param: star A literal *. /// - /// :returns A query with SELECT * applied. + /// :returns: A query with SELECT * applied. // rdar://18778670 causes select(distinct: *) to make select(*) ambiguous public func select(all star: Star) -> Query { return select(star(nil, nil)) @@ -95,7 +95,7 @@ public struct Query { /// /// :param: star A literal *. /// - /// :returns A query with SELECT * applied. + /// :returns: A query with SELECT * applied. public func select(distinct star: Star) -> Query { return select(distinct: star(nil, nil)) } @@ -106,7 +106,7 @@ public struct Query { /// /// :param: on A boolean expression describing the join condition. /// - /// :returns A query with the given INNER JOIN clause applied. + /// :returns: A query with the given INNER JOIN clause applied. public func join(table: Query, on: Expression) -> Query { return join(.Inner, table, on: on) } @@ -119,7 +119,7 @@ public struct Query { /// /// :param: on A boolean expression describing the join condition. /// - /// :returns A query with the given JOIN clause applied. + /// :returns: A query with the given JOIN clause applied. public func join(type: JoinType, _ table: Query, on: Expression) -> Query { var query = self let condition = table.filter.map { on && $0 } ?? on @@ -132,7 +132,7 @@ public struct Query { /// /// :param: condition A boolean expression to filter on. /// - /// :returns A query with the given WHERE clause applied. + /// :returns: A query with the given WHERE clause applied. public func filter(condition: Expression) -> Query { var query = self query.filter = filter.map { $0 && condition } ?? condition @@ -189,7 +189,7 @@ public struct Query { /// /// :param: to The maximum number of rows to return. /// - /// :returns A query with the given LIMIT clause applied. + /// :returns: A query with the given LIMIT clause applied. public func limit(to: Int?) -> Query { return limit(to: to, offset: nil) } @@ -200,7 +200,7 @@ public struct Query { /// /// :param: offset The number of rows to skip. /// - /// :returns A query with the given LIMIT and OFFSET clauses applied. + /// :returns: A query with the given LIMIT and OFFSET clauses applied. public func limit(to: Int, offset: Int) -> Query { return limit(to: to, offset: offset) } From e1b94003ae1e68074e5a88f3abd222a5d0720484 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 16:31:37 -0700 Subject: [PATCH 0025/1046] Move comment to re-enable documentation Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index c3b47d22..118b8c88 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -81,12 +81,12 @@ public struct Query { return query } + // rdar://18778670 causes select(distinct: *) to make select(*) ambiguous /// Sets the SELECT clause on the query. /// /// :param: star A literal *. /// /// :returns: A query with SELECT * applied. - // rdar://18778670 causes select(distinct: *) to make select(*) ambiguous public func select(all star: Star) -> Query { return select(star(nil, nil)) } From b29d7ff38b3b7b6825988c3cb2268d10ad80b271 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 17:20:51 -0700 Subject: [PATCH 0026/1046] Query shouldn't be Expressible yet In all likelihood, a Query.expression will be a SELECT statement in the future. Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index ec63ec01..566c5230 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -86,14 +86,6 @@ extension String: Expressible { } -extension Query: Expressible { - - public var expression: Expression<()> { - return Expression(tableName) - } - -} - extension Expression: Expressible { public var expression: Expression<()> { From 77c753609d05e215e2240f5d0f89b460eceb3a6f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 17:24:54 -0700 Subject: [PATCH 0027/1046] Add table alias helper Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 2 +- SQLite Common/Query.swift | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 81564ab2..c12ba98b 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -212,7 +212,7 @@ class QueryTests: XCTestCase { func test_SQL_compilesProperly() { let admin = Expression("managers.admin") - let managers = db["users AS managers"].filter(admin == true) + let managers = users.alias("managers").filter(admin == true) let managers_id = Expression("managers.id") let users_manager_id = Expression("users.manager_id") let email = Expression("users.email") diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 118b8c88..77df48df 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -53,12 +53,19 @@ public struct Query { private var columns: Expressible = Expression<()>("*") internal var tableName: String + private var alias: String? private var joins = [Expressible]() private var filter: Expression? private var group: Expressible? private var order = [Expressible]() private var limit: (to: Int, offset: Int?)? = nil + public func alias(alias: String?) -> Query { + var query = self + query.alias = alias + return query + } + /// Sets the SELECT clause on the query. /// /// :param: all A list of expressions to select. @@ -123,7 +130,7 @@ public struct Query { public func join(type: JoinType, _ table: Query, on: Expression) -> Query { var query = self let condition = table.filter.map { on && $0 } ?? on - let expression = Expression<()>("\(type.rawValue) JOIN \(table.tableName) ON \(condition.SQL)", condition.bindings) + let expression = Expression<()>("\(type.rawValue) JOIN \(table) ON \(condition.SQL)", condition.bindings) query.joins.append(expression) return query } @@ -256,7 +263,7 @@ public struct Query { // MARK: - private var selectClause: Expressible { - return SQLite.join(" ", [Expression<()>("SELECT"), columns, Expression<()>("FROM \(tableName)")]) + return SQLite.join(" ", [Expression<()>("SELECT"), columns, Expression<()>("FROM \(self)")]) } private var joinClause: Expressible? { @@ -460,6 +467,16 @@ public struct QueryGenerator: GeneratorType { } +// MARK: - Printable +extension Query: Printable { + + public var description: String { + if let alias = alias { return "\(tableName) AS \(alias)" } + return tableName + } + +} + extension Database { public subscript(tableName: String) -> Query { From df490925340ed8efdf60d1f1cbb4bd288643c4a3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 17:25:17 -0700 Subject: [PATCH 0028/1046] Add table[column] namespacing helpers If you have an ambiguous column, you can prefix it with a namespace by simply subscripting the table with the column. let id = Expression("id") let manager_id = Expression("manager_id") let users = db["users"] let managers = users.alias("managers") users.join(managers, on: users[manager_id] == managers[id]) Unfortunately, Swift does not (yet) support subscript (see rdar://18673897), so I've had to hardcode in the common types by hand. Note: This is not a idempotent (yet). Expressions need to be enhanced with prefix/suffix data to be idempotent. This would help with ASC/DESC and expression-level aliasing, as well. Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 60 +++++++++++++++++----------- SQLite Common/Expression.swift | 4 +- SQLite Common/Query.swift | 44 ++++++++++++++++++++ 3 files changed, 83 insertions(+), 25 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index c12ba98b..4644264f 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -9,6 +9,7 @@ class QueryTests: XCTestCase { let id = Expression("id") let email = Expression("email") let age = Expression("age") + let salary = Expression("salary") let admin = Expression("admin") let manager_id = Expression("manager_id") @@ -91,22 +92,17 @@ class QueryTests: XCTestCase { } func test_join_whenChained_compilesAggregateJoinClause() { - let managers = db["users AS managers"] - let managers_id = Expression("managers.id") - let users_manager_id = Expression("users.manager_id") - - let managed = db["users AS managed"] - let managed_manager_id = Expression("users.id") - let users_id = Expression("managed.manager_id") + let managers = users.alias("managers") + let managed = users.alias("managed") - let query = users - .join(managers, on: managers_id == users_manager_id) - .join(managed, on: managed_manager_id == users_id) + let middleManagers = users + .join(managers, on: managers[id] == users[manager_id]) + .join(managed, on: managed[manager_id] == users[id]) let SQL = "SELECT * FROM users " + "INNER JOIN users AS managers ON (managers.id = users.manager_id) " + - "INNER JOIN users AS managed ON (users.id = managed.manager_id)" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + "INNER JOIN users AS managed ON (managed.manager_id = users.id)" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} } } func test_filter_compilesWhereClause() { @@ -210,20 +206,38 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} } } + func test_alias_compilesAliasInSelectClause() { + let managers = users.alias("managers") + var SQL = "SELECT * FROM users AS managers" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in managers {} } + } + + func test_subscript_withExpression_returnsNamespacedExpression() { + XCTAssertEqual("users.admin", users[admin].SQL) + XCTAssertEqual("users.salary", users[salary].SQL) + XCTAssertEqual("users.age", users[age].SQL) + XCTAssertEqual("users.email", users[email].SQL) + } + + func test_subscript_withAliasAndExpression_returnsAliasedExpression() { + let managers = users.alias("managers") + XCTAssertEqual("managers.admin", managers[admin].SQL) + XCTAssertEqual("managers.salary", managers[salary].SQL) + XCTAssertEqual("managers.age", managers[age].SQL) + XCTAssertEqual("managers.email", managers[email].SQL) + } + func test_SQL_compilesProperly() { - let admin = Expression("managers.admin") - let managers = users.alias("managers").filter(admin == true) - let managers_id = Expression("managers.id") - let users_manager_id = Expression("users.manager_id") - let email = Expression("users.email") - let age = Expression("users.age") + var managers = users.alias("managers") + // TODO: automatically namespace in the future? + managers = managers.filter(managers[admin] == true) let query = users - .select(email, count(email)) - .join(.LeftOuter, managers, on: managers_id == users_manager_id) - .filter(21..<32 ~= age) - .group(age, having: count(email) > 1) - .order(email.desc) + .select(users[email], count(users[email])) + .join(.LeftOuter, managers, on: managers[id] == users[manager_id]) + .filter(21..<32 ~= users[age]) + .group(users[age], having: count(users[email]) > 1) + .order(users[email].desc) .limit(1, offset: 2) let SQL = "SELECT users.email, count(users.email) FROM users " + diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 566c5230..40791f45 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -23,8 +23,8 @@ public struct Expression { - internal var SQL: String - internal var bindings: [Value?] + public let SQL: String + public let bindings: [Value?] public init(_ SQL: String = "", _ bindings: [Value?] = []) { (self.SQL, self.bindings) = (SQL, bindings) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 77df48df..b71a88d9 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -223,6 +223,50 @@ public struct Query { return query } + // MARK: - Namespacing + + // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression + + /// Prefixes a column expression with the query’s table name or alias. + /// + /// :param: column A column expression. + /// + /// :returns: A column expression namespaced with the query’s table name or + /// alias. + public subscript(column: Expression) -> Expression { + return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + } + + /// Prefixes a column expression with the query’s table name or alias. + /// + /// :param: column A column expression. + /// + /// :returns: A column expression namespaced with the query’s table name or + /// alias. + public subscript(column: Expression) -> Expression { + return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + } + + /// Prefixes a column expression with the query’s table name or alias. + /// + /// :param: column A column expression. + /// + /// :returns: A column expression namespaced with the query’s table name or + /// alias. + public subscript(column: Expression) -> Expression { + return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + } + + /// Prefixes a column expression with the query’s table name or alias. + /// + /// :param: column A column expression. + /// + /// :returns: A column expression namespaced with the query’s table name or + /// alias. + public subscript(column: Expression) -> Expression { + return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + } + // MARK: - Compiling Statements private var selectStatement: Statement { From 4c72b714d7cd40c3e34823729a2894eb191932d1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 25 Oct 2014 13:34:18 -0700 Subject: [PATCH 0029/1046] Add Travis CI support Signed-off-by: Stephen Celis --- .travis.yml | 4 ++++ Makefile | 6 +++--- README.md | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..94b2e5f3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: objective-c +osx_image: xcode61 +script: + - xcodebuild -scheme 'SQLite iOS' test diff --git a/Makefile b/Makefile index 31d57116..eee06f47 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,13 @@ BUILD_ARGUMENTS = -scheme 'SQLite $(BUILD_PLATFORM)' default: test build: - @$(BUILD_TOOL) $(BUILD_ARGUMENTS) + $(BUILD_TOOL) $(BUILD_ARGUMENTS) test: - @$(BUILD_TOOL) $(BUILD_ARGUMENTS) test + $(BUILD_TOOL) $(BUILD_ARGUMENTS) test clean: - @$(BUILD_TOOL) $(BUILD_ARGUMENTS) clean + $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean sloc: @zsh -c "grep -vE '^ *//|^$$' SQLite\ Common/*.{swift,h,c} | wc -l" diff --git a/README.md b/README.md index dc25a7b0..dc73f8f6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -# SQLite.swift +# SQLite.swift [![Build Status][0.1]][0.2] A pure [Swift][1.1] framework wrapping [SQLite3][1.2]. [SQLite.swift][1.3] aims to be small, simple, and safe. +[0.1]: https://img.shields.io/travis/stephencelis/SQLite.swift.svg?style=flat +[0.2]: https://travis-ci.org/stephencelis/SQLite.swift [1.1]: https://developer.apple.com/swift/ [1.2]: http://www.sqlite.org [1.3]: https://github.com/stephencelis/SQLite.swift From ff8ae71ea666b2b1ba973c160bdddb2e3c135a7b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 19:04:41 -0700 Subject: [PATCH 0030/1046] Add table[*] To select from a specific table only. users .select(users[*]) .join(managers, on: managers[id] == users[manager_id]) Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 3 ++- SQLite Common/Query.swift | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 4644264f..02abfc3c 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -217,6 +217,7 @@ class QueryTests: XCTestCase { XCTAssertEqual("users.salary", users[salary].SQL) XCTAssertEqual("users.age", users[age].SQL) XCTAssertEqual("users.email", users[email].SQL) + XCTAssertEqual("users.*", users[*].SQL) } func test_subscript_withAliasAndExpression_returnsAliasedExpression() { @@ -224,7 +225,7 @@ class QueryTests: XCTestCase { XCTAssertEqual("managers.admin", managers[admin].SQL) XCTAssertEqual("managers.salary", managers[salary].SQL) XCTAssertEqual("managers.age", managers[age].SQL) - XCTAssertEqual("managers.email", managers[email].SQL) + XCTAssertEqual("managers.*", managers[*].SQL) } func test_SQL_compilesProperly() { diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index b71a88d9..1f2f4cbf 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -267,6 +267,16 @@ public struct Query { return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) } + /// Prefixes a star with the query’s table name or alias. + /// + /// :param: star A literal *. + /// + /// :returns: A * expression namespaced with the query’s table name or + /// alias. + public subscript(star: Star) -> Expression<()> { + return Expression("\(alias ?? tableName).*") + } + // MARK: - Compiling Statements private var selectStatement: Statement { From cd4b7fa2872d635c64ccb2a2ec65c09542013435 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 1 Nov 2014 19:15:44 -0700 Subject: [PATCH 0031/1046] Add namespace helper (for extensibility) Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 1f2f4cbf..1a5ef6a0 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -225,6 +225,16 @@ public struct Query { // MARK: - Namespacing + /// Prefixes a column expression with the query’s table name or alias. + /// + /// :param: column A column expression. + /// + /// :returns: A column expression namespaced with the query’s table name or + /// alias. + public func namespace(column: Expression) -> Expression { + return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + } + // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression /// Prefixes a column expression with the query’s table name or alias. @@ -234,7 +244,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public subscript(column: Expression) -> Expression { - return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + return namespace(column) } /// Prefixes a column expression with the query’s table name or alias. @@ -244,7 +254,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public subscript(column: Expression) -> Expression { - return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + return namespace(column) } /// Prefixes a column expression with the query’s table name or alias. @@ -254,7 +264,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public subscript(column: Expression) -> Expression { - return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + return namespace(column) } /// Prefixes a column expression with the query’s table name or alias. @@ -264,7 +274,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public subscript(column: Expression) -> Expression { - return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + return namespace(column) } /// Prefixes a star with the query’s table name or alias. @@ -274,7 +284,7 @@ public struct Query { /// :returns: A * expression namespaced with the query’s table name or /// alias. public subscript(star: Star) -> Expression<()> { - return Expression("\(alias ?? tableName).*") + return namespace(star(nil, nil)) } // MARK: - Compiling Statements From 6e1a1fc7a3f36f974be20635716383c3138ef747 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sun, 2 Nov 2014 23:16:13 +1100 Subject: [PATCH 0032/1046] Support REPLACE statement. --- SQLite Common Tests/QueryTests.swift | 10 ++++++++ SQLite Common/Query.swift | 37 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 02abfc3c..ebfbfafa 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -280,6 +280,16 @@ class QueryTests: XCTestCase { XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID == nil) } + func test_replace_replaceRows() { + let SQL = "REPLACE INTO users (email, age) VALUES ('alice@example.com', 30)" + + ExpectExecutions(db, [SQL: 1]) { _ in + XCTAssertEqual(1, self.users.replace(self.email <- "alice@example.com", self.age <- 30).ID!) + } + + XCTAssertEqual(1, self.users.replace(self.id <- 1, self.email <- "bob@example.com", self.age <- 30).ID!) + } + func test_update_updatesRows() { InsertUsers(db, "alice", "betsy") InsertUser(db, "dolly", admin: true) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 1a5ef6a0..b369c0c3 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -309,6 +309,15 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } + private func replaceStatement(values: [Setter]) -> Statement { + var expressions: [Expressible] = [Expression<()>("REPLACE INTO \(tableName)")] + let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) + expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) + whereClause.map(expressions.append) + let expression = SQLite.join(" ", expressions) + return database.prepare(expression.SQL, expression.bindings) + } + private func updateStatement(values: [Setter]) -> Statement { var expressions: [Expressible] = [Expression<()>("UPDATE \(tableName) SET")] expressions.append(SQLite.join(", ", values.map { SQLite.join(" = ", [$0, $1]) })) @@ -425,6 +434,34 @@ public struct Query { return (statement.failed ? 0 : database.lastChanges ?? 0, statement) } + /// Runs a REPLACE statement against the query. + /// + /// :param: values A list of values to set. + /// + /// :returns: The statement. + public func replace(values: Setter...) -> Statement { return replace(values).statement } + + /// Runs a REPLACE statement against the query. + /// + /// :param: values A list of values to set. + /// + /// :returns: The row ID. + public func replace(values: Setter...) -> Int? { return replace(values).ID } + + /// Runs a REPLACE statement against the query. + /// + /// :param: values A list of values to set. + /// + /// :returns: The row ID and statement. + public func replace(values: Setter...) -> (ID: Int?, statement: Statement) { + return replace(values) + } + + private func replace(values: [Setter]) -> (ID: Int?, statement: Statement) { + let statement = replaceStatement(values).run() + return (statement.failed ? nil : database.lastID, statement) + } + /// Runs a DELETE statement against the query. /// /// :returns: The statement. From a966cb5eec6380b7ee9c4432265327277358f57b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Nov 2014 09:16:08 -0800 Subject: [PATCH 0033/1046] Update README and Playground to reflect actual expression building Signed-off-by: Stephen Celis --- README.md | 6 +++--- SQLite.playground/section-38.swift | 2 +- SQLite.playground/section-40.swift | 2 +- SQLite.playground/section-42.swift | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dc73f8f6..8831cb0e 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,10 @@ let admin = Expression("admin") let age = Expression("age") for user in users.filter(admin && age >= 30).order(age.desc) { /* ... */ } -// SELECT * FROM users WHERE (admin) AND (age >= 30) ORDER BY age DESC +// SELECT * FROM users WHERE (admin AND (age >= 30)) ORDER BY age DESC for user in users.group(age, having: count(age) == 1) { /* ... */ } -// SELECT * FROM users GROUP BY age HAVING count(age) = 1 +// SELECT * FROM users GROUP BY age HAVING (count(age) = 1) users.count // SELECT count(*) FROM users @@ -91,7 +91,7 @@ if let id = users.insert(email <- "fiona@example.com") { /* ... */ } let ageless = users.filter(admin && age == nil) let updates: Int = ageless.update(admin <- false) -// UPDATE users SET admin = 0 WHERE (admin) AND (age IS NULL) +// UPDATE users SET admin = 0 WHERE (admin AND (age IS NULL)) ``` diff --git a/SQLite.playground/section-38.swift b/SQLite.playground/section-38.swift index 90d8900d..c7c0ec5e 100644 --- a/SQLite.playground/section-38.swift +++ b/SQLite.playground/section-38.swift @@ -1,4 +1,4 @@ let agelessAdmins = admins.filter(age == nil) -// SELECT count(*) FROM users WHERE admin AND age IS NULL +// SELECT count(*) FROM users WHERE (admin AND age IS NULL) agelessAdmins.count diff --git a/SQLite.playground/section-40.swift b/SQLite.playground/section-40.swift index a61552fe..d1700c48 100644 --- a/SQLite.playground/section-40.swift +++ b/SQLite.playground/section-40.swift @@ -1,2 +1,2 @@ -// UPDATE users SET admin = 0 WHERE admin AND age IS NULL +// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL) agelessAdmins.update(admin <- false).changes diff --git a/SQLite.playground/section-42.swift b/SQLite.playground/section-42.swift index db4ce0e2..fa9b1a10 100644 --- a/SQLite.playground/section-42.swift +++ b/SQLite.playground/section-42.swift @@ -1,2 +1,2 @@ -// DELETE FROM users WHERE email = 'alice@acme.com' +// DELETE FROM users WHERE (email = 'alice@acme.com') users.filter(email == "alice@acme.com").delete().changes From 833e9a35d25bb233e93e06de10b7d5e1cd9f06a6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Nov 2014 09:19:42 -0800 Subject: [PATCH 0034/1046] Add Row object Behaves like a dictionary, mostly, but provides typed access to a row's values. Query objects yield them on iteration/access: let email = Expression("email") let users = db["users"] for user in users.select(email) { if let email = user[email] { // email is String } } Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 7 ++- SQLite Common/Query.swift | 63 ++++++++++++++++++- .../Documentation/fragment-14.html | 2 +- SQLite.playground/section-28.swift | 2 +- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 02abfc3c..21b8b0f6 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -258,7 +258,7 @@ class QueryTests: XCTestCase { func test_first_returnsTheFirstRow() { InsertUsers(db, "alice", "betsy") ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 1]) { _ in - XCTAssertEqual(1, self.users.first!["id"] as Int) + XCTAssertEqual(1, self.users.first![self.id]!) } } @@ -354,4 +354,9 @@ class QueryTests: XCTestCase { XCTAssertEqual(50.0, users.total(age)) } + func test_row_withBoundColumn_returnsValue() { + InsertUser(db, "alice", age: 20) + XCTAssertEqual(21, users.select(age + 1).first![age + 1]!) + } + } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 1a5ef6a0..7c2c354c 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -362,7 +362,7 @@ public struct Query { // MARK: - Array /// The first result (or nil if the query has no results). - public var first: Values? { return limit(1).generate().next() } + public var first: Row? { return limit(1).generate().next() } /// Returns true if the query has no results. public var isEmpty: Bool { return first == nil } @@ -508,6 +508,63 @@ public struct Query { } +/// A row object. Returned by iterating over a Query. Provides typed subscript +/// access to a row’s values. +public struct Row { + + private var values: Values + + private init(_ values: Values) { + self.values = values + } + + /// Returns a row’s value for the given column. + /// + /// :param: column An expression representing a column selected in a Query. + /// + /// returns The value for the given column. + public func get(column: Expression) -> T? { + return values[column.SQL] as? T + } + + /// Returns a row’s value for the given column. + /// + /// :param: column An expression representing a column selected in a Query. + /// + /// returns The value for the given column. + public subscript(column: Expression) -> Bool? { + return get(column) + } + + /// Returns a row’s value for the given column. + /// + /// :param: column An expression representing a column selected in a Query. + /// + /// returns The value for the given column. + public subscript(column: Expression) -> Double? { + return get(column) + } + + /// Returns a row’s value for the given column. + /// + /// :param: column An expression representing a column selected in a Query. + /// + /// returns The value for the given column. + public subscript(column: Expression) -> Int? { + return get(column) + } + + /// Returns a row’s value for the given column. + /// + /// :param: column An expression representing a column selected in a Query. + /// + /// returns The value for the given column. + public subscript(column: Expression) -> String? { + return get(column) + } + +} + // MARK: - SequenceType extension Query: SequenceType { @@ -524,9 +581,9 @@ public struct QueryGenerator: GeneratorType { private init(_ statement: Statement) { self.statement = statement } - public func next() -> Values? { + public func next() -> Row? { statement.next() - return statement.values + return statement.values.map { Row($0) } } } diff --git a/SQLite.playground/Documentation/fragment-14.html b/SQLite.playground/Documentation/fragment-14.html index 1cbf1f92..716f2a76 100644 --- a/SQLite.playground/Documentation/fragment-14.html +++ b/SQLite.playground/Documentation/fragment-14.html @@ -4,7 +4,7 @@

- You may notice that iteration works a little differently here. Rather than arrays of rows, we are given dictionaries of column names to row values. This gives us a little more powerful of a mapping to work with and pass around. + You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given Row objects, which can be subscripted with the same expressions we prepared above. This gives us a little more powerful of a mapping to work with and pass around.

Queries can be used and reused, and can quickly return rows, counts and other aggregate values. diff --git a/SQLite.playground/section-28.swift b/SQLite.playground/section-28.swift index 104bdcb3..043375a4 100644 --- a/SQLite.playground/section-28.swift +++ b/SQLite.playground/section-28.swift @@ -1,4 +1,4 @@ // SELECT * FROM users for user in users { - println(user["email"]) + println(user[email]) } From a43a3e72cce8aadf60ef9c41a01fa06014471d95 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Nov 2014 11:18:59 -0800 Subject: [PATCH 0035/1046] CI updates 1. Support xcpretty, for the pretty. 2. Test both Mac and iOS environments. Signed-off-by: Stephen Celis --- .travis.yml | 7 ++++++- Makefile | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94b2e5f3..23250f09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,9 @@ language: objective-c osx_image: xcode61 +env: + - BUILD_PLATFORM=iOS + - BUILD_PLATFORM=Mac +before_install: + - gem install xcpretty --no-document script: - - xcodebuild -scheme 'SQLite iOS' test + - make test diff --git a/Makefile b/Makefile index eee06f47..cf775d17 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,19 @@ BUILD_TOOL = xcodebuild BUILD_PLATFORM ?= Mac BUILD_ARGUMENTS = -scheme 'SQLite $(BUILD_PLATFORM)' +XCPRETTY := $(shell command -v xcpretty) + default: test build: $(BUILD_TOOL) $(BUILD_ARGUMENTS) test: +ifdef XCPRETTY + @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) test | $(XCPRETTY) -c +else $(BUILD_TOOL) $(BUILD_ARGUMENTS) test +endif clean: $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean From 049e943f9639caf0b2d812cfc6c05870a9c819f6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Nov 2014 11:20:00 -0800 Subject: [PATCH 0036/1046] Another FIXME to FIX when Swift is ready Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 7c2c354c..61e545f3 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -527,6 +527,8 @@ public struct Row { return values[column.SQL] as? T } + // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression + /// Returns a row’s value for the given column. /// /// :param: column An expression representing a column selected in a Query. From 622bc3940fab30a66b76beaf6950b981c395822b Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Mon, 3 Nov 2014 08:07:03 +1100 Subject: [PATCH 0037/1046] Add enum for ON CONFLICT clause and use it for .replace(). --- SQLite Common Tests/QueryTests.swift | 2 +- SQLite Common/Query.swift | 45 +++++++++++++++++----------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index ebfbfafa..e2a34805 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -281,7 +281,7 @@ class QueryTests: XCTestCase { } func test_replace_replaceRows() { - let SQL = "REPLACE INTO users (email, age) VALUES ('alice@example.com', 30)" + let SQL = "INSERT OR REPLACE INTO users (email, age) VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in XCTAssertEqual(1, self.users.replace(self.email <- "alice@example.com", self.age <- 30).ID!) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index b369c0c3..829d26f4 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -51,6 +51,22 @@ public struct Query { } + // INSERT OR x (ON CONFLICT) clause values. + public enum OnConflict: String { + // No ON CONFLICT clause. + case None = "" + + case Replace = "REPLACE" + + case Rollback = "ROLLBACK" + + case Abort = "ABORT" + + case Fail = "FAIL" + + case Ignore = "IGNORE" + } + private var columns: Expressible = Expression<()>("*") internal var tableName: String private var alias: String? @@ -300,17 +316,10 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - private func insertStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression<()>("INSERT INTO \(tableName)")] - let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) - expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) - whereClause.map(expressions.append) - let expression = SQLite.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - private func replaceStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression<()>("REPLACE INTO \(tableName)")] + private func insertStatement(values: [Setter], or: OnConflict = .None) -> Statement { + var onClause = (or == .None ? "" : " OR \(or.rawValue)") + var expressions: [Expressible] = [Expression<()>("INSERT\(onClause) INTO \(tableName)")] + println(expressions) let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) whereClause.map(expressions.append) @@ -458,10 +467,10 @@ public struct Query { } private func replace(values: [Setter]) -> (ID: Int?, statement: Statement) { - let statement = replaceStatement(values).run() + let statement = insertStatement(values, or: .Replace).run() return (statement.failed ? nil : database.lastID, statement) } - + /// Runs a DELETE statement against the query. /// /// :returns: The statement. @@ -565,23 +574,23 @@ public struct QueryGenerator: GeneratorType { statement.next() return statement.values } - + } // MARK: - Printable extension Query: Printable { - + public var description: String { if let alias = alias { return "\(tableName) AS \(alias)" } return tableName } - + } extension Database { - + public subscript(tableName: String) -> Query { return Query(self, tableName) } - + } From 3986bc561111cf500ede82df31113931d5d477cb Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Nov 2014 14:54:07 -0800 Subject: [PATCH 0038/1046] Cleanup - Whitespace woes. - Use an optional instead of an empty string. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index b184f904..766c8658 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -51,10 +51,8 @@ public struct Query { } - // INSERT OR x (ON CONFLICT) clause values. + /// ON CONFLICT resolutions. public enum OnConflict: String { - // No ON CONFLICT clause. - case None = "" case Replace = "REPLACE" @@ -65,6 +63,7 @@ public struct Query { case Fail = "FAIL" case Ignore = "IGNORE" + } private var columns: Expressible = Expression<()>("*") @@ -316,9 +315,10 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - private func insertStatement(values: [Setter], or: OnConflict = .None) -> Statement { - var onClause = (or == .None ? "" : " OR \(or.rawValue)") - var expressions: [Expressible] = [Expression<()>("INSERT\(onClause) INTO \(tableName)")] + private func insertStatement(values: [Setter], or: OnConflict? = nil) -> Statement { + var insertClause = "INSERT" + if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } + var expressions: [Expressible] = [Expression<()>("\(insertClause) INTO \(tableName)")] println(expressions) let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) @@ -633,23 +633,23 @@ public struct QueryGenerator: GeneratorType { statement.next() return statement.values.map { Row($0) } } - + } // MARK: - Printable extension Query: Printable { - + public var description: String { if let alias = alias { return "\(tableName) AS \(alias)" } return tableName } - + } extension Database { - + public subscript(tableName: String) -> Query { return Query(self, tableName) } - + } From 2accda6d983afeed1c4c0b721d497f3a1e9d913c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 2 Nov 2014 13:30:43 -0800 Subject: [PATCH 0039/1046] Add CREATE/DROP TABLE schema helpers CREATE TABLE uses a builder to create a table. Our usual example: db.create(table: users) { t in t.column(id, primaryKey: true) t.column(email, null: false, unique: true) t.column(manager_id, references: users[id]) } // CREATE TABLE users( // id INTEGER PRIMARY KEY, // email TEXT NOT NULL UNIQUE, // manager_id INTEGER REFERENCES users(id) // ) There are other table-level helpers for PRIMARY KEY (for composite keys), UNIQUE, CHECK, and FOREIGN KEY (for UPDATE/DELETE hooks). A few notes on foreign keys: 1. The shorthand, `references:` only supports INTEGER PRIMARY KEYs right now. This is subject to change, but single primary keys are generally INTEGERs, so let's encourage it as a best practice for now. (It's still possible to use the `foreignKey` function to link non-integer keys.) 2. Foreign keys do not work out-of-the-box with SQLite, as foreign key support is off by default. We'll probably want to enable them by default in the (near) future. 3. Composite foreign keys are not supported (yet). DROP TABLE omits the builder. db.drop(table: users) // DROP TABLE users Both `create` and `drop` functions call `table` out as an explicitly named parameter to both emphasize the action and provide room for INDEX helpers (and, potentially, TRIGGER and VIEW helpers). Neither yet support the IF NOT EXISTS clause. This wordiness doesn't feel right as a named parameter. Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 213 ++++++++++++++++++++++++++ SQLite Common/Database.swift | 2 +- SQLite Common/Expression.swift | 20 ++- SQLite Common/Query.swift | 34 ++-- SQLite Common/Schema.swift | 187 ++++++++++++++++++++++ SQLite Common/Value.swift | 10 ++ SQLite.xcodeproj/project.pbxproj | 12 ++ 7 files changed, 459 insertions(+), 19 deletions(-) create mode 100644 SQLite Common Tests/SchemaTests.swift create mode 100644 SQLite Common/Schema.swift diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift new file mode 100644 index 00000000..d4ddf54c --- /dev/null +++ b/SQLite Common Tests/SchemaTests.swift @@ -0,0 +1,213 @@ +import XCTest +import SQLite + +private let id = Expression("id") +private let email = Expression("email") +private let age = Expression("age") +private let salary = Expression("salary") +private let admin = Expression("admin") +private let manager_id = Expression("manager_id") + +class SchemaTests: XCTestCase { + + let db = Database() + var users: Query { return db["users"] } + + func test_createTable_column_buildsColumnDefinition() { + ExpectExecution(db, "CREATE TABLE users (email TEXT)", + db.create(table: users) { t in + t.column(email) + } + ) + } + + func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { + ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY)", + db.create(table: users) { t in + t.column(id, primaryKey: true) + } + ) + } + + func test_createTable_column_withNullFalse_buildsNotNullClause() { + ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL)", + db.create(table: users) { t in + t.column(email, null: false) + } + ) + } + + func test_createTable_column_withUnique_buildsUniqueClause() { + ExpectExecution(db, "CREATE TABLE users (email TEXT UNIQUE)", + db.create(table: users) { t in + t.column(email, unique: true) + } + ) + } + + func test_createTable_column_withCheck_buildsCheckClause() { + ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN CHECK (admin IN (0, 1)))", + db.create(table: users) { t in + t.column(admin, check: contains([false, true], admin)) + } + ) + } + + func test_createTable_column_withDefaultValue_buildsDefaultClause() { + ExpectExecution(db, "CREATE TABLE users (salary REAL DEFAULT 0.0)", + db.create(table: users) { t in + t.column(salary, defaultValue: 0.0) + } + ) + } + + func test_createTable_stringColumn_collation_buildsCollateClause() { + ExpectExecution(db, "CREATE TABLE users (email TEXT COLLATE NOCASE)", + db.create(table: users) { t in + t.column(email, collate: .NoCase) + } + ) + } + + func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES users(id))", + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id, references: users[id]) + } + ) + } + + func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES users)", + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id, references: users) + } + ) + } + + func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (email TEXT, PRIMARY KEY(email))", + db.create(table: users) { t in + t.column(email) + t.primaryKey(email) + } + ) + } + + func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (id INTEGER, email TEXT, PRIMARY KEY(id, email))", + db.create(table: users) { t in + t.column(id) + t.column(email) + t.primaryKey(id, email) + } + ) + } + + func test_createTable_unique_buildsUniqueTableConstraint() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (email TEXT, UNIQUE(email))", + db.create(table: users) { t in + t.column(email) + t.unique(email) + } + ) + } + + func test_createTable_unique_buildsCompositeUniqueTableConstraint() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (id INTEGER, email TEXT, UNIQUE(id, email))", + db.create(table: users) { t in + t.column(id) + t.column(email) + t.unique(id, email) + } + ) + } + + func test_createTable_check_buildsCheckTableConstraint() { + let users = self.users + ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN, CHECK (admin IN (0, 1)))", + db.create(table: users) { t in + t.column(admin) + t.check(contains([false, true], admin)) + } + ) + } + + func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() { + let users = self.users + ExpectExecution(db, + "CREATE TABLE users (" + + "id INTEGER PRIMARY KEY, " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users(id)" + + ")", + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users[id]) + } + ) + } + + func test_createTable_foreignKey_referencingTable_buildsForeignKeyTableConstraint() { + let users = self.users + ExpectExecution(db, + "CREATE TABLE users (" + + "id INTEGER PRIMARY KEY, " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users" + + ")", + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users) + } + ) + } + + func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() { + let users = self.users + ExpectExecution(db, + "CREATE TABLE users (" + + "id INTEGER PRIMARY KEY, " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users ON UPDATE CASCADE" + + ")", + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users, update: .Cascade) + } + ) + } + + func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() { + let users = self.users + ExpectExecution(db, + "CREATE TABLE users (" + + "id INTEGER PRIMARY KEY, " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users ON DELETE CASCADE" + + ")", + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users, delete: .Cascade) + } + ) + } + + func test_dropTable_dropsTable() { + CreateUsersTable(db) + ExpectExecution(db, "DROP TABLE users", db.drop(table: users) ) + } + +} diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index 811cd03f..2fa15f58 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -340,7 +340,7 @@ extension Database: DebugPrintable { } -private func quote(#literal: String) -> String { +internal func quote(#literal: String) -> String { let escaped = join("''", split(literal) { $0 == "'" }) return "'\(escaped)'" } diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 40791f45..27562fab 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -46,6 +46,15 @@ public struct Expression { return join(" ", [self, Expression("DESC")]) } + // naïve compiler for statements that can't be bound, e.g., CREATE TABLE + internal func compile() -> String { + var idx = 0 + return Array(SQL).reduce("") { SQL, character in + let string = String(character) + return SQL + (string == "?" ? transcode(self.bindings[idx++]) : string) + } + } + } public protocol Expressible { @@ -546,7 +555,16 @@ internal func join(separator: String, expressions: [Expressible]) -> Expression< return Expression(Swift.join(separator, SQL), bindings) } -private func wrap(function: String, expression: Expression) -> Expression { +internal func transcode(literal: Value?) -> String { + if let literal = literal { + if let literal = literal as? String { return quote(literal: literal) } + if let literal = literal as? Bool { return literal ? "1" : "0" } + return "\(literal)" + } + return "NULL" +} + +internal func wrap(function: String, expression: Expression) -> Expression { return Expression("\(function)\(surround(expression.SQL))", expression.bindings) } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 766c8658..b31e313b 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -28,7 +28,7 @@ public typealias Values = [String: Value?] /// helper functions. public struct Query { - private var database: Database + internal var database: Database internal init(_ database: Database, _ tableName: String) { self.database = database @@ -51,21 +51,6 @@ public struct Query { } - /// ON CONFLICT resolutions. - public enum OnConflict: String { - - case Replace = "REPLACE" - - case Rollback = "ROLLBACK" - - case Abort = "ABORT" - - case Fail = "FAIL" - - case Ignore = "IGNORE" - - } - private var columns: Expressible = Expression<()>("*") internal var tableName: String private var alias: String? @@ -315,6 +300,21 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } + /// ON CONFLICT resolutions. + public enum OnConflict: String { + + case Replace = "REPLACE" + + case Rollback = "ROLLBACK" + + case Abort = "ABORT" + + case Fail = "FAIL" + + case Ignore = "IGNORE" + + } + private func insertStatement(values: [Setter], or: OnConflict? = nil) -> Statement { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } @@ -353,7 +353,7 @@ public struct Query { return SQLite.join(" ", joins) } - private var whereClause: Expressible? { + internal var whereClause: Expressible? { if let filter = filter { return Expression<()>("WHERE \(filter.SQL)", filter.bindings) } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift new file mode 100644 index 00000000..619bcb42 --- /dev/null +++ b/SQLite Common/Schema.swift @@ -0,0 +1,187 @@ +// +// SQLite.Schema +// Copyright (c) 2014 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public extension Database { + + public func create(#table: Query, _ block: SchemaBuilder -> ()) -> Statement { + var builder = SchemaBuilder(table) + block(builder) + return builder.statement.run() + } + + public func drop(#table: Query) -> Statement { + return run("DROP TABLE \(table.tableName)") + } + +} + +public final class SchemaBuilder { + + let table: Query + var columns = [Expressible]() + + private init(_ table: Query) { + self.table = table + } + + public func column( + name: Expression, + primaryKey: Bool = false, + null: Bool = true, + unique: Bool = false, + check: Expression? = nil, + defaultValue: T? = nil + ) { + column(name, primaryKey, null, unique, check, defaultValue) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + null: Bool = true, + unique: Bool = false, + check: Expression? = nil, + defaultValue: String? = nil, + collate: Collation + ) { + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(name, primaryKey, null, unique, check, defaultValue, expressions) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + null: Bool = true, + unique: Bool = false, + check: Expression? = nil, + defaultValue: Int? = nil, + references: Expression + ) { + let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] + column(name, primaryKey, null, unique, check, defaultValue, expressions) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + null: Bool = true, + unique: Bool = false, + check: Expression? = nil, + defaultValue: Int? = nil, + references: Query + ) { + return column( + name, + primaryKey: primaryKey, + unique: unique, + check: check, + defaultValue: defaultValue, + references: Expression(references.tableName) + ) + } + + private func column( + name: Expression, + _ primaryKey: Bool, + _ null: Bool, + _ unique: Bool, + _ check: Expression?, + _ defaultValue: T?, + _ expressions: [Expressible]? = nil + ) { + var parts: [Expressible] = [name, Expression<()>(T.datatype)] + if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } + if !null { parts.append(Expression<()>("NOT NULL")) } + if unique { parts.append(Expression<()>("UNIQUE")) } + if let check = check { parts.append(Expression<()>("CHECK \(check.SQL)", check.bindings)) } + if let defaultValue = defaultValue { parts.append(Expression<()>("DEFAULT ?", [defaultValue])) } + if let expressions = expressions { parts += expressions } + columns.append(SQLite.join(" ", parts)) + } + + public func primaryKey(column: Expressible...) { + let primaryKey = SQLite.join(", ", column) + columns.append(Expression<()>("PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) + } + + public func unique(column: Expressible...) { + let unique = SQLite.join(", ", column) + columns.append(Expression<()>("UNIQUE(\(unique.SQL))", unique.bindings)) + } + + public func check(condition: Expression) { + columns.append(Expression<()>("CHECK \(condition.SQL)", condition.bindings)) + } + + public enum Dependency: String { + + case NoAction = "NO ACTION" + + case Restrict = "RESTRICT" + + case SetNull = "SET NULL" + + case SetDefault = "SET DEFAULT" + + case Cascade = "CASCADE" + + } + + public func foreignKey( + column: Expression, + references: Expression, + update: Dependency? = nil, + delete: Dependency? = nil + ) { + var parts: [Expressible] = [Expression<()>("FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] + parts.append(namespace(references)) + if let update = update { parts.append(Expression<()>("ON UPDATE \(update.rawValue)")) } + if let delete = delete { parts.append(Expression<()>("ON DELETE \(delete.rawValue)")) } + columns.append(SQLite.join(" ", parts)) + } + + public func foreignKey( + column: Expression, + references: Query, + update: Dependency? = nil, + delete: Dependency? = nil + ) { + foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) + } + + private var statement: Statement { + let expression = SQLite.join(", ", columns) + let SQL = "CREATE TABLE \(table.tableName) (\(expression.compile()))" + return table.database.prepare(SQL) + } + + private func namespace(expression: Expression) -> Expression { + if !contains(expression.SQL, ".") { return expression } + let reference = Array(expression.SQL).reduce("") { SQL, character in + let string = String(character) + return SQL + (string == "." ? "(" : string) + } + return Expression("\(reference))", expression.bindings) + } + +} diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index 4476a708..39d41eff 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -23,6 +23,8 @@ public protocol Value { + class var datatype: String { get } + func bindTo(statement: Statement, atIndex idx: Int) } @@ -31,6 +33,8 @@ public protocol Number: Value {} extension Bool: Value { + public static var datatype: String { return "BOOLEAN" } + public func bindTo(statement: Statement, atIndex idx: Int) { statement.bind(bool: self, atIndex: idx) } @@ -39,6 +43,8 @@ extension Bool: Value { extension Double: Number { + public static var datatype: String { return "REAL" } + public func bindTo(statement: Statement, atIndex idx: Int) { statement.bind(double: self, atIndex: idx) } @@ -47,6 +53,8 @@ extension Double: Number { extension Int: Number { + public static var datatype: String { return "INTEGER" } + public func bindTo(statement: Statement, atIndex idx: Int) { statement.bind(int: self, atIndex: idx) } @@ -55,6 +63,8 @@ extension Int: Number { extension String: Value { + public static var datatype: String { return "TEXT" } + public func bindTo(statement: Statement, atIndex idx: Int) { statement.bind(text: self, atIndex: idx) } diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 33d46350..f4ebd8ab 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -13,6 +13,10 @@ DC0FA83819D87E0C009F3A35 /* SQLite-Bridging.c in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.c */; }; DC0FA83919D87E0C009F3A35 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Private, ); }; }; DC0FA83A19D87E0C009F3A35 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Private, ); }; }; + DC109CE11A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; + DC109CE21A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; + DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; + DC109CE51A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; DC3773F919C8CBB3004FCF85 /* SQLite iOS.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite iOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; }; DC37741919C8CC2F004FCF85 /* SQLite Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = DC37741819C8CC2F004FCF85 /* SQLite Mac.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -62,6 +66,8 @@ DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging-Header.h"; sourceTree = ""; }; DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "SQLite-Bridging.c"; sourceTree = ""; }; DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + DC109CE01A0C4D970070988E /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; + DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; DC3773F319C8CBB3004FCF85 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC3773F819C8CBB3004FCF85 /* SQLite iOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SQLite iOS.h"; sourceTree = ""; }; @@ -130,6 +136,7 @@ children = ( DC650B9519F0CDC3002FBE91 /* Expression.swift */, DCAD429619E2E0F1004A51DF /* Query.swift */, + DC109CE01A0C4D970070988E /* Schema.swift */, ); name = "Query Building"; sourceTree = ""; @@ -152,6 +159,7 @@ DCF37F8419DDAF3F001534AA /* StatementTests.swift */, DC475E9E19F2199900788FBD /* ExpressionTests.swift */, DCAD429919E2EE50004A51DF /* QueryTests.swift */, + DC109CE31A0C4F5D0070988E /* SchemaTests.swift */, ); name = "SQLite Tests"; path = "SQLite Common Tests"; @@ -463,6 +471,7 @@ DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */, DC37743519C8D626004FCF85 /* Database.swift in Sources */, DC37743819C8D693004FCF85 /* Value.swift in Sources */, + DC109CE11A0C4D970070988E /* Schema.swift in Sources */, DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -473,6 +482,7 @@ files = ( DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */, DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */, + DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */, DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */, DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */, DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */, @@ -488,6 +498,7 @@ DC650B9719F0CDC3002FBE91 /* Expression.swift in Sources */, DC37743619C8D626004FCF85 /* Database.swift in Sources */, DC37743919C8D693004FCF85 /* Value.swift in Sources */, + DC109CE21A0C4D970070988E /* Schema.swift in Sources */, DC0FA83819D87E0C009F3A35 /* SQLite-Bridging.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -498,6 +509,7 @@ files = ( DC475EA119F219AE00788FBD /* ExpressionTests.swift in Sources */, DCAD429B19E2EE50004A51DF /* QueryTests.swift in Sources */, + DC109CE51A0C4F5D0070988E /* SchemaTests.swift in Sources */, DCF37F8319DDAC2D001534AA /* DatabaseTests.swift in Sources */, DCF37F8619DDAF3F001534AA /* StatementTests.swift in Sources */, DCF37F8919DDAF79001534AA /* TestHelper.swift in Sources */, From 869bd4c5d993a7d1177bf7231b1b4845c0a82284 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 8 Nov 2014 12:19:58 -0800 Subject: [PATCH 0040/1046] Add CREATE/DROP INDEX helpers Index name is auto-generated. db.create(index: users, on: email, unique: true) // CREATE UNIQUE INDEX index_users_on_email ON users (email) db.drop(index: users, on: email) // DROP INDEX index_users_on_email Partial indexes are available where applicable. At this time, Yosemite ships with support; iOS does not. db.create(index: users.filter(verified), on: email) // CREATE INDEX index_users_on_email ON users (email) WHERE verified Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 41 +++++++++++++++++++++++++++ SQLite Common/Schema.swift | 22 ++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index d4ddf54c..57651a90 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -210,4 +210,45 @@ class SchemaTests: XCTestCase { ExpectExecution(db, "DROP TABLE users", db.drop(table: users) ) } + func test_index_executesIndexStatement() { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE INDEX index_users_on_email ON users (email)", + db.create(index: users, on: email) + ) + } + + func test_index_withUniqueness_executesUniqueIndexStatement() { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE UNIQUE INDEX index_users_on_email ON users (email)", + db.create(index: users, unique: true, on: email) + ) + } + + func test_index_withMultipleColumns_executesCompoundIndexStatement() { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE INDEX index_users_on_age_DESC_email ON users (age DESC, email)", + db.create(index: users, on: age.desc, email) + ) + } + + func test_index_withFilter_executesPartialIndexStatementWithWhereClause() { + if SQLITE_VERSION >= "3.8" { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE INDEX index_users_on_age ON users (age) WHERE admin", + db.create(index: users.filter(admin), on: age) + ) + } + } + + func test_dropIndex_dropsIndex() { + CreateUsersTable(db) + db.create(index: users, on: email) + + ExpectExecution(db, "DROP INDEX index_users_on_email", db.drop(index: users, on: email)) + } + } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 619bcb42..159b75f6 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -33,6 +33,28 @@ public extension Database { return run("DROP TABLE \(table.tableName)") } + public func create(index table: Query, unique: Bool = false, on columns: Expressible...) -> Statement { + var parts: [Expressible] = [Expression<()>("CREATE")] + if unique { parts.append(Expression<()>("UNIQUE")) } + parts.append(Expression<()>("INDEX \(indexName(table, on: columns))")) + let joined = SQLite.join(", ", columns) + parts.append(Expression<()>("ON \(table.tableName) (\(joined.SQL))", joined.bindings)) + if SQLITE_VERSION >= "3.8" { table.whereClause.map(parts.append) } // partial indexes + return run(SQLite.join(" ", parts).compile()) + } + + public func drop(index table: Query, on columns: Expressible...) -> Statement { + return run("DROP INDEX \(indexName(table, on: columns))") + } + + private func indexName(table: Query, on columns: [Expressible]) -> String { + let string = join(" ", ["index", table.tableName, "on"] + columns.map { $0.expression.SQL }) + return Array(string).reduce("") { underscored, character in + if "A"..."Z" ~= character || "a"..."z" ~= character { return underscored + String(character) } + return underscored + "_" + } + } + } public final class SchemaBuilder { From b3bf4ea576fb68ee3f19fc0ebbd259be13d922ca Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 8 Nov 2014 12:40:31 -0800 Subject: [PATCH 0041/1046] Remove partial index support (for now) It's not widely available: SQLite3 3.8.0 introduced it, which appears to ship with Yosemite, but not iOS. Regardless, the build is failing on Travis's new Mac VMs, so more investigation is necessary to support this properly. Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 18 +++++++++--------- SQLite Common/Schema.swift | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 57651a90..54df81d0 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -234,15 +234,15 @@ class SchemaTests: XCTestCase { ) } - func test_index_withFilter_executesPartialIndexStatementWithWhereClause() { - if SQLITE_VERSION >= "3.8" { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE INDEX index_users_on_age ON users (age) WHERE admin", - db.create(index: users.filter(admin), on: age) - ) - } - } +// func test_index_withFilter_executesPartialIndexStatementWithWhereClause() { +// if SQLITE_VERSION >= "3.8" { +// CreateUsersTable(db) +// ExpectExecution(db, +// "CREATE INDEX index_users_on_age ON users (age) WHERE admin", +// db.create(index: users.filter(admin), on: age) +// ) +// } +// } func test_dropIndex_dropsIndex() { CreateUsersTable(db) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 159b75f6..2c86aa8b 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -39,7 +39,7 @@ public extension Database { parts.append(Expression<()>("INDEX \(indexName(table, on: columns))")) let joined = SQLite.join(", ", columns) parts.append(Expression<()>("ON \(table.tableName) (\(joined.SQL))", joined.bindings)) - if SQLITE_VERSION >= "3.8" { table.whereClause.map(parts.append) } // partial indexes + // if SQLITE_VERSION >= "3.8" { table.whereClause.map(parts.append) } // partial indexes return run(SQLite.join(" ", parts).compile()) } From 3c1ecc2a302110d2198cecbe5cd4048ccd4528b8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 8 Nov 2014 16:47:18 -0800 Subject: [PATCH 0042/1046] Add Database userVersion getter/setter For getting/setting the PRAGMA user_version. Signed-off-by: Stephen Celis --- SQLite Common Tests/DatabaseTests.swift | 6 ++++++ SQLite Common/Database.swift | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/SQLite Common Tests/DatabaseTests.swift b/SQLite Common Tests/DatabaseTests.swift index 3b63dc34..120092c6 100644 --- a/SQLite Common Tests/DatabaseTests.swift +++ b/SQLite Common Tests/DatabaseTests.swift @@ -187,4 +187,10 @@ class DatabaseTests: XCTestCase { } } + func test_userVersion_getsAndSetsUserVersion() { + XCTAssertEqual(0, db.userVersion) + db.userVersion = 1 + XCTAssertEqual(1, db.userVersion) + } + } diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index 2fa15f58..f60c2364 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -281,6 +281,13 @@ public final class Database { return savepoint } + // MARK: - Configuration + + public var userVersion: Int { + get { return scalar("PRAGMA user_version") as Int } + set { run("PRAGMA user_version = \(transcode(newValue))") } + } + // MARK: - Handlers /// Sets a busy timeout to retry after encountering a busy signal (lock). From f588d5e5d160d40decb7d200276abc21f1a3f2b1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 8 Nov 2014 17:10:26 -0800 Subject: [PATCH 0043/1046] Add protection around foreign key constraints SQLite3 disables foreign key constraints by default. Let's throw an assertion to alert developers when they create a constraint with a connection that hasn't enabled them. Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 5 +++++ SQLite Common/Schema.swift | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 54df81d0..e9fb6cf9 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -13,6 +13,11 @@ class SchemaTests: XCTestCase { let db = Database() var users: Query { return db["users"] } + override func setUp() { + super.setUp() + db.run("PRAGMA foreign_keys = ON") + } + func test_createTable_column_buildsColumnDefinition() { ExpectExecution(db, "CREATE TABLE users (email TEXT)", db.create(table: users) { t in diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 2c86aa8b..9b9f38e3 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -99,6 +99,7 @@ public final class SchemaBuilder { defaultValue: Int? = nil, references: Expression ) { + assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] column(name, primaryKey, null, unique, check, defaultValue, expressions) } @@ -175,6 +176,7 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { + assertForeignKeysEnabled() var parts: [Expressible] = [Expression<()>("FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] parts.append(namespace(references)) if let update = update { parts.append(Expression<()>("ON UPDATE \(update.rawValue)")) } @@ -206,4 +208,8 @@ public final class SchemaBuilder { return Expression("\(reference))", expression.bindings) } + private func assertForeignKeysEnabled() { + assert(table.database.scalar("PRAGMA foreign_keys") as Int == 1, "foreign key constraints are disabled") + } + } From a50278290b5bf324f22fdec239706cf6bc21b2ef Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 8 Nov 2014 18:52:02 -0800 Subject: [PATCH 0044/1046] Throw assertions for all non-constraint-based errors Even constraint failures can be programmer error, so we probably want to revisit this in the future. Signed-off-by: Stephen Celis --- SQLite Common/Statement.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index d6f57bf9..2a6c02d9 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -182,7 +182,10 @@ public final class Statement { private func try(block: @autoclosure () -> Int32) { if failed { return } status = block() - if failed { reason = String.fromCString(sqlite3_errmsg(database)) } + if failed { + reason = String.fromCString(sqlite3_errmsg(database)) + assert(status == SQLITE_CONSTRAINT, "\(reason!)") + } } } From 9523dd87d7a0dfef93302d53d1ae3ea14cd6ac30 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 9 Nov 2014 16:08:07 -0800 Subject: [PATCH 0045/1046] Clean up tests to use Query alias/subscript Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 1641478c..163de482 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -55,34 +55,28 @@ class QueryTests: XCTestCase { } func test_join_compilesJoinClause() { - let managers = db["users AS managers"] - let managers_id = Expression("managers.id") - let users_manager_id = Expression("users.manager_id") + let managers = db["users"].alias("managers") - let query = users.join(managers, on: managers_id == users_manager_id) + let query = users.join(managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM users INNER JOIN users AS managers ON (managers.id = users.manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_join_withExplicitType_compilesJoinClauseWithType() { - let managers = db["users AS managers"] - let managers_id = Expression("managers.id") - let users_manager_id = Expression("users.manager_id") + let managers = db["users"].alias("managers") - let query = users.join(.LeftOuter, managers, on: managers_id == users_manager_id) + let query = users.join(.LeftOuter, managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON (managers.id = users.manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { - let admin = Expression("managers.admin") - let managers = db["users AS managers"].filter(admin) - let managers_id = Expression("managers.id") - let users_manager_id = Expression("users.manager_id") + var managers = db["users"].alias("managers") + managers = managers.filter(managers[admin]) - let query = users.join(managers, on: managers_id == users_manager_id) + let query = users.join(managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM users " + "INNER JOIN users AS managers " + From af82c47cb4065dae6dceab75fda1a5ef85d05325 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 9 Nov 2014 18:19:19 -0800 Subject: [PATCH 0046/1046] Optional changes around Database.lastChanges and Query.{update,delete} 1. Database.lastChanges will no longer return an optional. We could still scope it by checking totalChanges > 0, but it doesn't seem worth it for that one case. This gives us a bit more flexibility. 2. Query.update and Query.delete now return optional Ints for changes. This makes it easier to check if a statement succeeded or not using `if`-`let`, and it makes it easier to run fire-and-forget statements by simply tacking on a `?` or `!`. // used for the flat map on an optional new ROWID, below let findByID: Int -> Query { users.filter(id == $0) } let alice = users.insert(email <- "alice@me.com").map(findByID) alice?.update(admin <- true)? alice?.delete()? Optionals make it easier to disambiguate inline. Signed-off-by: Stephen Celis --- README.md | 13 +++--- SQLite Common Tests/DatabaseTests.swift | 14 +++---- SQLite Common Tests/QueryTests.swift | 10 ++--- SQLite Common/Database.swift | 5 +-- SQLite Common/Query.swift | 54 ++++++++++++------------- 5 files changed, 48 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 8831cb0e..41560b00 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,7 @@ db.execute( "CREATE TABLE users (" + "id INTEGER PRIMARY KEY, " + "email TEXT NOT NULL UNIQUE, " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + + "manager_id INTEGER REFERENCES users" ")" ) @@ -48,7 +47,7 @@ for email in ["alice@example.com", "betsy@example.com"] { } db.totalChanges // 2 -db.lastChanges // {Some 1} +db.lastChanges // 1 db.lastID // {Some 2} for row in db.prepare("SELECT id, email FROM users") { @@ -74,7 +73,9 @@ let email = Expression("email") let admin = Expression("admin") let age = Expression("age") -for user in users.filter(admin && age >= 30).order(age.desc) { /* ... */ } +for user in users.filter(admin && age >= 30).order(age.desc) { + println("id: \(user[id]), email: \(user[email])") +} // SELECT * FROM users WHERE (admin AND (age >= 30)) ORDER BY age DESC for user in users.group(age, having: count(age) == 1) { /* ... */ } @@ -86,11 +87,11 @@ users.count users.filter(admin).average(age) // SELECT average(age) FROM users WHERE admin -if let id = users.insert(email <- "fiona@example.com") { /* ... */ } +users.insert(email <- "fiona@example.com")? // INSERT INTO users (email) VALUES ('fiona@example.com') let ageless = users.filter(admin && age == nil) -let updates: Int = ageless.update(admin <- false) +ageless.update(admin <- false)? // UPDATE users SET admin = 0 WHERE (admin AND (age IS NULL)) ``` diff --git a/SQLite Common Tests/DatabaseTests.swift b/SQLite Common Tests/DatabaseTests.swift index 120092c6..4683f8dc 100644 --- a/SQLite Common Tests/DatabaseTests.swift +++ b/SQLite Common Tests/DatabaseTests.swift @@ -29,23 +29,23 @@ class DatabaseTests: XCTestCase { XCTAssert(db.lastID! == 1) } - func test_lastChanges_returnsNilOnNewConnections() { - XCTAssert(db.lastChanges == nil) + func test_lastChanges_returnsZeroOnNewConnections() { + XCTAssertEqual(0, db.lastChanges) } func test_lastChanges_returnsNumberOfChanges() { InsertUser(db, "alice") - XCTAssert(db.lastChanges! == 1) + XCTAssertEqual(1, db.lastChanges) InsertUser(db, "betsy") - XCTAssert(db.lastChanges! == 1) + XCTAssertEqual(1, db.lastChanges) } func test_totalChanges_returnsTotalNumberOfChanges() { - XCTAssert(db.totalChanges == 0) + XCTAssertEqual(0, db.totalChanges) InsertUser(db, "alice") - XCTAssert(db.totalChanges == 1) + XCTAssertEqual(1, db.totalChanges) InsertUser(db, "betsy") - XCTAssert(db.totalChanges == 2) + XCTAssertEqual(2, db.totalChanges) } func test_prepare_preparesAndReturnsStatements() { diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 163de482..e1c7945d 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -288,17 +288,17 @@ class QueryTests: XCTestCase { InsertUsers(db, "alice", "betsy") InsertUser(db, "dolly", admin: true) - XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes) - XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes) + XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes!) + XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes!) } func test_delete_deletesRows() { InsertUser(db, "alice", age: 20) - XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes) + XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes!) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(2, users.delete().changes) - XCTAssertEqual(0, users.delete().changes) + XCTAssertEqual(2, users.delete().changes!) + XCTAssertEqual(0, users.delete().changes!) } func test_count_returnsCount() { diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index f60c2364..856cf680 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -58,9 +58,8 @@ public final class Database { /// The last number of changes (inserts, updates, or deletes) made to the /// database via this connection. - public var lastChanges: Int? { - let lastChanges = Int(sqlite3_changes(handle)) - return lastChanges == 0 ? nil : lastChanges + public var lastChanges: Int { + return Int(sqlite3_changes(handle)) } /// The total number of changes (inserts, updates, or deletes) made to the diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index b31e313b..8ae537f6 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -415,60 +415,60 @@ public struct Query { return (statement.failed ? nil : database.lastID, statement) } - /// Runs an UPDATE statement against the query. + /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The statement. - public func update(values: Setter...) -> Statement { return update(values).statement } + public func replace(values: Setter...) -> Statement { return replace(values).statement } - /// Runs an UPDATE statement against the query. + /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. /// - /// :returns: The number of updated rows. - public func update(values: Setter...) -> Int { return update(values).changes } + /// :returns: The row ID. + public func replace(values: Setter...) -> Int? { return replace(values).ID } - /// Runs an UPDATE statement against the query. + /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. /// - /// :returns: The number of updated rows and statement. - public func update(values: Setter...) -> (changes: Int, statement: Statement) { - return update(values) + /// :returns: The row ID and statement. + public func replace(values: Setter...) -> (ID: Int?, statement: Statement) { + return replace(values) } - private func update(values: [Setter]) -> (changes: Int, statement: Statement) { - let statement = updateStatement(values).run() - return (statement.failed ? 0 : database.lastChanges ?? 0, statement) + private func replace(values: [Setter]) -> (ID: Int?, statement: Statement) { + let statement = insertStatement(values, or: .Replace).run() + return (statement.failed ? nil : database.lastID, statement) } - /// Runs a REPLACE statement against the query. + /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The statement. - public func replace(values: Setter...) -> Statement { return replace(values).statement } + public func update(values: Setter...) -> Statement { return update(values).statement } - /// Runs a REPLACE statement against the query. + /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. /// - /// :returns: The row ID. - public func replace(values: Setter...) -> Int? { return replace(values).ID } + /// :returns: The number of updated rows. + public func update(values: Setter...) -> Int? { return update(values).changes } - /// Runs a REPLACE statement against the query. + /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. /// - /// :returns: The row ID and statement. - public func replace(values: Setter...) -> (ID: Int?, statement: Statement) { - return replace(values) + /// :returns: The number of updated rows and statement. + public func update(values: Setter...) -> (changes: Int?, statement: Statement) { + return update(values) } - private func replace(values: [Setter]) -> (ID: Int?, statement: Statement) { - let statement = insertStatement(values, or: .Replace).run() - return (statement.failed ? nil : database.lastID, statement) + private func update(values: [Setter]) -> (changes: Int?, statement: Statement) { + let statement = updateStatement(values).run() + return (statement.failed ? nil : database.lastChanges, statement) } /// Runs a DELETE statement against the query. @@ -479,14 +479,14 @@ public struct Query { /// Runs a DELETE statement against the query. /// /// :returns: The number of deleted rows. - public func delete() -> Int { return delete().changes } + public func delete() -> Int? { return delete().changes } /// Runs a DELETE statement against the query. /// /// :returns: The number of deleted rows and statement. - public func delete() -> (changes: Int, statement: Statement) { + public func delete() -> (changes: Int?, statement: Statement) { let statement = deleteStatement.run() - return (statement.failed ? 0 : database.lastChanges ?? 0, statement) + return (statement.failed ? nil : database.lastChanges, statement) } // MARK: - Aggregate Functions From 387ca6eb57a606a79dd448065f60f14b04843db2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 9 Nov 2014 18:50:46 -0800 Subject: [PATCH 0047/1046] Conform statement to BooleanType Makes it easy to use `if` for whether or not a statement ran and succeeded. Ideally, this would also disambiguate Query.{insert,update,delete}, but it does not (rdar forthcoming). Signed-off-by: Stephen Celis --- SQLite Common/Statement.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index 2a6c02d9..54ca0a7d 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -250,6 +250,13 @@ extension Statement: GeneratorType { } +// MARK: - BooleanType +extension Statement: BooleanType { + + public var boolValue: Bool { return status == SQLITE_DONE } + +} + // MARK: - DebugPrintable extension Statement: DebugPrintable { From 61fc66202edbacb0f1d79e5cead0f87fd8695363 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 9 Nov 2014 19:23:13 -0800 Subject: [PATCH 0048/1046] Update README for legibility This removes some of the more powerful examples in the spirit of being more readable. A wiki is coming, and so is a revamped playground, but writing is hard :) Signed-off-by: Stephen Celis --- README.md | 102 ++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 41560b00..4ee6a3ef 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # SQLite.swift [![Build Status][0.1]][0.2] -A pure [Swift][1.1] framework wrapping [SQLite3][1.2]. +A pure-[Swift][1.1], type-safe layer over [SQLite3][1.2]. -[SQLite.swift][1.3] aims to be small, simple, and safe. +[SQLite.swift][1.3] provides compile-time confidence in SQL statement +syntax _and_ intent. [0.1]: https://img.shields.io/travis/stephencelis/SQLite.swift.svg?style=flat [0.2]: https://travis-ci.org/stephencelis/SQLite.swift @@ -13,9 +14,10 @@ A pure [Swift][1.1] framework wrapping [SQLite3][1.2]. ## Features + - A powerful interface for building type-safe SQL expressions + - A flexible, chainable, lazy-executing query layer + - Automatically-typed data access - A lightweight, uncomplicated query and parameter binding interface - - A flexible, chainable, type-safe query builder - - Safe, automatically-typed data access - Transactions with implicit commit/rollback - Developer-friendly error handling and debugging - Well-documented @@ -24,76 +26,70 @@ A pure [Swift][1.1] framework wrapping [SQLite3][1.2]. ## Usage -Explore interactively from the Xcode project’s playground. - -![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) - ``` swift import SQLite let db = Database("path/to/db.sqlite3") -db.execute( - "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "manager_id INTEGER REFERENCES users" - ")" -) +let users = db["users"] +let id = Expression("id") +let email = Expression("email") -let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") -for email in ["alice@example.com", "betsy@example.com"] { - stmt.run(email) +db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(email, null: false, unique: true) } +// CREATE TABLE users ( +// id INTEGER PRIMARY KEY, +// email TEXT NOT NULL UNIQUE +// ) + +var alice: Query? +if let insertedID = users.insert(email <- "alice@mac.com") { + println("inserted id: \(insertedID)") + // inserted id: 1 + alice = users.filter(id == insertedID) +} +// INSERT INTO users (email) VALUES 'alice@mac.com' -db.totalChanges // 2 -db.lastChanges // 1 -db.lastID // {Some 2} - -for row in db.prepare("SELECT id, email FROM users") { - println("id: \(row[0]), email: \(row[1])") - // id: Optional(1), email: Optional("betsy@example.com") - // id: Optional(2), email: Optional("alice@example.com") +for user in users { + println("id: \(user[id]), email: \(user[email])" + // id: Optional(1), email: Optional("alice@mac.com") } +// SELECT * FROM users -db.scalar("SELECT count(*) FROM users") // {Some 2} +alice?.update(email <- "alice@me.com")? +// UPDATE users SET email = 'alice@me.com' WHERE (id = 1) -let jr = db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)") -db.transaction( - stmt.run("dolly@example.com"), - jr.run("emery@example.com", db.lastID) -) +alice?.delete()? +// DELETE FROM users WHERE (id = 1) ``` -SQLite.swift also exposes a powerful, type-safe query building interface. +SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C +API. ``` swift -let users = db["users"] -let email = Expression("email") -let admin = Expression("admin") -let age = Expression("age") - -for user in users.filter(admin && age >= 30).order(age.desc) { - println("id: \(user[id]), email: \(user[email])") +let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") +for email in ["betty@me.com", "cathy@me.com"] { + stmt.run(email) } -// SELECT * FROM users WHERE (admin AND (age >= 30)) ORDER BY age DESC -for user in users.group(age, having: count(age) == 1) { /* ... */ } -// SELECT * FROM users GROUP BY age HAVING (count(age) = 1) +db.totalChanges // 3 +db.lastChanges // {Some 1} +db.lastID // {Some 3} -users.count -// SELECT count(*) FROM users +for row in db.prepare("SELECT id, email FROM users") { + println("id: \(row[0]), email: \(row[1])") + // id: Optional(2), email: Optional("betty@example.com") + // id: Optional(3), email: Optional("cathy@example.com") +} -users.filter(admin).average(age) -// SELECT average(age) FROM users WHERE admin +db.scalar("SELECT count(*) FROM users") // {Some 2} +``` -users.insert(email <- "fiona@example.com")? -// INSERT INTO users (email) VALUES ('fiona@example.com') +Explore more, interactively, from the Xcode project’s playground. -let ageless = users.filter(admin && age == nil) -ageless.update(admin <- false)? -// UPDATE users SET admin = 0 WHERE (admin AND (age IS NULL)) -``` +![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) ## Installation From 51e7f95edcff3580706c91f91c95fc8a37c9a4a4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 10 Nov 2014 09:09:23 -0800 Subject: [PATCH 0049/1046] Add optional support to NULLable expressions This adds support for Expression to denote SQL expessions that may evaluate to NULL. It's verbose given the number of permutations to overload most operators, but brings a lot of benefits to the table: 1. Stricter typing. The compiler will let you know if you do something like try to compare a NOT NULL column to nil. let age = Expression("age") let id = Expression("id") users.filter(age == nil) users.filter(id == nil) // Won't compile. Meaningless. Functions like coalesce and ifnull will also require optional expressions. 2. Automatic NOT NULL support during schema creation. t.column(age) // age INTEGER t.column(id) // id INTEGER NOT NULL 3. Perhaps most importantly, the ability to automatically unwrap a row and return a value directly. user[age] // Optional(30) user[id] // 1 Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 620 +++++++++++++--------- SQLite Common Tests/QueryTests.swift | 4 +- SQLite Common Tests/SchemaTests.swift | 40 +- SQLite Common/Expression.swift | 558 ++++++++++--------- SQLite Common/Query.swift | 141 +++-- SQLite Common/Schema.swift | 85 ++- SQLite.playground/section-20.swift | 4 +- SQLite.playground/section-36.swift | 3 +- 8 files changed, 885 insertions(+), 570 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 5b505e29..40f8a05f 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -6,6 +6,22 @@ class ExpressionTests: XCTestCase { let db = Database() var users: Query { return db["users"] } + func ExpectExecutionMatches(SQL: String, _ expression: Expressible) { + ExpectExecution(db, "SELECT \(SQL) FROM users", users.select(expression)) + } + + let stringA = Expression(value: "A") + let stringB = Expression(value: "B") + + let int1 = Expression(value: 1) + let int2 = Expression(value: 2) + + let double1 = Expression(value: 1.5) + let double2 = Expression(value: 2.5) + + let bool0 = Expression(value: false) + let bool1 = Expression(value: true) + override func setUp() { super.setUp() @@ -13,540 +29,654 @@ class ExpressionTests: XCTestCase { } func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { - let string = Expression(value: "Hello") - ExpectExecutions(db, ["SELECT ('Hello' || 'Hello') FROM users": 3]) { _ in - for _ in self.users.select(string + string) {} - for _ in self.users.select(string + "Hello") {} - for _ in self.users.select("Hello" + string) {} - } + ExpectExecutionMatches("('A' || 'A')", stringA + stringA) + ExpectExecutionMatches("('A' || 'B')", stringA + stringB) + ExpectExecutionMatches("('B' || 'A')", stringB + stringA) + ExpectExecutionMatches("('B' || 'B')", stringB + stringB) + ExpectExecutionMatches("('A' || 'B')", stringA + "B") + ExpectExecutionMatches("('B' || 'A')", stringB + "A") + ExpectExecutionMatches("('B' || 'A')", "B" + stringA) + ExpectExecutionMatches("('A' || 'B')", "A" + stringB) } func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 + 2) FROM users": 3]) { _ in - for _ in self.users.select(int + int) {} - for _ in self.users.select(int + 2) {} - for _ in self.users.select(2 + int) {} - } + ExpectExecutionMatches("(1 + 1)", int1 + int1) + ExpectExecutionMatches("(1 + 2)", int1 + int2) + ExpectExecutionMatches("(2 + 1)", int2 + int1) + ExpectExecutionMatches("(2 + 2)", int2 + int2) + ExpectExecutionMatches("(1 + 2)", int1 + 2) + ExpectExecutionMatches("(2 + 1)", int2 + 1) + ExpectExecutionMatches("(2 + 1)", 2 + int1) + ExpectExecutionMatches("(1 + 2)", 1 + int2) } func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() { - let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT (2.0 + 2.0) FROM users": 3]) { _ in - for _ in self.users.select(double + double) {} - for _ in self.users.select(double + 2.0) {} - for _ in self.users.select(2.0 + double) {} - } + ExpectExecutionMatches("(1.5 + 1.5)", double1 + double1) + ExpectExecutionMatches("(1.5 + 2.5)", double1 + double2) + ExpectExecutionMatches("(2.5 + 1.5)", double2 + double1) + ExpectExecutionMatches("(2.5 + 2.5)", double2 + double2) + ExpectExecutionMatches("(1.5 + 2.5)", double1 + 2.5) + ExpectExecutionMatches("(2.5 + 1.5)", double2 + 1.5) + ExpectExecutionMatches("(2.5 + 1.5)", 2.5 + double1) + ExpectExecutionMatches("(1.5 + 2.5)", 1.5 + double2) } func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 - 2) FROM users": 3]) { _ in - for _ in self.users.select(int - int) {} - for _ in self.users.select(int - 2) {} - for _ in self.users.select(2 - int) {} - } + ExpectExecutionMatches("(1 - 1)", int1 - int1) + ExpectExecutionMatches("(1 - 2)", int1 - int2) + ExpectExecutionMatches("(2 - 1)", int2 - int1) + ExpectExecutionMatches("(2 - 2)", int2 - int2) + ExpectExecutionMatches("(1 - 2)", int1 - 2) + ExpectExecutionMatches("(2 - 1)", int2 - 1) + ExpectExecutionMatches("(2 - 1)", 2 - int1) + ExpectExecutionMatches("(1 - 2)", 1 - int2) } func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() { - let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT (2.0 - 2.0) FROM users": 3]) { _ in - for _ in self.users.select(double - double) {} - for _ in self.users.select(double - 2.0) {} - for _ in self.users.select(2.0 - double) {} - } + ExpectExecutionMatches("(1.5 - 1.5)", double1 - double1) + ExpectExecutionMatches("(1.5 - 2.5)", double1 - double2) + ExpectExecutionMatches("(2.5 - 1.5)", double2 - double1) + ExpectExecutionMatches("(2.5 - 2.5)", double2 - double2) + ExpectExecutionMatches("(1.5 - 2.5)", double1 - 2.5) + ExpectExecutionMatches("(2.5 - 1.5)", double2 - 1.5) + ExpectExecutionMatches("(2.5 - 1.5)", 2.5 - double1) + ExpectExecutionMatches("(1.5 - 2.5)", 1.5 - double2) } func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 * 2) FROM users": 3]) { _ in - for _ in self.users.select(int * int) {} - for _ in self.users.select(int * 2) {} - for _ in self.users.select(2 * int) {} - } + ExpectExecutionMatches("(1 * 1)", int1 * int1) + ExpectExecutionMatches("(1 * 2)", int1 * int2) + ExpectExecutionMatches("(2 * 1)", int2 * int1) + ExpectExecutionMatches("(2 * 2)", int2 * int2) + ExpectExecutionMatches("(1 * 2)", int1 * 2) + ExpectExecutionMatches("(2 * 1)", int2 * 1) + ExpectExecutionMatches("(2 * 1)", 2 * int1) + ExpectExecutionMatches("(1 * 2)", 1 * int2) } func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() { - let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT (2.0 * 2.0) FROM users": 3]) { _ in - for _ in self.users.select(double * double) {} - for _ in self.users.select(double * 2.0) {} - for _ in self.users.select(2.0 * double) {} - } + ExpectExecutionMatches("(1.5 * 1.5)", double1 * double1) + ExpectExecutionMatches("(1.5 * 2.5)", double1 * double2) + ExpectExecutionMatches("(2.5 * 1.5)", double2 * double1) + ExpectExecutionMatches("(2.5 * 2.5)", double2 * double2) + ExpectExecutionMatches("(1.5 * 2.5)", double1 * 2.5) + ExpectExecutionMatches("(2.5 * 1.5)", double2 * 1.5) + ExpectExecutionMatches("(2.5 * 1.5)", 2.5 * double1) + ExpectExecutionMatches("(1.5 * 2.5)", 1.5 * double2) } func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 / 2) FROM users": 3]) { _ in - for _ in self.users.select(int / int) {} - for _ in self.users.select(int / 2) {} - for _ in self.users.select(2 / int) {} - } + ExpectExecutionMatches("(1 / 1)", int1 / int1) + ExpectExecutionMatches("(1 / 2)", int1 / int2) + ExpectExecutionMatches("(2 / 1)", int2 / int1) + ExpectExecutionMatches("(2 / 2)", int2 / int2) + ExpectExecutionMatches("(1 / 2)", int1 / 2) + ExpectExecutionMatches("(2 / 1)", int2 / 1) + ExpectExecutionMatches("(2 / 1)", 2 / int1) + ExpectExecutionMatches("(1 / 2)", 1 / int2) } func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() { - let double = Expression(value: 2.0) - ExpectExecutions(db, ["SELECT (2.0 / 2.0) FROM users": 3]) { _ in - for _ in self.users.select(double / double) {} - for _ in self.users.select(double / 2.0) {} - for _ in self.users.select(2.0 / double) {} - } + ExpectExecutionMatches("(1.5 / 1.5)", double1 / double1) + ExpectExecutionMatches("(1.5 / 2.5)", double1 / double2) + ExpectExecutionMatches("(2.5 / 1.5)", double2 / double1) + ExpectExecutionMatches("(2.5 / 2.5)", double2 / double2) + ExpectExecutionMatches("(1.5 / 2.5)", double1 / 2.5) + ExpectExecutionMatches("(2.5 / 1.5)", double2 / 1.5) + ExpectExecutionMatches("(2.5 / 1.5)", 2.5 / double1) + ExpectExecutionMatches("(1.5 / 2.5)", 1.5 / double2) } func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 % 2) FROM users": 3]) { _ in - for _ in self.users.select(int % int) {} - for _ in self.users.select(int % 2) {} - for _ in self.users.select(2 % int) {} - } + ExpectExecutionMatches("(1 % 1)", int1 % int1) + ExpectExecutionMatches("(1 % 2)", int1 % int2) + ExpectExecutionMatches("(2 % 1)", int2 % int1) + ExpectExecutionMatches("(2 % 2)", int2 % int2) + ExpectExecutionMatches("(1 % 2)", int1 % 2) + ExpectExecutionMatches("(2 % 1)", int2 % 1) + ExpectExecutionMatches("(2 % 1)", 2 % int1) + ExpectExecutionMatches("(1 % 2)", 1 % int2) } func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 << 2) FROM users": 3]) { _ in - for _ in self.users.select(int << int) {} - for _ in self.users.select(int << 2) {} - for _ in self.users.select(2 << int) {} - } + ExpectExecutionMatches("(1 << 1)", int1 << int1) + ExpectExecutionMatches("(1 << 2)", int1 << int2) + ExpectExecutionMatches("(2 << 1)", int2 << int1) + ExpectExecutionMatches("(2 << 2)", int2 << int2) + ExpectExecutionMatches("(1 << 2)", int1 << 2) + ExpectExecutionMatches("(2 << 1)", int2 << 1) + ExpectExecutionMatches("(2 << 1)", 2 << int1) + ExpectExecutionMatches("(1 << 2)", 1 << int2) } func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 >> 2) FROM users": 3]) { _ in - for _ in self.users.select(int >> int) {} - for _ in self.users.select(int >> 2) {} - for _ in self.users.select(2 >> int) {} - } + ExpectExecutionMatches("(1 >> 1)", int1 >> int1) + ExpectExecutionMatches("(1 >> 2)", int1 >> int2) + ExpectExecutionMatches("(2 >> 1)", int2 >> int1) + ExpectExecutionMatches("(2 >> 2)", int2 >> int2) + ExpectExecutionMatches("(1 >> 2)", int1 >> 2) + ExpectExecutionMatches("(2 >> 1)", int2 >> 1) + ExpectExecutionMatches("(2 >> 1)", 2 >> int1) + ExpectExecutionMatches("(1 >> 2)", 1 >> int2) } func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 & 2) FROM users": 3]) { _ in - for _ in self.users.select(int & int) {} - for _ in self.users.select(int & 2) {} - for _ in self.users.select(2 & int) {} - } + ExpectExecutionMatches("(1 & 1)", int1 & int1) + ExpectExecutionMatches("(1 & 2)", int1 & int2) + ExpectExecutionMatches("(2 & 1)", int2 & int1) + ExpectExecutionMatches("(2 & 2)", int2 & int2) + ExpectExecutionMatches("(1 & 2)", int1 & 2) + ExpectExecutionMatches("(2 & 1)", int2 & 1) + ExpectExecutionMatches("(2 & 1)", 2 & int1) + ExpectExecutionMatches("(1 & 2)", 1 & int2) } func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 | 2) FROM users": 3]) { _ in - for _ in self.users.select(int | int) {} - for _ in self.users.select(int | 2) {} - for _ in self.users.select(2 | int) {} - } + ExpectExecutionMatches("(1 | 1)", int1 | int1) + ExpectExecutionMatches("(1 | 2)", int1 | int2) + ExpectExecutionMatches("(2 | 1)", int2 | int1) + ExpectExecutionMatches("(2 | 2)", int2 | int2) + ExpectExecutionMatches("(1 | 2)", int1 | 2) + ExpectExecutionMatches("(2 | 1)", int2 | 1) + ExpectExecutionMatches("(2 | 1)", 2 | int1) + ExpectExecutionMatches("(1 | 2)", 1 | int2) } func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { - let int1 = Expression(value: 1) - let int2 = Expression(value: 2) - ExpectExecutions(db, ["SELECT (~((1 & 2)) & (1 | 2)) FROM users": 3]) { _ in - for _ in self.users.select(int1 ^ int2) {} - for _ in self.users.select(int1 ^ 2) {} - for _ in self.users.select(1 ^ int2) {} - } + ExpectExecutionMatches("(~((1 & 1)) & (1 | 1))", int1 ^ int1) + ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", int1 ^ int2) + ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", int2 ^ int1) + ExpectExecutionMatches("(~((2 & 2)) & (2 | 2))", int2 ^ int2) + ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", int1 ^ 2) + ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", int2 ^ 1) + ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", 2 ^ int1) + ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", 1 ^ int2) } func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { - let int = Expression(value: 2) - ExpectExecutionMatches(db, "~(2)", users.select(~int)) + ExpectExecutionMatches("~(1)", ~int1) + ExpectExecutionMatches("~(2)", ~int2) } func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { - let bool = Expression(value: true) - ExpectExecutions(db, ["SELECT (1 = 1) FROM users": 3]) { _ in - for _ in self.users.select(bool == bool) {} - for _ in self.users.select(bool == true) {} - for _ in self.users.select(true == bool) {} - } + ExpectExecutionMatches("(0 = 0)", bool0 == bool0) + ExpectExecutionMatches("(0 = 1)", bool0 == bool1) + ExpectExecutionMatches("(1 = 0)", bool1 == bool0) + ExpectExecutionMatches("(1 = 1)", bool1 == bool1) + ExpectExecutionMatches("(0 = 1)", bool0 == true) + ExpectExecutionMatches("(1 = 0)", bool1 == false) + ExpectExecutionMatches("(1 = 0)", true == bool0) + ExpectExecutionMatches("(0 = 1)", false == bool1) } func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { - let bool = Expression(value: true) - ExpectExecutions(db, ["SELECT (1 != 1) FROM users": 3]) { _ in - for _ in self.users.select(bool != bool) {} - for _ in self.users.select(bool != true) {} - for _ in self.users.select(true != bool) {} - } + ExpectExecutionMatches("(0 != 0)", bool0 != bool0) + ExpectExecutionMatches("(0 != 1)", bool0 != bool1) + ExpectExecutionMatches("(1 != 0)", bool1 != bool0) + ExpectExecutionMatches("(1 != 1)", bool1 != bool1) + ExpectExecutionMatches("(0 != 1)", bool0 != true) + ExpectExecutionMatches("(1 != 0)", bool1 != false) + ExpectExecutionMatches("(1 != 0)", true != bool0) + ExpectExecutionMatches("(0 != 1)", false != bool1) } func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 > 2) FROM users": 3]) { _ in - for _ in self.users.select(int > int) {} - for _ in self.users.select(int > 2) {} - for _ in self.users.select(2 > int) {} - } + ExpectExecutionMatches("(1 > 1)", int1 > int1) + ExpectExecutionMatches("(1 > 2)", int1 > int2) + ExpectExecutionMatches("(2 > 1)", int2 > int1) + ExpectExecutionMatches("(2 > 2)", int2 > int2) + ExpectExecutionMatches("(1 > 2)", int1 > 2) + ExpectExecutionMatches("(2 > 1)", int2 > 1) + ExpectExecutionMatches("(2 > 1)", 2 > int1) + ExpectExecutionMatches("(1 > 2)", 1 > int2) } func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 >= 2) FROM users": 3]) { _ in - for _ in self.users.select(int >= int) {} - for _ in self.users.select(int >= 2) {} - for _ in self.users.select(2 >= int) {} - } + ExpectExecutionMatches("(1 >= 1)", int1 >= int1) + ExpectExecutionMatches("(1 >= 2)", int1 >= int2) + ExpectExecutionMatches("(2 >= 1)", int2 >= int1) + ExpectExecutionMatches("(2 >= 2)", int2 >= int2) + ExpectExecutionMatches("(1 >= 2)", int1 >= 2) + ExpectExecutionMatches("(2 >= 1)", int2 >= 1) + ExpectExecutionMatches("(2 >= 1)", 2 >= int1) + ExpectExecutionMatches("(1 >= 2)", 1 >= int2) } func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 < 2) FROM users": 3]) { _ in - for _ in self.users.select(int < int) {} - for _ in self.users.select(int < 2) {} - for _ in self.users.select(2 < int) {} - } + ExpectExecutionMatches("(1 < 1)", int1 < int1) + ExpectExecutionMatches("(1 < 2)", int1 < int2) + ExpectExecutionMatches("(2 < 1)", int2 < int1) + ExpectExecutionMatches("(2 < 2)", int2 < int2) + ExpectExecutionMatches("(1 < 2)", int1 < 2) + ExpectExecutionMatches("(2 < 1)", int2 < 1) + ExpectExecutionMatches("(2 < 1)", 2 < int1) + ExpectExecutionMatches("(1 < 2)", 1 < int2) } func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - let int = Expression(value: 2) - ExpectExecutions(db, ["SELECT (2 <= 2) FROM users": 3]) { _ in - for _ in self.users.select(int <= int) {} - for _ in self.users.select(int <= 2) {} - for _ in self.users.select(2 <= int) {} - } + ExpectExecutionMatches("(1 <= 1)", int1 <= int1) + ExpectExecutionMatches("(1 <= 2)", int1 <= int2) + ExpectExecutionMatches("(2 <= 1)", int2 <= int1) + ExpectExecutionMatches("(2 <= 2)", int2 <= int2) + ExpectExecutionMatches("(1 <= 2)", int1 <= 2) + ExpectExecutionMatches("(2 <= 1)", int2 <= 1) + ExpectExecutionMatches("(2 <= 1)", 2 <= int1) + ExpectExecutionMatches("(1 <= 2)", 1 <= int2) } func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() { - let int = Expression(value: 2) - let query = users.select(-int) - ExpectExecutionMatches(db, "-(2)", query) + ExpectExecutionMatches("-(1)", -int1) + ExpectExecutionMatches("-(2)", -int2) } func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() { - let double = Expression(value: 2.0) - let query = users.select(-double) - ExpectExecutionMatches(db, "-(2.0)", query) + ExpectExecutionMatches("-(1.5)", -double1) + ExpectExecutionMatches("-(2.5)", -double2) } func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() { - let int = Expression(value: 2) - let query = users.select(0...5 ~= int) - ExpectExecutionMatches(db, "2 BETWEEN 0 AND 5", query) + ExpectExecutionMatches("1 BETWEEN 0 AND 5", 0...5 ~= int1) + ExpectExecutionMatches("2 BETWEEN 0 AND 5", 0...5 ~= int2) } func test_likeOperator_withStringExpression_buildsLikeExpression() { - let string = Expression(value: "Hello") - let query = users.select(like("%ello", string)) - ExpectExecutionMatches(db, "('Hello' LIKE '%ello')", query) + ExpectExecutionMatches("('A' LIKE 'B%')", like("B%", stringA)) + ExpectExecutionMatches("('B' LIKE 'A%')", like("A%", stringB)) } func test_globOperator_withStringExpression_buildsGlobExpression() { - let string = Expression(value: "Hello") - let query = users.select(glob("*ello", string)) - ExpectExecutionMatches(db, "('Hello' GLOB '*ello')", query) + ExpectExecutionMatches("('A' GLOB 'B*')", glob("B*", stringA)) + ExpectExecutionMatches("('B' GLOB 'A*')", glob("A*", stringB)) } func test_matchOperator_withStringExpression_buildsMatchExpression() { - let string = Expression(value: "Hello") - let query = users.select(match("ello", string)) - ExpectExecutionMatches(db, "('Hello' MATCH 'ello')", query) + ExpectExecutionMatches("('A' MATCH 'B')", match("B", stringA)) + ExpectExecutionMatches("('B' MATCH 'A')", match("A", stringB)) } func test_collateOperator_withStringExpression_buildsCollationExpression() { - let string = Expression(value: "Hello ") - ExpectExecutionMatches(db, "('Hello ' COLLATE BINARY)", users.select(collate(.Binary, string))) - ExpectExecutionMatches(db, "('Hello ' COLLATE NOCASE)", users.select(collate(.NoCase, string))) - ExpectExecutionMatches(db, "('Hello ' COLLATE RTRIM)", users.select(collate(.RTrim, string))) + ExpectExecutionMatches("('A' COLLATE BINARY)", collate(.Binary, stringA)) + ExpectExecutionMatches("('B' COLLATE NOCASE)", collate(.NoCase, stringB)) + ExpectExecutionMatches("('A' COLLATE RTRIM)", collate(.RTrim, stringA)) } func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { - let bool = Expression(value: true) - let query = users.select(bool && bool) - ExpectExecutionMatches(db, "(1 AND 1)", query) + ExpectExecutionMatches("(0 AND 0)", bool0 && bool0) + ExpectExecutionMatches("(0 AND 1)", bool0 && bool1) + ExpectExecutionMatches("(1 AND 0)", bool1 && bool0) + ExpectExecutionMatches("(1 AND 1)", bool1 && bool1) + ExpectExecutionMatches("(0 AND 1)", bool0 && true) + ExpectExecutionMatches("(1 AND 0)", bool1 && false) + ExpectExecutionMatches("(1 AND 0)", true && bool0) + ExpectExecutionMatches("(0 AND 1)", false && bool1) } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { - let bool = Expression(value: true) - let query = users.select(bool || bool) - ExpectExecutionMatches(db, "(1 OR 1)", query) + ExpectExecutionMatches("(0 OR 0)", bool0 || bool0) + ExpectExecutionMatches("(0 OR 1)", bool0 || bool1) + ExpectExecutionMatches("(1 OR 0)", bool1 || bool0) + ExpectExecutionMatches("(1 OR 1)", bool1 || bool1) + ExpectExecutionMatches("(0 OR 1)", bool0 || true) + ExpectExecutionMatches("(1 OR 0)", bool1 || false) + ExpectExecutionMatches("(1 OR 0)", true || bool0) + ExpectExecutionMatches("(0 OR 1)", false || bool1) } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { - let bool = Expression(value: true) - let query = users.select(!bool) - ExpectExecutionMatches(db, "NOT (1)", query) + ExpectExecutionMatches("NOT (0)", !bool0) + ExpectExecutionMatches("NOT (1)", !bool1) } func test_absFunction_withNumberExpressions_buildsAbsExpression() { - let int = Expression(value: -2) - let query = users.select(abs(int)) - ExpectExecutionMatches(db, "abs(-2)", query) + let int1 = Expression(value: -1) + let int2 = Expression(value: -2) + + ExpectExecutionMatches("abs(-1)", abs(int1)) + ExpectExecutionMatches("abs(-2)", abs(int2)) } func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { - let int1 = Expression(value: nil) - let int2 = Expression(value: nil) - let int3 = Expression(value: 3) - let query = users.select(coalesce(int1, int2, int3)) - ExpectExecutionMatches(db, "coalesce(NULL, NULL, 3)", query) + let int1 = Expression(value: nil) + let int2 = Expression(value: nil) + let int3 = Expression(value: 3) + + ExpectExecutionMatches("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3)) } func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { - let int = Expression(value: nil) - ExpectExecutionMatches(db, "ifnull(NULL, 1)", users.select(ifnull(int, 1))) - ExpectExecutionMatches(db, "ifnull(NULL, 1)", users.select(int ?? 1)) + let int = Expression(value: nil) + + ExpectExecutionMatches("ifnull(NULL, 1)", ifnull(int, 1)) + ExpectExecutionMatches("ifnull(NULL, 1)", int ?? 1) } func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { - let string = Expression(value: "Hello") - let query = users.select(length(string)) - ExpectExecutionMatches(db, "length('Hello')", query) + ExpectExecutionMatches("length('A')", length(stringA)) + ExpectExecutionMatches("length('B')", length(stringB)) } func test_lowerFunction_withStringExpression_buildsLowerStringExpression() { - let string = Expression(value: "Hello") - let query = users.select(lower(string)) - ExpectExecutionMatches(db, "lower('Hello')", query) + ExpectExecutionMatches("lower('A')", lower(stringA)) + ExpectExecutionMatches("lower('B')", lower(stringB)) } func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() { - let string = Expression(value: " Hello") - let query = users.select(ltrim(string)) - ExpectExecutionMatches(db, "ltrim(' Hello')", query) + ExpectExecutionMatches("ltrim('A')", ltrim(stringA)) + ExpectExecutionMatches("ltrim('B')", ltrim(stringB)) } func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - let string = Expression(value: "Hello") - let query = users.select(ltrim(string, "H")) - ExpectExecutionMatches(db, "ltrim('Hello', 'H')", query) + ExpectExecutionMatches("ltrim('A', 'A?')", ltrim(stringA, "A?")) + ExpectExecutionMatches("ltrim('B', 'B?')", ltrim(stringB, "B?")) } func test_randomFunction_buildsRandomIntExpression() { - let query = users.select(random) - ExpectExecutionMatches(db, "random()", query) + ExpectExecutionMatches("random()", random) } func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() { - let string = Expression(value: "Hello") - let query = users.select(replace(string, "He", "E")) - ExpectExecutionMatches(db, "replace('Hello', 'He', 'E')", query) + ExpectExecutionMatches("replace('A', 'A', 'B')", replace(stringA, "A", "B")) + ExpectExecutionMatches("replace('B', 'B', 'A')", replace(stringB, "B", "A")) } func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() { - let double = Expression(value: 3.14159) - let query = users.select(round(double)) - ExpectExecutionMatches(db, "round(3.14159)", query) + ExpectExecutionMatches("round(1.5)", round(double1)) + ExpectExecutionMatches("round(2.5)", round(double2)) } func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() { - let double = Expression(value: 3.14159) - let query = users.select(round(double, 2)) - ExpectExecutionMatches(db, "round(3.14159, 2)", query) + ExpectExecutionMatches("round(1.5, 1)", round(double1, 1)) + ExpectExecutionMatches("round(2.5, 1)", round(double2, 1)) } func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() { - let string = Expression(value: "Hello ") - let query = users.select(rtrim(string)) - ExpectExecutionMatches(db, "rtrim('Hello ')", query) + ExpectExecutionMatches("rtrim('A')", rtrim(stringA)) + ExpectExecutionMatches("rtrim('B')", rtrim(stringB)) } func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - let string = Expression(value: "Hello") - let query = users.select(rtrim(string, "lo")) - ExpectExecutionMatches(db, "rtrim('Hello', 'lo')", query) + ExpectExecutionMatches("rtrim('A', 'A?')", rtrim(stringA, "A?")) + ExpectExecutionMatches("rtrim('B', 'B?')", rtrim(stringB, "B?")) } func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() { - let string = Expression(value: "Hello") - let query = users.select(substr(string, 1)) - ExpectExecutionMatches(db, "substr('Hello', 1)", query) + ExpectExecutionMatches("substr('A', 1)", substr(stringA, 1)) + ExpectExecutionMatches("substr('B', 1)", substr(stringB, 1)) } func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() { - let string = Expression(value: "Hello") - let query = users.select(substr(string, 1, 2)) - ExpectExecutionMatches(db, "substr('Hello', 1, 2)", query) + ExpectExecutionMatches("substr('A', 1, 2)", substr(stringA, 1, 2)) + ExpectExecutionMatches("substr('B', 1, 2)", substr(stringB, 1, 2)) } func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() { - let string = Expression(value: "Hello") - let query = users.select(substr(string, 1..<3)) - ExpectExecutionMatches(db, "substr('Hello', 1, 2)", query) + ExpectExecutionMatches("substr('A', 1, 2)", substr(stringA, 1..<3)) + ExpectExecutionMatches("substr('B', 1, 2)", substr(stringB, 1..<3)) } func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() { - let string = Expression(value: " Hello ") - let query = users.select(trim(string)) - ExpectExecutionMatches(db, "trim(' Hello ')", query) + ExpectExecutionMatches("trim('A')", trim(stringA)) + ExpectExecutionMatches("trim('B')", trim(stringB)) } func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - let string = Expression(value: "Hello") - let query = users.select(trim(string, "lo")) - ExpectExecutionMatches(db, "trim('Hello', 'lo')", query) + ExpectExecutionMatches("trim('A', 'A?')", trim(stringA, "A?")) + ExpectExecutionMatches("trim('B', 'B?')", trim(stringB, "B?")) } func test_upperFunction_withStringExpression_buildsLowerStringExpression() { - let string = Expression(value: "Hello") - let query = users.select(upper(string)) - ExpectExecutionMatches(db, "upper('Hello')", query) + ExpectExecutionMatches("upper('A')", upper(stringA)) + ExpectExecutionMatches("upper('B')", upper(stringB)) } let id = Expression("id") - let age = Expression("age") + let age = Expression("age") let email = Expression("email") + let email2 = Expression("email") let salary = Expression("salary") + let salary2 = Expression("salary") let admin = Expression("admin") + let admin2 = Expression("admin") func test_countFunction_withExpression_buildsCountExpression() { - ExpectExecutionMatches(db, "count(id)", users.select(count(id))) - ExpectExecutionMatches(db, "count(email)", users.select(count(email))) - ExpectExecutionMatches(db, "count(salary)", users.select(count(salary))) - ExpectExecutionMatches(db, "count(admin)", users.select(count(admin))) + ExpectExecutionMatches("count(id)", count(id)) + ExpectExecutionMatches("count(age)", count(age)) + ExpectExecutionMatches("count(email)", count(email)) + ExpectExecutionMatches("count(email)", count(email2)) + ExpectExecutionMatches("count(salary)", count(salary)) + ExpectExecutionMatches("count(salary)", count(salary2)) + ExpectExecutionMatches("count(admin)", count(admin)) + ExpectExecutionMatches("count(admin)", count(admin2)) } func test_countFunction_withStar_buildsCountExpression() { - ExpectExecutionMatches(db, "count(*)", users.select(count(*))) + ExpectExecutionMatches("count(*)", count(*)) } func test_maxFunction_withExpression_buildsMaxExpression() { - ExpectExecutionMatches(db, "max(id)", users.select(max(id))) - ExpectExecutionMatches(db, "max(email)", users.select(max(email))) - ExpectExecutionMatches(db, "max(salary)", users.select(max(salary))) - ExpectExecutionMatches(db, "max(admin)", users.select(max(admin))) + ExpectExecutionMatches("max(id)", max(id)) + ExpectExecutionMatches("max(age)", max(age)) + ExpectExecutionMatches("max(email)", max(email)) + ExpectExecutionMatches("max(email)", max(email2)) + ExpectExecutionMatches("max(salary)", max(salary)) + ExpectExecutionMatches("max(salary)", max(salary2)) + ExpectExecutionMatches("max(admin)", max(admin)) + ExpectExecutionMatches("max(admin)", max(admin2)) } func test_minFunction_withExpression_buildsMinExpression() { - ExpectExecutionMatches(db, "min(id)", users.select(min(id))) - ExpectExecutionMatches(db, "min(email)", users.select(min(email))) - ExpectExecutionMatches(db, "min(salary)", users.select(min(salary))) - ExpectExecutionMatches(db, "min(admin)", users.select(min(admin))) + ExpectExecutionMatches("min(id)", min(id)) + ExpectExecutionMatches("min(age)", min(age)) + ExpectExecutionMatches("min(email)", min(email)) + ExpectExecutionMatches("min(email)", min(email2)) + ExpectExecutionMatches("min(salary)", min(salary)) + ExpectExecutionMatches("min(salary)", min(salary2)) + ExpectExecutionMatches("min(admin)", min(admin)) + ExpectExecutionMatches("min(admin)", min(admin2)) } func test_averageFunction_withExpression_buildsAverageExpression() { - ExpectExecutionMatches(db, "avg(id)", users.select(average(id))) - ExpectExecutionMatches(db, "avg(salary)", users.select(average(salary))) + ExpectExecutionMatches("avg(id)", average(id)) + ExpectExecutionMatches("avg(age)", average(age)) + ExpectExecutionMatches("avg(salary)", average(salary)) + ExpectExecutionMatches("avg(salary)", average(salary2)) } func test_sumFunction_withExpression_buildsSumExpression() { - ExpectExecutionMatches(db, "sum(id)", users.select(sum(id))) - ExpectExecutionMatches(db, "sum(salary)", users.select(sum(salary))) + ExpectExecutionMatches("sum(id)", sum(id)) + ExpectExecutionMatches("sum(age)", sum(age)) + ExpectExecutionMatches("sum(salary)", sum(salary)) + ExpectExecutionMatches("sum(salary)", sum(salary2)) } func test_totalFunction_withExpression_buildsTotalExpression() { - ExpectExecutionMatches(db, "total(id)", users.select(total(id))) - ExpectExecutionMatches(db, "total(salary)", users.select(total(salary))) + ExpectExecutionMatches("total(id)", total(id)) + ExpectExecutionMatches("total(age)", total(age)) + ExpectExecutionMatches("total(salary)", total(salary)) + ExpectExecutionMatches("total(salary)", total(salary2)) } func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - ExpectExecutionMatches(db, "(id IN (1, 2, 3))", users.select(contains([1, 2, 3], id))) + ExpectExecutionMatches("(id IN (1, 2, 3))", contains([1, 2, 3], id)) + ExpectExecutionMatches("(age IN (20, 30, 40))", contains([20, 30, 40], age)) } func test_plusEquals_withStringExpression_buildsSetter() { let SQL = "UPDATE users SET email = (email || email)" ExpectExecution(db, SQL, users.update(email += email)) + ExpectExecution(db, SQL, users.update(email += email2)) + ExpectExecution(db, SQL, users.update(email2 += email)) + ExpectExecution(db, SQL, users.update(email2 += email2)) } func test_plusEquals_withStringValue_buildsSetter() { let SQL = "UPDATE users SET email = (email || '.com')" ExpectExecution(db, SQL, users.update(email += ".com")) + ExpectExecution(db, SQL, users.update(email2 += ".com")) } func test_plusEquals_withNumberExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age + age)", users.update(age += age)) + ExpectExecution(db, "UPDATE users SET age = (age + id)", users.update(age += id)) + ExpectExecution(db, "UPDATE users SET id = (id + age)", users.update(id += age)) + ExpectExecution(db, "UPDATE users SET id = (id + id)", users.update(id += id)) ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary += salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary += salary2)) + ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary2 += salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary2 += salary2)) } func test_plusEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET id = (id + 1)", users.update(id += 1)) ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age += 1)) ExpectExecution(db, "UPDATE users SET salary = (salary + 100.0)", users.update(salary += 100)) + ExpectExecution(db, "UPDATE users SET salary = (salary + 100.0)", users.update(salary2 += 100)) } func test_minusEquals_withNumberExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age - age)", users.update(age -= age)) + ExpectExecution(db, "UPDATE users SET age = (age - id)", users.update(age -= id)) + ExpectExecution(db, "UPDATE users SET id = (id - age)", users.update(id -= age)) + ExpectExecution(db, "UPDATE users SET id = (id - id)", users.update(id -= id)) ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary -= salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary -= salary2)) + ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary2 -= salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary2 -= salary2)) } func test_minusEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET id = (id - 1)", users.update(id -= 1)) ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age -= 1)) ExpectExecution(db, "UPDATE users SET salary = (salary - 100.0)", users.update(salary -= 100)) + ExpectExecution(db, "UPDATE users SET salary = (salary - 100.0)", users.update(salary2 -= 100)) } func test_timesEquals_withNumberExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age * age)", users.update(age *= age)) + ExpectExecution(db, "UPDATE users SET age = (age * id)", users.update(age *= id)) + ExpectExecution(db, "UPDATE users SET id = (id * age)", users.update(id *= age)) + ExpectExecution(db, "UPDATE users SET id = (id * id)", users.update(id *= id)) ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary *= salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary *= salary2)) + ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary2 *= salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary2 *= salary2)) } func test_timesEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET id = (id * 1)", users.update(id *= 1)) ExpectExecution(db, "UPDATE users SET age = (age * 1)", users.update(age *= 1)) ExpectExecution(db, "UPDATE users SET salary = (salary * 100.0)", users.update(salary *= 100)) + ExpectExecution(db, "UPDATE users SET salary = (salary * 100.0)", users.update(salary2 *= 100)) } func test_divideEquals_withNumberExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age / age)", users.update(age /= age)) + ExpectExecution(db, "UPDATE users SET age = (age / id)", users.update(age /= id)) + ExpectExecution(db, "UPDATE users SET id = (id / age)", users.update(id /= age)) + ExpectExecution(db, "UPDATE users SET id = (id / id)", users.update(id /= id)) ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary /= salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary /= salary2)) + ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary2 /= salary)) + ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary2 /= salary2)) } func test_divideEquals_withNumberValue_buildsSetter() { + ExpectExecution(db, "UPDATE users SET id = (id / 1)", users.update(id /= 1)) ExpectExecution(db, "UPDATE users SET age = (age / 1)", users.update(age /= 1)) ExpectExecution(db, "UPDATE users SET salary = (salary / 100.0)", users.update(salary /= 100)) + ExpectExecution(db, "UPDATE users SET salary = (salary / 100.0)", users.update(salary2 /= 100)) } func test_moduloEquals_withIntegerExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age % age)", users.update(age %= age)) + ExpectExecution(db, "UPDATE users SET age = (age % id)", users.update(age %= id)) + ExpectExecution(db, "UPDATE users SET id = (id % age)", users.update(id %= age)) + ExpectExecution(db, "UPDATE users SET id = (id % id)", users.update(id %= id)) } func test_moduloEquals_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age % 10)", users.update(age %= 10)) + ExpectExecution(db, "UPDATE users SET id = (id % 10)", users.update(id %= 10)) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age >> age)", users.update(age >>= age)) + ExpectExecution(db, "UPDATE users SET age = (age >> id)", users.update(age >>= id)) + ExpectExecution(db, "UPDATE users SET id = (id >> age)", users.update(id >>= age)) + ExpectExecution(db, "UPDATE users SET id = (id >> id)", users.update(id >>= id)) } func test_rightShiftEquals_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age >> 1)", users.update(age >>= 1)) + ExpectExecution(db, "UPDATE users SET id = (id >> 1)", users.update(id >>= 1)) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age << age)", users.update(age <<= age)) + ExpectExecution(db, "UPDATE users SET age = (age << id)", users.update(age <<= id)) + ExpectExecution(db, "UPDATE users SET id = (id << age)", users.update(id <<= age)) + ExpectExecution(db, "UPDATE users SET id = (id << id)", users.update(id <<= id)) } func test_leftShiftEquals_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age << 1)", users.update(age <<= 1)) + ExpectExecution(db, "UPDATE users SET id = (id << 1)", users.update(id <<= 1)) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age & age)", users.update(age &= age)) + ExpectExecution(db, "UPDATE users SET age = (age & id)", users.update(age &= id)) + ExpectExecution(db, "UPDATE users SET id = (id & age)", users.update(id &= age)) + ExpectExecution(db, "UPDATE users SET id = (id & id)", users.update(id &= id)) } func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age & 1)", users.update(age &= 1)) + ExpectExecution(db, "UPDATE users SET id = (id & 1)", users.update(id &= 1)) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age | age)", users.update(age |= age)) + ExpectExecution(db, "UPDATE users SET age = (age | id)", users.update(age |= id)) + ExpectExecution(db, "UPDATE users SET id = (id | age)", users.update(id |= age)) + ExpectExecution(db, "UPDATE users SET id = (id | id)", users.update(id |= id)) } func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age | 1)", users.update(age |= 1)) + ExpectExecution(db, "UPDATE users SET id = (id | 1)", users.update(id |= 1)) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (~((age & age)) & (age | age))", users.update(age ^= age)) + ExpectExecution(db, "UPDATE users SET age = (~((age & id)) & (age | id))", users.update(age ^= id)) + ExpectExecution(db, "UPDATE users SET id = (~((id & age)) & (id | age))", users.update(id ^= age)) + ExpectExecution(db, "UPDATE users SET id = (~((id & id)) & (id | id))", users.update(id ^= id)) } func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (~((age & 1)) & (age | 1))", users.update(age ^= 1)) + ExpectExecution(db, "UPDATE users SET id = (~((id & 1)) & (id | 1))", users.update(id ^= 1)) } func test_postfixPlus_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age++)) + ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age++)) + ExpectExecution(db, "UPDATE users SET id = (id + 1)", users.update(id++)) + ExpectExecution(db, "UPDATE users SET id = (id + 1)", users.update(id++)) } func test_postfixMinus_withIntegerValue_buildsSetter() { ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age--)) + ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age--)) + ExpectExecution(db, "UPDATE users SET id = (id - 1)", users.update(id--)) + ExpectExecution(db, "UPDATE users SET id = (id - 1)", users.update(id--)) } func test_precedencePreserved() { let n = Expression(value: 1) - ExpectExecutionMatches(db, "(((1 = 1) AND (1 = 1)) OR (1 = 1))", users.select((n == n && n == n) || n == n)) - ExpectExecutionMatches(db, "((1 = 1) AND ((1 = 1) OR (1 = 1)))", users.select(n == n && (n == n || n == n))) + ExpectExecutionMatches("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) + ExpectExecutionMatches("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } } - -func ExpectExecutionMatches(db: Database, SQL: String, query: Query) { - ExpectExecution(db, "SELECT \(SQL) FROM users", query) -} diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index e1c7945d..f289cee7 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -8,7 +8,7 @@ class QueryTests: XCTestCase { let id = Expression("id") let email = Expression("email") - let age = Expression("age") + let age = Expression("age") let salary = Expression("salary") let admin = Expression("admin") let manager_id = Expression("manager_id") @@ -252,7 +252,7 @@ class QueryTests: XCTestCase { func test_first_returnsTheFirstRow() { InsertUsers(db, "alice", "betsy") ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 1]) { _ in - XCTAssertEqual(1, self.users.first![self.id]!) + XCTAssertEqual(1, self.users.first![self.id]) } } diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index e9fb6cf9..d407cb70 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -3,10 +3,10 @@ import SQLite private let id = Expression("id") private let email = Expression("email") -private let age = Expression("age") +private let age = Expression("age") private let salary = Expression("salary") private let admin = Expression("admin") -private let manager_id = Expression("manager_id") +private let manager_id = Expression("manager_id") class SchemaTests: XCTestCase { @@ -19,7 +19,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_buildsColumnDefinition() { - ExpectExecution(db, "CREATE TABLE users (email TEXT)", + ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL)", db.create(table: users) { t in t.column(email) } @@ -27,7 +27,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { - ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY)", + ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL)", db.create(table: users) { t in t.column(id, primaryKey: true) } @@ -37,13 +37,13 @@ class SchemaTests: XCTestCase { func test_createTable_column_withNullFalse_buildsNotNullClause() { ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL)", db.create(table: users) { t in - t.column(email, null: false) + t.column(email) } ) } func test_createTable_column_withUnique_buildsUniqueClause() { - ExpectExecution(db, "CREATE TABLE users (email TEXT UNIQUE)", + ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL UNIQUE)", db.create(table: users) { t in t.column(email, unique: true) } @@ -51,7 +51,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withCheck_buildsCheckClause() { - ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN CHECK (admin IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN NOT NULL CHECK (admin IN (0, 1)))", db.create(table: users) { t in t.column(admin, check: contains([false, true], admin)) } @@ -59,7 +59,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withDefaultValue_buildsDefaultClause() { - ExpectExecution(db, "CREATE TABLE users (salary REAL DEFAULT 0.0)", + ExpectExecution(db, "CREATE TABLE users (salary REAL NOT NULL DEFAULT 0.0)", db.create(table: users) { t in t.column(salary, defaultValue: 0.0) } @@ -67,7 +67,7 @@ class SchemaTests: XCTestCase { } func test_createTable_stringColumn_collation_buildsCollateClause() { - ExpectExecution(db, "CREATE TABLE users (email TEXT COLLATE NOCASE)", + ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL COLLATE NOCASE)", db.create(table: users) { t in t.column(email, collate: .NoCase) } @@ -76,7 +76,7 @@ class SchemaTests: XCTestCase { func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES users(id))", + ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, manager_id INTEGER REFERENCES users(id))", db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id, references: users[id]) @@ -86,7 +86,7 @@ class SchemaTests: XCTestCase { func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, manager_id INTEGER REFERENCES users)", + ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, manager_id INTEGER REFERENCES users)", db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id, references: users) @@ -96,7 +96,7 @@ class SchemaTests: XCTestCase { func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (email TEXT, PRIMARY KEY(email))", + ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL, PRIMARY KEY(email))", db.create(table: users) { t in t.column(email) t.primaryKey(email) @@ -106,7 +106,7 @@ class SchemaTests: XCTestCase { func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER, email TEXT, PRIMARY KEY(id, email))", + ExpectExecution(db, "CREATE TABLE users (id INTEGER NOT NULL, email TEXT NOT NULL, PRIMARY KEY(id, email))", db.create(table: users) { t in t.column(id) t.column(email) @@ -117,7 +117,7 @@ class SchemaTests: XCTestCase { func test_createTable_unique_buildsUniqueTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (email TEXT, UNIQUE(email))", + ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL, UNIQUE(email))", db.create(table: users) { t in t.column(email) t.unique(email) @@ -127,7 +127,7 @@ class SchemaTests: XCTestCase { func test_createTable_unique_buildsCompositeUniqueTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER, email TEXT, UNIQUE(id, email))", + ExpectExecution(db, "CREATE TABLE users (id INTEGER NOT NULL, email TEXT NOT NULL, UNIQUE(id, email))", db.create(table: users) { t in t.column(id) t.column(email) @@ -138,7 +138,7 @@ class SchemaTests: XCTestCase { func test_createTable_check_buildsCheckTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN, CHECK (admin IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN NOT NULL, CHECK (admin IN (0, 1)))", db.create(table: users) { t in t.column(admin) t.check(contains([false, true], admin)) @@ -150,7 +150,7 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + "FOREIGN KEY(manager_id) REFERENCES users(id)" + ")", @@ -166,7 +166,7 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + "FOREIGN KEY(manager_id) REFERENCES users" + ")", @@ -182,7 +182,7 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + "FOREIGN KEY(manager_id) REFERENCES users ON UPDATE CASCADE" + ")", @@ -198,7 +198,7 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + "FOREIGN KEY(manager_id) REFERENCES users ON DELETE CASCADE" + ")", diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 27562fab..de9fc47a 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -30,8 +30,8 @@ public struct Expression { (self.SQL, self.bindings) = (SQL, bindings) } - public init(value: Expression) { - self.init(value.SQL, value.bindings) + public init(_ expression: Expression) { + self.init(expression.SQL, expression.bindings) } public init(value: Value?) { @@ -98,82 +98,114 @@ extension String: Expressible { extension Expression: Expressible { public var expression: Expression<()> { - return Expression<()>(SQL, bindings) + return Expression<()>(self) } } // MARK: - Expressions -public func +(lhs: Expression, rhs: Expression) -> Expression { - return infix("||", lhs, rhs) -} +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(value: rhs) } +public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(value: rhs) } public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } +public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func +(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func +(lhs: Expression, rhs: T) -> Expression { return lhs + Expression(value: rhs) } +public func +(lhs: Expression, rhs: T) -> Expression { return lhs + Expression(value: rhs) } public func +(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } +public func +(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func -(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func -(lhs: Expression, rhs: T) -> Expression { return lhs - Expression(value: rhs) } +public func -(lhs: Expression, rhs: T) -> Expression { return lhs - Expression(value: rhs) } public func -(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } +public func -(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } -public func *(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func *(lhs: Expression, rhs: T) -> Expression { return lhs * Expression(value: rhs) } +public func *(lhs: Expression, rhs: T) -> Expression { return lhs * Expression(value: rhs) } public func *(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } +public func *(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } -public func /(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func /(lhs: Expression, rhs: T) -> Expression { return lhs / Expression(value: rhs) } +public func /(lhs: Expression, rhs: T) -> Expression { return lhs / Expression(value: rhs) } public func /(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } +public func /(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } -public func %(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(value: rhs) } +public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(value: rhs) } public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } +public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } -public func <<(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(value: rhs) } +public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(value: rhs) } public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } +public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } -public func >>(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) } +public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) } public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } +public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } -public func &(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(value: rhs) } +public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(value: rhs) } public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } +public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } -public func |(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} +public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(value: rhs) } +public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(value: rhs) } public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } +public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } -public func ^(lhs: Expression, rhs: Expression) -> Expression { - return (~(lhs & rhs)) & (lhs | rhs) -} +public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(value: rhs) } +public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(value: rhs) } public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } +public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } -public prefix func ~(rhs: Expression) -> Expression { - return wrap(__FUNCTION__, rhs) -} +public prefix func ~(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public prefix func ~(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public enum Collation: String { @@ -186,226 +218,250 @@ public enum Collation: String { } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.rawValue)) + return infix("COLLATE", expression, Expression(collation.rawValue)) +} +public func collate(collation: Collation, expression: Expression) -> Expression { + return infix("COLLATE", expression, Expression(collation.rawValue)) } // MARK: - Predicates -public func ==>(lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func ==>(lhs: Expression, rhs: T?) -> Expression { +public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } +public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } +public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } +public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } +public func ==(lhs: Expression, rhs: T) -> Expression { return lhs == Expression(value: rhs) } +public func ==(lhs: Expression, rhs: T?) -> Expression { if let rhs = rhs { return lhs == Expression(value: rhs) } return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) } -public func ==>(lhs: T?, rhs: Expression) -> Expression { +public func ==(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) == rhs } +public func ==(lhs: T?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs) == rhs } return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) } -public func !=>(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func !=>(lhs: Expression, rhs: T?) -> Expression { +public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func !=(lhs: Expression, rhs: T) -> Expression { return lhs != Expression(value: rhs) } +public func !=(lhs: Expression, rhs: T?) -> Expression { if let rhs = rhs { return lhs != Expression(value: rhs) } return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) } -public func !=>(lhs: T?, rhs: Expression) -> Expression { +public func !=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) != rhs } +public func !=(lhs: T?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs) != rhs } return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) } -public func >>(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >>(lhs: Expression, rhs: T) -> Expression { - return lhs > Expression(value: rhs) -} -public func >>(lhs: T, rhs: Expression) -> Expression { - return Expression(value: lhs) > rhs -} - -public func >=>(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >=>(lhs: Expression, rhs: T) -> Expression { - return lhs >= Expression(value: rhs) -} -public func >=>(lhs: T, rhs: Expression) -> Expression { - return Expression(value: lhs) >= rhs -} - -public func <>(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <>(lhs: Expression, rhs: T) -> Expression { - return lhs < Expression(value: rhs) -} -public func <>(lhs: T, rhs: Expression) -> Expression { - return Expression(value: lhs) < rhs -} - -public func <=>(lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <=>(lhs: Expression, rhs: T) -> Expression { - return lhs <= Expression(value: rhs) -} -public func <=>(lhs: T, rhs: Expression) -> Expression { - return Expression(value: lhs) <= rhs -} - -public prefix func -(rhs: Expression) -> Expression { - return wrap(__FUNCTION__, rhs) -} - -public func ~= where T == I.Bound>(lhs: I, rhs: Expression) -> Expression { +public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >(lhs: Expression, rhs: T) -> Expression { return lhs > Expression(value: rhs) } +public func >(lhs: Expression, rhs: T) -> Expression { return lhs > Expression(value: rhs) } +public func >(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) > rhs } +public func >(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) > rhs } + +public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >=(lhs: Expression, rhs: T) -> Expression { return lhs >= Expression(value: rhs) } +public func >=(lhs: Expression, rhs: T) -> Expression { return lhs >= Expression(value: rhs) } +public func >=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) >= rhs } +public func >=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) >= rhs } + +public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <(lhs: Expression, rhs: T) -> Expression { return lhs < Expression(value: rhs) } +public func <(lhs: Expression, rhs: T) -> Expression { return lhs < Expression(value: rhs) } +public func <(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) < rhs } +public func <(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) < rhs } + +public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func <=(lhs: Expression, rhs: T) -> Expression { return lhs <= Expression(value: rhs) } +public func <=(lhs: Expression, rhs: T) -> Expression { return lhs <= Expression(value: rhs) } +public func <=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) <= rhs } +public func <=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) <= rhs } + +public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } + +public func ~=(lhs: I, rhs: Expression) -> Expression { return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) } +public func ~=(lhs: I, rhs: Expression) -> Expression { + return Expression(lhs ~= Expression(rhs)) +} // MARK: Operators public func like(string: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(value: string)) + return infix("LIKE", expression, Expression(value: string)) +} +public func like(string: String, expression: Expression) -> Expression { + return infix("LIKE", expression, Expression(value: string)) } public func glob(string: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(value: string)) + return infix("GLOB", expression, Expression(value: string)) +} +public func glob(string: String, expression: Expression) -> Expression { + return infix("GLOB", expression, Expression(value: string)) } public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(value: string)) + return infix("MATCH", expression, Expression(value: string)) +} +public func match(string: String, expression: Expression) -> Expression { + return infix("MATCH", expression, Expression(value: string)) } // MARK: Compound -public func &&(lhs: Expression, rhs: Expression) -> Expression { - return infix("AND", lhs, rhs) -} +public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } +public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } +public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } +public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } +public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } +public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } -public func ||(lhs: Expression, rhs: Expression) -> Expression { - return infix("OR", lhs, rhs) -} +public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } +public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } +public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } +public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } +public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } +public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } -public prefix func !(rhs: Expression) -> Expression { - return wrap("NOT ", rhs) -} +public prefix func !(rhs: Expression) -> Expression { return wrap("NOT ", rhs) } +public prefix func !(rhs: Expression) -> Expression { return wrap("NOT ", rhs) } // MARK: - Core Functions -public func abs(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func coalesce(expressions: Expression...) -> Expression { +// FIXME: support Expression..., Expression when Swift supports inner variadic signatures +public func coalesce(expressions: Expression...) -> Expression { return wrap(__FUNCTION__, join(", ", expressions.map { $0 })) } -public func ifnull(expression: Expression, defaultValue: T) -> Expression { +public func ifnull(expression: Expression, defaultValue: T) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, defaultValue])) } - -public func ??(expression: Expression, defaultValue: T) -> Expression { +public func ??(expression: Expression, defaultValue: T) -> Expression { return ifnull(expression, defaultValue) } -public func length(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func lower(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func ltrim(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func ltrim(expression: Expression, characters: String) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, characters])) } - -public var random: Expression { - return wrap(__FUNCTION__, Expression<()>()) +public func ltrim(expression: Expression, characters: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, characters])) } +public var random: Expression { return wrap(__FUNCTION__, Expression<()>()) } + public func replace(expression: Expression, match: String, subtitute: String) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, match, subtitute])) } - -public func round(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) +public func replace(expression: Expression, match: String, subtitute: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, match, subtitute])) } +public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func round(expression: Expression, precision: Int) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, precision])) } - -public func rtrim(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) +public func round(expression: Expression, precision: Int) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, precision])) } +public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func rtrim(expression: Expression, characters: String) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, characters])) } +public func rtrim(expression: Expression, characters: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, characters])) +} public func substr(expression: Expression, startIndex: Int) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, startIndex])) } +public func substr(expression: Expression, startIndex: Int) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, startIndex])) +} public func substr(expression: Expression, position: Int, length: Int) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, position, length])) } +public func substr(expression: Expression, position: Int, length: Int) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, position, length])) +} public func substr(expression: Expression, subRange: Range) -> Expression { return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) } - -public func trim(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) +public func substr(expression: Expression, subRange: Range) -> Expression { + return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) } +public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func trim(expression: Expression, characters: String) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, characters])) } - -public func upper(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) +public func trim(expression: Expression, characters: String) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, characters])) } +public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } + // MARK: - Aggregate Functions -public func count(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func count(star: Star) -> Expression { - return count(star(nil, nil)) -} +public func count(star: Star) -> Expression { return count(star(nil, nil)) } -public func max(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func min(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func average(expression: Expression) -> Expression { - return wrap("avg", expression) -} +public func average(expression: Expression) -> Expression { return wrap("avg", expression) } +public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func sum(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} +public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } // MARK: - Helper @@ -415,9 +471,13 @@ public func *(Expression?, Expression?) -> Expression<()> { return Expression<()>("*") } -public func contains(values: [T?], column: Expression) -> Expression { +public func contains(values: [T], column: Expression) -> Expression { + let templates = join(", ", [String](count: values.count, repeatedValue: "?")) + return infix("IN", column, Expression("(\(templates))", values.map { $0 })) +} +public func contains(values: [T?], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression("(\(templates))", values.map { $0 })) + return infix("IN", column, Expression("(\(templates))", values.map { $0 })) } // MARK: - Modifying @@ -433,9 +493,8 @@ public typealias Setter = (Expressible, Expressible) /// /// :returns: A setter that can be used in a Query's insert and update /// functions. -public func set(column: Expression, value: T?) -> Setter { - return (column, Expression<()>(value: value)) -} +public func set(column: Expression, value: T) -> Setter { return (column, Expression<()>(value: value)) } +public func set(column: Expression, value: T?) -> Setter { return (column, Expression<()>(value: value)) } /// Returns a setter to be used with INSERT and UPDATE statements. /// @@ -445,103 +504,124 @@ public func set(column: Expression, value: T?) -> Setter { /// /// :returns: A setter that can be used in a Query's insert and update /// functions. -public func set(column: Expression, value: Expression) -> Setter { - return (column, value) -} +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } infix operator <- { associativity left precedence 140 } -public func <-(column: Expression, value: T?) -> Setter { - return set(column, value) -} -public func <-(column: Expression, value: Expression) -> Setter { - return set(column, value) -} +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: T) -> Setter { return set(column, value) } +public func <-(column: Expression, value: T?) -> Setter { return set(column, value) } -public func +=(column: Expression, value: String) -> Setter { - return set(column, column + value) -} public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } - -public func +=(column: Expression, value: T) -> Setter { +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } - -public func -=(column: Expression, value: T) -> Setter { - return set(column, column - value) -} -public func -=(column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} - -public func *=(column: Expression, value: T) -> Setter { - return set(column, column * value) -} -public func *=(column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} - -public func /=(column: Expression, value: T) -> Setter { - return set(column, column / value) -} -public func /=(column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} - -public func %=(column: Expression, value: Int) -> Setter { - return set(column, column % value) -} -public func %=(column: Expression, value: Expression) -> Setter { - return set(column, column % value) -} - -public func <<=(column: Expression, value: Int) -> Setter { - return set(column, column << value) -} -public func <<=(column: Expression, value: Expression) -> Setter { - return set(column, column << value) -} - -public func >>=(column: Expression, value: Int) -> Setter { - return set(column, column >> value) -} -public func >>=(column: Expression, value: Expression) -> Setter { - return set(column, column >> value) -} - -public func &=(column: Expression, value: Int) -> Setter { - return set(column, column & value) -} -public func &=(column: Expression, value: Expression) -> Setter { - return set(column, column & value) +public func +=(column: Expression, value: Expression) -> Setter { + return set(column, column + value) } - -public func |=(column: Expression, value: Int) -> Setter { - return set(column, column | value) +public func +=(column: Expression, value: String) -> Setter { + return set(column, column + value) } -public func |=(column: Expression, value: Expression) -> Setter { - return set(column, column | value) +public func +=(column: Expression, value: String) -> Setter { + return set(column, column + value) } -public func ^=(column: Expression, value: Int) -> Setter { - return set(column, column ^ value) -} -public func ^=(column: Expression, value: Expression) -> Setter { - return set(column, column ^ value) -} +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: T) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: T) -> Setter { return set(column, column + value) } + +public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } +public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } +public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } +public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } +public func -=(column: Expression, value: T) -> Setter { return set(column, column - value) } +public func -=(column: Expression, value: T) -> Setter { return set(column, column - value) } + +public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } +public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } +public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } +public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } +public func *=(column: Expression, value: T) -> Setter { return set(column, column * value) } +public func *=(column: Expression, value: T) -> Setter { return set(column, column * value) } + +public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } +public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } +public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } +public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } +public func /=(column: Expression, value: T) -> Setter { return set(column, column / value) } +public func /=(column: Expression, value: T) -> Setter { return set(column, column / value) } + +public func %=(column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %=(column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %=(column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %=(column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %=(column: Expression, value: Int) -> Setter { return set(column, column % value) } +public func %=(column: Expression, value: Int) -> Setter { return set(column, column % value) } + +public func <<=(column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<=(column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<=(column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<=(column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<=(column: Expression, value: Int) -> Setter { return set(column, column << value) } +public func <<=(column: Expression, value: Int) -> Setter { return set(column, column << value) } + +public func >>=(column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>=(column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>=(column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>=(column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>=(column: Expression, value: Int) -> Setter { return set(column, column >> value) } +public func >>=(column: Expression, value: Int) -> Setter { return set(column, column >> value) } + +public func &=(column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &=(column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &=(column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &=(column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &=(column: Expression, value: Int) -> Setter { return set(column, column & value) } +public func &=(column: Expression, value: Int) -> Setter { return set(column, column & value) } + +public func |=(column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |=(column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |=(column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |=(column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |=(column: Expression, value: Int) -> Setter { return set(column, column | value) } +public func |=(column: Expression, value: Int) -> Setter { return set(column, column | value) } + +public func ^=(column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^=(column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^=(column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^=(column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^=(column: Expression, value: Int) -> Setter { return set(column, column ^ value) } +public func ^=(column: Expression, value: Int) -> Setter { return set(column, column ^ value) } public postfix func ++(column: Expression) -> Setter { // rdar://18825175 segfaults during archive: // column += 1 return (column, Expression("(\(column.SQL) + 1)", column.bindings)) } +public postfix func ++(column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column += 1 + return (column, Expression("(\(column.SQL) + 1)", column.bindings)) +} public postfix func --(column: Expression) -> Setter { // rdar://18825175 segfaults during archive: // column -= 1 return (column, Expression("(\(column.SQL) - 1)", column.bindings)) } +public postfix func --(column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column -= 1 + return (column, Expression("(\(column.SQL) - 1)", column.bindings)) +} // MARK: - Internal @@ -568,7 +648,7 @@ internal func wrap(function: String, expression: Expression) -> Express return Expression("\(function)\(surround(expression.SQL))", expression.bindings) } -private func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { +private func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { return Expression(surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 8ae537f6..c3a9fd63 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -118,6 +118,17 @@ public struct Query { return join(.Inner, table, on: on) } + /// Adds an INNER JOIN clause to the query. + /// + /// :param: table A query representing the other table. + /// + /// :param: on A boolean expression describing the join condition. + /// + /// :returns: A query with the given INNER JOIN clause applied. + public func join(table: Query, on: Expression) -> Query { + return join(.Inner, table, on: on) + } + /// Adds a JOIN clause to the query. /// /// :param: type The JOIN operator. @@ -135,7 +146,20 @@ public struct Query { return query } - /// Adds a condition the the query’s WHERE clause. + /// Adds a JOIN clause to the query. + /// + /// :param: type The JOIN operator. + /// + /// :param: table A query representing the other table. + /// + /// :param: on A boolean expression describing the join condition. + /// + /// :returns: A query with the given JOIN clause applied. + public func join(type: JoinType, _ table: Query, on: Expression) -> Query { + return join(type, table, on: Expression(on)) + } + + /// Adds a condition to the query’s WHERE clause. /// /// :param: condition A boolean expression to filter on. /// @@ -146,6 +170,15 @@ public struct Query { return query } + /// Adds a condition to the query’s WHERE clause. + /// + /// :param: condition A boolean expression to filter on. + /// + /// :returns: A query with the given WHERE clause applied. + public func filter(condition: Expression) -> Query { + return filter(Expression(condition)) + } + /// Sets a GROUP BY clause on the query. /// /// :param: by A list of columns to group by. @@ -166,6 +199,17 @@ public struct Query { return group([by], having: having) } + /// Sets a GROUP BY clause (with optional HAVING) on the query. + /// + /// :param: by A column to group by. + /// + /// :param: having A condition determining which groups are returned. + /// + /// :returns: A query with the given GROUP BY clause applied. + public func group(by: Expressible, having: Expression) -> Query { + return group([by], having: having) + } + /// Sets a GROUP BY-HAVING clause on the query. /// /// :param: by A list of columns to group by. @@ -181,6 +225,17 @@ public struct Query { return query } + /// Sets a GROUP BY-HAVING clause on the query. + /// + /// :param: by A list of columns to group by. + /// + /// :param: having A condition determining which groups are returned. + /// + /// :returns: A query with the given GROUP BY clause applied. + public func group(by: [Expressible], having: Expression) -> Query { + return group(by, having: Expression(having)) + } + /// Sets an ORDER BY clause on the query. /// /// :param: by An ordered list of columns and directions to sort by. @@ -237,45 +292,17 @@ public struct Query { // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression - /// Prefixes a column expression with the query’s table name or alias. - /// - /// :param: column A column expression. - /// - /// :returns: A column expression namespaced with the query’s table name or - /// alias. - public subscript(column: Expression) -> Expression { - return namespace(column) - } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } - /// Prefixes a column expression with the query’s table name or alias. - /// - /// :param: column A column expression. - /// - /// :returns: A column expression namespaced with the query’s table name or - /// alias. - public subscript(column: Expression) -> Expression { - return namespace(column) - } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } - /// Prefixes a column expression with the query’s table name or alias. - /// - /// :param: column A column expression. - /// - /// :returns: A column expression namespaced with the query’s table name or - /// alias. - public subscript(column: Expression) -> Expression { - return namespace(column) - } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } - /// Prefixes a column expression with the query’s table name or alias. - /// - /// :param: column A column expression. - /// - /// :returns: A column expression namespaced with the query’s table name or - /// alias. - public subscript(column: Expression) -> Expression { - return namespace(column) - } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } /// Prefixes a star with the query’s table name or alias. /// @@ -511,6 +538,9 @@ public struct Query { public func max(column: Expression) -> T? { return calculate(SQLite.max(column)) } + public func max(column: Expression) -> T? { + return calculate(SQLite.max(column)) + } /// Runs min() against the query. /// @@ -520,6 +550,9 @@ public struct Query { public func min(column: Expression) -> T? { return calculate(SQLite.min(column)) } + public func min(column: Expression) -> T? { + return calculate(SQLite.min(column)) + } /// Runs avg() against the query. /// @@ -529,6 +562,9 @@ public struct Query { public func average(column: Expression) -> Double? { return calculate(SQLite.average(column)) } + public func average(column: Expression) -> Double? { + return calculate(SQLite.average(column)) + } /// Runs sum() against the query. /// @@ -538,6 +574,9 @@ public struct Query { public func sum(column: Expression) -> T? { return calculate(SQLite.sum(column)) } + public func sum(column: Expression) -> T? { + return calculate(SQLite.sum(column)) + } /// Runs total() against the query. /// @@ -547,6 +586,9 @@ public struct Query { public func total(column: Expression) -> Double { return calculate(SQLite.total(column))! } + public func total(column: Expression) -> Double { + return calculate(SQLite.total(column))! + } private func calculate(expression: Expression) -> U? { return select(expression).selectStatement.scalar() as? U @@ -569,9 +611,8 @@ public struct Row { /// :param: column An expression representing a column selected in a Query. /// /// returns The value for the given column. - public func get(column: Expression) -> T? { - return values[column.SQL] as? T - } + public func get(column: Expression) -> T { return values[column.SQL] as T } + public func get(column: Expression) -> T? { return values[column.SQL] as? T } // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression @@ -580,36 +621,32 @@ public struct Row { /// :param: column An expression representing a column selected in a Query. /// /// returns The value for the given column. - public subscript(column: Expression) -> Bool? { - return get(column) - } + public subscript(column: Expression) -> Bool { return get(column) } + public subscript(column: Expression) -> Bool? { return get(column) } /// Returns a row’s value for the given column. /// /// :param: column An expression representing a column selected in a Query. /// /// returns The value for the given column. - public subscript(column: Expression) -> Double? { - return get(column) - } + public subscript(column: Expression) -> Double { return get(column) } + public subscript(column: Expression) -> Double? { return get(column) } /// Returns a row’s value for the given column. /// /// :param: column An expression representing a column selected in a Query. /// /// returns The value for the given column. - public subscript(column: Expression) -> Int? { - return get(column) - } + public subscript(column: Expression) -> Int { return get(column) } + public subscript(column: Expression) -> Int? { return get(column) } /// Returns a row’s value for the given column. /// /// :param: column An expression representing a column selected in a Query. /// /// returns The value for the given column. - public subscript(column: Expression) -> String? { - return get(column) - } + public subscript(column: Expression) -> String { return get(column) } + public subscript(column: Expression) -> String? { return get(column) } } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 9b9f38e3..5fa13f8e 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -69,31 +69,48 @@ public final class SchemaBuilder { public func column( name: Expression, primaryKey: Bool = false, - null: Bool = true, unique: Bool = false, check: Expression? = nil, defaultValue: T? = nil ) { - column(name, primaryKey, null, unique, check, defaultValue) + column(name, primaryKey, false, unique, check, defaultValue) + } + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue: T? = nil + ) { + column(Expression(name.SQL, name.bindings), primaryKey, true, unique, check, defaultValue) } public func column( name: Expression, primaryKey: Bool = false, - null: Bool = true, unique: Bool = false, check: Expression? = nil, defaultValue: String? = nil, collate: Collation ) { let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(name, primaryKey, null, unique, check, defaultValue, expressions) + column(name, primaryKey, false, unique, check, defaultValue, expressions) + } + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue: String? = nil, + collate: Collation + ) { + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(Expression(name.SQL, name.bindings), primaryKey, true, unique, check, defaultValue, expressions) } public func column( name: Expression, primaryKey: Bool = false, - null: Bool = true, unique: Bool = false, check: Expression? = nil, defaultValue: Int? = nil, @@ -101,13 +118,41 @@ public final class SchemaBuilder { ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(name, primaryKey, null, unique, check, defaultValue, expressions) + column(name, primaryKey, false, unique, check, defaultValue, expressions) + } + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue: Int? = nil, + references: Expression + ) { + assertForeignKeysEnabled() + let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] + column(Expression(name.SQL, name.bindings), primaryKey, true, unique, check, defaultValue, expressions) } public func column( name: Expression, primaryKey: Bool = false, - null: Bool = true, + unique: Bool = false, + check: Expression? = nil, + defaultValue: Int? = nil, + references: Query + ) { + return column( + name, + primaryKey: primaryKey, + unique: unique, + check: check, + defaultValue: defaultValue, + references: Expression(references.tableName) + ) + } + public func column( + name: Expression, + primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue: Int? = nil, @@ -183,6 +228,19 @@ public final class SchemaBuilder { if let delete = delete { parts.append(Expression<()>("ON DELETE \(delete.rawValue)")) } columns.append(SQLite.join(" ", parts)) } + public func foreignKey( + column: Expression, + references: Expression, + update: Dependency? = nil, + delete: Dependency? = nil + ) { + assertForeignKeysEnabled() + var parts: [Expressible] = [Expression<()>("FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] + parts.append(namespace(references)) + if let update = update { parts.append(Expression<()>("ON UPDATE \(update.rawValue)")) } + if let delete = delete { parts.append(Expression<()>("ON DELETE \(delete.rawValue)")) } + columns.append(SQLite.join(" ", parts)) + } public func foreignKey( column: Expression, @@ -192,6 +250,14 @@ public final class SchemaBuilder { ) { foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) } + public func foreignKey( + column: Expression, + references: Query, + update: Dependency? = nil, + delete: Dependency? = nil + ) { + foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) + } private var statement: Statement { let expression = SQLite.join(", ", columns) @@ -199,13 +265,14 @@ public final class SchemaBuilder { return table.database.prepare(SQL) } - private func namespace(expression: Expression) -> Expression { + private func namespace(column: Expressible) -> Expressible { + let expression = column.expression if !contains(expression.SQL, ".") { return expression } let reference = Array(expression.SQL).reduce("") { SQL, character in let string = String(character) return SQL + (string == "." ? "(" : string) } - return Expression("\(reference))", expression.bindings) + return Expression<()>("\(reference))", expression.bindings) } private func assertForeignKeysEnabled() { diff --git a/SQLite.playground/section-20.swift b/SQLite.playground/section-20.swift index 4357de4a..df919ece 100644 --- a/SQLite.playground/section-20.swift +++ b/SQLite.playground/section-20.swift @@ -1,5 +1,5 @@ let id = Expression("id") let email = Expression("email") -let age = Expression("age") +let age = Expression("age") let admin = Expression("admin") -let manager_id = Expression("manager_id") +let manager_id = Expression("manager_id") diff --git a/SQLite.playground/section-36.swift b/SQLite.playground/section-36.swift index 587a6693..1dadf623 100644 --- a/SQLite.playground/section-36.swift +++ b/SQLite.playground/section-36.swift @@ -2,5 +2,6 @@ let ordered = admins.order(email.asc, age.asc).limit(3) // SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3 for admin in ordered { - println(admin) + println(admin[id]) + println(admin[age]) } From 975fad0d195309f467577dc95585e13de1f53c50 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 09:03:27 -0800 Subject: [PATCH 0050/1046] Update README with NULLable, optional expressions Signed-off-by: Stephen Celis --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4ee6a3ef..61c46ac0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ syntax _and_ intent. ## Features - - A powerful interface for building type-safe SQL expressions + - A powerful interface for building type-safe, optional-aware SQL + expressions - A flexible, chainable, lazy-executing query layer - Automatically-typed data access - A lightweight, uncomplicated query and parameter binding interface @@ -33,14 +34,17 @@ let db = Database("path/to/db.sqlite3") let users = db["users"] let id = Expression("id") +let name = Expression("name") let email = Expression("email") db.create(table: users) { t in t.column(id, primaryKey: true) - t.column(email, null: false, unique: true) + t.column(name) + t.column(email, unique: true) } // CREATE TABLE users ( -// id INTEGER PRIMARY KEY, +// id INTEGER PRIMARY KEY NOT NULL, +// name TEXT, // email TEXT NOT NULL UNIQUE // ) From cc39c56cc13381d68bf60406d0190dc6c788f407 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 11:21:25 -0800 Subject: [PATCH 0051/1046] More README tweaks Show non-optional vs. optional return values. Signed-off-by: Stephen Celis --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 61c46ac0..bba7ac8d 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ db.create(table: users) { t in // ) var alice: Query? -if let insertedID = users.insert(email <- "alice@mac.com") { +if let insertedID = users.insert(name <- "Alice", email <- "alice@mac.com") { println("inserted id: \(insertedID)") // inserted id: 1 alice = users.filter(id == insertedID) @@ -57,13 +57,13 @@ if let insertedID = users.insert(email <- "alice@mac.com") { // INSERT INTO users (email) VALUES 'alice@mac.com' for user in users { - println("id: \(user[id]), email: \(user[email])" - // id: Optional(1), email: Optional("alice@mac.com") + println("id: \(user[id]), name: \(user[name]), email: \(user[email])" + // id: 1, name: Optional("Alice"), email: "alice@mac.com" } // SELECT * FROM users -alice?.update(email <- "alice@me.com")? -// UPDATE users SET email = 'alice@me.com' WHERE (id = 1) +alice?.update(email <- replace(email, "mac.com", "me.com")) +// UPDATE users SET email = replace(email, "mac.com", "me.com") WHERE (id = 1) alice?.delete()? // DELETE FROM users WHERE (id = 1) From c5380dcdafacb72cdcec5f1361c9d72d6c3c6046 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 11:22:53 -0800 Subject: [PATCH 0052/1046] README fixes Feels more like REREADME. Signed-off-by: Stephen Celis --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bba7ac8d..5776c374 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ API. ``` swift let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") -for email in ["betty@me.com", "cathy@me.com"] { +for email in ["betty@icloud.com", "cathy@icloud.com"] { stmt.run(email) } @@ -84,8 +84,8 @@ db.lastID // {Some 3} for row in db.prepare("SELECT id, email FROM users") { println("id: \(row[0]), email: \(row[1])") - // id: Optional(2), email: Optional("betty@example.com") - // id: Optional(3), email: Optional("cathy@example.com") + // id: Optional(2), email: Optional("betty@icloud.com") + // id: Optional(3), email: Optional("cathy@icloud.com") } db.scalar("SELECT count(*) FROM users") // {Some 2} From 0e28ad9c7cc1c8f9bdd2a4843292f2f28821a44a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 11:55:43 -0800 Subject: [PATCH 0053/1046] Add missing question Signed-off-by: Stephen Celis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5776c374..2a5e97f1 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ for user in users { } // SELECT * FROM users -alice?.update(email <- replace(email, "mac.com", "me.com")) +alice?.update(email <- replace(email, "mac.com", "me.com"))? // UPDATE users SET email = replace(email, "mac.com", "me.com") WHERE (id = 1) alice?.delete()? From 51c6a90f1391ffc0b4d2e9b0b1c3ae2414622edd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 13:06:01 -0800 Subject: [PATCH 0054/1046] Add `make repl` Gives us easy SQLite.swift access in the Swift REPL on the command line. Signed-off-by: Stephen Celis --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index cf775d17..91909e24 100644 --- a/Makefile +++ b/Makefile @@ -19,5 +19,10 @@ endif clean: $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean +repl: + @$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null 2>&1 && \ + swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' + sloc: @zsh -c "grep -vE '^ *//|^$$' SQLite\ Common/*.{swift,h,c} | wc -l" + From addd7c0c670fdc4dbb29e7cdeeb3610aefded27d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 13:19:13 -0800 Subject: [PATCH 0055/1046] Accuracy in README output Signed-off-by: Stephen Celis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a5e97f1..e4da017d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ if let insertedID = users.insert(name <- "Alice", email <- "alice@mac.com") { for user in users { println("id: \(user[id]), name: \(user[name]), email: \(user[email])" - // id: 1, name: Optional("Alice"), email: "alice@mac.com" + // id: 1, name: Optional("Alice"), email: alice@mac.com } // SELECT * FROM users From 9aeb90d6c69b5e5f5bd1e4dd8fc600b5b7fa1292 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 15:24:11 -0800 Subject: [PATCH 0056/1046] Remove println Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index c3a9fd63..a46c902a 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -346,7 +346,6 @@ public struct Query { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } var expressions: [Expressible] = [Expression<()>("\(insertClause) INTO \(tableName)")] - println(expressions) let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) whereClause.map(expressions.append) From b2a6814180ea150a739f566bc1bc94c77cf1ef55 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 15:26:39 -0800 Subject: [PATCH 0057/1046] Support default expressions (like CURRENT_*) Signed-off-by: Stephen Celis --- SQLite Common/Schema.swift | 144 ++++++++++++++++++++++++++++++++----- 1 file changed, 127 insertions(+), 17 deletions(-) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 5fa13f8e..1f7225d0 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -71,18 +71,51 @@ public final class SchemaBuilder { primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: T? = nil + defaultValue value: Expression? = nil ) { - column(name, primaryKey, false, unique, check, defaultValue) + column(name, primaryKey, false, unique, check, value) } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: T + ) { + column(name, primaryKey, false, unique, check, Expression(value: value)) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: Expression? = nil + ) { + column(Expression(name), primaryKey, true, unique, check, value) + } + public func column( name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: T? = nil + defaultValue value: T? + ) { + column(Expression(name), primaryKey, true, unique, check, value.map { Expression(value: $0) }) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: Expression? = nil, + collate: Collation ) { - column(Expression(name.SQL, name.bindings), primaryKey, true, unique, check, defaultValue) + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(name, primaryKey, false, unique, check, value, expressions) } public func column( @@ -90,22 +123,48 @@ public final class SchemaBuilder { primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: String? = nil, + defaultValue value: String, + collate: Collation + ) { + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(name, primaryKey, false, unique, check, Expression(value: value), expressions) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: Expression? = nil, collate: Collation ) { let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(name, primaryKey, false, unique, check, defaultValue, expressions) + column(Expression(name), primaryKey, true, unique, check, value, expressions) } + public func column( name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: String? = nil, + defaultValue value: String?, collate: Collation ) { let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name.SQL, name.bindings), primaryKey, true, unique, check, defaultValue, expressions) + column(Expression(name), primaryKey, true, unique, check, Expression(value: value), expressions) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: Expression? = nil, + references: Expression + ) { + assertForeignKeysEnabled() + let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] + column(name, primaryKey, false, unique, check, value, expressions) } public func column( @@ -113,24 +172,56 @@ public final class SchemaBuilder { primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: Int? = nil, + defaultValue value: Int, + references: Expression + ) { + assertForeignKeysEnabled() + let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] + column(name, primaryKey, false, unique, check, Expression(value: value), expressions) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: Expression? = nil, references: Expression ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(name, primaryKey, false, unique, check, defaultValue, expressions) + column(Expression(name), primaryKey, true, unique, check, value, expressions) } + public func column( name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: Int? = nil, + defaultValue value: Int?, references: Expression ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(Expression(name.SQL, name.bindings), primaryKey, true, unique, check, defaultValue, expressions) + column(Expression(name), primaryKey, true, unique, check, Expression(value: value), expressions) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue: Expression? = nil, + references: Query + ) { + return column( + name, + primaryKey: primaryKey, + unique: unique, + check: check, + defaultValue: defaultValue, + references: Expression(references.tableName) + ) } public func column( @@ -138,7 +229,25 @@ public final class SchemaBuilder { primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: Int? = nil, + defaultValue: Int, + references: Query + ) { + return column( + name, + primaryKey: primaryKey, + unique: unique, + check: check, + defaultValue: defaultValue, + references: Expression(references.tableName) + ) + } + + public func column( + name: Expression, + primaryKey: Bool = false, + unique: Bool = false, + check: Expression? = nil, + defaultValue: Expression? = nil, references: Query ) { return column( @@ -150,12 +259,13 @@ public final class SchemaBuilder { references: Expression(references.tableName) ) } + public func column( name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue: Int? = nil, + defaultValue: Int?, references: Query ) { return column( @@ -174,15 +284,15 @@ public final class SchemaBuilder { _ null: Bool, _ unique: Bool, _ check: Expression?, - _ defaultValue: T?, + _ defaultValue: Expression?, _ expressions: [Expressible]? = nil ) { - var parts: [Expressible] = [name, Expression<()>(T.datatype)] + var parts: [Expressible] = [Expression<()>(name), Expression<()>(T.datatype)] if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } if !null { parts.append(Expression<()>("NOT NULL")) } if unique { parts.append(Expression<()>("UNIQUE")) } if let check = check { parts.append(Expression<()>("CHECK \(check.SQL)", check.bindings)) } - if let defaultValue = defaultValue { parts.append(Expression<()>("DEFAULT ?", [defaultValue])) } + if let value = defaultValue { parts.append(Expression<()>("DEFAULT \(value.SQL)", value.bindings)) } if let expressions = expressions { parts += expressions } columns.append(SQLite.join(" ", parts)) } From 71df60e29c40947eea8192a367762bd4d985c107 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 16:20:10 -0800 Subject: [PATCH 0058/1046] Add ALTER TABLE schema helpers Supports RENAME TO: db.alter(table: users, rename: "people") And ADD COLUMN: let name = Expression("name") db.alter(table: users, add: name) Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 41 +++++++++++++ SQLite Common/Schema.swift | 85 +++++++++++++++++++++------ 2 files changed, 108 insertions(+), 18 deletions(-) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index d407cb70..42c0a4ce 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -210,6 +210,47 @@ class SchemaTests: XCTestCase { ) } + func test_alterTable_renamesTable() { + CreateUsersTable(db) + ExpectExecution(db, "ALTER TABLE users RENAME TO people", db.alter(table: users, rename: "people") ) + } + + func test_alterTable_addsNotNullColumn() { + CreateUsersTable(db) + let column = Expression("bonus") + + ExpectExecution(db, "ALTER TABLE users ADD COLUMN bonus REAL NOT NULL DEFAULT 0.0", + db.alter(table: users, add: column, defaultValue: 0) + ) + } + + func test_alterTable_addsRegularColumn() { + CreateUsersTable(db) + let column = Expression("bonus") + + ExpectExecution(db, "ALTER TABLE users ADD COLUMN bonus REAL", + db.alter(table: users, add: column) + ) + } + + func test_alterTable_withDefaultValue_addsRegularColumn() { + CreateUsersTable(db) + let column = Expression("bonus") + + ExpectExecution(db, "ALTER TABLE users ADD COLUMN bonus REAL DEFAULT 0.0", + db.alter(table: users, add: column, defaultValue: 0) + ) + } + + func test_alterTable_withForeignKey_addsRegularColumn() { + CreateUsersTable(db) + let column = Expression("parent_id") + + ExpectExecution(db, "ALTER TABLE users ADD COLUMN parent_id INTEGER REFERENCES users(id)", + db.alter(table: users, add: column, references: users[id]) + ) + } + func test_dropTable_dropsTable() { CreateUsersTable(db) ExpectExecution(db, "DROP TABLE users", db.drop(table: users) ) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 1f7225d0..f6199f23 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -29,6 +29,43 @@ public extension Database { return builder.statement.run() } + public func alter(#table: Query, rename to: String) -> Statement { + return run("ALTER TABLE \(table.tableName) RENAME TO \(to)") + } + + public func alter( + #table: Query, + add column: Expression, + check: Expression? = nil, + defaultValue: T + ) -> Statement { + return alter(table, define(column, false, false, false, check, Expression(value: defaultValue), nil)) + } + + public func alter( + #table: Query, + add column: Expression, + check: Expression? = nil, + defaultValue: T? = nil + ) -> Statement { + let value = defaultValue.map { Expression(value: $0) } + return alter(table, define(Expression(column), false, true, false, check, value, nil)) + } + + public func alter( + #table: Query, + add column: Expression, + check: Expression? = nil, + references: Expression + ) -> Statement { + let expressions = [Expression<()>("REFERENCES"), namespace(references)] + return alter(table, define(Expression(column), false, true, false, check, nil, expressions)) + } + + private func alter(table: Query, _ definition: Expressible) -> Statement { + return run("ALTER TABLE \(table.tableName) ADD COLUMN \(definition.expression.compile())") + } + public func drop(#table: Query) -> Statement { return run("DROP TABLE \(table.tableName)") } @@ -287,14 +324,7 @@ public final class SchemaBuilder { _ defaultValue: Expression?, _ expressions: [Expressible]? = nil ) { - var parts: [Expressible] = [Expression<()>(name), Expression<()>(T.datatype)] - if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } - if !null { parts.append(Expression<()>("NOT NULL")) } - if unique { parts.append(Expression<()>("UNIQUE")) } - if let check = check { parts.append(Expression<()>("CHECK \(check.SQL)", check.bindings)) } - if let value = defaultValue { parts.append(Expression<()>("DEFAULT \(value.SQL)", value.bindings)) } - if let expressions = expressions { parts += expressions } - columns.append(SQLite.join(" ", parts)) + columns.append(define(name, primaryKey, null, unique, check, defaultValue, expressions)) } public func primaryKey(column: Expressible...) { @@ -375,18 +405,37 @@ public final class SchemaBuilder { return table.database.prepare(SQL) } - private func namespace(column: Expressible) -> Expressible { - let expression = column.expression - if !contains(expression.SQL, ".") { return expression } - let reference = Array(expression.SQL).reduce("") { SQL, character in - let string = String(character) - return SQL + (string == "." ? "(" : string) - } - return Expression<()>("\(reference))", expression.bindings) - } - private func assertForeignKeysEnabled() { assert(table.database.scalar("PRAGMA foreign_keys") as Int == 1, "foreign key constraints are disabled") } } + +private func namespace(column: Expressible) -> Expressible { + let expression = column.expression + if !contains(expression.SQL, ".") { return expression } + let reference = Array(expression.SQL).reduce("") { SQL, character in + let string = String(character) + return SQL + (string == "." ? "(" : string) + } + return Expression<()>("\(reference))", expression.bindings) +} + +private func define( + column: Expression, + primaryKey: Bool, + null: Bool, + unique: Bool, + check: Expression?, + defaultValue: Expression?, + expressions: [Expressible]? +) -> Expressible { + var parts: [Expressible] = [Expression<()>(column), Expression<()>(T.datatype)] + if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } + if !null { parts.append(Expression<()>("NOT NULL")) } + if unique { parts.append(Expression<()>("UNIQUE")) } + if let check = check { parts.append(Expression<()>("CHECK \(check.SQL)", check.bindings)) } + if let value = defaultValue { parts.append(Expression<()>("DEFAULT \(value.SQL)", value.bindings)) } + if let expressions = expressions { parts += expressions } + return SQLite.join(" ", parts) +} From 38f46675d24b9a3a869dc29636e548b52d202e91 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 18:54:48 -0800 Subject: [PATCH 0059/1046] Add CREATE TABLE ... AS helper Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 8 ++++++++ SQLite Common/Query.swift | 10 +++++++--- SQLite Common/Schema.swift | 5 +++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 42c0a4ce..8d04a90e 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -210,6 +210,14 @@ class SchemaTests: XCTestCase { ) } + func test_create_withQuery_createsTableWithQuery() { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE TABLE emails AS SELECT email FROM users", + db.create(table: db["emails"], from: users.select(email)) + ) + } + func test_alterTable_renamesTable() { CreateUsersTable(db) ExpectExecution(db, "ALTER TABLE users RENAME TO people", db.alter(table: users, rename: "people") ) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index a46c902a..37cf759d 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -316,15 +316,19 @@ public struct Query { // MARK: - Compiling Statements - private var selectStatement: Statement { + internal var selectStatement: Statement { + let expression = selectExpression + return database.prepare(expression.SQL, expression.bindings) + } + + internal var selectExpression: Expression<()> { var expressions = [selectClause] joinClause.map(expressions.append) whereClause.map(expressions.append) group.map(expressions.append) orderClause.map(expressions.append) limitClause.map(expressions.append) - let expression = SQLite.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) + return SQLite.join(" ", expressions) } /// ON CONFLICT resolutions. diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index f6199f23..dee526ad 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -29,6 +29,11 @@ public extension Database { return builder.statement.run() } + public func create(#table: Query, from: Query) -> Statement { + let selectExpression = from.selectExpression + return run("CREATE TABLE \(table.tableName) AS \(selectExpression.SQL)", selectExpression.bindings) + } + public func alter(#table: Query, rename to: String) -> Statement { return run("ALTER TABLE \(table.tableName) RENAME TO \(to)") } From ad5ffa694235bb49b1e2db44176157fc935ec787 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 18:57:03 -0800 Subject: [PATCH 0060/1046] Rename ALTER TABLE ... RENAME helper from alter to rename Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 2 +- SQLite Common/Schema.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 8d04a90e..9db2aa89 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -220,7 +220,7 @@ class SchemaTests: XCTestCase { func test_alterTable_renamesTable() { CreateUsersTable(db) - ExpectExecution(db, "ALTER TABLE users RENAME TO people", db.alter(table: users, rename: "people") ) + ExpectExecution(db, "ALTER TABLE users RENAME TO people", db.rename(table: users, to: "people") ) } func test_alterTable_addsNotNullColumn() { diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index dee526ad..e0371785 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -34,8 +34,8 @@ public extension Database { return run("CREATE TABLE \(table.tableName) AS \(selectExpression.SQL)", selectExpression.bindings) } - public func alter(#table: Query, rename to: String) -> Statement { - return run("ALTER TABLE \(table.tableName) RENAME TO \(to)") + public func rename(#table: Query, to tableName: String) -> Statement { + return run("ALTER TABLE \(table.tableName) RENAME TO \(tableName)") } public func alter( From c8ea381aba6667688bf7651f70868edc7ed91ace Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 19:10:05 -0800 Subject: [PATCH 0061/1046] Add INSERT INTO ... DEFAULT VALUES helpers Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 9 +++++++++ SQLite Common/Query.swift | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index f289cee7..a5ca5394 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -274,6 +274,15 @@ class QueryTests: XCTestCase { XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID == nil) } + func test_insert_insertsDefaultRow() { + db.execute("CREATE TABLE timestamps (id INTEGER PRIMARY KEY, timestamp TEXT DEFAULT CURRENT_DATETIME)") + let table = db["timestamps"] + + ExpectExecutions(db, ["INSERT INTO timestamps DEFAULT VALUES": 1]) { _ in + XCTAssertEqual(1, table.insert().ID!) + } + } + func test_replace_replaceRows() { let SQL = "INSERT OR REPLACE INTO users (email, age) VALUES ('alice@example.com', 30)" diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 37cf759d..690da4f3 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -422,22 +422,22 @@ public struct Query { /// :param: values A list of values to set. /// /// :returns: The statement. - public func insert(values: Setter...) -> Statement { return insert(values).statement } + public func insert(value: Setter, _ more: Setter...) -> Statement { return insert([value] + more).statement } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The row ID. - public func insert(values: Setter...) -> Int? { return insert(values).ID } + public func insert(value: Setter, _ more: Setter...) -> Int? { return insert([value] + more).ID } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The row ID and statement. - public func insert(values: Setter...) -> (ID: Int?, statement: Statement) { - return insert(values) + public func insert(value: Setter, _ more: Setter...) -> (ID: Int?, statement: Statement) { + return insert([value] + more) } private func insert(values: [Setter]) -> (ID: Int?, statement: Statement) { @@ -445,6 +445,15 @@ public struct Query { return (statement.failed ? nil : database.lastID, statement) } + public func insert() -> Int? { return insert().ID } + + public func insert() -> Statement { return insert().statement } + + public func insert() -> (ID: Int?, statement: Statement) { + let statement = database.run("INSERT INTO \(tableName) DEFAULT VALUES") + return (statement.failed ? nil : database.lastID, statement) + } + /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. From ec66a9baa54444a5a739687441e3e969b23a6b93 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 19:18:12 -0800 Subject: [PATCH 0062/1046] Add INSERT INTO ... SELECT helpers Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 8 ++++++++ SQLite Common/Query.swift | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index a5ca5394..43ce14f5 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -274,6 +274,14 @@ class QueryTests: XCTestCase { XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID == nil) } + func test_insert_withQuery_insertsRows() { + db.execute("CREATE TABLE emails (email TEXT)") + let emails = db["emails"] + let admins = users.select(email).filter(admin == true) + + ExpectExecution(db, "INSERT INTO emails SELECT email FROM users WHERE (admin = 1)", emails.insert(admins)) + } + func test_insert_insertsDefaultRow() { db.execute("CREATE TABLE timestamps (id INTEGER PRIMARY KEY, timestamp TEXT DEFAULT CURRENT_DATETIME)") let table = db["timestamps"] diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 690da4f3..268e26f3 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -445,6 +445,16 @@ public struct Query { return (statement.failed ? nil : database.lastID, statement) } + public func insert(query: Query) -> Int? { return insert(query).changes } + + public func insert(query: Query) -> Statement { return insert(query).statement } + + public func insert(query: Query) -> (changes: Int?, statement: Statement) { + let expression = query.selectExpression + let statement = database.run("INSERT INTO \(tableName) \(expression.SQL)", expression.bindings) + return (statement.failed ? nil : database.lastChanges, statement) + } + public func insert() -> Int? { return insert().ID } public func insert() -> Statement { return insert().statement } From e0af6610738fefb8931ea1010c3fcb1c467aad63 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 19:28:12 -0800 Subject: [PATCH 0063/1046] Cleanup Signed-off-by: Stephen Celis --- SQLite Common/Schema.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index e0371785..6808225f 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -30,8 +30,8 @@ public extension Database { } public func create(#table: Query, from: Query) -> Statement { - let selectExpression = from.selectExpression - return run("CREATE TABLE \(table.tableName) AS \(selectExpression.SQL)", selectExpression.bindings) + let expression = from.selectExpression + return run("CREATE TABLE \(table.tableName) AS \(expression.SQL)", expression.bindings) } public func rename(#table: Query, to tableName: String) -> Statement { From fd36a347d0d1f08a90ce086ff53408ab4b53cec2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 20:05:15 -0800 Subject: [PATCH 0064/1046] Add temporary, if{Not,}Exists helpers, ADD/DROP VIEW The naming is kind of ugly, but practical. db.create(table: users, temporary: true, ifNotExists: true) { t // .. db.create(table: users, temporary: true, ifNotExists: true, from: query) db.drop(table: users, ifExists: true) db.create(index: users, unique: true, ifNotExists: true, on: email) db.drop(index: users, ifExists: true, on: email) Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 34 +++++++++++++- SQLite Common/Schema.swift | 68 ++++++++++++++++++--------- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 9db2aa89..aac15863 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -18,6 +18,24 @@ class SchemaTests: XCTestCase { db.run("PRAGMA foreign_keys = ON") } + func test_createTable_createsTable() { + ExpectExecution(db, "CREATE TABLE users (age INTEGER)", + db.create(table: users) { t in t.column(age) } + ) + } + + func test_createTable_temporary_createsTemporaryTable() { + ExpectExecution(db, "CREATE TEMPORARY TABLE users (age INTEGER)", + db.create(table: users, temporary: true) { t in t.column(age) } + ) + } + + func test_createTable_ifNotExists_createsTableIfNotExists() { + ExpectExecution(db, "CREATE TABLE IF NOT EXISTS users (age INTEGER)", + db.create(table: users, ifNotExists: true) { t in t.column(age) } + ) + } + func test_createTable_column_buildsColumnDefinition() { ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL)", db.create(table: users) { t in @@ -210,12 +228,16 @@ class SchemaTests: XCTestCase { ) } - func test_create_withQuery_createsTableWithQuery() { + func test_createTable_withQuery_createsTableWithQuery() { CreateUsersTable(db) ExpectExecution(db, "CREATE TABLE emails AS SELECT email FROM users", db.create(table: db["emails"], from: users.select(email)) ) + ExpectExecution(db, + "CREATE TEMPORARY TABLE IF NOT EXISTS emails AS SELECT email FROM users", + db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) + ) } func test_alterTable_renamesTable() { @@ -262,6 +284,7 @@ class SchemaTests: XCTestCase { func test_dropTable_dropsTable() { CreateUsersTable(db) ExpectExecution(db, "DROP TABLE users", db.drop(table: users) ) + ExpectExecution(db, "DROP TABLE IF EXISTS users", db.drop(table: users, ifExists: true) ) } func test_index_executesIndexStatement() { @@ -280,6 +303,14 @@ class SchemaTests: XCTestCase { ) } + func test_index_ifNotExists_executesIndexStatement() { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE INDEX IF NOT EXISTS index_users_on_email ON users (email)", + db.create(index: users, ifNotExists: true, on: email) + ) + } + func test_index_withMultipleColumns_executesCompoundIndexStatement() { CreateUsersTable(db) ExpectExecution(db, @@ -303,6 +334,7 @@ class SchemaTests: XCTestCase { db.create(index: users, on: email) ExpectExecution(db, "DROP INDEX index_users_on_email", db.drop(index: users, on: email)) + ExpectExecution(db, "DROP INDEX IF EXISTS index_users_on_email", db.drop(index: users, ifExists: true, on: email)) } } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 6808225f..78c238af 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -23,15 +23,23 @@ public extension Database { - public func create(#table: Query, _ block: SchemaBuilder -> ()) -> Statement { + public func create( + #table: Query, + temporary: Bool = false, + ifNotExists: Bool = false, + _ block: SchemaBuilder -> () + ) -> Statement { var builder = SchemaBuilder(table) block(builder) - return builder.statement.run() + let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName) + let columns = SQLite.join(", ", builder.columns).compile() + return run("\(create) (\(columns))") } - public func create(#table: Query, from: Query) -> Statement { + public func create(#table: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { + let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName) let expression = from.selectExpression - return run("CREATE TABLE \(table.tableName) AS \(expression.SQL)", expression.bindings) + return run("\(create) AS \(expression.SQL)", expression.bindings) } public func rename(#table: Query, to tableName: String) -> Statement { @@ -71,22 +79,23 @@ public extension Database { return run("ALTER TABLE \(table.tableName) ADD COLUMN \(definition.expression.compile())") } - public func drop(#table: Query) -> Statement { - return run("DROP TABLE \(table.tableName)") + public func drop(#table: Query, ifExists: Bool = false) -> Statement { + return run(dropSQL("TABLE", ifExists, table.tableName)) } - public func create(index table: Query, unique: Bool = false, on columns: Expressible...) -> Statement { - var parts: [Expressible] = [Expression<()>("CREATE")] - if unique { parts.append(Expression<()>("UNIQUE")) } - parts.append(Expression<()>("INDEX \(indexName(table, on: columns))")) + public func create( + index table: Query, + unique: Bool = false, + ifNotExists: Bool = false, + on columns: Expressible... + ) -> Statement { + let create = createSQL("INDEX", false, unique, ifNotExists, indexName(table, on: columns)) let joined = SQLite.join(", ", columns) - parts.append(Expression<()>("ON \(table.tableName) (\(joined.SQL))", joined.bindings)) - // if SQLITE_VERSION >= "3.8" { table.whereClause.map(parts.append) } // partial indexes - return run(SQLite.join(" ", parts).compile()) + return run("\(create) ON \(table.tableName) (\(joined.compile()))") } - public func drop(index table: Query, on columns: Expressible...) -> Statement { - return run("DROP INDEX \(indexName(table, on: columns))") + public func drop(index table: Query, ifExists: Bool = false, on columns: Expressible...) -> Statement { + return run(dropSQL("INDEX", ifExists, indexName(table, on: columns))) } private func indexName(table: Query, on columns: [Expressible]) -> String { @@ -404,12 +413,6 @@ public final class SchemaBuilder { foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) } - private var statement: Statement { - let expression = SQLite.join(", ", columns) - let SQL = "CREATE TABLE \(table.tableName) (\(expression.compile()))" - return table.database.prepare(SQL) - } - private func assertForeignKeysEnabled() { assert(table.database.scalar("PRAGMA foreign_keys") as Int == 1, "foreign key constraints are disabled") } @@ -444,3 +447,26 @@ private func define( if let expressions = expressions { parts += expressions } return SQLite.join(" ", parts) } + +private func createSQL( + type: String, + temporary: Bool, + unique: Bool, + ifNotExists: Bool, + name: String +) -> String { + var parts: [String] = ["CREATE"] + if temporary { parts.append("TEMPORARY") } + if unique { parts.append("UNIQUE") } + parts.append(type) + if ifNotExists { parts.append("IF NOT EXISTS") } + parts.append(name) + return Swift.join(" ", parts) +} + +private func dropSQL(type: String, ifExists: Bool, name: String) -> String { + var parts: [String] = ["DROP \(type)"] + if ifExists { parts.append("IF EXISTS") } + parts.append(name) + return Swift.join(" ", parts) +} From 636dad592e5cd766078e9f8db2ef62130793ca1e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Nov 2014 20:11:05 -0800 Subject: [PATCH 0065/1046] Add CREATE/DROP VIEW helpers Mostly uses reusable logic from the previous commit. Signed-off-by: Stephen Celis --- SQLite Common Tests/SchemaTests.swift | 20 ++++++++++++++++++++ SQLite Common/Schema.swift | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index aac15863..90bf059a 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -337,4 +337,24 @@ class SchemaTests: XCTestCase { ExpectExecution(db, "DROP INDEX IF EXISTS index_users_on_email", db.drop(index: users, ifExists: true, on: email)) } + func test_createView_withQuery_createsViewWithQuery() { + CreateUsersTable(db) + ExpectExecution(db, + "CREATE VIEW emails AS SELECT email FROM users", + db.create(view: db["emails"], from: users.select(email)) + ) + ExpectExecution(db, + "CREATE TEMPORARY VIEW IF NOT EXISTS emails AS SELECT email FROM users", + db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) + ) + } + + func test_dropView_dropsView() { + CreateUsersTable(db) + db.create(view: db["emails"], from: users.select(email)) + + ExpectExecution(db, "DROP VIEW emails", db.drop(view: db["emails"])) + ExpectExecution(db, "DROP VIEW IF EXISTS emails", db.drop(view: db["emails"], ifExists: true)) + } + } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 78c238af..3c32dadc 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -106,6 +106,16 @@ public extension Database { } } + public func create(#view: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { + let create = createSQL("VIEW", temporary, false, ifNotExists, view.tableName) + let expression = from.selectExpression + return run("\(create) AS \(expression.SQL)", expression.bindings) + } + + public func drop(#view: Query, ifExists: Bool = false) -> Statement { + return run(dropSQL("VIEW", ifExists, view.tableName)) + } + } public final class SchemaBuilder { From 8f37d949747c4e5905b36f0da6ec7502b0b85234 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 14 Nov 2014 08:21:59 -0800 Subject: [PATCH 0066/1046] Remove public Value protocol methods There was originally the promise of serializing/deserializing your own types, but without a way to set up the expectation on both sides of the exchange this approach was naive. We can revisit this using Expression in the future, but we need more directly applicable functions. Signed-off-by: Stephen Celis --- SQLite Common/Schema.swift | 15 ++++++++++- SQLite Common/Statement.swift | 26 ++++++------------- SQLite Common/Value.swift | 48 ++++------------------------------- 3 files changed, 27 insertions(+), 62 deletions(-) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 3c32dadc..b99d9685 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -448,7 +448,7 @@ private func define( defaultValue: Expression?, expressions: [Expressible]? ) -> Expressible { - var parts: [Expressible] = [Expression<()>(column), Expression<()>(T.datatype)] + var parts: [Expressible] = [Expression<()>(column), Expression<()>(datatype(T))] if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } if !null { parts.append(Expression<()>("NOT NULL")) } if unique { parts.append(Expression<()>("UNIQUE")) } @@ -458,6 +458,19 @@ private func define( return SQLite.join(" ", parts) } +private func datatype(type: T.Type) -> String { + if type is Bool.Type { + return "BOOLEAN" + } else if type is Double.Type { + return "REAL" + } else if type is Int.Type { + return "INTEGER" + } else if type is String.Type { + return "TEXT" + } + return "NONE" +} + private func createSQL( type: String, temporary: Bool, diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index 54ca0a7d..642f9c98 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -80,25 +80,15 @@ public final class Statement { return self } - internal func bind(#bool: Bool, atIndex idx: Int) { - bind(int: bool ? 1 : 0, atIndex: idx) - } - - internal func bind(#double: Double, atIndex idx: Int) { - try(sqlite3_bind_double(handle, Int32(idx), double)) - } - - internal func bind(#int: Int, atIndex idx: Int) { - try(sqlite3_bind_int64(handle, Int32(idx), Int64(int))) - } - - internal func bind(#text: String, atIndex idx: Int) { - try(sqlite3_bind_text(handle, Int32(idx), text, -1, SQLITE_TRANSIENT)) - } - private func bind(value: Value?, atIndex idx: Int) { - if let Value = value { - Value.bindTo(self, atIndex: idx) + if let value = value as? Bool { + bind(value ? 1 : 0, atIndex: idx) + } else if let value = value as? Double { + try(sqlite3_bind_double(handle, Int32(idx), value)) + } else if let value = value as? Int { + try(sqlite3_bind_int64(handle, Int32(idx), Int64(value))) + } else if let value = value as? String { + try(sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT)) } else { try(sqlite3_bind_null(handle, Int32(idx))) } diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index 39d41eff..cdd53569 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -21,52 +21,14 @@ // THE SOFTWARE. // -public protocol Value { - - class var datatype: String { get } - - func bindTo(statement: Statement, atIndex idx: Int) - -} +public protocol Value {} public protocol Number: Value {} -extension Bool: Value { - - public static var datatype: String { return "BOOLEAN" } - - public func bindTo(statement: Statement, atIndex idx: Int) { - statement.bind(bool: self, atIndex: idx) - } - -} - -extension Double: Number { - - public static var datatype: String { return "REAL" } - - public func bindTo(statement: Statement, atIndex idx: Int) { - statement.bind(double: self, atIndex: idx) - } - -} - -extension Int: Number { - - public static var datatype: String { return "INTEGER" } - - public func bindTo(statement: Statement, atIndex idx: Int) { - statement.bind(int: self, atIndex: idx) - } - -} - -extension String: Value { +extension Bool: Value {} - public static var datatype: String { return "TEXT" } +extension Double: Number {} - public func bindTo(statement: Statement, atIndex idx: Int) { - statement.bind(text: self, atIndex: idx) - } +extension Int: Number {} -} +extension String: Value {} From 96b818ff517dd432c0aabe75048e0d6517bd6995 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 14 Nov 2014 19:07:31 -0800 Subject: [PATCH 0067/1046] Kill underused typealias, Values This was more useful before the introduction of Expression. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 268e26f3..613ac512 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -21,9 +21,6 @@ // THE SOFTWARE. // -/// A dictionary mapping column names to values. -public typealias Values = [String: Value?] - /// A query object. Used to build SQL statements with a collection of chainable /// helper functions. public struct Query { @@ -622,9 +619,9 @@ public struct Query { /// access to a row’s values. public struct Row { - private var values: Values + private var values: [String: Value?] - private init(_ values: Values) { + private init(_ values: [String: Value?]) { self.values = values } From db6d6621b4e1d67af1346311fbd215568b312a4a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 15 Nov 2014 15:32:35 -0800 Subject: [PATCH 0068/1046] Add custom datatype support This will be more helpful when proper BLOB support is available (for easy serialization/deserialization of binary data (images, etc.), but in the meantime it allows you to support dates flexibly. E.g., an NSDate TEXT column: extension NSDate: Value { public typealias Datatype = String public class var declaredDatatype: String { return Datatype.declaredDatatype } public class func fromDatatypeValue(datatypeValue: Datatype) -> NSDate { return formatter.dateFromString(datatypeValue)! } public var datatypeValue: Datatype { return formatter.stringFromDate(self) } } Or, an NSDate INTEGER column: extension NSDate: Value { public typealias Datatype = Int public class var declaredDatatype: String { return Datatype.declaredDatatype // or "DATETIME" } public class func fromDatatypeValue(datatypeValue: Datatype) -> NSDate { return self(timeIntervalSince1970: NSTimeInterval(datatypeValue)) } public var datatypeValue: Datatype { return Datatype(timeIntervalSince1970) } } Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 41 +++ SQLite Common/Database.swift | 18 +- SQLite Common/Expression.swift | 487 +++++++++++++++++---------- SQLite Common/Query.swift | 43 ++- SQLite Common/Schema.swift | 103 +++--- SQLite Common/Statement.swift | 28 +- SQLite Common/Value.swift | 82 ++++- 7 files changed, 511 insertions(+), 291 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 43ce14f5..c190626f 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -380,4 +380,45 @@ class QueryTests: XCTestCase { XCTAssertEqual(21, users.select(age + 1).first![age + 1]!) } + func test_valueExtension_serializesAndDeserializes() { + let id = Expression("id") + let timestamp = Expression("timestamp") + let touches = db["touches"] + db.create(table: touches) { t in + t.column(id, primaryKey: true) + t.column(timestamp) + } + + let date = NSDate(timeIntervalSince1970: 0) + touches.insert(timestamp <- date)! + XCTAssertEqual(touches.first!.get(timestamp)!, date) + + XCTAssertNil(touches.filter(id == touches.insert()!).first!.get(timestamp)) + + XCTAssert(touches.filter(timestamp < NSDate()).first != nil) + } + +} + +private let formatter: NSDateFormatter = { + let formatter = NSDateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + formatter.timeZone = NSTimeZone(abbreviation: "UTC") + return formatter +}() + +extension NSDate: Value { + + public typealias Datatype = String + + public class var declaredDatatype: String { return Datatype.declaredDatatype } + + public class func fromDatatypeValue(datatypeValue: Datatype) -> NSDate { + return formatter.dateFromString(datatypeValue)! + } + + public var datatypeValue: Datatype { + return formatter.stringFromDate(self) + } + } diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index 856cf680..97728404 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -84,7 +84,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: Value?...) -> Statement { + public func prepare(statement: String, _ bindings: Binding?...) -> Statement { if !bindings.isEmpty { return prepare(statement, bindings) } var statementHandle: COpaquePointer = nil @@ -99,7 +99,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [Value?]) -> Statement { + public func prepare(statement: String, _ bindings: [Binding?]) -> Statement { return prepare(statement).bind(bindings) } @@ -111,7 +111,7 @@ public final class Database { /// statement. /// /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [String: Value?]) -> Statement { + public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement { return prepare(statement).bind(bindings) } @@ -124,7 +124,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement. - public func run(statement: String, _ bindings: Value?...) -> Statement { + public func run(statement: String, _ bindings: Binding?...) -> Statement { return run(statement, bindings) } @@ -135,7 +135,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement. - public func run(statement: String, _ bindings: [Value?]) -> Statement { + public func run(statement: String, _ bindings: [Binding?]) -> Statement { return prepare(statement).run(bindings) } @@ -147,7 +147,7 @@ public final class Database { /// statement. /// /// :returns: The statement. - public func run(statement: String, _ bindings: [String: Value?]) -> Statement { + public func run(statement: String, _ bindings: [String: Binding?]) -> Statement { return prepare(statement).run(bindings) } @@ -161,7 +161,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: Value?...) -> Value? { + public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { return scalar(statement, bindings) } @@ -173,7 +173,7 @@ public final class Database { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [Value?]) -> Value? { + public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return prepare(statement).scalar(bindings) } @@ -186,7 +186,7 @@ public final class Database { /// statement. /// /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [String: Value?]) -> Value? { + public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return prepare(statement).scalar(bindings) } diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index de9fc47a..82a5b08c 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -24,17 +24,17 @@ public struct Expression { public let SQL: String - public let bindings: [Value?] + public let bindings: [Binding?] - public init(_ SQL: String = "", _ bindings: [Value?] = []) { + public init(_ SQL: String = "", _ bindings: [Binding?] = []) { (self.SQL, self.bindings) = (SQL, bindings) } - public init(_ expression: Expression) { + public init(_ expression: Expression) { self.init(expression.SQL, expression.bindings) } - public init(value: Value?) { + public init(value: Binding?) { self.init("?", [value]) } @@ -114,41 +114,41 @@ public func +(lhs: Expression, rhs: String) -> Expression { re public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func +(lhs: Expression, rhs: T) -> Expression { return lhs + Expression(value: rhs) } -public func +(lhs: Expression, rhs: T) -> Expression { return lhs + Expression(value: rhs) } -public func +(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func +(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } - -public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func -(lhs: Expression, rhs: T) -> Expression { return lhs - Expression(value: rhs) } -public func -(lhs: Expression, rhs: T) -> Expression { return lhs - Expression(value: rhs) } -public func -(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } -public func -(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } - -public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func *(lhs: Expression, rhs: T) -> Expression { return lhs * Expression(value: rhs) } -public func *(lhs: Expression, rhs: T) -> Expression { return lhs * Expression(value: rhs) } -public func *(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } -public func *(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } - -public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func /(lhs: Expression, rhs: T) -> Expression { return lhs / Expression(value: rhs) } -public func /(lhs: Expression, rhs: T) -> Expression { return lhs / Expression(value: rhs) } -public func /(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } -public func /(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func +(lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } +public func +(lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } +public func +(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } +public func +(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } + +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func -(lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } +public func -(lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } +public func -(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } +public func -(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } + +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func *(lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } +public func *(lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } +public func *(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } +public func *(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } + +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func /(lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } +public func /(lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } +public func /(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } +public func /(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } @@ -226,80 +226,168 @@ public func collate(collation: Collation, expression: Expression) -> Ex // MARK: - Predicates -public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: T) -> Expression { return lhs == Expression(value: rhs) } -public func ==(lhs: Expression, rhs: T?) -> Expression { - if let rhs = rhs { return lhs == Expression(value: rhs) } +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return infix("=", lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return infix("=", lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return infix("=", lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return infix("=", lhs, rhs) +} +public func ==(lhs: Expression, rhs: V) -> Expression { + return lhs == Expression(value: rhs.datatypeValue) +} +public func ==(lhs: Expression, rhs: V?) -> Expression { + if let rhs = rhs { return lhs == Expression(value: rhs.datatypeValue) } return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) } -public func ==(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) == rhs } -public func ==(lhs: T?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs) == rhs } +public func ==(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) == rhs +} +public func ==(lhs: V?, rhs: Expression) -> Expression { + if let lhs = lhs { return Expression(value: lhs.datatypeValue) == rhs } return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: T) -> Expression { return lhs != Expression(value: rhs) } -public func !=(lhs: Expression, rhs: T?) -> Expression { - if let rhs = rhs { return lhs != Expression(value: rhs) } +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func !=(lhs: Expression, rhs: V) -> Expression { + return lhs != Expression(value: rhs.datatypeValue) +} +public func !=(lhs: Expression, rhs: V?) -> Expression { + if let rhs = rhs { return lhs != Expression(value: rhs.datatypeValue) } return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) } -public func !=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) != rhs } -public func !=(lhs: T?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs) != rhs } +public func !=(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) != rhs +} +public func !=(lhs: V?, rhs: Expression) -> Expression { + if let lhs = lhs { return Expression(value: lhs.datatypeValue) != rhs } return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) } -public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: T) -> Expression { return lhs > Expression(value: rhs) } -public func >(lhs: Expression, rhs: T) -> Expression { return lhs > Expression(value: rhs) } -public func >(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) > rhs } -public func >(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) > rhs } - -public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: T) -> Expression { return lhs >= Expression(value: rhs) } -public func >=(lhs: Expression, rhs: T) -> Expression { return lhs >= Expression(value: rhs) } -public func >=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) >= rhs } -public func >=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) >= rhs } - -public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: T) -> Expression { return lhs < Expression(value: rhs) } -public func <(lhs: Expression, rhs: T) -> Expression { return lhs < Expression(value: rhs) } -public func <(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) < rhs } -public func <(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) < rhs } - -public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: T) -> Expression { return lhs <= Expression(value: rhs) } -public func <=(lhs: Expression, rhs: T) -> Expression { return lhs <= Expression(value: rhs) } -public func <=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) <= rhs } -public func <=(lhs: T, rhs: Expression) -> Expression { return Expression(value: lhs) <= rhs } - -public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } - -public func ~=(lhs: I, rhs: Expression) -> Expression { +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >(lhs: Expression, rhs: V) -> Expression { + return lhs > Expression(value: rhs.datatypeValue) +} +public func >(lhs: Expression, rhs: V) -> Expression { + return lhs > Expression(value: rhs.datatypeValue) +} +public func >(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) > rhs +} +public func >(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) > rhs +} + +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func >=(lhs: Expression, rhs: V) -> Expression { + return lhs >= Expression(value: rhs.datatypeValue) +} +public func >=(lhs: Expression, rhs: V) -> Expression { + return lhs >= Expression(value: rhs.datatypeValue) +} +public func >=(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) >= rhs +} +public func >=(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) >= rhs +} + +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <(lhs: Expression, rhs: V) -> Expression { + return lhs < Expression(value: rhs.datatypeValue) +} +public func <(lhs: Expression, rhs: V) -> Expression { + return lhs < Expression(value: rhs.datatypeValue) +} +public func <(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) < rhs +} +public func <(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) < rhs +} + +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(__FUNCTION__, lhs, rhs) +} +public func <=(lhs: Expression, rhs: V) -> Expression { + return lhs <= Expression(value: rhs.datatypeValue) +} +public func <=(lhs: Expression, rhs: V) -> Expression { + return lhs <= Expression(value: rhs.datatypeValue) +} +public func <=(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) <= rhs +} +public func <=(lhs: V, rhs: Expression) -> Expression { + return Expression(value: lhs.datatypeValue) <= rhs +} + +public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } + +public func ~=(lhs: I, rhs: Expression) -> Expression { return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) } -public func ~=(lhs: I, rhs: Expression) -> Expression { - return Expression(lhs ~= Expression(rhs)) +public func ~=(lhs: I, rhs: Expression) -> Expression { + return Expression(lhs ~= Expression(rhs)) } // MARK: Operators @@ -350,23 +438,23 @@ public prefix func !(rhs: Expression) -> Expression { return wrap( // MARK: - Core Functions -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -// FIXME: support Expression..., Expression when Swift supports inner variadic signatures -public func coalesce(expressions: Expression...) -> Expression { +// FIXME: support Expression..., Expression when Swift supports inner variadic signatures +public func coalesce(expressions: Expression...) -> Expression { return wrap(__FUNCTION__, join(", ", expressions.map { $0 })) } -public func ifnull(expression: Expression, defaultValue: T) -> Expression { +public func ifnull(expression: Expression, defaultValue: V) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, defaultValue])) } -public func ??(expression: Expression, defaultValue: T) -> Expression { +public func ??(expression: Expression, defaultValue: V) -> Expression { return ifnull(expression, defaultValue) } -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } @@ -443,41 +531,49 @@ public func upper(expression: Expression) -> Expression { retu // MARK: - Aggregate Functions -public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func count(star: Star) -> Expression { return count(star(nil, nil)) } -public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func max(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} +public func max(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} -public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func min(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} +public func min(expression: Expression) -> Expression { + return wrap(__FUNCTION__, expression) +} -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } +public func average(expression: Expression) -> Expression { return wrap("avg", expression) } +public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } // MARK: - Helper -public typealias Star = (Expression?, Expression?) -> Expression<()> +public typealias Star = (Expression?, Expression?) -> Expression<()> -public func *(Expression?, Expression?) -> Expression<()> { +public func *(Expression?, Expression?) -> Expression<()> { return Expression<()>("*") } -public func contains(values: [T], column: Expression) -> Expression { +public func contains(values: [V.Datatype], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression("(\(templates))", values.map { $0 })) + return infix("IN", column, Expression("(\(templates))", values.map { $0 })) } -public func contains(values: [T?], column: Expression) -> Expression { +public func contains(values: [V.Datatype?], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression("(\(templates))", values.map { $0 })) + return infix("IN", column, Expression("(\(templates))", values.map { $0 })) } // MARK: - Modifying @@ -493,76 +589,95 @@ public typealias Setter = (Expressible, Expressible) /// /// :returns: A setter that can be used in a Query's insert and update /// functions. -public func set(column: Expression, value: T) -> Setter { return (column, Expression<()>(value: value)) } -public func set(column: Expression, value: T?) -> Setter { return (column, Expression<()>(value: value)) } - -/// Returns a setter to be used with INSERT and UPDATE statements. -/// -/// :param: column The column being set. -/// -/// :param: value The value the column is being set to. -/// -/// :returns: A setter that can be used in a Query's insert and update -/// functions. -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: V) -> Setter { + return (column, Expression<()>(value: value.datatypeValue)) +} +public func set(column: Expression, value: V?) -> Setter { + return (column, Expression<()>(value: value?.datatypeValue)) +} +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } +public func set(column: Expression, value: Expression) -> Setter { return (column, value) } infix operator <- { associativity left precedence 140 } -public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <-(column: Expression, value: T) -> Setter { return set(column, value) } -public func <-(column: Expression, value: T?) -> Setter { return set(column, value) } - -public func +=(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: Expression) -> Setter { return set(column, value) } +public func <-(column: Expression, value: V) -> Setter { return set(column, value) } +public func <-(column: Expression, value: V?) -> Setter { return set(column, value) } + +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: String) -> Setter { return set(column, column + value) } +public func +=(column: Expression, value: String) -> Setter { return set(column, column + value) } + +public func +=>(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=>(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=>(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=>(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: String) -> Setter { - return set(column, column + value) +public func +=>(column: Expression, value: V) -> Setter { return set(column, column + value) } +public func +=>(column: Expression, value: V) -> Setter { return set(column, column + value) } + +public func -=>(column: Expression, value: Expression) -> Setter { + return set(column, column - value) } -public func +=(column: Expression, value: String) -> Setter { - return set(column, column + value) +public func -=>(column: Expression, value: Expression) -> Setter { + return set(column, column - value) +} +public func -=>(column: Expression, value: Expression) -> Setter { + return set(column, column - value) +} +public func -=>(column: Expression, value: Expression) -> Setter { + return set(column, column - value) +} +public func -=>(column: Expression, value: V) -> Setter { return set(column, column - value) } +public func -=>(column: Expression, value: V) -> Setter { return set(column, column - value) } + +public func *=>(column: Expression, value: Expression) -> Setter { + return set(column, column * value) +} +public func *=>(column: Expression, value: Expression) -> Setter { + return set(column, column * value) +} +public func *=>(column: Expression, value: Expression) -> Setter { + return set(column, column * value) } +public func *=>(column: Expression, value: Expression) -> Setter { + return set(column, column * value) +} +public func *=>(column: Expression, value: V) -> Setter { return set(column, column * value) } +public func *=>(column: Expression, value: V) -> Setter { return set(column, column * value) } -public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: T) -> Setter { return set(column, column + value) } -public func +=(column: Expression, value: T) -> Setter { return set(column, column + value) } - -public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -=(column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -=(column: Expression, value: T) -> Setter { return set(column, column - value) } -public func -=(column: Expression, value: T) -> Setter { return set(column, column - value) } - -public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *=(column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *=(column: Expression, value: T) -> Setter { return set(column, column * value) } -public func *=(column: Expression, value: T) -> Setter { return set(column, column * value) } - -public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /=(column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /=(column: Expression, value: T) -> Setter { return set(column, column / value) } -public func /=(column: Expression, value: T) -> Setter { return set(column, column / value) } +public func /=>(column: Expression, value: Expression) -> Setter { + return set(column, column / value) +} +public func /=>(column: Expression, value: Expression) -> Setter { + return set(column, column / value) +} +public func /=>(column: Expression, value: Expression) -> Setter { + return set(column, column / value) +} +public func /=>(column: Expression, value: Expression) -> Setter { + return set(column, column / value) +} +public func /=>(column: Expression, value: V) -> Setter { + return set(column, column / value) +} +public func /=>(column: Expression, value: V) -> Setter { + return set(column, column / value) +} public func %=(column: Expression, value: Expression) -> Setter { return set(column, column % value) } public func %=(column: Expression, value: Expression) -> Setter { return set(column, column % value) } @@ -626,7 +741,7 @@ public postfix func --(column: Expression) -> Setter { // MARK: - Internal internal func join(separator: String, expressions: [Expressible]) -> Expression<()> { - var (SQL, bindings) = ([String](), [Value?]()) + var (SQL, bindings) = ([String](), [Binding?]()) for expressible in expressions { let expression = expressible.expression SQL.append(expression.SQL) @@ -635,7 +750,7 @@ internal func join(separator: String, expressions: [Expressible]) -> Expression< return Expression(Swift.join(separator, SQL), bindings) } -internal func transcode(literal: Value?) -> String { +internal func transcode(literal: Binding?) -> String { if let literal = literal { if let literal = literal as? String { return quote(literal: literal) } if let literal = literal as? Bool { return literal ? "1" : "0" } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 613ac512..d653e4c6 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -283,11 +283,11 @@ public struct Query { /// /// :returns: A column expression namespaced with the query’s table name or /// alias. - public func namespace(column: Expression) -> Expression { + public func namespace(column: Expression) -> Expression { return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) } - // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression + // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression public subscript(column: Expression) -> Expression { return namespace(column) } public subscript(column: Expression) -> Expression { return namespace(column) } @@ -545,7 +545,7 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The number of rows matching the given column. - public func count(column: Expression) -> Int { + public func count(column: Expression) -> Int { return calculate(SQLite.count(column))! } @@ -554,10 +554,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The largest value of the given column. - public func max(column: Expression) -> T? { + public func max(column: Expression) -> V? { return calculate(SQLite.max(column)) } - public func max(column: Expression) -> T? { + public func max(column: Expression) -> V? { return calculate(SQLite.max(column)) } @@ -566,10 +566,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The smallest value of the given column. - public func min(column: Expression) -> T? { + public func min(column: Expression) -> V? { return calculate(SQLite.min(column)) } - public func min(column: Expression) -> T? { + public func min(column: Expression) -> V? { return calculate(SQLite.min(column)) } @@ -578,10 +578,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The average value of the given column. - public func average(column: Expression) -> Double? { + public func average(column: Expression) -> Double? { return calculate(SQLite.average(column)) } - public func average(column: Expression) -> Double? { + public func average(column: Expression) -> Double? { return calculate(SQLite.average(column)) } @@ -590,10 +590,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The sum of the given column’s values. - public func sum(column: Expression) -> T? { + public func sum(column: Expression) -> V? { return calculate(SQLite.sum(column)) } - public func sum(column: Expression) -> T? { + public func sum(column: Expression) -> V? { return calculate(SQLite.sum(column)) } @@ -602,10 +602,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The total of the given column’s values. - public func total(column: Expression) -> Double { + public func total(column: Expression) -> Double { return calculate(SQLite.total(column))! } - public func total(column: Expression) -> Double { + public func total(column: Expression) -> Double { return calculate(SQLite.total(column))! } @@ -619,9 +619,9 @@ public struct Query { /// access to a row’s values. public struct Row { - private var values: [String: Value?] + private var values: [String: Binding?] - private init(_ values: [String: Value?]) { + private init(_ values: [String: Binding?]) { self.values = values } @@ -630,10 +630,17 @@ public struct Row { /// :param: column An expression representing a column selected in a Query. /// /// returns The value for the given column. - public func get(column: Expression) -> T { return values[column.SQL] as T } - public func get(column: Expression) -> T? { return values[column.SQL] as? T } + public func get(column: Expression) -> V { + return V.fromDatatypeValue(values[column.SQL] as V.Datatype) as V + } + public func get(column: Expression) -> V? { + if let datatypeValue = values[column.SQL] as? V.Datatype { + return (V.fromDatatypeValue(datatypeValue) as V) + } + return nil + } - // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression + // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression /// Returns a row’s value for the given column. /// diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index b99d9685..45aeee83 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -46,33 +46,33 @@ public extension Database { return run("ALTER TABLE \(table.tableName) RENAME TO \(tableName)") } - public func alter( + public func alter( #table: Query, - add column: Expression, + add column: Expression, check: Expression? = nil, - defaultValue: T + defaultValue: V ) -> Statement { - return alter(table, define(column, false, false, false, check, Expression(value: defaultValue), nil)) + return alter(table, define(column, false, false, false, check, Expression(value: defaultValue.datatypeValue), nil)) } - public func alter( + public func alter( #table: Query, - add column: Expression, + add column: Expression, check: Expression? = nil, - defaultValue: T? = nil + defaultValue: V? = nil ) -> Statement { - let value = defaultValue.map { Expression(value: $0) } - return alter(table, define(Expression(column), false, true, false, check, value, nil)) + let value = defaultValue.map { Expression(value: $0.datatypeValue) } + return alter(table, define(Expression(column), false, true, false, check, value, nil)) } - public func alter( + public func alter( #table: Query, - add column: Expression, + add column: Expression, check: Expression? = nil, - references: Expression + references: Expression ) -> Statement { let expressions = [Expression<()>("REFERENCES"), namespace(references)] - return alter(table, define(Expression(column), false, true, false, check, nil, expressions)) + return alter(table, define(Expression(column), false, true, false, check, nil, expressions)) } private func alter(table: Query, _ definition: Expressible) -> Statement { @@ -127,44 +127,44 @@ public final class SchemaBuilder { self.table = table } - public func column( - name: Expression, + public func column( + name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression? = nil + defaultValue value: Expression? = nil ) { column(name, primaryKey, false, unique, check, value) } - public func column( - name: Expression, + public func column( + name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue value: T + defaultValue value: V ) { - column(name, primaryKey, false, unique, check, Expression(value: value)) + column(name, primaryKey, false, unique, check, Expression(value: value.datatypeValue)) } - public func column( - name: Expression, + public func column( + name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression? = nil + defaultValue value: Expression? = nil ) { - column(Expression(name), primaryKey, true, unique, check, value) + column(Expression(name), primaryKey, true, unique, check, value) } - public func column( - name: Expression, + public func column( + name: Expression, primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue value: T? + defaultValue value: V? ) { - column(Expression(name), primaryKey, true, unique, check, value.map { Expression(value: $0) }) + column(Expression(name), primaryKey, true, unique, check, value.map { Expression(value: $0.datatypeValue) }) } public func column( @@ -339,13 +339,13 @@ public final class SchemaBuilder { ) } - private func column( - name: Expression, + private func column( + name: Expression, _ primaryKey: Bool, _ null: Bool, _ unique: Bool, _ check: Expression?, - _ defaultValue: Expression?, + _ defaultValue: Expression?, _ expressions: [Expressible]? = nil ) { columns.append(define(name, primaryKey, null, unique, check, defaultValue, expressions)) @@ -379,9 +379,9 @@ public final class SchemaBuilder { } - public func foreignKey( - column: Expression, - references: Expression, + public func foreignKey( + column: Expression, + references: Expression, update: Dependency? = nil, delete: Dependency? = nil ) { @@ -392,9 +392,9 @@ public final class SchemaBuilder { if let delete = delete { parts.append(Expression<()>("ON DELETE \(delete.rawValue)")) } columns.append(SQLite.join(" ", parts)) } - public func foreignKey( - column: Expression, - references: Expression, + public func foreignKey( + column: Expression, + references: Expression, update: Dependency? = nil, delete: Dependency? = nil ) { @@ -406,16 +406,16 @@ public final class SchemaBuilder { columns.append(SQLite.join(" ", parts)) } - public func foreignKey( - column: Expression, + public func foreignKey( + column: Expression, references: Query, update: Dependency? = nil, delete: Dependency? = nil ) { foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) } - public func foreignKey( - column: Expression, + public func foreignKey( + column: Expression, references: Query, update: Dependency? = nil, delete: Dependency? = nil @@ -439,16 +439,16 @@ private func namespace(column: Expressible) -> Expressible { return Expression<()>("\(reference))", expression.bindings) } -private func define( - column: Expression, +private func define( + column: Expression, primaryKey: Bool, null: Bool, unique: Bool, check: Expression?, - defaultValue: Expression?, + defaultValue: Expression?, expressions: [Expressible]? ) -> Expressible { - var parts: [Expressible] = [Expression<()>(column), Expression<()>(datatype(T))] + var parts: [Expressible] = [Expression<()>(column), Expression<()>(V.declaredDatatype)] if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } if !null { parts.append(Expression<()>("NOT NULL")) } if unique { parts.append(Expression<()>("UNIQUE")) } @@ -458,19 +458,6 @@ private func define( return SQLite.join(" ", parts) } -private func datatype(type: T.Type) -> String { - if type is Bool.Type { - return "BOOLEAN" - } else if type is Double.Type { - return "REAL" - } else if type is Int.Type { - return "INTEGER" - } else if type is String.Type { - return "TEXT" - } - return "NONE" -} - private func createSQL( type: String, temporary: Bool, diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index 642f9c98..ef3ad8bb 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -47,7 +47,7 @@ public final class Statement { /// :param: values A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func bind(values: Value?...) -> Statement { + public func bind(values: Binding?...) -> Statement { return bind(values) } @@ -56,7 +56,7 @@ public final class Statement { /// :param: values A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func bind(values: [Value?]) -> Statement { + public func bind(values: [Binding?]) -> Statement { if values.isEmpty { return self } reset() assert(values.count == Int(sqlite3_bind_parameter_count(handle)), "\(Int(sqlite3_bind_parameter_count(handle))) values expected, \(values.count) passed") @@ -70,7 +70,7 @@ public final class Statement { /// statement. /// /// :returns: The statement object (useful for chaining). - public func bind(values: [String: Value?]) -> Statement { + public func bind(values: [String: Binding?]) -> Statement { reset() for (name, value) in values { let idx = sqlite3_bind_parameter_index(handle, name) @@ -80,7 +80,7 @@ public final class Statement { return self } - private func bind(value: Value?, atIndex idx: Int) { + private func bind(value: Binding?, atIndex idx: Int) { if let value = value as? Bool { bind(value ? 1 : 0, atIndex: idx) } else if let value = value as? Double { @@ -99,7 +99,7 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func run(bindings: Value?...) -> Statement { + public func run(bindings: Binding?...) -> Statement { if !bindings.isEmpty { return run(bindings) } reset(clearBindings: false) for _ in self {} @@ -109,7 +109,7 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The statement object (useful for chaining). - public func run(bindings: [Value?]) -> Statement { + public func run(bindings: [Binding?]) -> Statement { return bind(bindings).run() } @@ -117,7 +117,7 @@ public final class Statement { /// statement. /// /// :returns: The statement object (useful for chaining). - public func run(bindings: [String: Value?]) -> Statement { + public func run(bindings: [String: Binding?]) -> Statement { return bind(bindings).run() } @@ -126,10 +126,10 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(bindings: Value?...) -> Value? { + public func scalar(bindings: Binding?...) -> Binding? { if !bindings.isEmpty { return scalar(bindings) } reset(clearBindings: false) - let value: Value? = next()?[0] + let value: Binding? = next()?[0] for _ in self {} return value } @@ -137,7 +137,7 @@ public final class Statement { /// :param: bindings A list of parameters to bind to the statement. /// /// :returns: The first value of the first row returned. - public func scalar(bindings: [Value?]) -> Value? { + public func scalar(bindings: [Binding?]) -> Binding? { return bind(bindings).scalar() } @@ -145,7 +145,7 @@ public final class Statement { /// statement. /// /// :returns: The first value of the first row returned. - public func scalar(bindings: [String: Value?]) -> Value? { + public func scalar(bindings: [String: Binding?]) -> Binding? { return bind(bindings).scalar() } @@ -193,7 +193,7 @@ extension Statement: SequenceType { extension Statement: GeneratorType { /// A single row. - public typealias Element = [Value?] + public typealias Element = [Binding?] /// :returns: The next row from the result set (or nil). public func next() -> Element? { @@ -229,9 +229,9 @@ extension Statement: GeneratorType { } /// :returns: A dictionary of column name to row value. - public var values: [String: Value?]? { + public var values: [String: Binding?]? { if let row = row { - var values = [String: Value?]() + var values = [String: Binding?]() for idx in 0.. ValueType + + var datatypeValue: Datatype { get } + +} + +public protocol Number: Binding {} + +extension Bool: Binding, Value { + + public typealias Datatype = Bool + + public static var declaredDatatype = "BOOLEAN" + + public static func fromDatatypeValue(datatypeValue: Datatype) -> Bool { + return self(datatypeValue) + } + + public var datatypeValue: Datatype { + return self + } + +} + +extension Double: Number, Value { + + public typealias Datatype = Double + + public static var declaredDatatype = "REAL" + + public static func fromDatatypeValue(datatypeValue: Datatype) -> Double { + return self(datatypeValue) + } + + public var datatypeValue: Datatype { + return self + } + +} + +extension Int: Number, Value { + + public typealias Datatype = Int + + public static var declaredDatatype = "INTEGER" + + public static func fromDatatypeValue(datatypeValue: Datatype) -> Int { + return self(datatypeValue) + } + + public var datatypeValue: Datatype { + return self + } + +} + +extension String: Binding, Value { + + public typealias Datatype = String + + public static var declaredDatatype = "TEXT" + + public static func fromDatatypeValue(datatypeValue: Datatype) -> String { + return self(datatypeValue) + } + + public var datatypeValue: Datatype { + return self + } + +} From 40b05b2426c62e99a10508eaee6b6ccb82adf6d5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 14 Nov 2014 08:18:52 -0800 Subject: [PATCH 0069/1046] Perform internal database operations in FIFO queue This breaks our embargo with Foundation, but our application had been suffering as a result. Signed-off-by: Stephen Celis --- SQLite Common/Database.swift | 21 +++++++++++++-------- SQLite Common/Statement.swift | 19 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index 97728404..d747765a 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -21,10 +21,12 @@ // THE SOFTWARE. // +import Foundation + /// A connection (handle) to a SQLite database. public final class Database { - private let handle: COpaquePointer = nil + internal let handle: COpaquePointer = nil /// Whether or not the database was opened in a read-only state. public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } @@ -46,7 +48,7 @@ public final class Database { try(sqlite3_open_v2(path ?? "", &handle, flags, nil)) } - deinit { sqlite3_close(handle) } // sqlite3_close_v2 in Yosemite/iOS 8? + deinit { try(sqlite3_close(handle)) } // sqlite3_close_v2 in Yosemite/iOS 8? // MARK: - @@ -86,10 +88,7 @@ public final class Database { /// :returns: A prepared statement. public func prepare(statement: String, _ bindings: Binding?...) -> Statement { if !bindings.isEmpty { return prepare(statement, bindings) } - - var statementHandle: COpaquePointer = nil - try(sqlite3_prepare_v2(handle, statement, -1, &statementHandle, nil)) - return Statement(statementHandle) + return Statement(self, statement) } /// Prepares a single SQL statement and binds parameters to it. @@ -332,10 +331,16 @@ public final class Database { return String.fromCString(sqlite3_errmsg(handle))! } - private func try(block: @autoclosure () -> Int32) { - if block() != SQLITE_OK { assertionFailure("\(lastError)") } + internal func try(block: @autoclosure () -> Int32) { + perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError)") } } } + // MARK: - Threading + + private let queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) + + internal func perform(block: () -> ()) { dispatch_sync(queue, block) } + } extension Database: DebugPrintable { diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index ef3ad8bb..54b23176 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -27,11 +27,14 @@ private let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern /// A single SQL statement. public final class Statement { - private let handle: COpaquePointer + private let handle: COpaquePointer = nil - private var database: COpaquePointer { return sqlite3_db_handle(handle) } + private var database: Database - internal init(_ handle: COpaquePointer) { self.handle = handle } + internal init(_ database: Database, _ SQL: String) { + self.database = database + database.try(sqlite3_prepare_v2(database.handle, SQL, -1, &handle, nil)) + } deinit { sqlite3_finalize(handle) } @@ -171,10 +174,12 @@ public final class Statement { private func try(block: @autoclosure () -> Int32) { if failed { return } - status = block() - if failed { - reason = String.fromCString(sqlite3_errmsg(database)) - assert(status == SQLITE_CONSTRAINT, "\(reason!)") + database.perform { + self.status = block() + if self.failed { + self.reason = String.fromCString(sqlite3_errmsg(self.database.handle)) + assert(self.status == SQLITE_CONSTRAINT, "\(self.reason!)") + } } } From 35066a66c3bdb729f27f8de639a7dd7470ab7571 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 14 Nov 2014 08:29:10 -0800 Subject: [PATCH 0070/1046] BLOB support Via a new struct, Blob. Blob, like NSData, exposes `bytes` and `length` (and actually uses NSData under the hood, for the sake of memory management). Without any extensions, you can use Blob's public interface, though it's slightly verbose: let data = UIImagePNGRepresentation(image) let blob = Blob(bytes: data.bytes, length: data.length) users.insert(avatar <- blob) Simple extension simplifies: extension Blob { init(_ data: NSData) { self.init(bytes: data.bytes, length: data.length) } public var data -> NSData { return NSData(bytes: bytes, length: length) } } users.insert(avatar <- Blob(UIImagePNGRepresentation(image))) // INSERT INTO users (avatar) VALUES (x'...') You can, of course, extend further to suit your needs: extension Blob { init(_ image: UIImage) { self.init(UIImagePNGRepresentation(image)) } public var image -> UIImage { return UIImage(data: data) } } users.insert(avatar <- Blob(image)) // INSERT INTO users (avatar) VALUES (x'...') Signed-off-by: Stephen Celis --- SQLite Common Tests/StatementTests.swift | 49 ++++++++++++++++++------ SQLite Common/Query.swift | 26 +++---------- SQLite Common/Statement.swift | 8 +++- SQLite Common/Value.swift | 46 +++++++++++++++++++--- 4 files changed, 91 insertions(+), 38 deletions(-) diff --git a/SQLite Common Tests/StatementTests.swift b/SQLite Common Tests/StatementTests.swift index 453f8e19..b406ed07 100644 --- a/SQLite Common Tests/StatementTests.swift +++ b/SQLite Common Tests/StatementTests.swift @@ -12,26 +12,42 @@ class StatementTests: XCTestCase { } func test_bind_withVariadicParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?") - ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3'": 1]) { _ in - stmt.bind(false, 1, 2.0, "3").run() - return + let stmt = db.prepare("SELECT ?, ?, ?, ?, ?") + ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in + withBlob { blob in + stmt.bind(false, 1, 2.0, "3", blob).run() + return + } } } func test_bind_withArrayOfParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?") - ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3'": 1]) { _ in - stmt.bind([false, 1, 2.0, "3"]).run() - return + let stmt = db.prepare("SELECT ?, ?, ?, ?, ?") + ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in + withBlob { blob in + stmt.bind([false, 1, 2.0, "3", blob]).run() + return + } } } func test_bind_withNamedParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4") - ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3'": 1]) { _ in - stmt.bind(["?1": false, "?2": 1, "?3": 2.0, "?4": "3"]).run() - return + let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4, ?5") + ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in + withBlob { blob in + stmt.bind(["?1": false, "?2": 1, "?3": 2.0, "?4": "3", "?5": blob]).run() + return + } + } + } + + func test_bind_withBlob_bindsBlob() { + let stmt = db.prepare("SELECT ?") + ExpectExecutions(db, ["SELECT x'34'": 1]) { _ in + withBlob { blob in + stmt.bind(blob).run() + return + } } } @@ -183,3 +199,12 @@ class StatementTests: XCTestCase { } } + +func withBlob(block: Blob -> ()) { + let length = 1 + let buflen = Int(length) + 1 + let buffer = UnsafeMutablePointer<()>.alloc(buflen) + memcpy(buffer, "4", UInt(length)) + block(Blob(bytes: buffer, length: length)) + buffer.dealloc(buflen) +} diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index d653e4c6..85857efd 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -289,6 +289,9 @@ public struct Query { // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } public subscript(column: Expression) -> Expression { return namespace(column) } @@ -642,35 +645,18 @@ public struct Row { // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression - /// Returns a row’s value for the given column. - /// - /// :param: column An expression representing a column selected in a Query. - /// - /// returns The value for the given column. + public subscript(column: Expression) -> Blob { return get(column) } + public subscript(column: Expression) -> Blob? { return get(column) } + public subscript(column: Expression) -> Bool { return get(column) } public subscript(column: Expression) -> Bool? { return get(column) } - /// Returns a row’s value for the given column. - /// - /// :param: column An expression representing a column selected in a Query. - /// - /// returns The value for the given column. public subscript(column: Expression) -> Double { return get(column) } public subscript(column: Expression) -> Double? { return get(column) } - /// Returns a row’s value for the given column. - /// - /// :param: column An expression representing a column selected in a Query. - /// - /// returns The value for the given column. public subscript(column: Expression) -> Int { return get(column) } public subscript(column: Expression) -> Int? { return get(column) } - /// Returns a row’s value for the given column. - /// - /// :param: column An expression representing a column selected in a Query. - /// - /// returns The value for the given column. public subscript(column: Expression) -> String { return get(column) } public subscript(column: Expression) -> String? { return get(column) } diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index 54b23176..ddfbee49 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -84,7 +84,9 @@ public final class Statement { } private func bind(value: Binding?, atIndex idx: Int) { - if let value = value as? Bool { + if let value = value as? Blob { + try(sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT)) + } else if let value = value as? Bool { bind(value ? 1 : 0, atIndex: idx) } else if let value = value as? Double { try(sqlite3_bind_double(handle, Int32(idx), value)) @@ -213,6 +215,10 @@ extension Statement: GeneratorType { var row = Element() for idx in 0.. { + return data.bytes + } + + public var length: Int { + return data.length + } + + public init(bytes: UnsafePointer<()>, length: Int) { + data = NSData(bytes: bytes, length: length) + } + +} + +extension Blob: Binding, Value { + + public typealias Datatype = Blob + + public static var declaredDatatype = "BLOB" + + public static func fromDatatypeValue(datatypeValue: Datatype) -> Blob { + return datatypeValue + } + + public var datatypeValue: Datatype { + return self + } + +} extension Bool: Binding, Value { @@ -46,7 +82,7 @@ extension Bool: Binding, Value { public static var declaredDatatype = "BOOLEAN" public static func fromDatatypeValue(datatypeValue: Datatype) -> Bool { - return self(datatypeValue) + return datatypeValue } public var datatypeValue: Datatype { @@ -62,7 +98,7 @@ extension Double: Number, Value { public static var declaredDatatype = "REAL" public static func fromDatatypeValue(datatypeValue: Datatype) -> Double { - return self(datatypeValue) + return datatypeValue } public var datatypeValue: Datatype { @@ -78,7 +114,7 @@ extension Int: Number, Value { public static var declaredDatatype = "INTEGER" public static func fromDatatypeValue(datatypeValue: Datatype) -> Int { - return self(datatypeValue) + return datatypeValue } public var datatypeValue: Datatype { @@ -94,7 +130,7 @@ extension String: Binding, Value { public static var declaredDatatype = "TEXT" public static func fromDatatypeValue(datatypeValue: Datatype) -> String { - return self(datatypeValue) + return datatypeValue } public var datatypeValue: Datatype { From 0b1c179562a43000522e747504ea14626a6b47bc Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 15 Nov 2014 18:19:54 -0800 Subject: [PATCH 0071/1046] Enforce Equatable/Comparable datatypes in expressions This prevents BLOB columns from being compared (though they can be equated). If someone has a valid reason for extending Blob to be Comparable, I'm all ears. In the meantime, it seems like an accident waiting to happen. Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 100 ++++++++++++++++----------------- SQLite Common/Value.swift | 6 ++ 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 82a5b08c..cf08b193 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -226,167 +226,167 @@ public func collate(collation: Collation, expression: Expression) -> Ex // MARK: - Predicates -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==>(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==>(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==>(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==>(lhs: Expression, rhs: Expression) -> Expression { return infix("=", lhs, rhs) } -public func ==(lhs: Expression, rhs: V) -> Expression { +public func ==>(lhs: Expression, rhs: V) -> Expression { return lhs == Expression(value: rhs.datatypeValue) } -public func ==(lhs: Expression, rhs: V?) -> Expression { +public func ==>(lhs: Expression, rhs: V?) -> Expression { if let rhs = rhs { return lhs == Expression(value: rhs.datatypeValue) } return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) } -public func ==(lhs: V, rhs: Expression) -> Expression { +public func ==>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) == rhs } -public func ==(lhs: V?, rhs: Expression) -> Expression { +public func ==>(lhs: V?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs.datatypeValue) == rhs } return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func !=(lhs: Expression, rhs: V) -> Expression { +public func !=>(lhs: Expression, rhs: V) -> Expression { return lhs != Expression(value: rhs.datatypeValue) } -public func !=(lhs: Expression, rhs: V?) -> Expression { +public func !=>(lhs: Expression, rhs: V?) -> Expression { if let rhs = rhs { return lhs != Expression(value: rhs.datatypeValue) } return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) } -public func !=(lhs: V, rhs: Expression) -> Expression { +public func !=>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) != rhs } -public func !=(lhs: V?, rhs: Expression) -> Expression { +public func !=>(lhs: V?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs.datatypeValue) != rhs } return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression { +public func >>(lhs: Expression, rhs: V) -> Expression { return lhs > Expression(value: rhs.datatypeValue) } -public func >(lhs: Expression, rhs: V) -> Expression { +public func >>(lhs: Expression, rhs: V) -> Expression { return lhs > Expression(value: rhs.datatypeValue) } -public func >(lhs: V, rhs: Expression) -> Expression { +public func >>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) > rhs } -public func >(lhs: V, rhs: Expression) -> Expression { +public func >>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) > rhs } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression { +public func >=>(lhs: Expression, rhs: V) -> Expression { return lhs >= Expression(value: rhs.datatypeValue) } -public func >=(lhs: Expression, rhs: V) -> Expression { +public func >=>(lhs: Expression, rhs: V) -> Expression { return lhs >= Expression(value: rhs.datatypeValue) } -public func >=(lhs: V, rhs: Expression) -> Expression { +public func >=>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) >= rhs } -public func >=(lhs: V, rhs: Expression) -> Expression { +public func >=>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) >= rhs } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression { +public func <>(lhs: Expression, rhs: V) -> Expression { return lhs < Expression(value: rhs.datatypeValue) } -public func <(lhs: Expression, rhs: V) -> Expression { +public func <>(lhs: Expression, rhs: V) -> Expression { return lhs < Expression(value: rhs.datatypeValue) } -public func <(lhs: V, rhs: Expression) -> Expression { +public func <>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) < rhs } -public func <(lhs: V, rhs: Expression) -> Expression { +public func <>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) < rhs } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression { +public func <=>(lhs: Expression, rhs: V) -> Expression { return lhs <= Expression(value: rhs.datatypeValue) } -public func <=(lhs: Expression, rhs: V) -> Expression { +public func <=>(lhs: Expression, rhs: V) -> Expression { return lhs <= Expression(value: rhs.datatypeValue) } -public func <=(lhs: V, rhs: Expression) -> Expression { +public func <=>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) <= rhs } -public func <=(lhs: V, rhs: Expression) -> Expression { +public func <=>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs.datatypeValue) <= rhs } public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public func ~=(lhs: I, rhs: Expression) -> Expression { +public func ~=, V == I.Bound>(lhs: I, rhs: Expression) -> Expression { return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) } -public func ~=(lhs: I, rhs: Expression) -> Expression { +public func ~=, V == I.Bound>(lhs: I, rhs: Expression) -> Expression { return Expression(lhs ~= Expression(rhs)) } diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index 12d822ee..a17915e6 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -59,6 +59,12 @@ public struct Blob { } +extension Blob: Equatable {} + +public func ==(lhs: Blob, rhs: Blob) -> Bool { + return lhs.data == rhs.data +} + extension Blob: Binding, Value { public typealias Datatype = Blob From 4de1277d5dc6c431b88f4cb1f110cfc17f119bf0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 15 Nov 2014 18:29:32 -0800 Subject: [PATCH 0072/1046] Document loss of purity At least the dependencies on Foundation are implementation details that can be swapped out at any time. Signed-off-by: Stephen Celis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e4da017d..f1d4590a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift [![Build Status][0.1]][0.2] -A pure-[Swift][1.1], type-safe layer over [SQLite3][1.2]. +A type-safe, [Swift][1.1]-language layer over [SQLite3][1.2]. [SQLite.swift][1.3] provides compile-time confidence in SQL statement syntax _and_ intent. From e5c4cf827306118f0cd927e9d0527989e91ba06c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 15 Nov 2014 18:37:07 -0800 Subject: [PATCH 0073/1046] README SQL FIX Signed-off-by: Stephen Celis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1d4590a..6989add8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ if let insertedID = users.insert(name <- "Alice", email <- "alice@mac.com") { // inserted id: 1 alice = users.filter(id == insertedID) } -// INSERT INTO users (email) VALUES 'alice@mac.com' +// INSERT INTO users (name, email) VALUES ('Alice', 'alice@mac.com') for user in users { println("id: \(user[id]), name: \(user[name]), email: \(user[email])" From 4a5c8bf06c8a076530d67ffdafd87e9fc6ed8ae5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 15 Nov 2014 18:38:08 -0800 Subject: [PATCH 0074/1046] Add simple aggregation example Signed-off-by: Stephen Celis --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6989add8..14fe136c 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,9 @@ alice?.update(email <- replace(email, "mac.com", "me.com"))? alice?.delete()? // DELETE FROM users WHERE (id = 1) + +users.count +// SELECT count(*) FROM users ``` SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C From d8595993757c6a370ada989d5e05a4a40bfc2a65 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 16 Nov 2014 00:41:00 -0800 Subject: [PATCH 0075/1046] Make sure NSData is private (for now) Signed-off-by: Stephen Celis --- SQLite Common/Value.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index a17915e6..a78523e6 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -43,7 +43,7 @@ public protocol Value { public struct Blob { - public let data: NSData + private let data: NSData public var bytes: UnsafePointer<()> { return data.bytes From 270e7e4312973abeb55664316b610aa876dd8701 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 16 Nov 2014 12:21:03 -0800 Subject: [PATCH 0076/1046] Store Query columns individually We need to defer joining the expressions prematurely so that we can expand each one during statement execution. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 85857efd..863d4180 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -48,7 +48,8 @@ public struct Query { } - private var columns: Expressible = Expression<()>("*") + private var columns: [Expressible] = [Expression<()>("*")] + private var distinct: Bool = false internal var tableName: String private var alias: String? private var joins = [Expressible]() @@ -70,7 +71,7 @@ public struct Query { /// :returns: A query with the given SELECT clause applied. public func select(all: Expressible...) -> Query { var query = self - query.columns = SQLite.join(", ", all) + (query.distinct, query.columns) = (false, all) return query } @@ -81,7 +82,7 @@ public struct Query { /// :returns: A query with the given SELECT DISTINCT clause applied. public func select(distinct columns: Expressible...) -> Query { var query = self - query.columns = SQLite.join(" ", [Expression<()>("DISTINCT"), SQLite.join(", ", columns)]) + (query.distinct, query.columns) = (true, columns) return query } @@ -375,7 +376,11 @@ public struct Query { // MARK: - private var selectClause: Expressible { - return SQLite.join(" ", [Expression<()>("SELECT"), columns, Expression<()>("FROM \(self)")]) + var expressions: [Expressible] = [Expression<()>("SELECT")] + if distinct { expressions.append(Expression<()>("DISTINCT")) } + expressions.append(SQLite.join(", ", columns)) + expressions.append(Expression<()>("FROM \(self)")) + return SQLite.join(" ", expressions) } private var joinClause: Expressible? { From 8279881a7fe94bcc61ff60ebb7fdea7448ad621d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 16 Nov 2014 12:28:58 -0800 Subject: [PATCH 0077/1046] Store JOIN metadata We can defer expansion to statement compile time. We need this data intact to properly expand "SELECT *" and "SELECT table.*". Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 863d4180..57120fcd 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -52,7 +52,7 @@ public struct Query { private var distinct: Bool = false internal var tableName: String private var alias: String? - private var joins = [Expressible]() + private var joins: [(type: JoinType, table: Query, condition: Expression)] = [] private var filter: Expression? private var group: Expressible? private var order = [Expressible]() @@ -138,9 +138,8 @@ public struct Query { /// :returns: A query with the given JOIN clause applied. public func join(type: JoinType, _ table: Query, on: Expression) -> Query { var query = self - let condition = table.filter.map { on && $0 } ?? on - let expression = Expression<()>("\(type.rawValue) JOIN \(table) ON \(condition.SQL)", condition.bindings) - query.joins.append(expression) + let join = (type: type, table: table, condition: table.filter.map { on && $0 } ?? on) + query.joins.append(join) return query } @@ -385,7 +384,9 @@ public struct Query { private var joinClause: Expressible? { if joins.count == 0 { return nil } - return SQLite.join(" ", joins) + return SQLite.join(" ", joins.map { type, table, condition in + Expression<()>("\(type.rawValue) JOIN \(table) ON \(condition.SQL)", condition.bindings) + }) } internal var whereClause: Expressible? { From b1f7105b46d53b7f3d0ae83aac09c41a3c9709a0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 16 Nov 2014 18:31:47 -0800 Subject: [PATCH 0078/1046] Don't rely on public Expression.SQL property Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index c190626f..bf1b026f 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -207,19 +207,20 @@ class QueryTests: XCTestCase { } func test_subscript_withExpression_returnsNamespacedExpression() { - XCTAssertEqual("users.admin", users[admin].SQL) - XCTAssertEqual("users.salary", users[salary].SQL) - XCTAssertEqual("users.age", users[age].SQL) - XCTAssertEqual("users.email", users[email].SQL) - XCTAssertEqual("users.*", users[*].SQL) + ExpectExecution(db, "SELECT users.admin FROM users", users.select(users[admin])) + ExpectExecution(db, "SELECT users.salary FROM users", users.select(users[salary])) + ExpectExecution(db, "SELECT users.age FROM users", users.select(users[age])) + ExpectExecution(db, "SELECT users.email FROM users", users.select(users[email])) + ExpectExecution(db, "SELECT users.* FROM users", users.select(users[*])) } func test_subscript_withAliasAndExpression_returnsAliasedExpression() { let managers = users.alias("managers") - XCTAssertEqual("managers.admin", managers[admin].SQL) - XCTAssertEqual("managers.salary", managers[salary].SQL) - XCTAssertEqual("managers.age", managers[age].SQL) - XCTAssertEqual("managers.*", managers[*].SQL) + ExpectExecution(db, "SELECT managers.admin FROM users AS managers", managers.select(managers[admin])) + ExpectExecution(db, "SELECT managers.salary FROM users AS managers", managers.select(managers[salary])) + ExpectExecution(db, "SELECT managers.age FROM users AS managers", managers.select(managers[age])) + ExpectExecution(db, "SELECT managers.email FROM users AS managers", managers.select(managers[email])) + ExpectExecution(db, "SELECT managers.* FROM users AS managers", managers.select(managers[*])) } func test_SQL_compilesProperly() { From 7fa9a57cee70cbd9a06eea9531377b19c9447581 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 17 Nov 2014 08:36:12 -0800 Subject: [PATCH 0079/1046] Namespace Row column names A developer asks a SQLite statement, "What is the column name at this index?" It responds: "id." "But SQLite," she asks, "I'm joining another table which also has a column name 'id.' What is the table name at this index?" It responds: "Undefined symbols for architecture." The world crumbles under the developer's feet. -- SQLite doesn't preserve column metadata unless compiled with SQLITE_ENABLE_COLUMN_METADATA, and it isn't, by default, so we can't rely on it. So for every column of a result set, we only have the column name to work with. We can't trick SQLite by being explicit: if we namespace a column ("SELECT table.column") doesn't preserve the namespace in the result set, and neither does aliasing ("table.column AS unambiguous_column"). I guess we'll need to manage the logic ourselves: 1. If a Query calls select with specific column names, honor the namespacing or lack thereof explicitly. 2. If a Query calls select with a namespaced star, honor the namespacing while expanding the columns. 3. If a Query joins another table and the select is the default, *, namespace all expanded columns. 4. If a Query does not join another table, do not namespace expanded columns. I think the third point may be problematic without proper error handling, but otherwise think the interface is intuitive enough. This commit removes the Statement.values dictionary, as it cannot return all data in a result set with ambiguous column names. Signed-off-by: Stephen Celis --- SQLite Common Tests/QueryTests.swift | 14 +++++++ SQLite Common Tests/StatementTests.swift | 11 ------ SQLite Common/Query.swift | 47 +++++++++++++++++++++--- SQLite Common/Statement.swift | 12 +----- 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index bf1b026f..9eba5b64 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -99,6 +99,20 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} } } + func test_namespacedColumnRowValueAccess() { + let aliceID = users.insert(email <- "alice@example.com")! + let bettyID = users.insert(email <- "betty@example.com", manager_id <- aliceID)! + + let alice = users.first! + XCTAssertEqual(aliceID, alice[id]) + + let managers = db["users"].alias("managers") + let query = users.join(managers, on: managers[id] == users[manager_id]) + + let betty = query.first! + XCTAssertEqual(alice[email], betty[managers[email]]) + } + func test_filter_compilesWhereClause() { let query = users.filter(admin == true) diff --git a/SQLite Common Tests/StatementTests.swift b/SQLite Common Tests/StatementTests.swift index b406ed07..0f9bd12b 100644 --- a/SQLite Common Tests/StatementTests.swift +++ b/SQLite Common Tests/StatementTests.swift @@ -187,17 +187,6 @@ class StatementTests: XCTestCase { XCTAssertEqual("alice@example.com", row[1] as String) } - func test_values_returnsDictionaryOfExpressionsToValues() { - InsertUser(db, "alice") - let stmt = db.prepare("SELECT id, \"email\" FROM users") - stmt.next() - - let values = stmt.values! - - XCTAssertEqual(1, values["id"] as Int) - XCTAssertEqual("alice@example.com", values["email"] as String) - } - } func withBlob(block: Blob -> ()) { diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 57120fcd..a21d4cfd 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -673,20 +673,57 @@ extension Query: SequenceType { public typealias Generator = QueryGenerator - public func generate() -> Generator { return Generator(selectStatement) } + public func generate() -> Generator { return Generator(self) } + + public var columnNames: [String] { + var columnNames = [String]() + for each in columns { + let pair = split(each.expression.SQL) { $0 == "." } + let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) + + func expandGlob(namespace: Bool) -> Query -> () { + return { table in + var names = self.database[table.tableName].selectStatement.columnNames + if namespace { names = names.map { "\(table.alias ?? table.tableName).\($0)" } } + columnNames.extend(names) + } + } + + if column == "*" { + if let tableName = tableName { + expandGlob(true)(database[tableName]) + continue + } + let tables = [self] + joins.map { $0.table } + tables.map(expandGlob(joins.count > 0)) + continue + } + + columnNames.append(each.expression.SQL) + } + return columnNames + } } // MARK: - GeneratorType public struct QueryGenerator: GeneratorType { - private var statement: Statement + private let query: Query + private let statement: Statement - private init(_ statement: Statement) { self.statement = statement } + private init(_ query: Query) { + (self.query, self.statement) = (query, query.selectStatement) + } public func next() -> Row? { - statement.next() - return statement.values.map { Row($0) } + if let row = statement.next() { + var values = [String: Binding?]() + let columnNames = query.columnNames + for idx in 0.. Date: Mon, 17 Nov 2014 09:31:44 -0800 Subject: [PATCH 0080/1046] Restrict min/max to comparable values Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 4 ---- SQLite Common/Expression.swift | 8 ++++---- SQLite Common/Query.swift | 8 ++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 40f8a05f..80d717ca 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -460,8 +460,6 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("max(email)", max(email2)) ExpectExecutionMatches("max(salary)", max(salary)) ExpectExecutionMatches("max(salary)", max(salary2)) - ExpectExecutionMatches("max(admin)", max(admin)) - ExpectExecutionMatches("max(admin)", max(admin2)) } func test_minFunction_withExpression_buildsMinExpression() { @@ -471,8 +469,6 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("min(email)", min(email2)) ExpectExecutionMatches("min(salary)", min(salary)) ExpectExecutionMatches("min(salary)", min(salary2)) - ExpectExecutionMatches("min(admin)", min(admin)) - ExpectExecutionMatches("min(admin)", min(admin2)) } func test_averageFunction_withExpression_buildsAverageExpression() { diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index cf08b193..ba15f9d0 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -536,17 +536,17 @@ public func count(expression: Expression) -> Expression { return wr public func count(star: Star) -> Expression { return count(star(nil, nil)) } -public func max(expression: Expression) -> Expression { +public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func max(expression: Expression) -> Expression { +public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func min(expression: Expression) -> Expression { +public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func min(expression: Expression) -> Expression { +public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index a21d4cfd..b9171431 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -563,10 +563,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The largest value of the given column. - public func max(column: Expression) -> V? { + public func max(column: Expression) -> V? { return calculate(SQLite.max(column)) } - public func max(column: Expression) -> V? { + public func max(column: Expression) -> V? { return calculate(SQLite.max(column)) } @@ -575,10 +575,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The smallest value of the given column. - public func min(column: Expression) -> V? { + public func min(column: Expression) -> V? { return calculate(SQLite.min(column)) } - public func min(column: Expression) -> V? { + public func min(column: Expression) -> V? { return calculate(SQLite.min(column)) } From 837cb4140b3ad0b0ebc5ba163f58e0644b32d3e3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 17 Nov 2014 10:02:34 -0800 Subject: [PATCH 0081/1046] Add DISTINCT aggregate helpers E.g., users.count(distinct: age) // SELECT count(DISTINCT age) FROM users Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 20 ++++++++ SQLite Common Tests/QueryTests.swift | 16 +++++-- SQLite Common/Expression.swift | 16 +++++++ SQLite Common/Query.swift | 56 ++++++++++++++++++++++- 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 80d717ca..c50bc17c 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -447,6 +447,14 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("count(salary)", count(salary2)) ExpectExecutionMatches("count(admin)", count(admin)) ExpectExecutionMatches("count(admin)", count(admin2)) + ExpectExecutionMatches("count(DISTINCT id)", count(distinct: id)) + ExpectExecutionMatches("count(DISTINCT age)", count(distinct: age)) + ExpectExecutionMatches("count(DISTINCT email)", count(distinct: email)) + ExpectExecutionMatches("count(DISTINCT email)", count(distinct: email2)) + ExpectExecutionMatches("count(DISTINCT salary)", count(distinct: salary)) + ExpectExecutionMatches("count(DISTINCT salary)", count(distinct: salary2)) + ExpectExecutionMatches("count(DISTINCT admin)", count(distinct: admin)) + ExpectExecutionMatches("count(DISTINCT admin)", count(distinct: admin2)) } func test_countFunction_withStar_buildsCountExpression() { @@ -476,6 +484,10 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("avg(age)", average(age)) ExpectExecutionMatches("avg(salary)", average(salary)) ExpectExecutionMatches("avg(salary)", average(salary2)) + ExpectExecutionMatches("avg(DISTINCT id)", average(distinct: id)) + ExpectExecutionMatches("avg(DISTINCT age)", average(distinct: age)) + ExpectExecutionMatches("avg(DISTINCT salary)", average(distinct: salary)) + ExpectExecutionMatches("avg(DISTINCT salary)", average(distinct: salary2)) } func test_sumFunction_withExpression_buildsSumExpression() { @@ -483,6 +495,10 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("sum(age)", sum(age)) ExpectExecutionMatches("sum(salary)", sum(salary)) ExpectExecutionMatches("sum(salary)", sum(salary2)) + ExpectExecutionMatches("sum(DISTINCT id)", sum(distinct: id)) + ExpectExecutionMatches("sum(DISTINCT age)", sum(distinct: age)) + ExpectExecutionMatches("sum(DISTINCT salary)", sum(distinct: salary)) + ExpectExecutionMatches("sum(DISTINCT salary)", sum(distinct: salary2)) } func test_totalFunction_withExpression_buildsTotalExpression() { @@ -490,6 +506,10 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("total(age)", total(age)) ExpectExecutionMatches("total(salary)", total(salary)) ExpectExecutionMatches("total(salary)", total(salary2)) + ExpectExecutionMatches("total(DISTINCT id)", total(distinct: id)) + ExpectExecutionMatches("total(DISTINCT age)", total(distinct: age)) + ExpectExecutionMatches("total(DISTINCT salary)", total(distinct: salary)) + ExpectExecutionMatches("total(DISTINCT salary)", total(distinct: salary2)) } func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 9eba5b64..1af02db9 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -347,7 +347,7 @@ class QueryTests: XCTestCase { InsertUser(db, "cindy") XCTAssertEqual(2, users.count(age)) - XCTAssertEqual(1, users.count(Expression("DISTINCT age"))) + XCTAssertEqual(1, users.count(distinct: age)) } func test_max_withInt_returnsMaximumInt() { @@ -370,8 +370,10 @@ class QueryTests: XCTestCase { XCTAssert(users.average(age) == nil) InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 30) - XCTAssertEqual(25.0, users.average(age)!) + InsertUser(db, "betsy", age: 50) + InsertUser(db, "cindy", age: 50) + XCTAssertEqual(40.0, users.average(age)!) + XCTAssertEqual(35.0, users.average(distinct: age)!) } func test_sum_returnsSum() { @@ -379,7 +381,9 @@ class QueryTests: XCTestCase { InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(50, users.sum(age)!) + InsertUser(db, "cindy", age: 30) + XCTAssertEqual(80, users.sum(age)!) + XCTAssertEqual(50, users.sum(distinct: age)!) } func test_total_returnsTotal() { @@ -387,7 +391,9 @@ class QueryTests: XCTestCase { InsertUser(db, "alice", age: 20) InsertUser(db, "betsy", age: 30) - XCTAssertEqual(50.0, users.total(age)) + InsertUser(db, "cindy", age: 30) + XCTAssertEqual(80.0, users.total(age)) + XCTAssertEqual(50.0, users.total(distinct: age)) } func test_row_withBoundColumn_returnsValue() { diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index ba15f9d0..91a633fa 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -534,6 +534,9 @@ public func upper(expression: Expression) -> Expression { retu public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func count(#distinct: Expression) -> Expression { return wrapDistinct("count", distinct) } +public func count(#distinct: Expression) -> Expression { return wrapDistinct("count", distinct) } + public func count(star: Star) -> Expression { return count(star(nil, nil)) } public func max(expression: Expression) -> Expression { @@ -553,12 +556,25 @@ public func min(expression: Expression(expression: Expression) -> Expression { return wrap("avg", expression) } public func average(expression: Expression) -> Expression { return wrap("avg", expression) } +public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } +public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } + public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } +public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } + public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } +public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } + +private func wrapDistinct(function: String, expression: Expression) -> Expression { + return wrap(function, SQLite.join(" ", [Expression<()>("DISTINCT"), expression])) +} + // MARK: - Helper public typealias Star = (Expression?, Expression?) -> Expression<()> diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index b9171431..da17ca03 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -96,7 +96,7 @@ public struct Query { return select(star(nil, nil)) } - /// Sets the SELECT clause on the query. + /// Sets the SELECT DISTINCT * clause on the query. /// /// :param: star A literal *. /// @@ -558,6 +558,24 @@ public struct Query { return calculate(SQLite.count(column))! } + /// Runs count() with DISTINCT against the query. + /// + /// :param: column The column used for the calculation. + /// + /// :returns: The number of rows matching the given column. + public func count(distinct column: Expression) -> Int { + return calculate(SQLite.count(distinct: column))! + } + + /// Runs count(DISTINCT *) against the query. + /// + /// :param: star A literal *. + /// + /// :returns: The number of rows matching the given column. + public func count(distinct star: Star) -> Int { + return calculate(SQLite.count(distinct: star(nil, nil)))! + } + /// Runs max() against the query. /// /// :param: column The column used for the calculation. @@ -594,6 +612,18 @@ public struct Query { return calculate(SQLite.average(column)) } + /// Runs avg() with DISTINCT against the query. + /// + /// :param: column The column used for the calculation. + /// + /// :returns: The average value of the given column. + public func average(distinct column: Expression) -> Double? { + return calculate(SQLite.average(distinct: column)) + } + public func average(distinct column: Expression) -> Double? { + return calculate(SQLite.average(distinct: column)) + } + /// Runs sum() against the query. /// /// :param: column The column used for the calculation. @@ -606,6 +636,18 @@ public struct Query { return calculate(SQLite.sum(column)) } + /// Runs sum() with DISTINCT against the query. + /// + /// :param: column The column used for the calculation. + /// + /// :returns: The sum of the given column’s values. + public func sum(distinct column: Expression) -> V? { + return calculate(SQLite.sum(distinct: column)) + } + public func sum(distinct column: Expression) -> V? { + return calculate(SQLite.sum(distinct: column)) + } + /// Runs total() against the query. /// /// :param: column The column used for the calculation. @@ -618,6 +660,18 @@ public struct Query { return calculate(SQLite.total(column))! } + /// Runs total() with DISTINCT against the query. + /// + /// :param: column The column used for the calculation. + /// + /// :returns: The total of the given column’s values. + public func total(distinct column: Expression) -> Double { + return calculate(SQLite.total(distinct: column))! + } + public func total(distinct column: Expression) -> Double { + return calculate(SQLite.total(distinct: column))! + } + private func calculate(expression: Expression) -> U? { return select(expression).selectStatement.scalar() as? U } From 236ee4f404bde86e45030fb889faab5932a32716 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 17 Nov 2014 10:57:45 -0800 Subject: [PATCH 0082/1046] Better row value access and error messaging 1. If I request an unambiguous column, just return it. 2. If I request an ambiguous column, suggest disambiguated versions. 3. If I request an unknown column, report the known columns. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index da17ca03..b4550117 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -694,13 +694,23 @@ public struct Row { /// /// returns The value for the given column. public func get(column: Expression) -> V { - return V.fromDatatypeValue(values[column.SQL] as V.Datatype) as V + return get(Expression(column))! } public func get(column: Expression) -> V? { - if let datatypeValue = values[column.SQL] as? V.Datatype { - return (V.fromDatatypeValue(datatypeValue) as V) + func bindingToValue(binding: Binding?) -> V? { + if let value = binding as? V.Datatype { return (V.fromDatatypeValue(value) as V) } + return nil } - return nil + + if let binding = values[column.SQL] { return bindingToValue(binding) } + + let similar = filter(values.keys) { $0.hasSuffix(".\(column.SQL)") } + if similar.count == 1 { return bindingToValue(values[similar[0]]!) } + + if similar.count > 1 { + fatalError("ambiguous column \(quote(literal: column.SQL)) (please disambiguate: \(similar))") + } + fatalError("no such column \(quote(literal: column.SQL)) in columns: \(Array(values.keys))") } // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression From 356f54d7d81264780db5549803e1e9c6c1881a70 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 18 Nov 2014 13:46:40 -0800 Subject: [PATCH 0083/1046] Simplify the creation of expressions from values This way, if you extend a custom type, you can instantiate expressions from instances rather easily: Expression(value: NSDate()) Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 6 +- SQLite Common/Expression.swift | 148 +++++++++++----------- SQLite Common/Schema.swift | 8 +- 3 files changed, 83 insertions(+), 79 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index c50bc17c..09f030ed 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -336,15 +336,15 @@ class ExpressionTests: XCTestCase { } func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { - let int1 = Expression(value: nil) - let int2 = Expression(value: nil) + let int1 = Expression(value: nil as Int?) + let int2 = Expression(value: nil as Int?) let int3 = Expression(value: 3) ExpectExecutionMatches("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3)) } func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { - let int = Expression(value: nil) + let int = Expression(value: nil as Int?) ExpectExecutionMatches("ifnull(NULL, 1)", ifnull(int, 1)) ExpectExecutionMatches("ifnull(NULL, 1)", int ?? 1) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 91a633fa..cf064b28 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -34,8 +34,12 @@ public struct Expression { self.init(expression.SQL, expression.bindings) } - public init(value: Binding?) { - self.init("?", [value]) + public init(value: V?) { + self.init(binding: value?.datatypeValue) + } + + private init(binding: Binding?) { + self.init("?", [binding]) } public var asc: Expression<()> { @@ -66,7 +70,7 @@ public protocol Expressible { extension Bool: Expressible { public var expression: Expression<()> { - return Expression(value: self) + return Expression(binding: self) } } @@ -74,7 +78,7 @@ extension Bool: Expressible { extension Double: Expressible { public var expression: Expression<()> { - return Expression(value: self) + return Expression(binding: self) } } @@ -82,7 +86,7 @@ extension Double: Expressible { extension Int: Expressible { public var expression: Expression<()> { - return Expression(value: self) + return Expression(binding: self) } } @@ -90,7 +94,7 @@ extension Int: Expressible { extension String: Expressible { public var expression: Expression<()> { - return Expression(value: self) + return Expression(binding: self) } } @@ -118,91 +122,91 @@ public func +(lhs: Expression, rhs: Expression) -> Expression(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } -public func +(lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } -public func +(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func +(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } +public func +(lhs: Expression, rhs: V) -> Expression { return lhs + Expression(binding: rhs) } +public func +(lhs: Expression, rhs: V) -> Expression { return lhs + Expression(binding: rhs) } +public func +(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } +public func +(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } -public func -(lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } -public func -(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } -public func -(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } +public func -(lhs: Expression, rhs: V) -> Expression { return lhs - Expression(binding: rhs) } +public func -(lhs: Expression, rhs: V) -> Expression { return lhs - Expression(binding: rhs) } +public func -(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) - rhs } +public func -(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) - rhs } public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } -public func *(lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } -public func *(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } -public func *(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } +public func *(lhs: Expression, rhs: V) -> Expression { return lhs * Expression(binding: rhs) } +public func *(lhs: Expression, rhs: V) -> Expression { return lhs * Expression(binding: rhs) } +public func *(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) * rhs } +public func *(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) * rhs } public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } -public func /(lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } -public func /(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } -public func /(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } +public func /(lhs: Expression, rhs: V) -> Expression { return lhs / Expression(binding: rhs) } +public func /(lhs: Expression, rhs: V) -> Expression { return lhs / Expression(binding: rhs) } +public func /(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) / rhs } +public func /(lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) / rhs } public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(value: rhs) } -public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(value: rhs) } -public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } -public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } +public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(binding: rhs) } +public func %(lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(binding: rhs) } +public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) % rhs } +public func %(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) % rhs } public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(value: rhs) } -public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(value: rhs) } -public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } -public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } +public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(binding: rhs) } +public func <<(lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(binding: rhs) } +public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) << rhs } +public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) << rhs } public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) } -public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) } -public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } -public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } +public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(binding: rhs) } +public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(binding: rhs) } +public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) >> rhs } +public func >>(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) >> rhs } public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(value: rhs) } -public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(value: rhs) } -public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } -public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } +public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(binding: rhs) } +public func &(lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(binding: rhs) } +public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) & rhs } +public func &(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) & rhs } public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(value: rhs) } -public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(value: rhs) } -public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } -public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } +public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(binding: rhs) } +public func |(lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(binding: rhs) } +public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) | rhs } +public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) | rhs } public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(value: rhs) } -public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(value: rhs) } -public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } -public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } +public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(binding: rhs) } +public func ^(lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(binding: rhs) } +public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) ^ rhs } +public func ^(lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) ^ rhs } public prefix func ~(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public prefix func ~(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } @@ -239,17 +243,17 @@ public func ==>(lhs: Exp return infix("=", lhs, rhs) } public func ==>(lhs: Expression, rhs: V) -> Expression { - return lhs == Expression(value: rhs.datatypeValue) + return lhs == Expression(value: rhs) } public func ==>(lhs: Expression, rhs: V?) -> Expression { - if let rhs = rhs { return lhs == Expression(value: rhs.datatypeValue) } + if let rhs = rhs { return lhs == Expression(value: rhs) } return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) } public func ==>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) == rhs + return Expression(value: lhs) == rhs } public func ==>(lhs: V?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs.datatypeValue) == rhs } + if let lhs = lhs { return Expression(value: lhs) == rhs } return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) } @@ -266,17 +270,17 @@ public func !=>(lhs: Exp return infix(__FUNCTION__, lhs, rhs) } public func !=>(lhs: Expression, rhs: V) -> Expression { - return lhs != Expression(value: rhs.datatypeValue) + return lhs != Expression(value: rhs) } public func !=>(lhs: Expression, rhs: V?) -> Expression { - if let rhs = rhs { return lhs != Expression(value: rhs.datatypeValue) } + if let rhs = rhs { return lhs != Expression(value: rhs) } return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) } public func !=>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) != rhs + return Expression(value: lhs) != rhs } public func !=>(lhs: V?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs.datatypeValue) != rhs } + if let lhs = lhs { return Expression(value: lhs) != rhs } return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) } @@ -293,16 +297,16 @@ public func >>(lhs: Exp return infix(__FUNCTION__, lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression { - return lhs > Expression(value: rhs.datatypeValue) + return lhs > Expression(value: rhs) } public func >>(lhs: Expression, rhs: V) -> Expression { - return lhs > Expression(value: rhs.datatypeValue) + return lhs > Expression(value: rhs) } public func >>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) > rhs + return Expression(value: lhs) > rhs } public func >>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) > rhs + return Expression(value: lhs) > rhs } public func >=>(lhs: Expression, rhs: Expression) -> Expression { @@ -318,16 +322,16 @@ public func >=>(lhs: Ex return infix(__FUNCTION__, lhs, rhs) } public func >=>(lhs: Expression, rhs: V) -> Expression { - return lhs >= Expression(value: rhs.datatypeValue) + return lhs >= Expression(value: rhs) } public func >=>(lhs: Expression, rhs: V) -> Expression { - return lhs >= Expression(value: rhs.datatypeValue) + return lhs >= Expression(value: rhs) } public func >=>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) >= rhs + return Expression(value: lhs) >= rhs } public func >=>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) >= rhs + return Expression(value: lhs) >= rhs } public func <>(lhs: Expression, rhs: Expression) -> Expression { @@ -343,16 +347,16 @@ public func <>(lhs: Exp return infix(__FUNCTION__, lhs, rhs) } public func <>(lhs: Expression, rhs: V) -> Expression { - return lhs < Expression(value: rhs.datatypeValue) + return lhs < Expression(value: rhs) } public func <>(lhs: Expression, rhs: V) -> Expression { - return lhs < Expression(value: rhs.datatypeValue) + return lhs < Expression(value: rhs) } public func <>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) < rhs + return Expression(value: lhs) < rhs } public func <>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) < rhs + return Expression(value: lhs) < rhs } public func <=>(lhs: Expression, rhs: Expression) -> Expression { @@ -368,16 +372,16 @@ public func <=>(lhs: Ex return infix(__FUNCTION__, lhs, rhs) } public func <=>(lhs: Expression, rhs: V) -> Expression { - return lhs <= Expression(value: rhs.datatypeValue) + return lhs <= Expression(value: rhs) } public func <=>(lhs: Expression, rhs: V) -> Expression { - return lhs <= Expression(value: rhs.datatypeValue) + return lhs <= Expression(value: rhs) } public func <=>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) <= rhs + return Expression(value: lhs) <= rhs } public func <=>(lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs.datatypeValue) <= rhs + return Expression(value: lhs) <= rhs } public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } @@ -606,10 +610,10 @@ public typealias Setter = (Expressible, Expressible) /// :returns: A setter that can be used in a Query's insert and update /// functions. public func set(column: Expression, value: V) -> Setter { - return (column, Expression<()>(value: value.datatypeValue)) + return (column, Expression<()>(value: value)) } public func set(column: Expression, value: V?) -> Setter { - return (column, Expression<()>(value: value?.datatypeValue)) + return (column, Expression<()>(value: value)) } public func set(column: Expression, value: Expression) -> Setter { return (column, value) } public func set(column: Expression, value: Expression) -> Setter { return (column, value) } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 45aeee83..d7370537 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -52,7 +52,7 @@ public extension Database { check: Expression? = nil, defaultValue: V ) -> Statement { - return alter(table, define(column, false, false, false, check, Expression(value: defaultValue.datatypeValue), nil)) + return alter(table, define(column, false, false, false, check, Expression(value: defaultValue), nil)) } public func alter( @@ -61,7 +61,7 @@ public extension Database { check: Expression? = nil, defaultValue: V? = nil ) -> Statement { - let value = defaultValue.map { Expression(value: $0.datatypeValue) } + let value = defaultValue.map { Expression(value: $0) } return alter(table, define(Expression(column), false, true, false, check, value, nil)) } @@ -144,7 +144,7 @@ public final class SchemaBuilder { check: Expression? = nil, defaultValue value: V ) { - column(name, primaryKey, false, unique, check, Expression(value: value.datatypeValue)) + column(name, primaryKey, false, unique, check, Expression(value: value)) } public func column( @@ -164,7 +164,7 @@ public final class SchemaBuilder { check: Expression? = nil, defaultValue value: V? ) { - column(Expression(name), primaryKey, true, unique, check, value.map { Expression(value: $0.datatypeValue) }) + column(Expression(name), primaryKey, true, unique, check, value.map { Expression(value: $0) }) } public func column( From 245ca91d8a4c4cbc68cee3f8066af5667bcf6f41 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 18 Nov 2014 14:54:45 -0800 Subject: [PATCH 0084/1046] Ensure Value extensions have datatypes conforming to Binding Signed-off-by: Stephen Celis --- SQLite Common/Value.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index a78523e6..940e3397 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -31,7 +31,7 @@ public protocol Value { typealias ValueType = Self - typealias Datatype + typealias Datatype: Binding class var declaredDatatype: String { get } From ccd197de6a8132554426b07b76d63cac8ca568ae Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 18 Nov 2014 15:03:07 -0800 Subject: [PATCH 0085/1046] Document Binding's internal purpose (And warn against conforming to it.) Signed-off-by: Stephen Celis --- SQLite Common/Value.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index 940e3397..19039b60 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -23,6 +23,11 @@ import Foundation +/// Binding is a protocol that SQLite.swift uses internally to directly map +/// SQLite types to Swift types. +/// +/// Do not conform custom types to the Binding protocol. See the Value protocol, +/// instead. public protocol Binding {} public protocol Number: Binding {} From a9ac8f5191d5e547ae5474805a372424bb953240 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 18 Nov 2014 17:00:24 -0800 Subject: [PATCH 0086/1046] Don't support Expression primary keys Primary keys aren't generally NULL-able, so let's not support the interface. Signed-off-by: Stephen Celis --- SQLite Common/Schema.swift | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index d7370537..e171fdaa 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -149,22 +149,20 @@ public final class SchemaBuilder { public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: Expression? = nil ) { - column(Expression(name), primaryKey, true, unique, check, value) + column(Expression(name), false, true, unique, check, value) } public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: V? ) { - column(Expression(name), primaryKey, true, unique, check, value.map { Expression(value: $0) }) + column(Expression(name), false, true, unique, check, value.map { Expression(value: $0) }) } public func column( @@ -193,26 +191,24 @@ public final class SchemaBuilder { public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: Expression? = nil, collate: Collation ) { let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name), primaryKey, true, unique, check, value, expressions) + column(Expression(name), false, true, unique, check, value, expressions) } public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: String?, collate: Collation ) { let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name), primaryKey, true, unique, check, Expression(value: value), expressions) + column(Expression(name), false, true, unique, check, Expression(value: value), expressions) } public func column( @@ -243,7 +239,6 @@ public final class SchemaBuilder { public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: Expression? = nil, @@ -251,12 +246,11 @@ public final class SchemaBuilder { ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(Expression(name), primaryKey, true, unique, check, value, expressions) + column(Expression(name), false, true, unique, check, value, expressions) } public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: Int?, @@ -264,7 +258,7 @@ public final class SchemaBuilder { ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(Expression(name), primaryKey, true, unique, check, Expression(value: value), expressions) + column(Expression(name), false, true, unique, check, Expression(value: value), expressions) } public func column( @@ -305,7 +299,6 @@ public final class SchemaBuilder { public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, @@ -313,7 +306,6 @@ public final class SchemaBuilder { ) { return column( name, - primaryKey: primaryKey, unique: unique, check: check, defaultValue: defaultValue, @@ -323,7 +315,6 @@ public final class SchemaBuilder { public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue: Int?, @@ -331,7 +322,6 @@ public final class SchemaBuilder { ) { return column( name, - primaryKey: primaryKey, unique: unique, check: check, defaultValue: defaultValue, From 5faf7a92598b4b9f22dee35e2c62622fd0b52fe0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 18 Nov 2014 17:53:49 -0800 Subject: [PATCH 0087/1046] Schema creation simplification 1. Prefer INTEGER inline primary keys 2. Make primaryKey/defaultValue/references mutually exclusive. Signed-off-by: Stephen Celis --- SQLite Common/Schema.swift | 145 ++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 81 deletions(-) diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index e171fdaa..fd8fb921 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -129,22 +129,20 @@ public final class SchemaBuilder { public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: Expression? = nil ) { - column(name, primaryKey, false, unique, check, value) + column(name, false, false, unique, check, value) } public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, defaultValue value: V ) { - column(name, primaryKey, false, unique, check, Expression(value: value)) + column(name, false, false, unique, check, Expression(value: value)) } public func column( @@ -165,168 +163,153 @@ public final class SchemaBuilder { column(Expression(name), false, true, unique, check, value.map { Expression(value: $0) }) } + // MARK: - INTEGER Columns + + // MARK: PRIMARY KEY + public func column( - name: Expression, + name: Expression, primaryKey: Bool = false, unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression? = nil, - collate: Collation + check: Expression? = nil ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(name, primaryKey, false, unique, check, value, expressions) + column(name, primaryKey, false, unique, check, nil, nil) } + // MARK: DEFAULT + public func column( - name: Expression, - primaryKey: Bool = false, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: String, - collate: Collation + defaultValue value: Expression ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(name, primaryKey, false, unique, check, Expression(value: value), expressions) + column(name, false, false, unique, check, value, nil) } public func column( - name: Expression, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression? = nil, - collate: Collation + defaultValue value: Int ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name), false, true, unique, check, value, expressions) + column(name, false, false, unique, check, Expression(value: value), nil) } public func column( - name: Expression, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: String?, - collate: Collation + defaultValue value: Expression ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name), false, true, unique, check, Expression(value: value), expressions) + column(Expression(name), false, false, unique, check, Expression(value), nil) } public func column( - name: Expression, - primaryKey: Bool = false, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression? = nil, - references: Expression + defaultValue value: Int? ) { - assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(name, primaryKey, false, unique, check, value, expressions) + column(Expression(name), false, false, unique, check, Expression(value: value), nil) } + // MARK: REFERENCES + public func column( name: Expression, - primaryKey: Bool = false, unique: Bool = false, check: Expression? = nil, - defaultValue value: Int, references: Expression ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(name, primaryKey, false, unique, check, Expression(value: value), expressions) + column(name, false, false, unique, check, nil, expressions) } public func column( - name: Expression, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression? = nil, - references: Expression + references: Query ) { - assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(Expression(name), false, true, unique, check, value, expressions) + return column( + name, + unique: unique, + check: check, + references: Expression(references.tableName) + ) } public func column( name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: Int?, references: Expression ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] - column(Expression(name), false, true, unique, check, Expression(value: value), expressions) + column(Expression(name), false, true, unique, check, nil, expressions) } public func column( - name: Expression, - primaryKey: Bool = false, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue: Expression? = nil, references: Query ) { return column( name, - primaryKey: primaryKey, unique: unique, check: check, - defaultValue: defaultValue, references: Expression(references.tableName) ) } + // MARK: TEXT Columns + public func column( - name: Expression, - primaryKey: Bool = false, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue: Int, - references: Query + defaultValue value: Expression? = nil, + collate: Collation ) { - return column( - name, - primaryKey: primaryKey, - unique: unique, - check: check, - defaultValue: defaultValue, - references: Expression(references.tableName) - ) + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(name, false, false, unique, check, value, expressions) } public func column( - name: Expression, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue: Expression? = nil, - references: Query + defaultValue value: String, + collate: Collation ) { - return column( - name, - unique: unique, - check: check, - defaultValue: defaultValue, - references: Expression(references.tableName) - ) + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(name, false, false, unique, check, Expression(value: value), expressions) } public func column( - name: Expression, + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue: Int?, - references: Query + defaultValue value: Expression? = nil, + collate: Collation ) { - return column( - name, - unique: unique, - check: check, - defaultValue: defaultValue, - references: Expression(references.tableName) - ) + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(Expression(name), false, true, unique, check, value, expressions) + } + + public func column( + name: Expression, + unique: Bool = false, + check: Expression? = nil, + defaultValue value: String?, + collate: Collation + ) { + let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + column(Expression(name), false, true, unique, check, Expression(value: value), expressions) } private func column( From 41879204d557d6db7e95c9c9175cd4b114edca83 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Nov 2014 00:55:04 -0800 Subject: [PATCH 0088/1046] Allow ifnull to be called with expressions Signed-off-by: Stephen Celis --- SQLite Common Tests/ExpressionTests.swift | 14 ++++++++++---- SQLite Common/Expression.swift | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 09f030ed..5d9283b4 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -344,10 +344,16 @@ class ExpressionTests: XCTestCase { } func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { - let int = Expression(value: nil as Int?) - - ExpectExecutionMatches("ifnull(NULL, 1)", ifnull(int, 1)) - ExpectExecutionMatches("ifnull(NULL, 1)", int ?? 1) + let int1 = Expression(value: nil as Int?) + let int2 = Expression(value: 2) + let int3 = Expression(value: 3) + + ExpectExecutionMatches("ifnull(NULL, 1)", ifnull(int1, 1)) + ExpectExecutionMatches("ifnull(NULL, 1)", int1 ?? 1) + ExpectExecutionMatches("ifnull(NULL, 2)", ifnull(int1, int2)) + ExpectExecutionMatches("ifnull(NULL, 2)", int1 ?? int2) + ExpectExecutionMatches("ifnull(NULL, 3)", ifnull(int1, int3)) + ExpectExecutionMatches("ifnull(NULL, 3)", int1 ?? int3) } func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index cf064b28..fb6b6ed1 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -453,9 +453,21 @@ public func coalesce(expressions: Expression...) -> Expression { public func ifnull(expression: Expression, defaultValue: V) -> Expression { return wrap(__FUNCTION__, join(", ", [expression, defaultValue])) } +public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, defaultValue])) +} +public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { + return wrap(__FUNCTION__, join(", ", [expression, defaultValue])) +} public func ??(expression: Expression, defaultValue: V) -> Expression { return ifnull(expression, defaultValue) } +public func ??(expression: Expression, defaultValue: Expression) -> Expression { + return ifnull(expression, defaultValue) +} +public func ??(expression: Expression, defaultValue: Expression) -> Expression { + return ifnull(expression, defaultValue) +} public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } From bc9390ee37cc684e8709050ae6dcd3c677e3c177 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Nov 2014 07:49:40 -0800 Subject: [PATCH 0089/1046] Make columnNames private before someone relies on it My rule is: don't publicize an API unless it's proven itself to have legitimate, public use. I broke my rule. Oops. Signed-off-by: Stephen Celis --- SQLite Common/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index b4550117..ceeebb4a 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -739,7 +739,7 @@ extension Query: SequenceType { public func generate() -> Generator { return Generator(self) } - public var columnNames: [String] { + private var columnNames: [String] { var columnNames = [String]() for each in columns { let pair = split(each.expression.SQL) { $0 == "." } From f5ab9b1198dbfb70fc10ca1d869516f38cc1be89 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 19 Nov 2014 09:15:04 -0800 Subject: [PATCH 0090/1046] Add Documentation: First Draft Signed-off-by: Stephen Celis --- Documentation/Index.md | 1231 ++++++++++++++++++++++++++++++++++++++++ README.md | 7 +- 2 files changed, 1236 insertions(+), 2 deletions(-) create mode 100644 Documentation/Index.md diff --git a/Documentation/Index.md b/Documentation/Index.md new file mode 100644 index 00000000..d2ad9fb8 --- /dev/null +++ b/Documentation/Index.md @@ -0,0 +1,1231 @@ +# SQLite.swift Documentation + + - [Installation](#installation) + - [Getting Started](#getting-started) + - [Connecting to a Database](#connecting-to-a-database) + - [Read-Write Databases](#read-write-databases) + - [Read-Only Databases](#read-only-databases) + - [In-Memory Databases](#in-memory-databases) + - [A Note on Thread-Safety](#a-note-on-thread-safety) + - [Building Type-Safe SQL](#building-type-safe-sql) + - [Expressions](#expressions) + - [Compound Expressions](#compound-expressions) + - [Queries](#queries) + - [Creating a Table](#creating-a-table) + - [Create Table Options](#create-table-options) + - [Column Constraints](#column-constraints) + - [Table Constraints](#table-constraints) + - [Inserting Rows](#inserting-rows) + - [Selecting Rows](#selecting-rows) + - [Iterating and Accessing Values](#iterating-and-accessing-values) + - [Plucking Rows](#plucking-rows) + - [Building Complex Queries](#building-complex-queries) + - [Selecting Columns](#selecting-columns) + - [Joining Other Tables](#joining-other-tables) + - [Column Namespacing](#column-namespacing) + - [Table Aliasing](#table-aliasing) + - [Filtering Rows](#filtering-rows) + - [Filter Operators and Functions](#filter-operators-and-functions) + - [Sorting Rows](#sorting-rows) + - [Limiting and Paging Results](#limiting-and-paging-results) + - [Aggregation](#aggregation) + - [Updating Rows](#updating-rows) + - [Deleting Rows](#deleting-rows) + - [Transactions and Savepoints](#transactions-and-savepoints) + - [Altering the Schema](#altering-the-schema) + - [Renaming Tables](#renaming-tables) + - [Adding Columns](#adding-columns) + - [Added Column Constraints](#added-column-constraints) + - [Indexes](#indexes) + - [Creating Indexes](#creating-indexes) + - [Dropping Indexes](#dropping-indexes) + - [Dropping Tables](#dropping-tables) + - [Migrations and Schema Versioning](#migrations-and-schema-versioning) + - [Custom Types](#custom-types) + - [Date-Time Values](#date-time-values) + - [Binary Data](#binary-data) + - [Custom Type Caveats](#custom-type-caveats) + - [Other Operators](#other-operators) + - [Core SQLite Functions](#core-sqlite-functions) + - [Aggregate SQLite Functions](#aggregate-sqlite-functions) + - [Executing Arbitrary SQL](#executing-arbitrary-sql) + - [Logging](#logging) + + +[↩]: #sqliteswift-documentation + + +## Installation + +> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode 6.1](https://developer.apple.com/xcode/downloads/)) or greater. + +To install SQLite.swift as an Xcode sub-project: + + 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.) + + ![Installation](Resources/installation@2x.png) + + 2. In your target’s **Build Phases**, add **SQLite iOS** (or **SQLite Mac**) to the **Target Dependencies** build phase. + + 3. Add the appropriate **SQLite.framework** product to the **Link Binary With Libraries** build phase. + + 4. Add the same **SQLite.framework** to a **Copy Files** build phase with a **Frameworks** destination. (Add a new build phase if need be.) + +You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift. + + +## Getting Started + +To use SQLite.swift classes or structures in your target’s source file, first import the `SQLite` module. + +``` swift +import SQLite +``` + + +### Connecting to a Database + +Database connections are established using the `Database` class. A database is initialized with a path. SQLite will attempt to create the database file if it does not already exist. + +``` swift +let db = Database("path/to/db.sqlite3") +``` + + +#### Read-Write Databases + +On iOS, you can create a writable database in your app’s **Documents** directory. + +``` swift +let path = NSSearchPathForDirectoriesInDomains( + .DocumentDirectory, .UserDomainMask, true +).first as String + +let db = Database("\(path)/db.sqlite3") +``` + +On OS X, you can use your app’s **Application Support** directory: + +``` swift +var path = NSSearchPathForDirectoriesInDomains( + .ApplicationSupportDirectory, .UserDomainMask, true +).first as String + NSBundle.mainBundle().bundleIdentifier! + +// create parent directory iff it doesn't exist +NSFileManager.defaultManager().createDirectoryAtPath( + path, withIntermediateDirectories: true, attributes: nil, error: nil +) + +let db = Database("\(path)/db.sqlite3") +``` + + +#### Read-Only Databases + +If you bundle a database with your app, you can establish a _read-only_ connection to it. + +``` swift +let path = NSBundle.mainBundle().pathForResource("db", ofType: "sqlite3")! + +let db = Database(path, readonly: true) +``` + +> _Note_: Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). + + +#### In-Memory Databases + +If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). + +``` swift +let db = Database() // equivalent to Database(":memory:") +``` + +To create a temporary, disk-backed database, pass an empty file name. + +``` swift +let db = Database("") +``` + +In-memory databases are automatically deleted when the database connection is closed. + + +### A Note on Thread-Safety + +> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints, however, do not block other threads from executing statements within the transaction. + + +## Building Type-Safe SQL + +SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). + +| Swift Type | SQLite Type | +| --------------- | ----------- | +| `Int` | `INTEGER` | +| `Double` | `REAL` | +| `String` | `TEXT` | +| `Bool` | `BOOLEAN` | +| `nil` | `NULL` | +| `SQLite.Blob`* | `BLOB` | + +> *SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes. +> +> See [Custom Types](#custom-types) for more information about extending other classes and structures to work with SQLite.swift. + +These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`Query`](#queries)), can create and execute SQL statements. + + +### Expressions + +Expressions are generic structures associated with a type ([built-in](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and (optionally) values to bind to that SQL. Typically, you will only explicitly create expressions to describe your columns, and typically only once per column. + +``` swift +let id = Expression("id") +let email = Expression("email") +let balance = Expression("balance") +let verified = Expression("verified") +``` + +Use optional generics for expressions that can evaluate to `NULL`. + +``` swift +let name = Expression("name") +``` + + +### Compound Expressions + +Expressions can be combined with other expressions and types using [filters](#filter-operators-and-functions), and [other operators](#other-operators) and [functions](#core-sqlite-functions). These building blocks can create complex SQLite statements. + + +### Queries + +Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a `Query` by subscripting a database connection with a table name. + +``` swift +let users = db["users"] +``` + +Assuming [the table exists](#creating-a-table), we can immediately [insert](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and [delete](#deleting-rows) rows. + + +## Creating a Table + +We can run [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create(table:)` function on a database connection. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. + +``` swift +db.create(table: users) { t in // CREATE TABLE users ( + t.column(id, primaryKey: true) // id INTEGER PRIMARY KEY NOT NULL, + t.column(email, unique: true) // email TEXT UNIQUE NOT NULL, + t.column(name) // name TEXT +} // ) +``` + +> _Note:_ `Expression` structures (in this case, the `id` and `email` columns), generate `NOT NULL` constraints automatically, while `Expression` structures (`name`) do not. + + +### Create Table Options + +The `create(table:)` function has several default parameters we can override. + + - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to create a temporary table that will automatically drop when the database connection closes). Default: `false`. + + ``` swift + db.create(table: users, temporary: true) { t in /* ... */ } + // CREATE TEMPORARY TABLE users -- ... + ``` + + - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. + + ``` swift + db.create(table: users, ifNotExists: true) { t in /* ... */ } + // CREATE TABLE users IF NOT EXISTS -- ... + ``` + +### Column Constraints + +The `column` function is used for a single column definition. It takes an [expression](#expressions) describing the column name and type, and accepts several parameters that map to various column constraints and clauses. + + - `primaryKey` adds an `INTEGER PRIMARY KEY` constraint to a single column. (See the `primaryKey` function under [Table Constraints](#table-constraints) for non-integer primary keys). + + ``` swift + t.column(id, primaryKey: true) + // id INTEGER PRIMARY KEY NOT NULL + ``` + + > _Note:_ The `primaryKey` parameter cannot be used alongside `defaultValue` or `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > + > Primary keys cannot be optional (`Expression`). + + - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns). + + ``` swift + t.column(email, unique: true) + // email TEXT UNIQUE NOT NULL + ``` + + - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).) + + ``` swift + t.column(email, check: like("%@%", email)) + // email TEXT NOT NULL CHECK (email LIKE '%@%') + ``` + + - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). + + ``` swift + t.column(name, defaultValue: "Anonymous") + // name TEXT DEFAULT 'Anonymous' + ``` + + > _Note:_ The `defaultValue` parameter cannot be used alongside `primaryKey` and `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + + - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. + + ``` swift + t.column(email, collate: .NoCase) + // email TEXT NOT NULL COLLATE NOCASE + ``` + + - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + + ``` swift + t.column(user_id, references: users[id]) + // user_id INTEGER REFERENCES users(id) + + t.column(user_id, references: users) + // user_id INTEGER REFERENCES users + // -- assumes "users" has a PRIMARY KEY + ``` + + > _Note:_ The `references` parameter cannot be used alongside `primaryKey` and `defaultValue`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + + +### Table Constraints + +Additional constraints may be provided outside the scope of a single column using the following functions. + + - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports all SQLite types, [ascending and descending orders](#sorting-rows), and composite (multiple column) keys. + + ``` swift + t.primaryKey(email.asc, name) + // PRIMARY KEY(email ASC, name) + ``` + + - `unique` adds a `UNIQUE` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports composite (multiple column) constraints. + + ``` swift + t.unique(local, domain) + // UNIQUE(local, domain) + ``` + + - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` parameter under [Column Constraints](#column-constraints).) + + ``` swift + t.check(balance >= 0) + // CHECK (balance >= 0.0) + ``` + + - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, and both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions). + + ``` swift + t.foreignKey(user_id, on: users[id], delete: .SetNull) + // FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL + ``` + + > _Note:_ Composite foreign keys are not supported at this time. If you add support, please [submit a pull request](https://github.com/stephencelis/SQLite.swift/fork). + + + + +## Inserting Rows + +We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. + +``` swift +users.insert(email <- "alice@mac.com", name <- "Alice")? +// INSERT INTO users (email, name) VALUES ('alice@mac.com', 'Alice') +``` + +The `insert` function can return several different types that are useful in different contexts. + + - An `Int?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity. + + ``` swift + if let insertID = users.insert(email <- "alice@mac.com") { + println("inserted id: \(insertID)") + } + ``` + + We can use the optional nature of the value to disambiguate with a simple `?` or `!`. + + ``` swift + // ignore failure + users.insert(email <- "alice@mac.com")? + + // assertion on failure + users.insert(email <- "alice@mac.com")! + ``` + + - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. + + ``` swift + db.transaction( + users.insert(email <- "alice@mac.com"), + users.insert(email <- "betty@mac.com") + ) + // BEGIN DEFERRED TRANSACTION; + // INSERT INTO users (email) VALUES ('alice@mac.com'); + // INSERT INTO users (email) VALUES ('betty@mac.com'); + // COMMIT TRANSACTION; + ``` + + - A tuple of the above [`ROWID`][ROWID] and statement: `(ID: Int?, Statement)`, for flexibility. + + ``` swift + let (ID, statement) = users.insert(email <- "alice@mac.com") + if let ID = ID { + println("inserted id: \(ID)") + } else if statement.failed { + println("insertion failed: \(statement.reason)") + } + ``` + +The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns. + +> _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values. +> +> ``` swift +> timestamps.insert()! +> // INSERT INTO timestamps DEFAULT VALUES +> ``` + + +## Selecting Rows + +[`Query` structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. + + +### Iterating and Accessing Values + +[Queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. + +``` swift +for user in users { + println("id: \(user[id]), email: \(user[email]), name: \(user[name])") + // id: 1, email: alice@mac.com, name: Optional("Alice") +} +// SELECT * FROM users +``` + +`Expression` column values are _automatically unwrapped_ (we’ve made a promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. + + +### Plucking Rows + +We can pluck the first row by calling the `first` computed property on [`Query`](#queries). + +``` swift +if let user = users.first { /* ... */ } // Row +// SELECT * FROM users LIMIT 1 +``` + +To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea). + +``` swift +let all = Array(users) +// SELECT * FROM users +``` + + +### Building Complex Queries + +[`Query`](#queries) structures have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. + +``` swift +let query = users.select(email) // SELECT email FROM users + .filter(name != nil) // WHERE name IS NOT NULL + .order(email.desc, name) // ORDER BY email DESC, name + .limit(5, offset: 1) // LIMIT 5 OFFSET 1 +``` + + +#### Selecting Columns + +By default, [`Query`](#queries) objects select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. + +``` swift +let query = users.select(id, email) +// SELECT id, email FROM users +``` + + + + +#### Joining Other Tables + +We can join tables using a [query’s](#queries) `join` function. + +``` swift +users.join(posts, on: user_id == users[id]) +// SELECT * FROM users INNER JOIN posts ON (user_id = users.id) +``` + +The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.Inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing). + + +##### Column Namespacing + +When joining tables, column names can become ambiguous. _E.g._, both tables may have an `id` column. + +``` swift +let query = users.join(posts, on: user_id == id) +// assertion failure: ambiguous column 'id' +``` + +We can disambiguate by namespacing `id`. + +``` swift +let query = users.join(posts, on: user_id == users[id]) +// SELECT * FROM users INNER JOIN posts ON (user_id = users.id) +``` + +Namespacing is achieved by subscripting a [query](#queries) with a [column expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`). + +> _Note:_ We can namespace all of a table’s columns using `*`. +> +> ``` swift +> let query = users.select(users[*]) +> // SELECT users.* FROM users +> ``` + + +##### Table Aliasing + +Occasionally, we need to join a table to itself, in which case we must alias the table with another name. We can achieve this using the [query’s](#queries) `alias` function. + +``` swift +let managers = users.alias("managers") + +let query = users.join(managers, on: managers[id] == users[manager_id]) +// SELECT * FROM users +// INNER JOIN users AS managers ON (managers.id = users.manager_id) +``` + +If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. + +``` swift +let user = query.first! +user[id] // fatal error: ambiguous column 'id' + // (please disambiguate: [users.id, managers.id]) + +user[users[id]] // returns users.id +user[managers[id]] // returns managers.id +``` + + +#### Filtering Rows + +SQLite.swift filters rows using a [query’s](#queries) `filter` function with a boolean [expression](#expressions) (`Expression`). + +``` swift +users.filter(id == 1) +// SELECT * FROM users WHERE (id = 1) +``` + +You can build your own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). + +> _Note:_ SQLite.swift defines `filter` instead of `where` because `where` is [a reserved keyword](https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_906). + + +##### Filter Operators and Functions + +SQLite.swift defines a number of operators for building filtering predicates. Operators and functions work together in a type-safe manner, so attempting to equate or compare different types will prevent compilation. + + +###### Infix Filter Operators + +| Swift | Types | SQLite | +| ----- | -------------------------------- | --------- | +| `==` | `Equatable -> Bool` | `=` | +| `!=` | `Equatable -> Bool` | `!=` | +| `>` | `Comparable -> Bool` | `>` | +| `>=` | `Comparable -> Bool` | `>=` | +| `<` | `Comparable -> Bool` | `<` | +| `<=` | `Comparable -> Bool` | `<=` | +| `~=` | `(Interval, Comparable) -> Bool` | `BETWEEN` | +| `&&` | `Bool -> Bool` | `AND` | +| `||` | `Bool -> Bool` | `OR` | + + +###### Prefix Filter Operators + +| Swift | Types | SQLite | +| ----- | ------------------ | ------ | +| `!` | `Bool -> Bool` | `NOT` | + + +###### Filtering Functions + +| Swift | Types | SQLite | +| ------- | ---------------- | ------- | +| `like` | `String -> Bool` | `LIKE` | +| `glob` | `String -> Bool` | `GLOB` | +| `match` | `String -> Bool` | `MATCH` | + + + + + +#### Sorting Rows + +We can pre-sort returned rows using the [query’s](#queries) `order` function. + +_E.g._, to return users sorted by `email`, then `name`, in ascending order: + +``` swift +users.order(email, name) +// SELECT * FROM users ORDER BY email, name +``` + +The `order` function takes a list of [column expressions](#expressions). + +`Expression` objects have two computed properties to assist sorting: `asc` and `desc`. These properties append the expression with `ASC` and `DESC` to mark ascending and descending order respectively. + +``` swift +users.order(email.desc, name.asc) +// SELECT * FROM users ORDER BY email DESC, name ASC +``` + + +#### Limiting and Paging Results + +We can limit and skip returned rows using a [query’s](#queries) `limit` function (and its optional `offset` parameter). + +``` swift +users.limit(5) +// SELECT * FROM users LIMIT 5 + +users.limit(5, offset: 5) +// SELECT * FROM users LIMIT 5 OFFSET 5 +``` + + +#### Aggregation + +[`Query`](#queries) structures come with a number of functions that quickly return aggregate values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. + +``` swift +users.count +// SELECT count(*) FROM users +``` + +Filtered queries will appropriately filter aggregate values. + +``` swift +users.filter(name != nil).count +// SELECT count(*) FROM users WHERE name IS NOT NULL +``` + + - `count` as a computed property (see examples above) returns the total number of rows matching the query. + + `count` as a function takes a [column name](#expressions) and returns the total number of rows where that column is not `NULL`. + + ``` swift + users.count(name) // -> Int + // SELECT count(name) FROM users + ``` + + - `max` takes a comparable column expression and returns the largest value if any exists. + + ``` swift + users.max(id) // -> Int? + // SELECT max(id) FROM users + ``` + + - `min` takes a comparable column expression and returns the smallest value if any exists. + + ``` swift + users.min(id) // -> Int? + // SELECT min(id) FROM users + ``` + + - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists. + + ``` swift + users.average(balance) // -> Double? + // SELECT avg(balance) FROM users + ``` + + - `sum` takes a numeric column expression and returns the sum total of all rows if any exist. + + ``` swift + users.sum(balance) // -> Double? + // SELECT sum(balance) FROM users + ``` + + - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query. + + ``` swift + users.total(balance) // -> Double + // SELECT total(balance) FROM users + ``` + +> _Note:_ Most of the above aggregate functions (except `max` and `min`) can be called with a `distinct` parameter to aggregate `DISTINCT` values only. +> +> ``` swift +> users.count(distinct: name) +> // SELECT count(DISTINCT name) FROM users +> ``` + + +## Updating Rows + +We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. + +When an unscoped query calls `update`, it will update _every_ row in the table. + +``` swift +users.update(email <- "alice@me.com")? +// UPDATE users SET email = 'alice@me.com' +``` + +Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#filtering-rows). + +``` swift +let alice = users.filter(id == 1) +alice.update(email <- "alice@me.com")? +// UPDATE users SET email = 'alice@me.com' WHERE (id = 1) +``` + +[Like `insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts. + + - An `Int?` representing the number of updated rows (or `nil` on failure), for simplicity. + + ``` swift + if alice.update(email <- "alice@me.com") > 0 { + println("updated Alice") + } + ``` + + We can use the optional nature of the value to disambiguate with a simple `?` or `!`. + + ``` swift + // ignore failure + alice.update(email <- "alice@me.com")? + + // assertion on failure + alice.update(email <- "alice@me.com")! + ``` + + - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. + + - A tuple of the above number of updated rows and statement: `(changes: Int?, Statement)`, for flexibility. + + +## Deleting Rows + +We can delete rows from a table by calling a [query’s](#queries) `delete` function. + +When an unscoped query calls `delete`, it will delete _every_ row in the table. + +``` swift +users.delete()? +// DELETE FROM users +``` + +Be sure to scope `DELETE` statements beforehand using [the `filter` function](#filtering-rows). + +``` swift +let alice = users.filter(id == 1) +alice.delete()? +// DELETE FROM users WHERE (id = 1) +``` + +Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can return several different types that are useful in different contexts. + + - An `Int?` representing the number of deleted rows (or `nil` on failure), for simplicity. + + ``` swift + if alice.delete() > 0 { + println("deleted Alice") + } + ``` + + We can use the optional nature of the value to disambiguate with a simple `?` or `!`. + + ``` swift + // ignore failure + alice.delete()? + + // assertion on failure + alice.delete()! + ``` + + - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. + + - A tuple of the above number of deleted rows and statement: `(changes: Int?, Statement)`, for flexibility. + + +## Transactions and Savepoints + +Using the `transaction` and `savepoint` functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back. + +``` swift +db.transaction( + users.insert(email <- "betty@icloud.com"), + users.insert(email <- "cathy@icloud.com", manager_id <- db.lastID) +) +``` + +> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastID` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. + + +## Altering the Schema + +SQLite.swift comes with several functions (in addition to `create(table:)`) for altering a database schema in a type-safe manner. + + +### Renaming Tables + +We can rename a table by calling the `rename(table:to:)` function on a database connection. + +``` swift +db.rename(users, to: "users_old") +// ALTER TABLE users RENAME TO users_old +``` + + +### Adding Columns + +We can add columns to a table by calling `alter` function on a database connection. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. + +``` swift +db.alter(table: users, add: suffix) +// ALTER TABLE users ADD COLUMN suffix TEXT +``` + + +#### Added Column Constraints + +The `alter` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). + + - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). (See also the `check` function under [Table Constraints](#table-constraints).) + + ``` swift + let check = contains(["JR", "SR"], suffix) + db.alter(table: users, add: suffix, check: check) + // ALTER TABLE users + // ADD COLUMN suffix TEXT CHECK (suffix IN ('JR', 'SR')) + ``` + + - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). + + ``` swift + db.alter(table: users, add: suffix, defaultValue: "SR") + // ALTER TABLE users ADD COLUMN suffix TEXT DEFAULT 'SR' + ``` + + > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), default values may not be expression structures (including `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). + + + + - `references` adds a `REFERENCES` clause to `Int` (and `Int?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + + ``` swift + db.alter(table: posts, add: user_id, references: users[id]) + // ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users(id) + + db.alter(table: posts, add: user_id, references: users) + // ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users + // -- assumes "users" has a PRIMARY KEY + ``` + + +### Indexes + + +#### Creating Indexes + +We can run [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `create(index:)` function on a database connection. + +``` swift +db.create(index: users, on: email) +// CREATE INDEX index_users_on_email ON users (email) +``` + +The index name is generated automatically based on the table and column names. + +The `create(index:)` function has a couple default parameters we can override. + + - `unique` adds a `UNIQUE` constraint to the index. Default: `false`. + + ``` swift + db.create(index: users, on: email, unique: true) + // CREATE UNIQUE INDEX index_users_on_email ON users (email) + ``` + + - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. + + ``` swift + db.create(index: users, on: email, ifNotExists: true) + // CREATE INDEX IF NOT EXISTS index_users_on_email ON users (email) + ``` + + +#### Dropping Indexes + +We can run [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `drop(index:)` function on a database connection. + +``` swift +db.drop(index: users, on: email) +// DROP INDEX index_users_on_email +``` + +The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. + +``` swift +db.drop(index: users, on: email, ifExists: true) +// DROP INDEX IF EXISTS index_users_on_email +``` + + +### Dropping Tables + +We can run [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `drop(table:)` function on a database connection. + +``` swift +db.drop(table: users) +// DROP TABLE users +``` + +The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. + +``` swift +db.drop(table: users, ifExists: true) +// DROP TABLE IF EXISTS users +``` + + +### Migrations and Schema Versioning + +SQLite.swift provides a convenience property on `Database` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_schema_version). This is a great way to manage your schema’s version over migrations. + +``` swift +if db.userVersion == 0 { + // handle first migration + db.userVersion = 1 +} +if db.userVersion == 1 { + // handle second migration + db.userVersion = 2 +} +``` + + +## Custom Types + +SQLite.swift supports serializing and deserializing any custom type as long as it conforms to the `Value` protocol. + +> ``` swift +> protocol Value { +> typealias Datatype: Binding +> class var declaredDatatype: String { get } +> class func fromDatatypeValue(datatypeValue: Datatype) -> Self +> var datatypeValue: Datatype { get } +> } +> ``` + +`Datatype` refers to a one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL](#building-type-safe-sql) for a list of types). + +> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to directly map SQLite types to Swift types. **Do _not_** conform custom types to the `Binding` protocol. + +Once extended, the type can be used [_almost_](#custom-type-caveats) wherever typed expressions can be. + + +### Date-Time Values + +In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can transparently bridge `NSDate` objects through Swift’s `String` or `Int` types. + +To serialize `NSDate` objects as `TEXT` values (in ISO 8601), we’ll use `String`. + +``` swift +extension NSDate: Value { + class var declaredDatatype: String { + return String.declaredDatatype + } + class func fromDatatypeValue(stringValue: String) -> NSDate { + return SQLDateFormatter.dateFromString(stringValue)! + } + var datatypeValue: String { + return SQLDateFormatter.stringFromDate(self) + } +} + +let SQLDateFormatter: NSDateFormatter = { + let formatter = NSDateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + formatter.timeZone = NSTimeZone(abbreviation: "UTC") + return formatter +}() +``` + +We can also treat them as `INTEGER` values using `Int`. + +``` swift +extension NSDate: Value { + class var declaredDatatype: String { + return Int.declaredDatatype + } + class func fromDatatypeValue(intValue: Int) -> Self { + return self(timeIntervalSince1970: NSTimeInterval(intValue)) + } + var datatypeValue: Int { + return Int(timeIntervalSince1970) + } +} +``` + +> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` helpers return `TEXT` values. Because of this (and the fact that Unix time is far less human-readable when we’re faced with the raw data), we recommend using the `TEXT` extension. + +Once defined, we can use these types directly in SQLite statements. + +``` swift +let published_at = Expression("published_at") + +let published = posts.filter(published_at <= Date()) +// extension where Datatype == String: +// SELECT * FROM posts WHERE published_at <= '2014-11-18 12:45:30' +// extension where Datatype == Int: +// SELECT * FROM posts WHERE published_at <= 1416314730 +``` + + +### Binary Data + +Any object that can be encoded and decoded can be stored as a blob of data in SQL. + +We can create an `NSData` bridge rather trivially. + +``` swift +extension NSData: Value { + class var declaredDatatype: String { + return Blob.declaredDatatype + } + class func fromDatatypeValue(blobValue: Blob) -> Self { + return self(bytes: blobValue.bytes, blob: blobValue.length) + } + var datatypeValue: Blob { + return Blob(bytes: bytes, length: length) + } +} +``` + +We can bridge any type that can be initialized from and encoded to `NSData`. + +``` swift +// assumes NSData conformance, above +extension UIImage: Value { + class var declaredDatatype: String { + return NSData.declaredDatatype + } + class func fromDatatypeValue(blobValue: Blob) -> Self { + return self(data: NSData.fromDatatypeValue(blobValue)) + } + var datatypeValue: Blob { + return UIImagePNGRepresentation(self).datatypeValue + } +} +``` + +> _Note:_ See the [Archives and Serializations Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i) for more information on encoding and decoding custom types. + + +### Custom Type Caveats + +Swift does _not_ currently support generic subscripting, which means we cannot, by default, subscript Expressions with custom types to: + + 1. **Namespace expressions**. Use the `namespace` function, instead: + + ``` swift + let avatar = Expression("avatar") + users[avatar] // fails to compile + users.namespace(avatar) // Expression("users.avatar") + ``` + + 2. **Access column data**. Use the `get` function, instead: + + ``` swift + let user = users.first! + user[avatar] // fails to compile + user.get(avatar) // UIImage? + ``` + +We can, of course, write extensions, but they’re rather wordy. + +``` swift +extension Query { + subscript(column: Expression) -> Expression { + return namespace(column) + } + subscript(column: Expression) -> Expression { + return namespace(column) + } +} + +extension Row { + subscript(column: Expression) -> UIImage { + return get(column) + } + subscript(column: Expression) -> UIImage? { + return get(column) + } +} +``` + + +## Other Operators + + +### Other Infix Operators + +In addition to [filter operators](#filtering-infix-operators), SQLite.swift defines a number of operators that can modify expression values with arithmetic, bitwise operations, and concatenation. + +| Swift | Types | SQLite | +| ----- | -------------------------------- | -------- | +| `+` | `Number -> Number` | `+` | +| `-` | `Number -> Number` | `-` | +| `*` | `Number -> Number` | `*` | +| `/` | `Number -> Number` | `/` | +| `%` | `Int -> Int` | `%` | +| `<<` | `Int -> Int` | `<<` | +| `>>` | `Int -> Int` | `>>` | +| `&` | `Int -> Int` | `&` | +| `|` | `Int -> Int` | `|` | +| `+` | `String -> String` | `||` | + +> _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. + + +### Other Prefix Operators + +| Swift | Types | SQLite | +| ----- | ------------------ | ------ | +| `~` | `Int -> Int` | `~` | +| `-` | `Number -> Number` | `-` | + + +## Core SQLite Functions + +Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) have been surfaced in and type-audited for SQLite.swift. + +> _Note:_ SQLite.swift aliases the `??` operator to the `ifnull` function. +> +> ``` swift +> name ?? email // ifnull(name, email) +> ``` + + +## Aggregate SQLite Functions + +Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. + + +## Executing Arbitrary SQL + +Though we recommend you stick with SQLite.swift’s type-safe system whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. + + - `execute` runs an arbitrary number of SQL statements as a convenience. + + ``` swift + db.execute( + "BEGIN TRANSACTION;" + + "CREATE TABLE users (" + + "id INTEGER PRIMARY KEY NOT NULL," + + "email TEXT UNIQUE NOT NULL," + + "name TEXT" + + ");" + + "CREATE TABLE posts (" + + "id INTEGER PRIMARY KEY NOT NULL," + + "title TEXT NOT NULL," + + "body TEXT NOT NULL," + + "published_at DATETIME" + + ");" + + "PRAGMA user_version = 1;" + + "COMMIT TRANSACTION;" + ) + ``` + + - `prepare` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), and returns the statement for deferred execution. + + ``` swift + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") + ``` + + Once prepared, statements may be executed using `run`, binding any unbound parameters. + + ``` swift + stmt.run("alice@mac.com") + db.lastChanges // -> {Some 1} + ``` + + Statements with results may be iterated over. + + ``` swift + let stmt = db.prepare("SELECT id, email FROM users") + for row in stmt { + println("id: \(row[0]), email: \(row[1])") + // id: Optional(1), email: Optional("alice@mac.com") + } + ``` + + - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. + + ``` swift + db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") + ``` + + - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. + + ``` swift + db.scalar("SELECT count(*) FROM users") as Int + ``` + + Statements also have a `scalar` function, which can optionally re-bind values at execution. + + ``` swift + let stmt = db.prepare("SELECT count (*) FROM users") + stmt.scalar() as Int + ``` + + +## Logging + +We can log SQL using the database’s `trace` function. + +``` swift +#if DEBUG + db.trace(println) +#endif +``` + + +[ROWID]: https://sqlite.org/lang_createtable.html#rowid \ No newline at end of file diff --git a/README.md b/README.md index 14fe136c..ea4ca1c9 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,11 @@ syntax _and_ intent. - A lightweight, uncomplicated query and parameter binding interface - Transactions with implicit commit/rollback - Developer-friendly error handling and debugging - - Well-documented + - [Well-documented][See Documentation] - Extensively tested +[See Documentation]: Documentation/Index.md#sqliteswift-documentation + ## Usage @@ -94,7 +96,8 @@ for row in db.prepare("SELECT id, email FROM users") { db.scalar("SELECT count(*) FROM users") // {Some 2} ``` -Explore more, interactively, from the Xcode project’s playground. +[Read the documentation][See Documentation] or explore more, +interactively, from the Xcode project’s playground. ![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) From a167a06bf7d8a9f2bf23e3686361b3383ffe1894 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 20 Nov 2014 08:04:55 -0800 Subject: [PATCH 0091/1046] Avoid segfault on Release builds Signed-off-by: Stephen Celis --- SQLite Common/Expression.swift | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index fb6b6ed1..fcbbde11 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -113,10 +113,10 @@ public func +(lhs: Expression, rhs: Expression) -> Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(value: rhs) } -public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(value: rhs) } -public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func +(lhs: String, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } +public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) } +public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) } +public func +(lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } +public func +(lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } @@ -397,24 +397,24 @@ public func ~=, // MARK: Operators public func like(string: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(value: string)) + return infix("LIKE", expression, Expression(binding: string)) } public func like(string: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(value: string)) + return infix("LIKE", expression, Expression(binding: string)) } public func glob(string: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(value: string)) + return infix("GLOB", expression, Expression(binding: string)) } public func glob(string: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(value: string)) + return infix("GLOB", expression, Expression(binding: string)) } public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(value: string)) + return infix("MATCH", expression, Expression(binding: string)) } public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(value: string)) + return infix("MATCH", expression, Expression(binding: string)) } // MARK: Compound @@ -423,19 +423,19 @@ public func &&(lhs: Expression, rhs: Expression) -> Expression public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } -public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } -public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } -public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } +public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs) } +public func &&(lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs) } +public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) && rhs } +public func &&(lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) && rhs } public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } -public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } -public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } -public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } +public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs) } +public func ||(lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs) } +public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) || rhs } +public func ||(lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) || rhs } public prefix func !(rhs: Expression) -> Expression { return wrap("NOT ", rhs) } public prefix func !(rhs: Expression) -> Expression { return wrap("NOT ", rhs) } From 15183934123e2bf223a6381a9995c87b1f5fc76d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 20 Nov 2014 08:59:14 -0800 Subject: [PATCH 0092/1046] Document setters Signed-off-by: Stephen Celis --- Documentation/Index.md | 64 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index d2ad9fb8..bd8e5f05 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -16,6 +16,7 @@ - [Column Constraints](#column-constraints) - [Table Constraints](#table-constraints) - [Inserting Rows](#inserting-rows) + - [Setters](#setters) - [Selecting Rows](#selecting-rows) - [Iterating and Accessing Values](#iterating-and-accessing-values) - [Plucking Rows](#plucking-rows) @@ -342,7 +343,7 @@ Additional constraints may be provided outside the scope of a single column usin ## Inserting Rows -We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. +We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. ``` swift users.insert(email <- "alice@mac.com", name <- "Alice")? @@ -403,6 +404,65 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s > ``` +### Setters + +SQLite.swift typically uses the `<-` operator to set values during [inserts](#inserting-rows) and [updates](#updating-rows). + +``` swift +views.update(count <- 0) +// UPDATE views SET count = 0 WHERE (id = 1) +``` + +There are also a number of convenience setters that take the existing value into account using native Swift operators. + +For example, to atomically increment a column, we can use `++`: + +``` swift +views.update(count++) // equivalent to views.update(count -> count + 1) +// UPDATE views SET count = count + 1 WHERE (id = 1) +``` + +To take an amount and “move” it via transaction, we can use `-=` and `+=`: + +``` swift +let amount = 100.0 +db.transaction( + alice.update(balance -= amount), + betty.update(balance += amount) +) +// BEGIN DEFERRED TRANSACTION; +// UPDATE users SET balance = balance - 100.0 WHERE (id = 1); +// UPDATE users SET balance = balance + 100.0 WHERE (id = 2); +// COMMIT TRANSACTION; +``` + + +###### Infix Setters + +| Operator | Types | +| -------- | ------------------ | +| `<-` | `Value -> Value` | +| `+=` | `Number -> Number` | +| `-=` | `Number -> Number` | +| `*=` | `Number -> Number` | +| `/=` | `Number -> Number` | +| `%=` | `Int -> Int` | +| `<<=` | `Int -> Int` | +| `>>=` | `Int -> Int` | +| `&=` | `Int -> Int` | +| `||=` | `Int -> Int` | +| `^=` | `Int -> Int` | +| `+=` | `String -> String` | + + +###### Postfix Setters + +| Operator | Types | +| -------- | ------------ | +| `++` | `Int -> Int` | +| `--` | `Int -> Int` | + + ## Selecting Rows [`Query` structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. @@ -692,7 +752,7 @@ users.filter(name != nil).count ## Updating Rows -We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. +We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. When an unscoped query calls `update`, it will update _every_ row in the table. From 0ef090dd55898d0fab9b3d19d9819c15bdd670b6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 20 Nov 2014 10:02:59 -0800 Subject: [PATCH 0093/1046] Document pure-Swift-ness Yes, it uses Foundation for a couple things under the hood. Yes, this could change in the future. Signed-off-by: Stephen Celis --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ea4ca1c9..48c609ba 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ syntax _and_ intent. ## Features + - A pure-Swift interface - A powerful interface for building type-safe, optional-aware SQL expressions - A flexible, chainable, lazy-executing query layer From 6538900c7c3d8730c32813c5af2cdf7d34210177 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 20 Nov 2014 10:04:43 -0800 Subject: [PATCH 0094/1046] Update README requirements Signed-off-by: Stephen Celis --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 48c609ba..fbee2201 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,8 @@ interactively, from the Xcode project’s playground. ## Installation -_Note: SQLite.swift requires Swift 1.1 (available in [Xcode 6.1][4.1])._ +> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode +> 6.1](https://developer.apple.com/xcode/downloads/)) or greater. To install SQLite.swift: From 05dab30cfc34d732db50b9e5f09cb9d8f10fb302 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 20 Nov 2014 17:58:49 -0800 Subject: [PATCH 0095/1046] Documentation tweaks Signed-off-by: Stephen Celis --- Documentation/Index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bd8e5f05..4973e44c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -139,7 +139,7 @@ let db = Database(path, readonly: true) If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). ``` swift -let db = Database() // equivalent to Database(":memory:") +let db = Database() // equivalent to `Database(":memory:")` ``` To create a temporary, disk-backed database, pass an empty file name. @@ -418,7 +418,7 @@ There are also a number of convenience setters that take the existing value into For example, to atomically increment a column, we can use `++`: ``` swift -views.update(count++) // equivalent to views.update(count -> count + 1) +views.update(count++) // equivalent to `views.update(count -> count + 1)` // UPDATE views SET count = count + 1 WHERE (id = 1) ``` @@ -769,7 +769,7 @@ alice.update(email <- "alice@me.com")? // UPDATE users SET email = 'alice@me.com' WHERE (id = 1) ``` -[Like `insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts. +Like [`insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts. - An `Int?` representing the number of updated rows (or `nil` on failure), for simplicity. @@ -1014,7 +1014,7 @@ SQLite.swift supports serializing and deserializing any custom type as long as i > } > ``` -`Datatype` refers to a one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL](#building-type-safe-sql) for a list of types). +`Datatype` is one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL](#building-type-safe-sql) for a list of types). > _Note:_ `Binding` is a protocol that SQLite.swift uses internally to directly map SQLite types to Swift types. **Do _not_** conform custom types to the `Binding` protocol. @@ -1164,10 +1164,10 @@ extension Row { ## Other Operators +In addition to [filter operators](#filtering-infix-operators), SQLite.swift defines a number of operators that can modify expression values with arithmetic, bitwise operations, and concatenation. -### Other Infix Operators -In addition to [filter operators](#filtering-infix-operators), SQLite.swift defines a number of operators that can modify expression values with arithmetic, bitwise operations, and concatenation. +###### Other Infix Operators | Swift | Types | SQLite | | ----- | -------------------------------- | -------- | @@ -1185,7 +1185,7 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi > _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. -### Other Prefix Operators +###### Other Prefix Operators | Swift | Types | SQLite | | ----- | ------------------ | ------ | From 36cd61b4289ab2940a8231c928ae9a0fa7719fe5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 22 Nov 2014 09:35:08 -0800 Subject: [PATCH 0096/1046] Avoid word repetition in README Signed-off-by: Stephen Celis --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index fbee2201..5dcb54b3 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ syntax _and_ intent. ## Features - A pure-Swift interface - - A powerful interface for building type-safe, optional-aware SQL - expressions + - A type-safe, optional-aware SQL expression builder - A flexible, chainable, lazy-executing query layer - Automatically-typed data access - A lightweight, uncomplicated query and parameter binding interface From 0a22008e60b85bea95b8af24cc115f985190ec31 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 22 Nov 2014 09:36:36 -0800 Subject: [PATCH 0097/1046] Quote table (query) names This makes it possible to have tables named "table", tables with spaces, quotation marks, etc. Columns still aren't quoted because they're generic expressions, but perhaps we can move towards something like: let id = Expression("id") // equivalent to `Expression(literal: quote(identifier: "id"))` users[id] // "users"."id" Signed-off-by: Stephen Celis --- README.md | 12 +- SQLite Common Tests/ExpressionTests.swift | 190 +++++++++++----------- SQLite Common Tests/QueryTests.swift | 109 +++++++------ SQLite Common Tests/SchemaTests.swift | 92 ++++++----- SQLite Common Tests/TestHelper.swift | 4 +- SQLite Common/Database.swift | 12 +- SQLite Common/Query.swift | 4 +- SQLite Common/Schema.swift | 4 +- 8 files changed, 222 insertions(+), 205 deletions(-) diff --git a/README.md b/README.md index 5dcb54b3..922a0326 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ db.create(table: users) { t in t.column(name) t.column(email, unique: true) } -// CREATE TABLE users ( +// CREATE TABLE "users" ( // id INTEGER PRIMARY KEY NOT NULL, // name TEXT, // email TEXT NOT NULL UNIQUE @@ -56,22 +56,22 @@ if let insertedID = users.insert(name <- "Alice", email <- "alice@mac.com") { // inserted id: 1 alice = users.filter(id == insertedID) } -// INSERT INTO users (name, email) VALUES ('Alice', 'alice@mac.com') +// INSERT INTO "users" (name, email) VALUES ('Alice', 'alice@mac.com') for user in users { println("id: \(user[id]), name: \(user[name]), email: \(user[email])" // id: 1, name: Optional("Alice"), email: alice@mac.com } -// SELECT * FROM users +// SELECT * FROM "users" alice?.update(email <- replace(email, "mac.com", "me.com"))? -// UPDATE users SET email = replace(email, "mac.com", "me.com") WHERE (id = 1) +// UPDATE "users" SET email = replace(email, "mac.com", "me.com") WHERE (id = 1) alice?.delete()? -// DELETE FROM users WHERE (id = 1) +// DELETE FROM "users" WHERE (id = 1) users.count -// SELECT count(*) FROM users +// SELECT count(*) FROM "users" ``` SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 5d9283b4..65e539e0 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -7,7 +7,7 @@ class ExpressionTests: XCTestCase { var users: Query { return db["users"] } func ExpectExecutionMatches(SQL: String, _ expression: Expressible) { - ExpectExecution(db, "SELECT \(SQL) FROM users", users.select(expression)) + ExpectExecution(db, "SELECT \(SQL) FROM \"users\"", users.select(expression)) } let stringA = Expression(value: "A") @@ -524,7 +524,7 @@ class ExpressionTests: XCTestCase { } func test_plusEquals_withStringExpression_buildsSetter() { - let SQL = "UPDATE users SET email = (email || email)" + let SQL = "UPDATE \"users\" SET email = (email || email)" ExpectExecution(db, SQL, users.update(email += email)) ExpectExecution(db, SQL, users.update(email += email2)) ExpectExecution(db, SQL, users.update(email2 += email)) @@ -532,167 +532,167 @@ class ExpressionTests: XCTestCase { } func test_plusEquals_withStringValue_buildsSetter() { - let SQL = "UPDATE users SET email = (email || '.com')" + let SQL = "UPDATE \"users\" SET email = (email || '.com')" ExpectExecution(db, SQL, users.update(email += ".com")) ExpectExecution(db, SQL, users.update(email2 += ".com")) } func test_plusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age + age)", users.update(age += age)) - ExpectExecution(db, "UPDATE users SET age = (age + id)", users.update(age += id)) - ExpectExecution(db, "UPDATE users SET id = (id + age)", users.update(id += age)) - ExpectExecution(db, "UPDATE users SET id = (id + id)", users.update(id += id)) - ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary += salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary += salary2)) - ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary2 += salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary + salary)", users.update(salary2 += salary2)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age + age)", users.update(age += age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age + id)", users.update(age += id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id + age)", users.update(id += age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id + id)", users.update(id += id)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary += salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary += salary2)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary2 += salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary2 += salary2)) } func test_plusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET id = (id + 1)", users.update(id += 1)) - ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age += 1)) - ExpectExecution(db, "UPDATE users SET salary = (salary + 100.0)", users.update(salary += 100)) - ExpectExecution(db, "UPDATE users SET salary = (salary + 100.0)", users.update(salary2 += 100)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id + 1)", users.update(id += 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age + 1)", users.update(age += 1)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + 100.0)", users.update(salary += 100)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + 100.0)", users.update(salary2 += 100)) } func test_minusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age - age)", users.update(age -= age)) - ExpectExecution(db, "UPDATE users SET age = (age - id)", users.update(age -= id)) - ExpectExecution(db, "UPDATE users SET id = (id - age)", users.update(id -= age)) - ExpectExecution(db, "UPDATE users SET id = (id - id)", users.update(id -= id)) - ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary -= salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary -= salary2)) - ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary2 -= salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary - salary)", users.update(salary2 -= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age - age)", users.update(age -= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age - id)", users.update(age -= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id - age)", users.update(id -= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id - id)", users.update(id -= id)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary -= salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary -= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary2 -= salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary2 -= salary2)) } func test_minusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET id = (id - 1)", users.update(id -= 1)) - ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age -= 1)) - ExpectExecution(db, "UPDATE users SET salary = (salary - 100.0)", users.update(salary -= 100)) - ExpectExecution(db, "UPDATE users SET salary = (salary - 100.0)", users.update(salary2 -= 100)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id - 1)", users.update(id -= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age - 1)", users.update(age -= 1)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - 100.0)", users.update(salary -= 100)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - 100.0)", users.update(salary2 -= 100)) } func test_timesEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age * age)", users.update(age *= age)) - ExpectExecution(db, "UPDATE users SET age = (age * id)", users.update(age *= id)) - ExpectExecution(db, "UPDATE users SET id = (id * age)", users.update(id *= age)) - ExpectExecution(db, "UPDATE users SET id = (id * id)", users.update(id *= id)) - ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary *= salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary *= salary2)) - ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary2 *= salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary * salary)", users.update(salary2 *= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age * age)", users.update(age *= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age * id)", users.update(age *= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id * age)", users.update(id *= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id * id)", users.update(id *= id)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary *= salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary *= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary2 *= salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary2 *= salary2)) } func test_timesEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET id = (id * 1)", users.update(id *= 1)) - ExpectExecution(db, "UPDATE users SET age = (age * 1)", users.update(age *= 1)) - ExpectExecution(db, "UPDATE users SET salary = (salary * 100.0)", users.update(salary *= 100)) - ExpectExecution(db, "UPDATE users SET salary = (salary * 100.0)", users.update(salary2 *= 100)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id * 1)", users.update(id *= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age * 1)", users.update(age *= 1)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * 100.0)", users.update(salary *= 100)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * 100.0)", users.update(salary2 *= 100)) } func test_divideEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age / age)", users.update(age /= age)) - ExpectExecution(db, "UPDATE users SET age = (age / id)", users.update(age /= id)) - ExpectExecution(db, "UPDATE users SET id = (id / age)", users.update(id /= age)) - ExpectExecution(db, "UPDATE users SET id = (id / id)", users.update(id /= id)) - ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary /= salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary /= salary2)) - ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary2 /= salary)) - ExpectExecution(db, "UPDATE users SET salary = (salary / salary)", users.update(salary2 /= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age / age)", users.update(age /= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age / id)", users.update(age /= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id / age)", users.update(id /= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id / id)", users.update(id /= id)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary /= salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary /= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary2 /= salary)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary2 /= salary2)) } func test_divideEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET id = (id / 1)", users.update(id /= 1)) - ExpectExecution(db, "UPDATE users SET age = (age / 1)", users.update(age /= 1)) - ExpectExecution(db, "UPDATE users SET salary = (salary / 100.0)", users.update(salary /= 100)) - ExpectExecution(db, "UPDATE users SET salary = (salary / 100.0)", users.update(salary2 /= 100)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id / 1)", users.update(id /= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age / 1)", users.update(age /= 1)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / 100.0)", users.update(salary /= 100)) + ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / 100.0)", users.update(salary2 /= 100)) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age % age)", users.update(age %= age)) - ExpectExecution(db, "UPDATE users SET age = (age % id)", users.update(age %= id)) - ExpectExecution(db, "UPDATE users SET id = (id % age)", users.update(id %= age)) - ExpectExecution(db, "UPDATE users SET id = (id % id)", users.update(id %= id)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age % age)", users.update(age %= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age % id)", users.update(age %= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id % age)", users.update(id %= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id % id)", users.update(id %= id)) } func test_moduloEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age % 10)", users.update(age %= 10)) - ExpectExecution(db, "UPDATE users SET id = (id % 10)", users.update(id %= 10)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age % 10)", users.update(age %= 10)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id % 10)", users.update(id %= 10)) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age >> age)", users.update(age >>= age)) - ExpectExecution(db, "UPDATE users SET age = (age >> id)", users.update(age >>= id)) - ExpectExecution(db, "UPDATE users SET id = (id >> age)", users.update(id >>= age)) - ExpectExecution(db, "UPDATE users SET id = (id >> id)", users.update(id >>= id)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age >> age)", users.update(age >>= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age >> id)", users.update(age >>= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id >> age)", users.update(id >>= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id >> id)", users.update(id >>= id)) } func test_rightShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age >> 1)", users.update(age >>= 1)) - ExpectExecution(db, "UPDATE users SET id = (id >> 1)", users.update(id >>= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age >> 1)", users.update(age >>= 1)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id >> 1)", users.update(id >>= 1)) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age << age)", users.update(age <<= age)) - ExpectExecution(db, "UPDATE users SET age = (age << id)", users.update(age <<= id)) - ExpectExecution(db, "UPDATE users SET id = (id << age)", users.update(id <<= age)) - ExpectExecution(db, "UPDATE users SET id = (id << id)", users.update(id <<= id)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age << age)", users.update(age <<= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age << id)", users.update(age <<= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id << age)", users.update(id <<= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id << id)", users.update(id <<= id)) } func test_leftShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age << 1)", users.update(age <<= 1)) - ExpectExecution(db, "UPDATE users SET id = (id << 1)", users.update(id <<= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age << 1)", users.update(age <<= 1)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id << 1)", users.update(id <<= 1)) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age & age)", users.update(age &= age)) - ExpectExecution(db, "UPDATE users SET age = (age & id)", users.update(age &= id)) - ExpectExecution(db, "UPDATE users SET id = (id & age)", users.update(id &= age)) - ExpectExecution(db, "UPDATE users SET id = (id & id)", users.update(id &= id)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age & age)", users.update(age &= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age & id)", users.update(age &= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id & age)", users.update(id &= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id & id)", users.update(id &= id)) } func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age & 1)", users.update(age &= 1)) - ExpectExecution(db, "UPDATE users SET id = (id & 1)", users.update(id &= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age & 1)", users.update(age &= 1)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id & 1)", users.update(id &= 1)) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age | age)", users.update(age |= age)) - ExpectExecution(db, "UPDATE users SET age = (age | id)", users.update(age |= id)) - ExpectExecution(db, "UPDATE users SET id = (id | age)", users.update(id |= age)) - ExpectExecution(db, "UPDATE users SET id = (id | id)", users.update(id |= id)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age | age)", users.update(age |= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age | id)", users.update(age |= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id | age)", users.update(id |= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id | id)", users.update(id |= id)) } func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age | 1)", users.update(age |= 1)) - ExpectExecution(db, "UPDATE users SET id = (id | 1)", users.update(id |= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age | 1)", users.update(age |= 1)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id | 1)", users.update(id |= 1)) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (~((age & age)) & (age | age))", users.update(age ^= age)) - ExpectExecution(db, "UPDATE users SET age = (~((age & id)) & (age | id))", users.update(age ^= id)) - ExpectExecution(db, "UPDATE users SET id = (~((id & age)) & (id | age))", users.update(id ^= age)) - ExpectExecution(db, "UPDATE users SET id = (~((id & id)) & (id | id))", users.update(id ^= id)) + ExpectExecution(db, "UPDATE \"users\" SET age = (~((age & age)) & (age | age))", users.update(age ^= age)) + ExpectExecution(db, "UPDATE \"users\" SET age = (~((age & id)) & (age | id))", users.update(age ^= id)) + ExpectExecution(db, "UPDATE \"users\" SET id = (~((id & age)) & (id | age))", users.update(id ^= age)) + ExpectExecution(db, "UPDATE \"users\" SET id = (~((id & id)) & (id | id))", users.update(id ^= id)) } func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (~((age & 1)) & (age | 1))", users.update(age ^= 1)) - ExpectExecution(db, "UPDATE users SET id = (~((id & 1)) & (id | 1))", users.update(id ^= 1)) + ExpectExecution(db, "UPDATE \"users\" SET age = (~((age & 1)) & (age | 1))", users.update(age ^= 1)) + ExpectExecution(db, "UPDATE \"users\" SET id = (~((id & 1)) & (id | 1))", users.update(id ^= 1)) } func test_postfixPlus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age++)) - ExpectExecution(db, "UPDATE users SET age = (age + 1)", users.update(age++)) - ExpectExecution(db, "UPDATE users SET id = (id + 1)", users.update(id++)) - ExpectExecution(db, "UPDATE users SET id = (id + 1)", users.update(id++)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age + 1)", users.update(age++)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age + 1)", users.update(age++)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id + 1)", users.update(id++)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id + 1)", users.update(id++)) } func test_postfixMinus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age--)) - ExpectExecution(db, "UPDATE users SET age = (age - 1)", users.update(age--)) - ExpectExecution(db, "UPDATE users SET id = (id - 1)", users.update(id--)) - ExpectExecution(db, "UPDATE users SET id = (id - 1)", users.update(id--)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age - 1)", users.update(age--)) + ExpectExecution(db, "UPDATE \"users\" SET age = (age - 1)", users.update(age--)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id - 1)", users.update(id--)) + ExpectExecution(db, "UPDATE \"users\" SET id = (id - 1)", users.update(id--)) } func test_precedencePreserved() { diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index 1af02db9..a258fae4 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -22,35 +22,35 @@ class QueryTests: XCTestCase { func test_select_withExpression_compilesSelectClause() { let query = users.select(email) - let SQL = "SELECT email FROM users" + let SQL = "SELECT email FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_select_withVariadicExpressions_compilesSelectClause() { let query = users.select(email, count(*)) - let SQL = "SELECT email, count(*) FROM users" + let SQL = "SELECT email, count(*) FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_select_withStar_resetsSelectClause() { let query = users.select(email) - let SQL = "SELECT * FROM users" + let SQL = "SELECT * FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(all: *) {} } } func test_selectDistinct_withExpression_compilesSelectClause() { let query = users.select(distinct: age) - let SQL = "SELECT DISTINCT age FROM users" + let SQL = "SELECT DISTINCT age FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_selectDistinct_withStar_compilesSelectClause() { let query = users.select(distinct: *) - let SQL = "SELECT DISTINCT * FROM users" + let SQL = "SELECT DISTINCT * FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -59,7 +59,8 @@ class QueryTests: XCTestCase { let query = users.join(managers, on: managers[id] == users[manager_id]) - let SQL = "SELECT * FROM users INNER JOIN users AS managers ON (managers.id = users.manager_id)" + let SQL = "SELECT * FROM \"users\" " + + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".id = \"users\".manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -68,7 +69,8 @@ class QueryTests: XCTestCase { let query = users.join(.LeftOuter, managers, on: managers[id] == users[manager_id]) - let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON (managers.id = users.manager_id)" + let SQL = "SELECT * FROM \"users\" " + + "LEFT OUTER JOIN \"users\" AS \"managers\" ON (\"managers\".id = \"users\".manager_id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -78,10 +80,10 @@ class QueryTests: XCTestCase { let query = users.join(managers, on: managers[id] == users[manager_id]) - let SQL = "SELECT * FROM users " + - "INNER JOIN users AS managers " + - "ON ((managers.id = users.manager_id) " + - "AND managers.admin)" + let SQL = "SELECT * FROM \"users\" " + + "INNER JOIN \"users\" AS \"managers\" " + + "ON ((\"managers\".id = \"users\".manager_id) " + + "AND \"managers\".admin)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -93,9 +95,9 @@ class QueryTests: XCTestCase { .join(managers, on: managers[id] == users[manager_id]) .join(managed, on: managed[manager_id] == users[id]) - let SQL = "SELECT * FROM users " + - "INNER JOIN users AS managers ON (managers.id = users.manager_id) " + - "INNER JOIN users AS managed ON (managed.manager_id = users.id)" + let SQL = "SELECT * FROM \"users\" " + + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".id = \"users\".manager_id) " + + "INNER JOIN \"users\" AS \"managed\" ON (\"managed\".manager_id = \"users\".id)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} } } @@ -116,7 +118,7 @@ class QueryTests: XCTestCase { func test_filter_compilesWhereClause() { let query = users.filter(admin == true) - let SQL = "SELECT * FROM users WHERE (admin = 1)" + let SQL = "SELECT * FROM \"users\" WHERE (admin = 1)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -125,7 +127,7 @@ class QueryTests: XCTestCase { .filter(email == "alice@example.com") .filter(age >= 21) - let SQL = "SELECT * FROM users " + + let SQL = "SELECT * FROM \"users\" " + "WHERE ((email = 'alice@example.com') " + "AND (age >= 21))" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } @@ -134,107 +136,107 @@ class QueryTests: XCTestCase { func test_group_withSingleExpressionName_compilesGroupClause() { let query = users.group(age) - let SQL = "SELECT * FROM users GROUP BY age" + let SQL = "SELECT * FROM \"users\" GROUP BY age" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withVariadicExpressionNames_compilesGroupClause() { let query = users.group(age, admin) - let SQL = "SELECT * FROM users GROUP BY age, admin" + let SQL = "SELECT * FROM \"users\" GROUP BY age, admin" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { let query = users.group(age, having: age >= 30) - let SQL = "SELECT * FROM users GROUP BY age HAVING (age >= 30)" + let SQL = "SELECT * FROM \"users\" GROUP BY age HAVING (age >= 30)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { let query = users.group([age, admin], having: age >= 30) - let SQL = "SELECT * FROM users GROUP BY age, admin HAVING (age >= 30)" + let SQL = "SELECT * FROM \"users\" GROUP BY age, admin HAVING (age >= 30)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_order_withSingleExpressionName_compilesOrderClause() { let query = users.order(age) - let SQL = "SELECT * FROM users ORDER BY age" + let SQL = "SELECT * FROM \"users\" ORDER BY age" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_order_withVariadicExpressionNames_compilesOrderClause() { let query = users.order(age, email) - let SQL = "SELECT * FROM users ORDER BY age, email" + let SQL = "SELECT * FROM \"users\" ORDER BY age, email" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_order_withExpressionAndSortDirection_compilesOrderClause() { let query = users.order(age.desc, email.asc) - let SQL = "SELECT * FROM users ORDER BY age DESC, email ASC" + let SQL = "SELECT * FROM \"users\" ORDER BY age DESC, email ASC" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_limit_compilesLimitClause() { let query = users.limit(5) - let SQL = "SELECT * FROM users LIMIT 5" + let SQL = "SELECT * FROM \"users\" LIMIT 5" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_limit_withOffset_compilesOffsetClause() { let query = users.limit(5, offset: 5) - let SQL = "SELECT * FROM users LIMIT 5 OFFSET 5" + let SQL = "SELECT * FROM \"users\" LIMIT 5 OFFSET 5" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_limit_whenChained_overridesLimit() { let query = users.limit(5).limit(10) - var SQL = "SELECT * FROM users LIMIT 10" + var SQL = "SELECT * FROM \"users\" LIMIT 10" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - SQL = "SELECT * FROM users" + SQL = "SELECT * FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} } } func test_limit_whenChained_withOffset_overridesOffset() { let query = users.limit(5, offset: 5).limit(10, offset: 10) - var SQL = "SELECT * FROM users LIMIT 10 OFFSET 10" + var SQL = "SELECT * FROM \"users\" LIMIT 10 OFFSET 10" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - SQL = "SELECT * FROM users" + SQL = "SELECT * FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} } } func test_alias_compilesAliasInSelectClause() { let managers = users.alias("managers") - var SQL = "SELECT * FROM users AS managers" + var SQL = "SELECT * FROM \"users\" AS \"managers\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in managers {} } } func test_subscript_withExpression_returnsNamespacedExpression() { - ExpectExecution(db, "SELECT users.admin FROM users", users.select(users[admin])) - ExpectExecution(db, "SELECT users.salary FROM users", users.select(users[salary])) - ExpectExecution(db, "SELECT users.age FROM users", users.select(users[age])) - ExpectExecution(db, "SELECT users.email FROM users", users.select(users[email])) - ExpectExecution(db, "SELECT users.* FROM users", users.select(users[*])) + ExpectExecution(db, "SELECT \"users\".admin FROM \"users\"", users.select(users[admin])) + ExpectExecution(db, "SELECT \"users\".salary FROM \"users\"", users.select(users[salary])) + ExpectExecution(db, "SELECT \"users\".age FROM \"users\"", users.select(users[age])) + ExpectExecution(db, "SELECT \"users\".email FROM \"users\"", users.select(users[email])) + ExpectExecution(db, "SELECT \"users\".* FROM \"users\"", users.select(users[*])) } func test_subscript_withAliasAndExpression_returnsAliasedExpression() { let managers = users.alias("managers") - ExpectExecution(db, "SELECT managers.admin FROM users AS managers", managers.select(managers[admin])) - ExpectExecution(db, "SELECT managers.salary FROM users AS managers", managers.select(managers[salary])) - ExpectExecution(db, "SELECT managers.age FROM users AS managers", managers.select(managers[age])) - ExpectExecution(db, "SELECT managers.email FROM users AS managers", managers.select(managers[email])) - ExpectExecution(db, "SELECT managers.* FROM users AS managers", managers.select(managers[*])) + ExpectExecution(db, "SELECT \"managers\".admin FROM \"users\" AS \"managers\"", managers.select(managers[admin])) + ExpectExecution(db, "SELECT \"managers\".salary FROM \"users\" AS \"managers\"", managers.select(managers[salary])) + ExpectExecution(db, "SELECT \"managers\".age FROM \"users\" AS \"managers\"", managers.select(managers[age])) + ExpectExecution(db, "SELECT \"managers\".email FROM \"users\" AS \"managers\"", managers.select(managers[email])) + ExpectExecution(db, "SELECT \"managers\".* FROM \"users\" AS \"managers\"", managers.select(managers[*])) } func test_SQL_compilesProperly() { @@ -250,11 +252,12 @@ class QueryTests: XCTestCase { .order(users[email].desc) .limit(1, offset: 2) - let SQL = "SELECT users.email, count(users.email) FROM users " + - "LEFT OUTER JOIN users AS managers ON ((managers.id = users.manager_id) AND (managers.admin = 1)) " + - "WHERE users.age BETWEEN 21 AND 32 " + - "GROUP BY users.age HAVING (count(users.email) > 1) " + - "ORDER BY users.email DESC " + + let SQL = "SELECT \"users\".email, count(\"users\".email) FROM \"users\" " + + "LEFT OUTER JOIN \"users\" AS \"managers\" " + + "ON ((\"managers\".id = \"users\".manager_id) AND (\"managers\".admin = 1)) " + + "WHERE \"users\".age BETWEEN 21 AND 32 " + + "GROUP BY \"users\".age HAVING (count(\"users\".email) > 1) " + + "ORDER BY \"users\".email DESC " + "LIMIT 1 " + "OFFSET 2" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } @@ -266,13 +269,13 @@ class QueryTests: XCTestCase { func test_first_returnsTheFirstRow() { InsertUsers(db, "alice", "betsy") - ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 1]) { _ in + ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 1]) { _ in XCTAssertEqual(1, self.users.first![self.id]) } } func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() { - ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 2]) { _ in + ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 2]) { _ in XCTAssertTrue(self.users.isEmpty) InsertUser(self.db, "alice") XCTAssertFalse(self.users.isEmpty) @@ -280,7 +283,7 @@ class QueryTests: XCTestCase { } func test_insert_insertsRows() { - let SQL = "INSERT INTO users (email, age) VALUES ('alice@example.com', 30)" + let SQL = "INSERT INTO \"users\" (email, age) VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in XCTAssertEqual(1, self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID!) @@ -290,24 +293,24 @@ class QueryTests: XCTestCase { } func test_insert_withQuery_insertsRows() { - db.execute("CREATE TABLE emails (email TEXT)") + db.execute("CREATE TABLE \"emails\" (email TEXT)") let emails = db["emails"] let admins = users.select(email).filter(admin == true) - ExpectExecution(db, "INSERT INTO emails SELECT email FROM users WHERE (admin = 1)", emails.insert(admins)) + ExpectExecution(db, "INSERT INTO \"emails\" SELECT email FROM \"users\" WHERE (admin = 1)", emails.insert(admins)) } func test_insert_insertsDefaultRow() { - db.execute("CREATE TABLE timestamps (id INTEGER PRIMARY KEY, timestamp TEXT DEFAULT CURRENT_DATETIME)") + db.execute("CREATE TABLE \"timestamps\" (id INTEGER PRIMARY KEY, timestamp TEXT DEFAULT CURRENT_DATETIME)") let table = db["timestamps"] - ExpectExecutions(db, ["INSERT INTO timestamps DEFAULT VALUES": 1]) { _ in + ExpectExecutions(db, ["INSERT INTO \"timestamps\" DEFAULT VALUES": 1]) { _ in XCTAssertEqual(1, table.insert().ID!) } } func test_replace_replaceRows() { - let SQL = "INSERT OR REPLACE INTO users (email, age) VALUES ('alice@example.com', 30)" + let SQL = "INSERT OR REPLACE INTO \"users\" (email, age) VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in XCTAssertEqual(1, self.users.replace(self.email <- "alice@example.com", self.age <- 30).ID!) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 90bf059a..dcd97682 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -19,25 +19,25 @@ class SchemaTests: XCTestCase { } func test_createTable_createsTable() { - ExpectExecution(db, "CREATE TABLE users (age INTEGER)", + ExpectExecution(db, "CREATE TABLE \"users\" (age INTEGER)", db.create(table: users) { t in t.column(age) } ) } func test_createTable_temporary_createsTemporaryTable() { - ExpectExecution(db, "CREATE TEMPORARY TABLE users (age INTEGER)", + ExpectExecution(db, "CREATE TEMPORARY TABLE \"users\" (age INTEGER)", db.create(table: users, temporary: true) { t in t.column(age) } ) } func test_createTable_ifNotExists_createsTableIfNotExists() { - ExpectExecution(db, "CREATE TABLE IF NOT EXISTS users (age INTEGER)", + ExpectExecution(db, "CREATE TABLE IF NOT EXISTS \"users\" (age INTEGER)", db.create(table: users, ifNotExists: true) { t in t.column(age) } ) } func test_createTable_column_buildsColumnDefinition() { - ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL)", + ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL)", db.create(table: users) { t in t.column(email) } @@ -45,7 +45,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { - ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL)", + ExpectExecution(db, "CREATE TABLE \"users\" (id INTEGER PRIMARY KEY NOT NULL)", db.create(table: users) { t in t.column(id, primaryKey: true) } @@ -53,7 +53,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withNullFalse_buildsNotNullClause() { - ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL)", + ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL)", db.create(table: users) { t in t.column(email) } @@ -61,7 +61,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withUnique_buildsUniqueClause() { - ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL UNIQUE)", + ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL UNIQUE)", db.create(table: users) { t in t.column(email, unique: true) } @@ -69,7 +69,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withCheck_buildsCheckClause() { - ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN NOT NULL CHECK (admin IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE \"users\" (admin BOOLEAN NOT NULL CHECK (admin IN (0, 1)))", db.create(table: users) { t in t.column(admin, check: contains([false, true], admin)) } @@ -77,7 +77,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withDefaultValue_buildsDefaultClause() { - ExpectExecution(db, "CREATE TABLE users (salary REAL NOT NULL DEFAULT 0.0)", + ExpectExecution(db, "CREATE TABLE \"users\" (salary REAL NOT NULL DEFAULT 0.0)", db.create(table: users) { t in t.column(salary, defaultValue: 0.0) } @@ -85,7 +85,7 @@ class SchemaTests: XCTestCase { } func test_createTable_stringColumn_collation_buildsCollateClause() { - ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL COLLATE NOCASE)", + ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL COLLATE NOCASE)", db.create(table: users) { t in t.column(email, collate: .NoCase) } @@ -94,7 +94,10 @@ class SchemaTests: XCTestCase { func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, manager_id INTEGER REFERENCES users(id))", + ExpectExecution(db, "CREATE TABLE \"users\" (" + + "id INTEGER PRIMARY KEY NOT NULL, " + + "manager_id INTEGER REFERENCES \"users\"(id)" + + ")", db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id, references: users[id]) @@ -104,7 +107,10 @@ class SchemaTests: XCTestCase { func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, manager_id INTEGER REFERENCES users)", + ExpectExecution(db, "CREATE TABLE \"users\" (" + + "id INTEGER PRIMARY KEY NOT NULL, " + + "manager_id INTEGER REFERENCES \"users\"" + + ")", db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id, references: users) @@ -114,7 +120,7 @@ class SchemaTests: XCTestCase { func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL, PRIMARY KEY(email))", + ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL, PRIMARY KEY(email))", db.create(table: users) { t in t.column(email) t.primaryKey(email) @@ -124,7 +130,7 @@ class SchemaTests: XCTestCase { func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER NOT NULL, email TEXT NOT NULL, PRIMARY KEY(id, email))", + ExpectExecution(db, "CREATE TABLE \"users\" (id INTEGER NOT NULL, email TEXT NOT NULL, PRIMARY KEY(id, email))", db.create(table: users) { t in t.column(id) t.column(email) @@ -135,7 +141,7 @@ class SchemaTests: XCTestCase { func test_createTable_unique_buildsUniqueTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (email TEXT NOT NULL, UNIQUE(email))", + ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL, UNIQUE(email))", db.create(table: users) { t in t.column(email) t.unique(email) @@ -145,7 +151,7 @@ class SchemaTests: XCTestCase { func test_createTable_unique_buildsCompositeUniqueTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (id INTEGER NOT NULL, email TEXT NOT NULL, UNIQUE(id, email))", + ExpectExecution(db, "CREATE TABLE \"users\" (id INTEGER NOT NULL, email TEXT NOT NULL, UNIQUE(id, email))", db.create(table: users) { t in t.column(id) t.column(email) @@ -156,7 +162,7 @@ class SchemaTests: XCTestCase { func test_createTable_check_buildsCheckTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE users (admin BOOLEAN NOT NULL, CHECK (admin IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE \"users\" (admin BOOLEAN NOT NULL, CHECK (admin IN (0, 1)))", db.create(table: users) { t in t.column(admin) t.check(contains([false, true], admin)) @@ -167,10 +173,10 @@ class SchemaTests: XCTestCase { func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() { let users = self.users ExpectExecution(db, - "CREATE TABLE users (" + + "CREATE TABLE \"users\" (" + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + + "FOREIGN KEY(manager_id) REFERENCES \"users\"(id)" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -183,10 +189,10 @@ class SchemaTests: XCTestCase { func test_createTable_foreignKey_referencingTable_buildsForeignKeyTableConstraint() { let users = self.users ExpectExecution(db, - "CREATE TABLE users (" + + "CREATE TABLE \"users\" (" + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users" + + "FOREIGN KEY(manager_id) REFERENCES \"users\"" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -199,10 +205,10 @@ class SchemaTests: XCTestCase { func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() { let users = self.users ExpectExecution(db, - "CREATE TABLE users (" + + "CREATE TABLE \"users\" (" + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users ON UPDATE CASCADE" + + "FOREIGN KEY(manager_id) REFERENCES \"users\" ON UPDATE CASCADE" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -215,10 +221,10 @@ class SchemaTests: XCTestCase { func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() { let users = self.users ExpectExecution(db, - "CREATE TABLE users (" + + "CREATE TABLE \"users\" (" + "id INTEGER PRIMARY KEY NOT NULL, " + "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users ON DELETE CASCADE" + + "FOREIGN KEY(manager_id) REFERENCES \"users\" ON DELETE CASCADE" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -231,25 +237,25 @@ class SchemaTests: XCTestCase { func test_createTable_withQuery_createsTableWithQuery() { CreateUsersTable(db) ExpectExecution(db, - "CREATE TABLE emails AS SELECT email FROM users", + "CREATE TABLE \"emails\" AS SELECT email FROM \"users\"", db.create(table: db["emails"], from: users.select(email)) ) ExpectExecution(db, - "CREATE TEMPORARY TABLE IF NOT EXISTS emails AS SELECT email FROM users", + "CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT email FROM \"users\"", db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) ) } func test_alterTable_renamesTable() { CreateUsersTable(db) - ExpectExecution(db, "ALTER TABLE users RENAME TO people", db.rename(table: users, to: "people") ) + ExpectExecution(db, "ALTER TABLE \"users\" RENAME TO \"people\"", db.rename(table: users, to: "people") ) } func test_alterTable_addsNotNullColumn() { CreateUsersTable(db) let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE users ADD COLUMN bonus REAL NOT NULL DEFAULT 0.0", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN bonus REAL NOT NULL DEFAULT 0.0", db.alter(table: users, add: column, defaultValue: 0) ) } @@ -258,7 +264,7 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE users ADD COLUMN bonus REAL", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN bonus REAL", db.alter(table: users, add: column) ) } @@ -267,7 +273,7 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE users ADD COLUMN bonus REAL DEFAULT 0.0", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN bonus REAL DEFAULT 0.0", db.alter(table: users, add: column, defaultValue: 0) ) } @@ -276,21 +282,21 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("parent_id") - ExpectExecution(db, "ALTER TABLE users ADD COLUMN parent_id INTEGER REFERENCES users(id)", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN parent_id INTEGER REFERENCES \"users\"(id)", db.alter(table: users, add: column, references: users[id]) ) } func test_dropTable_dropsTable() { CreateUsersTable(db) - ExpectExecution(db, "DROP TABLE users", db.drop(table: users) ) - ExpectExecution(db, "DROP TABLE IF EXISTS users", db.drop(table: users, ifExists: true) ) + ExpectExecution(db, "DROP TABLE \"users\"", db.drop(table: users) ) + ExpectExecution(db, "DROP TABLE IF EXISTS \"users\"", db.drop(table: users, ifExists: true) ) } func test_index_executesIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX index_users_on_email ON users (email)", + "CREATE INDEX index_users_on_email ON \"users\" (email)", db.create(index: users, on: email) ) } @@ -298,7 +304,7 @@ class SchemaTests: XCTestCase { func test_index_withUniqueness_executesUniqueIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE UNIQUE INDEX index_users_on_email ON users (email)", + "CREATE UNIQUE INDEX index_users_on_email ON \"users\" (email)", db.create(index: users, unique: true, on: email) ) } @@ -306,7 +312,7 @@ class SchemaTests: XCTestCase { func test_index_ifNotExists_executesIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX IF NOT EXISTS index_users_on_email ON users (email)", + "CREATE INDEX IF NOT EXISTS index_users_on_email ON \"users\" (email)", db.create(index: users, ifNotExists: true, on: email) ) } @@ -314,7 +320,7 @@ class SchemaTests: XCTestCase { func test_index_withMultipleColumns_executesCompoundIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX index_users_on_age_DESC_email ON users (age DESC, email)", + "CREATE INDEX index_users_on_age_DESC_email ON \"users\" (age DESC, email)", db.create(index: users, on: age.desc, email) ) } @@ -323,7 +329,7 @@ class SchemaTests: XCTestCase { // if SQLITE_VERSION >= "3.8" { // CreateUsersTable(db) // ExpectExecution(db, -// "CREATE INDEX index_users_on_age ON users (age) WHERE admin", +// "CREATE INDEX index_users_on_age ON \"users\" (age) WHERE admin", // db.create(index: users.filter(admin), on: age) // ) // } @@ -340,11 +346,11 @@ class SchemaTests: XCTestCase { func test_createView_withQuery_createsViewWithQuery() { CreateUsersTable(db) ExpectExecution(db, - "CREATE VIEW emails AS SELECT email FROM users", + "CREATE VIEW \"emails\" AS SELECT email FROM \"users\"", db.create(view: db["emails"], from: users.select(email)) ) ExpectExecution(db, - "CREATE TEMPORARY VIEW IF NOT EXISTS emails AS SELECT email FROM users", + "CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT email FROM \"users\"", db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) ) } @@ -353,8 +359,8 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) db.create(view: db["emails"], from: users.select(email)) - ExpectExecution(db, "DROP VIEW emails", db.drop(view: db["emails"])) - ExpectExecution(db, "DROP VIEW IF EXISTS emails", db.drop(view: db["emails"], ifExists: true)) + ExpectExecution(db, "DROP VIEW \"emails\"", db.drop(view: db["emails"])) + ExpectExecution(db, "DROP VIEW IF EXISTS \"emails\"", db.drop(view: db["emails"], ifExists: true)) } } diff --git a/SQLite Common Tests/TestHelper.swift b/SQLite Common Tests/TestHelper.swift index b5e1a03d..b9d36e6e 100644 --- a/SQLite Common Tests/TestHelper.swift +++ b/SQLite Common Tests/TestHelper.swift @@ -7,7 +7,7 @@ func Trace(SQL: String) { func CreateUsersTable(db: Database) { db.execute( - "CREATE TABLE users (" + + "CREATE TABLE \"users\" (" + "id INTEGER PRIMARY KEY, " + "email TEXT NOT NULL UNIQUE, " + "age INTEGER, " + @@ -22,7 +22,7 @@ func CreateUsersTable(db: Database) { func InsertUser(db: Database, name: String, age: Int? = nil, admin: Bool? = false) -> Statement { return db.run( - "INSERT INTO users (email, age, admin) values (?, ?, ?)", + "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", ["\(name)@example.com", age, admin] ) } diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index d747765a..c0194f2b 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -352,6 +352,14 @@ extension Database: DebugPrintable { } internal func quote(#literal: String) -> String { - let escaped = join("''", split(literal) { $0 == "'" }) - return "'\(escaped)'" + return quote(literal, "'") +} + +internal func quote(#identifier: String) -> String { + return quote(identifier, "\"") +} + +private func quote(string: String, character: Character) -> String { + let escaped = join("\(character)\(character)", split(string) { $0 == character }) + return "\(character)\(escaped)\(character)" } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index ceeebb4a..7cd352a8 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -60,7 +60,7 @@ public struct Query { public func alias(alias: String?) -> Query { var query = self - query.alias = alias + query.alias = alias.map { quote(identifier: $0) } return query } @@ -805,7 +805,7 @@ extension Query: Printable { extension Database { public subscript(tableName: String) -> Query { - return Query(self, tableName) + return Query(self, quote(identifier: tableName)) } } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index fd8fb921..b3b302ff 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -43,7 +43,7 @@ public extension Database { } public func rename(#table: Query, to tableName: String) -> Statement { - return run("ALTER TABLE \(table.tableName) RENAME TO \(tableName)") + return run("ALTER TABLE \(table.tableName) RENAME TO \(quote(identifier: tableName))") } public func alter( @@ -102,7 +102,7 @@ public extension Database { let string = join(" ", ["index", table.tableName, "on"] + columns.map { $0.expression.SQL }) return Array(string).reduce("") { underscored, character in if "A"..."Z" ~= character || "a"..."z" ~= character { return underscored + String(character) } - return underscored + "_" + return underscored.hasSuffix("_") ? underscored : underscored + "_" } } From 05313fcf218911e0b91de06b8089b27e67b68176 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 22 Nov 2014 10:41:48 -0800 Subject: [PATCH 0098/1046] Quote column names Literal expressions are now made with `init(literal:)`: Expression(literal: "1 + 1") By default, `init(_:)` is for identifiers, like column names: Expression("id") // returns quoted identifier: \"id\" This makes things a bit safer and more flexible. Signed-off-by: Stephen Celis --- Documentation/Index.md | 148 +++++------ README.md | 18 +- SQLite Common Tests/ExpressionTests.swift | 296 +++++++++++----------- SQLite Common Tests/QueryTests.swift | 74 +++--- SQLite Common Tests/SchemaTests.swift | 90 +++---- SQLite Common/Expression.swift | 50 ++-- SQLite Common/Query.swift | 36 +-- SQLite Common/Schema.swift | 63 +++-- 8 files changed, 389 insertions(+), 386 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4973e44c..cc800262 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -215,10 +215,10 @@ Assuming [the table exists](#creating-a-table), we can immediately [insert](#ins We can run [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create(table:)` function on a database connection. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. ``` swift -db.create(table: users) { t in // CREATE TABLE users ( - t.column(id, primaryKey: true) // id INTEGER PRIMARY KEY NOT NULL, - t.column(email, unique: true) // email TEXT UNIQUE NOT NULL, - t.column(name) // name TEXT +db.create(table: users) { t in // CREATE TABLE "users" ( + t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL, + t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL, + t.column(name) // "name" TEXT } // ) ``` @@ -233,14 +233,14 @@ The `create(table:)` function has several default parameters we can override. ``` swift db.create(table: users, temporary: true) { t in /* ... */ } - // CREATE TEMPORARY TABLE users -- ... + // CREATE TEMPORARY TABLE "users" -- ... ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift db.create(table: users, ifNotExists: true) { t in /* ... */ } - // CREATE TABLE users IF NOT EXISTS -- ... + // CREATE TABLE "users" IF NOT EXISTS -- ... ``` ### Column Constraints @@ -262,21 +262,21 @@ The `column` function is used for a single column definition. It takes an [expre ``` swift t.column(email, unique: true) - // email TEXT UNIQUE NOT NULL + // "email" TEXT UNIQUE NOT NULL ``` - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).) ``` swift t.column(email, check: like("%@%", email)) - // email TEXT NOT NULL CHECK (email LIKE '%@%') + // "email" TEXT NOT NULL CHECK ("email" LIKE '%@%') ``` - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). ``` swift t.column(name, defaultValue: "Anonymous") - // name TEXT DEFAULT 'Anonymous' + // "name" TEXT DEFAULT 'Anonymous' ``` > _Note:_ The `defaultValue` parameter cannot be used alongside `primaryKey` and `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). @@ -285,17 +285,17 @@ The `column` function is used for a single column definition. It takes an [expre ``` swift t.column(email, collate: .NoCase) - // email TEXT NOT NULL COLLATE NOCASE + // "email" TEXT NOT NULL COLLATE NOCASE ``` - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift t.column(user_id, references: users[id]) - // user_id INTEGER REFERENCES users(id) + // "user_id" INTEGER REFERENCES "users"("id") t.column(user_id, references: users) - // user_id INTEGER REFERENCES users + // "user_id" INTEGER REFERENCES "users" // -- assumes "users" has a PRIMARY KEY ``` @@ -310,28 +310,28 @@ Additional constraints may be provided outside the scope of a single column usin ``` swift t.primaryKey(email.asc, name) - // PRIMARY KEY(email ASC, name) + // PRIMARY KEY("email" ASC, "name") ``` - `unique` adds a `UNIQUE` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports composite (multiple column) constraints. ``` swift t.unique(local, domain) - // UNIQUE(local, domain) + // UNIQUE("local", "domain") ``` - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` parameter under [Column Constraints](#column-constraints).) ``` swift t.check(balance >= 0) - // CHECK (balance >= 0.0) + // CHECK ("balance" >= 0.0) ``` - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, and both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions). ``` swift t.foreignKey(user_id, on: users[id], delete: .SetNull) - // FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL + // FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL ``` > _Note:_ Composite foreign keys are not supported at this time. If you add support, please [submit a pull request](https://github.com/stephencelis/SQLite.swift/fork). @@ -347,7 +347,7 @@ We can insert rows into a table by calling a [query’s](#queries) `insert` func ``` swift users.insert(email <- "alice@mac.com", name <- "Alice")? -// INSERT INTO users (email, name) VALUES ('alice@mac.com', 'Alice') +// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') ``` The `insert` function can return several different types that are useful in different contexts. @@ -378,8 +378,8 @@ The `insert` function can return several different types that are useful in diff users.insert(email <- "betty@mac.com") ) // BEGIN DEFERRED TRANSACTION; - // INSERT INTO users (email) VALUES ('alice@mac.com'); - // INSERT INTO users (email) VALUES ('betty@mac.com'); + // INSERT INTO "users" ("email") VALUES ('alice@mac.com'); + // INSERT INTO "users" ("email") VALUES ('betty@mac.com'); // COMMIT TRANSACTION; ``` @@ -400,7 +400,7 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s > > ``` swift > timestamps.insert()! -> // INSERT INTO timestamps DEFAULT VALUES +> // INSERT INTO "timestamps" DEFAULT VALUES > ``` @@ -410,7 +410,7 @@ SQLite.swift typically uses the `<-` operator to set values during [inserts](#in ``` swift views.update(count <- 0) -// UPDATE views SET count = 0 WHERE (id = 1) +// UPDATE "views" SET "count" = 0 WHERE ("id" = 1) ``` There are also a number of convenience setters that take the existing value into account using native Swift operators. @@ -419,7 +419,7 @@ For example, to atomically increment a column, we can use `++`: ``` swift views.update(count++) // equivalent to `views.update(count -> count + 1)` -// UPDATE views SET count = count + 1 WHERE (id = 1) +// UPDATE "views" SET "count" = "count" + 1 WHERE ("id" = 1) ``` To take an amount and “move” it via transaction, we can use `-=` and `+=`: @@ -431,8 +431,8 @@ db.transaction( betty.update(balance += amount) ) // BEGIN DEFERRED TRANSACTION; -// UPDATE users SET balance = balance - 100.0 WHERE (id = 1); -// UPDATE users SET balance = balance + 100.0 WHERE (id = 2); +// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1); +// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2); // COMMIT TRANSACTION; ``` @@ -477,7 +477,7 @@ for user in users { println("id: \(user[id]), email: \(user[email]), name: \(user[name])") // id: 1, email: alice@mac.com, name: Optional("Alice") } -// SELECT * FROM users +// SELECT * FROM "users" ``` `Expression` column values are _automatically unwrapped_ (we’ve made a promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. @@ -489,14 +489,14 @@ We can pluck the first row by calling the `first` computed property on [`Query`] ``` swift if let user = users.first { /* ... */ } // Row -// SELECT * FROM users LIMIT 1 +// SELECT * FROM "users" LIMIT 1 ``` To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea). ``` swift let all = Array(users) -// SELECT * FROM users +// SELECT * FROM "users" ``` @@ -505,9 +505,9 @@ let all = Array(users) [`Query`](#queries) structures have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. ``` swift -let query = users.select(email) // SELECT email FROM users - .filter(name != nil) // WHERE name IS NOT NULL - .order(email.desc, name) // ORDER BY email DESC, name +let query = users.select(email) // SELECT "email" FROM "users" + .filter(name != nil) // WHERE "name" IS NOT NULL + .order(email.desc, name) // ORDER BY "email" DESC, "name" .limit(5, offset: 1) // LIMIT 5 OFFSET 1 ``` @@ -518,7 +518,7 @@ By default, [`Query`](#queries) objects select every column of the result set (u ``` swift let query = users.select(id, email) -// SELECT id, email FROM users +// SELECT "id", "email" FROM "users" ``` @@ -537,7 +537,7 @@ We can join tables using a [query’s](#queries) `join` function. ``` swift users.join(posts, on: user_id == users[id]) -// SELECT * FROM users INNER JOIN posts ON (user_id = users.id) +// SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.Inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing). @@ -556,7 +556,7 @@ We can disambiguate by namespacing `id`. ``` swift let query = users.join(posts, on: user_id == users[id]) -// SELECT * FROM users INNER JOIN posts ON (user_id = users.id) +// SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` Namespacing is achieved by subscripting a [query](#queries) with a [column expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`). @@ -565,7 +565,7 @@ Namespacing is achieved by subscripting a [query](#queries) with a [column expre > > ``` swift > let query = users.select(users[*]) -> // SELECT users.* FROM users +> // SELECT "users".* FROM "users" > ``` @@ -577,8 +577,8 @@ Occasionally, we need to join a table to itself, in which case we must alias the let managers = users.alias("managers") let query = users.join(managers, on: managers[id] == users[manager_id]) -// SELECT * FROM users -// INNER JOIN users AS managers ON (managers.id = users.manager_id) +// SELECT * FROM "users" +// INNER JOIN "users" AS "managers" ON ("managers"."id" = "users"."manager_id") ``` If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. @@ -586,10 +586,10 @@ If query results can have ambiguous column names, row values should be accessed ``` swift let user = query.first! user[id] // fatal error: ambiguous column 'id' - // (please disambiguate: [users.id, managers.id]) + // (please disambiguate: ["users"."id", "managers"."id"]) -user[users[id]] // returns users.id -user[managers[id]] // returns managers.id +user[users[id]] // returns "users"."id" +user[managers[id]] // returns "managers"."id" ``` @@ -599,7 +599,7 @@ SQLite.swift filters rows using a [query’s](#queries) `filter` function with a ``` swift users.filter(id == 1) -// SELECT * FROM users WHERE (id = 1) +// SELECT * FROM "users" WHERE ("id" = 1) ``` You can build your own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). @@ -656,7 +656,7 @@ _E.g._, to return users sorted by `email`, then `name`, in ascending order: ``` swift users.order(email, name) -// SELECT * FROM users ORDER BY email, name +// SELECT * FROM "users" ORDER BY "email", "name" ``` The `order` function takes a list of [column expressions](#expressions). @@ -665,7 +665,7 @@ The `order` function takes a list of [column expressions](#expressions). ``` swift users.order(email.desc, name.asc) -// SELECT * FROM users ORDER BY email DESC, name ASC +// SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC ``` @@ -675,10 +675,10 @@ We can limit and skip returned rows using a [query’s](#queries) `limit` functi ``` swift users.limit(5) -// SELECT * FROM users LIMIT 5 +// SELECT * FROM "users" LIMIT 5 users.limit(5, offset: 5) -// SELECT * FROM users LIMIT 5 OFFSET 5 +// SELECT * FROM "users" LIMIT 5 OFFSET 5 ``` @@ -688,14 +688,14 @@ users.limit(5, offset: 5) ``` swift users.count -// SELECT count(*) FROM users +// SELECT count(*) FROM "users" ``` Filtered queries will appropriately filter aggregate values. ``` swift users.filter(name != nil).count -// SELECT count(*) FROM users WHERE name IS NOT NULL +// SELECT count(*) FROM "users" WHERE "name" IS NOT NULL ``` - `count` as a computed property (see examples above) returns the total number of rows matching the query. @@ -704,49 +704,49 @@ users.filter(name != nil).count ``` swift users.count(name) // -> Int - // SELECT count(name) FROM users + // SELECT count("name") FROM "users" ``` - `max` takes a comparable column expression and returns the largest value if any exists. ``` swift users.max(id) // -> Int? - // SELECT max(id) FROM users + // SELECT max("id") FROM "users" ``` - `min` takes a comparable column expression and returns the smallest value if any exists. ``` swift users.min(id) // -> Int? - // SELECT min(id) FROM users + // SELECT min("id") FROM "users" ``` - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists. ``` swift users.average(balance) // -> Double? - // SELECT avg(balance) FROM users + // SELECT avg("balance") FROM "users" ``` - `sum` takes a numeric column expression and returns the sum total of all rows if any exist. ``` swift users.sum(balance) // -> Double? - // SELECT sum(balance) FROM users + // SELECT sum("balance") FROM "users" ``` - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query. ``` swift users.total(balance) // -> Double - // SELECT total(balance) FROM users + // SELECT total("balance") FROM "users" ``` > _Note:_ Most of the above aggregate functions (except `max` and `min`) can be called with a `distinct` parameter to aggregate `DISTINCT` values only. > > ``` swift > users.count(distinct: name) -> // SELECT count(DISTINCT name) FROM users +> // SELECT count(DISTINCT "name") FROM "users" > ``` @@ -758,7 +758,7 @@ When an unscoped query calls `update`, it will update _every_ row in the table. ``` swift users.update(email <- "alice@me.com")? -// UPDATE users SET email = 'alice@me.com' +// UPDATE "users" SET "email" = 'alice@me.com' ``` Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#filtering-rows). @@ -766,7 +766,7 @@ Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) alice.update(email <- "alice@me.com")? -// UPDATE users SET email = 'alice@me.com' WHERE (id = 1) +// UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1) ``` Like [`insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts. @@ -802,7 +802,7 @@ When an unscoped query calls `delete`, it will delete _every_ row in the table. ``` swift users.delete()? -// DELETE FROM users +// DELETE FROM "users" ``` Be sure to scope `DELETE` statements beforehand using [the `filter` function](#filtering-rows). @@ -810,7 +810,7 @@ Be sure to scope `DELETE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) alice.delete()? -// DELETE FROM users WHERE (id = 1) +// DELETE FROM "users" WHERE ("id" = 1) ``` Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can return several different types that are useful in different contexts. @@ -863,7 +863,7 @@ We can rename a table by calling the `rename(table:to:)` function on a database ``` swift db.rename(users, to: "users_old") -// ALTER TABLE users RENAME TO users_old +// ALTER TABLE "users" RENAME TO "users_old" ``` @@ -873,7 +873,7 @@ We can add columns to a table by calling `alter` function on a database connecti ``` swift db.alter(table: users, add: suffix) -// ALTER TABLE users ADD COLUMN suffix TEXT +// ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` @@ -886,15 +886,15 @@ The `alter` function shares several of the same [`column` function parameters](# ``` swift let check = contains(["JR", "SR"], suffix) db.alter(table: users, add: suffix, check: check) - // ALTER TABLE users - // ADD COLUMN suffix TEXT CHECK (suffix IN ('JR', 'SR')) + // ALTER TABLE "users" + // ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) ``` - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). ``` swift db.alter(table: users, add: suffix, defaultValue: "SR") - // ALTER TABLE users ADD COLUMN suffix TEXT DEFAULT 'SR' + // ALTER TABLE "users" ADD COLUMN "suffix" TEXT DEFAULT 'SR' ``` > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), default values may not be expression structures (including `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). @@ -912,10 +912,10 @@ The `alter` function shares several of the same [`column` function parameters](# ``` swift db.alter(table: posts, add: user_id, references: users[id]) - // ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users(id) + // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users"("id") db.alter(table: posts, add: user_id, references: users) - // ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users + // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" // -- assumes "users" has a PRIMARY KEY ``` @@ -929,7 +929,7 @@ We can run [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.h ``` swift db.create(index: users, on: email) -// CREATE INDEX index_users_on_email ON users (email) +// CREATE INDEX index_users_on_email ON "users" ("email") ``` The index name is generated automatically based on the table and column names. @@ -940,14 +940,14 @@ The `create(index:)` function has a couple default parameters we can override. ``` swift db.create(index: users, on: email, unique: true) - // CREATE UNIQUE INDEX index_users_on_email ON users (email) + // CREATE UNIQUE INDEX index_users_on_email ON "users" ("email") ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift db.create(index: users, on: email, ifNotExists: true) - // CREATE INDEX IF NOT EXISTS index_users_on_email ON users (email) + // CREATE INDEX IF NOT EXISTS index_users_on_email ON "users" ("email") ``` @@ -974,14 +974,14 @@ We can run [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) ``` swift db.drop(table: users) -// DROP TABLE users +// DROP TABLE "users" ``` The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. ``` swift db.drop(table: users, ifExists: true) -// DROP TABLE IF EXISTS users +// DROP TABLE IF EXISTS "users" ``` @@ -1073,9 +1073,9 @@ let published_at = Expression("published_at") let published = posts.filter(published_at <= Date()) // extension where Datatype == String: -// SELECT * FROM posts WHERE published_at <= '2014-11-18 12:45:30' +// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18 12:45:30' // extension where Datatype == Int: -// SELECT * FROM posts WHERE published_at <= 1416314730 +// SELECT * FROM "posts" WHERE "published_at" <= 1416314730 ``` @@ -1128,7 +1128,7 @@ Swift does _not_ currently support generic subscripting, which means we cannot, ``` swift let avatar = Expression("avatar") users[avatar] // fails to compile - users.namespace(avatar) // Expression("users.avatar") + users.namespace(avatar) // "users"."avatar" ``` 2. **Access column data**. Use the `get` function, instead: @@ -1200,7 +1200,7 @@ Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) h > _Note:_ SQLite.swift aliases the `??` operator to the `ifnull` function. > > ``` swift -> name ?? email // ifnull(name, email) +> name ?? email // ifnull("name", "email") > ``` diff --git a/README.md b/README.md index 922a0326..40085103 100644 --- a/README.md +++ b/README.md @@ -45,18 +45,18 @@ db.create(table: users) { t in t.column(email, unique: true) } // CREATE TABLE "users" ( -// id INTEGER PRIMARY KEY NOT NULL, -// name TEXT, -// email TEXT NOT NULL UNIQUE +// "id" INTEGER PRIMARY KEY NOT NULL, +// "name" TEXT, +// "email" TEXT NOT NULL UNIQUE // ) var alice: Query? if let insertedID = users.insert(name <- "Alice", email <- "alice@mac.com") { - println("inserted id: \(insertedID)") - // inserted id: 1 - alice = users.filter(id == insertedID) + println("inserted id: \(insertedID)") + // inserted id: 1 + alice = users.filter(id == insertedID) } -// INSERT INTO "users" (name, email) VALUES ('Alice', 'alice@mac.com') +// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') for user in users { println("id: \(user[id]), name: \(user[name]), email: \(user[email])" @@ -65,10 +65,10 @@ for user in users { // SELECT * FROM "users" alice?.update(email <- replace(email, "mac.com", "me.com"))? -// UPDATE "users" SET email = replace(email, "mac.com", "me.com") WHERE (id = 1) +// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') WHERE ("id" = 1) alice?.delete()? -// DELETE FROM "users" WHERE (id = 1) +// DELETE FROM "users" WHERE ("id" = 1) users.count // SELECT count(*) FROM "users" diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift index 65e539e0..039599ae 100644 --- a/SQLite Common Tests/ExpressionTests.swift +++ b/SQLite Common Tests/ExpressionTests.swift @@ -445,22 +445,22 @@ class ExpressionTests: XCTestCase { let admin2 = Expression("admin") func test_countFunction_withExpression_buildsCountExpression() { - ExpectExecutionMatches("count(id)", count(id)) - ExpectExecutionMatches("count(age)", count(age)) - ExpectExecutionMatches("count(email)", count(email)) - ExpectExecutionMatches("count(email)", count(email2)) - ExpectExecutionMatches("count(salary)", count(salary)) - ExpectExecutionMatches("count(salary)", count(salary2)) - ExpectExecutionMatches("count(admin)", count(admin)) - ExpectExecutionMatches("count(admin)", count(admin2)) - ExpectExecutionMatches("count(DISTINCT id)", count(distinct: id)) - ExpectExecutionMatches("count(DISTINCT age)", count(distinct: age)) - ExpectExecutionMatches("count(DISTINCT email)", count(distinct: email)) - ExpectExecutionMatches("count(DISTINCT email)", count(distinct: email2)) - ExpectExecutionMatches("count(DISTINCT salary)", count(distinct: salary)) - ExpectExecutionMatches("count(DISTINCT salary)", count(distinct: salary2)) - ExpectExecutionMatches("count(DISTINCT admin)", count(distinct: admin)) - ExpectExecutionMatches("count(DISTINCT admin)", count(distinct: admin2)) + ExpectExecutionMatches("count(\"id\")", count(id)) + ExpectExecutionMatches("count(\"age\")", count(age)) + ExpectExecutionMatches("count(\"email\")", count(email)) + ExpectExecutionMatches("count(\"email\")", count(email2)) + ExpectExecutionMatches("count(\"salary\")", count(salary)) + ExpectExecutionMatches("count(\"salary\")", count(salary2)) + ExpectExecutionMatches("count(\"admin\")", count(admin)) + ExpectExecutionMatches("count(\"admin\")", count(admin2)) + ExpectExecutionMatches("count(DISTINCT \"id\")", count(distinct: id)) + ExpectExecutionMatches("count(DISTINCT \"age\")", count(distinct: age)) + ExpectExecutionMatches("count(DISTINCT \"email\")", count(distinct: email)) + ExpectExecutionMatches("count(DISTINCT \"email\")", count(distinct: email2)) + ExpectExecutionMatches("count(DISTINCT \"salary\")", count(distinct: salary)) + ExpectExecutionMatches("count(DISTINCT \"salary\")", count(distinct: salary2)) + ExpectExecutionMatches("count(DISTINCT \"admin\")", count(distinct: admin)) + ExpectExecutionMatches("count(DISTINCT \"admin\")", count(distinct: admin2)) } func test_countFunction_withStar_buildsCountExpression() { @@ -468,63 +468,63 @@ class ExpressionTests: XCTestCase { } func test_maxFunction_withExpression_buildsMaxExpression() { - ExpectExecutionMatches("max(id)", max(id)) - ExpectExecutionMatches("max(age)", max(age)) - ExpectExecutionMatches("max(email)", max(email)) - ExpectExecutionMatches("max(email)", max(email2)) - ExpectExecutionMatches("max(salary)", max(salary)) - ExpectExecutionMatches("max(salary)", max(salary2)) + ExpectExecutionMatches("max(\"id\")", max(id)) + ExpectExecutionMatches("max(\"age\")", max(age)) + ExpectExecutionMatches("max(\"email\")", max(email)) + ExpectExecutionMatches("max(\"email\")", max(email2)) + ExpectExecutionMatches("max(\"salary\")", max(salary)) + ExpectExecutionMatches("max(\"salary\")", max(salary2)) } func test_minFunction_withExpression_buildsMinExpression() { - ExpectExecutionMatches("min(id)", min(id)) - ExpectExecutionMatches("min(age)", min(age)) - ExpectExecutionMatches("min(email)", min(email)) - ExpectExecutionMatches("min(email)", min(email2)) - ExpectExecutionMatches("min(salary)", min(salary)) - ExpectExecutionMatches("min(salary)", min(salary2)) + ExpectExecutionMatches("min(\"id\")", min(id)) + ExpectExecutionMatches("min(\"age\")", min(age)) + ExpectExecutionMatches("min(\"email\")", min(email)) + ExpectExecutionMatches("min(\"email\")", min(email2)) + ExpectExecutionMatches("min(\"salary\")", min(salary)) + ExpectExecutionMatches("min(\"salary\")", min(salary2)) } func test_averageFunction_withExpression_buildsAverageExpression() { - ExpectExecutionMatches("avg(id)", average(id)) - ExpectExecutionMatches("avg(age)", average(age)) - ExpectExecutionMatches("avg(salary)", average(salary)) - ExpectExecutionMatches("avg(salary)", average(salary2)) - ExpectExecutionMatches("avg(DISTINCT id)", average(distinct: id)) - ExpectExecutionMatches("avg(DISTINCT age)", average(distinct: age)) - ExpectExecutionMatches("avg(DISTINCT salary)", average(distinct: salary)) - ExpectExecutionMatches("avg(DISTINCT salary)", average(distinct: salary2)) + ExpectExecutionMatches("avg(\"id\")", average(id)) + ExpectExecutionMatches("avg(\"age\")", average(age)) + ExpectExecutionMatches("avg(\"salary\")", average(salary)) + ExpectExecutionMatches("avg(\"salary\")", average(salary2)) + ExpectExecutionMatches("avg(DISTINCT \"id\")", average(distinct: id)) + ExpectExecutionMatches("avg(DISTINCT \"age\")", average(distinct: age)) + ExpectExecutionMatches("avg(DISTINCT \"salary\")", average(distinct: salary)) + ExpectExecutionMatches("avg(DISTINCT \"salary\")", average(distinct: salary2)) } func test_sumFunction_withExpression_buildsSumExpression() { - ExpectExecutionMatches("sum(id)", sum(id)) - ExpectExecutionMatches("sum(age)", sum(age)) - ExpectExecutionMatches("sum(salary)", sum(salary)) - ExpectExecutionMatches("sum(salary)", sum(salary2)) - ExpectExecutionMatches("sum(DISTINCT id)", sum(distinct: id)) - ExpectExecutionMatches("sum(DISTINCT age)", sum(distinct: age)) - ExpectExecutionMatches("sum(DISTINCT salary)", sum(distinct: salary)) - ExpectExecutionMatches("sum(DISTINCT salary)", sum(distinct: salary2)) + ExpectExecutionMatches("sum(\"id\")", sum(id)) + ExpectExecutionMatches("sum(\"age\")", sum(age)) + ExpectExecutionMatches("sum(\"salary\")", sum(salary)) + ExpectExecutionMatches("sum(\"salary\")", sum(salary2)) + ExpectExecutionMatches("sum(DISTINCT \"id\")", sum(distinct: id)) + ExpectExecutionMatches("sum(DISTINCT \"age\")", sum(distinct: age)) + ExpectExecutionMatches("sum(DISTINCT \"salary\")", sum(distinct: salary)) + ExpectExecutionMatches("sum(DISTINCT \"salary\")", sum(distinct: salary2)) } func test_totalFunction_withExpression_buildsTotalExpression() { - ExpectExecutionMatches("total(id)", total(id)) - ExpectExecutionMatches("total(age)", total(age)) - ExpectExecutionMatches("total(salary)", total(salary)) - ExpectExecutionMatches("total(salary)", total(salary2)) - ExpectExecutionMatches("total(DISTINCT id)", total(distinct: id)) - ExpectExecutionMatches("total(DISTINCT age)", total(distinct: age)) - ExpectExecutionMatches("total(DISTINCT salary)", total(distinct: salary)) - ExpectExecutionMatches("total(DISTINCT salary)", total(distinct: salary2)) + ExpectExecutionMatches("total(\"id\")", total(id)) + ExpectExecutionMatches("total(\"age\")", total(age)) + ExpectExecutionMatches("total(\"salary\")", total(salary)) + ExpectExecutionMatches("total(\"salary\")", total(salary2)) + ExpectExecutionMatches("total(DISTINCT \"id\")", total(distinct: id)) + ExpectExecutionMatches("total(DISTINCT \"age\")", total(distinct: age)) + ExpectExecutionMatches("total(DISTINCT \"salary\")", total(distinct: salary)) + ExpectExecutionMatches("total(DISTINCT \"salary\")", total(distinct: salary2)) } func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - ExpectExecutionMatches("(id IN (1, 2, 3))", contains([1, 2, 3], id)) - ExpectExecutionMatches("(age IN (20, 30, 40))", contains([20, 30, 40], age)) + ExpectExecutionMatches("(\"id\" IN (1, 2, 3))", contains([1, 2, 3], id)) + ExpectExecutionMatches("(\"age\" IN (20, 30, 40))", contains([20, 30, 40], age)) } func test_plusEquals_withStringExpression_buildsSetter() { - let SQL = "UPDATE \"users\" SET email = (email || email)" + let SQL = "UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")" ExpectExecution(db, SQL, users.update(email += email)) ExpectExecution(db, SQL, users.update(email += email2)) ExpectExecution(db, SQL, users.update(email2 += email)) @@ -532,167 +532,167 @@ class ExpressionTests: XCTestCase { } func test_plusEquals_withStringValue_buildsSetter() { - let SQL = "UPDATE \"users\" SET email = (email || '.com')" + let SQL = "UPDATE \"users\" SET \"email\" = (\"email\" || '.com')" ExpectExecution(db, SQL, users.update(email += ".com")) ExpectExecution(db, SQL, users.update(email2 += ".com")) } func test_plusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age + age)", users.update(age += age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age + id)", users.update(age += id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id + age)", users.update(id += age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id + id)", users.update(id += id)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary += salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary += salary2)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary2 += salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + salary)", users.update(salary2 += salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", users.update(age += age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + \"id\")", users.update(age += id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + \"age\")", users.update(id += age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + \"id\")", users.update(id += id)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary += salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary += salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary2 += salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary2 += salary2)) } func test_plusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET id = (id + 1)", users.update(id += 1)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age + 1)", users.update(age += 1)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + 100.0)", users.update(salary += 100)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary + 100.0)", users.update(salary2 += 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id += 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age += 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", users.update(salary += 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", users.update(salary2 += 100)) } func test_minusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age - age)", users.update(age -= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age - id)", users.update(age -= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id - age)", users.update(id -= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id - id)", users.update(id -= id)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary -= salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary -= salary2)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary2 -= salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - salary)", users.update(salary2 -= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", users.update(age -= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - \"id\")", users.update(age -= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - \"age\")", users.update(id -= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - \"id\")", users.update(id -= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary -= salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary -= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary2 -= salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary2 -= salary2)) } func test_minusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET id = (id - 1)", users.update(id -= 1)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age - 1)", users.update(age -= 1)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - 100.0)", users.update(salary -= 100)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary - 100.0)", users.update(salary2 -= 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id -= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age -= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", users.update(salary -= 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", users.update(salary2 -= 100)) } func test_timesEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age * age)", users.update(age *= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age * id)", users.update(age *= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id * age)", users.update(id *= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id * id)", users.update(id *= id)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary *= salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary *= salary2)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary2 *= salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * salary)", users.update(salary2 *= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", users.update(age *= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * \"id\")", users.update(age *= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * \"age\")", users.update(id *= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * \"id\")", users.update(id *= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary *= salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary *= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary2 *= salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary2 *= salary2)) } func test_timesEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET id = (id * 1)", users.update(id *= 1)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age * 1)", users.update(age *= 1)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * 100.0)", users.update(salary *= 100)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary * 100.0)", users.update(salary2 *= 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * 1)", users.update(id *= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * 1)", users.update(age *= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", users.update(salary *= 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", users.update(salary2 *= 100)) } func test_divideEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age / age)", users.update(age /= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age / id)", users.update(age /= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id / age)", users.update(id /= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id / id)", users.update(id /= id)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary /= salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary /= salary2)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary2 /= salary)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / salary)", users.update(salary2 /= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", users.update(age /= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / \"id\")", users.update(age /= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / \"age\")", users.update(id /= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / \"id\")", users.update(id /= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary /= salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary /= salary2)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary2 /= salary)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary2 /= salary2)) } func test_divideEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET id = (id / 1)", users.update(id /= 1)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age / 1)", users.update(age /= 1)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / 100.0)", users.update(salary /= 100)) - ExpectExecution(db, "UPDATE \"users\" SET salary = (salary / 100.0)", users.update(salary2 /= 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / 1)", users.update(id /= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / 1)", users.update(age /= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", users.update(salary /= 100)) + ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", users.update(salary2 /= 100)) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age % age)", users.update(age %= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age % id)", users.update(age %= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id % age)", users.update(id %= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id % id)", users.update(id %= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", users.update(age %= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % \"id\")", users.update(age %= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % \"age\")", users.update(id %= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % \"id\")", users.update(id %= id)) } func test_moduloEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age % 10)", users.update(age %= 10)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id % 10)", users.update(id %= 10)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % 10)", users.update(age %= 10)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % 10)", users.update(id %= 10)) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age >> age)", users.update(age >>= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age >> id)", users.update(age >>= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id >> age)", users.update(id >>= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id >> id)", users.update(id >>= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", users.update(age >>= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> \"id\")", users.update(age >>= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> \"age\")", users.update(id >>= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> \"id\")", users.update(id >>= id)) } func test_rightShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age >> 1)", users.update(age >>= 1)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id >> 1)", users.update(id >>= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", users.update(age >>= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> 1)", users.update(id >>= 1)) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age << age)", users.update(age <<= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age << id)", users.update(age <<= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id << age)", users.update(id <<= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id << id)", users.update(id <<= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", users.update(age <<= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << \"id\")", users.update(age <<= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << \"age\")", users.update(id <<= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << \"id\")", users.update(id <<= id)) } func test_leftShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age << 1)", users.update(age <<= 1)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id << 1)", users.update(id <<= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << 1)", users.update(age <<= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << 1)", users.update(id <<= 1)) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age & age)", users.update(age &= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age & id)", users.update(age &= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id & age)", users.update(id &= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id & id)", users.update(id &= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", users.update(age &= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & \"id\")", users.update(age &= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & \"age\")", users.update(id &= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & \"id\")", users.update(id &= id)) } func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age & 1)", users.update(age &= 1)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id & 1)", users.update(id &= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & 1)", users.update(age &= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & 1)", users.update(id &= 1)) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age | age)", users.update(age |= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age | id)", users.update(age |= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id | age)", users.update(id |= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id | id)", users.update(id |= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", users.update(age |= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | \"id\")", users.update(age |= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | \"age\")", users.update(id |= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | \"id\")", users.update(id |= id)) } func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age | 1)", users.update(age |= 1)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id | 1)", users.update(id |= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | 1)", users.update(age |= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | 1)", users.update(id |= 1)) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (~((age & age)) & (age | age))", users.update(age ^= age)) - ExpectExecution(db, "UPDATE \"users\" SET age = (~((age & id)) & (age | id))", users.update(age ^= id)) - ExpectExecution(db, "UPDATE \"users\" SET id = (~((id & age)) & (id | age))", users.update(id ^= age)) - ExpectExecution(db, "UPDATE \"users\" SET id = (~((id & id)) & (id | id))", users.update(id ^= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", users.update(age ^= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & \"id\")) & (\"age\" | \"id\"))", users.update(age ^= id)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & \"age\")) & (\"id\" | \"age\"))", users.update(id ^= age)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & \"id\")) & (\"id\" | \"id\"))", users.update(id ^= id)) } func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (~((age & 1)) & (age | 1))", users.update(age ^= 1)) - ExpectExecution(db, "UPDATE \"users\" SET id = (~((id & 1)) & (id | 1))", users.update(id ^= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", users.update(age ^= 1)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & 1)) & (\"id\" | 1))", users.update(id ^= 1)) } func test_postfixPlus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age + 1)", users.update(age++)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age + 1)", users.update(age++)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id + 1)", users.update(id++)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id + 1)", users.update(id++)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age++)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age++)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id++)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id++)) } func test_postfixMinus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET age = (age - 1)", users.update(age--)) - ExpectExecution(db, "UPDATE \"users\" SET age = (age - 1)", users.update(age--)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id - 1)", users.update(id--)) - ExpectExecution(db, "UPDATE \"users\" SET id = (id - 1)", users.update(id--)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age--)) + ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age--)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id--)) + ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id--)) } func test_precedencePreserved() { diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift index a258fae4..e39e198f 100644 --- a/SQLite Common Tests/QueryTests.swift +++ b/SQLite Common Tests/QueryTests.swift @@ -22,14 +22,14 @@ class QueryTests: XCTestCase { func test_select_withExpression_compilesSelectClause() { let query = users.select(email) - let SQL = "SELECT email FROM \"users\"" + let SQL = "SELECT \"email\" FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_select_withVariadicExpressions_compilesSelectClause() { let query = users.select(email, count(*)) - let SQL = "SELECT email, count(*) FROM \"users\"" + let SQL = "SELECT \"email\", count(*) FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -43,7 +43,7 @@ class QueryTests: XCTestCase { func test_selectDistinct_withExpression_compilesSelectClause() { let query = users.select(distinct: age) - let SQL = "SELECT DISTINCT age FROM \"users\"" + let SQL = "SELECT DISTINCT \"age\" FROM \"users\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -60,7 +60,7 @@ class QueryTests: XCTestCase { let query = users.join(managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".id = \"users\".manager_id)" + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -70,7 +70,7 @@ class QueryTests: XCTestCase { let query = users.join(.LeftOuter, managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM \"users\" " + - "LEFT OUTER JOIN \"users\" AS \"managers\" ON (\"managers\".id = \"users\".manager_id)" + "LEFT OUTER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -82,8 +82,8 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM \"users\" " + "INNER JOIN \"users\" AS \"managers\" " + - "ON ((\"managers\".id = \"users\".manager_id) " + - "AND \"managers\".admin)" + "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") " + + "AND \"managers\".\"admin\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -96,8 +96,8 @@ class QueryTests: XCTestCase { .join(managed, on: managed[manager_id] == users[id]) let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".id = \"users\".manager_id) " + - "INNER JOIN \"users\" AS \"managed\" ON (\"managed\".manager_id = \"users\".id)" + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " + + "INNER JOIN \"users\" AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} } } @@ -118,7 +118,7 @@ class QueryTests: XCTestCase { func test_filter_compilesWhereClause() { let query = users.filter(admin == true) - let SQL = "SELECT * FROM \"users\" WHERE (admin = 1)" + let SQL = "SELECT * FROM \"users\" WHERE (\"admin\" = 1)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -128,57 +128,57 @@ class QueryTests: XCTestCase { .filter(age >= 21) let SQL = "SELECT * FROM \"users\" " + - "WHERE ((email = 'alice@example.com') " + - "AND (age >= 21))" + "WHERE ((\"email\" = 'alice@example.com') " + + "AND (\"age\" >= 21))" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withSingleExpressionName_compilesGroupClause() { let query = users.group(age) - let SQL = "SELECT * FROM \"users\" GROUP BY age" + let SQL = "SELECT * FROM \"users\" GROUP BY \"age\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withVariadicExpressionNames_compilesGroupClause() { let query = users.group(age, admin) - let SQL = "SELECT * FROM \"users\" GROUP BY age, admin" + let SQL = "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { let query = users.group(age, having: age >= 30) - let SQL = "SELECT * FROM \"users\" GROUP BY age HAVING (age >= 30)" + let SQL = "SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { let query = users.group([age, admin], having: age >= 30) - let SQL = "SELECT * FROM \"users\" GROUP BY age, admin HAVING (age >= 30)" + let SQL = "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_order_withSingleExpressionName_compilesOrderClause() { let query = users.order(age) - let SQL = "SELECT * FROM \"users\" ORDER BY age" + let SQL = "SELECT * FROM \"users\" ORDER BY \"age\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_order_withVariadicExpressionNames_compilesOrderClause() { let query = users.order(age, email) - let SQL = "SELECT * FROM \"users\" ORDER BY age, email" + let SQL = "SELECT * FROM \"users\" ORDER BY \"age\", \"email\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } func test_order_withExpressionAndSortDirection_compilesOrderClause() { let query = users.order(age.desc, email.asc) - let SQL = "SELECT * FROM \"users\" ORDER BY age DESC, email ASC" + let SQL = "SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -223,19 +223,19 @@ class QueryTests: XCTestCase { } func test_subscript_withExpression_returnsNamespacedExpression() { - ExpectExecution(db, "SELECT \"users\".admin FROM \"users\"", users.select(users[admin])) - ExpectExecution(db, "SELECT \"users\".salary FROM \"users\"", users.select(users[salary])) - ExpectExecution(db, "SELECT \"users\".age FROM \"users\"", users.select(users[age])) - ExpectExecution(db, "SELECT \"users\".email FROM \"users\"", users.select(users[email])) + ExpectExecution(db, "SELECT \"users\".\"admin\" FROM \"users\"", users.select(users[admin])) + ExpectExecution(db, "SELECT \"users\".\"salary\" FROM \"users\"", users.select(users[salary])) + ExpectExecution(db, "SELECT \"users\".\"age\" FROM \"users\"", users.select(users[age])) + ExpectExecution(db, "SELECT \"users\".\"email\" FROM \"users\"", users.select(users[email])) ExpectExecution(db, "SELECT \"users\".* FROM \"users\"", users.select(users[*])) } func test_subscript_withAliasAndExpression_returnsAliasedExpression() { let managers = users.alias("managers") - ExpectExecution(db, "SELECT \"managers\".admin FROM \"users\" AS \"managers\"", managers.select(managers[admin])) - ExpectExecution(db, "SELECT \"managers\".salary FROM \"users\" AS \"managers\"", managers.select(managers[salary])) - ExpectExecution(db, "SELECT \"managers\".age FROM \"users\" AS \"managers\"", managers.select(managers[age])) - ExpectExecution(db, "SELECT \"managers\".email FROM \"users\" AS \"managers\"", managers.select(managers[email])) + ExpectExecution(db, "SELECT \"managers\".\"admin\" FROM \"users\" AS \"managers\"", managers.select(managers[admin])) + ExpectExecution(db, "SELECT \"managers\".\"salary\" FROM \"users\" AS \"managers\"", managers.select(managers[salary])) + ExpectExecution(db, "SELECT \"managers\".\"age\" FROM \"users\" AS \"managers\"", managers.select(managers[age])) + ExpectExecution(db, "SELECT \"managers\".\"email\" FROM \"users\" AS \"managers\"", managers.select(managers[email])) ExpectExecution(db, "SELECT \"managers\".* FROM \"users\" AS \"managers\"", managers.select(managers[*])) } @@ -252,12 +252,12 @@ class QueryTests: XCTestCase { .order(users[email].desc) .limit(1, offset: 2) - let SQL = "SELECT \"users\".email, count(\"users\".email) FROM \"users\" " + + let SQL = "SELECT \"users\".\"email\", count(\"users\".\"email\") FROM \"users\" " + "LEFT OUTER JOIN \"users\" AS \"managers\" " + - "ON ((\"managers\".id = \"users\".manager_id) AND (\"managers\".admin = 1)) " + - "WHERE \"users\".age BETWEEN 21 AND 32 " + - "GROUP BY \"users\".age HAVING (count(\"users\".email) > 1) " + - "ORDER BY \"users\".email DESC " + + "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") AND (\"managers\".\"admin\" = 1)) " + + "WHERE \"users\".\"age\" BETWEEN 21 AND 32 " + + "GROUP BY \"users\".\"age\" HAVING (count(\"users\".\"email\") > 1) " + + "ORDER BY \"users\".\"email\" DESC " + "LIMIT 1 " + "OFFSET 2" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } @@ -283,7 +283,7 @@ class QueryTests: XCTestCase { } func test_insert_insertsRows() { - let SQL = "INSERT INTO \"users\" (email, age) VALUES ('alice@example.com', 30)" + let SQL = "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in XCTAssertEqual(1, self.users.insert(self.email <- "alice@example.com", self.age <- 30).ID!) @@ -293,15 +293,15 @@ class QueryTests: XCTestCase { } func test_insert_withQuery_insertsRows() { - db.execute("CREATE TABLE \"emails\" (email TEXT)") + db.execute("CREATE TABLE \"emails\" (\"email\" TEXT)") let emails = db["emails"] let admins = users.select(email).filter(admin == true) - ExpectExecution(db, "INSERT INTO \"emails\" SELECT email FROM \"users\" WHERE (admin = 1)", emails.insert(admins)) + ExpectExecution(db, "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)", emails.insert(admins)) } func test_insert_insertsDefaultRow() { - db.execute("CREATE TABLE \"timestamps\" (id INTEGER PRIMARY KEY, timestamp TEXT DEFAULT CURRENT_DATETIME)") + db.execute("CREATE TABLE \"timestamps\" (\"id\" INTEGER PRIMARY KEY, \"timestamp\" TEXT DEFAULT CURRENT_DATETIME)") let table = db["timestamps"] ExpectExecutions(db, ["INSERT INTO \"timestamps\" DEFAULT VALUES": 1]) { _ in @@ -310,7 +310,7 @@ class QueryTests: XCTestCase { } func test_replace_replaceRows() { - let SQL = "INSERT OR REPLACE INTO \"users\" (email, age) VALUES ('alice@example.com', 30)" + let SQL = "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in XCTAssertEqual(1, self.users.replace(self.email <- "alice@example.com", self.age <- 30).ID!) diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index dcd97682..9f6f1daf 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -19,25 +19,25 @@ class SchemaTests: XCTestCase { } func test_createTable_createsTable() { - ExpectExecution(db, "CREATE TABLE \"users\" (age INTEGER)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"age\" INTEGER)", db.create(table: users) { t in t.column(age) } ) } func test_createTable_temporary_createsTemporaryTable() { - ExpectExecution(db, "CREATE TEMPORARY TABLE \"users\" (age INTEGER)", + ExpectExecution(db, "CREATE TEMPORARY TABLE \"users\" (\"age\" INTEGER)", db.create(table: users, temporary: true) { t in t.column(age) } ) } func test_createTable_ifNotExists_createsTableIfNotExists() { - ExpectExecution(db, "CREATE TABLE IF NOT EXISTS \"users\" (age INTEGER)", + ExpectExecution(db, "CREATE TABLE IF NOT EXISTS \"users\" (\"age\" INTEGER)", db.create(table: users, ifNotExists: true) { t in t.column(age) } ) } func test_createTable_column_buildsColumnDefinition() { - ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)", db.create(table: users) { t in t.column(email) } @@ -45,7 +45,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (id INTEGER PRIMARY KEY NOT NULL)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)", db.create(table: users) { t in t.column(id, primaryKey: true) } @@ -53,7 +53,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withNullFalse_buildsNotNullClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)", db.create(table: users) { t in t.column(email) } @@ -61,7 +61,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withUnique_buildsUniqueClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL UNIQUE)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL UNIQUE)", db.create(table: users) { t in t.column(email, unique: true) } @@ -69,7 +69,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withCheck_buildsCheckClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (admin BOOLEAN NOT NULL CHECK (admin IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" BOOLEAN NOT NULL CHECK (\"admin\" IN (0, 1)))", db.create(table: users) { t in t.column(admin, check: contains([false, true], admin)) } @@ -77,7 +77,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withDefaultValue_buildsDefaultClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (salary REAL NOT NULL DEFAULT 0.0)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"salary\" REAL NOT NULL DEFAULT 0.0)", db.create(table: users) { t in t.column(salary, defaultValue: 0.0) } @@ -85,7 +85,7 @@ class SchemaTests: XCTestCase { } func test_createTable_stringColumn_collation_buildsCollateClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL COLLATE NOCASE)", + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)", db.create(table: users) { t in t.column(email, collate: .NoCase) } @@ -95,8 +95,8 @@ class SchemaTests: XCTestCase { func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { let users = self.users ExpectExecution(db, "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY NOT NULL, " + - "manager_id INTEGER REFERENCES \"users\"(id)" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER REFERENCES \"users\"(\"id\")" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -108,8 +108,8 @@ class SchemaTests: XCTestCase { func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { let users = self.users ExpectExecution(db, "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY NOT NULL, " + - "manager_id INTEGER REFERENCES \"users\"" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER REFERENCES \"users\"" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -120,7 +120,7 @@ class SchemaTests: XCTestCase { func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL, PRIMARY KEY(email))", + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, PRIMARY KEY(\"email\"))", db.create(table: users) { t in t.column(email) t.primaryKey(email) @@ -130,7 +130,9 @@ class SchemaTests: XCTestCase { func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (id INTEGER NOT NULL, email TEXT NOT NULL, PRIMARY KEY(id, email))", + ExpectExecution(db, "CREATE TABLE \"users\" (" + + "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, PRIMARY KEY(\"id\", \"email\")" + + ")", db.create(table: users) { t in t.column(id) t.column(email) @@ -141,7 +143,7 @@ class SchemaTests: XCTestCase { func test_createTable_unique_buildsUniqueTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (email TEXT NOT NULL, UNIQUE(email))", + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, UNIQUE(\"email\"))", db.create(table: users) { t in t.column(email) t.unique(email) @@ -151,7 +153,9 @@ class SchemaTests: XCTestCase { func test_createTable_unique_buildsCompositeUniqueTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (id INTEGER NOT NULL, email TEXT NOT NULL, UNIQUE(id, email))", + ExpectExecution(db, "CREATE TABLE \"users\" (" + + "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, UNIQUE(\"id\", \"email\")" + + ")", db.create(table: users) { t in t.column(id) t.column(email) @@ -162,7 +166,7 @@ class SchemaTests: XCTestCase { func test_createTable_check_buildsCheckTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (admin BOOLEAN NOT NULL, CHECK (admin IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" BOOLEAN NOT NULL, CHECK (\"admin\" IN (0, 1)))", db.create(table: users) { t in t.column(admin) t.check(contains([false, true], admin)) @@ -174,9 +178,9 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY NOT NULL, " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES \"users\"(id)" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\")" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -190,9 +194,9 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY NOT NULL, " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES \"users\"" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -206,9 +210,9 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY NOT NULL, " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES \"users\" ON UPDATE CASCADE" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\" ON UPDATE CASCADE" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -222,9 +226,9 @@ class SchemaTests: XCTestCase { let users = self.users ExpectExecution(db, "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY NOT NULL, " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES \"users\" ON DELETE CASCADE" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\" ON DELETE CASCADE" + ")", db.create(table: users) { t in t.column(id, primaryKey: true) @@ -237,11 +241,11 @@ class SchemaTests: XCTestCase { func test_createTable_withQuery_createsTableWithQuery() { CreateUsersTable(db) ExpectExecution(db, - "CREATE TABLE \"emails\" AS SELECT email FROM \"users\"", + "CREATE TABLE \"emails\" AS SELECT \"email\" FROM \"users\"", db.create(table: db["emails"], from: users.select(email)) ) ExpectExecution(db, - "CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT email FROM \"users\"", + "CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"", db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) ) } @@ -255,7 +259,7 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN bonus REAL NOT NULL DEFAULT 0.0", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL NOT NULL DEFAULT 0.0", db.alter(table: users, add: column, defaultValue: 0) ) } @@ -264,7 +268,7 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN bonus REAL", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL", db.alter(table: users, add: column) ) } @@ -273,7 +277,7 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN bonus REAL DEFAULT 0.0", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL DEFAULT 0.0", db.alter(table: users, add: column, defaultValue: 0) ) } @@ -282,7 +286,7 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) let column = Expression("parent_id") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN parent_id INTEGER REFERENCES \"users\"(id)", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")", db.alter(table: users, add: column, references: users[id]) ) } @@ -296,7 +300,7 @@ class SchemaTests: XCTestCase { func test_index_executesIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX index_users_on_email ON \"users\" (email)", + "CREATE INDEX index_users_on_email ON \"users\" (\"email\")", db.create(index: users, on: email) ) } @@ -304,7 +308,7 @@ class SchemaTests: XCTestCase { func test_index_withUniqueness_executesUniqueIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE UNIQUE INDEX index_users_on_email ON \"users\" (email)", + "CREATE UNIQUE INDEX index_users_on_email ON \"users\" (\"email\")", db.create(index: users, unique: true, on: email) ) } @@ -312,7 +316,7 @@ class SchemaTests: XCTestCase { func test_index_ifNotExists_executesIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX IF NOT EXISTS index_users_on_email ON \"users\" (email)", + "CREATE INDEX IF NOT EXISTS index_users_on_email ON \"users\" (\"email\")", db.create(index: users, ifNotExists: true, on: email) ) } @@ -320,7 +324,7 @@ class SchemaTests: XCTestCase { func test_index_withMultipleColumns_executesCompoundIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX index_users_on_age_DESC_email ON \"users\" (age DESC, email)", + "CREATE INDEX index_users_on_age_DESC_email ON \"users\" (\"age\" DESC, \"email\")", db.create(index: users, on: age.desc, email) ) } @@ -346,11 +350,11 @@ class SchemaTests: XCTestCase { func test_createView_withQuery_createsViewWithQuery() { CreateUsersTable(db) ExpectExecution(db, - "CREATE VIEW \"emails\" AS SELECT email FROM \"users\"", + "CREATE VIEW \"emails\" AS SELECT \"email\" FROM \"users\"", db.create(view: db["emails"], from: users.select(email)) ) ExpectExecution(db, - "CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT email FROM \"users\"", + "CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"", db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) ) } diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index fcbbde11..0758b6bb 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -26,12 +26,16 @@ public struct Expression { public let SQL: String public let bindings: [Binding?] - public init(_ SQL: String = "", _ bindings: [Binding?] = []) { + public init(literal SQL: String = "", _ bindings: [Binding?] = []) { (self.SQL, self.bindings) = (SQL, bindings) } + public init(_ identifier: String) { + self.init(literal: quote(identifier: identifier)) + } + public init(_ expression: Expression) { - self.init(expression.SQL, expression.bindings) + self.init(literal: expression.SQL, expression.bindings) } public init(value: V?) { @@ -39,15 +43,15 @@ public struct Expression { } private init(binding: Binding?) { - self.init("?", [binding]) + self.init(literal: "?", [binding]) } public var asc: Expression<()> { - return join(" ", [self, Expression("ASC")]) + return join(" ", [self, Expression(literal: "ASC")]) } public var desc: Expression<()> { - return join(" ", [self, Expression("DESC")]) + return join(" ", [self, Expression(literal: "DESC")]) } // naïve compiler for statements that can't be bound, e.g., CREATE TABLE @@ -222,10 +226,10 @@ public enum Collation: String { } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.rawValue)) + return infix("COLLATE", expression, Expression(literal: collation.rawValue)) } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.rawValue)) + return infix("COLLATE", expression, Expression(literal: collation.rawValue)) } // MARK: - Predicates @@ -247,14 +251,14 @@ public func ==>(lhs: Exp } public func ==>(lhs: Expression, rhs: V?) -> Expression { if let rhs = rhs { return lhs == Expression(value: rhs) } - return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil]) + return Expression(literal: "\(lhs.SQL) IS ?", lhs.bindings + [nil]) } public func ==>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) == rhs } public func ==>(lhs: V?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs) == rhs } - return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings) + return Expression(literal: "? IS \(rhs.SQL)", [nil] + rhs.bindings) } public func !=>(lhs: Expression, rhs: Expression) -> Expression { @@ -274,14 +278,14 @@ public func !=>(lhs: Exp } public func !=>(lhs: Expression, rhs: V?) -> Expression { if let rhs = rhs { return lhs != Expression(value: rhs) } - return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) + return Expression(literal: "\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) } public func !=>(lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) != rhs } public func !=>(lhs: V?, rhs: Expression) -> Expression { if let lhs = lhs { return Expression(value: lhs) != rhs } - return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) + return Expression(literal: "? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) } public func >>(lhs: Expression, rhs: Expression) -> Expression { @@ -388,7 +392,7 @@ public prefix func -(rhs: Expression) -> Expression { return wr public prefix func -(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public func ~=, V == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) + return Expression(literal: "\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) } public func ~=, V == I.Bound>(lhs: I, rhs: Expression) -> Expression { return Expression(lhs ~= Expression(rhs)) @@ -588,7 +592,7 @@ public func total(#distinct: Expression) -> Expression { r public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } private func wrapDistinct(function: String, expression: Expression) -> Expression { - return wrap(function, SQLite.join(" ", [Expression<()>("DISTINCT"), expression])) + return wrap(function, SQLite.join(" ", [Expression<()>(literal: "DISTINCT"), expression])) } // MARK: - Helper @@ -596,16 +600,16 @@ private func wrapDistinct(function: String, expression: Expression) -> public typealias Star = (Expression?, Expression?) -> Expression<()> public func *(Expression?, Expression?) -> Expression<()> { - return Expression<()>("*") + return Expression<()>(literal: "*") } public func contains(values: [V.Datatype], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression("(\(templates))", values.map { $0 })) + return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0 })) } public func contains(values: [V.Datatype?], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression("(\(templates))", values.map { $0 })) + return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0 })) } // MARK: - Modifying @@ -755,19 +759,19 @@ public func ^=(column: Expression, value: Int) -> Setter { return set(colu public postfix func ++(column: Expression) -> Setter { // rdar://18825175 segfaults during archive: // column += 1 - return (column, Expression("(\(column.SQL) + 1)", column.bindings)) + return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) } public postfix func ++(column: Expression) -> Setter { // rdar://18825175 segfaults during archive: // column += 1 - return (column, Expression("(\(column.SQL) + 1)", column.bindings)) + return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) } public postfix func --(column: Expression) -> Setter { // rdar://18825175 segfaults during archive: // column -= 1 - return (column, Expression("(\(column.SQL) - 1)", column.bindings)) + return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) } public postfix func --(column: Expression) -> Setter { // rdar://18825175 segfaults during archive: // column -= 1 - return (column, Expression("(\(column.SQL) - 1)", column.bindings)) + return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) } // MARK: - Internal @@ -779,7 +783,7 @@ internal func join(separator: String, expressions: [Expressible]) -> Expression< SQL.append(expression.SQL) bindings.extend(expression.bindings) } - return Expression(Swift.join(separator, SQL), bindings) + return Expression(literal: Swift.join(separator, SQL), bindings) } internal func transcode(literal: Binding?) -> String { @@ -792,11 +796,11 @@ internal func transcode(literal: Binding?) -> String { } internal func wrap(function: String, expression: Expression) -> Expression { - return Expression("\(function)\(surround(expression.SQL))", expression.bindings) + return Expression(literal: "\(function)\(surround(expression.SQL))", expression.bindings) } private func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { - return Expression(surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) + return Expression(literal: surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) } private func surround(expression: String) -> String { return "(\(expression))" } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index 7cd352a8..f65a02f1 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -48,7 +48,7 @@ public struct Query { } - private var columns: [Expressible] = [Expression<()>("*")] + private var columns: [Expressible] = [Expression<()>(literal: "*")] private var distinct: Bool = false internal var tableName: String private var alias: String? @@ -216,8 +216,8 @@ public struct Query { /// :returns: A query with the given GROUP BY clause applied. public func group(by: [Expressible], having: Expression? = nil) -> Query { var query = self - var group = SQLite.join(" ", [Expression<()>("GROUP BY"), SQLite.join(", ", by)]) - if let having = having { group = SQLite.join(" ", [group, Expression<()>("HAVING"), having]) } + var group = SQLite.join(" ", [Expression<()>(literal: "GROUP BY"), SQLite.join(", ", by)]) + if let having = having { group = SQLite.join(" ", [group, Expression<()>(literal: "HAVING"), having]) } query.group = group return query } @@ -284,7 +284,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public func namespace(column: Expression) -> Expression { - return Expression("\(alias ?? tableName).\(column.SQL)", column.bindings) + return Expression(literal: "\(alias ?? tableName).\(column.SQL)", column.bindings) } // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression @@ -349,16 +349,16 @@ public struct Query { private func insertStatement(values: [Setter], or: OnConflict? = nil) -> Statement { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } - var expressions: [Expressible] = [Expression<()>("\(insertClause) INTO \(tableName)")] + var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(tableName)")] let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) - expressions.append(Expression<()>("(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) + expressions.append(Expression<()>(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) whereClause.map(expressions.append) let expression = SQLite.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) } private func updateStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression<()>("UPDATE \(tableName) SET")] + var expressions: [Expressible] = [Expression<()>(literal: "UPDATE \(tableName) SET")] expressions.append(SQLite.join(", ", values.map { SQLite.join(" = ", [$0, $1]) })) whereClause.map(expressions.append) let expression = SQLite.join(" ", expressions) @@ -366,7 +366,7 @@ public struct Query { } private var deleteStatement: Statement { - var expressions: [Expressible] = [Expression<()>("DELETE FROM \(tableName)")] + var expressions: [Expressible] = [Expression<()>(literal: "DELETE FROM \(tableName)")] whereClause.map(expressions.append) let expression = SQLite.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) @@ -375,23 +375,23 @@ public struct Query { // MARK: - private var selectClause: Expressible { - var expressions: [Expressible] = [Expression<()>("SELECT")] - if distinct { expressions.append(Expression<()>("DISTINCT")) } + var expressions: [Expressible] = [Expression<()>(literal: "SELECT")] + if distinct { expressions.append(Expression<()>(literal: "DISTINCT")) } expressions.append(SQLite.join(", ", columns)) - expressions.append(Expression<()>("FROM \(self)")) + expressions.append(Expression<()>(literal: "FROM \(self)")) return SQLite.join(" ", expressions) } private var joinClause: Expressible? { if joins.count == 0 { return nil } return SQLite.join(" ", joins.map { type, table, condition in - Expression<()>("\(type.rawValue) JOIN \(table) ON \(condition.SQL)", condition.bindings) + Expression<()>(literal: "\(type.rawValue) JOIN \(table) ON \(condition.SQL)", condition.bindings) }) } internal var whereClause: Expressible? { if let filter = filter { - return Expression<()>("WHERE \(filter.SQL)", filter.bindings) + return Expression<()>(literal: "WHERE \(filter.SQL)", filter.bindings) } return nil } @@ -399,14 +399,14 @@ public struct Query { private var orderClause: Expressible? { if order.count == 0 { return nil } let clause = SQLite.join(", ", order) - return Expression<()>("ORDER BY \(clause.SQL)", clause.bindings) + return Expression<()>(literal: "ORDER BY \(clause.SQL)", clause.bindings) } private var limitClause: Expressible? { if let limit = limit { - var clause = Expression<()>("LIMIT \(limit.to)") + var clause = Expression<()>(literal: "LIMIT \(limit.to)") if let offset = limit.offset { - clause = SQLite.join(" ", [clause, Expression<()>("OFFSET \(offset)")]) + clause = SQLite.join(" ", [clause, Expression<()>(literal: "OFFSET \(offset)")]) } return clause } @@ -547,7 +547,7 @@ public struct Query { // MARK: - Aggregate Functions /// Runs count(*) against the query and returns it. - public var count: Int { return count(Expression<()>("*")) } + public var count: Int { return count(Expression<()>(literal: "*")) } /// Runs count() against the query. /// @@ -747,7 +747,7 @@ extension Query: SequenceType { func expandGlob(namespace: Bool) -> Query -> () { return { table in - var names = self.database[table.tableName].selectStatement.columnNames + var names = self.database[table.tableName].selectStatement.columnNames.map { quote(identifier: $0) } if namespace { names = names.map { "\(table.alias ?? table.tableName).\($0)" } } columnNames.extend(names) } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index b3b302ff..4b07a142 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -71,7 +71,7 @@ public extension Database { check: Expression? = nil, references: Expression ) -> Statement { - let expressions = [Expression<()>("REFERENCES"), namespace(references)] + let expressions = [Expression<()>(literal: "REFERENCES"), namespace(references)] return alter(table, define(Expression(column), false, true, false, check, nil, expressions)) } @@ -101,8 +101,9 @@ public extension Database { private func indexName(table: Query, on columns: [Expressible]) -> String { let string = join(" ", ["index", table.tableName, "on"] + columns.map { $0.expression.SQL }) return Array(string).reduce("") { underscored, character in + if character == "\"" { return underscored } if "A"..."Z" ~= character || "a"..."z" ~= character { return underscored + String(character) } - return underscored.hasSuffix("_") ? underscored : underscored + "_" + return underscored + "_" } } @@ -223,7 +224,7 @@ public final class SchemaBuilder { references: Expression ) { assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] + let expressions: [Expressible] = [Expression<()>(literal: "REFERENCES"), namespace(references)] column(name, false, false, unique, check, nil, expressions) } @@ -237,7 +238,7 @@ public final class SchemaBuilder { name, unique: unique, check: check, - references: Expression(references.tableName) + references: Expression(literal: references.tableName) ) } @@ -248,7 +249,7 @@ public final class SchemaBuilder { references: Expression ) { assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression<()>("REFERENCES"), namespace(references)] + let expressions: [Expressible] = [Expression<()>(literal: "REFERENCES"), namespace(references)] column(Expression(name), false, true, unique, check, nil, expressions) } @@ -262,7 +263,7 @@ public final class SchemaBuilder { name, unique: unique, check: check, - references: Expression(references.tableName) + references: Expression(literal: references.tableName) ) } @@ -275,7 +276,7 @@ public final class SchemaBuilder { defaultValue value: Expression? = nil, collate: Collation ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] + let expressions: [Expressible] = [Expression<()>(literal: "COLLATE \(collate.rawValue)")] column(name, false, false, unique, check, value, expressions) } @@ -286,8 +287,7 @@ public final class SchemaBuilder { defaultValue value: String, collate: Collation ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(name, false, false, unique, check, Expression(value: value), expressions) + column(name, unique: unique, check: check, defaultValue: Expression(value: value), collate: collate) } public func column( @@ -297,19 +297,18 @@ public final class SchemaBuilder { defaultValue value: Expression? = nil, collate: Collation ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name), false, true, unique, check, value, expressions) + column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) } public func column( name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: String?, + defaultValue: String?, collate: Collation ) { - let expressions: [Expressible] = [Expression<()>("COLLATE \(collate.rawValue)")] - column(Expression(name), false, true, unique, check, Expression(value: value), expressions) + let value = defaultValue.map { Expression(value: $0) } + column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) } private func column( @@ -326,16 +325,16 @@ public final class SchemaBuilder { public func primaryKey(column: Expressible...) { let primaryKey = SQLite.join(", ", column) - columns.append(Expression<()>("PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) + columns.append(Expression<()>(literal: "PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) } public func unique(column: Expressible...) { let unique = SQLite.join(", ", column) - columns.append(Expression<()>("UNIQUE(\(unique.SQL))", unique.bindings)) + columns.append(Expression<()>(literal: "UNIQUE(\(unique.SQL))", unique.bindings)) } public func check(condition: Expression) { - columns.append(Expression<()>("CHECK \(condition.SQL)", condition.bindings)) + columns.append(Expression<()>(literal: "CHECK \(condition.SQL)", condition.bindings)) } public enum Dependency: String { @@ -359,10 +358,10 @@ public final class SchemaBuilder { delete: Dependency? = nil ) { assertForeignKeysEnabled() - var parts: [Expressible] = [Expression<()>("FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] + var parts: [Expressible] = [Expression<()>(literal: "FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] parts.append(namespace(references)) - if let update = update { parts.append(Expression<()>("ON UPDATE \(update.rawValue)")) } - if let delete = delete { parts.append(Expression<()>("ON DELETE \(delete.rawValue)")) } + if let update = update { parts.append(Expression<()>(literal: "ON UPDATE \(update.rawValue)")) } + if let delete = delete { parts.append(Expression<()>(literal: "ON DELETE \(delete.rawValue)")) } columns.append(SQLite.join(" ", parts)) } public func foreignKey( @@ -372,11 +371,7 @@ public final class SchemaBuilder { delete: Dependency? = nil ) { assertForeignKeysEnabled() - var parts: [Expressible] = [Expression<()>("FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] - parts.append(namespace(references)) - if let update = update { parts.append(Expression<()>("ON UPDATE \(update.rawValue)")) } - if let delete = delete { parts.append(Expression<()>("ON DELETE \(delete.rawValue)")) } - columns.append(SQLite.join(" ", parts)) + foreignKey(Expression(column), references: references, update: update, delete: delete) } public func foreignKey( @@ -385,7 +380,7 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { - foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) + foreignKey(column, references: Expression(literal: references.tableName), update: update, delete: delete) } public func foreignKey( column: Expression, @@ -393,7 +388,7 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { - foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) + foreignKey(column, references: Expression(literal: references.tableName), update: update, delete: delete) } private func assertForeignKeysEnabled() { @@ -409,7 +404,7 @@ private func namespace(column: Expressible) -> Expressible { let string = String(character) return SQL + (string == "." ? "(" : string) } - return Expression<()>("\(reference))", expression.bindings) + return Expression<()>(literal: "\(reference))", expression.bindings) } private func define( @@ -421,12 +416,12 @@ private func define( defaultValue: Expression?, expressions: [Expressible]? ) -> Expressible { - var parts: [Expressible] = [Expression<()>(column), Expression<()>(V.declaredDatatype)] - if primaryKey { parts.append(Expression<()>("PRIMARY KEY")) } - if !null { parts.append(Expression<()>("NOT NULL")) } - if unique { parts.append(Expression<()>("UNIQUE")) } - if let check = check { parts.append(Expression<()>("CHECK \(check.SQL)", check.bindings)) } - if let value = defaultValue { parts.append(Expression<()>("DEFAULT \(value.SQL)", value.bindings)) } + var parts: [Expressible] = [Expression<()>(column), Expression<()>(literal: V.declaredDatatype)] + if primaryKey { parts.append(Expression<()>(literal: "PRIMARY KEY")) } + if !null { parts.append(Expression<()>(literal: "NOT NULL")) } + if unique { parts.append(Expression<()>(literal: "UNIQUE")) } + if let check = check { parts.append(Expression<()>(literal: "CHECK \(check.SQL)", check.bindings)) } + if let value = defaultValue { parts.append(Expression<()>(literal: "DEFAULT \(value.SQL)", value.bindings)) } if let expressions = expressions { parts += expressions } return SQLite.join(" ", parts) } From 1ed56b9b078790d3caa97ae6d682091b12b2b036 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 22 Nov 2014 10:45:48 -0800 Subject: [PATCH 0099/1046] Prefer an < 80-column README Signed-off-by: Stephen Celis --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 40085103..55028110 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ for user in users { // SELECT * FROM "users" alice?.update(email <- replace(email, "mac.com", "me.com"))? -// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') WHERE ("id" = 1) +// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') +// WHERE ("id" = 1) alice?.delete()? // DELETE FROM "users" WHERE ("id" = 1) From cb4f3ed8ed3b1cf5a12aed941b2d8b30214d8748 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 22 Nov 2014 10:48:59 -0800 Subject: [PATCH 0100/1046] Document expression identifier quoting Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index cc800262..6d54ff5b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -193,6 +193,8 @@ Use optional generics for expressions that can evaluate to `NULL`. let name = Expression("name") ``` +> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. + ### Compound Expressions From c7625386bc04d54bfe7bf83a7f05f389750767c2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 22 Nov 2014 10:51:46 -0800 Subject: [PATCH 0101/1046] Expose Statement.columnNames 7fa9a57cee70cbd9a06eea9531377b19c9447581 removed Statement.values, which provided dictionary access to a row. It was removed to avoid bugs when joining tables with ambiguous column names. Developers should have the ability to introspect enough to extend Statement themselves, though, should they so choose. Signed-off-by: Stephen Celis --- SQLite Common/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index 2db17adc..0aa49618 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -38,7 +38,7 @@ public final class Statement { deinit { sqlite3_finalize(handle) } - internal lazy var columnNames: [String] = { [unowned self] in + public lazy var columnNames: [String] = { [unowned self] in let count = sqlite3_column_count(self.handle) return (0.. Date: Sat, 22 Nov 2014 13:03:45 -0800 Subject: [PATCH 0102/1046] Fix quoting Swift.split only splits when a string of length results, so quotes at the beginning and end of a string are lost (rather than captured in an empty string). Here we use reduce instead to escape quotation marks. Signed-off-by: Stephen Celis --- Documentation/Index.md | 10 +++++----- SQLite Common Tests/SchemaTests.swift | 22 ++++++++++++++++------ SQLite Common/Database.swift | 8 +++++--- SQLite Common/Query.swift | 22 +++++++++++----------- SQLite Common/Schema.swift | 16 ++++++++-------- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 6d54ff5b..0c699e3c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -931,7 +931,7 @@ We can run [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.h ``` swift db.create(index: users, on: email) -// CREATE INDEX index_users_on_email ON "users" ("email") +// CREATE INDEX "index_users_on_email" ON "users" ("email") ``` The index name is generated automatically based on the table and column names. @@ -942,14 +942,14 @@ The `create(index:)` function has a couple default parameters we can override. ``` swift db.create(index: users, on: email, unique: true) - // CREATE UNIQUE INDEX index_users_on_email ON "users" ("email") + // CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email") ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift db.create(index: users, on: email, ifNotExists: true) - // CREATE INDEX IF NOT EXISTS index_users_on_email ON "users" ("email") + // CREATE INDEX IF NOT EXISTS "index_users_on_email" ON "users" ("email") ``` @@ -959,14 +959,14 @@ We can run [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) ``` swift db.drop(index: users, on: email) -// DROP INDEX index_users_on_email +// DROP INDEX "index_users_on_email" ``` The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. ``` swift db.drop(index: users, on: email, ifExists: true) -// DROP INDEX IF EXISTS index_users_on_email +// DROP INDEX IF EXISTS "index_users_on_email" ``` diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index 9f6f1daf..7d1f3919 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -16,6 +16,7 @@ class SchemaTests: XCTestCase { override func setUp() { super.setUp() db.run("PRAGMA foreign_keys = ON") + db.trace(println) } func test_createTable_createsTable() { @@ -300,7 +301,7 @@ class SchemaTests: XCTestCase { func test_index_executesIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX index_users_on_email ON \"users\" (\"email\")", + "CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")", db.create(index: users, on: email) ) } @@ -308,7 +309,7 @@ class SchemaTests: XCTestCase { func test_index_withUniqueness_executesUniqueIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE UNIQUE INDEX index_users_on_email ON \"users\" (\"email\")", + "CREATE UNIQUE INDEX \"index_users_on_email\" ON \"users\" (\"email\")", db.create(index: users, unique: true, on: email) ) } @@ -316,7 +317,7 @@ class SchemaTests: XCTestCase { func test_index_ifNotExists_executesIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX IF NOT EXISTS index_users_on_email ON \"users\" (\"email\")", + "CREATE INDEX IF NOT EXISTS \"index_users_on_email\" ON \"users\" (\"email\")", db.create(index: users, ifNotExists: true, on: email) ) } @@ -324,7 +325,7 @@ class SchemaTests: XCTestCase { func test_index_withMultipleColumns_executesCompoundIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX index_users_on_age_DESC_email ON \"users\" (\"age\" DESC, \"email\")", + "CREATE INDEX \"index_users_on_age_DESC_email\" ON \"users\" (\"age\" DESC, \"email\")", db.create(index: users, on: age.desc, email) ) } @@ -343,8 +344,8 @@ class SchemaTests: XCTestCase { CreateUsersTable(db) db.create(index: users, on: email) - ExpectExecution(db, "DROP INDEX index_users_on_email", db.drop(index: users, on: email)) - ExpectExecution(db, "DROP INDEX IF EXISTS index_users_on_email", db.drop(index: users, ifExists: true, on: email)) + ExpectExecution(db, "DROP INDEX \"index_users_on_email\"", db.drop(index: users, on: email)) + ExpectExecution(db, "DROP INDEX IF EXISTS \"index_users_on_email\"", db.drop(index: users, ifExists: true, on: email)) } func test_createView_withQuery_createsViewWithQuery() { @@ -367,4 +368,13 @@ class SchemaTests: XCTestCase { ExpectExecution(db, "DROP VIEW IF EXISTS \"emails\"", db.drop(view: db["emails"], ifExists: true)) } + func test_quotedIdentifiers() { + let table = db["table"] + let column = Expression("My lil' primary key, \"Kiwi\"") + + ExpectExecution(db, "CREATE TABLE \"table\" (\"My lil' primary key, \"\"Kiwi\"\"\" INTEGER NOT NULL)", + db.create(table: db["table"]) { $0.column(column) } + ) + } + } diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift index c0194f2b..dbfe55a3 100644 --- a/SQLite Common/Database.swift +++ b/SQLite Common/Database.swift @@ -359,7 +359,9 @@ internal func quote(#identifier: String) -> String { return quote(identifier, "\"") } -private func quote(string: String, character: Character) -> String { - let escaped = join("\(character)\(character)", split(string) { $0 == character }) - return "\(character)\(escaped)\(character)" +private func quote(string: String, mark: Character) -> String { + let escaped = Array(string).reduce("") { string, character in + string + (character == mark ? "\(mark)\(mark)" : "\(character)") + } + return "\(mark)\(escaped)\(mark)" } diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift index f65a02f1..6256055b 100644 --- a/SQLite Common/Query.swift +++ b/SQLite Common/Query.swift @@ -60,7 +60,7 @@ public struct Query { public func alias(alias: String?) -> Query { var query = self - query.alias = alias.map { quote(identifier: $0) } + query.alias = alias return query } @@ -284,7 +284,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public func namespace(column: Expression) -> Expression { - return Expression(literal: "\(alias ?? tableName).\(column.SQL)", column.bindings) + return Expression(literal: "\(quote(identifier: alias ?? tableName)).\(column.SQL)", column.bindings) } // FIXME: rdar://18673897 subscript(expression: Expression) -> Expression @@ -349,7 +349,7 @@ public struct Query { private func insertStatement(values: [Setter], or: OnConflict? = nil) -> Statement { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } - var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(tableName)")] + var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(quote(identifier: tableName))")] let (c, v) = (SQLite.join(", ", values.map { $0.0 }), SQLite.join(", ", values.map { $0.1 })) expressions.append(Expression<()>(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) whereClause.map(expressions.append) @@ -358,7 +358,7 @@ public struct Query { } private func updateStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression<()>(literal: "UPDATE \(tableName) SET")] + var expressions: [Expressible] = [Expression<()>(literal: "UPDATE \(quote(identifier: tableName)) SET")] expressions.append(SQLite.join(", ", values.map { SQLite.join(" = ", [$0, $1]) })) whereClause.map(expressions.append) let expression = SQLite.join(" ", expressions) @@ -366,7 +366,7 @@ public struct Query { } private var deleteStatement: Statement { - var expressions: [Expressible] = [Expression<()>(literal: "DELETE FROM \(tableName)")] + var expressions: [Expressible] = [Expression<()>(literal: "DELETE FROM \(quote(identifier: tableName))")] whereClause.map(expressions.append) let expression = SQLite.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) @@ -457,7 +457,7 @@ public struct Query { public func insert(query: Query) -> (changes: Int?, statement: Statement) { let expression = query.selectExpression - let statement = database.run("INSERT INTO \(tableName) \(expression.SQL)", expression.bindings) + let statement = database.run("INSERT INTO \(quote(identifier: tableName)) \(expression.SQL)", expression.bindings) return (statement.failed ? nil : database.lastChanges, statement) } @@ -466,7 +466,7 @@ public struct Query { public func insert() -> Statement { return insert().statement } public func insert() -> (ID: Int?, statement: Statement) { - let statement = database.run("INSERT INTO \(tableName) DEFAULT VALUES") + let statement = database.run("INSERT INTO \(quote(identifier: tableName)) DEFAULT VALUES") return (statement.failed ? nil : database.lastID, statement) } @@ -748,7 +748,7 @@ extension Query: SequenceType { func expandGlob(namespace: Bool) -> Query -> () { return { table in var names = self.database[table.tableName].selectStatement.columnNames.map { quote(identifier: $0) } - if namespace { names = names.map { "\(table.alias ?? table.tableName).\($0)" } } + if namespace { names = names.map { "\(quote(identifier: table.alias ?? table.tableName)).\($0)" } } columnNames.extend(names) } } @@ -796,8 +796,8 @@ public struct QueryGenerator: GeneratorType { extension Query: Printable { public var description: String { - if let alias = alias { return "\(tableName) AS \(alias)" } - return tableName + if let alias = alias { return "\(quote(identifier: tableName)) AS \(quote(identifier: alias))" } + return quote(identifier: tableName) } } @@ -805,7 +805,7 @@ extension Query: Printable { extension Database { public subscript(tableName: String) -> Query { - return Query(self, quote(identifier: tableName)) + return Query(self, tableName) } } diff --git a/SQLite Common/Schema.swift b/SQLite Common/Schema.swift index 4b07a142..2fb530c2 100644 --- a/SQLite Common/Schema.swift +++ b/SQLite Common/Schema.swift @@ -43,7 +43,7 @@ public extension Database { } public func rename(#table: Query, to tableName: String) -> Statement { - return run("ALTER TABLE \(table.tableName) RENAME TO \(quote(identifier: tableName))") + return run("ALTER TABLE \(quote(identifier: table.tableName)) RENAME TO \(quote(identifier: tableName))") } public func alter( @@ -76,7 +76,7 @@ public extension Database { } private func alter(table: Query, _ definition: Expressible) -> Statement { - return run("ALTER TABLE \(table.tableName) ADD COLUMN \(definition.expression.compile())") + return run("ALTER TABLE \(quote(identifier: table.tableName)) ADD COLUMN \(definition.expression.compile())") } public func drop(#table: Query, ifExists: Bool = false) -> Statement { @@ -91,7 +91,7 @@ public extension Database { ) -> Statement { let create = createSQL("INDEX", false, unique, ifNotExists, indexName(table, on: columns)) let joined = SQLite.join(", ", columns) - return run("\(create) ON \(table.tableName) (\(joined.compile()))") + return run("\(create) ON \(quote(identifier: table.tableName)) (\(joined.compile()))") } public func drop(index table: Query, ifExists: Bool = false, on columns: Expressible...) -> Statement { @@ -263,7 +263,7 @@ public final class SchemaBuilder { name, unique: unique, check: check, - references: Expression(literal: references.tableName) + references: Expression(references.tableName) ) } @@ -380,7 +380,7 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { - foreignKey(column, references: Expression(literal: references.tableName), update: update, delete: delete) + foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) } public func foreignKey( column: Expression, @@ -388,7 +388,7 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { - foreignKey(column, references: Expression(literal: references.tableName), update: update, delete: delete) + foreignKey(column, references: Expression(references.tableName), update: update, delete: delete) } private func assertForeignKeysEnabled() { @@ -438,13 +438,13 @@ private func createSQL( if unique { parts.append("UNIQUE") } parts.append(type) if ifNotExists { parts.append("IF NOT EXISTS") } - parts.append(name) + parts.append(quote(identifier: name)) return Swift.join(" ", parts) } private func dropSQL(type: String, ifExists: Bool, name: String) -> String { var parts: [String] = ["DROP \(type)"] if ifExists { parts.append("IF EXISTS") } - parts.append(name) + parts.append(quote(identifier: name)) return Swift.join(" ", parts) } From f1141b6872422967bdf8711b4f18acc712cac459 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 23 Nov 2014 09:29:49 -0800 Subject: [PATCH 0103/1046] Add missing quotes Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 0c699e3c..82eb0453 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -253,7 +253,7 @@ The `column` function is used for a single column definition. It takes an [expre ``` swift t.column(id, primaryKey: true) - // id INTEGER PRIMARY KEY NOT NULL + // "id" INTEGER PRIMARY KEY NOT NULL ``` > _Note:_ The `primaryKey` parameter cannot be used alongside `defaultValue` or `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). From 2f2ca7dfc296af4d4e13063fb3e6ab288cfc276f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 23 Nov 2014 19:53:45 -0800 Subject: [PATCH 0104/1046] Document contains/IN Signed-off-by: Stephen Celis --- Documentation/Index.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 82eb0453..cc3029d8 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -638,11 +638,12 @@ SQLite.swift defines a number of operators for building filtering predicates. Op ###### Filtering Functions -| Swift | Types | SQLite | -| ------- | ---------------- | ------- | -| `like` | `String -> Bool` | `LIKE` | -| `glob` | `String -> Bool` | `GLOB` | -| `match` | `String -> Bool` | `MATCH` | +| Swift | Types | SQLite | +| ---------- | ----------------------- | ------- | +| `like` | `String -> Bool` | `LIKE` | +| `glob` | `String -> Bool` | `GLOB` | +| `match` | `String -> Bool` | `MATCH` | +| `contains` | `(Array, T) -> Bool` | `IN` | From 228d9d36ed8788891e1ea424cd7fbcad421a2be6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Feb 2015 09:18:08 -0800 Subject: [PATCH 0175/1046] Make Products the last group Having Vendor at the bottom made for a slightly more confusing installation. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 72164780..bbb84ad4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -193,8 +193,8 @@ DC10500F19C904DD00D8CA30 /* SQLite Tests */, DCC6B3A11A91949C00734B78 /* SQLiteCipher */, DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */, - DC3773F419C8CBB3004FCF85 /* Products */, DCC6B3951A91936500734B78 /* Vendor */, + DC3773F419C8CBB3004FCF85 /* Products */, ); sourceTree = ""; }; From 6355b6d367244ca3d3bd25581536163a9ee78d5e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 17 Feb 2015 17:51:35 -0800 Subject: [PATCH 0176/1046] SQLITE_DETERMINISTIC is not defined in iOS 8.1 It's there in OS X and iOS 8.3, though, so let's just make it a no-op for older versions. Signed-off-by: Stephen Celis --- SQLite/SQLite-Bridging.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite/SQLite-Bridging.c b/SQLite/SQLite-Bridging.c index cf315271..797835a2 100644 --- a/SQLite/SQLite-Bridging.c +++ b/SQLite/SQLite-Bridging.c @@ -58,7 +58,9 @@ int SQLiteCreateFunction(sqlite3 * handle, const char * name, int deterministic, if (callback) { int flags = SQLITE_UTF8; if (deterministic) { +#ifdef SQLITE_DETERMINISTIC flags |= SQLITE_DETERMINISTIC; +#endif } return sqlite3_create_function_v2(handle, name, -1, flags, Block_copy(callback), &_SQLiteCreateFunction, 0, 0, 0); // FIXME: leak } else { From 4961138e0c3c2533557560250d4d82e35b349041 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 19 Feb 2015 09:25:08 -0800 Subject: [PATCH 0177/1046] Add custom collation support For example: db.create(collation: "NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } let stmt = db.prepare( "SELECT * FROM venues ORDER BY name COLLATE NODIACRITIC" ) With the type-safe interface: restaurants.order(collate(.Custom("NODIACRITIC"), name)) // SELECT * FROM "restaurants" ORDER BY "name" COLLATE NODIACRITIC Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 7 +++++++ SQLite Tests/ExpressionTests.swift | 6 ++++++ SQLite/Database.swift | 15 +++++++++++++++ SQLite/Expression.swift | 31 ++++++++++++++++++++++++------ SQLite/SQLite-Bridging.c | 12 ++++++++++++ SQLite/SQLite-Bridging.h | 3 +++ SQLite/Schema.swift | 6 +++--- 7 files changed, 71 insertions(+), 9 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index cb01eab8..db82a3c1 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -199,4 +199,11 @@ class DatabaseTests: XCTestCase { XCTAssertEqual(true, db.foreignKeys) } + func test_createCollation_createsCollation() { + db.create(collation: "NODIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as Int) + } + } diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index 7d718083..e44f7abb 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -298,6 +298,12 @@ class ExpressionTests: XCTestCase { ExpectExecutionMatches("('A' COLLATE BINARY)", collate(.Binary, stringA)) ExpectExecutionMatches("('B' COLLATE NOCASE)", collate(.NoCase, stringB)) ExpectExecutionMatches("('A' COLLATE RTRIM)", collate(.RTrim, stringA)) + + let NoDiacritic = "NODIACRITIC" + db.create(collation: NoDiacritic) { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + ExpectExecutionMatches("('A' COLLATE NODIACRITIC)", collate(.Custom(NoDiacritic), stringA)) } func test_cast_buildsCastingExpressions() { diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 11f98b26..85b36569 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -383,6 +383,21 @@ public final class Database { } } + /// The return type of a collation comparison function. + public typealias ComparisonResult = NSComparisonResult + + /// Defines a new collating sequence. + /// + /// :param: collation The name of the collation added. + /// + /// :param: block A collation function that takes two strings and + /// returns the comparison result. + public func create(#collation: String, _ block: (String, String) -> ComparisonResult) { + try(SQLiteCreateCollation(handle, collation) { lhs, rhs in + return Int32(block(String.fromCString(lhs)!, String.fromCString(rhs)!).rawValue) + }) + } + // MARK: - Error Handling /// Returns the last error produced on this connection. diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 8a529dbb..83756517 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -235,21 +235,40 @@ public func ^ (lhs: Int, rhs: Expression) -> Expression { return Exp public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public enum Collation: String { +public enum Collation { - case Binary = "BINARY" + case Binary - case NoCase = "NOCASE" + case NoCase - case RTrim = "RTRIM" + case RTrim + + case Custom(String) + +} + +extension Collation: Printable { + + public var description: String { + switch self { + case Binary: + return "BINARY" + case NoCase: + return "NOCASE" + case RTrim: + return "RTRIM" + case Custom(let collation): + return collation + } + } } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(literal: collation.rawValue)) + return infix("COLLATE", expression, Expression(literal: collation.description)) } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(literal: collation.rawValue)) + return infix("COLLATE", expression, Expression(literal: collation.description)) } public func cast(expression: Expression) -> Expression { diff --git a/SQLite/SQLite-Bridging.c b/SQLite/SQLite-Bridging.c index 797835a2..56f309b3 100644 --- a/SQLite/SQLite-Bridging.c +++ b/SQLite/SQLite-Bridging.c @@ -67,3 +67,15 @@ int SQLiteCreateFunction(sqlite3 * handle, const char * name, int deterministic, return sqlite3_create_function_v2(handle, name, 0, 0, 0, 0, 0, 0, 0); } } + +int _SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { + return ((SQLiteCreateCollationCallback)context)(lhs, rhs); +} + +int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback) { + if (callback) { + return sqlite3_create_collation_v2(handle, name, SQLITE_UTF8, Block_copy(callback), &_SQLiteCreateCollation, 0); // FIXME: leak + } else { + return sqlite3_create_collation_v2(handle, name, 0, 0, 0, 0); + } +} diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index 4bcdb5db..b0ff07c9 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -32,3 +32,6 @@ void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback); typedef void (^SQLiteCreateFunctionCallback)(sqlite3_context * context, int argc, sqlite3_value ** argv); int SQLiteCreateFunction(sqlite3 * handle, const char * name, int deterministic, SQLiteCreateFunctionCallback callback); + +typedef int (^SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); +int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback); diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index a90a045f..deb0edb3 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -85,7 +85,7 @@ public extension Database { collate: Collation ) -> Statement { return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ - Expression<()>(literal: "COLLATE \(collate.rawValue)") + Expression<()>(literal: "COLLATE \(collate)") ])) } @@ -98,7 +98,7 @@ public extension Database { ) -> Statement { let value = defaultValue.map { Expression(value: $0) } return alter(table, define(Expression(column), nil, true, false, check, value, [ - Expression<()>(literal: "COLLATE \(collate.rawValue)") + Expression<()>(literal: "COLLATE \(collate)") ])) } @@ -284,7 +284,7 @@ public final class SchemaBuilder { defaultValue value: Expression?, collate: Collation ) { - let expressions: [Expressible] = [Expression<()>(literal: "COLLATE \(collate.rawValue)")] + let expressions: [Expressible] = [Expression<()>(literal: "COLLATE \(collate)")] column(name, nil, false, unique, check, value, expressions) } From acb66725c5904fb94b905b1fe550633293cf98eb Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 19 Feb 2015 16:58:43 -0800 Subject: [PATCH 0178/1046] Move create(function:) to Database.swift It's more of a core function than a type-safe enhancement. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 51 ++++++++++++++++++++++++++++++++++++++++++ SQLite/Functions.swift | 51 ------------------------------------------ 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 85b36569..90976112 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -383,6 +383,57 @@ public final class Database { } } + /// Creates or redefines a custom SQL function. + /// + /// :param: function The name of the function to create or redefine. + /// + /// :param: deterministic Whether or not the function is deterministic. If + /// the function always returns the same result for a + /// given input, SQLite can make optimizations. + /// (Default: false.) + /// + /// :param: block A block of code to run when the function is + /// called. The block is called with an array of raw + /// SQL values mapped to the function's parameters and + /// should return a raw SQL value (or nil). + public func create(#function: String, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { + try(SQLiteCreateFunction(handle, function, deterministic ? 1 : 0) { context, argc, argv in + let arguments: [Binding?] = map(0.. Binding?) { - try(SQLiteCreateFunction(handle, function, deterministic ? 1 : 0) { context, argc, argv in - let arguments: [Binding?] = map(0.. Date: Tue, 24 Feb 2015 08:06:02 -0800 Subject: [PATCH 0179/1046] Escape collation identifiers It's possible to define collation identifiers that must be quoted (with spaces, etc.), so let's quote them. Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 7 +++++++ SQLite Tests/ExpressionTests.swift | 10 +++++----- SQLite Tests/SchemaTests.swift | 4 ++-- SQLite/Expression.swift | 4 ++-- SQLite/Schema.swift | 4 ++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index db82a3c1..c0238cfe 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -206,4 +206,11 @@ class DatabaseTests: XCTestCase { XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as Int) } + func test_createCollation_createsQuotableCollation() { + db.create(collation: "NO DIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as Int) + } + } diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index e44f7abb..d59ee3d9 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -295,16 +295,16 @@ class ExpressionTests: XCTestCase { } func test_collateOperator_withStringExpression_buildsCollationExpression() { - ExpectExecutionMatches("('A' COLLATE BINARY)", collate(.Binary, stringA)) - ExpectExecutionMatches("('B' COLLATE NOCASE)", collate(.NoCase, stringB)) - ExpectExecutionMatches("('A' COLLATE RTRIM)", collate(.RTrim, stringA)) + ExpectExecutionMatches("('A' COLLATE \"BINARY\")", collate(.Binary, stringA)) + ExpectExecutionMatches("('B' COLLATE \"NOCASE\")", collate(.NoCase, stringB)) + ExpectExecutionMatches("('A' COLLATE \"RTRIM\")", collate(.RTrim, stringA)) let NoDiacritic = "NODIACRITIC" db.create(collation: NoDiacritic) { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - ExpectExecutionMatches("('A' COLLATE NODIACRITIC)", collate(.Custom(NoDiacritic), stringA)) - } + ExpectExecutionMatches("('A' COLLATE \"NODIACRITIC\")", collate(.Custom(NoDiacritic), stringA)) +} func test_cast_buildsCastingExpressions() { let string1 = Expression(value: "10") diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 2fb0b997..64fc3709 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -308,11 +308,11 @@ class SchemaTests: XCTestCase { let columnA = Expression("column_a") let columnB = Expression("column_b") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE NOCASE", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE \"NOCASE\"", db.alter(table: users, add: columnA, defaultValue: "", collate: .NoCase) ) - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE NOCASE", + ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE \"NOCASE\"", db.alter(table: users, add: columnB, collate: .NoCase) ) } diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 83756517..219242de 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -265,10 +265,10 @@ extension Collation: Printable { } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(literal: collation.description)) + return infix("COLLATE", expression, Expression(collation.description)) } public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(literal: collation.description)) + return infix("COLLATE", expression, Expression(collation.description)) } public func cast(expression: Expression) -> Expression { diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index deb0edb3..cddfad49 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -85,7 +85,7 @@ public extension Database { collate: Collation ) -> Statement { return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ - Expression<()>(literal: "COLLATE \(collate)") + Expression<()>(literal: "COLLATE"), Expression<()>(collate.description) ])) } @@ -98,7 +98,7 @@ public extension Database { ) -> Statement { let value = defaultValue.map { Expression(value: $0) } return alter(table, define(Expression(column), nil, true, false, check, value, [ - Expression<()>(literal: "COLLATE \(collate)") + Expression<()>(literal: "COLLATE"), Expression<()>(collate.description) ])) } From d0480aff950084bf6f3dfc06e66d8393710cb833 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Feb 2015 08:13:14 -0800 Subject: [PATCH 0180/1046] Escape function identifiers It's possible to define function identifiers that must be quoted (with spaces, etc.), so let's quote them. Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 14 +++++++ SQLite Tests/FunctionsTests.swift | 69 ++++++++++++++----------------- SQLite/Functions.swift | 2 +- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index c0238cfe..bf2028d1 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -199,6 +199,20 @@ class DatabaseTests: XCTestCase { XCTAssertEqual(true, db.foreignKeys) } + func test_createFunction_withArrayArguments() { + db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } } + + XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as String) + XCTAssert(db.scalar("SELECT hello(NULL)") == nil) + } + + func test_createFunction_createsQuotableFunction() { + db.create(function: "hello world") { $0[0].map { "Hello, \($0)!" } } + + XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as String) + XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) + } + func test_createCollation_createsCollation() { db.create(collation: "NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) diff --git a/SQLite Tests/FunctionsTests.swift b/SQLite Tests/FunctionsTests.swift index 8e3ed602..d47ca99b 100644 --- a/SQLite Tests/FunctionsTests.swift +++ b/SQLite Tests/FunctionsTests.swift @@ -5,13 +5,6 @@ class FunctionsTests: XCTestCase { let db = Database() - func test_createFunction_withArrayArguments() { - db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } } - - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as String) - XCTAssert(db.scalar("SELECT hello(NULL)") == nil) - } - func test_createFunction_withZeroArguments() { let f1: () -> Expression = db.create(function: "f1") { true } let f2: () -> Expression = db.create(function: "f2") { nil } @@ -20,10 +13,10 @@ class FunctionsTests: XCTestCase { db.create(table: table) { $0.column(Expression("id"), primaryKey: true) } table.insert()! - ExpectExecutions(db, ["SELECT f1() FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f1\"() FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f1()).first![f1()]) } - ExpectExecutions(db, ["SELECT f2() FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f2\"() FROM \"table\" LIMIT 1": 1]) { db in XCTAssertNil(table.select(f2()).first![f2()]) } } @@ -45,16 +38,16 @@ class FunctionsTests: XCTestCase { let null = Expression(value: nil as String?) - ExpectExecutions(db, ["SELECT f1(\"s1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f1\"(\"s1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f1(s1)).first![f1(s1)]) } - ExpectExecutions(db, ["SELECT f2(\"s2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f2\"(\"s2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(!table.select(f2(s2)).first![f2(s2)]) } - ExpectExecutions(db, ["SELECT f3(\"s1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f3\"(\"s1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f3(s1)).first![f3(s1)]!) } - ExpectExecutions(db, ["SELECT f4(NULL) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f4\"(NULL) FROM \"table\" LIMIT 1": 1]) { db in XCTAssertNil(table.select(f4(null)).first![f4(null)]) } } @@ -73,7 +66,7 @@ class FunctionsTests: XCTestCase { } table.insert(b <- true)! - ExpectExecutions(db, ["SELECT f1(\"b\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f1(b)).first![f1(b)]) } } @@ -93,16 +86,16 @@ class FunctionsTests: XCTestCase { let f3: (Bool, Expression) -> Expression = db.create(function: "f3") { $0 && $1 != nil } let f4: (Bool?, Expression) -> Expression = db.create(function: "f4") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT f1(1, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f1\"(1, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f1(true, b1)).first![f1(true, b1)]) } - ExpectExecutions(db, ["SELECT f2(NULL, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f2\"(NULL, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f2(nil, b1)).first![f2(nil, b1)]) } - ExpectExecutions(db, ["SELECT f3(0, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f3\"(0, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f3(false, b2)).first![f3(false, b2)]) } - ExpectExecutions(db, ["SELECT f4(NULL, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f4\"(NULL, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f4(nil, b2)).first![f4(nil, b2)]) } @@ -111,16 +104,16 @@ class FunctionsTests: XCTestCase { let f7: (Bool, Expression) -> Expression = db.create(function: "f7") { $0 && $1 != nil } let f8: (Bool?, Expression) -> Expression = db.create(function: "f8") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT f5(1, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f5\"(1, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f5(true, b1)).first![f5(true, b1)]!) } - ExpectExecutions(db, ["SELECT f6(NULL, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f6\"(NULL, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f6(nil, b1)).first![f6(nil, b1)]!) } - ExpectExecutions(db, ["SELECT f7(0, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f7\"(0, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f7(false, b2)).first![f7(false, b2)]!) } - ExpectExecutions(db, ["SELECT f8(NULL, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f8\"(NULL, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f8(nil, b2)).first![f8(nil, b2)]!) } @@ -129,16 +122,16 @@ class FunctionsTests: XCTestCase { let f11: (Expression, Expression) -> Expression = db.create(function: "f11") { $0 && $1 != nil } let f12: (Expression, Expression) -> Expression = db.create(function: "f12") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT f9(\"b1\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f9\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f9(b1, b1)).first![f9(b1, b1)]) } - ExpectExecutions(db, ["SELECT f10(\"b2\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f10\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f10(b2, b1)).first![f10(b2, b1)]) } - ExpectExecutions(db, ["SELECT f11(\"b1\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f11\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f11(b1, b2)).first![f11(b1, b2)]) } - ExpectExecutions(db, ["SELECT f12(\"b2\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f12\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f12(b2, b2)).first![f12(b2, b2)]) } @@ -147,16 +140,16 @@ class FunctionsTests: XCTestCase { let f15: (Expression, Expression) -> Expression = db.create(function: "f15") { $0 && $1 != nil } let f16: (Expression, Expression) -> Expression = db.create(function: "f16") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT f13(\"b1\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f13\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f13(b1, b1)).first![f13(b1, b1)]!) } - ExpectExecutions(db, ["SELECT f14(\"b2\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f14\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f14(b2, b1)).first![f14(b2, b1)]!) } - ExpectExecutions(db, ["SELECT f15(\"b1\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f15\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f15(b1, b2)).first![f15(b1, b2)]!) } - ExpectExecutions(db, ["SELECT f16(\"b2\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f16\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f16(b2, b2)).first![f16(b2, b2)]!) } @@ -165,16 +158,16 @@ class FunctionsTests: XCTestCase { let f19: (Expression, Bool?) -> Expression = db.create(function: "f19") { $0 && $1 != nil } let f20: (Expression, Bool?) -> Expression = db.create(function: "f20") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT f17(\"b1\", 1) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f17\"(\"b1\", 1) FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f17(b1, true)).first![f17(b1, true)]) } - ExpectExecutions(db, ["SELECT f18(\"b2\", 1) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f18\"(\"b2\", 1) FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f18(b2, true)).first![f18(b2, true)]) } - ExpectExecutions(db, ["SELECT f19(\"b1\", NULL) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f19\"(\"b1\", NULL) FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f19(b1, nil)).first![f19(b1, nil)]) } - ExpectExecutions(db, ["SELECT f20(\"b2\", NULL) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f20\"(\"b2\", NULL) FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f20(b2, nil)).first![f20(b2, nil)]) } @@ -183,16 +176,16 @@ class FunctionsTests: XCTestCase { let f23: (Expression, Bool?) -> Expression = db.create(function: "f23") { $0 && $1 != nil } let f24: (Expression, Bool?) -> Expression = db.create(function: "f24") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT f21(\"b1\", 1) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f21\"(\"b1\", 1) FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f21(b1, true)).first![f21(b1, true)]!) } - ExpectExecutions(db, ["SELECT f22(\"b2\", 1) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f22\"(\"b2\", 1) FROM \"table\" LIMIT 1": 1]) { db in XCTAssert(table.select(f22(b2, true)).first![f22(b2, true)]!) } - ExpectExecutions(db, ["SELECT f23(\"b1\", NULL) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f23\"(\"b1\", NULL) FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f23(b1, nil)).first![f23(b1, nil)]!) } - ExpectExecutions(db, ["SELECT f24(\"b2\", NULL) FROM \"table\" LIMIT 1": 1]) { db in + ExpectExecutions(db, ["SELECT \"f24\"(\"b2\", NULL) FROM \"table\" LIMIT 1": 1]) { db in XCTAssertFalse(table.select(f24(b2, nil)).first![f24(b2, nil)]!) } } diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift index 851e3aa9..9b4d8fd4 100644 --- a/SQLite/Functions.swift +++ b/SQLite/Functions.swift @@ -168,7 +168,7 @@ public extension Database { private func create(function: String, _ deterministic: Bool, _ block: [Binding?] -> Z?) -> ([Expressible] -> Expression) { create(function: function, deterministic: deterministic) { block($0)?.datatypeValue } - return { arguments in wrap(function, Expression.join(", ", arguments)) } + return { arguments in wrap(quote(identifier: function), Expression.join(", ", arguments)) } } } From fd131101e108cc5770478d2de3813673b233ff5c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Feb 2015 08:14:23 -0800 Subject: [PATCH 0181/1046] Enforce a four-space indent in Xcode Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index bbb84ad4..f52e459e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -196,7 +196,10 @@ DCC6B3951A91936500734B78 /* Vendor */, DC3773F419C8CBB3004FCF85 /* Products */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; + usesTabs = 0; }; DC3773F419C8CBB3004FCF85 /* Products */ = { isa = PBXGroup; From 9eca2a78468199a20821457c0619019bd5efe75b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Feb 2015 08:26:36 -0800 Subject: [PATCH 0182/1046] Update documentation - Properly represent quoted collations/functions - Expose documentation for ALTER TABLE ADD COLUMN with COLLATE Signed-off-by: Stephen Celis --- Documentation/Index.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 97aa6504..871f4d5f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -317,7 +317,7 @@ The `column` function is used for a single column definition. It takes an [expre ``` swift t.column(email, collate: .NoCase) - // "email" TEXT NOT NULL COLLATE NOCASE + // "email" TEXT NOT NULL COLLATE "NOCASE" ``` - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) @@ -944,14 +944,12 @@ The `alter` function shares several of the same [`column` function parameters](# > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), default values may not be expression structures (including `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). - - `references` adds a `REFERENCES` clause to `Int` (and `Int?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) @@ -1292,7 +1290,7 @@ let attachments = db["attachments"] let UTI = Expression("UTI") attachments.filter(typeConformsTo(UTI, kUTTypeImage)) -// SELECT * FROM "attachments" WHERE typeConformsTo("UTI", 'public.image') +// SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` > _Note:_ The return type of a function must be [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types). From 7ee7afc58db92d46b89939ec94df2ec4c9359518 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Feb 2015 09:08:06 -0800 Subject: [PATCH 0183/1046] Support any collection type for the contains function Makes more sense in Swift 1.2 given Set. Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 219242de..7c3fb4b3 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -691,13 +691,12 @@ public func * (Expression?, Expression?) -> Expression<()> { return Expression<()>(literal: "*") } -public func contains(values: [V], column: Expression) -> Expression { - let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0.datatypeValue })) +public func contains(values: C, column: Expression) -> Expression { + let templates = join(", ", [String](count: countElements(values), repeatedValue: "?")) + return infix("IN", column, Expression(literal: "(\(templates))", map(values) { $0.datatypeValue })) } -public func contains(values: [V?], column: Expression) -> Expression { - let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0?.datatypeValue })) +public func contains(values: C, column: Expression) -> Expression { + return contains(values, Expression(column)) } // MARK: - Modifying From 159baa4dd60544387c11e44b619074595f5ca153 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 10:42:42 -0800 Subject: [PATCH 0184/1046] Document custom collations Signed-off-by: Stephen Celis --- Documentation/Index.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 871f4d5f..d5be95c1 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -52,6 +52,7 @@ - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) - [Custom SQL Functions](#custom-sql-functions) + - [Custom Collations](#custom-collations) - [Executing Arbitrary SQL](#executing-arbitrary-sql) - [Logging](#logging) @@ -1316,6 +1317,24 @@ for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` +## Custom Collations + +We can create custom collating sequences by calling `create(collation:)` on a database connection. + +``` swift +db.create(collation: "NODIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) +} +``` + +We can reference a custom collation using the `Custom` member of the `Collation` enumeration. + +``` +restaurants.order(collate(.Custom("NODIACRITIC"), name)) +// SELECT * FROM "restaurants" ORDER BY "name" COLLATE "NODIACRITIC" +``` + + ## Executing Arbitrary SQL Though we recommend you stick with SQLite.swift’s type-safe system whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. From e107dfba16e8aa0c0ed6c660f8b11c03e3bf07f7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 10:42:58 -0800 Subject: [PATCH 0185/1046] Use a consistent documentation voice Signed-off-by: Stephen Celis --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index d5be95c1..0bbc6949 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -645,7 +645,7 @@ users.filter(verified || balance >= 10_000) // SELECT * FROM "users" WHERE ("verified" OR ("balance" >= 10000.0)) ``` -You can build your own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). +We can build our own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). > _Note:_ SQLite.swift defines `filter` instead of `where` because `where` is [a reserved keyword](https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_906). @@ -1296,7 +1296,7 @@ attachments.filter(typeConformsTo(UTI, kUTTypeImage)) > _Note:_ The return type of a function must be [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types). -You can create loosely-typed functions by handling an array of raw arguments, instead. +We can create loosely-typed functions by handling an array of raw arguments, instead. ``` swift db.create(function: "typeConformsTo", deterministic: true) { args in From 9ddad9cff4a68268f3c4942bcc304e445a4227a8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 10:46:12 -0800 Subject: [PATCH 0186/1046] No need for Statement.database to ever change Signed-off-by: Stephen Celis --- SQLite/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index ec08311e..94bf5fad 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -30,7 +30,7 @@ public final class Statement { private let handle: COpaquePointer = nil - private var database: Database + private let database: Database internal init(_ database: Database, _ SQL: String) { self.database = database From e55fb57295bffd3a663a920552c6c9075c989522 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 11:16:58 -0800 Subject: [PATCH 0187/1046] Expose Cursor for raw statement row access Allows for direct access to a column value as any SQLite type without the intermediary array of optional values. let stmt = db.prepare("SELECT age FROM users") while stmt.step() { let age = stmt.cursor[0] as String // "30" // ... } The cursor should be the default value that is yielded to the for loop, but Swift's current inability to resolve the following prevents that from working well: for row in stmt { let age = row[0] as Binding? as? String // passes compilation let age = row[0] as? String // fails } If the above case is resolved (rdar://19997098), then we can avoid automatically unpacking every row into an array by default. This means the following won't work: let rows = Array(stmt) But the following will: let rows = map(stmt) { Array($0) } Signed-off-by: Stephen Celis --- SQLite Tests/StatementTests.swift | 10 +-- SQLite/Statement.swift | 122 +++++++++++++++++++----------- 2 files changed, 80 insertions(+), 52 deletions(-) diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index 7107d207..9f6cfe87 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -163,15 +163,13 @@ class StatementTests: XCTestCase { XCTAssertEqual(3, count) } - func test_row_returnsArrayOfValues() { + func test_cursor_returnsValues() { InsertUser(db, "alice") let stmt = db.prepare("SELECT id, email FROM users") - stmt.next() + stmt.cursor.step() - let row = stmt.row! - - XCTAssertEqual(1, row[0] as Int) - XCTAssertEqual("alice@example.com", row[1] as String) + XCTAssertEqual(1, stmt.cursor[0] as Int) + XCTAssertEqual("alice@example.com", stmt.cursor[1] as String) } } diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 94bf5fad..2873afa9 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -32,6 +32,8 @@ public final class Statement { private let database: Database + public lazy var cursor: Cursor = { Cursor(self) }() + internal init(_ database: Database, _ SQL: String) { self.database = database database.try(sqlite3_prepare_v2(database.handle, SQL, -1, &handle, nil)) @@ -39,9 +41,12 @@ public final class Statement { deinit { sqlite3_finalize(handle) } + public lazy var columnCount: Int = { [unowned self] in + return Int(sqlite3_column_count(self.handle)) + }() + public lazy var columnNames: [String] = { [unowned self] in - let count = sqlite3_column_count(self.handle) - return (0.. Statement { if !bindings.isEmpty { return run(bindings) } reset(clearBindings: false) - for _ in self {} + while cursor.step() {} return self } @@ -133,9 +138,8 @@ public final class Statement { public func scalar(bindings: Binding?...) -> Binding? { if !bindings.isEmpty { return scalar(bindings) } reset(clearBindings: false) - let value: Binding? = next()?[0] - for _ in self {} - return value + cursor.step() + return cursor[0] } /// :param: bindings A list of parameters to bind to the statement. @@ -189,57 +193,20 @@ public final class Statement { // MARK: - SequenceType extension Statement: SequenceType { - public typealias Generator = Statement - - public func generate() -> Generator { return self } + public func generate() -> Statement { return self } } // MARK: - GeneratorType extension Statement: GeneratorType { - /// A single row. - public typealias Element = [Binding?] - /// :returns: The next row from the result set (or nil). - public func next() -> Element? { - if status == SQLITE_DONE { return nil } - try(sqlite3_step(handle)) - return row - } - - /// :returns: The current row of an open statement. - public var row: Element? { - if status != SQLITE_ROW { return nil } - return (0.. [Binding?]? { + return cursor.step() ? Array(cursor) : nil } } -// MARK: - BooleanType -extension Statement: BooleanType { - - public var boolValue: Bool { return status == SQLITE_DONE } - -} - // MARK: - DebugPrintable extension Statement: DebugPrintable { @@ -258,3 +225,66 @@ public func || (lhs: Statement, rhs: @autoclosure () -> Statement) -> Statement if lhs.status == SQLITE_OK { lhs.run() } return lhs.failed ? rhs() : lhs } + +/// Cursors provide direct access to a statement's current row. +public struct Cursor { + + private unowned let statement: Statement + + private init(_ statement: Statement) { + self.statement = statement + } + + public func step() -> Bool { + statement.try(sqlite3_step(statement.handle)) + return statement.status == SQLITE_ROW + } + + public subscript(idx: Int) -> Blob { + let bytes = sqlite3_column_blob(statement.handle, Int32(idx)) + let length = sqlite3_column_bytes(statement.handle, Int32(idx)) + return Blob(bytes: bytes, length: Int(length)) + } + + public subscript(idx: Int) -> Double { + return Double(sqlite3_column_double(statement.handle, Int32(idx))) + } + + public subscript(idx: Int) -> Int { + return Int(sqlite3_column_int64(statement.handle, Int32(idx))) + } + + public subscript(idx: Int) -> String { + return String.fromCString(UnsafePointer(sqlite3_column_text(statement.handle, Int32(idx))))! + } + +} + +// MARK: - SequenceType +extension Cursor: SequenceType { + + public subscript(idx: Int) -> Binding? { + switch sqlite3_column_type(statement.handle, Int32(idx)) { + case SQLITE_BLOB: + return self[idx] as Blob + case SQLITE_FLOAT: + return self[idx] as Double + case SQLITE_INTEGER: + return self[idx] as Int + case SQLITE_NULL: + return nil + case SQLITE_TEXT: + return self[idx] as String + case let type: + assertionFailure("unsupported column type: \(type)") + } + } + + public func generate() -> GeneratorOf { + var idx = 0 + return GeneratorOf { + idx >= self.statement.columnCount ? Optional.None : self[idx++] + } + } + +} From 8431ca3c075eaaf062137fd44ba986693e2d1c08 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 11:53:28 -0800 Subject: [PATCH 0188/1046] Conform to Printable, not DebugPrintable DebugPrintable provides valuable information for debugging when left to the defaults. Meanwhile, let Database/Statement be printable: - Database prints the path - Statement prints the raw SQL Signed-off-by: Stephen Celis --- SQLite/Database.swift | 7 ++++--- SQLite/Statement.swift | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 90976112..dc7ca34f 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -468,10 +468,11 @@ public final class Database { } -extension Database: DebugPrintable { +// MARK: - Printable +extension Database: Printable { - public var debugDescription: String { - return "Database(\(String.fromCString(sqlite3_db_filename(handle, nil))!))" + public var description: String { + return String.fromCString(sqlite3_db_filename(handle, nil))! } } diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 2873afa9..892afe67 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -207,11 +207,11 @@ extension Statement: GeneratorType { } -// MARK: - DebugPrintable -extension Statement: DebugPrintable { +// MARK: - Printable +extension Statement: Printable { - public var debugDescription: String { - return "Statement(\"\(String.fromCString(sqlite3_sql(handle))!)\")" + public var description: String { + return String.fromCString(sqlite3_sql(handle))! } } From 01724ad5a8283491ee3854eff31db75e151a818f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 11:56:32 -0800 Subject: [PATCH 0189/1046] Remove unneeded "unowned" Lazy blocks aren't actually retained, so there's no need to use "unowned" in these instances. Signed-off-by: Stephen Celis --- SQLite/Statement.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 892afe67..02ae5531 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -41,12 +41,10 @@ public final class Statement { deinit { sqlite3_finalize(handle) } - public lazy var columnCount: Int = { [unowned self] in - return Int(sqlite3_column_count(self.handle)) - }() + public lazy var columnCount: Int = { Int(sqlite3_column_count(self.handle)) }() - public lazy var columnNames: [String] = { [unowned self] in - return (0.. Date: Sun, 1 Mar 2015 09:14:40 -0800 Subject: [PATCH 0190/1046] Remove unnecessary conversions Signed-off-by: Stephen Celis --- SQLite/Database.swift | 2 +- SQLite/Statement.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index dc7ca34f..ec626e35 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -406,7 +406,7 @@ public final class Database { let length = sqlite3_value_bytes(value) return Blob(bytes: bytes, length: Int(length)) case SQLITE_FLOAT: - return Double(sqlite3_value_double(value)) + return sqlite3_value_double(value) case SQLITE_INTEGER: return Int(sqlite3_value_int64(value)) case SQLITE_NULL: diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 02ae5531..145e4ee5 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -66,7 +66,7 @@ public final class Statement { public func bind(values: [Binding?]) -> Statement { if values.isEmpty { return self } reset() - assert(values.count == Int(sqlite3_bind_parameter_count(handle)), "\(Int(sqlite3_bind_parameter_count(handle))) values expected, \(values.count) passed") + assert(values.count == Int(sqlite3_bind_parameter_count(handle)), "\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed") for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) } return self } @@ -245,7 +245,7 @@ public struct Cursor { } public subscript(idx: Int) -> Double { - return Double(sqlite3_column_double(statement.handle, Int32(idx))) + return sqlite3_column_double(statement.handle, Int32(idx)) } public subscript(idx: Int) -> Int { From 12cdc95e95c899fd64d144f4d78bd76d0b672798 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Feb 2015 13:28:12 -0800 Subject: [PATCH 0191/1046] Add basic documentation to Expression Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 7c3fb4b3..6032a86a 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -22,39 +22,58 @@ // THE SOFTWARE. // +/// A typed SQL expression that wraps a raw string and bindings. public struct Expression { public let SQL: String + public let bindings: [Binding?] + /// Builds a SQL expression with a literal string and an optional list of + /// bindings. + /// + /// :param: SQL A SQL string. + /// + /// :param: bindings Values to be bound to the given SQL. public init(literal SQL: String = "", _ bindings: [Binding?] = []) { (self.SQL, self.bindings) = (SQL, bindings) } + /// Builds a SQL expression with a quoted identifier. + /// + /// :param: identifier A SQL identifier (*e.g.*, a column name). public init(_ identifier: String) { self.init(literal: quote(identifier: identifier)) } - public init(_ expression: Expression) { - self.init(literal: expression.SQL, expression.bindings) - } - + /// Builds a SQL expression with the given value. + /// + /// :param: value An encodable SQL value. public init(value: V?) { self.init(binding: value?.datatypeValue) } + /// Builds a SQL expression with the given value. + /// + /// :param: binding A raw SQL value. private init(binding: Binding?) { self.init(literal: "?", [binding]) } + /// Returns an ascending sort version of the expression. public var asc: Expression<()> { return Expression.join(" ", [self, Expression(literal: "ASC")]) } + /// Returns an descending sort version of the expression. public var desc: Expression<()> { return Expression.join(" ", [self, Expression(literal: "DESC")]) } + internal init(_ expression: Expression) { + self.init(literal: expression.SQL, expression.bindings) + } + internal static func join(separator: String, _ expressions: [Expressible]) -> Expression<()> { var (SQL, bindings) = ([String](), [Binding?]()) for expressible in expressions { From eff065121c34926f64823f6c0917e886b0ec13cf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 1 Mar 2015 09:11:27 -0800 Subject: [PATCH 0192/1046] Make asc/desc idempotent, query reversible This commit adds behavior that internally tracks ascending/descending state on expressions, expanding only when necessary. This makes it possible to reverse queries by reversing each of the stored sort descriptors. let sorted = users.order(name) // SELECT * FROM "users" ORDER BY "name" let reversed = sorted.reverse() // SELECT * FROM "users" ORDER BY "name" DESC let rereversed = reversed.reverse() // SELECT * FROM "users" ORDER BY "name" ASC This also allows for more array-like access, including `last`: users.last // SELECT * FROM "users" ORDER BY "ROWID" DESC LIMIT 1 Signed-off-by: Stephen Celis --- SQLite Tests/QueryTests.swift | 21 +++++++++++++++ SQLite Tests/SchemaTests.swift | 2 +- SQLite/Expression.swift | 38 ++++++++++++++++++++------ SQLite/Query.swift | 49 +++++++++++++++++++++++----------- SQLite/Schema.swift | 2 +- 5 files changed, 87 insertions(+), 25 deletions(-) diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index b1bcce71..fe351554 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -198,6 +198,27 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } + func test_order_whenChained_overridesOrder() { + let query = users.order(email).order(age) + + let SQL = "SELECT * FROM \"users\" ORDER BY \"age\"" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + } + + func test_reverse_withoutOrder_ordersByRowIdDescending() { + let query = users.reverse() + + let SQL = "SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + } + + func test_reverse_withOrder_reversesOrder() { + let query = users.order(age, email.desc).reverse() + + let SQL = "SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + } + func test_limit_compilesLimitClause() { let query = users.limit(5) diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 64fc3709..0bbba376 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -350,7 +350,7 @@ class SchemaTests: XCTestCase { func test_index_withMultipleColumns_executesCompoundIndexStatement() { CreateUsersTable(db) ExpectExecution(db, - "CREATE INDEX \"index_users_on_age_DESC_email\" ON \"users\" (\"age\" DESC, \"email\")", + "CREATE INDEX \"index_users_on_age_email\" ON \"users\" (\"age\" DESC, \"email\")", db.create(index: users, on: age.desc, email) ) } diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 6032a86a..5f522843 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -29,6 +29,8 @@ public struct Expression { public let bindings: [Binding?] + internal var ascending: Bool? + /// Builds a SQL expression with a literal string and an optional list of /// bindings. /// @@ -61,17 +63,17 @@ public struct Expression { } /// Returns an ascending sort version of the expression. - public var asc: Expression<()> { - return Expression.join(" ", [self, Expression(literal: "ASC")]) + public var asc: Expression { + var expression = self + expression.ascending = true + return expression } /// Returns an descending sort version of the expression. - public var desc: Expression<()> { - return Expression.join(" ", [self, Expression(literal: "DESC")]) - } - - internal init(_ expression: Expression) { - self.init(literal: expression.SQL, expression.bindings) + public var desc: Expression { + var expression = self + expression.ascending = false + return expression } internal static func join(separator: String, _ expressions: [Expressible]) -> Expression<()> { @@ -84,6 +86,24 @@ public struct Expression { return Expression<()>(literal: Swift.join(separator, SQL), bindings) } + internal init(_ expression: Expression) { + self.init(literal: expression.SQL, expression.bindings) + ascending = expression.ascending + } + + internal var ordered: Expression<()> { + if let ascending = ascending { + return Expression.join(" ", [self, Expression(literal: ascending ? "ASC" : "DESC")]) + } + return Expression<()>(self) + } + + internal func reverse() -> Expression { + var expression = self + expression.ascending = expression.ascending.map(!) ?? false + return expression + } + // naïve compiler for statements that can't be bound, e.g., CREATE TABLE internal func compile() -> String { var idx = 0 @@ -882,6 +902,8 @@ public postfix func -- (column: Expression) -> Setter { // MARK: - Internal +internal let rowid = Expression("ROWID") + internal func transcode(literal: Binding?) -> String { if let literal = literal { if let literal = literal as? Blob { return literal.description } diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 69180c5a..47ce570b 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -239,6 +239,15 @@ public struct Query { /// /// :returns: A query with the given ORDER BY clause applied. public func order(by: Expressible...) -> Query { + return order(by) + } + + /// Sets an ORDER BY clause on the query. + /// + /// :param: by An ordered list of columns and directions to sort by. + /// + /// :returns: A query with the given ORDER BY clause applied. + public func order(by: [Expressible]) -> Query { var query = self query.order = by return query @@ -398,7 +407,7 @@ public struct Query { private var orderClause: Expressible? { if order.count == 0 { return nil } - let clause = Expression<()>.join(", ", order) + let clause = Expression<()>.join(", ", order.map { $0.expression.ordered }) return Expression<()>(literal: "ORDER BY \(clause.SQL)", clause.bindings) } @@ -413,17 +422,6 @@ public struct Query { return nil } - // MARK: - Array - - /// The first result (or nil if the query has no results). - public var first: Row? { - var generator = limit(1).generate() - return generator.next() - } - - /// Returns true if the query has no results. - public var isEmpty: Bool { return first == nil } - // MARK: - Modifying /// Runs an INSERT statement against the query. @@ -585,9 +583,6 @@ public struct Query { // MARK: - Aggregate Functions - /// Runs count(*) against the query and returns it. - public var count: Int { return calculate(SQLite_count(*))! } - /// Runs count() against the query. /// /// :param: column The column used for the calculation. @@ -715,6 +710,30 @@ public struct Query { return select(expression).selectStatement.scalar() as? U } + // MARK: - Array + + /// Runs count(*) against the query and returns it. + public var count: Int { return calculate(SQLite_count(*))! } + + /// Returns true if the query has no rows. + public var isEmpty: Bool { return first == nil } + + /// The first row (or nil if the query returns no rows). + public var first: Row? { + var generator = limit(1).generate() + return generator.next() + } + + /// The last row (or nil if the query returns no rows). + public var last: Row? { + return reverse().first + } + + /// A query in reverse order. + public func reverse() -> Query { + return order(order.isEmpty ? [rowid.desc] : order.map { $0.expression.reverse() }) + } + } /// A row object. Returned by iterating over a Query. Provides typed subscript diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index cddfad49..1be991db 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -117,7 +117,7 @@ public extension Database { on columns: Expressible... ) -> Statement { let create = createSQL("INDEX", false, unique, ifNotExists, indexName(table, on: columns)) - let joined = Expression<()>.join(", ", columns) + let joined = Expression<()>.join(", ", columns.map { $0.expression.ordered }) return run("\(create) ON \(quote(identifier: table.tableName)) (\(joined.compile()))") } From 5380d5b502bd76ea99fe0ec54785f6bacd1cb8c1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 1 Mar 2015 22:39:06 -0800 Subject: [PATCH 0193/1046] Preserve 64-bit integers on 32-bit platforms This commit ensures the preservation of 64-bit integers on 32-bit platforms by replacing Int with Int64 as the basic, raw binding type. Int (like Bool before it) is supported in the expression layer as a Value type. The one part of the query layer that has changed is that rowids are now emitted as Int64, which means you should probably update your expressions: let id = Expression("id") Because integer literals convert to Double where they can (and not Int64), there were some confusing results in the raw SQL layer: db.scalar("SELECT * FROM users WHERE id = ?", 1) // SELECT * FROM users WHERE id = 1.0 To avoid this (and make binding simpler), both Int and Bool now conform to Bindable. db.run("SELECT * FROM users WHERE admin = ?", false) When a row is unpacked into an array of bindings or scalar, however, INTEGER values will always become Int64. Thus some gotchas: for row in stmt { row[0] as! Int // crashes, even if an integer } stmt.scalar() as? Int // silently fails, even if an integer Cursors, meanwhile, can return Bool and Int values just fine. while stmt.cursor.step() { stmt.cursor[0] as Int } Signed-off-by: Stephen Celis --- Documentation/Index.md | 28 +- README.md | 8 +- SQLite Tests/DatabaseTests.swift | 12 +- SQLite Tests/QueryTests.swift | 24 +- SQLite Tests/SchemaTests.swift | 8 +- SQLite Tests/StatementTests.swift | 20 +- SQLite/Database.swift | 18 +- SQLite/Expression.swift | 430 +++++++++++++++--------------- SQLite/Query.swift | 63 +++-- SQLite/Schema.swift | 78 +++--- SQLite/Statement.swift | 36 ++- SQLite/Value.swift | 68 ++--- 12 files changed, 421 insertions(+), 372 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 0bbc6949..2b994610 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -194,13 +194,15 @@ SQLite.swift comes with a typed expression layer that directly maps [Swift types | Swift Type | SQLite Type | | --------------- | ----------- | -| `Int` | `INTEGER` | +| `Int64`* | `INTEGER` | | `Double` | `REAL` | | `String` | `TEXT` | | `nil` | `NULL` | -| `SQLite.Blob`* | `BLOB` | +| `SQLite.Blob`† | `BLOB` | -> *SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes. +> *While `Int64` is the basic, raw type (to preserve 64-bit integers on 32-bit platforms), `Int` and `Bool` work transparently. +> +> †SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes. > > See [Custom Types](#custom-types) for more information about extending other classes and structures to work with SQLite.swift. @@ -212,7 +214,7 @@ These expressions (in the form of the structure, [`Expression`](#expressions)) b Expressions are generic structures associated with a type ([built-in](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and (optionally) values to bind to that SQL. Typically, you will only explicitly create expressions to describe your columns, and typically only once per column. ``` swift -let id = Expression("id") +let id = Expression("id") let email = Expression("email") let balance = Expression("balance") let verified = Expression("verified") @@ -289,7 +291,7 @@ The `column` function is used for a single column definition. It takes an [expre > _Note:_ The `primaryKey` parameter cannot be used alongside `defaultValue` or `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). > - > Primary keys cannot be optional (`Expression`). + > Primary keys cannot be optional (`Expression`). - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns). @@ -321,7 +323,7 @@ The `column` function is used for a single column definition. It takes an [expre // "email" TEXT NOT NULL COLLATE "NOCASE" ``` - - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift t.column(user_id, references: users[id]) @@ -383,7 +385,7 @@ users.insert(email <- "alice@mac.com", name <- "Alice")? The `insert` function can return several different types that are useful in different contexts. - - An `Int?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity. + - An `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity. ``` swift if let insertId = users.insert(email <- "alice@mac.com") { @@ -414,7 +416,7 @@ The `insert` function can return several different types that are useful in diff // COMMIT TRANSACTION; ``` - - A tuple of the above [`ROWID`][ROWID] and statement: `(id: Int?, statement: Statement)`, for flexibility. + - A tuple of the above [`ROWID`][ROWID] and statement: `(id: Int64?, statement: Statement)`, for flexibility. ``` swift let (id, statement) = users.insert(email <- "alice@mac.com") @@ -756,14 +758,14 @@ users.filter(name != nil).count - `max` takes a comparable column expression and returns the largest value if any exists. ``` swift - users.max(id) // -> Int? + users.max(id) // -> Int64? // SELECT max("id") FROM "users" ``` - `min` takes a comparable column expression and returns the smallest value if any exists. ``` swift - users.min(id) // -> Int? + users.min(id) // -> Int64? // SELECT min("id") FROM "users" ``` @@ -952,7 +954,7 @@ The `alter` function shares several of the same [`column` function parameters](# // email TEXT NOT NULL COLLATE "NOCASE" ``` - - `references` adds a `REFERENCES` clause to `Int` (and `Int?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift db.alter(table: posts, add: user_id, references: users[id]) @@ -1392,14 +1394,14 @@ Though we recommend you stick with SQLite.swift’s type-safe system whenever po - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. ``` swift - db.scalar("SELECT count(*) FROM users") as Int + db.scalar("SELECT count(*) FROM users") as Int64 ``` Statements also have a `scalar` function, which can optionally re-bind values at execution. ``` swift let stmt = db.prepare("SELECT count (*) FROM users") - stmt.scalar() as Int + stmt.scalar() as Int64 ``` diff --git a/README.md b/README.md index a1d9772f..9e8083aa 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ import SQLite let db = Database("path/to/db.sqlite3") let users = db["users"] -let id = Expression("id") +let id = Expression("id") let name = Expression("name") let email = Expression("email") @@ -84,8 +84,8 @@ for email in ["betty@icloud.com", "cathy@icloud.com"] { } db.totalChanges // 3 -db.lastChanges // {Some 1} -db.lastId // {Some 3} +db.lastChanges // 1 +db.lastId // 3 for row in db.prepare("SELECT id, email FROM users") { println("id: \(row[0]), email: \(row[1])") @@ -93,7 +93,7 @@ for row in db.prepare("SELECT id, email FROM users") { // id: Optional(3), email: Optional("cathy@icloud.com") } -db.scalar("SELECT count(*) FROM users") // {Some 2} +db.scalar("SELECT count(*) FROM users") // 2 ``` [Read the documentation][See Documentation] or explore more, diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index bf2028d1..7aaf894e 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -66,10 +66,10 @@ class DatabaseTests: XCTestCase { } func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as Int) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as Int) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as Int) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as Int) + XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = 0") as Int64) + XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as Int64) + XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as Int64) + XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as Int64) } func test_transaction_beginsAndCommitsStatements() { @@ -217,14 +217,14 @@ class DatabaseTests: XCTestCase { db.create(collation: "NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as Int) + XCTAssertEqual(Int64(1), db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as Int64) } func test_createCollation_createsQuotableCollation() { db.create(collation: "NO DIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as Int) + XCTAssertEqual(Int64(1), db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as Int64) } } diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index fe351554..5280e40f 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -6,12 +6,12 @@ class QueryTests: XCTestCase { let db = Database() var users: Query { return db["users"] } - let id = Expression("id") + let id = Expression("id") let email = Expression("email") let age = Expression("age") let salary = Expression("salary") let admin = Expression("admin") - let manager_id = Expression("manager_id") + let manager_id = Expression("manager_id") override func setUp() { super.setUp() @@ -105,7 +105,7 @@ class QueryTests: XCTestCase { let managers = db["users"].alias("managers") let aliceId = users.insert(email <- "alice@example.com")! - users.insert(email <- "betty@example.com", manager_id <- aliceId)! + users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId))! let query = users .select(users[*], managers[*]) @@ -119,10 +119,10 @@ class QueryTests: XCTestCase { func test_namespacedColumnRowValueAccess() { let aliceId = users.insert(email <- "alice@example.com")! - let bettyId = users.insert(email <- "betty@example.com", manager_id <- aliceId)! + let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId))! let alice = users.first! - XCTAssertEqual(aliceId, alice[id]) + XCTAssertEqual(Int64(aliceId), alice[id]) let managers = db["users"].alias("managers") let query = users.join(managers, on: managers[id] == users[manager_id]) @@ -307,7 +307,7 @@ class QueryTests: XCTestCase { func test_first_returnsTheFirstRow() { InsertUsers(db, "alice", "betsy") ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 1]) { _ in - XCTAssertEqual(1, self.users.first![self.id]) + XCTAssertEqual(Int64(1), self.users.first![self.id]) } } @@ -323,7 +323,7 @@ class QueryTests: XCTestCase { let SQL = "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in - XCTAssertEqual(1, self.users.insert(self.email <- "alice@example.com", self.age <- 30).id!) + XCTAssertEqual(Int64(1), self.users.insert(self.email <- "alice@example.com", self.age <- 30).id!) } XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).id == nil) @@ -342,7 +342,7 @@ class QueryTests: XCTestCase { let table = db["timestamps"] ExpectExecutions(db, ["INSERT INTO \"timestamps\" DEFAULT VALUES": 1]) { _ in - XCTAssertEqual(1, table.insert().id!) + XCTAssertEqual(Int64(1), table.insert().id!) } } @@ -350,10 +350,10 @@ class QueryTests: XCTestCase { let SQL = "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)" ExpectExecutions(db, [SQL: 1]) { _ in - XCTAssertEqual(1, self.users.replace(self.email <- "alice@example.com", self.age <- 30).id!) + XCTAssertEqual(Int64(1), self.users.replace(self.email <- "alice@example.com", self.age <- 30).id!) } - XCTAssertEqual(1, self.users.replace(self.id <- 1, self.email <- "bob@example.com", self.age <- 30).id!) + XCTAssertEqual(Int64(1), self.users.replace(self.id <- 1, self.email <- "bob@example.com", self.age <- 30).id!) } func test_update_updatesRows() { @@ -442,7 +442,7 @@ class QueryTests: XCTestCase { } func test_valueExtension_serializesAndDeserializes() { - let id = Expression("id") + let id = Expression("id") let timestamp = Expression("timestamp") let touches = db["touches"] db.create(table: touches) { t in @@ -454,7 +454,7 @@ class QueryTests: XCTestCase { touches.insert(timestamp <- date)! XCTAssertEqual(touches.first!.get(timestamp)!, date) - XCTAssertNil(touches.filter(id == touches.insert()!).first!.get(timestamp)) + XCTAssertNil(touches.filter(id == Int64(touches.insert()!)).first!.get(timestamp)) XCTAssert(touches.filter(timestamp < NSDate()).first != nil) } diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 0bbba376..4bb98b9f 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -1,12 +1,12 @@ import XCTest import SQLite -private let id = Expression("id") +private let id = Expression("id") private let email = Expression("email") private let age = Expression("age") private let salary = Expression("salary") private let admin = Expression("admin") -private let manager_id = Expression("manager_id") +private let manager_id = Expression("manager_id") class SchemaTests: XCTestCase { @@ -233,7 +233,7 @@ class SchemaTests: XCTestCase { func test_createTable_foreignKey_withCompositeKey_buildsForeignKeyTableConstraint() { let users = self.users - let manager_id = Expression("manager_id") // required + let manager_id = Expression("manager_id") // required ExpectExecution(db, "CREATE TABLE \"users\" (" + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + @@ -296,7 +296,7 @@ class SchemaTests: XCTestCase { func test_alterTable_withForeignKey_addsRegularColumn() { CreateUsersTable(db) - let column = Expression("parent_id") + let column = Expression("parent_id") ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")", db.alter(table: users, add: column, references: users[id]) diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index 9f6cfe87..d86c38ae 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -105,40 +105,40 @@ class StatementTests: XCTestCase { func test_scalar_withNoParameters() { let zero = db.prepare("SELECT 0") - XCTAssertEqual(0, zero.scalar() as Int) + XCTAssertEqual(Int64(0), zero.scalar() as Int64) } func test_scalar_withNoParameters_retainsBindings() { let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?", 21) - XCTAssertEqual(0, count.scalar() as Int) + XCTAssertEqual(Int64(0), count.scalar() as Int64) InsertUser(db, "alice", age: 21) - XCTAssertEqual(1, count.scalar() as Int) + XCTAssertEqual(Int64(1), count.scalar() as Int64) } func test_scalar_withVariadicParameters() { InsertUser(db, "alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar(21) as Int) + XCTAssertEqual(Int64(1), count.scalar(21) as Int64) } func test_scalar_withArrayOfParameters() { InsertUser(db, "alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar([21]) as Int) + XCTAssertEqual(Int64(1), count.scalar([21]) as Int64) } func test_scalar_withNamedParameters() { InsertUser(db, "alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= $age") - XCTAssertEqual(1, count.scalar(["$age": 21]) as Int) + XCTAssertEqual(Int64(1), count.scalar(["$age": 21]) as Int64) } func test_scalar_withParameters_updatesBindings() { InsertUser(db, "alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar(21) as Int) - XCTAssertEqual(0, count.scalar(22) as Int) + XCTAssertEqual(Int64(1), count.scalar(21) as Int64) + XCTAssertEqual(Int64(0), count.scalar(22) as Int64) } func test_scalar_doubleReturnValue() { @@ -146,7 +146,7 @@ class StatementTests: XCTestCase { } func test_scalar_intReturnValue() { - XCTAssertEqual(3, db.scalar("SELECT 3") as Int) + XCTAssertEqual(Int64(3), db.scalar("SELECT 3") as Int64) } func test_scalar_stringReturnValue() { @@ -168,7 +168,7 @@ class StatementTests: XCTestCase { let stmt = db.prepare("SELECT id, email FROM users") stmt.cursor.step() - XCTAssertEqual(1, stmt.cursor[0] as Int) + XCTAssertEqual(Int64(1), stmt.cursor[0] as Int64) XCTAssertEqual("alice@example.com", stmt.cursor[1] as String) } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index ec626e35..b77ab1b1 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -54,8 +54,8 @@ public final class Database { // MARK: - /// The last row id inserted into the database via this connection. - public var lastId: Int? { - let lastId = Int(sqlite3_last_insert_rowid(handle)) + public var lastId: Int64? { + let lastId = sqlite3_last_insert_rowid(handle) return lastId == 0 ? nil : lastId } @@ -336,13 +336,13 @@ public final class Database { // MARK: - Configuration public var foreignKeys: Bool { - get { return Bool.fromDatatypeValue(scalar("PRAGMA foreign_keys") as Int) } + get { return Bool.fromDatatypeValue(scalar("PRAGMA foreign_keys") as Int64) } set { run("PRAGMA foreign_keys = \(transcode(newValue.datatypeValue))") } } public var userVersion: Int { - get { return scalar("PRAGMA user_version") as Int } - set { run("PRAGMA user_version = \(transcode(newValue))") } + get { return Int(scalar("PRAGMA user_version") as Int64) } + set { run("PRAGMA user_version = \(transcode(Int64(newValue)))") } } // MARK: - Handlers @@ -398,7 +398,7 @@ public final class Database { /// should return a raw SQL value (or nil). public func create(#function: String, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { try(SQLiteCreateFunction(handle, function, deterministic ? 1 : 0) { context, argc, argv in - let arguments: [Binding?] = map(0.. { + return Expression(value: self) + } + +} + +extension Int64: Expressible { + public var expression: Expression<()> { return Expression(binding: self) } @@ -181,98 +189,98 @@ public func + (lhs: Expression, rhs: String) -> Expression { r public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(binding: rhs) } -public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(binding: rhs) } -public func + (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } -public func + (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } - -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(binding: rhs) } -public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(binding: rhs) } -public func - (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) - rhs } -public func - (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) - rhs } - -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(binding: rhs) } -public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(binding: rhs) } -public func * (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) * rhs } -public func * (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) * rhs } - -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(binding: rhs) } -public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(binding: rhs) } -public func / (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) / rhs } -public func / (lhs: V, rhs: Expression) -> Expression { return Expression(binding: lhs) / rhs } - -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(binding: rhs) } -public func % (lhs: Expression, rhs: Int) -> Expression { return lhs % Expression(binding: rhs) } -public func % (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) % rhs } -public func % (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) % rhs } - -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(binding: rhs) } -public func << (lhs: Expression, rhs: Int) -> Expression { return lhs << Expression(binding: rhs) } -public func << (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) << rhs } -public func << (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) << rhs } - -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(binding: rhs) } -public func >> (lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(binding: rhs) } -public func >> (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) >> rhs } -public func >> (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) >> rhs } - -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(binding: rhs) } -public func & (lhs: Expression, rhs: Int) -> Expression { return lhs & Expression(binding: rhs) } -public func & (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) & rhs } -public func & (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) & rhs } - -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(binding: rhs) } -public func | (lhs: Expression, rhs: Int) -> Expression { return lhs | Expression(binding: rhs) } -public func | (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) | rhs } -public func | (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) | rhs } - -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(binding: rhs) } -public func ^ (lhs: Expression, rhs: Int) -> Expression { return lhs ^ Expression(binding: rhs) } -public func ^ (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) ^ rhs } -public func ^ (lhs: Int, rhs: Expression) -> Expression { return Expression(binding: lhs) ^ rhs } - -public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } +public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } +public func + (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } +public func + (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } + +public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } +public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } +public func - (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } +public func - (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } + +public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } +public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } +public func * (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } +public func * (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } + +public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } +public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } +public func / (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } +public func / (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } + +public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func % (lhs: Expression, rhs: V) -> Expression { return lhs % Expression(value: rhs) } +public func % (lhs: Expression, rhs: V) -> Expression { return lhs % Expression(value: rhs) } +public func % (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } +public func % (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } + +public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func << (lhs: Expression, rhs: V) -> Expression { return lhs << Expression(value: rhs) } +public func << (lhs: Expression, rhs: V) -> Expression { return lhs << Expression(value: rhs) } +public func << (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } +public func << (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } + +public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func >> (lhs: Expression, rhs: V) -> Expression { return lhs >> Expression(value: rhs) } +public func >> (lhs: Expression, rhs: V) -> Expression { return lhs >> Expression(value: rhs) } +public func >> (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } +public func >> (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } + +public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func & (lhs: Expression, rhs: V) -> Expression { return lhs & Expression(value: rhs) } +public func & (lhs: Expression, rhs: V) -> Expression { return lhs & Expression(value: rhs) } +public func & (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } +public func & (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } + +public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } +public func | (lhs: Expression, rhs: V) -> Expression { return lhs | Expression(value: rhs) } +public func | (lhs: Expression, rhs: V) -> Expression { return lhs | Expression(value: rhs) } +public func | (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } +public func | (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } + +public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } +public func ^ (lhs: Expression, rhs: V) -> Expression { return lhs ^ Expression(value: rhs) } +public func ^ (lhs: Expression, rhs: V) -> Expression { return lhs ^ Expression(value: rhs) } +public func ^ (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } +public func ^ (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } + +public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public enum Collation { @@ -473,13 +481,13 @@ public func <= (lhs: V, rhs: Expression(rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public func ~= , V == I.Bound>(lhs: I, rhs: Expression) -> Expression { +public func ~= , V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { return Expression(literal: "\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) } -public func ~= , V == I.Bound>(lhs: I, rhs: Expression) -> Expression { +public func ~= , V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { return Expression(lhs ~= Expression(rhs)) } @@ -535,8 +543,8 @@ public prefix func ! (rhs: Expression) -> Expression { return wrap // MARK: - Core Functions -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } // FIXME: support Expression..., Expression when Swift supports inner variadic signatures public func coalesce(expressions: Expression...) -> Expression { @@ -647,37 +655,37 @@ public func count(#distinct: Expression) -> Expression { retu public func count(star: Star) -> Expression { return wrap(__FUNCTION__, star(nil, nil)) } -public func max(expression: Expression) -> Expression { +public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func max(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func min(expression: Expression) -> Expression { +public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func min(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } +public func average(expression: Expression) -> Expression { return wrap("avg", expression) } +public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } -public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } +public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } +public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } -public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } +public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } +public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } -public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } +public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } +public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } internal func SQLite_count(expression: Expression) -> Expression { return count(expression) } @@ -686,37 +694,37 @@ internal func SQLite_count(#distinct: Expression) -> Expression Expression { return count(star) } -internal func SQLite_max(expression: Expression) -> Expression { +internal func SQLite_max(expression: Expression) -> Expression { return max(expression) } internal func SQLite_max(expression: Expression) -> Expression { return max(expression) } -internal func SQLite_min(expression: Expression) -> Expression { +internal func SQLite_min(expression: Expression) -> Expression { return min(expression) } internal func SQLite_min(expression: Expression) -> Expression { return min(expression) } -internal func SQLite_average(expression: Expression) -> Expression { return average(expression) } -internal func SQLite_average(expression: Expression) -> Expression { return average(expression) } +internal func SQLite_average(expression: Expression) -> Expression { return average(expression) } +internal func SQLite_average(expression: Expression) -> Expression { return average(expression) } -internal func SQLite_average(#distinct: Expression) -> Expression { return average(distinct: distinct) } -internal func SQLite_average(#distinct: Expression) -> Expression { return average(distinct: distinct) } +internal func SQLite_average(#distinct: Expression) -> Expression { return average(distinct: distinct) } +internal func SQLite_average(#distinct: Expression) -> Expression { return average(distinct: distinct) } -internal func SQLite_sum(expression: Expression) -> Expression { return sum(expression) } -internal func SQLite_sum(expression: Expression) -> Expression { return sum(expression) } +internal func SQLite_sum(expression: Expression) -> Expression { return sum(expression) } +internal func SQLite_sum(expression: Expression) -> Expression { return sum(expression) } -internal func SQLite_sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } -internal func SQLite_sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } +internal func SQLite_sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } +internal func SQLite_sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } -internal func SQLite_total(expression: Expression) -> Expression { return total(expression) } -internal func SQLite_total(expression: Expression) -> Expression { return total(expression) } +internal func SQLite_total(expression: Expression) -> Expression { return total(expression) } +internal func SQLite_total(expression: Expression) -> Expression { return total(expression) } -internal func SQLite_total(#distinct: Expression) -> Expression { return total(distinct: distinct) } -internal func SQLite_total(#distinct: Expression) -> Expression { return total(distinct: distinct) } +internal func SQLite_total(#distinct: Expression) -> Expression { return total(distinct: distinct) } +internal func SQLite_total(#distinct: Expression) -> Expression { return total(distinct: distinct) } private func wrapDistinct(function: String, expression: Expression) -> Expression { return wrap(function, Expression<()>.join(" ", [Expression<()>(literal: "DISTINCT"), expression])) @@ -777,132 +785,136 @@ public func += (column: Expression, value: Expression) -> Sett public func += (column: Expression, value: String) -> Setter { return set(column, column + value) } public func += (column: Expression, value: String) -> Setter { return set(column, column + value) } -public func += >(column: Expression, value: Expression) -> Setter { +public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += >(column: Expression, value: Expression) -> Setter { +public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += >(column: Expression, value: Expression) -> Setter { +public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += >(column: Expression, value: Expression) -> Setter { +public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += >(column: Expression, value: V) -> Setter { return set(column, column + value) } -public func += >(column: Expression, value: V) -> Setter { return set(column, column + value) } +public func += (column: Expression, value: V) -> Setter { return set(column, column + value) } +public func += (column: Expression, value: V) -> Setter { return set(column, column + value) } -public func -= >(column: Expression, value: Expression) -> Setter { +public func -= (column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -= >(column: Expression, value: Expression) -> Setter { +public func -= (column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -= >(column: Expression, value: Expression) -> Setter { +public func -= (column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -= >(column: Expression, value: Expression) -> Setter { +public func -= (column: Expression, value: Expression) -> Setter { return set(column, column - value) } -public func -= >(column: Expression, value: V) -> Setter { return set(column, column - value) } -public func -= >(column: Expression, value: V) -> Setter { return set(column, column - value) } +public func -= (column: Expression, value: V) -> Setter { return set(column, column - value) } +public func -= (column: Expression, value: V) -> Setter { return set(column, column - value) } -public func *= >(column: Expression, value: Expression) -> Setter { +public func *= (column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *= >(column: Expression, value: Expression) -> Setter { +public func *= (column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *= >(column: Expression, value: Expression) -> Setter { +public func *= (column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *= >(column: Expression, value: Expression) -> Setter { +public func *= (column: Expression, value: Expression) -> Setter { return set(column, column * value) } -public func *= >(column: Expression, value: V) -> Setter { return set(column, column * value) } -public func *= >(column: Expression, value: V) -> Setter { return set(column, column * value) } +public func *= (column: Expression, value: V) -> Setter { return set(column, column * value) } +public func *= (column: Expression, value: V) -> Setter { return set(column, column * value) } -public func /= >(column: Expression, value: Expression) -> Setter { +public func /= (column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /= >(column: Expression, value: Expression) -> Setter { +public func /= (column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /= >(column: Expression, value: Expression) -> Setter { +public func /= (column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /= >(column: Expression, value: Expression) -> Setter { +public func /= (column: Expression, value: Expression) -> Setter { return set(column, column / value) } -public func /= >(column: Expression, value: V) -> Setter { +public func /= (column: Expression, value: V) -> Setter { return set(column, column / value) } -public func /= >(column: Expression, value: V) -> Setter { +public func /= (column: Expression, value: V) -> Setter { return set(column, column / value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Int) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Int) -> Setter { return set(column, column % value) } - -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Int) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Int) -> Setter { return set(column, column << value) } - -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Int) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Int) -> Setter { return set(column, column >> value) } - -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Int) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Int) -> Setter { return set(column, column & value) } - -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Int) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Int) -> Setter { return set(column, column | value) } - -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Int) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Int) -> Setter { return set(column, column ^ value) } - -public postfix func ++ (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column += 1 - return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) -} -public postfix func ++ (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column += 1 - return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) -} -public postfix func -- (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column -= 1 - return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) -} -public postfix func -- (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column -= 1 - return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) -} +public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } +public func %= (column: Expression, value: V) -> Setter { return set(column, column % value) } +public func %= (column: Expression, value: V) -> Setter { return set(column, column % value) } + +public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } +public func <<= (column: Expression, value: V) -> Setter { return set(column, column << value) } +public func <<= (column: Expression, value: V) -> Setter { return set(column, column << value) } + +public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } +public func >>= (column: Expression, value: V) -> Setter { return set(column, column >> value) } +public func >>= (column: Expression, value: V) -> Setter { return set(column, column >> value) } + +public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } +public func &= (column: Expression, value: V) -> Setter { return set(column, column & value) } +public func &= (column: Expression, value: V) -> Setter { return set(column, column & value) } + +public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } +public func |= (column: Expression, value: V) -> Setter { return set(column, column | value) } +public func |= (column: Expression, value: V) -> Setter { return set(column, column | value) } + +public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } +public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } +public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } + +public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } +public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } +public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } +public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } +//public postfix func ++ (column: Expression) -> Setter { +// // rdar://18825175 segfaults during archive: // column += 1 +// return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) +//} +//public postfix func ++ (column: Expression) -> Setter { +// // rdar://18825175 segfaults during archive: // column += 1 +// return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) +//} +//public postfix func -- (column: Expression) -> Setter { +// // rdar://18825175 segfaults during archive: // column -= 1 +// return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) +//} +//public postfix func -- (column: Expression) -> Setter { +// // rdar://18825175 segfaults during archive: // column -= 1 +// return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) +//} // MARK: - Internal -internal let rowid = Expression("ROWID") +internal let rowid = Expression("ROWID") internal func transcode(literal: Binding?) -> String { if let literal = literal { diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 47ce570b..f0481cd6 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -310,6 +310,9 @@ public struct Query { public subscript(column: Expression) -> Expression { return namespace(column) } public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } + public subscript(column: Expression) -> Expression { return namespace(column) } public subscript(column: Expression) -> Expression { return namespace(column) } @@ -436,14 +439,14 @@ public struct Query { /// :param: values A list of values to set. /// /// :returns: The row id. - public func insert(value: Setter, _ more: Setter...) -> Int? { return insert([value] + more).id } + public func insert(value: Setter, _ more: Setter...) -> Int64? { return insert([value] + more).id } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The row id and statement. - public func insert(value: Setter, _ more: Setter...) -> (id: Int?, statement: Statement) { + public func insert(value: Setter, _ more: Setter...) -> (id: Int64?, statement: Statement) { return insert([value] + more) } @@ -452,14 +455,14 @@ public struct Query { /// :param: values An array of values to set. /// /// :returns: The row id. - public func insert(values: [Setter]) -> Int? { return insert(values).id } + public func insert(values: [Setter]) -> Int64? { return insert(values).id } /// Runs an INSERT statement against the query. /// /// :param: values An array of values to set. /// /// :returns: The row id and statement. - public func insert(values: [Setter]) -> (id: Int?, statement: Statement) { + public func insert(values: [Setter]) -> (id: Int64?, statement: Statement) { let statement = insertStatement(values).run() return (statement.failed ? nil : database.lastId, statement) } @@ -474,11 +477,11 @@ public struct Query { return (statement.failed ? nil : database.lastChanges, statement) } - public func insert() -> Int? { return insert().id } + public func insert() -> Int64? { return insert().id } public func insert() -> Statement { return insert().statement } - public func insert() -> (id: Int?, statement: Statement) { + public func insert() -> (id: Int64?, statement: Statement) { let statement = database.run("INSERT INTO \(quote(identifier: tableName)) DEFAULT VALUES") return (statement.failed ? nil : database.lastId, statement) } @@ -495,14 +498,14 @@ public struct Query { /// :param: values A list of values to set. /// /// :returns: The row id. - public func replace(values: Setter...) -> Int? { return replace(values).id } + public func replace(values: Setter...) -> Int64? { return replace(values).id } /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The row id and statement. - public func replace(values: Setter...) -> (id: Int?, statement: Statement) { + public func replace(values: Setter...) -> (id: Int64?, statement: Statement) { return replace(values) } @@ -511,14 +514,14 @@ public struct Query { /// :param: values An array of values to set. /// /// :returns: The row id. - public func replace(values: [Setter]) -> Int? { return replace(values).id } + public func replace(values: [Setter]) -> Int64? { return replace(values).id } /// Runs a REPLACE statement against the query. /// /// :param: values An array of values to set. /// /// :returns: The row id and statement. - public func replace(values: [Setter]) -> (id: Int?, statement: Statement) { + public func replace(values: [Setter]) -> (id: Int64?, statement: Statement) { let statement = insertStatement(values, or: .Replace).run() return (statement.failed ? nil : database.lastId, statement) } @@ -639,10 +642,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The average value of the given column. - public func average(column: Expression) -> Double? { + public func average(column: Expression) -> Double? { return calculate(SQLite_average(column)) } - public func average(column: Expression) -> Double? { + public func average(column: Expression) -> Double? { return calculate(SQLite_average(column)) } @@ -651,10 +654,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The average value of the given column. - public func average(distinct column: Expression) -> Double? { + public func average(distinct column: Expression) -> Double? { return calculate(SQLite_average(distinct: column)) } - public func average(distinct column: Expression) -> Double? { + public func average(distinct column: Expression) -> Double? { return calculate(SQLite_average(distinct: column)) } @@ -663,10 +666,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The sum of the given column’s values. - public func sum(column: Expression) -> V? { + public func sum(column: Expression) -> V? { return calculate(SQLite_sum(column)) } - public func sum(column: Expression) -> V? { + public func sum(column: Expression) -> V? { return calculate(SQLite_sum(column)) } @@ -675,10 +678,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The sum of the given column’s values. - public func sum(distinct column: Expression) -> V? { + public func sum(distinct column: Expression) -> V? { return calculate(SQLite_sum(distinct: column)) } - public func sum(distinct column: Expression) -> V? { + public func sum(distinct column: Expression) -> V? { return calculate(SQLite_sum(distinct: column)) } @@ -687,10 +690,10 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The total of the given column’s values. - public func total(column: Expression) -> Double { + public func total(column: Expression) -> Double { return calculate(SQLite_total(column))! } - public func total(column: Expression) -> Double { + public func total(column: Expression) -> Double { return calculate(SQLite_total(column))! } @@ -699,15 +702,24 @@ public struct Query { /// :param: column The column used for the calculation. /// /// :returns: The total of the given column’s values. - public func total(distinct column: Expression) -> Double { + public func total(distinct column: Expression) -> Double { return calculate(SQLite_total(distinct: column))! } - public func total(distinct column: Expression) -> Double { + public func total(distinct column: Expression) -> Double { return calculate(SQLite_total(distinct: column))! } - private func calculate(expression: Expression) -> U? { - return select(expression).selectStatement.scalar() as? U + private func calculate(expression: Expression) -> V? { + if let scalar = select(expression).selectStatement.scalar() as? V.Datatype { + return (V.fromDatatypeValue(scalar) as V) + } + return nil + } + private func calculate(expression: Expression) -> V? { + if let scalar = select(expression).selectStatement.scalar() as? V.Datatype { + return (V.fromDatatypeValue(scalar) as V) + } + return nil } // MARK: - Array @@ -786,6 +798,9 @@ public struct Row { public subscript(column: Expression) -> Int { return get(column) } public subscript(column: Expression) -> Int? { return get(column) } + public subscript(column: Expression) -> Int64 { return get(column) } + public subscript(column: Expression) -> Int64? { return get(column) } + public subscript(column: Expression) -> String { return get(column) } public subscript(column: Expression) -> String? { return get(column) } diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 1be991db..303b309a 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -77,27 +77,27 @@ public extension Database { ])) } - public func alter( + public func alter( #table: Query, - add column: Expression, + add column: Expression, check: Expression? = nil, - defaultValue: String, + defaultValue: V, collate: Collation ) -> Statement { - return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ + return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ Expression<()>(literal: "COLLATE"), Expression<()>(collate.description) ])) } - public func alter( + public func alter( #table: Query, - add column: Expression, + add column: Expression, check: Expression? = nil, - defaultValue: String? = nil, + defaultValue: V? = nil, collate: Collation ) -> Statement { - let value = defaultValue.map { Expression(value: $0) } - return alter(table, define(Expression(column), nil, true, false, check, value, [ + let value = defaultValue.map { Expression(value: $0) } + return alter(table, define(Expression(column), nil, true, false, check, value, [ Expression<()>(literal: "COLLATE"), Expression<()>(collate.description) ])) } @@ -197,9 +197,9 @@ public final class SchemaBuilder { // MARK: PRIMARY KEY - public func column( - name: Expression, - primaryKey: Bool = false, + public func column( + name: Expression, + primaryKey: Bool, unique: Bool = false, check: Expression? = nil ) { @@ -214,8 +214,8 @@ public final class SchemaBuilder { } - public func column( - name: Expression, + public func column( + name: Expression, primaryKey: PrimaryKey?, unique: Bool = false, check: Expression? = nil @@ -225,19 +225,19 @@ public final class SchemaBuilder { // MARK: REFERENCES - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, - references: Expression + references: Expression ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>(literal: "REFERENCES"), namespace(references)] column(name, nil, false, unique, check, nil, expressions) } - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, references: Query @@ -250,19 +250,19 @@ public final class SchemaBuilder { ) } - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, - references: Expression + references: Expression ) { assertForeignKeysEnabled() let expressions: [Expressible] = [Expression<()>(literal: "REFERENCES"), namespace(references)] column(Expression(name), nil, true, unique, check, nil, expressions) } - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, references: Query @@ -277,46 +277,46 @@ public final class SchemaBuilder { // MARK: TEXT Columns (COLLATE) - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression?, + defaultValue value: Expression?, collate: Collation ) { let expressions: [Expressible] = [Expression<()>(literal: "COLLATE \(collate)")] column(name, nil, false, unique, check, value, expressions) } - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: String? = nil, + defaultValue value: V? = nil, collate: Collation ) { column(name, unique: unique, check: check, defaultValue: value.map { Expression(value: $0) }, collate: collate) } - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue value: Expression?, + defaultValue value: Expression?, collate: Collation ) { - column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) + column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) } - public func column( - name: Expression, + public func column( + name: Expression, unique: Bool = false, check: Expression? = nil, - defaultValue: String? = nil, + defaultValue: V? = nil, collate: Collation ) { - let value = defaultValue.map { Expression(value: $0) } - column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) + let value = defaultValue.map { Expression(value: $0) } + column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) } // MARK: - diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 145e4ee5..585fa8d7 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -88,16 +88,22 @@ public final class Statement { } private func bind(value: Binding?, atIndex idx: Int) { - if let value = value as? Blob { - try(sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT)) + if value == nil { + try(sqlite3_bind_null(self.handle, Int32(idx))) + } else if let value = value as? Blob { + try(sqlite3_bind_blob(self.handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT)) } else if let value = value as? Double { - try(sqlite3_bind_double(handle, Int32(idx), value)) - } else if let value = value as? Int { - try(sqlite3_bind_int64(handle, Int32(idx), Int64(value))) + try(sqlite3_bind_double(self.handle, Int32(idx), value)) + } else if let value = value as? Int64 { + try(sqlite3_bind_int64(self.handle, Int32(idx), value)) } else if let value = value as? String { try(sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT)) - } else { - try(sqlite3_bind_null(handle, Int32(idx))) + } else if let value = value as? Bool { + bind(value.datatypeValue, atIndex: idx) + } else if let value = value as? Int { + bind(value.datatypeValue, atIndex: idx) + } else if let value = value { + assertionFailure("tried to bind unexpected value \(value)") } } @@ -248,12 +254,20 @@ public struct Cursor { return sqlite3_column_double(statement.handle, Int32(idx)) } - public subscript(idx: Int) -> Int { - return Int(sqlite3_column_int64(statement.handle, Int32(idx))) + public subscript(idx: Int) -> Int64 { + return sqlite3_column_int64(statement.handle, Int32(idx)) } public subscript(idx: Int) -> String { - return String.fromCString(UnsafePointer(sqlite3_column_text(statement.handle, Int32(idx))))! + return String.fromCString(UnsafePointer(sqlite3_column_text(statement.handle, Int32(idx)))) ?? "" + } + + public subscript(idx: Int) -> Bool { + return Bool.fromDatatypeValue(self[idx]) + } + + public subscript(idx: Int) -> Int { + return Int.fromDatatypeValue(self[idx]) } } @@ -268,7 +282,7 @@ extension Cursor: SequenceType { case SQLITE_FLOAT: return self[idx] as Double case SQLITE_INTEGER: - return self[idx] as Int + return self[idx] as Int64 case SQLITE_NULL: return nil case SQLITE_TEXT: diff --git a/SQLite/Value.swift b/SQLite/Value.swift index 67c50858..256d7e26 100644 --- a/SQLite/Value.swift +++ b/SQLite/Value.swift @@ -73,15 +73,13 @@ public func ==(lhs: Blob, rhs: Blob) -> Bool { extension Blob: Binding, Value { - public typealias Datatype = Blob - public static var declaredDatatype = "BLOB" - public static func fromDatatypeValue(datatypeValue: Datatype) -> Blob { + public static func fromDatatypeValue(datatypeValue: Blob) -> Blob { return datatypeValue } - public var datatypeValue: Datatype { + public var datatypeValue: Blob { return self } @@ -97,66 +95,74 @@ extension Blob: Printable { } -extension Bool: Value { - - public typealias Datatype = Int +extension Double: Number, Value { - public static var declaredDatatype = Int.declaredDatatype + public static var declaredDatatype = "REAL" - public static func fromDatatypeValue(datatypeValue: Datatype) -> Bool { - return datatypeValue != 0 + public static func fromDatatypeValue(datatypeValue: Double) -> Double { + return datatypeValue } - public var datatypeValue: Datatype { - return self ? 1 : 0 + public var datatypeValue: Double { + return self } } -extension Double: Number, Value { - - public typealias Datatype = Double +extension Int64: Number, Value { - public static var declaredDatatype = "REAL" + public static var declaredDatatype = "INTEGER" - public static func fromDatatypeValue(datatypeValue: Datatype) -> Double { + public static func fromDatatypeValue(datatypeValue: Int64) -> Int64 { return datatypeValue } - public var datatypeValue: Datatype { + public var datatypeValue: Int64 { return self } } -extension Int: Number, Value { - - public typealias Datatype = Int +extension String: Binding, Value { - public static var declaredDatatype = "INTEGER" + public static var declaredDatatype = "TEXT" - public static func fromDatatypeValue(datatypeValue: Datatype) -> Int { + public static func fromDatatypeValue(datatypeValue: String) -> String { return datatypeValue } - public var datatypeValue: Datatype { + public var datatypeValue: String { return self } } -extension String: Binding, Value { +// MARK: - - public typealias Datatype = String +extension Bool: Binding, Value { - public static var declaredDatatype = "TEXT" + public static var declaredDatatype = Int64.declaredDatatype - public static func fromDatatypeValue(datatypeValue: Datatype) -> String { - return datatypeValue + public static func fromDatatypeValue(datatypeValue: Int64) -> Bool { + return datatypeValue != 0 } - public var datatypeValue: Datatype { - return self + public var datatypeValue: Int64 { + return self ? 1 : 0 + } + +} + +extension Int: Binding, Value { + + public static var declaredDatatype = Int64.declaredDatatype + + public static func fromDatatypeValue(datatypeValue: Int64) -> Int { + return Int(datatypeValue) + } + + public var datatypeValue: Int64 { + return Int64(self) } } From 26a4ef72b727bd076aeb8a3f6b86e662093dd968 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 2 Mar 2015 16:37:37 -0800 Subject: [PATCH 0194/1046] Add Stack Overflow link Signed-off-by: Stephen Celis --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9e8083aa..bf17f679 100644 --- a/README.md +++ b/README.md @@ -155,11 +155,14 @@ To install SQLite.swift with [SQLCipher][] support: ## Communication - - Found a **bug** or have a **feature request**? [Open an issue][5.1]. - - Want to **contribute**? [Submit a pull request][5.2]. - -[5.1]: https://github.com/stephencelis/SQLite.swift/issues/new -[5.2]: https://github.com/stephencelis/SQLite.swift/fork + - Need **help** or have a **general question**? [Ask on Stack + Overflow][5.1] (tag `sqlite.swift`). + - Found a **bug** or have a **feature request**? [Open an issue][5.2]. + - Want to **contribute**? [Submit a pull request][5.3]. + +[5.1]: http://stackoverflow.com/questions/tagged/sqlite.swift +[5.2]: https://github.com/stephencelis/SQLite.swift/issues/new +[5.3]: https://github.com/stephencelis/SQLite.swift/fork ## Author From 82f9c752a493097e74580b098f6bfba9e202e6cf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 2 Mar 2015 16:51:55 -0800 Subject: [PATCH 0195/1046] Add minimal documentation for AUTOINCREMENT Signed-off-by: Stephen Celis --- Documentation/Index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2b994610..7ed169ee 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -287,6 +287,9 @@ The `column` function is used for a single column definition. It takes an [expre ``` swift t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL + + t.column(id, primaryKey: .Autoincrement) + // "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ``` > _Note:_ The `primaryKey` parameter cannot be used alongside `defaultValue` or `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). From 74e80d3eb4b1108b37c2acec3321cf27f5037843 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 2 Mar 2015 17:02:22 -0800 Subject: [PATCH 0196/1046] Un-camelcase collating sequence names As with .Autoincrement, let the standard be to merely capitalize the raw, uppercased value. This is a breaking change and .NoCase and .RTrim must be changed to .Nocase and .Rtrim. Let's also better document RTRIM. Signed-off-by: Stephen Celis --- Documentation/Index.md | 10 ++++++++-- SQLite Tests/ExpressionTests.swift | 9 ++++----- SQLite Tests/SchemaTests.swift | 6 +++--- SQLite/Expression.swift | 8 ++++---- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7ed169ee..4e490bee 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -322,8 +322,11 @@ The `column` function is used for a single column definition. It takes an [expre - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - t.column(email, collate: .NoCase) + t.column(email, collate: .Nocase) // "email" TEXT NOT NULL COLLATE "NOCASE" + + t.column(name, collate: .Rtrim) + // "name" TEXT COLLATE "RTRIM" ``` - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) @@ -953,8 +956,11 @@ The `alter` function shares several of the same [`column` function parameters](# - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - t.column(email, collate: .NoCase) + t.column(email, collate: .Nocase) // email TEXT NOT NULL COLLATE "NOCASE" + + t.column(name, collate: .Rtrim) + // name TEXT COLLATE "RTRIM" ``` - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index d59ee3d9..f846542b 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -296,14 +296,13 @@ class ExpressionTests: XCTestCase { func test_collateOperator_withStringExpression_buildsCollationExpression() { ExpectExecutionMatches("('A' COLLATE \"BINARY\")", collate(.Binary, stringA)) - ExpectExecutionMatches("('B' COLLATE \"NOCASE\")", collate(.NoCase, stringB)) - ExpectExecutionMatches("('A' COLLATE \"RTRIM\")", collate(.RTrim, stringA)) + ExpectExecutionMatches("('B' COLLATE \"NOCASE\")", collate(.Nocase, stringB)) + ExpectExecutionMatches("('A' COLLATE \"RTRIM\")", collate(.Rtrim, stringA)) - let NoDiacritic = "NODIACRITIC" - db.create(collation: NoDiacritic) { lhs, rhs in + db.create(collation: "NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - ExpectExecutionMatches("('A' COLLATE \"NODIACRITIC\")", collate(.Custom(NoDiacritic), stringA)) + ExpectExecutionMatches("('A' COLLATE \"NODIACRITIC\")", collate(.Custom("NODIACRITIC"), stringA)) } func test_cast_buildsCastingExpressions() { diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 4bb98b9f..780ca18c 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -96,7 +96,7 @@ class SchemaTests: XCTestCase { func test_createTable_stringColumn_collation_buildsCollateClause() { ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)", db.create(table: users) { t in - t.column(email, collate: .NoCase) + t.column(email, collate: .Nocase) } ) } @@ -309,11 +309,11 @@ class SchemaTests: XCTestCase { let columnB = Expression("column_b") ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE \"NOCASE\"", - db.alter(table: users, add: columnA, defaultValue: "", collate: .NoCase) + db.alter(table: users, add: columnA, defaultValue: "", collate: .Nocase) ) ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE \"NOCASE\"", - db.alter(table: users, add: columnB, collate: .NoCase) + db.alter(table: users, add: columnB, collate: .Nocase) ) } diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 0935f418..5216be80 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -286,9 +286,9 @@ public enum Collation { case Binary - case NoCase + case Nocase - case RTrim + case Rtrim case Custom(String) @@ -300,9 +300,9 @@ extension Collation: Printable { switch self { case Binary: return "BINARY" - case NoCase: + case Nocase: return "NOCASE" - case RTrim: + case Rtrim: return "RTRIM" case Custom(let collation): return collation From fc0cf38e417e4fd81ebf98c6edc2191122537e9c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 11:26:59 -0800 Subject: [PATCH 0197/1046] Support expression aliasing This commit adds support for expression aliasing: let maxAge = max(age).alias("max_age") for row in users.select(name, maxAge).group(name) { println("the oldest \(row[name]) is \(row[maxAge]!)") // the oldest Stephen is 30 } // SELECT "name", (max("age")) AS "max_age" FROM "users" GROUP BY "name" Signed-off-by: Stephen Celis --- SQLite Tests/ExpressionTests.swift | 5 +++++ SQLite Tests/QueryTests.swift | 10 ++++++++++ SQLite/Expression.swift | 20 +++++++++++++++++++- SQLite/Query.swift | 2 +- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index f846542b..44e80c71 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -28,6 +28,11 @@ class ExpressionTests: XCTestCase { CreateUsersTable(db) } + func test_alias_aliasesExpression() { + let aliased = stringA.alias("string_a") + ExpectExecutionMatches("('A') AS \"string_a\"", aliased) + } + func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { ExpectExecutionMatches("('A' || 'A')", stringA + stringA) ExpectExecutionMatches("('A' || 'B')", stringA + stringB) diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index 5280e40f..ccd2c218 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -131,6 +131,16 @@ class QueryTests: XCTestCase { XCTAssertEqual(alice[email], betty[managers[email]]) } + func test_aliasedColumnRowValueAccess() { + users.insert(email <- "alice@example.com")! + + let alias = email.alias("user_email") + let query = users.select(alias) + let alice = query.first! + + XCTAssertEqual("alice@example.com", alice[alias]) + } + func test_filter_compilesWhereClause() { let query = users.filter(admin == true) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 5216be80..b940f331 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -29,7 +29,9 @@ public struct Expression { public let bindings: [Binding?] - internal var ascending: Bool? + private var ascending: Bool? + + private var original: Expressible? /// Builds a SQL expression with a literal string and an optional list of /// bindings. @@ -76,6 +78,14 @@ public struct Expression { return expression } + /// Returns an aliased version of the expression using AS. The expression is + /// expanded contextually (e.g., in SELECT clauses). + public func alias(alias: String) -> Expression { + var expression = Expression(alias) + expression.original = self + return expression + } + internal static func join(separator: String, _ expressions: [Expressible]) -> Expression<()> { var (SQL, bindings) = ([String](), [Binding?]()) for expressible in expressions { @@ -89,6 +99,7 @@ public struct Expression { internal init(_ expression: Expression) { self.init(literal: expression.SQL, expression.bindings) ascending = expression.ascending + original = expression.original } internal var ordered: Expression<()> { @@ -98,6 +109,13 @@ public struct Expression { return Expression<()>(self) } + internal var aliased: Expression { + if let aliased = original?.expression { + return Expression(literal: "(\(aliased.SQL)) AS \(SQL)", aliased.bindings + bindings) + } + return self + } + internal func reverse() -> Expression { var expression = self expression.ascending = expression.ascending.map(!) ?? false diff --git a/SQLite/Query.swift b/SQLite/Query.swift index f0481cd6..a62c53e5 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -389,7 +389,7 @@ public struct Query { private var selectClause: Expressible { var expressions: [Expressible] = [Expression<()>(literal: "SELECT")] if distinct { expressions.append(Expression<()>(literal: "DISTINCT")) } - expressions.append(Expression<()>.join(", ", columns)) + expressions.append(Expression<()>.join(", ", columns.map { $0.expression.aliased })) expressions.append(Expression<()>(literal: "FROM \(self)")) return Expression<()>.join(" ", expressions) } From 76ad0d873377da98a34cac646454f1bf488fc51a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 12:00:41 -0800 Subject: [PATCH 0198/1046] Allow statements to reiterate Signed-off-by: Stephen Celis --- SQLite Tests/StatementTests.swift | 9 +++++++++ SQLite/Statement.swift | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index d86c38ae..86a0ce3b 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -163,6 +163,15 @@ class StatementTests: XCTestCase { XCTAssertEqual(3, count) } + func test_generate_allowsReuse() { + InsertUsers(db, "alice", "betsy", "cindy") + var count = 0 + let stmt = db.prepare("SELECT id FROM users") + for row in stmt { count++ } + for row in stmt { count++ } + XCTAssertEqual(6, count) + } + func test_cursor_returnsValues() { InsertUser(db, "alice") let stmt = db.prepare("SELECT id, email FROM users") diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 585fa8d7..d50d5e6c 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -197,7 +197,10 @@ public final class Statement { // MARK: - SequenceType extension Statement: SequenceType { - public func generate() -> Statement { return self } + public func generate() -> Statement { + reset(clearBindings: false) + return self + } } From 3df3ed734bfbf23fce979dadd7611635b68448ab Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 12:01:20 -0800 Subject: [PATCH 0199/1046] Avoid circular reference The REPL particularly hates this. This does mean, however, that `step()` needs to live on the statement itself, not the cursor. In fact, cursor may be better accessed as `row`, so let's do just that: while stmt.step() { let id = stmt.row[0] as Int // ... } Signed-off-by: Stephen Celis --- SQLite Tests/StatementTests.swift | 8 +++--- SQLite/Statement.swift | 41 +++++++++++++++++-------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index 86a0ce3b..cf297c6d 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -172,13 +172,13 @@ class StatementTests: XCTestCase { XCTAssertEqual(6, count) } - func test_cursor_returnsValues() { + func test_row_returnsValues() { InsertUser(db, "alice") let stmt = db.prepare("SELECT id, email FROM users") - stmt.cursor.step() + stmt.step() - XCTAssertEqual(Int64(1), stmt.cursor[0] as Int64) - XCTAssertEqual("alice@example.com", stmt.cursor[1] as String) + XCTAssertEqual(Int64(1), stmt.row[0] as Int64) + XCTAssertEqual("alice@example.com", stmt.row[1] as String) } } diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index d50d5e6c..4be14a87 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -32,7 +32,7 @@ public final class Statement { private let database: Database - public lazy var cursor: Cursor = { Cursor(self) }() + public lazy var row: Cursor = { Cursor(self) }() internal init(_ database: Database, _ SQL: String) { self.database = database @@ -115,7 +115,7 @@ public final class Statement { public func run(bindings: Binding?...) -> Statement { if !bindings.isEmpty { return run(bindings) } reset(clearBindings: false) - while cursor.step() {} + while step() {} return self } @@ -142,8 +142,8 @@ public final class Statement { public func scalar(bindings: Binding?...) -> Binding? { if !bindings.isEmpty { return scalar(bindings) } reset(clearBindings: false) - cursor.step() - return cursor[0] + step() + return row[0] } /// :param: bindings A list of parameters to bind to the statement. @@ -163,6 +163,11 @@ public final class Statement { // MARK: - + public func step() -> Bool { + try(sqlite3_step(handle)) + return status == SQLITE_ROW + } + private func reset(clearBindings: Bool = true) { (status, reason) = (SQLITE_OK, nil) sqlite3_reset(handle) @@ -209,7 +214,7 @@ extension Statement: GeneratorType { /// :returns: The next row from the result set (or nil). public func next() -> [Binding?]? { - return cursor.step() ? Array(cursor) : nil + return step() ? Array(row) : nil } } @@ -236,33 +241,31 @@ public func || (lhs: Statement, rhs: @autoclosure () -> Statement) -> Statement /// Cursors provide direct access to a statement's current row. public struct Cursor { - private unowned let statement: Statement + private let handle: COpaquePointer - private init(_ statement: Statement) { - self.statement = statement - } + private let columnCount: Int - public func step() -> Bool { - statement.try(sqlite3_step(statement.handle)) - return statement.status == SQLITE_ROW + private init(_ statement: Statement) { + handle = statement.handle + columnCount = statement.columnCount } public subscript(idx: Int) -> Blob { - let bytes = sqlite3_column_blob(statement.handle, Int32(idx)) - let length = sqlite3_column_bytes(statement.handle, Int32(idx)) + let bytes = sqlite3_column_blob(handle, Int32(idx)) + let length = sqlite3_column_bytes(handle, Int32(idx)) return Blob(bytes: bytes, length: Int(length)) } public subscript(idx: Int) -> Double { - return sqlite3_column_double(statement.handle, Int32(idx)) + return sqlite3_column_double(handle, Int32(idx)) } public subscript(idx: Int) -> Int64 { - return sqlite3_column_int64(statement.handle, Int32(idx)) + return sqlite3_column_int64(handle, Int32(idx)) } public subscript(idx: Int) -> String { - return String.fromCString(UnsafePointer(sqlite3_column_text(statement.handle, Int32(idx)))) ?? "" + return String.fromCString(UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) ?? "" } public subscript(idx: Int) -> Bool { @@ -279,7 +282,7 @@ public struct Cursor { extension Cursor: SequenceType { public subscript(idx: Int) -> Binding? { - switch sqlite3_column_type(statement.handle, Int32(idx)) { + switch sqlite3_column_type(handle, Int32(idx)) { case SQLITE_BLOB: return self[idx] as Blob case SQLITE_FLOAT: @@ -298,7 +301,7 @@ extension Cursor: SequenceType { public func generate() -> GeneratorOf { var idx = 0 return GeneratorOf { - idx >= self.statement.columnCount ? Optional.None : self[idx++] + idx >= self.columnCount ? Optional.None : self[idx++] } } From 0d6c6412455f414c1e89612389740b9b3cb0d7fa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 12:07:19 -0800 Subject: [PATCH 0200/1046] It's technically a rowid, not a row id Signed-off-by: Stephen Celis --- SQLite/Database.swift | 2 +- SQLite/Query.swift | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index b77ab1b1..f2fef23c 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -53,7 +53,7 @@ public final class Database { // MARK: - - /// The last row id inserted into the database via this connection. + /// The last rowid inserted into the database via this connection. public var lastId: Int64? { let lastId = sqlite3_last_insert_rowid(handle) return lastId == 0 ? nil : lastId diff --git a/SQLite/Query.swift b/SQLite/Query.swift index a62c53e5..49481e40 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -438,14 +438,14 @@ public struct Query { /// /// :param: values A list of values to set. /// - /// :returns: The row id. + /// :returns: The rowid. public func insert(value: Setter, _ more: Setter...) -> Int64? { return insert([value] + more).id } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// - /// :returns: The row id and statement. + /// :returns: The rowid and statement. public func insert(value: Setter, _ more: Setter...) -> (id: Int64?, statement: Statement) { return insert([value] + more) } @@ -454,14 +454,14 @@ public struct Query { /// /// :param: values An array of values to set. /// - /// :returns: The row id. + /// :returns: The rowid. public func insert(values: [Setter]) -> Int64? { return insert(values).id } /// Runs an INSERT statement against the query. /// /// :param: values An array of values to set. /// - /// :returns: The row id and statement. + /// :returns: The rowid and statement. public func insert(values: [Setter]) -> (id: Int64?, statement: Statement) { let statement = insertStatement(values).run() return (statement.failed ? nil : database.lastId, statement) @@ -497,14 +497,14 @@ public struct Query { /// /// :param: values A list of values to set. /// - /// :returns: The row id. + /// :returns: The rowid. public func replace(values: Setter...) -> Int64? { return replace(values).id } /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. /// - /// :returns: The row id and statement. + /// :returns: The rowid and statement. public func replace(values: Setter...) -> (id: Int64?, statement: Statement) { return replace(values) } @@ -513,14 +513,14 @@ public struct Query { /// /// :param: values An array of values to set. /// - /// :returns: The row id. + /// :returns: The rowid. public func replace(values: [Setter]) -> Int64? { return replace(values).id } /// Runs a REPLACE statement against the query. /// /// :param: values An array of values to set. /// - /// :returns: The row id and statement. + /// :returns: The rowid and statement. public func replace(values: [Setter]) -> (id: Int64?, statement: Statement) { let statement = insertStatement(values, or: .Replace).run() return (statement.failed ? nil : database.lastId, statement) From b8c6605c38b2d0fb06a9f8f70224eef5bff34710 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 12:27:14 -0800 Subject: [PATCH 0201/1046] Remove unnecessary typealiasing Signed-off-by: Stephen Celis --- SQLite Tests/QueryTests.swift | 8 +++----- SQLite/Query.swift | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index ccd2c218..37ee2bb8 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -481,15 +481,13 @@ private let formatter: NSDateFormatter = { extension NSDate: Value { - public typealias Datatype = String + public class var declaredDatatype: String { return String.declaredDatatype } - public class var declaredDatatype: String { return Datatype.declaredDatatype } - - public class func fromDatatypeValue(datatypeValue: Datatype) -> NSDate { + public class func fromDatatypeValue(datatypeValue: String) -> NSDate { return formatter.dateFromString(datatypeValue)! } - public var datatypeValue: Datatype { + public var datatypeValue: String { return formatter.stringFromDate(self) } diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 49481e40..d3789532 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -809,9 +809,7 @@ public struct Row { // MARK: - SequenceType extension Query: SequenceType { - public typealias Generator = QueryGenerator - - public func generate() -> Generator { return Generator(self) } + public func generate() -> QueryGenerator { return QueryGenerator(self) } } From 992bb380a8d0565bcdc48fef280c2c576e46980a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 13:29:05 -0800 Subject: [PATCH 0202/1046] Re-use expression aliasing on a query's table Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- SQLite Tests/QueryTests.swift | 26 ++++++++++++------------- SQLite/Expression.swift | 4 ++++ SQLite/Query.swift | 36 ++++++++++++++++++----------------- SQLite/Schema.swift | 35 +++++++++++++++++----------------- 5 files changed, 55 insertions(+), 48 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4e490bee..d6e0d8fa 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -617,7 +617,7 @@ let managers = users.alias("managers") let query = users.join(managers, on: managers[id] == users[manager_id]) // SELECT * FROM "users" -// INNER JOIN "users" AS "managers" ON ("managers"."id" = "users"."manager_id") +// INNER JOIN ("users") AS "managers" ON ("managers"."id" = "users"."manager_id") ``` If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index 37ee2bb8..212b6fd4 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -60,7 +60,7 @@ class QueryTests: XCTestCase { let query = users.join(managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" + "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -70,7 +70,7 @@ class QueryTests: XCTestCase { let query = users.join(.LeftOuter, managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM \"users\" " + - "LEFT OUTER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" + "LEFT OUTER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } @@ -81,7 +81,7 @@ class QueryTests: XCTestCase { let query = users.join(managers, on: managers[id] == users[manager_id]) let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN \"users\" AS \"managers\" " + + "INNER JOIN (\"users\") AS \"managers\" " + "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") " + "AND \"managers\".\"admin\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } @@ -96,8 +96,8 @@ class QueryTests: XCTestCase { .join(managed, on: managed[manager_id] == users[id]) let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " + - "INNER JOIN \"users\" AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")" + "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " + + "INNER JOIN (\"users\") AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")" ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} } } @@ -112,7 +112,7 @@ class QueryTests: XCTestCase { .join(managers, on: managers[id] == users[manager_id]) let SQL = "SELECT \"users\".*, \"managers\".* FROM \"users\" " + - "INNER JOIN \"users\" AS \"managers\" " + + "INNER JOIN (\"users\") AS \"managers\" " + "ON (\"managers\".\"id\" = \"users\".\"manager_id\")" ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } } @@ -265,7 +265,7 @@ class QueryTests: XCTestCase { func test_alias_compilesAliasInSelectClause() { let managers = users.alias("managers") - var SQL = "SELECT * FROM \"users\" AS \"managers\"" + var SQL = "SELECT * FROM (\"users\") AS \"managers\"" ExpectExecutions(db, [SQL: 1]) { _ in for _ in managers {} } } @@ -279,11 +279,11 @@ class QueryTests: XCTestCase { func test_subscript_withAliasAndExpression_returnsAliasedExpression() { let managers = users.alias("managers") - ExpectExecution(db, "SELECT \"managers\".\"admin\" FROM \"users\" AS \"managers\"", managers.select(managers[admin])) - ExpectExecution(db, "SELECT \"managers\".\"salary\" FROM \"users\" AS \"managers\"", managers.select(managers[salary])) - ExpectExecution(db, "SELECT \"managers\".\"age\" FROM \"users\" AS \"managers\"", managers.select(managers[age])) - ExpectExecution(db, "SELECT \"managers\".\"email\" FROM \"users\" AS \"managers\"", managers.select(managers[email])) - ExpectExecution(db, "SELECT \"managers\".* FROM \"users\" AS \"managers\"", managers.select(managers[*])) + ExpectExecution(db, "SELECT \"managers\".\"admin\" FROM (\"users\") AS \"managers\"", managers.select(managers[admin])) + ExpectExecution(db, "SELECT \"managers\".\"salary\" FROM (\"users\") AS \"managers\"", managers.select(managers[salary])) + ExpectExecution(db, "SELECT \"managers\".\"age\" FROM (\"users\") AS \"managers\"", managers.select(managers[age])) + ExpectExecution(db, "SELECT \"managers\".\"email\" FROM (\"users\") AS \"managers\"", managers.select(managers[email])) + ExpectExecution(db, "SELECT \"managers\".* FROM (\"users\") AS \"managers\"", managers.select(managers[*])) } func test_SQL_compilesProperly() { @@ -300,7 +300,7 @@ class QueryTests: XCTestCase { .limit(1, offset: 2) let SQL = "SELECT \"users\".\"email\", count(\"users\".\"age\") FROM \"users\" " + - "LEFT OUTER JOIN \"users\" AS \"managers\" " + + "LEFT OUTER JOIN (\"users\") AS \"managers\" " + "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") AND (\"managers\".\"admin\" = 1)) " + "WHERE \"users\".\"age\" BETWEEN 21 AND 32 " + "GROUP BY \"users\".\"age\" HAVING (count(\"users\".\"age\") > 1) " + diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index b940f331..10e5f40a 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -116,6 +116,10 @@ public struct Expression { return self } + internal var unaliased: Expression { + return original.map { Expression($0.expression) } ?? self + } + internal func reverse() -> Expression { var expression = self expression.ascending = expression.ascending.map(!) ?? false diff --git a/SQLite/Query.swift b/SQLite/Query.swift index d3789532..5d3abc3e 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -29,6 +29,10 @@ public struct Query { internal var database: Database internal init(_ database: Database, _ tableName: String) { + (self.database, self.tableName) = (database, Expression(tableName)) + } + + private init(_ database: Database, _ tableName: Expression<()>) { (self.database, self.tableName) = (database, tableName) } @@ -50,17 +54,16 @@ public struct Query { private var columns: [Expressible] = [Expression<()>(literal: "*")] private var distinct: Bool = false - internal var tableName: String - private var alias: String? + internal var tableName: Expression<()> private var joins: [(type: JoinType, table: Query, condition: Expression)] = [] private var filter: Expression? private var group: Expressible? private var order = [Expressible]() private var limit: (to: Int, offset: Int?)? = nil - public func alias(alias: String?) -> Query { + public func alias(alias: String) -> Query { var query = self - query.alias = alias + query.tableName = query.tableName.alias(alias) return query } @@ -293,7 +296,7 @@ public struct Query { /// :returns: A column expression namespaced with the query’s table name or /// alias. public func namespace(column: Expression) -> Expression { - return Expression(literal: "\(quote(identifier: alias ?? tableName)).\(column.SQL)", column.bindings) + return Expression(.join(".", [tableName, column])) } // FIXME: rdar://18673897 // ... subscript(expression: Expression) -> Expression @@ -361,7 +364,7 @@ public struct Query { private func insertStatement(values: [Setter], or: OnConflict? = nil) -> Statement { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } - var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(quote(identifier: tableName))")] + var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(tableName.unaliased.SQL)")] let (c, v) = (Expression<()>.join(", ", values.map { $0.0 }), Expression<()>.join(", ", values.map { $0.1 })) expressions.append(Expression<()>(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) whereClause.map(expressions.append) @@ -370,7 +373,7 @@ public struct Query { } private func updateStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression<()>(literal: "UPDATE \(quote(identifier: tableName)) SET")] + var expressions: [Expressible] = [Expression<()>(literal: "UPDATE \(tableName.unaliased.SQL) SET")] expressions.append(Expression<()>.join(", ", values.map { Expression<()>.join(" = ", [$0, $1]) })) whereClause.map(expressions.append) let expression = Expression<()>.join(" ", expressions) @@ -378,7 +381,7 @@ public struct Query { } private var deleteStatement: Statement { - var expressions: [Expressible] = [Expression<()>(literal: "DELETE FROM \(quote(identifier: tableName))")] + var expressions: [Expressible] = [Expression<()>(literal: "DELETE FROM \(tableName.unaliased.SQL)")] whereClause.map(expressions.append) let expression = Expression<()>.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) @@ -390,14 +393,14 @@ public struct Query { var expressions: [Expressible] = [Expression<()>(literal: "SELECT")] if distinct { expressions.append(Expression<()>(literal: "DISTINCT")) } expressions.append(Expression<()>.join(", ", columns.map { $0.expression.aliased })) - expressions.append(Expression<()>(literal: "FROM \(self)")) + expressions.append(Expression<()>(literal: "FROM \(tableName.aliased.SQL)")) return Expression<()>.join(" ", expressions) } private var joinClause: Expressible? { if joins.count == 0 { return nil } return Expression<()>.join(" ", joins.map { type, table, condition in - Expression<()>(literal: "\(type.rawValue) JOIN \(table) ON \(condition.SQL)", condition.bindings) + Expression<()>(literal: "\(type.rawValue) JOIN \(table.tableName.aliased.SQL) ON \(condition.SQL)", condition.bindings) }) } @@ -473,7 +476,7 @@ public struct Query { public func insert(query: Query) -> (changes: Int?, statement: Statement) { let expression = query.selectExpression - let statement = database.run("INSERT INTO \(quote(identifier: tableName)) \(expression.SQL)", expression.bindings) + let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings) return (statement.failed ? nil : database.lastChanges, statement) } @@ -482,7 +485,7 @@ public struct Query { public func insert() -> Statement { return insert().statement } public func insert() -> (id: Int64?, statement: Statement) { - let statement = database.run("INSERT INTO \(quote(identifier: tableName)) DEFAULT VALUES") + let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) DEFAULT VALUES") return (statement.failed ? nil : database.lastId, statement) } @@ -827,8 +830,8 @@ public struct QueryGenerator: GeneratorType { func expandGlob(namespace: Bool) -> Query -> () { return { table in - var names = self.query.database[table.tableName].selectStatement.columnNames.map { quote(identifier: $0) } - if namespace { names = names.map { "\(quote(identifier: table.alias ?? table.tableName)).\($0)" } } + var names = Query(self.query.database, self.query.tableName.unaliased).selectStatement.columnNames.map { quote(identifier: $0) } + if namespace { names = names.map { "\(table.tableName.SQL).\($0)" } } for name in names { columnNames[name] = idx++ } } } @@ -837,7 +840,7 @@ public struct QueryGenerator: GeneratorType { let tables = [self.query] + self.query.joins.map { $0.table } if let tableName = tableName { for table in tables { - if quote(identifier: table.alias ?? table.tableName) == tableName { + if table.tableName.SQL == tableName { expandGlob(true)(table) continue column } @@ -867,8 +870,7 @@ public struct QueryGenerator: GeneratorType { extension Query: Printable { public var description: String { - if let alias = alias { return "\(quote(identifier: tableName)) AS \(quote(identifier: alias))" } - return quote(identifier: tableName) + return tableName.SQL } } diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 303b309a..97e49a8d 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -32,19 +32,19 @@ public extension Database { ) -> Statement { var builder = SchemaBuilder(table) block(builder) - let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName) + let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) let columns = Expression<()>.join(", ", builder.columns).compile() return run("\(create) (\(columns))") } public func create(#table: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { - let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName) + let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) let expression = from.selectExpression return run("\(create) AS \(expression.SQL)", expression.bindings) } public func rename(#table: Query, to tableName: String) -> Statement { - return run("ALTER TABLE \(quote(identifier: table.tableName)) RENAME TO \(quote(identifier: tableName))") + return run("ALTER TABLE \(table.tableName.unaliased.SQL) RENAME TO \(quote(identifier: tableName))") } public func alter( @@ -103,11 +103,11 @@ public extension Database { } private func alter(table: Query, _ definition: Expressible) -> Statement { - return run("ALTER TABLE \(quote(identifier: table.tableName)) ADD COLUMN \(definition.expression.compile())") + return run("ALTER TABLE \(table.tableName.unaliased.SQL) ADD COLUMN \(definition.expression.compile())") } public func drop(#table: Query, ifExists: Bool = false) -> Statement { - return run(dropSQL("TABLE", ifExists, table.tableName)) + return run(dropSQL("TABLE", ifExists, table.tableName.unaliased.SQL)) } public func create( @@ -116,32 +116,33 @@ public extension Database { ifNotExists: Bool = false, on columns: Expressible... ) -> Statement { - let create = createSQL("INDEX", false, unique, ifNotExists, indexName(table, on: columns)) + let tableName = table.tableName.unaliased.SQL + let create = createSQL("INDEX", false, unique, ifNotExists, indexName(tableName, on: columns)) let joined = Expression<()>.join(", ", columns.map { $0.expression.ordered }) - return run("\(create) ON \(quote(identifier: table.tableName)) (\(joined.compile()))") + return run("\(create) ON \(tableName) (\(joined.compile()))") } public func drop(index table: Query, ifExists: Bool = false, on columns: Expressible...) -> Statement { - return run(dropSQL("INDEX", ifExists, indexName(table, on: columns))) + return run(dropSQL("INDEX", ifExists, indexName(table.tableName.unaliased.SQL, on: columns))) } - private func indexName(table: Query, on columns: [Expressible]) -> String { - let string = join(" ", ["index", table.tableName, "on"] + columns.map { $0.expression.SQL }) - return reduce(string, "") { underscored, character in + private func indexName(table: String, on columns: [Expressible]) -> String { + let string = join(" ", ["index", table, "on"] + columns.map { $0.expression.SQL }) + return quote(identifier: reduce(string, "") { underscored, character in if character == "\"" { return underscored } if "A"..."Z" ~= character || "a"..."z" ~= character { return underscored + String(character) } return underscored + "_" - } + }) } public func create(#view: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { - let create = createSQL("VIEW", temporary, false, ifNotExists, view.tableName) + let create = createSQL("VIEW", temporary, false, ifNotExists, view.tableName.unaliased.SQL) let expression = from.selectExpression return run("\(create) AS \(expression.SQL)", expression.bindings) } public func drop(#view: Query, ifExists: Bool = false) -> Statement { - return run(dropSQL("VIEW", ifExists, view.tableName)) + return run(dropSQL("VIEW", ifExists, view.tableName.unaliased.SQL)) } } @@ -246,7 +247,7 @@ public final class SchemaBuilder { name, unique: unique, check: check, - references: Expression(literal: references.tableName) + references: Expression(references.tableName) ) } @@ -471,13 +472,13 @@ private func createSQL( if unique { parts.append("UNIQUE") } parts.append(type) if ifNotExists { parts.append("IF NOT EXISTS") } - parts.append(quote(identifier: name)) + parts.append(name) return Swift.join(" ", parts) } private func dropSQL(type: String, ifExists: Bool, name: String) -> String { var parts: [String] = ["DROP \(type)"] if ifExists { parts.append("IF EXISTS") } - parts.append(quote(identifier: name)) + parts.append(name) return Swift.join(" ", parts) } From 1efaf8b87b072a17fca47b4ed5f092f00739f8ff Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 13:37:50 -0800 Subject: [PATCH 0203/1046] Change ALTER TABLE RENAME TO signature This is mainly important for migrations, so let's take the old table name as a string and pass in a query as the new table, instead. Signed-off-by: Stephen Celis --- SQLite Tests/SchemaTests.swift | 3 ++- SQLite/Schema.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 780ca18c..a73ab49b 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -264,7 +264,8 @@ class SchemaTests: XCTestCase { func test_alterTable_renamesTable() { CreateUsersTable(db) - ExpectExecution(db, "ALTER TABLE \"users\" RENAME TO \"people\"", db.rename(table: users, to: "people") ) + let people = db["people"] + ExpectExecution(db, "ALTER TABLE \"users\" RENAME TO \"people\"", db.rename(table: "users", to: people) ) } func test_alterTable_addsNotNullColumn() { diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 97e49a8d..1c6e4554 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -43,8 +43,8 @@ public extension Database { return run("\(create) AS \(expression.SQL)", expression.bindings) } - public func rename(#table: Query, to tableName: String) -> Statement { - return run("ALTER TABLE \(table.tableName.unaliased.SQL) RENAME TO \(quote(identifier: tableName))") + public func rename(table tableName: String, to table: Query) -> Statement { + return run("ALTER TABLE \(quote(identifier: tableName)) RENAME TO \(table.tableName.unaliased.SQL)") } public func alter( From 0ba651defd8159f2c820bfd74071031d376b62cf Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 14:31:19 -0800 Subject: [PATCH 0204/1046] Basic subquery support Signed-off-by: Stephen Celis --- SQLite Tests/QueryTests.swift | 34 ++++++++++++++++++++++++++++++++++ SQLite/Expression.swift | 15 +++++++++++++++ SQLite/Query.swift | 13 ++++++++----- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index 212b6fd4..3d7ca134 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -54,6 +54,18 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } + func test_select_withSubquery() { + let subquery = users.select(id) + + var query = users.select(subquery) + var SQL = "SELECT (SELECT \"id\" FROM \"users\") FROM \"users\"" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + + query = users.select(subquery.alias("u")) + SQL = "SELECT (SELECT \"id\" FROM (\"users\") AS \"u\") AS \"u\" FROM \"users\"" + ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + } + func test_join_compilesJoinClause() { let managers = db["users"].alias("managers") @@ -117,6 +129,28 @@ class QueryTests: XCTestCase { ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } } + func test_join_withSubquery_joinsSubquery() { + let maxId = max(id).alias("max_id") + let subquery = users.select(maxId).group(age) + let query = users.join(subquery, on: maxId == id) + + let SQL = "SELECT * FROM \"users\" " + + "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM \"users\" GROUP BY \"age\") " + + "ON (\"max_id\" = \"id\")" + ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } + } + + func test_join_withAliasedSubquery_joinsSubquery() { + let maxId = max(id).alias("max_id") + let subquery = users.select(maxId).group(age).alias("u") + let query = users.join(subquery, on: subquery[maxId] == id) + + let SQL = "SELECT * FROM \"users\" " + + "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM (\"users\") AS \"u\" GROUP BY \"age\") AS \"u\" " + + "ON (\"u\".\"max_id\" = \"id\")" + ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } + } + func test_namespacedColumnRowValueAccess() { let aliceId = users.insert(email <- "alice@example.com")! let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId))! diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 10e5f40a..02bb742c 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -81,6 +81,10 @@ public struct Expression { /// Returns an aliased version of the expression using AS. The expression is /// expanded contextually (e.g., in SELECT clauses). public func alias(alias: String) -> Expression { + return self.alias(Expression(alias)) + } + + private func alias(alias: Expression) -> Expression { var expression = Expression(alias) expression.original = self return expression @@ -200,6 +204,17 @@ extension Expression: Expressible { } +extension Query: Expressible { + + public var expression: Expression<()> { + if tableName.original != nil { + return selectExpression.alias(tableName) + } + return wrap("", selectExpression) + } + +} + // MARK: - Expressions public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 5d3abc3e..e1d2ed12 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -52,7 +52,7 @@ public struct Query { } - private var columns: [Expressible] = [Expression<()>(literal: "*")] + private var columns: [Expressible]? private var distinct: Bool = false internal var tableName: Expression<()> private var joins: [(type: JoinType, table: Query, condition: Expression)] = [] @@ -96,7 +96,9 @@ public struct Query { /// /// :returns: A query with SELECT * applied. public func select(all star: Star) -> Query { - return select(star(nil, nil)) + var query = self + (query.distinct, query.columns) = (false, nil) + return query } /// Sets the SELECT DISTINCT * clause on the query. @@ -392,7 +394,7 @@ public struct Query { private var selectClause: Expressible { var expressions: [Expressible] = [Expression<()>(literal: "SELECT")] if distinct { expressions.append(Expression<()>(literal: "DISTINCT")) } - expressions.append(Expression<()>.join(", ", columns.map { $0.expression.aliased })) + expressions.append(Expression<()>.join(", ", (columns ?? [Expression<()>(literal: "*")]).map { $0.expression.aliased })) expressions.append(Expression<()>(literal: "FROM \(tableName.aliased.SQL)")) return Expression<()>.join(" ", expressions) } @@ -400,7 +402,8 @@ public struct Query { private var joinClause: Expressible? { if joins.count == 0 { return nil } return Expression<()>.join(" ", joins.map { type, table, condition in - Expression<()>(literal: "\(type.rawValue) JOIN \(table.tableName.aliased.SQL) ON \(condition.SQL)", condition.bindings) + let join = (table.columns == nil ? table.tableName : table.expression).aliased + return Expression<()>(literal: "\(type.rawValue) JOIN \(join.SQL) ON \(condition.SQL)", join.bindings + condition.bindings) }) } @@ -824,7 +827,7 @@ public struct QueryGenerator: GeneratorType { private lazy var columnNames: [String: Int] = { var (columnNames, idx) = ([String: Int](), 0) - column: for each in self.query.columns { + column: for each in self.query.columns ?? [Expression<()>(literal: "*")] { let pair = split(each.expression.SQL) { $0 == "." } let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) From 87946a6e95e53086e7499740636002f26089ea9a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 15:24:12 -0800 Subject: [PATCH 0205/1046] Fix column access on joined subqueries Signed-off-by: Stephen Celis --- SQLite Tests/QueryTests.swift | 8 ++++++++ SQLite/Query.swift | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index 3d7ca134..9243e941 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -130,10 +130,14 @@ class QueryTests: XCTestCase { } func test_join_withSubquery_joinsSubquery() { + InsertUser(db, "alice", age: 20) + let maxId = max(id).alias("max_id") let subquery = users.select(maxId).group(age) let query = users.join(subquery, on: maxId == id) + XCTAssertEqual(Int64(1), query.first![maxId]!) + let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM \"users\" GROUP BY \"age\") " + "ON (\"max_id\" = \"id\")" @@ -141,10 +145,14 @@ class QueryTests: XCTestCase { } func test_join_withAliasedSubquery_joinsSubquery() { + InsertUser(db, "alice", age: 20) + let maxId = max(id).alias("max_id") let subquery = users.select(maxId).group(age).alias("u") let query = users.join(subquery, on: subquery[maxId] == id) + XCTAssertEqual(Int64(1), query.first![subquery[maxId]]!) + let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM (\"users\") AS \"u\" GROUP BY \"age\") AS \"u\" " + "ON (\"u\".\"max_id\" = \"id\")" diff --git a/SQLite/Query.swift b/SQLite/Query.swift index e1d2ed12..752f697f 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -787,7 +787,7 @@ public struct Row { if similar.count > 1 { fatalError("ambiguous column \(quote(literal: column.SQL)) (please disambiguate: \(similar))") } - fatalError("no such column \(quote(literal: column.SQL)) in columns: \(Array(columnNames.keys))") + fatalError("no such column \(quote(literal: column.SQL)) in columns: \(sorted(columnNames.keys))") } // FIXME: rdar://18673897 // ... subscript(expression: Expression) -> Expression @@ -833,14 +833,16 @@ public struct QueryGenerator: GeneratorType { func expandGlob(namespace: Bool) -> Query -> () { return { table in - var names = Query(self.query.database, self.query.tableName.unaliased).selectStatement.columnNames.map { quote(identifier: $0) } + var query = Query(table.database, table.tableName.unaliased) + if let columns = table.columns { query.columns = columns } + var names = query.selectStatement.columnNames.map { quote(identifier: $0) } if namespace { names = names.map { "\(table.tableName.SQL).\($0)" } } for name in names { columnNames[name] = idx++ } } } if column == "*" { - let tables = [self.query] + self.query.joins.map { $0.table } + let tables = [self.query.select(all: *)] + self.query.joins.map { $0.table } if let tableName = tableName { for table in tables { if table.tableName.SQL == tableName { From 10ce76ff94726182dbcb61fb9b25d41c6dba0fca Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Mar 2015 15:24:38 -0800 Subject: [PATCH 0206/1046] Fix Makefile SLOC check Signed-off-by: Stephen Celis --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d789ee09..70142cb1 100644 --- a/Makefile +++ b/Makefile @@ -18,5 +18,5 @@ repl: swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' sloc: - @zsh -c "grep -vE '^ *//|^$$' SQLite\ Common/*.{swift,h,c} | wc -l" + @zsh -c "grep -vE '^ *//|^$$' SQLite/*.{swift,h,c} | wc -l" From 2b3780cccac7cb9d38b3d887ec8515b265c490b6 Mon Sep 17 00:00:00 2001 From: Russ Bishop Date: Wed, 4 Mar 2015 10:22:12 -0800 Subject: [PATCH 0207/1046] Support non-integer primary key in shorthand function --- SQLite Tests/SchemaTests.swift | 13 +++++++++++++ SQLite Tests/TestHelper.swift | 13 +++++++++++++ SQLite/Schema.swift | 12 ++++++------ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index a73ab49b..16da956f 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -7,6 +7,7 @@ private let age = Expression("age") private let salary = Expression("salary") private let admin = Expression("admin") private let manager_id = Expression("manager_id") +private let uniqueIdentifier = Expression("uniqueIdentifier") class SchemaTests: XCTestCase { @@ -45,6 +46,18 @@ class SchemaTests: XCTestCase { ) } + func test_createTable_column_nonIntegerPrimaryKey() { + ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT PRIMARY KEY NOT NULL)", + db.create(table: users) { $0.column(email, primaryKey: true) } + ) + } + + func test_createTable_column_customTypePrimaryKey() { + ExpectExecution(db, "CREATE TABLE \"users\" (\"uniqueIdentifier\" TEXT PRIMARY KEY NOT NULL)", + db.create(table: users) { $0.column(uniqueIdentifier, primaryKey: true) } + ) + } + func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { ExpectExecution(db, "CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)", db.create(table: users) { t in diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift index c9aef700..d7f81fef 100644 --- a/SQLite Tests/TestHelper.swift +++ b/SQLite Tests/TestHelper.swift @@ -55,3 +55,16 @@ func ExpectExecution(db: Database, SQL: String, statement: @autoclosure () -> St func ExpectExecution(db: Database, SQL: String, query: Query) { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } + + +public class UniqueIdentifier : NSUUID, Value { + public class var declaredDatatype:String { get { return "TEXT" } } + + public class func fromDatatypeValue(datatypeValue: String) -> UniqueIdentifier { + return UniqueIdentifier(UUIDString: datatypeValue)! + } + + public var datatypeValue: String { + return self.UUIDString + } +} \ No newline at end of file diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 1c6e4554..6532e07a 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -193,12 +193,8 @@ public final class SchemaBuilder { ) { column(Expression(name), nil, true, unique, check, value.map { Expression(value: $0) }) } - - // MARK: - INTEGER Columns - - // MARK: PRIMARY KEY - - public func column( + + public func column( name: Expression, primaryKey: Bool, unique: Bool = false, @@ -207,6 +203,10 @@ public final class SchemaBuilder { column(name, primaryKey ? .Default : nil, false, unique, check, nil, nil) } + // MARK: - INTEGER Columns + + // MARK: PRIMARY KEY + public enum PrimaryKey { case Default From 96f4b35dec47794231d199d7cd87580e6bf91a33 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 5 Mar 2015 18:03:56 -0800 Subject: [PATCH 0208/1046] PRIMARY KEY cleanup Let's enable defaults for primary keys, and let's not bother with a uniqueness parameter, since it's implied. Signed-off-by: Stephen Celis --- Documentation/Index.md | 8 +++++--- SQLite Tests/SchemaTests.swift | 20 ++++++++++++++------ SQLite Tests/TestHelper.swift | 13 ------------- SQLite/Schema.swift | 13 ++++++------- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index d6e0d8fa..d0859560 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -282,7 +282,7 @@ The `create(table:)` function has several default parameters we can override. The `column` function is used for a single column definition. It takes an [expression](#expressions) describing the column name and type, and accepts several parameters that map to various column constraints and clauses. - - `primaryKey` adds an `INTEGER PRIMARY KEY` constraint to a single column. (See the `primaryKey` function under [Table Constraints](#table-constraints) for non-integer primary keys). + - `primaryKey` adds a `PRIMARY KEY` constraint to a single column. ``` swift t.column(id, primaryKey: true) @@ -292,9 +292,11 @@ The `column` function is used for a single column definition. It takes an [expre // "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ``` - > _Note:_ The `primaryKey` parameter cannot be used alongside `defaultValue` or `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `primaryKey` parameter cannot be used alongside `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). > - > Primary keys cannot be optional (`Expression`). + > Primary keys cannot be optional (_e.g._, `Expression`). + > + > Only an `INTEGER PRIMARY KEY` can take `.Autoincrement`. - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns). diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 16da956f..140274eb 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -7,7 +7,6 @@ private let age = Expression("age") private let salary = Expression("salary") private let admin = Expression("admin") private let manager_id = Expression("manager_id") -private let uniqueIdentifier = Expression("uniqueIdentifier") class SchemaTests: XCTestCase { @@ -48,13 +47,22 @@ class SchemaTests: XCTestCase { func test_createTable_column_nonIntegerPrimaryKey() { ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT PRIMARY KEY NOT NULL)", - db.create(table: users) { $0.column(email, primaryKey: true) } + db.create(table: users) { t in + t.column(email, primaryKey: true) + } ) } - - func test_createTable_column_customTypePrimaryKey() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"uniqueIdentifier\" TEXT PRIMARY KEY NOT NULL)", - db.create(table: users) { $0.column(uniqueIdentifier, primaryKey: true) } + + func test_createTable_column_nonIntegerPrimaryKey_withDefaultValue() { + let uuid = Expression("uuid") + let uuidgen: () -> Expression = db.create(function: "uuidgen") { + return NSUUID().UUIDString + } + + ExpectExecution(db, "CREATE TABLE \"users\" (\"uuid\" TEXT PRIMARY KEY NOT NULL DEFAULT (\"uuidgen\"()))", + db.create(table: users) { t in + t.column(uuid, primaryKey: true, defaultValue: uuidgen()) + } ) } diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift index d7f81fef..c9aef700 100644 --- a/SQLite Tests/TestHelper.swift +++ b/SQLite Tests/TestHelper.swift @@ -55,16 +55,3 @@ func ExpectExecution(db: Database, SQL: String, statement: @autoclosure () -> St func ExpectExecution(db: Database, SQL: String, query: Query) { ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } - - -public class UniqueIdentifier : NSUUID, Value { - public class var declaredDatatype:String { get { return "TEXT" } } - - public class func fromDatatypeValue(datatypeValue: String) -> UniqueIdentifier { - return UniqueIdentifier(UUIDString: datatypeValue)! - } - - public var datatypeValue: String { - return self.UUIDString - } -} \ No newline at end of file diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 6532e07a..3f56acc5 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -164,7 +164,7 @@ public final class SchemaBuilder { check: Expression? = nil, defaultValue value: Expression? ) { - column(name, nil, false, unique, check, value) + column(name, nil, false, unique, check, value.map { wrap("", $0) }) } public func column( @@ -182,7 +182,7 @@ public final class SchemaBuilder { check: Expression? = nil, defaultValue value: Expression? ) { - column(Expression(name), nil, true, unique, check, value) + column(Expression(name), nil, true, unique, check, value.map { wrap("", $0) }) } public func column( @@ -197,10 +197,10 @@ public final class SchemaBuilder { public func column( name: Expression, primaryKey: Bool, - unique: Bool = false, - check: Expression? = nil + check: Expression? = nil, + defaultValue value: Expression? = nil ) { - column(name, primaryKey ? .Default : nil, false, unique, check, nil, nil) + column(name, primaryKey ? .Default : nil, false, false, check, value.map { wrap("", $0) }, nil) } // MARK: - INTEGER Columns @@ -218,10 +218,9 @@ public final class SchemaBuilder { public func column( name: Expression, primaryKey: PrimaryKey?, - unique: Bool = false, check: Expression? = nil ) { - column(name, primaryKey, false, unique, check, nil, nil) + column(name, primaryKey, false, false, check, nil, nil) } // MARK: REFERENCES From c7c5cf9524acaef606f5f345c532ab7b0fdf2fca Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 8 Mar 2015 16:01:46 -0700 Subject: [PATCH 0209/1046] Very basic FTS support This commit provides basic helpers for creating virtual tables with fts4() and for running match queries against them. Still needed: - Custom tokenizer support (and the ability to build with "unicode61" and "icu" tokenizers) - Better support for offsets(), snippet(), matchinfo() - Additional FTS4 options: http://www.sqlite.org/fts3.html#fts4_options Signed-off-by: Stephen Celis --- SQLite Tests/FTSTests.swift | 32 +++++++++++++++++++ SQLite.xcodeproj/project.pbxproj | 10 ++++++ SQLite/Expression.swift | 4 +-- SQLite/FTS.swift | 54 ++++++++++++++++++++++++++++++++ SQLite/Schema.swift | 5 +++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 SQLite Tests/FTSTests.swift create mode 100644 SQLite/FTS.swift diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift new file mode 100644 index 00000000..2f728984 --- /dev/null +++ b/SQLite Tests/FTSTests.swift @@ -0,0 +1,32 @@ +import XCTest +import SQLite + +let subject = Expression("subject") +let body = Expression("body") + +class FTSTests: XCTestCase { + + let db = Database() + var emails: Query { return db["emails"] } + + func test_createVtable_usingFts4_createsVirtualTable() { + ExpectExecution(db, "CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\")", db.create(vtable: emails, using: fts4(subject, body))) + } + + func test_createVtable_usingFts4_withPorterTokenizer_createsVirtualTableWithTokenizer() { + ExpectExecution(db, "CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=porter)", db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter))) + } + + func test_match_withColumnExpression_buildsMatchExpressionWithColumnIdentifier() { + db.create(vtable: emails, using: fts4(subject, body)) + + ExpectExecution(db, "SELECT * FROM \"emails\" WHERE (\"subject\" MATCH 'hello')", emails.filter(match("hello", subject))) + } + + func test_match_withQuery_buildsMatchExpressionWithTableIdentifier() { + db.create(vtable: emails, using: fts4(subject, body)) + + ExpectExecution(db, "SELECT * FROM \"emails\" WHERE (\"emails\" MATCH 'hello')", emails.filter(match("hello", emails))) + } + +} diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index f52e459e..ba5a2ec2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -22,6 +22,9 @@ DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; + DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; + DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; + DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */; }; DCC6B36F1A9191C300734B78 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCC6B3701A9191C300734B78 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; DCC6B3711A9191C300734B78 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; @@ -101,6 +104,8 @@ DCAAE66D19D8A71B00158FEF /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; + DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; + DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; @@ -155,6 +160,7 @@ DC3F170F1A8127A300C83A2F /* Functions.swift */, DCAD429619E2E0F1004A51DF /* Query.swift */, DC109CE01A0C4D970070988E /* Schema.swift */, + DCAFEAD21AABC818000C21A1 /* FTS.swift */, ); name = "Query Building"; sourceTree = ""; @@ -179,6 +185,7 @@ DC3F17121A814F7000C83A2F /* FunctionsTests.swift */, DCAD429919E2EE50004A51DF /* QueryTests.swift */, DC109CE31A0C4F5D0070988E /* SchemaTests.swift */, + DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */, DC37740319C8CBB3004FCF85 /* Supporting Files */, ); path = "SQLite Tests"; @@ -466,6 +473,7 @@ buildActionMask = 2147483647; files = ( DC37743519C8D626004FCF85 /* Database.swift in Sources */, + DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */, DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */, DC37743819C8D693004FCF85 /* Value.swift in Sources */, DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */, @@ -481,6 +489,7 @@ buildActionMask = 2147483647; files = ( DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */, + DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */, DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */, DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */, DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */, @@ -501,6 +510,7 @@ DCC6B36F1A9191C300734B78 /* Query.swift in Sources */, DCC6B3741A9191C300734B78 /* Schema.swift in Sources */, DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */, + DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */, DCC6B3751A9191C300734B78 /* SQLite-Bridging.c in Sources */, DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */, ); diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 02bb742c..26f2a670 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -60,7 +60,7 @@ public struct Expression { /// Builds a SQL expression with the given value. /// /// :param: binding A raw SQL value. - private init(binding: Binding?) { + internal init(binding: Binding?) { self.init(literal: "?", [binding]) } @@ -966,7 +966,7 @@ internal func wrap(function: String, expression: Expression) -> Express return Expression(literal: "\(function)\(surround(expression.SQL))", expression.bindings) } -private func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { +internal func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { return Expression(literal: surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) } diff --git a/SQLite/FTS.swift b/SQLite/FTS.swift new file mode 100644 index 00000000..25e577dd --- /dev/null +++ b/SQLite/FTS.swift @@ -0,0 +1,54 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright (c) 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public func fts4(columns: Expression...) -> Expression<()> { + return fts4(columns) +} + +// TODO: matchinfo, compress, uncompress +public func fts4(columns: [Expression], tokenize tokenizer: Tokenizer? = nil) -> Expression<()> { + var options = [String: String]() + options["tokenize"] = tokenizer?.rawValue + return fts("fts4", columns, options) +} + +private func fts(function: String, columns: [Expression], options: [String: String]) -> Expression<()> { + var definitions: [Expressible] = columns.map { $0.expression } + for (key, value) in options { + definitions.append(Expression<()>(literal: "\(key)=\(value)")) + } + return wrap(function, Expression<()>.join(", ", definitions)) +} + +public enum Tokenizer: String { + + case Simple = "simple" + + case Porter = "porter" + +} + +public func match(string: String, expression: Query) -> Expression { + return infix("MATCH", Expression(expression.tableName), Expression(binding: string)) +} diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 3f56acc5..8d4e4996 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -43,6 +43,11 @@ public extension Database { return run("\(create) AS \(expression.SQL)", expression.bindings) } + public func create(#vtable: Query, ifNotExists: Bool = false, using: Expression<()>) -> Statement { + let create = createSQL("VIRTUAL TABLE", false, false, ifNotExists, vtable.tableName.unaliased.SQL) + return run("\(create) USING \(using.SQL)") + } + public func rename(table tableName: String, to table: Query) -> Statement { return run("ALTER TABLE \(quote(identifier: tableName)) RENAME TO \(table.tableName.unaliased.SQL)") } From ba9a175e73b657e223c290c1d05aa4d884dc5ad7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 8 Mar 2015 16:18:12 -0700 Subject: [PATCH 0210/1046] Prefer tuple assignment in initializers For now, anyhow. Style changes, after all. Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 26f2a670..f070f565 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -102,8 +102,7 @@ public struct Expression { internal init(_ expression: Expression) { self.init(literal: expression.SQL, expression.bindings) - ascending = expression.ascending - original = expression.original + (ascending, original) = (expression.ascending, expression.original) } internal var ordered: Expression<()> { From 86af78632bd872e53567ee1bb5de2a158fc93ee4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 8 Mar 2015 16:18:46 -0700 Subject: [PATCH 0211/1046] Always remove inferred types Signed-off-by: Stephen Celis --- SQLite/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 4be14a87..3426087d 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -300,7 +300,7 @@ extension Cursor: SequenceType { public func generate() -> GeneratorOf { var idx = 0 - return GeneratorOf { + return GeneratorOf { idx >= self.columnCount ? Optional.None : self[idx++] } } From ae7d539f4d18cdd064a8cece9e6765a217e12b6a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 11 Mar 2015 09:10:15 -0700 Subject: [PATCH 0212/1046] Update for Xcode 6.2 Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 2 +- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme | 2 +- SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ba5a2ec2..655879ee 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -389,7 +389,7 @@ DC3773EA19C8CBB3004FCF85 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 0620; ORGANIZATIONNAME = "Stephen Celis"; TargetAttributes = { DC3773F219C8CBB3004FCF85 = { diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme index c756b8ef..8530c286 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 11 Mar 2015 09:10:35 -0700 Subject: [PATCH 0213/1046] Fix some old archive-only segfaults Looking forward to Swift 1.2! Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index f070f565..eee23edb 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -174,7 +174,8 @@ extension Double: Expressible { extension Int: Expressible { public var expression: Expression<()> { - return Expression(value: self) + // FIXME: rdar://TODO segfaults during archive // return Expression(value: self) + return Expression(binding: datatypeValue) } } @@ -927,26 +928,22 @@ public func ^= (column: Expression, valu public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } -public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } -public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } -public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } -public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } -//public postfix func ++ (column: Expression) -> Setter { -// // rdar://18825175 segfaults during archive: // column += 1 -// return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) -//} -//public postfix func ++ (column: Expression) -> Setter { -// // rdar://18825175 segfaults during archive: // column += 1 -// return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) -//} -//public postfix func -- (column: Expression) -> Setter { -// // rdar://18825175 segfaults during archive: // column -= 1 -// return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) -//} -//public postfix func -- (column: Expression) -> Setter { -// // rdar://18825175 segfaults during archive: // column -= 1 -// return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) -//} +public postfix func ++ (column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column += 1 + return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) +} +public postfix func ++ (column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column += 1 + return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) +} +public postfix func -- (column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column -= 1 + return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) +} +public postfix func -- (column: Expression) -> Setter { + // rdar://18825175 segfaults during archive: // column -= 1 + return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) +} // MARK: - Internal From e0e94a3bd67972dd74ccf254ec1110b8a0c6d063 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 21 Mar 2015 10:31:48 -0700 Subject: [PATCH 0214/1046] Highlight unhighlighted documentation Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index d0859560..368e16aa 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1342,7 +1342,7 @@ db.create(collation: "NODIACRITIC") { lhs, rhs in We can reference a custom collation using the `Custom` member of the `Collation` enumeration. -``` +``` swift restaurants.order(collate(.Custom("NODIACRITIC"), name)) // SELECT * FROM "restaurants" ORDER BY "name" COLLATE "NODIACRITIC" ``` From 4521c358272e4d0df412d81bbca97325f181d918 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 21 Mar 2015 10:58:06 -0700 Subject: [PATCH 0215/1046] README cleanup - Stop using numeric link references. - Document discrepancy between SQLCipher and FTS4 Signed-off-by: Stephen Celis --- README.md | 58 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index bf17f679..f8463ba6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # SQLite.swift -A type-safe, [Swift][1.1]-language layer over [SQLite3][1.2]. +A type-safe, [Swift][]-language layer over [SQLite3][]. -[SQLite.swift][1.3] provides compile-time confidence in SQL statement +[SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. -[1.1]: https://developer.apple.com/swift/ -[1.2]: http://www.sqlite.org -[1.3]: https://github.com/stephencelis/SQLite.swift +[Swift]: https://developer.apple.com/swift/ +[SQLite3]: http://www.sqlite.org +[SQLite.swift]: https://github.com/stephencelis/SQLite.swift ## Features @@ -104,8 +104,8 @@ interactively, from the Xcode project’s playground. ## Installation -> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode -> 6.1](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode][] 6.1) or +> greater. > > For the Swift 1.2 beta (included in Xcode 6.3), use the > [`swift-1-2`](https://github.com/stephencelis/SQLite.swift/tree/swift-1-2) @@ -113,13 +113,13 @@ interactively, from the Xcode project’s playground. > > The following instructions apply to targets that support embedded > Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line -> tool, please read the [Frameworkless Targets][4.0] section of the +> tool, please read the [Frameworkless Targets][] section of the > documentation. To install SQLite.swift: 1. Drag the **SQLite.xcodeproj** file into your own project. - ([Submodule][4.2], clone, or [download][4.3] the project first.) + ([Submodule][], clone, or [download][] the project first.) ![](Documentation/Resources/installation@2x.png) @@ -132,10 +132,10 @@ To install SQLite.swift: 4. Add **SQLite.framework** to a **Copy Files** build phase with a **Frameworks** destination. (Add a new build phase if need be.) -[4.0]: Documentation/Index.md#frameworkless-targets -[4.1]: https://developer.apple.com/xcode/downloads/ -[4.2]: http://git-scm.com/book/en/Git-Tools-Submodules -[4.3]: https://github.com/stephencelis/SQLite.swift/archive/master.zip +[Frameworkless Targets]: Documentation/Index.md#frameworkless-targets +[Xcode]: https://developer.apple.com/xcode/downloads/ +[Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules +[download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip ### SQLCipher @@ -150,19 +150,29 @@ To install SQLite.swift with [SQLCipher][] support: 2. Follow [the instructions above](#installation) with the **SQLiteCipher** target, instead. +> _Note:_ By default, SQLCipher compiles [without support for full-text +> search][]. If you intend to use [FTS4][], make sure you add the +> following to **Other C Flags** in the **Build Settings** of the +> **sqlcipher** target (in the **sqlcipher.xcodeproj** project): +> +> - `-DSQLITE_ENABLE_FTS4` +> - `-DSQLITE_ENABLE_FTS3_PARENTHESIS` + [SQLCipher]: http://sqlcipher.net +[without support for full-text search]: https://github.com/sqlcipher/sqlcipher/issues/102 +[FTS4]: http://www.sqlite.org/fts3.html ## Communication - Need **help** or have a **general question**? [Ask on Stack - Overflow][5.1] (tag `sqlite.swift`). - - Found a **bug** or have a **feature request**? [Open an issue][5.2]. - - Want to **contribute**? [Submit a pull request][5.3]. + Overflow][] (tag `sqlite.swift`). + - Found a **bug** or have a **feature request**? [Open an issue][]. + - Want to **contribute**? [Submit a pull request][]. -[5.1]: http://stackoverflow.com/questions/tagged/sqlite.swift -[5.2]: https://github.com/stephencelis/SQLite.swift/issues/new -[5.3]: https://github.com/stephencelis/SQLite.swift/fork +[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new +[Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork ## Author @@ -173,15 +183,13 @@ To install SQLite.swift with [SQLCipher][] support: ## License -SQLite.swift is available under the MIT license. See [the LICENSE file][7.1] -for more information. - -[7.1]: ./LICENSE.txt +SQLite.swift is available under the MIT license. See [the LICENSE +file](./LICENSE.txt) for more information. ## Alternatives -Looking for something else? Try another Swift wrapper (or [FMDB][8.1]): +Looking for something else? Try another Swift wrapper (or [FMDB][]): - [Camembert](https://github.com/remirobert/Camembert) - [EonilSQLite3](https://github.com/Eonil/SQLite3) @@ -190,4 +198,4 @@ Looking for something else? Try another Swift wrapper (or [FMDB][8.1]): - [SwiftData](https://github.com/ryanfowler/SwiftData) - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) -[8.1]: https://github.com/ccgus/fmdb +[FMDB]: https://github.com/ccgus/fmdb From c82559f6e51461d8d98146fe1b7617bca34a8d73 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 21 Mar 2015 11:28:24 -0700 Subject: [PATCH 0216/1046] Document FTS And update a few other documentation-related things along the way. Signed-off-by: Stephen Celis --- Documentation/Index.md | 52 ++++++++++++++++++++++++++++++++++++++---- README.md | 2 ++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 368e16aa..f97ab412 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -53,6 +53,7 @@ - [Aggregate SQLite Functions](#aggregate-sqlite-functions) - [Custom SQL Functions](#custom-sql-functions) - [Custom Collations](#custom-collations) + - [Full-text Search](#full-text-search) - [Executing Arbitrary SQL](#executing-arbitrary-sql) - [Logging](#logging) @@ -81,13 +82,16 @@ You should now be able to `import SQLite` from any of your target’s source fil ### SQLCipher -To install SQLite.swift with [SQLCipher][] support: +To install SQLite.swift with [SQLCipher](http://sqlcipher.net) support: 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If **sqlcipher.xcodeproj** (in the **Vendor** group) is unavailable (and appears red), go to the **Source Control** menu and select **Check Out sqlcipher…** from the **sqlcipher** menu item. 2. Follow [the instructions above](#installation) with the **SQLiteCipher** target, instead. -[SQLCipher]: http://sqlcipher.net +> _Note:_ By default, SQLCipher compiles [without support for full-text search](https://github.com/sqlcipher/sqlcipher/issues/102). If you intend to use [FTS4](#full-text-search), make sure you add the following to **Other C Flags** in the **Build Settings** of the **sqlcipher** target (in the **sqlcipher.xcodeproj** project): +> +> - `-DSQLITE_ENABLE_FTS4` +> - `-DSQLITE_ENABLE_FTS3_PARENTHESIS` ### Frameworkless Targets @@ -101,7 +105,7 @@ It’s possible to use SQLite.swift in a target that doesn’t support framework 3. Add the following line to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`. ``` swift - #import "SQLite-Bridging.h' + #import "SQLite-Bridging.h" ``` > _Note:_ Adding SQLite.swift source files directly to your application will both remove the `SQLite` module namespace and expose internal functions and variables. Please [report any namespace collisions and bugs](https://github.com/stephencelis/SQLite.swift/issues/new) you encounter. @@ -144,7 +148,7 @@ var path = NSSearchPathForDirectoriesInDomains( .ApplicationSupportDirectory, .UserDomainMask, true ).first as String + NSBundle.mainBundle().bundleIdentifier! -// create parent directory iff it doesn't exist +// create parent directory iff it doesn’t exist NSFileManager.defaultManager().createDirectoryAtPath( path, withIntermediateDirectories: true, attributes: nil, error: nil ) @@ -205,6 +209,8 @@ SQLite.swift comes with a typed expression layer that directly maps [Swift types > †SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes. > > See [Custom Types](#custom-types) for more information about extending other classes and structures to work with SQLite.swift. +> +> See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed layer and execute raw SQL, instead. These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`Query`](#queries)), can create and execute SQL statements. @@ -1348,9 +1354,45 @@ restaurants.order(collate(.Custom("NODIACRITIC"), name)) ``` +## Full-text Search + +We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create(vtable:)` on a database connection. + +``` swift +let emails = db["emails"] +let subject = Expression("subject") +let body = Expression("body") + +db.create(vtable: emails, using: fts4(subject, body)) +// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body") +``` + +We can specify a [tokenizer](http://www.sqlite.org/fts3.html#tokenizer) using the `tokenize` parameter. + +``` swift +db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) +// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) +``` + +Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. + +``` swift +emails.insert( + subject <- "Just Checking In", + body <- "Hey, I was just wondering...did you get my last email?" +)! + +emails.filter(match(emails, "wonder*")) +// SELECT * FROM "emails" WHERE "emails" MATCH 'wonder*' + +emails.filter(match(subject, "Re:*")) +// SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*' +``` + + ## Executing Arbitrary SQL -Though we recommend you stick with SQLite.swift’s type-safe system whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. +Though we recommend you stick with SQLite.swift’s [type-safe system](#building-type-safe-sql) whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. - `execute` runs an arbitrary number of SQL statements as a convenience. diff --git a/README.md b/README.md index f8463ba6..e9a58f07 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,12 @@ syntax _and_ intent. - A lightweight, uncomplicated query and parameter binding interface - Transactions with implicit commit/rollback - Developer-friendly error handling and debugging + - [Full-text search][] support - [SQLCipher](#sqlcipher) support - [Well-documented][See Documentation] - Extensively tested +[Full-text search]: Documentation/Index.md#full-text-search [See Documentation]: Documentation/Index.md#sqliteswift-documentation From 6f3eef7619ddbf1e97726dc87008bf322db86e29 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 21 Mar 2015 11:57:58 -0700 Subject: [PATCH 0217/1046] Basic R*Tree module support Signed-off-by: Stephen Celis --- SQLite Tests/RTreeTests.swift | 19 +++++++++++++++++++ SQLite.xcodeproj/project.pbxproj | 10 ++++++++++ SQLite/RTree.swift | 29 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 SQLite Tests/RTreeTests.swift create mode 100644 SQLite/RTree.swift diff --git a/SQLite Tests/RTreeTests.swift b/SQLite Tests/RTreeTests.swift new file mode 100644 index 00000000..64eecac3 --- /dev/null +++ b/SQLite Tests/RTreeTests.swift @@ -0,0 +1,19 @@ +import XCTest +import SQLite + +class RTreeTests: XCTestCase { + + let id = Expression("id") + let minX = Expression("minX") + let maxX = Expression("maxX") + let minY = Expression("minY") + let maxY = Expression("maxY") + + let db = Database() + var index: Query { return db["index"] } + + func test_createVtable_usingRtree_createsVirtualTable() { + ExpectExecution(db, "CREATE VIRTUAL TABLE \"index\" USING rtree(\"id\", \"minX\", \"maxX\", \"minY\", \"maxY\")", db.create(vtable: index, using: rtree(id, minX, maxX, minY, maxY))) + } + +} diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 655879ee..c6c9868d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -25,6 +25,9 @@ DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */; }; + DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28401ABDF18F0042A3FC /* RTree.swift */; }; + DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28401ABDF18F0042A3FC /* RTree.swift */; }; + DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */; }; DCC6B36F1A9191C300734B78 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCC6B3701A9191C300734B78 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; DCC6B3711A9191C300734B78 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; @@ -106,6 +109,8 @@ DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; + DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; + DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; @@ -161,6 +166,7 @@ DCAD429619E2E0F1004A51DF /* Query.swift */, DC109CE01A0C4D970070988E /* Schema.swift */, DCAFEAD21AABC818000C21A1 /* FTS.swift */, + DCBE28401ABDF18F0042A3FC /* RTree.swift */, ); name = "Query Building"; sourceTree = ""; @@ -186,6 +192,7 @@ DCAD429919E2EE50004A51DF /* QueryTests.swift */, DC109CE31A0C4F5D0070988E /* SchemaTests.swift */, DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */, + DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */, DC37740319C8CBB3004FCF85 /* Supporting Files */, ); path = "SQLite Tests"; @@ -477,6 +484,7 @@ DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */, DC37743819C8D693004FCF85 /* Value.swift in Sources */, DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */, + DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */, DCAD429719E2E0F1004A51DF /* Query.swift in Sources */, DC109CE11A0C4D970070988E /* Schema.swift in Sources */, DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, @@ -496,6 +504,7 @@ DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */, DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */, DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */, + DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,6 +521,7 @@ DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */, DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */, DCC6B3751A9191C300734B78 /* SQLite-Bridging.c in Sources */, + DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */, DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SQLite/RTree.swift b/SQLite/RTree.swift new file mode 100644 index 00000000..13866f60 --- /dev/null +++ b/SQLite/RTree.swift @@ -0,0 +1,29 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright (c) 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public func rtree(primaryKey: Expression, columns: Expression...) -> Expression<()> { + var definitions: [Expressible] = [primaryKey] + definitions.extend(columns.map { $0 }) + return wrap(__FUNCTION__, Expression<()>.join(", ", definitions)) +} From 83f08f721591003b95a03bf6c6139da5b8fee737 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Mar 2015 10:41:13 -0700 Subject: [PATCH 0218/1046] Add argument count to create(function:) Allows for the definition of multiple functions with the same name but a differing number of arguments. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 8 +++-- SQLite/Functions.swift | 68 ++++++++++++++++++++-------------------- SQLite/SQLite-Bridging.c | 4 +-- SQLite/SQLite-Bridging.h | 2 +- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index f2fef23c..88841715 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -387,6 +387,10 @@ public final class Database { /// /// :param: function The name of the function to create or redefine. /// + /// :param: argc The number of arguments that the function takes. + /// If this parameter is -1, then the SQL function may + /// take any number of arguments. (Default: -1.) + /// /// :param: deterministic Whether or not the function is deterministic. If /// the function always returns the same result for a /// given input, SQLite can make optimizations. @@ -396,8 +400,8 @@ public final class Database { /// called. The block is called with an array of raw /// SQL values mapped to the function's parameters and /// should return a raw SQL value (or nil). - public func create(#function: String, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { - try(SQLiteCreateFunction(handle, function, deterministic ? 1 : 0) { context, argc, argv in + public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { + try(SQLiteCreateFunction(handle, function, Int32(argc), deterministic ? 1 : 0) { context, argc, argv in let arguments: [Binding?] = map(0..(#function: String, deterministic: Bool = false, _ block: () -> Z) -> (() -> Expression) { - return { self.create(function, deterministic) { _ in return block() }([]) } + return { self.create(function, 0, deterministic) { _ in return block() }([]) } } public func create(#function: String, deterministic: Bool = false, _ block: () -> Z?) -> (() -> Expression) { - return { self.create(function, deterministic) { _ in return block() }([]) } + return { self.create(function, 0, deterministic) { _ in return block() }([]) } } // MARK: 1 Argument public func create(#function: String, deterministic: Bool = false, _ block: A -> Z) -> (Expression -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0])) }([$0]) } + return { self.create(function, 1, deterministic) { block(asValue($0[0])) }([$0]) } } public func create(#function: String, deterministic: Bool = false, _ block: A? -> Z) -> (Expression -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue)) }([$0]) } + return { self.create(function, 1, deterministic) { block($0[0].map(asValue)) }([$0]) } } public func create(#function: String, deterministic: Bool = false, _ block: A -> Z?) -> (Expression -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0])) }([$0]) } + return { self.create(function, 1, deterministic) { block(asValue($0[0])) }([$0]) } } public func create(#function: String, deterministic: Bool = false, _ block: A? -> Z?) -> (Expression -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue)) }([$0]) } + return { self.create(function, 1, deterministic) { block($0[0].map(asValue)) }([$0]) } } // MARK: 2 Arguments public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((A, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((A?, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((A, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((A?, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((A, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((A?, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((A, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } } public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((A?, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } } public func create(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((Expression, B) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((Expression, B) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((Expression, B?) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((Expression, B?) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((Expression, B) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((Expression, B) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((Expression, B?) -> Expression) { - return { self.create(function, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } + return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } } public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((Expression, B?) -> Expression) { - return { self.create(function, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } + return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } } // MARK: - - private func create(function: String, _ deterministic: Bool, _ block: [Binding?] -> Z) -> ([Expressible] -> Expression) { - return { Expression(self.create(function, deterministic) { (arguments: [Binding?]) -> Z? in block(arguments) }($0)) } + private func create(function: String, _ argc: Int, _ deterministic: Bool, _ block: [Binding?] -> Z) -> ([Expressible] -> Expression) { + return { Expression(self.create(function, argc, deterministic) { (arguments: [Binding?]) -> Z? in block(arguments) }($0)) } } - private func create(function: String, _ deterministic: Bool, _ block: [Binding?] -> Z?) -> ([Expressible] -> Expression) { - create(function: function, deterministic: deterministic) { block($0)?.datatypeValue } + private func create(function: String, _ argc: Int, _ deterministic: Bool, _ block: [Binding?] -> Z?) -> ([Expressible] -> Expression) { + create(function: function, argc: argc, deterministic: deterministic) { block($0)?.datatypeValue } return { arguments in wrap(quote(identifier: function), Expression.join(", ", arguments)) } } diff --git a/SQLite/SQLite-Bridging.c b/SQLite/SQLite-Bridging.c index 56f309b3..eccfa431 100644 --- a/SQLite/SQLite-Bridging.c +++ b/SQLite/SQLite-Bridging.c @@ -54,7 +54,7 @@ void _SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** ((SQLiteCreateFunctionCallback)sqlite3_user_data(context))(context, argc, argv); } -int SQLiteCreateFunction(sqlite3 * handle, const char * name, int deterministic, SQLiteCreateFunctionCallback callback) { +int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback) { if (callback) { int flags = SQLITE_UTF8; if (deterministic) { @@ -62,7 +62,7 @@ int SQLiteCreateFunction(sqlite3 * handle, const char * name, int deterministic, flags |= SQLITE_DETERMINISTIC; #endif } - return sqlite3_create_function_v2(handle, name, -1, flags, Block_copy(callback), &_SQLiteCreateFunction, 0, 0, 0); // FIXME: leak + return sqlite3_create_function_v2(handle, name, argc, flags, Block_copy(callback), &_SQLiteCreateFunction, 0, 0, 0); // FIXME: leak } else { return sqlite3_create_function_v2(handle, name, 0, 0, 0, 0, 0, 0, 0); } diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index b0ff07c9..283254e8 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -31,7 +31,7 @@ typedef void (^SQLiteTraceCallback)(const char * SQL); void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback); typedef void (^SQLiteCreateFunctionCallback)(sqlite3_context * context, int argc, sqlite3_value ** argv); -int SQLiteCreateFunction(sqlite3 * handle, const char * name, int deterministic, SQLiteCreateFunctionCallback callback); +int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback); typedef int (^SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback); From 5532303135d228d4bb4dee52a8f2cf2df5ac1dfd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 10 Feb 2015 08:59:10 -0800 Subject: [PATCH 0219/1046] Swift 1.2 beta 1 Most things should work as they have been, with a few notes: - The `db.transaction` helpers that took variadic auto-closures have been removed (Swift no longer supports variadic auto-closures). Update path: use `&&` and `||` for control flow: db.transaction() && stmt1 && stmt2 && db.commit() || db.rollback() Or use the block-based helper: db.transaction { _ in stmt1.run() if stmt1.failed { return .Rollback } stmt2.run() if stmt2.failed { return .Rollback } return .Commit } Note: You'll need to explicitly call/return COMMIT and ROLLBACK now. - There appears to be a bug in Swift causing 2 memory-related, over-releasing crashes in the test suite. Filed: rdar://19782170 Many bugs marked FIXME with links to rdars are now fixable and have been fixed. The tests have also been heavily refactored (they were abusing the power of `@autoclosure`, which has been curtailed with `@noescape`), but should be generally more readable, if slightly less flexible. Signed-off-by: Stephen Celis --- Documentation/Index.md | 56 +- README.md | 7 +- SQLite Tests/DatabaseTests.swift | 313 ++++--- SQLite Tests/ExpressionTests.swift | 885 +++++++++--------- SQLite Tests/FTSTests.swift | 15 +- SQLite Tests/FunctionsTests.swift | 163 ++-- SQLite Tests/QueryTests.swift | 282 ++---- SQLite Tests/RTreeTests.swift | 16 +- SQLite Tests/SchemaTests.swift | 461 ++++----- SQLite Tests/StatementTests.swift | 103 +- SQLite Tests/TestHelper.swift | 100 +- SQLite.playground/section-16.swift | 9 +- SQLite.playground/section-18.swift | 10 +- SQLite.playground/section-26.swift | 8 +- SQLite.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/SQLite.xcscheme | 2 +- .../xcschemes/SQLiteCipher.xcscheme | 2 +- SQLite/Database.swift | 270 +++--- SQLite/Expression.swift | 53 +- SQLite/Functions.swift | 2 +- SQLite/Query.swift | 20 +- SQLite/Statement.swift | 22 +- SQLite/Value.swift | 4 +- SQLiteCipher Tests/CipherTests.swift | 10 +- SQLiteCipher/Cipher.swift | 4 +- 25 files changed, 1362 insertions(+), 1457 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index f97ab412..4edfb708 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -237,7 +237,7 @@ let name = Expression("name") ### Compound Expressions -Expressions can be combined with other expressions and types using [filters](#filter-operators-and-functions), and [other operators](#other-operators) and [functions](#core-sqlite-functions). These building blocks can create complex SQLite statements. +Expressions can be combined with other expressions and types using [filter operators and functions](#filter-operators-and-functions) (as well as other [non-filter operators](#other-operators) and [functions](#core-sqlite-functions)). These building blocks can create complex SQLite statements. ### Queries @@ -407,23 +407,19 @@ The `insert` function can return several different types that are useful in diff } ``` - We can use the optional nature of the value to disambiguate with a simple `?` or `!`. + If a value is always expected, we can disambiguate with a `!`. ``` swift - // ignore failure - users.insert(email <- "alice@mac.com")? - - // assertion on failure users.insert(email <- "alice@mac.com")! ``` - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. ``` swift - db.transaction( - users.insert(email <- "alice@mac.com"), - users.insert(email <- "betty@mac.com") - ) + db.transaction() + && users.insert(email <- "alice@mac.com") + && users.insert(email <- "betty@mac.com") + && db.commit() || db.rollback() // BEGIN DEFERRED TRANSACTION; // INSERT INTO "users" ("email") VALUES ('alice@mac.com'); // INSERT INTO "users" ("email") VALUES ('betty@mac.com'); @@ -473,10 +469,10 @@ To take an amount and “move” it via transaction, we can use `-=` and `+=`: ``` swift let amount = 100.0 -db.transaction( - alice.update(balance -= amount), - betty.update(balance += amount) -) +db.transaction() + && alice.update(balance -= amount) + && betty.update(balance += amount) + && db.commit() || db.rollback() // BEGIN DEFERRED TRANSACTION; // UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1); // UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2); @@ -841,13 +837,9 @@ Like [`insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can } ``` - We can use the optional nature of the value to disambiguate with a simple `?` or `!`. + If a value is always expected, we can disambiguate with a `!`. ``` swift - // ignore failure - alice.update(email <- "alice@me.com")? - - // assertion on failure alice.update(email <- "alice@me.com")! ``` @@ -885,13 +877,9 @@ Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can re } ``` - We can use the optional nature of the value to disambiguate with a simple `?` or `!`. + If a value is always expected, we can disambiguate with a `!`. ``` swift - // ignore failure - alice.delete()? - - // assertion on failure alice.delete()! ``` @@ -902,13 +890,13 @@ Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can re ## Transactions and Savepoints -Using the `transaction` and `savepoint` functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back. +Using the `transaction` and `savepoint` functions, we can run a series of statements, committing the changes to the database if they all succeed. If a single statement fails, we can bail out early and roll back. ``` swift -db.transaction( - users.insert(email <- "betty@icloud.com"), - users.insert(email <- "cathy@icloud.com", manager_id <- db.lastId) -) +db.transaction() + && users.insert(email <- "betty@icloud.com") + && users.insert(email <- "cathy@icloud.com", manager_id <- db.lastId) + && db.commit() || db.rollback() ``` > _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastId` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. @@ -1319,12 +1307,10 @@ We can create loosely-typed functions by handling an array of raw arguments, ins ``` swift db.create(function: "typeConformsTo", deterministic: true) { args in - switch (args[0], args[1]) { - case let (UTI as String, conformsToUTI as String): + if let UTI = args[0] as? String, conformsToUTI = args[1] as? String { return Int(UTTypeConformsTo(UTI, conformsToUTI)) - default: - return nil } + return nil } ``` @@ -1447,14 +1433,14 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. ``` swift - db.scalar("SELECT count(*) FROM users") as Int64 + db.scalar("SELECT count(*) FROM users") as! Int64 ``` Statements also have a `scalar` function, which can optionally re-bind values at execution. ``` swift let stmt = db.prepare("SELECT count (*) FROM users") - stmt.scalar() as Int64 + stmt.scalar() as! Int64 ``` diff --git a/README.md b/README.md index e9a58f07..2b60a0c7 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ syntax _and_ intent. - A flexible, chainable, lazy-executing query layer - Automatically-typed data access - A lightweight, uncomplicated query and parameter binding interface - - Transactions with implicit commit/rollback - Developer-friendly error handling and debugging - [Full-text search][] support - [SQLCipher](#sqlcipher) support @@ -106,13 +105,9 @@ interactively, from the Xcode project’s playground. ## Installation -> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode][] 6.1) or +> _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode][] 6.3) or > greater. > -> For the Swift 1.2 beta (included in Xcode 6.3), use the -> [`swift-1-2`](https://github.com/stephencelis/SQLite.swift/tree/swift-1-2) -> branch. -> > The following instructions apply to targets that support embedded > Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line > tool, please read the [Frameworkless Targets][] section of the diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index 7aaf894e..58f3fd2e 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -1,14 +1,12 @@ import XCTest import SQLite -class DatabaseTests: XCTestCase { - - let db = Database() +class DatabaseTests: SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable(db) + createUsersTable() } func test_readonly_returnsFalseOnReadWriteConnections() { @@ -25,7 +23,7 @@ class DatabaseTests: XCTestCase { } func test_lastId_returnsLastIdAfterInserts() { - InsertUser(db, "alice") + insertUser("alice") XCTAssert(db.lastId! == 1) } @@ -34,17 +32,17 @@ class DatabaseTests: XCTestCase { } func test_lastChanges_returnsNumberOfChanges() { - InsertUser(db, "alice") + insertUser("alice") XCTAssertEqual(1, db.lastChanges) - InsertUser(db, "betsy") + insertUser("betsy") XCTAssertEqual(1, db.lastChanges) } func test_totalChanges_returnsTotalNumberOfChanges() { XCTAssertEqual(0, db.totalChanges) - InsertUser(db, "alice") + insertUser("alice") XCTAssertEqual(1, db.totalChanges) - InsertUser(db, "betsy") + insertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -57,134 +55,213 @@ class DatabaseTests: XCTestCase { } func test_run_preparesRunsAndReturnsStatements() { - ExpectExecutions(db, ["SELECT * FROM users WHERE admin = 0": 4]) { db in - db.run("SELECT * FROM users WHERE admin = 0") - db.run("SELECT * FROM users WHERE admin = ?", 0) - db.run("SELECT * FROM users WHERE admin = ?", [0]) - db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) - } + db.run("SELECT * FROM users WHERE admin = 0") + db.run("SELECT * FROM users WHERE admin = ?", 0) + db.run("SELECT * FROM users WHERE admin = ?", [0]) + db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + AssertSQL("SELECT * FROM users WHERE admin = 0", 4) } func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = 0") as Int64) - XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as Int64) - XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as Int64) - XCTAssertEqual(Int64(0), db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as Int64) - } - - func test_transaction_beginsAndCommitsStatements() { - let fulfilled = [ - "BEGIN DEFERRED TRANSACTION": 1, - "COMMIT TRANSACTION": 1, - "ROLLBACK TRANSACTION": 0 - ] - ExpectExecutions(db, fulfilled) { db in - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(stmt.bind("alice@example.com", 1)) - } + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as! Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as! Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as! Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as! Int64) + AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } func test_transaction_executesBeginDeferred() { - ExpectExecutions(db, ["BEGIN DEFERRED TRANSACTION": 1]) { db in - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(.Deferred, stmt.bind("alice@example.com", 1)) - } + db.transaction(.Deferred) { _ in .Commit } + + AssertSQL("BEGIN DEFERRED TRANSACTION") } func test_transaction_executesBeginImmediate() { - ExpectExecutions(db, ["BEGIN IMMEDIATE TRANSACTION": 1]) { db in - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(.Immediate, stmt.bind("alice@example.com", 1)) - } + db.transaction(.Immediate) { _ in .Commit } + + AssertSQL("BEGIN IMMEDIATE TRANSACTION") } func test_transaction_executesBeginExclusive() { - ExpectExecutions(db, ["BEGIN EXCLUSIVE TRANSACTION": 1]) { db in - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(.Exclusive, stmt.bind("alice@example.com", 1)) - } + db.transaction(.Exclusive) { _ in .Commit } + + AssertSQL("BEGIN EXCLUSIVE TRANSACTION") } - func test_transaction_rollsBackOnFailure() { + func test_commit_commitsTransaction() { + db.transaction() + + db.commit() + + AssertSQL("COMMIT TRANSACTION") + } + + func test_rollback_rollsTransactionBack() { + db.transaction() + + db.rollback() + + AssertSQL("ROLLBACK TRANSACTION") + } + + func test_transaction_beginsAndCommitsTransactions() { + let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) + + db.transaction { _ in stmt.run().failed ? .Rollback : .Commit } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)") + AssertSQL("COMMIT TRANSACTION") + AssertSQL("ROLLBACK TRANSACTION", 0) + } + + func test_transaction_beginsAndRollsTransactionsBack() { + let stmt = db.run("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) + + db.transaction { _ in stmt.run().failed ? .Rollback : .Commit } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) + AssertSQL("ROLLBACK TRANSACTION") + AssertSQL("COMMIT TRANSACTION", 0) + } + + func test_transaction_withOperators_allowsForFlowControl() { let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(stmt.bind("alice@example.com", 1)) - let fulfilled = [ - "COMMIT TRANSACTION": 0, - "ROLLBACK TRANSACTION": 1, - "INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)": 1 - ] - var txn: Statement! - ExpectExecutions(db, fulfilled) { db in - txn = db.transaction( - stmt.bind("alice@example.com", 1), - stmt.bind("alice@example.com", 1) - ) - return - } + let txn = db.transaction() && + stmt.bind("alice@example.com", 1) && + stmt.bind("alice@example.com", 1) && + stmt.bind("alice@example.com", 1) && + db.commit() + txn || db.rollback() + XCTAssertTrue(txn.failed) XCTAssert(txn.reason!.lowercaseString.rangeOfString("unique") != nil) + + AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) + AssertSQL("ROLLBACK TRANSACTION") + AssertSQL("COMMIT TRANSACTION", 0) } - func test_savepoint_nestsAndNamesSavepointsAutomatically() { - let fulfilled = [ - "SAVEPOINT '1'": 1, - "SAVEPOINT '2'": 2, - "RELEASE SAVEPOINT '2'": 2, - "RELEASE SAVEPOINT '1'": 1, - ] - ExpectExecutions(db, fulfilled) { db in - db.savepoint( - db.savepoint( - InsertUser(db, "alice"), - InsertUser(db, "betsy"), - InsertUser(db, "cindy") - ), - db.savepoint( - InsertUser(db, "donna"), - InsertUser(db, "emery"), - InsertUser(db, "flint") - ) - ) - return - } + func test_savepoint_quotesSavepointNames() { + db.savepoint("That's all, Folks!") + + AssertSQL("SAVEPOINT 'That''s all, Folks!'") } - func test_savepoint_rollsBackOnFailure() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - let fulfilled = [ - "SAVEPOINT '1'": 1, - "SAVEPOINT '2'": 1, - "RELEASE SAVEPOINT '2'": 0, - "RELEASE SAVEPOINT '1'": 0, - "ROLLBACK TO SAVEPOINT '1'": 1, - "INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)": 2 - ] - ExpectExecutions(db, fulfilled) { db in - db.savepoint( - db.savepoint( - stmt.run("alice@example.com", 1), - stmt.run("alice@example.com", 1), - stmt.run("alice@example.com", 1) - ), - db.savepoint( - stmt.run("alice@example.com", 1), - stmt.run("alice@example.com", 1), - stmt.run("alice@example.com", 1) - ) - ) - return - } + func test_release_quotesSavepointNames() { + let savepointName = "That's all, Folks!" + db.savepoint(savepointName) + + db.release(savepointName) + + AssertSQL("RELEASE SAVEPOINT 'That''s all, Folks!'") } - func test_savepoint_quotesNames() { - let fulfilled = [ - "SAVEPOINT 'That''s all, Folks!'": 1, - "RELEASE SAVEPOINT 'That''s all, Folks!'": 1 - ] - ExpectExecutions(db, fulfilled) { db in - db.savepoint("That's all, Folks!", db.run("SELECT 1")) - return - } + func test_release_defaultsToCurrentSavepointName() { + db.savepoint("Hello, World!") + + db.release() + + AssertSQL("RELEASE SAVEPOINT 'Hello, World!'") + } + + func test_release_maintainsTheSavepointNameStack() { + db.savepoint("1") + db.savepoint("2") + db.savepoint("3") + + db.release("2") + db.release() + + AssertSQL("RELEASE SAVEPOINT '2'") + AssertSQL("RELEASE SAVEPOINT '1'") + AssertSQL("RELEASE SAVEPOINT '3'", 0) + } + + func test_rollback_quotesSavepointNames() { + let savepointName = "That's all, Folks!" + db.savepoint(savepointName) + + db.rollback(savepointName) + + AssertSQL("ROLLBACK TO SAVEPOINT 'That''s all, Folks!'") + } + + func test_rollback_defaultsToCurrentSavepointName() { + db.savepoint("Hello, World!") + + db.rollback() + + AssertSQL("ROLLBACK TO SAVEPOINT 'Hello, World!'") + } + + func test_rollback_maintainsTheSavepointNameStack() { + db.savepoint("1") + db.savepoint("2") + db.savepoint("3") + + db.rollback("2") + db.rollback() + + AssertSQL("ROLLBACK TO SAVEPOINT '2'") + AssertSQL("ROLLBACK TO SAVEPOINT '1'") + AssertSQL("ROLLBACK TO SAVEPOINT '3'", 0) + } + + func test_savepoint_beginsAndReleasesTransactions() { + let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) + + db.savepoint("1") { _ in stmt.run().failed ? .Rollback : .Release } + + AssertSQL("SAVEPOINT '1'") + AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)") + AssertSQL("RELEASE SAVEPOINT '1'") + AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) + } + + func test_savepoint_beginsAndRollsTransactionsBack() { + let stmt = db.run("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) + + db.savepoint("1") { _ in stmt.run().failed ? .Rollback : .Release } + + AssertSQL("SAVEPOINT '1'") + AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) + AssertSQL("ROLLBACK TO SAVEPOINT '1'", 1) + AssertSQL("RELEASE SAVEPOINT '1'", 0) + } + + func test_savepoint_withOperators_allowsForFlowControl() { + let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") + + var txn = db.savepoint("1") + + txn = txn && ( + db.savepoint("2") && + stmt.run("alice@example.com", 1) && + stmt.run("alice@example.com", 1) && + stmt.run("alice@example.com", 1) && + db.release() + ) + txn || db.rollback() + + txn = txn && ( + db.savepoint("2") && + stmt.run("alice@example.com", 1) && + stmt.run("alice@example.com", 1) && + stmt.run("alice@example.com", 1) && + db.release() + ) + txn || db.rollback() + + txn && db.release() || db.rollback() + + AssertSQL("SAVEPOINT '1'") + AssertSQL("SAVEPOINT '2'") + AssertSQL("RELEASE SAVEPOINT '2'", 0) + AssertSQL("RELEASE SAVEPOINT '1'", 0) + AssertSQL("ROLLBACK TO SAVEPOINT '1'") + AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) } func test_userVersion_getsAndSetsUserVersion() { @@ -202,14 +279,14 @@ class DatabaseTests: XCTestCase { func test_createFunction_withArrayArguments() { db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as String) + XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as! String) XCTAssert(db.scalar("SELECT hello(NULL)") == nil) } func test_createFunction_createsQuotableFunction() { db.create(function: "hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as String) + XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as! String) XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) } @@ -217,14 +294,14 @@ class DatabaseTests: XCTestCase { db.create(collation: "NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(Int64(1), db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as Int64) + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as! Int64) } func test_createCollation_createsQuotableCollation() { db.create(collation: "NO DIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(Int64(1), db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as Int64) + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) } } diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index 44e80c71..31e94b57 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -1,359 +1,356 @@ import XCTest import SQLite -class ExpressionTests: XCTestCase { +let stringA = Expression(value: "A") +let stringB = Expression(value: "B") - let db = Database() - var users: Query { return db["users"] } +let int1 = Expression(value: 1) +let int2 = Expression(value: 2) - func ExpectExecutionMatches(SQL: String, _ expression: Expressible) { - ExpectExecution(db, "SELECT \(SQL) FROM \"users\"", users.select(expression)) - } - - let stringA = Expression(value: "A") - let stringB = Expression(value: "B") +let double1 = Expression(value: 1.5) +let double2 = Expression(value: 2.5) - let int1 = Expression(value: 1) - let int2 = Expression(value: 2) +let bool0 = Expression(value: false) +let bool1 = Expression(value: true) - let double1 = Expression(value: 1.5) - let double2 = Expression(value: 2.5) +class ExpressionTests: SQLiteTestCase { - let bool0 = Expression(value: false) - let bool1 = Expression(value: true) + func AssertSQLContains(SQL: String, _ expression: Expression, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + AssertSQL("SELECT \(SQL) FROM \"users\"", users.select(expression), file: file, line: line) + } override func setUp() { - super.setUp() + createUsersTable() - CreateUsersTable(db) + super.setUp() } func test_alias_aliasesExpression() { let aliased = stringA.alias("string_a") - ExpectExecutionMatches("('A') AS \"string_a\"", aliased) + AssertSQLContains("('A') AS \"string_a\"", aliased) } func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { - ExpectExecutionMatches("('A' || 'A')", stringA + stringA) - ExpectExecutionMatches("('A' || 'B')", stringA + stringB) - ExpectExecutionMatches("('B' || 'A')", stringB + stringA) - ExpectExecutionMatches("('B' || 'B')", stringB + stringB) - ExpectExecutionMatches("('A' || 'B')", stringA + "B") - ExpectExecutionMatches("('B' || 'A')", stringB + "A") - ExpectExecutionMatches("('B' || 'A')", "B" + stringA) - ExpectExecutionMatches("('A' || 'B')", "A" + stringB) + AssertSQLContains("('A' || 'A')", stringA + stringA) + AssertSQLContains("('A' || 'B')", stringA + stringB) + AssertSQLContains("('B' || 'A')", stringB + stringA) + AssertSQLContains("('B' || 'B')", stringB + stringB) + AssertSQLContains("('A' || 'B')", stringA + "B") + AssertSQLContains("('B' || 'A')", stringB + "A") + AssertSQLContains("('B' || 'A')", "B" + stringA) + AssertSQLContains("('A' || 'B')", "A" + stringB) } func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() { - ExpectExecutionMatches("(1 + 1)", int1 + int1) - ExpectExecutionMatches("(1 + 2)", int1 + int2) - ExpectExecutionMatches("(2 + 1)", int2 + int1) - ExpectExecutionMatches("(2 + 2)", int2 + int2) - ExpectExecutionMatches("(1 + 2)", int1 + 2) - ExpectExecutionMatches("(2 + 1)", int2 + 1) - ExpectExecutionMatches("(2 + 1)", 2 + int1) - ExpectExecutionMatches("(1 + 2)", 1 + int2) + AssertSQLContains("(1 + 1)", int1 + int1) + AssertSQLContains("(1 + 2)", int1 + int2) + AssertSQLContains("(2 + 1)", int2 + int1) + AssertSQLContains("(2 + 2)", int2 + int2) + AssertSQLContains("(1 + 2)", int1 + 2) + AssertSQLContains("(2 + 1)", int2 + 1) + AssertSQLContains("(2 + 1)", 2 + int1) + AssertSQLContains("(1 + 2)", 1 + int2) } func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() { - ExpectExecutionMatches("(1.5 + 1.5)", double1 + double1) - ExpectExecutionMatches("(1.5 + 2.5)", double1 + double2) - ExpectExecutionMatches("(2.5 + 1.5)", double2 + double1) - ExpectExecutionMatches("(2.5 + 2.5)", double2 + double2) - ExpectExecutionMatches("(1.5 + 2.5)", double1 + 2.5) - ExpectExecutionMatches("(2.5 + 1.5)", double2 + 1.5) - ExpectExecutionMatches("(2.5 + 1.5)", 2.5 + double1) - ExpectExecutionMatches("(1.5 + 2.5)", 1.5 + double2) + AssertSQLContains("(1.5 + 1.5)", double1 + double1) + AssertSQLContains("(1.5 + 2.5)", double1 + double2) + AssertSQLContains("(2.5 + 1.5)", double2 + double1) + AssertSQLContains("(2.5 + 2.5)", double2 + double2) + AssertSQLContains("(1.5 + 2.5)", double1 + 2.5) + AssertSQLContains("(2.5 + 1.5)", double2 + 1.5) + AssertSQLContains("(2.5 + 1.5)", 2.5 + double1) + AssertSQLContains("(1.5 + 2.5)", 1.5 + double2) } func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() { - ExpectExecutionMatches("(1 - 1)", int1 - int1) - ExpectExecutionMatches("(1 - 2)", int1 - int2) - ExpectExecutionMatches("(2 - 1)", int2 - int1) - ExpectExecutionMatches("(2 - 2)", int2 - int2) - ExpectExecutionMatches("(1 - 2)", int1 - 2) - ExpectExecutionMatches("(2 - 1)", int2 - 1) - ExpectExecutionMatches("(2 - 1)", 2 - int1) - ExpectExecutionMatches("(1 - 2)", 1 - int2) + AssertSQLContains("(1 - 1)", int1 - int1) + AssertSQLContains("(1 - 2)", int1 - int2) + AssertSQLContains("(2 - 1)", int2 - int1) + AssertSQLContains("(2 - 2)", int2 - int2) + AssertSQLContains("(1 - 2)", int1 - 2) + AssertSQLContains("(2 - 1)", int2 - 1) + AssertSQLContains("(2 - 1)", 2 - int1) + AssertSQLContains("(1 - 2)", 1 - int2) } func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() { - ExpectExecutionMatches("(1.5 - 1.5)", double1 - double1) - ExpectExecutionMatches("(1.5 - 2.5)", double1 - double2) - ExpectExecutionMatches("(2.5 - 1.5)", double2 - double1) - ExpectExecutionMatches("(2.5 - 2.5)", double2 - double2) - ExpectExecutionMatches("(1.5 - 2.5)", double1 - 2.5) - ExpectExecutionMatches("(2.5 - 1.5)", double2 - 1.5) - ExpectExecutionMatches("(2.5 - 1.5)", 2.5 - double1) - ExpectExecutionMatches("(1.5 - 2.5)", 1.5 - double2) + AssertSQLContains("(1.5 - 1.5)", double1 - double1) + AssertSQLContains("(1.5 - 2.5)", double1 - double2) + AssertSQLContains("(2.5 - 1.5)", double2 - double1) + AssertSQLContains("(2.5 - 2.5)", double2 - double2) + AssertSQLContains("(1.5 - 2.5)", double1 - 2.5) + AssertSQLContains("(2.5 - 1.5)", double2 - 1.5) + AssertSQLContains("(2.5 - 1.5)", 2.5 - double1) + AssertSQLContains("(1.5 - 2.5)", 1.5 - double2) } func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() { - ExpectExecutionMatches("(1 * 1)", int1 * int1) - ExpectExecutionMatches("(1 * 2)", int1 * int2) - ExpectExecutionMatches("(2 * 1)", int2 * int1) - ExpectExecutionMatches("(2 * 2)", int2 * int2) - ExpectExecutionMatches("(1 * 2)", int1 * 2) - ExpectExecutionMatches("(2 * 1)", int2 * 1) - ExpectExecutionMatches("(2 * 1)", 2 * int1) - ExpectExecutionMatches("(1 * 2)", 1 * int2) + AssertSQLContains("(1 * 1)", int1 * int1) + AssertSQLContains("(1 * 2)", int1 * int2) + AssertSQLContains("(2 * 1)", int2 * int1) + AssertSQLContains("(2 * 2)", int2 * int2) + AssertSQLContains("(1 * 2)", int1 * 2) + AssertSQLContains("(2 * 1)", int2 * 1) + AssertSQLContains("(2 * 1)", 2 * int1) + AssertSQLContains("(1 * 2)", 1 * int2) } func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() { - ExpectExecutionMatches("(1.5 * 1.5)", double1 * double1) - ExpectExecutionMatches("(1.5 * 2.5)", double1 * double2) - ExpectExecutionMatches("(2.5 * 1.5)", double2 * double1) - ExpectExecutionMatches("(2.5 * 2.5)", double2 * double2) - ExpectExecutionMatches("(1.5 * 2.5)", double1 * 2.5) - ExpectExecutionMatches("(2.5 * 1.5)", double2 * 1.5) - ExpectExecutionMatches("(2.5 * 1.5)", 2.5 * double1) - ExpectExecutionMatches("(1.5 * 2.5)", 1.5 * double2) + AssertSQLContains("(1.5 * 1.5)", double1 * double1) + AssertSQLContains("(1.5 * 2.5)", double1 * double2) + AssertSQLContains("(2.5 * 1.5)", double2 * double1) + AssertSQLContains("(2.5 * 2.5)", double2 * double2) + AssertSQLContains("(1.5 * 2.5)", double1 * 2.5) + AssertSQLContains("(2.5 * 1.5)", double2 * 1.5) + AssertSQLContains("(2.5 * 1.5)", 2.5 * double1) + AssertSQLContains("(1.5 * 2.5)", 1.5 * double2) } func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() { - ExpectExecutionMatches("(1 / 1)", int1 / int1) - ExpectExecutionMatches("(1 / 2)", int1 / int2) - ExpectExecutionMatches("(2 / 1)", int2 / int1) - ExpectExecutionMatches("(2 / 2)", int2 / int2) - ExpectExecutionMatches("(1 / 2)", int1 / 2) - ExpectExecutionMatches("(2 / 1)", int2 / 1) - ExpectExecutionMatches("(2 / 1)", 2 / int1) - ExpectExecutionMatches("(1 / 2)", 1 / int2) + AssertSQLContains("(1 / 1)", int1 / int1) + AssertSQLContains("(1 / 2)", int1 / int2) + AssertSQLContains("(2 / 1)", int2 / int1) + AssertSQLContains("(2 / 2)", int2 / int2) + AssertSQLContains("(1 / 2)", int1 / 2) + AssertSQLContains("(2 / 1)", int2 / 1) + AssertSQLContains("(2 / 1)", 2 / int1) + AssertSQLContains("(1 / 2)", 1 / int2) } func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() { - ExpectExecutionMatches("(1.5 / 1.5)", double1 / double1) - ExpectExecutionMatches("(1.5 / 2.5)", double1 / double2) - ExpectExecutionMatches("(2.5 / 1.5)", double2 / double1) - ExpectExecutionMatches("(2.5 / 2.5)", double2 / double2) - ExpectExecutionMatches("(1.5 / 2.5)", double1 / 2.5) - ExpectExecutionMatches("(2.5 / 1.5)", double2 / 1.5) - ExpectExecutionMatches("(2.5 / 1.5)", 2.5 / double1) - ExpectExecutionMatches("(1.5 / 2.5)", 1.5 / double2) + AssertSQLContains("(1.5 / 1.5)", double1 / double1) + AssertSQLContains("(1.5 / 2.5)", double1 / double2) + AssertSQLContains("(2.5 / 1.5)", double2 / double1) + AssertSQLContains("(2.5 / 2.5)", double2 / double2) + AssertSQLContains("(1.5 / 2.5)", double1 / 2.5) + AssertSQLContains("(2.5 / 1.5)", double2 / 1.5) + AssertSQLContains("(2.5 / 1.5)", 2.5 / double1) + AssertSQLContains("(1.5 / 2.5)", 1.5 / double2) } func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { - ExpectExecutionMatches("(1 % 1)", int1 % int1) - ExpectExecutionMatches("(1 % 2)", int1 % int2) - ExpectExecutionMatches("(2 % 1)", int2 % int1) - ExpectExecutionMatches("(2 % 2)", int2 % int2) - ExpectExecutionMatches("(1 % 2)", int1 % 2) - ExpectExecutionMatches("(2 % 1)", int2 % 1) - ExpectExecutionMatches("(2 % 1)", 2 % int1) - ExpectExecutionMatches("(1 % 2)", 1 % int2) + AssertSQLContains("(1 % 1)", int1 % int1) + AssertSQLContains("(1 % 2)", int1 % int2) + AssertSQLContains("(2 % 1)", int2 % int1) + AssertSQLContains("(2 % 2)", int2 % int2) + AssertSQLContains("(1 % 2)", int1 % 2) + AssertSQLContains("(2 % 1)", int2 % 1) + AssertSQLContains("(2 % 1)", 2 % int1) + AssertSQLContains("(1 % 2)", 1 % int2) } func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { - ExpectExecutionMatches("(1 << 1)", int1 << int1) - ExpectExecutionMatches("(1 << 2)", int1 << int2) - ExpectExecutionMatches("(2 << 1)", int2 << int1) - ExpectExecutionMatches("(2 << 2)", int2 << int2) - ExpectExecutionMatches("(1 << 2)", int1 << 2) - ExpectExecutionMatches("(2 << 1)", int2 << 1) - ExpectExecutionMatches("(2 << 1)", 2 << int1) - ExpectExecutionMatches("(1 << 2)", 1 << int2) + AssertSQLContains("(1 << 1)", int1 << int1) + AssertSQLContains("(1 << 2)", int1 << int2) + AssertSQLContains("(2 << 1)", int2 << int1) + AssertSQLContains("(2 << 2)", int2 << int2) + AssertSQLContains("(1 << 2)", int1 << 2) + AssertSQLContains("(2 << 1)", int2 << 1) + AssertSQLContains("(2 << 1)", 2 << int1) + AssertSQLContains("(1 << 2)", 1 << int2) } func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { - ExpectExecutionMatches("(1 >> 1)", int1 >> int1) - ExpectExecutionMatches("(1 >> 2)", int1 >> int2) - ExpectExecutionMatches("(2 >> 1)", int2 >> int1) - ExpectExecutionMatches("(2 >> 2)", int2 >> int2) - ExpectExecutionMatches("(1 >> 2)", int1 >> 2) - ExpectExecutionMatches("(2 >> 1)", int2 >> 1) - ExpectExecutionMatches("(2 >> 1)", 2 >> int1) - ExpectExecutionMatches("(1 >> 2)", 1 >> int2) + AssertSQLContains("(1 >> 1)", int1 >> int1) + AssertSQLContains("(1 >> 2)", int1 >> int2) + AssertSQLContains("(2 >> 1)", int2 >> int1) + AssertSQLContains("(2 >> 2)", int2 >> int2) + AssertSQLContains("(1 >> 2)", int1 >> 2) + AssertSQLContains("(2 >> 1)", int2 >> 1) + AssertSQLContains("(2 >> 1)", 2 >> int1) + AssertSQLContains("(1 >> 2)", 1 >> int2) } func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { - ExpectExecutionMatches("(1 & 1)", int1 & int1) - ExpectExecutionMatches("(1 & 2)", int1 & int2) - ExpectExecutionMatches("(2 & 1)", int2 & int1) - ExpectExecutionMatches("(2 & 2)", int2 & int2) - ExpectExecutionMatches("(1 & 2)", int1 & 2) - ExpectExecutionMatches("(2 & 1)", int2 & 1) - ExpectExecutionMatches("(2 & 1)", 2 & int1) - ExpectExecutionMatches("(1 & 2)", 1 & int2) + AssertSQLContains("(1 & 1)", int1 & int1) + AssertSQLContains("(1 & 2)", int1 & int2) + AssertSQLContains("(2 & 1)", int2 & int1) + AssertSQLContains("(2 & 2)", int2 & int2) + AssertSQLContains("(1 & 2)", int1 & 2) + AssertSQLContains("(2 & 1)", int2 & 1) + AssertSQLContains("(2 & 1)", 2 & int1) + AssertSQLContains("(1 & 2)", 1 & int2) } func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { - ExpectExecutionMatches("(1 | 1)", int1 | int1) - ExpectExecutionMatches("(1 | 2)", int1 | int2) - ExpectExecutionMatches("(2 | 1)", int2 | int1) - ExpectExecutionMatches("(2 | 2)", int2 | int2) - ExpectExecutionMatches("(1 | 2)", int1 | 2) - ExpectExecutionMatches("(2 | 1)", int2 | 1) - ExpectExecutionMatches("(2 | 1)", 2 | int1) - ExpectExecutionMatches("(1 | 2)", 1 | int2) + AssertSQLContains("(1 | 1)", int1 | int1) + AssertSQLContains("(1 | 2)", int1 | int2) + AssertSQLContains("(2 | 1)", int2 | int1) + AssertSQLContains("(2 | 2)", int2 | int2) + AssertSQLContains("(1 | 2)", int1 | 2) + AssertSQLContains("(2 | 1)", int2 | 1) + AssertSQLContains("(2 | 1)", 2 | int1) + AssertSQLContains("(1 | 2)", 1 | int2) } func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { - ExpectExecutionMatches("(~((1 & 1)) & (1 | 1))", int1 ^ int1) - ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", int1 ^ int2) - ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", int2 ^ int1) - ExpectExecutionMatches("(~((2 & 2)) & (2 | 2))", int2 ^ int2) - ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", int1 ^ 2) - ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", int2 ^ 1) - ExpectExecutionMatches("(~((2 & 1)) & (2 | 1))", 2 ^ int1) - ExpectExecutionMatches("(~((1 & 2)) & (1 | 2))", 1 ^ int2) + AssertSQLContains("(~((1 & 1)) & (1 | 1))", int1 ^ int1) + AssertSQLContains("(~((1 & 2)) & (1 | 2))", int1 ^ int2) + AssertSQLContains("(~((2 & 1)) & (2 | 1))", int2 ^ int1) + AssertSQLContains("(~((2 & 2)) & (2 | 2))", int2 ^ int2) + AssertSQLContains("(~((1 & 2)) & (1 | 2))", int1 ^ 2) + AssertSQLContains("(~((2 & 1)) & (2 | 1))", int2 ^ 1) + AssertSQLContains("(~((2 & 1)) & (2 | 1))", 2 ^ int1) + AssertSQLContains("(~((1 & 2)) & (1 | 2))", 1 ^ int2) } func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { - ExpectExecutionMatches("~(1)", ~int1) - ExpectExecutionMatches("~(2)", ~int2) + AssertSQLContains("~(1)", ~int1) + AssertSQLContains("~(2)", ~int2) } func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { - ExpectExecutionMatches("(0 = 0)", bool0 == bool0) - ExpectExecutionMatches("(0 = 1)", bool0 == bool1) - ExpectExecutionMatches("(1 = 0)", bool1 == bool0) - ExpectExecutionMatches("(1 = 1)", bool1 == bool1) - ExpectExecutionMatches("(0 = 1)", bool0 == true) - ExpectExecutionMatches("(1 = 0)", bool1 == false) - ExpectExecutionMatches("(1 = 0)", true == bool0) - ExpectExecutionMatches("(0 = 1)", false == bool1) + AssertSQLContains("(0 = 0)", bool0 == bool0) + AssertSQLContains("(0 = 1)", bool0 == bool1) + AssertSQLContains("(1 = 0)", bool1 == bool0) + AssertSQLContains("(1 = 1)", bool1 == bool1) + AssertSQLContains("(0 = 1)", bool0 == true) + AssertSQLContains("(1 = 0)", bool1 == false) + AssertSQLContains("(1 = 0)", true == bool0) + AssertSQLContains("(0 = 1)", false == bool1) } func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { - ExpectExecutionMatches("(0 != 0)", bool0 != bool0) - ExpectExecutionMatches("(0 != 1)", bool0 != bool1) - ExpectExecutionMatches("(1 != 0)", bool1 != bool0) - ExpectExecutionMatches("(1 != 1)", bool1 != bool1) - ExpectExecutionMatches("(0 != 1)", bool0 != true) - ExpectExecutionMatches("(1 != 0)", bool1 != false) - ExpectExecutionMatches("(1 != 0)", true != bool0) - ExpectExecutionMatches("(0 != 1)", false != bool1) + AssertSQLContains("(0 != 0)", bool0 != bool0) + AssertSQLContains("(0 != 1)", bool0 != bool1) + AssertSQLContains("(1 != 0)", bool1 != bool0) + AssertSQLContains("(1 != 1)", bool1 != bool1) + AssertSQLContains("(0 != 1)", bool0 != true) + AssertSQLContains("(1 != 0)", bool1 != false) + AssertSQLContains("(1 != 0)", true != bool0) + AssertSQLContains("(0 != 1)", false != bool1) } func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { - ExpectExecutionMatches("(1 > 1)", int1 > int1) - ExpectExecutionMatches("(1 > 2)", int1 > int2) - ExpectExecutionMatches("(2 > 1)", int2 > int1) - ExpectExecutionMatches("(2 > 2)", int2 > int2) - ExpectExecutionMatches("(1 > 2)", int1 > 2) - ExpectExecutionMatches("(2 > 1)", int2 > 1) - ExpectExecutionMatches("(2 > 1)", 2 > int1) - ExpectExecutionMatches("(1 > 2)", 1 > int2) + AssertSQLContains("(1 > 1)", int1 > int1) + AssertSQLContains("(1 > 2)", int1 > int2) + AssertSQLContains("(2 > 1)", int2 > int1) + AssertSQLContains("(2 > 2)", int2 > int2) + AssertSQLContains("(1 > 2)", int1 > 2) + AssertSQLContains("(2 > 1)", int2 > 1) + AssertSQLContains("(2 > 1)", 2 > int1) + AssertSQLContains("(1 > 2)", 1 > int2) } func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - ExpectExecutionMatches("(1 >= 1)", int1 >= int1) - ExpectExecutionMatches("(1 >= 2)", int1 >= int2) - ExpectExecutionMatches("(2 >= 1)", int2 >= int1) - ExpectExecutionMatches("(2 >= 2)", int2 >= int2) - ExpectExecutionMatches("(1 >= 2)", int1 >= 2) - ExpectExecutionMatches("(2 >= 1)", int2 >= 1) - ExpectExecutionMatches("(2 >= 1)", 2 >= int1) - ExpectExecutionMatches("(1 >= 2)", 1 >= int2) + AssertSQLContains("(1 >= 1)", int1 >= int1) + AssertSQLContains("(1 >= 2)", int1 >= int2) + AssertSQLContains("(2 >= 1)", int2 >= int1) + AssertSQLContains("(2 >= 2)", int2 >= int2) + AssertSQLContains("(1 >= 2)", int1 >= 2) + AssertSQLContains("(2 >= 1)", int2 >= 1) + AssertSQLContains("(2 >= 1)", 2 >= int1) + AssertSQLContains("(1 >= 2)", 1 >= int2) } func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { - ExpectExecutionMatches("(1 < 1)", int1 < int1) - ExpectExecutionMatches("(1 < 2)", int1 < int2) - ExpectExecutionMatches("(2 < 1)", int2 < int1) - ExpectExecutionMatches("(2 < 2)", int2 < int2) - ExpectExecutionMatches("(1 < 2)", int1 < 2) - ExpectExecutionMatches("(2 < 1)", int2 < 1) - ExpectExecutionMatches("(2 < 1)", 2 < int1) - ExpectExecutionMatches("(1 < 2)", 1 < int2) + AssertSQLContains("(1 < 1)", int1 < int1) + AssertSQLContains("(1 < 2)", int1 < int2) + AssertSQLContains("(2 < 1)", int2 < int1) + AssertSQLContains("(2 < 2)", int2 < int2) + AssertSQLContains("(1 < 2)", int1 < 2) + AssertSQLContains("(2 < 1)", int2 < 1) + AssertSQLContains("(2 < 1)", 2 < int1) + AssertSQLContains("(1 < 2)", 1 < int2) } func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - ExpectExecutionMatches("(1 <= 1)", int1 <= int1) - ExpectExecutionMatches("(1 <= 2)", int1 <= int2) - ExpectExecutionMatches("(2 <= 1)", int2 <= int1) - ExpectExecutionMatches("(2 <= 2)", int2 <= int2) - ExpectExecutionMatches("(1 <= 2)", int1 <= 2) - ExpectExecutionMatches("(2 <= 1)", int2 <= 1) - ExpectExecutionMatches("(2 <= 1)", 2 <= int1) - ExpectExecutionMatches("(1 <= 2)", 1 <= int2) + AssertSQLContains("(1 <= 1)", int1 <= int1) + AssertSQLContains("(1 <= 2)", int1 <= int2) + AssertSQLContains("(2 <= 1)", int2 <= int1) + AssertSQLContains("(2 <= 2)", int2 <= int2) + AssertSQLContains("(1 <= 2)", int1 <= 2) + AssertSQLContains("(2 <= 1)", int2 <= 1) + AssertSQLContains("(2 <= 1)", 2 <= int1) + AssertSQLContains("(1 <= 2)", 1 <= int2) } func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() { - ExpectExecutionMatches("-(1)", -int1) - ExpectExecutionMatches("-(2)", -int2) + AssertSQLContains("-(1)", -int1) + AssertSQLContains("-(2)", -int2) } func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() { - ExpectExecutionMatches("-(1.5)", -double1) - ExpectExecutionMatches("-(2.5)", -double2) + AssertSQLContains("-(1.5)", -double1) + AssertSQLContains("-(2.5)", -double2) } func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() { - ExpectExecutionMatches("1 BETWEEN 0 AND 5", 0...5 ~= int1) - ExpectExecutionMatches("2 BETWEEN 0 AND 5", 0...5 ~= int2) + AssertSQLContains("1 BETWEEN 0 AND 5", 0...5 ~= int1) + AssertSQLContains("2 BETWEEN 0 AND 5", 0...5 ~= int2) } func test_likeOperator_withStringExpression_buildsLikeExpression() { - ExpectExecutionMatches("('A' LIKE 'B%')", like("B%", stringA)) - ExpectExecutionMatches("('B' LIKE 'A%')", like("A%", stringB)) + AssertSQLContains("('A' LIKE 'B%')", like("B%", stringA)) + AssertSQLContains("('B' LIKE 'A%')", like("A%", stringB)) } func test_globOperator_withStringExpression_buildsGlobExpression() { - ExpectExecutionMatches("('A' GLOB 'B*')", glob("B*", stringA)) - ExpectExecutionMatches("('B' GLOB 'A*')", glob("A*", stringB)) + AssertSQLContains("('A' GLOB 'B*')", glob("B*", stringA)) + AssertSQLContains("('B' GLOB 'A*')", glob("A*", stringB)) } func test_matchOperator_withStringExpression_buildsMatchExpression() { - ExpectExecutionMatches("('A' MATCH 'B')", match("B", stringA)) - ExpectExecutionMatches("('B' MATCH 'A')", match("A", stringB)) + AssertSQLContains("('A' MATCH 'B')", match("B", stringA)) + AssertSQLContains("('B' MATCH 'A')", match("A", stringB)) } func test_collateOperator_withStringExpression_buildsCollationExpression() { - ExpectExecutionMatches("('A' COLLATE \"BINARY\")", collate(.Binary, stringA)) - ExpectExecutionMatches("('B' COLLATE \"NOCASE\")", collate(.Nocase, stringB)) - ExpectExecutionMatches("('A' COLLATE \"RTRIM\")", collate(.Rtrim, stringA)) + AssertSQLContains("('A' COLLATE \"BINARY\")", collate(.Binary, stringA)) + AssertSQLContains("('B' COLLATE \"NOCASE\")", collate(.Nocase, stringB)) + AssertSQLContains("('A' COLLATE \"RTRIM\")", collate(.Rtrim, stringA)) db.create(collation: "NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - ExpectExecutionMatches("('A' COLLATE \"NODIACRITIC\")", collate(.Custom("NODIACRITIC"), stringA)) -} + AssertSQLContains("('A' COLLATE \"NODIACRITIC\")", collate(.Custom("NODIACRITIC"), stringA)) + } func test_cast_buildsCastingExpressions() { let string1 = Expression(value: "10") let string2 = Expression(value: "10") - ExpectExecutionMatches("CAST ('10' AS REAL)", cast(string1) as Expression) - ExpectExecutionMatches("CAST ('10' AS INTEGER)", cast(string1) as Expression) - ExpectExecutionMatches("CAST ('10' AS TEXT)", cast(string1) as Expression) - ExpectExecutionMatches("CAST ('10' AS REAL)", cast(string2) as Expression) - ExpectExecutionMatches("CAST ('10' AS INTEGER)", cast(string2) as Expression) - ExpectExecutionMatches("CAST ('10' AS TEXT)", cast(string2) as Expression) + AssertSQLContains("CAST ('10' AS REAL)", cast(string1) as Expression) + AssertSQLContains("CAST ('10' AS INTEGER)", cast(string1) as Expression) + AssertSQLContains("CAST ('10' AS TEXT)", cast(string1) as Expression) + AssertSQLContains("CAST ('10' AS REAL)", cast(string2) as Expression) + AssertSQLContains("CAST ('10' AS INTEGER)", cast(string2) as Expression) + AssertSQLContains("CAST ('10' AS TEXT)", cast(string2) as Expression) } func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { - ExpectExecutionMatches("(0 AND 0)", bool0 && bool0) - ExpectExecutionMatches("(0 AND 1)", bool0 && bool1) - ExpectExecutionMatches("(1 AND 0)", bool1 && bool0) - ExpectExecutionMatches("(1 AND 1)", bool1 && bool1) - ExpectExecutionMatches("(0 AND 1)", bool0 && true) - ExpectExecutionMatches("(1 AND 0)", bool1 && false) - ExpectExecutionMatches("(1 AND 0)", true && bool0) - ExpectExecutionMatches("(0 AND 1)", false && bool1) + AssertSQLContains("(0 AND 0)", bool0 && bool0) + AssertSQLContains("(0 AND 1)", bool0 && bool1) + AssertSQLContains("(1 AND 0)", bool1 && bool0) + AssertSQLContains("(1 AND 1)", bool1 && bool1) + AssertSQLContains("(0 AND 1)", bool0 && true) + AssertSQLContains("(1 AND 0)", bool1 && false) + AssertSQLContains("(1 AND 0)", true && bool0) + AssertSQLContains("(0 AND 1)", false && bool1) } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { - ExpectExecutionMatches("(0 OR 0)", bool0 || bool0) - ExpectExecutionMatches("(0 OR 1)", bool0 || bool1) - ExpectExecutionMatches("(1 OR 0)", bool1 || bool0) - ExpectExecutionMatches("(1 OR 1)", bool1 || bool1) - ExpectExecutionMatches("(0 OR 1)", bool0 || true) - ExpectExecutionMatches("(1 OR 0)", bool1 || false) - ExpectExecutionMatches("(1 OR 0)", true || bool0) - ExpectExecutionMatches("(0 OR 1)", false || bool1) + AssertSQLContains("(0 OR 0)", bool0 || bool0) + AssertSQLContains("(0 OR 1)", bool0 || bool1) + AssertSQLContains("(1 OR 0)", bool1 || bool0) + AssertSQLContains("(1 OR 1)", bool1 || bool1) + AssertSQLContains("(0 OR 1)", bool0 || true) + AssertSQLContains("(1 OR 0)", bool1 || false) + AssertSQLContains("(1 OR 0)", true || bool0) + AssertSQLContains("(0 OR 1)", false || bool1) } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { - ExpectExecutionMatches("NOT (0)", !bool0) - ExpectExecutionMatches("NOT (1)", !bool1) + AssertSQLContains("NOT (0)", !bool0) + AssertSQLContains("NOT (1)", !bool1) } func test_absFunction_withNumberExpressions_buildsAbsExpression() { let int1 = Expression(value: -1) let int2 = Expression(value: -2) - ExpectExecutionMatches("abs(-1)", abs(int1)) - ExpectExecutionMatches("abs(-2)", abs(int2)) + AssertSQLContains("abs(-1)", abs(int1)) + AssertSQLContains("abs(-2)", abs(int2)) } func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { @@ -361,7 +358,7 @@ class ExpressionTests: XCTestCase { let int2 = Expression(value: nil as Int?) let int3 = Expression(value: 3) - ExpectExecutionMatches("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3)) + AssertSQLContains("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3)) } func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { @@ -369,353 +366,387 @@ class ExpressionTests: XCTestCase { let int2 = Expression(value: 2) let int3 = Expression(value: 3) - ExpectExecutionMatches("ifnull(NULL, 1)", ifnull(int1, 1)) - ExpectExecutionMatches("ifnull(NULL, 1)", int1 ?? 1) - ExpectExecutionMatches("ifnull(NULL, 2)", ifnull(int1, int2)) - ExpectExecutionMatches("ifnull(NULL, 2)", int1 ?? int2) - ExpectExecutionMatches("ifnull(NULL, 3)", ifnull(int1, int3)) - ExpectExecutionMatches("ifnull(NULL, 3)", int1 ?? int3) + AssertSQLContains("ifnull(NULL, 1)", ifnull(int1, 1)) + AssertSQLContains("ifnull(NULL, 1)", int1 ?? 1) + AssertSQLContains("ifnull(NULL, 2)", ifnull(int1, int2)) + AssertSQLContains("ifnull(NULL, 2)", int1 ?? int2) + AssertSQLContains("ifnull(NULL, 3)", ifnull(int1, int3)) + AssertSQLContains("ifnull(NULL, 3)", int1 ?? int3) } func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { - ExpectExecutionMatches("length('A')", length(stringA)) - ExpectExecutionMatches("length('B')", length(stringB)) + AssertSQLContains("length('A')", length(stringA)) + AssertSQLContains("length('B')", length(stringB)) } func test_lowerFunction_withStringExpression_buildsLowerStringExpression() { - ExpectExecutionMatches("lower('A')", lower(stringA)) - ExpectExecutionMatches("lower('B')", lower(stringB)) + AssertSQLContains("lower('A')", lower(stringA)) + AssertSQLContains("lower('B')", lower(stringB)) } func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() { - ExpectExecutionMatches("ltrim('A')", ltrim(stringA)) - ExpectExecutionMatches("ltrim('B')", ltrim(stringB)) + AssertSQLContains("ltrim('A')", ltrim(stringA)) + AssertSQLContains("ltrim('B')", ltrim(stringB)) } func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - ExpectExecutionMatches("ltrim('A', 'A?')", ltrim(stringA, "A?")) - ExpectExecutionMatches("ltrim('B', 'B?')", ltrim(stringB, "B?")) + AssertSQLContains("ltrim('A', 'A?')", ltrim(stringA, "A?")) + AssertSQLContains("ltrim('B', 'B?')", ltrim(stringB, "B?")) } func test_randomFunction_buildsRandomIntExpression() { - ExpectExecutionMatches("random()", random) + AssertSQLContains("random()", random()) } func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() { - ExpectExecutionMatches("replace('A', 'A', 'B')", replace(stringA, "A", "B")) - ExpectExecutionMatches("replace('B', 'B', 'A')", replace(stringB, "B", "A")) + AssertSQLContains("replace('A', 'A', 'B')", replace(stringA, "A", "B")) + AssertSQLContains("replace('B', 'B', 'A')", replace(stringB, "B", "A")) } func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() { - ExpectExecutionMatches("round(1.5)", round(double1)) - ExpectExecutionMatches("round(2.5)", round(double2)) + AssertSQLContains("round(1.5)", round(double1)) + AssertSQLContains("round(2.5)", round(double2)) } func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() { - ExpectExecutionMatches("round(1.5, 1)", round(double1, 1)) - ExpectExecutionMatches("round(2.5, 1)", round(double2, 1)) + AssertSQLContains("round(1.5, 1)", round(double1, 1)) + AssertSQLContains("round(2.5, 1)", round(double2, 1)) } func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() { - ExpectExecutionMatches("rtrim('A')", rtrim(stringA)) - ExpectExecutionMatches("rtrim('B')", rtrim(stringB)) + AssertSQLContains("rtrim('A')", rtrim(stringA)) + AssertSQLContains("rtrim('B')", rtrim(stringB)) } func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - ExpectExecutionMatches("rtrim('A', 'A?')", rtrim(stringA, "A?")) - ExpectExecutionMatches("rtrim('B', 'B?')", rtrim(stringB, "B?")) + AssertSQLContains("rtrim('A', 'A?')", rtrim(stringA, "A?")) + AssertSQLContains("rtrim('B', 'B?')", rtrim(stringB, "B?")) } func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() { - ExpectExecutionMatches("substr('A', 1)", substr(stringA, 1)) - ExpectExecutionMatches("substr('B', 1)", substr(stringB, 1)) + AssertSQLContains("substr('A', 1)", substr(stringA, 1)) + AssertSQLContains("substr('B', 1)", substr(stringB, 1)) } func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() { - ExpectExecutionMatches("substr('A', 1, 2)", substr(stringA, 1, 2)) - ExpectExecutionMatches("substr('B', 1, 2)", substr(stringB, 1, 2)) + AssertSQLContains("substr('A', 1, 2)", substr(stringA, 1, 2)) + AssertSQLContains("substr('B', 1, 2)", substr(stringB, 1, 2)) } func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() { - ExpectExecutionMatches("substr('A', 1, 2)", substr(stringA, 1..<3)) - ExpectExecutionMatches("substr('B', 1, 2)", substr(stringB, 1..<3)) + AssertSQLContains("substr('A', 1, 2)", substr(stringA, 1..<3)) + AssertSQLContains("substr('B', 1, 2)", substr(stringB, 1..<3)) } func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() { - ExpectExecutionMatches("trim('A')", trim(stringA)) - ExpectExecutionMatches("trim('B')", trim(stringB)) + AssertSQLContains("trim('A')", trim(stringA)) + AssertSQLContains("trim('B')", trim(stringB)) } func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - ExpectExecutionMatches("trim('A', 'A?')", trim(stringA, "A?")) - ExpectExecutionMatches("trim('B', 'B?')", trim(stringB, "B?")) + AssertSQLContains("trim('A', 'A?')", trim(stringA, "A?")) + AssertSQLContains("trim('B', 'B?')", trim(stringB, "B?")) } func test_upperFunction_withStringExpression_buildsLowerStringExpression() { - ExpectExecutionMatches("upper('A')", upper(stringA)) - ExpectExecutionMatches("upper('B')", upper(stringB)) + AssertSQLContains("upper('A')", upper(stringA)) + AssertSQLContains("upper('B')", upper(stringB)) } - let id = Expression("id") - let age = Expression("age") - let email = Expression("email") let email2 = Expression("email") - let salary = Expression("salary") + let age2 = Expression("age") let salary2 = Expression("salary") - let admin = Expression("admin") let admin2 = Expression("admin") func test_countFunction_withExpression_buildsCountExpression() { - ExpectExecutionMatches("count(\"age\")", count(age)) - ExpectExecutionMatches("count(\"email\")", count(email2)) - ExpectExecutionMatches("count(\"salary\")", count(salary2)) - ExpectExecutionMatches("count(\"admin\")", count(admin2)) - ExpectExecutionMatches("count(DISTINCT \"id\")", count(distinct: id)) - ExpectExecutionMatches("count(DISTINCT \"age\")", count(distinct: age)) - ExpectExecutionMatches("count(DISTINCT \"email\")", count(distinct: email)) - ExpectExecutionMatches("count(DISTINCT \"email\")", count(distinct: email2)) - ExpectExecutionMatches("count(DISTINCT \"salary\")", count(distinct: salary)) - ExpectExecutionMatches("count(DISTINCT \"salary\")", count(distinct: salary2)) - ExpectExecutionMatches("count(DISTINCT \"admin\")", count(distinct: admin)) - ExpectExecutionMatches("count(DISTINCT \"admin\")", count(distinct: admin2)) + AssertSQLContains("count(\"age\")", count(age)) + AssertSQLContains("count(\"email\")", count(email2)) + AssertSQLContains("count(\"salary\")", count(salary2)) + AssertSQLContains("count(\"admin\")", count(admin2)) + AssertSQLContains("count(DISTINCT \"id\")", count(distinct: id)) + AssertSQLContains("count(DISTINCT \"age\")", count(distinct: age)) + AssertSQLContains("count(DISTINCT \"email\")", count(distinct: email)) + AssertSQLContains("count(DISTINCT \"email\")", count(distinct: email2)) + AssertSQLContains("count(DISTINCT \"salary\")", count(distinct: salary)) + AssertSQLContains("count(DISTINCT \"salary\")", count(distinct: salary2)) + AssertSQLContains("count(DISTINCT \"admin\")", count(distinct: admin)) + AssertSQLContains("count(DISTINCT \"admin\")", count(distinct: admin2)) } func test_countFunction_withStar_buildsCountExpression() { - ExpectExecutionMatches("count(*)", count(*)) + AssertSQLContains("count(*)", count(*)) } func test_maxFunction_withExpression_buildsMaxExpression() { - ExpectExecutionMatches("max(\"id\")", max(id)) - ExpectExecutionMatches("max(\"age\")", max(age)) - ExpectExecutionMatches("max(\"email\")", max(email)) - ExpectExecutionMatches("max(\"email\")", max(email2)) - ExpectExecutionMatches("max(\"salary\")", max(salary)) - ExpectExecutionMatches("max(\"salary\")", max(salary2)) + AssertSQLContains("max(\"id\")", max(id)) + AssertSQLContains("max(\"age\")", max(age)) + AssertSQLContains("max(\"email\")", max(email)) + AssertSQLContains("max(\"email\")", max(email2)) + AssertSQLContains("max(\"salary\")", max(salary)) + AssertSQLContains("max(\"salary\")", max(salary2)) } func test_minFunction_withExpression_buildsMinExpression() { - ExpectExecutionMatches("min(\"id\")", min(id)) - ExpectExecutionMatches("min(\"age\")", min(age)) - ExpectExecutionMatches("min(\"email\")", min(email)) - ExpectExecutionMatches("min(\"email\")", min(email2)) - ExpectExecutionMatches("min(\"salary\")", min(salary)) - ExpectExecutionMatches("min(\"salary\")", min(salary2)) + AssertSQLContains("min(\"id\")", min(id)) + AssertSQLContains("min(\"age\")", min(age)) + AssertSQLContains("min(\"email\")", min(email)) + AssertSQLContains("min(\"email\")", min(email2)) + AssertSQLContains("min(\"salary\")", min(salary)) + AssertSQLContains("min(\"salary\")", min(salary2)) } func test_averageFunction_withExpression_buildsAverageExpression() { - ExpectExecutionMatches("avg(\"id\")", average(id)) - ExpectExecutionMatches("avg(\"age\")", average(age)) - ExpectExecutionMatches("avg(\"salary\")", average(salary)) - ExpectExecutionMatches("avg(\"salary\")", average(salary2)) - ExpectExecutionMatches("avg(DISTINCT \"id\")", average(distinct: id)) - ExpectExecutionMatches("avg(DISTINCT \"age\")", average(distinct: age)) - ExpectExecutionMatches("avg(DISTINCT \"salary\")", average(distinct: salary)) - ExpectExecutionMatches("avg(DISTINCT \"salary\")", average(distinct: salary2)) + AssertSQLContains("avg(\"id\")", average(id)) + AssertSQLContains("avg(\"age\")", average(age)) + AssertSQLContains("avg(\"salary\")", average(salary)) + AssertSQLContains("avg(\"salary\")", average(salary2)) + AssertSQLContains("avg(DISTINCT \"id\")", average(distinct: id)) + AssertSQLContains("avg(DISTINCT \"age\")", average(distinct: age)) + AssertSQLContains("avg(DISTINCT \"salary\")", average(distinct: salary)) + AssertSQLContains("avg(DISTINCT \"salary\")", average(distinct: salary2)) } func test_sumFunction_withExpression_buildsSumExpression() { - ExpectExecutionMatches("sum(\"id\")", sum(id)) - ExpectExecutionMatches("sum(\"age\")", sum(age)) - ExpectExecutionMatches("sum(\"salary\")", sum(salary)) - ExpectExecutionMatches("sum(\"salary\")", sum(salary2)) - ExpectExecutionMatches("sum(DISTINCT \"id\")", sum(distinct: id)) - ExpectExecutionMatches("sum(DISTINCT \"age\")", sum(distinct: age)) - ExpectExecutionMatches("sum(DISTINCT \"salary\")", sum(distinct: salary)) - ExpectExecutionMatches("sum(DISTINCT \"salary\")", sum(distinct: salary2)) + AssertSQLContains("sum(\"id\")", sum(id)) + AssertSQLContains("sum(\"age\")", sum(age)) + AssertSQLContains("sum(\"salary\")", sum(salary)) + AssertSQLContains("sum(\"salary\")", sum(salary2)) + AssertSQLContains("sum(DISTINCT \"id\")", sum(distinct: id)) + AssertSQLContains("sum(DISTINCT \"age\")", sum(distinct: age)) + AssertSQLContains("sum(DISTINCT \"salary\")", sum(distinct: salary)) + AssertSQLContains("sum(DISTINCT \"salary\")", sum(distinct: salary2)) } func test_totalFunction_withExpression_buildsTotalExpression() { - ExpectExecutionMatches("total(\"id\")", total(id)) - ExpectExecutionMatches("total(\"age\")", total(age)) - ExpectExecutionMatches("total(\"salary\")", total(salary)) - ExpectExecutionMatches("total(\"salary\")", total(salary2)) - ExpectExecutionMatches("total(DISTINCT \"id\")", total(distinct: id)) - ExpectExecutionMatches("total(DISTINCT \"age\")", total(distinct: age)) - ExpectExecutionMatches("total(DISTINCT \"salary\")", total(distinct: salary)) - ExpectExecutionMatches("total(DISTINCT \"salary\")", total(distinct: salary2)) + AssertSQLContains("total(\"id\")", total(id)) + AssertSQLContains("total(\"age\")", total(age)) + AssertSQLContains("total(\"salary\")", total(salary)) + AssertSQLContains("total(\"salary\")", total(salary2)) + AssertSQLContains("total(DISTINCT \"id\")", total(distinct: id)) + AssertSQLContains("total(DISTINCT \"age\")", total(distinct: age)) + AssertSQLContains("total(DISTINCT \"salary\")", total(distinct: salary)) + AssertSQLContains("total(DISTINCT \"salary\")", total(distinct: salary2)) } func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - ExpectExecutionMatches("(\"id\" IN (1, 2, 3))", contains([1, 2, 3], id)) - ExpectExecutionMatches("(\"age\" IN (20, 30, 40))", contains([20, 30, 40], age)) + AssertSQLContains("(\"id\" IN (1, 2, 3))", contains([1, 2, 3], id)) + AssertSQLContains("(\"age\" IN (20, 30, 40))", contains([20, 30, 40], age)) + + AssertSQLContains("(\"id\" IN (1))", contains(Set([1]), id)) + AssertSQLContains("(\"age\" IN (20))", contains(Set([20]), age)) } func test_plusEquals_withStringExpression_buildsSetter() { - let SQL = "UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")" - ExpectExecution(db, SQL, users.update(email += email)) - ExpectExecution(db, SQL, users.update(email += email2)) - ExpectExecution(db, SQL, users.update(email2 += email)) - ExpectExecution(db, SQL, users.update(email2 += email2)) + users.update(email += email)! + users.update(email += email2)! + users.update(email2 += email)! + users.update(email2 += email2)! + AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")", 4) } func test_plusEquals_withStringValue_buildsSetter() { - let SQL = "UPDATE \"users\" SET \"email\" = (\"email\" || '.com')" - ExpectExecution(db, SQL, users.update(email += ".com")) - ExpectExecution(db, SQL, users.update(email2 += ".com")) + users.update(email += ".com")! + users.update(email2 += ".com")! + + AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || '.com')", 2) } func test_plusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", users.update(age += age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + \"id\")", users.update(age += id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + \"age\")", users.update(id += age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + \"id\")", users.update(id += id)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary += salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary += salary2)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary2 += salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", users.update(salary2 += salary2)) + users.update(age += age)! + users.update(age += age2)! + users.update(age2 += age)! + users.update(age2 += age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", 4) + + users.update(salary += salary)! + users.update(salary += salary2)! + users.update(salary2 += salary)! + users.update(salary2 += salary2)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", 4) } func test_plusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id += 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age += 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", users.update(salary += 100)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", users.update(salary2 += 100)) + users.update(age += 1)! + users.update(age2 += 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) + + users.update(salary += 100)! + users.update(salary2 += 100)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", 2) } func test_minusEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", users.update(age -= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - \"id\")", users.update(age -= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - \"age\")", users.update(id -= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - \"id\")", users.update(id -= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary -= salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary -= salary2)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary2 -= salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", users.update(salary2 -= salary2)) + users.update(age -= age)! + users.update(age -= age2)! + users.update(age2 -= age)! + users.update(age2 -= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", 4) + + users.update(salary -= salary)! + users.update(salary -= salary2)! + users.update(salary2 -= salary)! + users.update(salary2 -= salary2)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", 4) } func test_minusEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id -= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age -= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", users.update(salary -= 100)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", users.update(salary2 -= 100)) + users.update(age -= 1)! + users.update(age2 -= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) + + users.update(salary -= 100)! + users.update(salary2 -= 100)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", 2) } func test_timesEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", users.update(age *= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * \"id\")", users.update(age *= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * \"age\")", users.update(id *= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * \"id\")", users.update(id *= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary *= salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary *= salary2)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary2 *= salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", users.update(salary2 *= salary2)) + users.update(age *= age)! + users.update(age *= age2)! + users.update(age2 *= age)! + users.update(age2 *= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", 4) + + users.update(salary *= salary)! + users.update(salary *= salary2)! + users.update(salary2 *= salary)! + users.update(salary2 *= salary2)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", 4) } func test_timesEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" * 1)", users.update(id *= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" * 1)", users.update(age *= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", users.update(salary *= 100)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", users.update(salary2 *= 100)) + users.update(age *= 1)! + users.update(age2 *= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * 1)", 2) + + users.update(salary *= 100)! + users.update(salary2 *= 100)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", 2) } func test_divideEquals_withNumberExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", users.update(age /= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / \"id\")", users.update(age /= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / \"age\")", users.update(id /= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / \"id\")", users.update(id /= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary /= salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary /= salary2)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary2 /= salary)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", users.update(salary2 /= salary2)) + users.update(age /= age)! + users.update(age /= age2)! + users.update(age2 /= age)! + users.update(age2 /= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", 4) + + users.update(salary /= salary)! + users.update(salary /= salary2)! + users.update(salary2 /= salary)! + users.update(salary2 /= salary2)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", 4) } func test_divideEquals_withNumberValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" / 1)", users.update(id /= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" / 1)", users.update(age /= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", users.update(salary /= 100)) - ExpectExecution(db, "UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", users.update(salary2 /= 100)) + users.update(age /= 1)! + users.update(age2 /= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / 1)", 2) + + users.update(salary /= 100)! + users.update(salary2 /= 100)! + AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", 2) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", users.update(age %= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % \"id\")", users.update(age %= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % \"age\")", users.update(id %= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % \"id\")", users.update(id %= id)) + users.update(age %= age)! + users.update(age %= age2)! + users.update(age2 %= age)! + users.update(age2 %= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", 4) } func test_moduloEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" % 10)", users.update(age %= 10)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" % 10)", users.update(id %= 10)) + users.update(age %= 10)! + users.update(age2 %= 10)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % 10)", 2) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", users.update(age >>= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> \"id\")", users.update(age >>= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> \"age\")", users.update(id >>= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> \"id\")", users.update(id >>= id)) + users.update(age >>= age)! + users.update(age >>= age2)! + users.update(age2 >>= age)! + users.update(age2 >>= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", 4) } func test_rightShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", users.update(age >>= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" >> 1)", users.update(id >>= 1)) + users.update(age >>= 1)! + users.update(age2 >>= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", 2) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", users.update(age <<= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << \"id\")", users.update(age <<= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << \"age\")", users.update(id <<= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << \"id\")", users.update(id <<= id)) + users.update(age <<= age)! + users.update(age <<= age2)! + users.update(age2 <<= age)! + users.update(age2 <<= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", 4) } func test_leftShiftEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" << 1)", users.update(age <<= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" << 1)", users.update(id <<= 1)) + users.update(age <<= 1)! + users.update(age2 <<= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << 1)", 2) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", users.update(age &= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & \"id\")", users.update(age &= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & \"age\")", users.update(id &= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & \"id\")", users.update(id &= id)) + users.update(age &= age)! + users.update(age &= age2)! + users.update(age2 &= age)! + users.update(age2 &= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", 4) } func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" & 1)", users.update(age &= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" & 1)", users.update(id &= 1)) + users.update(age &= 1)! + users.update(age2 &= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & 1)", 2) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", users.update(age |= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | \"id\")", users.update(age |= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | \"age\")", users.update(id |= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | \"id\")", users.update(id |= id)) + users.update(age |= age)! + users.update(age |= age2)! + users.update(age2 |= age)! + users.update(age2 |= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", 4) } func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" | 1)", users.update(age |= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" | 1)", users.update(id |= 1)) + users.update(age |= 1)! + users.update(age2 |= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | 1)", 2) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", users.update(age ^= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & \"id\")) & (\"age\" | \"id\"))", users.update(age ^= id)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & \"age\")) & (\"id\" | \"age\"))", users.update(id ^= age)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & \"id\")) & (\"id\" | \"id\"))", users.update(id ^= id)) + users.update(age ^= age)! + users.update(age ^= age2)! + users.update(age2 ^= age)! + users.update(age2 ^= age2)! + AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", 4) } func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", users.update(age ^= 1)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (~((\"id\" & 1)) & (\"id\" | 1))", users.update(id ^= 1)) + users.update(age ^= 1)! + users.update(age2 ^= 1)! + AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", 2) } func test_postfixPlus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age++)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" + 1)", users.update(age++)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id++)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" + 1)", users.update(id++)) + users.update(age++)! + users.update(age2++)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) } func test_postfixMinus_withIntegerValue_buildsSetter() { - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age--)) - ExpectExecution(db, "UPDATE \"users\" SET \"age\" = (\"age\" - 1)", users.update(age--)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id--)) - ExpectExecution(db, "UPDATE \"users\" SET \"id\" = (\"id\" - 1)", users.update(id--)) + users.update(age--)! + users.update(age2--)! + AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) } func test_precedencePreserved() { let n = Expression(value: 1) - ExpectExecutionMatches("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) - ExpectExecutionMatches("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) + AssertSQLContains("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) + AssertSQLContains("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } } diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift index 2f728984..972a2660 100644 --- a/SQLite Tests/FTSTests.swift +++ b/SQLite Tests/FTSTests.swift @@ -4,29 +4,32 @@ import SQLite let subject = Expression("subject") let body = Expression("body") -class FTSTests: XCTestCase { +class FTSTests: SQLiteTestCase { - let db = Database() var emails: Query { return db["emails"] } func test_createVtable_usingFts4_createsVirtualTable() { - ExpectExecution(db, "CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\")", db.create(vtable: emails, using: fts4(subject, body))) + db.create(vtable: emails, using: fts4(subject, body)) + + AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\")") } func test_createVtable_usingFts4_withPorterTokenizer_createsVirtualTableWithTokenizer() { - ExpectExecution(db, "CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=porter)", db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter))) + db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) + + AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=porter)") } func test_match_withColumnExpression_buildsMatchExpressionWithColumnIdentifier() { db.create(vtable: emails, using: fts4(subject, body)) - ExpectExecution(db, "SELECT * FROM \"emails\" WHERE (\"subject\" MATCH 'hello')", emails.filter(match("hello", subject))) + AssertSQL("SELECT * FROM \"emails\" WHERE (\"subject\" MATCH 'hello')", emails.filter(match("hello", subject))) } func test_match_withQuery_buildsMatchExpressionWithTableIdentifier() { db.create(vtable: emails, using: fts4(subject, body)) - ExpectExecution(db, "SELECT * FROM \"emails\" WHERE (\"emails\" MATCH 'hello')", emails.filter(match("hello", emails))) + AssertSQL("SELECT * FROM \"emails\" WHERE (\"emails\" MATCH 'hello')", emails.filter(match("hello", emails))) } } diff --git a/SQLite Tests/FunctionsTests.swift b/SQLite Tests/FunctionsTests.swift index d47ca99b..5a2e3386 100644 --- a/SQLite Tests/FunctionsTests.swift +++ b/SQLite Tests/FunctionsTests.swift @@ -1,9 +1,7 @@ import XCTest import SQLite -class FunctionsTests: XCTestCase { - - let db = Database() +class FunctionsTests: SQLiteTestCase { func test_createFunction_withZeroArguments() { let f1: () -> Expression = db.create(function: "f1") { true } @@ -13,12 +11,11 @@ class FunctionsTests: XCTestCase { db.create(table: table) { $0.column(Expression("id"), primaryKey: true) } table.insert()! - ExpectExecutions(db, ["SELECT \"f1\"() FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f1()).first![f1()]) - } - ExpectExecutions(db, ["SELECT \"f2\"() FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertNil(table.select(f2()).first![f2()]) - } + XCTAssert(table.select(f1()).first![f1()]) + AssertSQL("SELECT \"f1\"() FROM \"table\" LIMIT 1") + + XCTAssertNil(table.select(f2()).first![f2()]) + AssertSQL("SELECT \"f2\"() FROM \"table\" LIMIT 1") } func test_createFunction_withOneArgument() { @@ -38,18 +35,17 @@ class FunctionsTests: XCTestCase { let null = Expression(value: nil as String?) - ExpectExecutions(db, ["SELECT \"f1\"(\"s1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f1(s1)).first![f1(s1)]) - } - ExpectExecutions(db, ["SELECT \"f2\"(\"s2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(!table.select(f2(s2)).first![f2(s2)]) - } - ExpectExecutions(db, ["SELECT \"f3\"(\"s1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f3(s1)).first![f3(s1)]!) - } - ExpectExecutions(db, ["SELECT \"f4\"(NULL) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertNil(table.select(f4(null)).first![f4(null)]) - } + XCTAssert(table.select(f1(s1)).first![f1(s1)]) + AssertSQL("SELECT \"f1\"(\"s1\") FROM \"table\" LIMIT 1") + + XCTAssert(!table.select(f2(s2)).first![f2(s2)]) + AssertSQL("SELECT \"f2\"(\"s2\") FROM \"table\" LIMIT 1") + + XCTAssert(table.select(f3(s1)).first![f3(s1)]!) + AssertSQL("SELECT \"f3\"(\"s1\") FROM \"table\" LIMIT 1") + + XCTAssertNil(table.select(f4(null)).first![f4(null)]) + AssertSQL("SELECT \"f4\"(NULL) FROM \"table\" LIMIT 1") } func test_createFunction_withValueArgument() { @@ -66,9 +62,8 @@ class FunctionsTests: XCTestCase { } table.insert(b <- true)! - ExpectExecutions(db, ["SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f1(b)).first![f1(b)]) - } + XCTAssert(table.select(f1(b)).first![f1(b)]) + AssertSQL("SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1") } func test_createFunction_withTwoArguments() { @@ -86,108 +81,84 @@ class FunctionsTests: XCTestCase { let f3: (Bool, Expression) -> Expression = db.create(function: "f3") { $0 && $1 != nil } let f4: (Bool?, Expression) -> Expression = db.create(function: "f4") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT \"f1\"(1, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f1(true, b1)).first![f1(true, b1)]) - } - ExpectExecutions(db, ["SELECT \"f2\"(NULL, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f2(nil, b1)).first![f2(nil, b1)]) - } - ExpectExecutions(db, ["SELECT \"f3\"(0, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f3(false, b2)).first![f3(false, b2)]) - } - ExpectExecutions(db, ["SELECT \"f4\"(NULL, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f4(nil, b2)).first![f4(nil, b2)]) - } + XCTAssert(table.select(f1(true, b1)).first![f1(true, b1)]) + AssertSQL("SELECT \"f1\"(1, \"b1\") FROM \"table\" LIMIT 1") + XCTAssert(table.select(f2(nil, b1)).first![f2(nil, b1)]) + AssertSQL("SELECT \"f2\"(NULL, \"b1\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f3(false, b2)).first![f3(false, b2)]) + AssertSQL("SELECT \"f3\"(0, \"b2\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f4(nil, b2)).first![f4(nil, b2)]) + AssertSQL("SELECT \"f4\"(NULL, \"b2\") FROM \"table\" LIMIT 1") let f5: (Bool, Expression) -> Expression = db.create(function: "f5") { $0 && $1 } let f6: (Bool?, Expression) -> Expression = db.create(function: "f6") { $0 ?? $1 } let f7: (Bool, Expression) -> Expression = db.create(function: "f7") { $0 && $1 != nil } let f8: (Bool?, Expression) -> Expression = db.create(function: "f8") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT \"f5\"(1, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f5(true, b1)).first![f5(true, b1)]!) - } - ExpectExecutions(db, ["SELECT \"f6\"(NULL, \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f6(nil, b1)).first![f6(nil, b1)]!) - } - ExpectExecutions(db, ["SELECT \"f7\"(0, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f7(false, b2)).first![f7(false, b2)]!) - } - ExpectExecutions(db, ["SELECT \"f8\"(NULL, \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f8(nil, b2)).first![f8(nil, b2)]!) - } + XCTAssert(table.select(f5(true, b1)).first![f5(true, b1)]!) + AssertSQL("SELECT \"f5\"(1, \"b1\") FROM \"table\" LIMIT 1") + XCTAssert(table.select(f6(nil, b1)).first![f6(nil, b1)]!) + AssertSQL("SELECT \"f6\"(NULL, \"b1\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f7(false, b2)).first![f7(false, b2)]!) + AssertSQL("SELECT \"f7\"(0, \"b2\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f8(nil, b2)).first![f8(nil, b2)]!) + AssertSQL("SELECT \"f8\"(NULL, \"b2\") FROM \"table\" LIMIT 1") let f9: (Expression, Expression) -> Expression = db.create(function: "f9") { $0 && $1 } let f10: (Expression, Expression) -> Expression = db.create(function: "f10") { $0 ?? $1 } let f11: (Expression, Expression) -> Expression = db.create(function: "f11") { $0 && $1 != nil } let f12: (Expression, Expression) -> Expression = db.create(function: "f12") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT \"f9\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f9(b1, b1)).first![f9(b1, b1)]) - } - ExpectExecutions(db, ["SELECT \"f10\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f10(b2, b1)).first![f10(b2, b1)]) - } - ExpectExecutions(db, ["SELECT \"f11\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f11(b1, b2)).first![f11(b1, b2)]) - } - ExpectExecutions(db, ["SELECT \"f12\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f12(b2, b2)).first![f12(b2, b2)]) - } + XCTAssert(table.select(f9(b1, b1)).first![f9(b1, b1)]) + AssertSQL("SELECT \"f9\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1") + XCTAssert(table.select(f10(b2, b1)).first![f10(b2, b1)]) + AssertSQL("SELECT \"f10\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f11(b1, b2)).first![f11(b1, b2)]) + AssertSQL("SELECT \"f11\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f12(b2, b2)).first![f12(b2, b2)]) + AssertSQL("SELECT \"f12\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1") let f13: (Expression, Expression) -> Expression = db.create(function: "f13") { $0 && $1 } let f14: (Expression, Expression) -> Expression = db.create(function: "f14") { $0 ?? $1 } let f15: (Expression, Expression) -> Expression = db.create(function: "f15") { $0 && $1 != nil } let f16: (Expression, Expression) -> Expression = db.create(function: "f16") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT \"f13\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f13(b1, b1)).first![f13(b1, b1)]!) - } - ExpectExecutions(db, ["SELECT \"f14\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f14(b2, b1)).first![f14(b2, b1)]!) - } - ExpectExecutions(db, ["SELECT \"f15\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f15(b1, b2)).first![f15(b1, b2)]!) - } - ExpectExecutions(db, ["SELECT \"f16\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f16(b2, b2)).first![f16(b2, b2)]!) - } + XCTAssert(table.select(f13(b1, b1)).first![f13(b1, b1)]!) + AssertSQL("SELECT \"f13\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1") + XCTAssert(table.select(f14(b2, b1)).first![f14(b2, b1)]!) + AssertSQL("SELECT \"f14\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f15(b1, b2)).first![f15(b1, b2)]!) + AssertSQL("SELECT \"f15\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f16(b2, b2)).first![f16(b2, b2)]!) + AssertSQL("SELECT \"f16\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1") let f17: (Expression, Bool) -> Expression = db.create(function: "f17") { $0 && $1 } let f18: (Expression, Bool) -> Expression = db.create(function: "f18") { $0 ?? $1 } let f19: (Expression, Bool?) -> Expression = db.create(function: "f19") { $0 && $1 != nil } let f20: (Expression, Bool?) -> Expression = db.create(function: "f20") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT \"f17\"(\"b1\", 1) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f17(b1, true)).first![f17(b1, true)]) - } - ExpectExecutions(db, ["SELECT \"f18\"(\"b2\", 1) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f18(b2, true)).first![f18(b2, true)]) - } - ExpectExecutions(db, ["SELECT \"f19\"(\"b1\", NULL) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f19(b1, nil)).first![f19(b1, nil)]) - } - ExpectExecutions(db, ["SELECT \"f20\"(\"b2\", NULL) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f20(b2, nil)).first![f20(b2, nil)]) - } + XCTAssert(table.select(f17(b1, true)).first![f17(b1, true)]) + AssertSQL("SELECT \"f17\"(\"b1\", 1) FROM \"table\" LIMIT 1") + XCTAssert(table.select(f18(b2, true)).first![f18(b2, true)]) + AssertSQL("SELECT \"f18\"(\"b2\", 1) FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f19(b1, nil)).first![f19(b1, nil)]) + AssertSQL("SELECT \"f19\"(\"b1\", NULL) FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f20(b2, nil)).first![f20(b2, nil)]) + AssertSQL("SELECT \"f20\"(\"b2\", NULL) FROM \"table\" LIMIT 1") let f21: (Expression, Bool) -> Expression = db.create(function: "f21") { $0 && $1 } let f22: (Expression, Bool) -> Expression = db.create(function: "f22") { $0 ?? $1 } let f23: (Expression, Bool?) -> Expression = db.create(function: "f23") { $0 && $1 != nil } let f24: (Expression, Bool?) -> Expression = db.create(function: "f24") { $0 ?? $1 != nil } - ExpectExecutions(db, ["SELECT \"f21\"(\"b1\", 1) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f21(b1, true)).first![f21(b1, true)]!) - } - ExpectExecutions(db, ["SELECT \"f22\"(\"b2\", 1) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssert(table.select(f22(b2, true)).first![f22(b2, true)]!) - } - ExpectExecutions(db, ["SELECT \"f23\"(\"b1\", NULL) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f23(b1, nil)).first![f23(b1, nil)]!) - } - ExpectExecutions(db, ["SELECT \"f24\"(\"b2\", NULL) FROM \"table\" LIMIT 1": 1]) { db in - XCTAssertFalse(table.select(f24(b2, nil)).first![f24(b2, nil)]!) - } + XCTAssert(table.select(f21(b1, true)).first![f21(b1, true)]!) + AssertSQL("SELECT \"f21\"(\"b1\", 1) FROM \"table\" LIMIT 1") + XCTAssert(table.select(f22(b2, true)).first![f22(b2, true)]!) + AssertSQL("SELECT \"f22\"(\"b2\", 1) FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f23(b1, nil)).first![f23(b1, nil)]!) + AssertSQL("SELECT \"f23\"(\"b1\", NULL) FROM \"table\" LIMIT 1") + XCTAssertFalse(table.select(f24(b2, nil)).first![f24(b2, nil)]!) + AssertSQL("SELECT \"f24\"(\"b2\", NULL) FROM \"table\" LIMIT 1") } } diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index 9243e941..723a8e01 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -1,102 +1,69 @@ import XCTest import SQLite -class QueryTests: XCTestCase { - - let db = Database() - var users: Query { return db["users"] } - - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") - let salary = Expression("salary") - let admin = Expression("admin") - let manager_id = Expression("manager_id") +class QueryTests: SQLiteTestCase { override func setUp() { - super.setUp() + createUsersTable() - CreateUsersTable(db) + super.setUp() } func test_select_withExpression_compilesSelectClause() { - let query = users.select(email) - - let SQL = "SELECT \"email\" FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) } func test_select_withVariadicExpressions_compilesSelectClause() { - let query = users.select(email, count(*)) - - let SQL = "SELECT \"email\", count(*) FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) } func test_select_withStar_resetsSelectClause() { let query = users.select(email) - let SQL = "SELECT * FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(all: *) {} } + AssertSQL("SELECT * FROM \"users\"", query.select(*)) } func test_selectDistinct_withExpression_compilesSelectClause() { - let query = users.select(distinct: age) - - let SQL = "SELECT DISTINCT \"age\" FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) } func test_selectDistinct_withStar_compilesSelectClause() { - let query = users.select(distinct: *) - - let SQL = "SELECT DISTINCT * FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } func test_select_withSubquery() { let subquery = users.select(id) - var query = users.select(subquery) - var SQL = "SELECT (SELECT \"id\" FROM \"users\") FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } - - query = users.select(subquery.alias("u")) - SQL = "SELECT (SELECT \"id\" FROM (\"users\") AS \"u\") AS \"u\" FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT (SELECT \"id\" FROM \"users\") FROM \"users\"", users.select(subquery)) + AssertSQL("SELECT (SELECT \"id\" FROM (\"users\") AS \"u\") AS \"u\" FROM \"users\"", + users.select(subquery.alias("u"))) } func test_join_compilesJoinClause() { let managers = db["users"].alias("managers") - let query = users.join(managers, on: managers[id] == users[manager_id]) - let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL(SQL, users.join(managers, on: managers[id] == users[manager_id])) } func test_join_withExplicitType_compilesJoinClauseWithType() { let managers = db["users"].alias("managers") - let query = users.join(.LeftOuter, managers, on: managers[id] == users[manager_id]) - let SQL = "SELECT * FROM \"users\" " + "LEFT OUTER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL(SQL, users.join(.LeftOuter, managers, on: managers[id] == users[manager_id])) } func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { var managers = db["users"].alias("managers") managers = managers.filter(managers[admin]) - let query = users.join(managers, on: managers[id] == users[manager_id]) - let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (\"users\") AS \"managers\" " + "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") " + "AND \"managers\".\"admin\")" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL(SQL, users.join(managers, on: managers[id] == users[manager_id])) } func test_join_whenChained_compilesAggregateJoinClause() { @@ -110,7 +77,7 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " + "INNER JOIN (\"users\") AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in middleManagers {} } + AssertSQL(SQL, middleManagers) } func test_join_withNamespacedStar_expandsColumnNames() { @@ -126,11 +93,11 @@ class QueryTests: XCTestCase { let SQL = "SELECT \"users\".*, \"managers\".* FROM \"users\" " + "INNER JOIN (\"users\") AS \"managers\" " + "ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } + AssertSQL(SQL, query) } func test_join_withSubquery_joinsSubquery() { - InsertUser(db, "alice", age: 20) + insertUser("alice", age: 20) let maxId = max(id).alias("max_id") let subquery = users.select(maxId).group(age) @@ -140,12 +107,12 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM \"users\" GROUP BY \"age\") " + - "ON (\"max_id\" = \"id\")" - ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } + "ON (\"max_id\" = \"id\") LIMIT 1" + AssertSQL(SQL, query) } func test_join_withAliasedSubquery_joinsSubquery() { - InsertUser(db, "alice", age: 20) + insertUser("alice", age: 20) let maxId = max(id).alias("max_id") let subquery = users.select(maxId).group(age).alias("u") @@ -155,8 +122,8 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM \"users\" " + "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM (\"users\") AS \"u\" GROUP BY \"age\") AS \"u\" " + - "ON (\"u\".\"max_id\" = \"id\")" - ExpectExecutions(db, [SQL: 1]) { _ in for row in query { println(row) } } + "ON (\"u\".\"max_id\" = \"id\") LIMIT 1" + AssertSQL(SQL, query) } func test_namespacedColumnRowValueAccess() { @@ -184,10 +151,7 @@ class QueryTests: XCTestCase { } func test_filter_compilesWhereClause() { - let query = users.filter(admin == true) - - let SQL = "SELECT * FROM \"users\" WHERE (\"admin\" = 1)" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) } func test_filter_whenChained_compilesAggregateWhereClause() { @@ -198,134 +162,92 @@ class QueryTests: XCTestCase { let SQL = "SELECT * FROM \"users\" " + "WHERE ((\"email\" = 'alice@example.com') " + "AND (\"age\" >= 21))" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL(SQL, query) } func test_group_withSingleExpressionName_compilesGroupClause() { - let query = users.group(age) - - let SQL = "SELECT * FROM \"users\" GROUP BY \"age\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", + users.group(age)) } func test_group_withVariadicExpressionNames_compilesGroupClause() { - let query = users.group(age, admin) - - let SQL = "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) } func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { - let query = users.group(age, having: age >= 30) - - let SQL = "SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) } func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { - let query = users.group([age, admin], having: age >= 30) - - let SQL = "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", + users.group([age, admin], having: age >= 30)) } func test_order_withSingleExpressionName_compilesOrderClause() { - let query = users.order(age) - - let SQL = "SELECT * FROM \"users\" ORDER BY \"age\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) } func test_order_withVariadicExpressionNames_compilesOrderClause() { - let query = users.order(age, email) - - let SQL = "SELECT * FROM \"users\" ORDER BY \"age\", \"email\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) } func test_order_withExpressionAndSortDirection_compilesOrderClause() { - let query = users.order(age.desc, email.asc) - - let SQL = "SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc)) } func test_order_whenChained_overridesOrder() { - let query = users.order(email).order(age) - - let SQL = "SELECT * FROM \"users\" ORDER BY \"age\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) } func test_reverse_withoutOrder_ordersByRowIdDescending() { - let query = users.reverse() - - let SQL = "SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC", users.reverse()) } func test_reverse_withOrder_reversesOrder() { - let query = users.order(age, email.desc).reverse() - - let SQL = "SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age, email.desc).reverse()) } func test_limit_compilesLimitClause() { - let query = users.limit(5) - - let SQL = "SELECT * FROM \"users\" LIMIT 5" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) } func test_limit_withOffset_compilesOffsetClause() { - let query = users.limit(5, offset: 5) - - let SQL = "SELECT * FROM \"users\" LIMIT 5 OFFSET 5" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) } func test_limit_whenChained_overridesLimit() { - let query = users.limit(5).limit(10) - - var SQL = "SELECT * FROM \"users\" LIMIT 10" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + let query = users.limit(5) - SQL = "SELECT * FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} } + AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) + AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) } func test_limit_whenChained_withOffset_overridesOffset() { - let query = users.limit(5, offset: 5).limit(10, offset: 10) - - var SQL = "SELECT * FROM \"users\" LIMIT 10 OFFSET 10" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + let query = users.limit(5, offset: 5) - SQL = "SELECT * FROM \"users\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} } + AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 10", query.limit(10, offset: 10)) + AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) } func test_alias_compilesAliasInSelectClause() { - let managers = users.alias("managers") - var SQL = "SELECT * FROM (\"users\") AS \"managers\"" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in managers {} } + AssertSQL("SELECT * FROM (\"users\") AS \"managers\"", users.alias("managers")) } func test_subscript_withExpression_returnsNamespacedExpression() { - ExpectExecution(db, "SELECT \"users\".\"admin\" FROM \"users\"", users.select(users[admin])) - ExpectExecution(db, "SELECT \"users\".\"salary\" FROM \"users\"", users.select(users[salary])) - ExpectExecution(db, "SELECT \"users\".\"age\" FROM \"users\"", users.select(users[age])) - ExpectExecution(db, "SELECT \"users\".\"email\" FROM \"users\"", users.select(users[email])) - ExpectExecution(db, "SELECT \"users\".* FROM \"users\"", users.select(users[*])) + AssertSQL("SELECT \"users\".\"admin\" FROM \"users\"", users.select(users[admin])) + AssertSQL("SELECT \"users\".\"salary\" FROM \"users\"", users.select(users[salary])) + AssertSQL("SELECT \"users\".\"age\" FROM \"users\"", users.select(users[age])) + AssertSQL("SELECT \"users\".\"email\" FROM \"users\"", users.select(users[email])) + AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) } func test_subscript_withAliasAndExpression_returnsAliasedExpression() { let managers = users.alias("managers") - ExpectExecution(db, "SELECT \"managers\".\"admin\" FROM (\"users\") AS \"managers\"", managers.select(managers[admin])) - ExpectExecution(db, "SELECT \"managers\".\"salary\" FROM (\"users\") AS \"managers\"", managers.select(managers[salary])) - ExpectExecution(db, "SELECT \"managers\".\"age\" FROM (\"users\") AS \"managers\"", managers.select(managers[age])) - ExpectExecution(db, "SELECT \"managers\".\"email\" FROM (\"users\") AS \"managers\"", managers.select(managers[email])) - ExpectExecution(db, "SELECT \"managers\".* FROM (\"users\") AS \"managers\"", managers.select(managers[*])) + AssertSQL("SELECT \"managers\".\"admin\" FROM (\"users\") AS \"managers\"", managers.select(managers[admin])) + AssertSQL("SELECT \"managers\".\"salary\" FROM (\"users\") AS \"managers\"", managers.select(managers[salary])) + AssertSQL("SELECT \"managers\".\"age\" FROM (\"users\") AS \"managers\"", managers.select(managers[age])) + AssertSQL("SELECT \"managers\".\"email\" FROM (\"users\") AS \"managers\"", managers.select(managers[email])) + AssertSQL("SELECT \"managers\".* FROM (\"users\") AS \"managers\"", managers.select(managers[*])) } func test_SQL_compilesProperly() { @@ -349,7 +271,7 @@ class QueryTests: XCTestCase { "ORDER BY \"users\".\"email\" DESC " + "LIMIT 1 " + "OFFSET 2" - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } + AssertSQL(SQL, query) } func test_first_withAnEmptyQuery_returnsNil() { @@ -357,28 +279,28 @@ class QueryTests: XCTestCase { } func test_first_returnsTheFirstRow() { - InsertUsers(db, "alice", "betsy") - ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 1]) { _ in - XCTAssertEqual(Int64(1), self.users.first![self.id]) - } + insertUsers("alice", "betsy") + + XCTAssertEqual(1, users.first![id]) + AssertSQL("SELECT * FROM \"users\" LIMIT 1") } func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() { - ExpectExecutions(db, ["SELECT * FROM \"users\" LIMIT 1": 2]) { _ in - XCTAssertTrue(self.users.isEmpty) - InsertUser(self.db, "alice") - XCTAssertFalse(self.users.isEmpty) - } + XCTAssertTrue(users.isEmpty) + + insertUser("alice") + + XCTAssertFalse(users.isEmpty) + + AssertSQL("SELECT * FROM \"users\" LIMIT 1", 2) } func test_insert_insertsRows() { - let SQL = "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)" + XCTAssertEqual(1, users.insert(email <- "alice@example.com", age <- 30).id!) - ExpectExecutions(db, [SQL: 1]) { _ in - XCTAssertEqual(Int64(1), self.users.insert(self.email <- "alice@example.com", self.age <- 30).id!) - } + AssertSQL("INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - XCTAssert(self.users.insert(self.email <- "alice@example.com", self.age <- 30).id == nil) + XCTAssert(users.insert(email <- "alice@example.com", age <- 30).id == nil) } func test_insert_withQuery_insertsRows() { @@ -386,41 +308,39 @@ class QueryTests: XCTestCase { let emails = db["emails"] let admins = users.select(email).filter(admin == true) - ExpectExecution(db, "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)", emails.insert(admins)) + emails.insert(admins)! + AssertSQL("INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)") } func test_insert_insertsDefaultRow() { db.execute("CREATE TABLE \"timestamps\" (\"id\" INTEGER PRIMARY KEY, \"timestamp\" TEXT DEFAULT CURRENT_DATETIME)") let table = db["timestamps"] - ExpectExecutions(db, ["INSERT INTO \"timestamps\" DEFAULT VALUES": 1]) { _ in - XCTAssertEqual(Int64(1), table.insert().id!) - } + XCTAssertEqual(1, table.insert().id!) + AssertSQL("INSERT INTO \"timestamps\" DEFAULT VALUES") } func test_replace_replaceRows() { - let SQL = "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)" + XCTAssertEqual(1, users.replace(email <- "alice@example.com", age <- 30).id!) + AssertSQL("INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - ExpectExecutions(db, [SQL: 1]) { _ in - XCTAssertEqual(Int64(1), self.users.replace(self.email <- "alice@example.com", self.age <- 30).id!) - } - - XCTAssertEqual(Int64(1), self.users.replace(self.id <- 1, self.email <- "bob@example.com", self.age <- 30).id!) + XCTAssertEqual(1, users.replace(id <- 1, email <- "betty@example.com", age <- 30).id!) + AssertSQL("INSERT OR REPLACE INTO \"users\" (\"id\", \"email\", \"age\") VALUES (1, 'betty@example.com', 30)") } - + func test_update_updatesRows() { - InsertUsers(db, "alice", "betsy") - InsertUser(db, "dolly", admin: true) + insertUsers("alice", "betsy") + insertUser("dolly", admin: true) XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes!) XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes!) } func test_delete_deletesRows() { - InsertUser(db, "alice", age: 20) + insertUser("alice", age: 20) XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes!) - InsertUser(db, "betsy", age: 30) + insertUser("betsy", age: 30) XCTAssertEqual(2, users.delete().changes!) XCTAssertEqual(0, users.delete().changes!) } @@ -428,15 +348,15 @@ class QueryTests: XCTestCase { func test_count_returnsCount() { XCTAssertEqual(0, users.count) - InsertUser(db, "alice") + insertUser("alice") XCTAssertEqual(1, users.count) XCTAssertEqual(0, users.filter(age != nil).count) } func test_count_withExpression_returnsCount() { - InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 20) - InsertUser(db, "cindy") + insertUser("alice", age: 20) + insertUser("betsy", age: 20) + insertUser("cindy") XCTAssertEqual(2, users.count(age)) XCTAssertEqual(1, users.count(distinct: age)) @@ -445,25 +365,25 @@ class QueryTests: XCTestCase { func test_max_withInt_returnsMaximumInt() { XCTAssert(users.max(age) == nil) - InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 30) + insertUser("alice", age: 20) + insertUser("betsy", age: 30) XCTAssertEqual(30, users.max(age)!) } func test_min_withInt_returnsMinimumInt() { XCTAssert(users.min(age) == nil) - InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 30) + insertUser("alice", age: 20) + insertUser("betsy", age: 30) XCTAssertEqual(20, users.min(age)!) } func test_averageWithInt_returnsDouble() { XCTAssert(users.average(age) == nil) - InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 50) - InsertUser(db, "cindy", age: 50) + insertUser("alice", age: 20) + insertUser("betsy", age: 50) + insertUser("cindy", age: 50) XCTAssertEqual(40.0, users.average(age)!) XCTAssertEqual(35.0, users.average(distinct: age)!) } @@ -471,9 +391,9 @@ class QueryTests: XCTestCase { func test_sum_returnsSum() { XCTAssert(users.sum(age) == nil) - InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 30) - InsertUser(db, "cindy", age: 30) + insertUser("alice", age: 20) + insertUser("betsy", age: 30) + insertUser("cindy", age: 30) XCTAssertEqual(80, users.sum(age)!) XCTAssertEqual(50, users.sum(distinct: age)!) } @@ -481,15 +401,15 @@ class QueryTests: XCTestCase { func test_total_returnsTotal() { XCTAssertEqual(0.0, users.total(age)) - InsertUser(db, "alice", age: 20) - InsertUser(db, "betsy", age: 30) - InsertUser(db, "cindy", age: 30) + insertUser("alice", age: 20) + insertUser("betsy", age: 30) + insertUser("cindy", age: 30) XCTAssertEqual(80.0, users.total(age)) XCTAssertEqual(50.0, users.total(distinct: age)) } func test_row_withBoundColumn_returnsValue() { - InsertUser(db, "alice", age: 20) + insertUser("alice", age: 20) XCTAssertEqual(21, users.select(age + 1).first![age + 1]!) } diff --git a/SQLite Tests/RTreeTests.swift b/SQLite Tests/RTreeTests.swift index 64eecac3..fe5feb57 100644 --- a/SQLite Tests/RTreeTests.swift +++ b/SQLite Tests/RTreeTests.swift @@ -1,19 +1,19 @@ import XCTest import SQLite -class RTreeTests: XCTestCase { +let minX = Expression("minX") +let maxX = Expression("maxX") +let minY = Expression("minY") +let maxY = Expression("maxY") - let id = Expression("id") - let minX = Expression("minX") - let maxX = Expression("maxX") - let minY = Expression("minY") - let maxY = Expression("maxY") +class RTreeTests: SQLiteTestCase { - let db = Database() var index: Query { return db["index"] } func test_createVtable_usingRtree_createsVirtualTable() { - ExpectExecution(db, "CREATE VIRTUAL TABLE \"index\" USING rtree(\"id\", \"minX\", \"maxX\", \"minY\", \"maxY\")", db.create(vtable: index, using: rtree(id, minX, maxX, minY, maxY))) + db.create(vtable: index, using: rtree(id, minX, maxX, minY, maxY)) + + AssertSQL("CREATE VIRTUAL TABLE \"index\" USING rtree(\"id\", \"minX\", \"maxX\", \"minY\", \"maxY\")") } } diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 140274eb..6d8b2d42 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -1,56 +1,42 @@ import XCTest import SQLite -private let id = Expression("id") -private let email = Expression("email") -private let age = Expression("age") -private let salary = Expression("salary") -private let admin = Expression("admin") -private let manager_id = Expression("manager_id") - -class SchemaTests: XCTestCase { - - let db = Database() - var users: Query { return db["users"] } +class SchemaTests: SQLiteTestCase { override func setUp() { - super.setUp() db.foreignKeys = true - db.trace(println) + + super.setUp() } func test_createTable_createsTable() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"age\" INTEGER)", - db.create(table: users) { t in t.column(age) } - ) + db.create(table: users) { $0.column(age) } + + AssertSQL("CREATE TABLE \"users\" (\"age\" INTEGER)") } func test_createTable_temporary_createsTemporaryTable() { - ExpectExecution(db, "CREATE TEMPORARY TABLE \"users\" (\"age\" INTEGER)", - db.create(table: users, temporary: true) { t in t.column(age) } - ) + db.create(table: users, temporary: true) { $0.column(age) } + + AssertSQL("CREATE TEMPORARY TABLE \"users\" (\"age\" INTEGER)") } func test_createTable_ifNotExists_createsTableIfNotExists() { - ExpectExecution(db, "CREATE TABLE IF NOT EXISTS \"users\" (\"age\" INTEGER)", - db.create(table: users, ifNotExists: true) { t in t.column(age) } - ) + db.create(table: users, ifNotExists: true) { $0.column(age) } + + AssertSQL("CREATE TABLE IF NOT EXISTS \"users\" (\"age\" INTEGER)") } func test_createTable_column_buildsColumnDefinition() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)", - db.create(table: users) { t in - t.column(email) - } - ) + db.create(table: users) { $0.column(email) } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)") } func test_createTable_column_nonIntegerPrimaryKey() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT PRIMARY KEY NOT NULL)", - db.create(table: users) { t in - t.column(email, primaryKey: true) - } - ) + db.create(table: users) { $0.column(email, primaryKey: true) } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT PRIMARY KEY NOT NULL)") } func test_createTable_column_nonIntegerPrimaryKey_withDefaultValue() { @@ -58,323 +44,284 @@ class SchemaTests: XCTestCase { let uuidgen: () -> Expression = db.create(function: "uuidgen") { return NSUUID().UUIDString } + db.create(table: users) { $0.column(uuid, primaryKey: true, defaultValue: uuidgen()) } - ExpectExecution(db, "CREATE TABLE \"users\" (\"uuid\" TEXT PRIMARY KEY NOT NULL DEFAULT (\"uuidgen\"()))", - db.create(table: users) { t in - t.column(uuid, primaryKey: true, defaultValue: uuidgen()) - } - ) + AssertSQL("CREATE TABLE \"users\" (\"uuid\" TEXT PRIMARY KEY NOT NULL DEFAULT (\"uuidgen\"()))") } func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)", - db.create(table: users) { t in - t.column(id, primaryKey: true) - } - ) + db.create(table: users) { $0.column(id, primaryKey: true) } + + AssertSQL("CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)") } func test_createTable_column_withPrimaryKey_buildsPrimaryKeyAutoincrementClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", - db.create(table: users) { t in - t.column(id, primaryKey: .Autoincrement) - } - ) + db.create(table: users) { $0.column(id, primaryKey: .Autoincrement) } + + AssertSQL("CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") } func test_createTable_column_withNullFalse_buildsNotNullClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)", - db.create(table: users) { t in - t.column(email) - } - ) + db.create(table: users) { $0 .column(email) } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)") } func test_createTable_column_withUnique_buildsUniqueClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL UNIQUE)", - db.create(table: users) { t in - t.column(email, unique: true) - } - ) + db.create(table: users) { $0.column(email, unique: true) } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL UNIQUE)") } func test_createTable_column_withCheck_buildsCheckClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL CHECK ((\"admin\" IN (0, 1))))", - db.create(table: users) { t in - t.column(admin, check: contains([false, true], admin)) - } - ) + db.create(table: users) { $0.column(admin, check: contains([false, true], admin)) } + + AssertSQL("CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL CHECK ((\"admin\" IN (0, 1))))") } func test_createTable_column_withDefaultValue_buildsDefaultClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"salary\" REAL NOT NULL DEFAULT 0.0)", - db.create(table: users) { t in - t.column(salary, defaultValue: 0) - } - ) + db.create(table: users) { $0.column(salary, defaultValue: 0) } + + AssertSQL("CREATE TABLE \"users\" (\"salary\" REAL NOT NULL DEFAULT 0.0)") } func test_createTable_stringColumn_collation_buildsCollateClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)", - db.create(table: users) { t in - t.column(email, collate: .Nocase) - } - ) + db.create(table: users) { $0.column(email, collate: .Nocase) } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)") } func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (" + + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id, references: users[id]) + } + + AssertSQL("CREATE TABLE \"users\" (" + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER REFERENCES \"users\"(\"id\")" + - ")", - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id, references: users[id]) - } - ) + "\"manager_id\" INTEGER REFERENCES \"users\"(\"id\"))") } func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (" + + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id, references: users) + } + + AssertSQL("CREATE TABLE \"users\" (" + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER REFERENCES \"users\"" + - ")", - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id, references: users) - } - ) + "\"manager_id\" INTEGER REFERENCES \"users\")") } func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { - let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, PRIMARY KEY(\"email\"))", - db.create(table: users) { t in - t.column(email) - t.primaryKey(email) - } - ) + db.create(table: users) { t in + t.column(email) + t.primaryKey(email) + } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, PRIMARY KEY(\"email\"))") } func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { - let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (" + - "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, PRIMARY KEY(\"id\", \"email\")" + - ")", - db.create(table: users) { t in - t.column(id) - t.column(email) - t.primaryKey(id, email) - } - ) + db.create(table: users) { t in + t.column(id) + t.column(email) + t.primaryKey(id, email) + } + + AssertSQL("CREATE TABLE \"users\" (" + + "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, PRIMARY KEY(\"id\", \"email\"))") } func test_createTable_unique_buildsUniqueTableConstraint() { - let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, UNIQUE(\"email\"))", - db.create(table: users) { t in - t.column(email) - t.unique(email) - } - ) + db.create(table: users) { t in + t.column(email) + t.unique(email) + } + + AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, UNIQUE(\"email\"))") } func test_createTable_unique_buildsCompositeUniqueTableConstraint() { - let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (" + - "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, UNIQUE(\"id\", \"email\")" + - ")", - db.create(table: users) { t in - t.column(id) - t.column(email) - t.unique(id, email) - } - ) + db.create(table: users) { t in + t.column(id) + t.column(email) + t.unique(id, email) + } + + AssertSQL("CREATE TABLE \"users\" (" + + "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, UNIQUE(\"id\", \"email\"))") } func test_createTable_check_buildsCheckTableConstraint() { - let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL, CHECK ((\"admin\" IN (0, 1))))", - db.create(table: users) { t in - t.column(admin) - t.check(contains([false, true], admin)) - } - ) + db.create(table: users) { t in + t.column(admin) + t.check(contains([false, true], admin)) + } + + AssertSQL("CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL, CHECK ((\"admin\" IN (0, 1))))") } func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() { let users = self.users - ExpectExecution(db, - "CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\")" + - ")", - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id]) - } - ) + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users[id]) + } + + AssertSQL("CREATE TABLE \"users\" (" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\"))") } func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() { let users = self.users - ExpectExecution(db, - "CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON UPDATE CASCADE" + - ")", - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id], update: .Cascade) - } - ) + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users[id], update: .Cascade) + } + + AssertSQL("CREATE TABLE \"users\" (" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON UPDATE CASCADE)") } func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() { let users = self.users - ExpectExecution(db, - "CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON DELETE CASCADE" + - ")", - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id], delete: .Cascade) - } - ) + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.foreignKey(manager_id, references: users[id], delete: .Cascade) + } + + AssertSQL("CREATE TABLE \"users\" (" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER, " + + "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON DELETE CASCADE)") } func test_createTable_foreignKey_withCompositeKey_buildsForeignKeyTableConstraint() { let users = self.users let manager_id = Expression("manager_id") // required - ExpectExecution(db, - "CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER NOT NULL, " + - "\"email\" TEXT NOT NULL, " + - "FOREIGN KEY(\"manager_id\", \"email\") REFERENCES \"users\"(\"id\", \"email\")" + - ")", - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.column(email) - t.foreignKey((manager_id, email), references: (users[id], email)) - } - ) + + db.create(table: users) { t in + t.column(id, primaryKey: true) + t.column(manager_id) + t.column(email) + t.foreignKey((manager_id, email), references: (users[id], email)) + } + + AssertSQL("CREATE TABLE \"users\" (" + + "\"id\" INTEGER PRIMARY KEY NOT NULL, " + + "\"manager_id\" INTEGER NOT NULL, " + + "\"email\" TEXT NOT NULL, " + + "FOREIGN KEY(\"manager_id\", \"email\") REFERENCES \"users\"(\"id\", \"email\"))") } func test_createTable_withQuery_createsTableWithQuery() { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE TABLE \"emails\" AS SELECT \"email\" FROM \"users\"", - db.create(table: db["emails"], from: users.select(email)) - ) - ExpectExecution(db, - "CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"", - db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) - ) + createUsersTable() + + db.create(table: db["emails"], from: users.select(email)) + AssertSQL("CREATE TABLE \"emails\" AS SELECT \"email\" FROM \"users\"") + + db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) + AssertSQL("CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"") } func test_alterTable_renamesTable() { - CreateUsersTable(db) + createUsersTable() let people = db["people"] - ExpectExecution(db, "ALTER TABLE \"users\" RENAME TO \"people\"", db.rename(table: "users", to: people) ) + + db.rename(table: "users", to: people) + AssertSQL("ALTER TABLE \"users\" RENAME TO \"people\"") } func test_alterTable_addsNotNullColumn() { - CreateUsersTable(db) + createUsersTable() let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL NOT NULL DEFAULT 0.0", - db.alter(table: users, add: column, defaultValue: 0) - ) + db.alter(table: users, add: column, defaultValue: 0) + AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL NOT NULL DEFAULT 0.0") } func test_alterTable_addsRegularColumn() { - CreateUsersTable(db) + createUsersTable() let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL", - db.alter(table: users, add: column) - ) + db.alter(table: users, add: column) + AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL") } func test_alterTable_withDefaultValue_addsRegularColumn() { - CreateUsersTable(db) + createUsersTable() let column = Expression("bonus") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL DEFAULT 0.0", - db.alter(table: users, add: column, defaultValue: 0) - ) + db.alter(table: users, add: column, defaultValue: 0) + AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL DEFAULT 0.0") } func test_alterTable_withForeignKey_addsRegularColumn() { - CreateUsersTable(db) + createUsersTable() let column = Expression("parent_id") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")", - db.alter(table: users, add: column, references: users[id]) - ) + db.alter(table: users, add: column, references: users[id]) + AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")") } func test_alterTable_stringColumn_collation_buildsCollateClause() { - CreateUsersTable(db) + createUsersTable() let columnA = Expression("column_a") let columnB = Expression("column_b") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE \"NOCASE\"", - db.alter(table: users, add: columnA, defaultValue: "", collate: .Nocase) - ) + db.alter(table: users, add: columnA, defaultValue: "", collate: .Nocase) + AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE \"NOCASE\"") - ExpectExecution(db, "ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE \"NOCASE\"", - db.alter(table: users, add: columnB, collate: .Nocase) - ) + db.alter(table: users, add: columnB, collate: .Nocase) + AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE \"NOCASE\"") } func test_dropTable_dropsTable() { - CreateUsersTable(db) - ExpectExecution(db, "DROP TABLE \"users\"", db.drop(table: users) ) - ExpectExecution(db, "DROP TABLE IF EXISTS \"users\"", db.drop(table: users, ifExists: true) ) + createUsersTable() + + db.drop(table: users) + AssertSQL("DROP TABLE \"users\"") + + db.drop(table: users, ifExists: true) + AssertSQL("DROP TABLE IF EXISTS \"users\"") } func test_index_executesIndexStatement() { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")", - db.create(index: users, on: email) - ) + createUsersTable() + + db.create(index: users, on: email) + AssertSQL("CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")") } func test_index_withUniqueness_executesUniqueIndexStatement() { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE UNIQUE INDEX \"index_users_on_email\" ON \"users\" (\"email\")", - db.create(index: users, unique: true, on: email) - ) + createUsersTable() + + db.create(index: users, unique: true, on: email) + AssertSQL("CREATE UNIQUE INDEX \"index_users_on_email\" ON \"users\" (\"email\")") } func test_index_ifNotExists_executesIndexStatement() { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE INDEX IF NOT EXISTS \"index_users_on_email\" ON \"users\" (\"email\")", - db.create(index: users, ifNotExists: true, on: email) - ) + createUsersTable() + + db.create(index: users, ifNotExists: true, on: email) + AssertSQL("CREATE INDEX IF NOT EXISTS \"index_users_on_email\" ON \"users\" (\"email\")") } func test_index_withMultipleColumns_executesCompoundIndexStatement() { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE INDEX \"index_users_on_age_email\" ON \"users\" (\"age\" DESC, \"email\")", - db.create(index: users, on: age.desc, email) - ) + createUsersTable() + + db.create(index: users, on: age.desc, email) + AssertSQL("CREATE INDEX \"index_users_on_age_email\" ON \"users\" (\"age\" DESC, \"email\")") } // func test_index_withFilter_executesPartialIndexStatementWithWhereClause() { @@ -388,40 +335,44 @@ class SchemaTests: XCTestCase { // } func test_dropIndex_dropsIndex() { - CreateUsersTable(db) + createUsersTable() db.create(index: users, on: email) - ExpectExecution(db, "DROP INDEX \"index_users_on_email\"", db.drop(index: users, on: email)) - ExpectExecution(db, "DROP INDEX IF EXISTS \"index_users_on_email\"", db.drop(index: users, ifExists: true, on: email)) + db.drop(index: users, on: email) + AssertSQL("DROP INDEX \"index_users_on_email\"") + + db.drop(index: users, ifExists: true, on: email) + AssertSQL("DROP INDEX IF EXISTS \"index_users_on_email\"") } func test_createView_withQuery_createsViewWithQuery() { - CreateUsersTable(db) - ExpectExecution(db, - "CREATE VIEW \"emails\" AS SELECT \"email\" FROM \"users\"", - db.create(view: db["emails"], from: users.select(email)) - ) - ExpectExecution(db, - "CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"", - db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) - ) + createUsersTable() + + db.create(view: db["emails"], from: users.select(email)) + AssertSQL("CREATE VIEW \"emails\" AS SELECT \"email\" FROM \"users\"") + + db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) + AssertSQL("CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"") } func test_dropView_dropsView() { - CreateUsersTable(db) - db.create(view: db["emails"], from: users.select(email)) + createUsersTable() + let emails = db["emails"] + db.create(view: emails, from: users.select(email)) + + db.drop(view: emails) + AssertSQL("DROP VIEW \"emails\"") - ExpectExecution(db, "DROP VIEW \"emails\"", db.drop(view: db["emails"])) - ExpectExecution(db, "DROP VIEW IF EXISTS \"emails\"", db.drop(view: db["emails"], ifExists: true)) + db.drop(view: emails, ifExists: true) + AssertSQL("DROP VIEW IF EXISTS \"emails\"") } func test_quotedIdentifiers() { let table = db["table"] let column = Expression("My lil' primary key, \"Kiwi\"") - ExpectExecution(db, "CREATE TABLE \"table\" (\"My lil' primary key, \"\"Kiwi\"\"\" INTEGER NOT NULL)", - db.create(table: db["table"]) { $0.column(column) } - ) + db.create(table: table) { $0.column(column) } + AssertSQL("CREATE TABLE \"table\" (\"My lil' primary key, \"\"Kiwi\"\"\" INTEGER NOT NULL)") } } diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index cf297c6d..3bcd96d0 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -1,85 +1,52 @@ import XCTest import SQLite -class StatementTests: XCTestCase { - - let db = Database() +class StatementTests: SQLiteTestCase { override func setUp() { - super.setUp() + createUsersTable() - CreateUsersTable(db) + super.setUp() } func test_bind_withVariadicParameters_bindsParameters() { let stmt = db.prepare("SELECT ?, ?, ?, ?") - ExpectExecutions(db, ["SELECT 1, 2.0, '3', x'34'": 1]) { _ in - withBlob { blob in - stmt.bind(1, 2.0, "3", blob).run() - return - } - } + withBlob { stmt.bind(1, 2.0, "3", $0) } + AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) } func test_bind_withArrayOfParameters_bindsParameters() { let stmt = db.prepare("SELECT ?, ?, ?, ?") - ExpectExecutions(db, ["SELECT 1, 2.0, '3', x'34'": 1]) { _ in - withBlob { blob in - stmt.bind([1, 2.0, "3", blob]).run() - return - } - } + withBlob { stmt.bind([1, 2.0, "3", $0]) } + AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) } func test_bind_withNamedParameters_bindsParameters() { let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4") - ExpectExecutions(db, ["SELECT 1, 2.0, '3', x'34'": 1]) { _ in - withBlob { blob in - stmt.bind(["?1": 1, "?2": 2.0, "?3": "3", "?4": blob]).run() - return - } - } + withBlob { stmt.bind(["?1": 1, "?2": 2.0, "?3": "3", "?4": $0]) } + AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) } func test_bind_withBlob_bindsBlob() { let stmt = db.prepare("SELECT ?") - ExpectExecutions(db, ["SELECT x'34'": 1]) { _ in - withBlob { blob in - stmt.bind(blob).run() - return - } - } + withBlob { stmt.bind($0) } + AssertSQL("SELECT x'34'", stmt) } func test_bind_withDouble_bindsDouble() { - let stmt = db.prepare("SELECT ?") - ExpectExecutions(db, ["SELECT 2.0": 1]) { _ in - stmt.bind(2.0).run() - return - } + AssertSQL("SELECT 2.0", db.prepare("SELECT ?").bind(2.0)) } func test_bind_withInt_bindsInt() { - let stmt = db.prepare("SELECT ?") - ExpectExecutions(db, ["SELECT 3": 1]) { _ in - stmt.bind(3).run() - return - } + AssertSQL("SELECT 3", db.prepare("SELECT ?").bind(3)) } func test_bind_withString() { - let stmt = db.prepare("SELECT ?") - ExpectExecutions(db, ["SELECT '4'": 1]) { _ in - stmt.bind("4").run() - return - } + AssertSQL("SELECT '4'", db.prepare("SELECT ?").bind("4")) } func test_run_withNoParameters() { - let stmt = db.prepare( - "INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)" - ) - stmt.run() + db.prepare("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)").run() XCTAssertEqual(1, db.totalChanges) } @@ -105,56 +72,56 @@ class StatementTests: XCTestCase { func test_scalar_withNoParameters() { let zero = db.prepare("SELECT 0") - XCTAssertEqual(Int64(0), zero.scalar() as Int64) + XCTAssertEqual(0, zero.scalar() as! Int64) } func test_scalar_withNoParameters_retainsBindings() { let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?", 21) - XCTAssertEqual(Int64(0), count.scalar() as Int64) + XCTAssertEqual(0, count.scalar() as! Int64) - InsertUser(db, "alice", age: 21) - XCTAssertEqual(Int64(1), count.scalar() as Int64) + insertUser("alice", age: 21) + XCTAssertEqual(1, count.scalar() as! Int64) } func test_scalar_withVariadicParameters() { - InsertUser(db, "alice", age: 21) + insertUser( "alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(Int64(1), count.scalar(21) as Int64) + XCTAssertEqual(1, count.scalar(21) as! Int64) } func test_scalar_withArrayOfParameters() { - InsertUser(db, "alice", age: 21) + insertUser( "alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(Int64(1), count.scalar([21]) as Int64) + XCTAssertEqual(1, count.scalar([21]) as! Int64) } func test_scalar_withNamedParameters() { - InsertUser(db, "alice", age: 21) + insertUser("alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= $age") - XCTAssertEqual(Int64(1), count.scalar(["$age": 21]) as Int64) + XCTAssertEqual(1, count.scalar(["$age": 21]) as! Int64) } func test_scalar_withParameters_updatesBindings() { - InsertUser(db, "alice", age: 21) + insertUser("alice", age: 21) let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(Int64(1), count.scalar(21) as Int64) - XCTAssertEqual(Int64(0), count.scalar(22) as Int64) + XCTAssertEqual(1, count.scalar(21) as! Int64) + XCTAssertEqual(0, count.scalar(22) as! Int64) } func test_scalar_doubleReturnValue() { - XCTAssertEqual(2.0, db.scalar("SELECT 2.0") as Double) + XCTAssertEqual(2.0, db.scalar("SELECT 2.0") as! Double) } func test_scalar_intReturnValue() { - XCTAssertEqual(Int64(3), db.scalar("SELECT 3") as Int64) + XCTAssertEqual(3, db.scalar("SELECT 3") as! Int64) } func test_scalar_stringReturnValue() { - XCTAssertEqual("4", db.scalar("SELECT '4'") as String) + XCTAssertEqual("4", db.scalar("SELECT '4'") as! String) } func test_generate_allowsIteration() { - InsertUsers(db, "alice", "betsy", "cindy") + insertUsers("alice", "betsy", "cindy") var count = 0 for row in db.prepare("SELECT id FROM users") { XCTAssertEqual(1, row.count) @@ -164,7 +131,7 @@ class StatementTests: XCTestCase { } func test_generate_allowsReuse() { - InsertUsers(db, "alice", "betsy", "cindy") + insertUsers("alice", "betsy", "cindy") var count = 0 let stmt = db.prepare("SELECT id FROM users") for row in stmt { count++ } @@ -173,11 +140,11 @@ class StatementTests: XCTestCase { } func test_row_returnsValues() { - InsertUser(db, "alice") + insertUser("alice") let stmt = db.prepare("SELECT id, email FROM users") stmt.step() - XCTAssertEqual(Int64(1), stmt.row[0] as Int64) + XCTAssertEqual(1, stmt.row[0] as Int64) XCTAssertEqual("alice@example.com", stmt.row[1] as String) } diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift index c9aef700..eff00fd4 100644 --- a/SQLite Tests/TestHelper.swift +++ b/SQLite Tests/TestHelper.swift @@ -1,57 +1,73 @@ import SQLite import XCTest -func Trace(SQL: String) { - println(SQL) -} +let id = Expression("id") +let email = Expression("email") +let age = Expression("age") +let salary = Expression("salary") +let admin = Expression("admin") +let manager_id = Expression("manager_id") -func CreateUsersTable(db: Database) { - db.execute( - "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "salary REAL, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" - ) - db.trace(Trace) -} +class SQLiteTestCase: XCTestCase { -func InsertUser(db: Database, name: String, age: Int? = nil, admin: Bool = false) -> Statement { - return db.run( - "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - ["\(name)@example.com", age, admin.datatypeValue] - ) -} + var trace = [String: Int]() -func InsertUsers(db: Database, names: String...) { - for name in names { InsertUser(db, name) } -} + let db = Database() + var users: Query { return db["users"] } -func ExpectExecutions(db: Database, statements: [String: Int], block: Database -> ()) { - var fulfilled = [String: Int]() - for (SQL, _) in statements { fulfilled[SQL] = 0 } - db.trace { SQL in - Trace(SQL) - if let count = fulfilled[SQL] { fulfilled[SQL] = count + 1 } + override func setUp() { + super.setUp() + + db.trace { SQL in + println(SQL) + self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 + } } - block(db) + func createUsersTable() { + db.execute( + "CREATE TABLE \"users\" (" + + "id INTEGER PRIMARY KEY, " + + "email TEXT NOT NULL UNIQUE, " + + "age INTEGER, " + + "salary REAL, " + + "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users(id)" + + ")" + ) + } - XCTAssertEqual(statements, fulfilled) -} + func insertUsers(names: String...) { + for name in names { insertUser(name) } + } -func ExpectExecution(db: Database, SQL: String, statement: @autoclosure () -> Statement) { - ExpectExecutions(db, [SQL: 1]) { _ in - statement() - return + func insertUser(name: String, age: Int? = nil, admin: Bool = false) -> Statement { + return db.run( + "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + ["\(name)@example.com", age, admin.datatypeValue] + ) + } + + func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + XCTAssertEqual( + executions, trace[SQL] ?? 0, + message ?? SQL, + file: file, line: line + ) + } + + func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + statement.run() + AssertSQL(SQL, 1, message, file: file, line: line) + if let count = trace[SQL] { trace[SQL] = count - 1 } + } + + func AssertSQL(SQL: String, _ query: Query, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + for _ in query {} + AssertSQL(SQL, 1, message, file: file, line: line) + if let count = trace[SQL] { trace[SQL] = count - 1 } } -} -func ExpectExecution(db: Database, SQL: String, query: Query) { - ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} } } diff --git a/SQLite.playground/section-16.swift b/SQLite.playground/section-16.swift index 41aa55cc..5bcfe10d 100644 --- a/SQLite.playground/section-16.swift +++ b/SQLite.playground/section-16.swift @@ -1,5 +1,4 @@ -db.transaction( - sr.run("dolly@acme.com"), - jr.run("emery@acme.com", db.lastId) -) -count.scalar() +db.transaction() && + sr.run("dolly@acme.com") && + jr.run("emery@acme.com", db.lastId) && + db.commit() || db.rollback() diff --git a/SQLite.playground/section-18.swift b/SQLite.playground/section-18.swift index dedba10c..0aff1a5b 100644 --- a/SQLite.playground/section-18.swift +++ b/SQLite.playground/section-18.swift @@ -1,7 +1,9 @@ -let txn = db.transaction( - sr.run("fiona@acme.com"), - jr.run("emery@acme.com", db.lastId) -) +let txn = db.transaction() && + sr.run("fiona@acme.com") && + jr.run("emery@acme.com", db.lastId) && + db.commit() +txn || db.rollback() + count.scalar() txn.failed diff --git a/SQLite.playground/section-26.swift b/SQLite.playground/section-26.swift index 25f64125..58a328f3 100644 --- a/SQLite.playground/section-26.swift +++ b/SQLite.playground/section-26.swift @@ -1,4 +1,4 @@ -db.transaction( - users.insert(email <- "julie@acme.com"), - users.insert(email <- "kelly@acme.com", manager_id <- db.lastId) -) +db.transaction() && + users.insert(email <- "julie@acme.com") && + users.insert(email <- "kelly@acme.com", manager_id <- db.lastId) && + db.commit() || db.rollback() diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index c6c9868d..dced7302 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -396,7 +396,7 @@ DC3773EA19C8CBB3004FCF85 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0620; + LastUpgradeCheck = 0630; ORGANIZATIONNAME = "Stephen Celis"; TargetAttributes = { DC3773F219C8CBB3004FCF85 = { diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme index 8530c286..0e27d856 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme @@ -1,6 +1,6 @@ Statement)...) -> Statement { - return transaction(statements) + /// :returns: The BEGIN TRANSACTION statement. + public func transaction(_ mode: TransactionMode = .Deferred) -> Statement { + return run("BEGIN \(mode.rawValue) TRANSACTION") } - - /// Runs a series of statements in a transaction. The first statement to - /// fail will short-circuit the rest and roll back the changes. A successful - /// transaction will automatically be committed. + + /// Runs a transaction with the given savepoint name (if omitted, it will + /// generate a UUID). + /// + /// :param: mode The mode in which a transaction acquires a lock. (Default: + /// .Deferred.) /// - /// :param: statements Statements to run in the transaction. + /// :param: block A closure to run SQL statements within the transaction. + /// Should return a TransactionResult depending on success or + /// failure. /// - /// :returns: The last statement executed, successful or not. - public func transaction(statements: [@autoclosure () -> Statement]) -> Statement { - return transaction(.Deferred, statements) + /// :returns: The COMMIT or ROLLBACK statement. + public func transaction(_ mode: TransactionMode = .Deferred, _ block: Statement -> TransactionResult) -> Statement { + return run(block(transaction(mode)).rawValue) } - /// Runs a series of statements in a transaction. The first statement to - /// fail will short-circuit the rest and roll back the changes. A successful - /// transaction will automatically be committed. - /// - /// :param: mode The mode in which the transaction will acquire a - /// lock. + /// Commits the current transaction (or, if a savepoint is open, releases + /// the current savepoint). /// - /// :param: statements Statements to run in the transaction. + /// :param: all Only applicable if a savepoint is open. If true, commits all + /// open savepoints, otherwise releases the current savepoint. + /// (Default: false.) /// - /// :returns: The last statement executed, successful or not. - public func transaction(mode: TransactionMode, _ statements: (@autoclosure () -> Statement)...) -> Statement { - return transaction(mode, statements) + /// :returns: The COMMIT (or RELEASE) statement. + public func commit(all: Bool = false) -> Statement { + if !savepointStack.isEmpty && !all { + return release() + } + savepointStack.removeAll() + return run(TransactionResult.Commit.rawValue) } - /// Runs a series of statements in a transaction. The first statement to - /// fail will short-circuit the rest and roll back the changes. A successful - /// transaction will automatically be committed. - /// - /// :param: mode The mode in which the transaction will acquire a - /// lock. + /// Rolls back the current transaction (or, if a savepoint is open, the + /// current savepoint). /// - /// :param: statements Statements to run in the transaction. + /// :param: all Only applicable if a savepoint is open. If true, rolls back + /// all open savepoints, otherwise rolls back the current + /// savepoint. (Default: false.) /// - /// :returns: The last statement executed, successful or not. - public func transaction(mode: TransactionMode, _ statements: [@autoclosure () -> Statement]) -> Statement { - var statement: Statement! - let result = transaction(mode) { transaction in - statement = reduce(statements, transaction, &&) - return statement.failed ? .Rollback : .Commit + /// :returns: The ROLLBACK statement. + public func rollback(all: Bool = false) -> Statement { + if !savepointStack.isEmpty && !all { + return rollback(savepointStack.removeLast()) } - return statement && result + savepointStack.removeAll() + return run(TransactionResult.Rollback.rawValue) } - public func transaction(_ mode: TransactionMode = .Deferred, block: Statement -> TransactionResult) -> Statement { - return run(block(run("BEGIN \(mode.rawValue) TRANSACTION")).rawValue) + // MARK: - Savepoints + + /// The result of a savepoint. + public enum SavepointResult { + + /// Releases a savepoint. + case Release + + /// Rolls a savepoint back. + case Rollback + } - // MARK: - Savepoints + private var savepointStack = [String]() - private var saveName = 0 + private var uuid: String { return NSUUID().UUIDString } - /// Runs a series of statements in a new savepoint. The first statement to - /// fail will short-circuit the rest and roll back the changes. A successful - /// savepoint will automatically be committed. + /// Starts a new transaction with the given savepoint name. /// - /// :param: statements Statements to run in the savepoint. + /// :param: savepointName A unique identifier for the savepoint. /// - /// :returns: The last statement executed, successful or not. - public func savepoint(statements: (@autoclosure () -> Statement)...) -> Statement { - return savepoint(statements) + /// :returns: The SAVEPOINT statement. + public func savepoint(_ savepointName: String? = nil) -> Statement { + let name = savepointName ?? uuid + savepointStack.append(name) + return run("SAVEPOINT \(quote(literal: name))") } - /// Runs a series of statements in a new savepoint. The first statement to - /// fail will short-circuit the rest and roll back the changes. A successful - /// savepoint will automatically be committed. + /// Runs a transaction with the given savepoint name (if omitted, it will + /// generate a UUID). + /// + /// :param: savepointName A unique identifier for the savepoint (optional). /// - /// :param: statements Statements to run in the savepoint. + /// :param: block A closure to run SQL statements within the + /// transaction. Should return a SavepointResult + /// depending on success or failure. /// - /// :returns: The last statement executed, successful or not. - public func savepoint(statements: [@autoclosure () -> Statement]) -> Statement { - let transaction = savepoint("\(++saveName)", statements) - --saveName - return transaction + /// :returns: The RELEASE or ROLLBACK statement. + public func savepoint(_ savepointName: String? = nil, _ block: Statement -> SavepointResult) -> Statement { + switch block(savepoint(savepointName)) { + case .Release: + return release() + case .Rollback: + return rollback() + } } - /// Runs a series of statements in a new savepoint. The first statement to - /// fail will short-circuit the rest and roll back the changes. A successful - /// savepoint will automatically be committed. + /// Releases a savepoint with the given savepoint name (or the most + /// recently-opened savepoint). /// - /// :param: name The name of the savepoint. + /// :param: savepointName A unique identifier for the savepoint (optional). /// - /// :param: statements Statements to run in the savepoint. - /// - /// :returns: The last statement executed, successful or not. - public func savepoint(name: String, _ statements: (@autoclosure () -> Statement)...) -> Statement { - return savepoint(name, statements) + /// :returns: The RELEASE SAVEPOINT statement. + public func release(_ savepointName: String? = nil) -> Statement { + let name = savepointName ?? savepointStack.removeLast() + if let idx = find(savepointStack, name) { savepointStack.removeRange(idx.. Statement]) -> Statement { - let quotedName = quote(literal: name) - var savepoint = run("SAVEPOINT \(quotedName)") - // FIXME: rdar://15217242 // for statement in statements { savepoint = savepoint && statement() } - for idx in 0.. Statement { + if let idx = find(savepointStack, savepointName) { savepointStack.removeRange(idx.. Bool)?) { if let callback = callback { - try(SQLiteBusyHandler(handle) { callback(Int($0)) ? 1 : 0 }) + try { SQLiteBusyHandler(self.handle) { callback(Int($0)) ? 1 : 0 } } } else { - try(SQLiteBusyHandler(handle, nil)) + try { SQLiteBusyHandler(self.handle, nil) } } } @@ -401,41 +411,43 @@ public final class Database { /// SQL values mapped to the function's parameters and /// should return a raw SQL value (or nil). public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { - try(SQLiteCreateFunction(handle, function, Int32(argc), deterministic ? 1 : 0) { context, argc, argv in - let arguments: [Binding?] = map(0.. ComparisonResult) { - try(SQLiteCreateCollation(handle, collation) { lhs, rhs in - return Int32(block(String.fromCString(lhs)!, String.fromCString(rhs)!).rawValue) - }) + try { + SQLiteCreateCollation(self.handle, collation) { lhs, rhs in + return Int32(block(String.fromCString(lhs)!, String.fromCString(rhs)!).rawValue) + } + } } // MARK: - Error Handling @@ -460,7 +474,7 @@ public final class Database { return String.fromCString(sqlite3_errmsg(handle))! } - internal func try(block: @autoclosure () -> Int32) { + internal func try(block: () -> Int32) { perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError)") } } } diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index eee23edb..a01685b0 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -157,8 +157,7 @@ extension Blob: Expressible { extension Bool: Expressible { public var expression: Expression<()> { - // FIXME: rdar://TODO segfaults during archive // return Expression(value: self) - return Expression(binding: datatypeValue) + return Expression(value: self) } } @@ -557,23 +556,19 @@ public func && (lhs: Expression, rhs: Expression) -> Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -// FIXME: rdar://TODO segfaults during archive // ... Expression(value: lhs) -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs.datatypeValue) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs.datatypeValue) } -// FIXME: rdar://TODO segfaults during archive // ... Expression(value: rhs) -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) && rhs } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) && rhs } +public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } +public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } +public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } +public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -// FIXME: rdar://TODO segfaults during archive // ... Expression(value: lhs) -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs.datatypeValue) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs.datatypeValue) } -// FIXME: rdar://TODO segfaults during archive // ... Expression(value: rhs) -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) || rhs } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) || rhs } +public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } +public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } +public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } +public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } @@ -607,8 +602,8 @@ public func ?? (expression: Expression, defaultValue: Expres return ifnull(expression, defaultValue) } -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } +public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } @@ -623,7 +618,7 @@ public func ltrim(expression: Expression, characters: String) -> Expres return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) } -public var random: Expression { return wrap(__FUNCTION__, Expression<()>()) } +public func random() -> Expression { return Expression(literal: __FUNCTION__) } public func replace(expression: Expression, match: String, subtitute: String) -> Expression { return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, match, subtitute])) @@ -772,11 +767,11 @@ private func wrapDistinct(function: String, expression: Expression) -> public typealias Star = (Expression?, Expression?) -> Expression<()> public func * (Expression?, Expression?) -> Expression<()> { - return Expression<()>(literal: "*") + return Expression(literal: "*") } public func contains(values: C, column: Expression) -> Expression { - let templates = join(", ", [String](count: countElements(values), repeatedValue: "?")) + let templates = join(", ", [String](count: count(values), repeatedValue: "?")) return infix("IN", column, Expression(literal: "(\(templates))", map(values) { $0.datatypeValue })) } public func contains(values: C, column: Expression) -> Expression { @@ -928,22 +923,10 @@ public func ^= (column: Expression, valu public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } -public postfix func ++ (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column += 1 - return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) -} -public postfix func ++ (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column += 1 - return (column, Expression(literal: "(\(column.SQL) + 1)", column.bindings)) -} -public postfix func -- (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column -= 1 - return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) -} -public postfix func -- (column: Expression) -> Setter { - // rdar://18825175 segfaults during archive: // column -= 1 - return (column, Expression(literal: "(\(column.SQL) - 1)", column.bindings)) -} +public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } +public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } +public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } +public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } // MARK: - Internal diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift index 9d19127c..c74dd9d0 100644 --- a/SQLite/Functions.swift +++ b/SQLite/Functions.swift @@ -174,5 +174,5 @@ public extension Database { } private func asValue(value: Binding?) -> A { - return A.fromDatatypeValue(value as A.Datatype) as A + return A.fromDatatypeValue(value as! A.Datatype) as! A } diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 752f697f..21cd6e0c 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -89,13 +89,12 @@ public struct Query { return query } - // rdar://18778670 causes select(distinct: *) to make select(*) ambiguous /// Sets the SELECT clause on the query. /// /// :param: star A literal *. /// /// :returns: A query with SELECT * applied. - public func select(all star: Star) -> Query { + public func select(star: Star) -> Query { var query = self (query.distinct, query.columns) = (false, nil) return query @@ -716,16 +715,10 @@ public struct Query { } private func calculate(expression: Expression) -> V? { - if let scalar = select(expression).selectStatement.scalar() as? V.Datatype { - return (V.fromDatatypeValue(scalar) as V) - } - return nil + return (select(expression).selectStatement.scalar() as? V.Datatype).map(V.fromDatatypeValue) as? V } private func calculate(expression: Expression) -> V? { - if let scalar = select(expression).selectStatement.scalar() as? V.Datatype { - return (V.fromDatatypeValue(scalar) as V) - } - return nil + return (select(expression).selectStatement.scalar() as? V.Datatype).map(V.fromDatatypeValue) as? V } // MARK: - Array @@ -775,7 +768,7 @@ public struct Row { } public func get(column: Expression) -> V? { func valueAtIndex(idx: Int) -> V? { - if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as V) } + if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } return nil } @@ -828,7 +821,8 @@ public struct QueryGenerator: GeneratorType { private lazy var columnNames: [String: Int] = { var (columnNames, idx) = ([String: Int](), 0) column: for each in self.query.columns ?? [Expression<()>(literal: "*")] { - let pair = split(each.expression.SQL) { $0 == "." } + // FIXME: rdar://19769314 // split(each.expression.SQL) { $0 == "." } + let pair = split(each.expression.SQL, { $0 == "." }) let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) func expandGlob(namespace: Bool) -> Query -> () { @@ -842,7 +836,7 @@ public struct QueryGenerator: GeneratorType { } if column == "*" { - let tables = [self.query.select(all: *)] + self.query.joins.map { $0.table } + let tables = [self.query.select(*)] + self.query.joins.map { $0.table } if let tableName = tableName { for table in tables { if table.tableName.SQL == tableName { diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 3426087d..20d149e1 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -28,7 +28,7 @@ internal let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPatter /// A single SQL statement. public final class Statement { - private let handle: COpaquePointer = nil + private var handle = COpaquePointer.null() private let database: Database @@ -36,7 +36,7 @@ public final class Statement { internal init(_ database: Database, _ SQL: String) { self.database = database - database.try(sqlite3_prepare_v2(database.handle, SQL, -1, &handle, nil)) + database.try { sqlite3_prepare_v2(database.handle, SQL, -1, &self.handle, nil) } } deinit { sqlite3_finalize(handle) } @@ -89,15 +89,15 @@ public final class Statement { private func bind(value: Binding?, atIndex idx: Int) { if value == nil { - try(sqlite3_bind_null(self.handle, Int32(idx))) + try { sqlite3_bind_null(self.handle, Int32(idx)) } } else if let value = value as? Blob { - try(sqlite3_bind_blob(self.handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT)) + try { sqlite3_bind_blob(self.handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) } } else if let value = value as? Double { - try(sqlite3_bind_double(self.handle, Int32(idx), value)) + try { sqlite3_bind_double(self.handle, Int32(idx), value) } } else if let value = value as? Int64 { - try(sqlite3_bind_int64(self.handle, Int32(idx), value)) + try { sqlite3_bind_int64(self.handle, Int32(idx), value) } } else if let value = value as? String { - try(sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT)) + try { sqlite3_bind_text(self.handle, Int32(idx), value, -1, SQLITE_TRANSIENT) } } else if let value = value as? Bool { bind(value.datatypeValue, atIndex: idx) } else if let value = value as? Int { @@ -164,7 +164,7 @@ public final class Statement { // MARK: - public func step() -> Bool { - try(sqlite3_step(handle)) + try { sqlite3_step(self.handle) } return status == SQLITE_ROW } @@ -186,7 +186,7 @@ public final class Statement { private var status: Int32 = SQLITE_OK - private func try(block: @autoclosure () -> Int32) { + private func try(block: () -> Int32) { if failed { return } database.perform { self.status = block() @@ -228,12 +228,12 @@ extension Statement: Printable { } -public func && (lhs: Statement, rhs: @autoclosure () -> Statement) -> Statement { +public func && (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { if lhs.status == SQLITE_OK { lhs.run() } return lhs.failed ? lhs : rhs() } -public func || (lhs: Statement, rhs: @autoclosure () -> Statement) -> Statement { +public func || (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { if lhs.status == SQLITE_OK { lhs.run() } return lhs.failed ? rhs() : lhs } diff --git a/SQLite/Value.swift b/SQLite/Value.swift index 256d7e26..af4383a8 100644 --- a/SQLite/Value.swift +++ b/SQLite/Value.swift @@ -39,9 +39,9 @@ public protocol Value { typealias Datatype: Binding - class var declaredDatatype: String { get } + static var declaredDatatype: String { get } - class func fromDatatypeValue(datatypeValue: Datatype) -> ValueType + static func fromDatatypeValue(datatypeValue: Datatype) -> ValueType var datatypeValue: Datatype { get } diff --git a/SQLiteCipher Tests/CipherTests.swift b/SQLiteCipher Tests/CipherTests.swift index 381a4a7d..d96d20af 100644 --- a/SQLiteCipher Tests/CipherTests.swift +++ b/SQLiteCipher Tests/CipherTests.swift @@ -1,16 +1,12 @@ import XCTest import SQLite -class CipherTests: XCTestCase { - - let db = Database() - - var users: Query { return db["users"] } +class CipherTests: SQLiteTestCase { override func setUp() { db.key("hello") - CreateUsersTable(db) - InsertUser(db, "alice") + createUsersTable() + insertUser("alice") super.setUp() } diff --git a/SQLiteCipher/Cipher.swift b/SQLiteCipher/Cipher.swift index 8ab21f10..3fd2f60c 100644 --- a/SQLiteCipher/Cipher.swift +++ b/SQLiteCipher/Cipher.swift @@ -25,11 +25,11 @@ extension Database { public func key(key: String) { - try(sqlite3_key(handle, key, Int32(countElements(key.utf8)))) + try { sqlite3_key(self.handle, key, Int32(count(key.utf8))) } } public func rekey(key: String) { - try(sqlite3_rekey(handle, key, Int32(countElements(key.utf8)))) + try { sqlite3_rekey(self.handle, key, Int32(count(key.utf8))) } } } From ae868916ea619b71db3693a8189e41c3e3e41d76 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 23 Feb 2015 11:12:55 -0800 Subject: [PATCH 0220/1046] Swift 1.2 beta 2 Signed-off-by: Stephen Celis --- SQLite Tests/StatementTests.swift | 2 +- SQLite/Database.swift | 2 +- SQLite/Functions.swift | 6 +++++- SQLite/Query.swift | 5 ++--- SQLite/Statement.swift | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index 3bcd96d0..5370aedc 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -154,7 +154,7 @@ func withBlob(block: Blob -> ()) { let length = 1 let buflen = Int(length) + 1 let buffer = UnsafeMutablePointer<()>.alloc(buflen) - memcpy(buffer, "4", UInt(length)) + memcpy(buffer, "4", length) block(Blob(bytes: buffer, length: length)) buffer.dealloc(buflen) } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index ad6d93da..6b96a3e5 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -27,7 +27,7 @@ import Foundation /// A connection (handle) to a SQLite database. public final class Database { - internal var handle = COpaquePointer.null() + internal var handle: COpaquePointer = nil /// Whether or not the database was opened in a read-only state. public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift index c74dd9d0..d4cfbd0b 100644 --- a/SQLite/Functions.swift +++ b/SQLite/Functions.swift @@ -173,6 +173,10 @@ public extension Database { } -private func asValue(value: Binding?) -> A { +private func asValue(value: Binding) -> A { return A.fromDatatypeValue(value as! A.Datatype) as! A } + +private func asValue(value: Binding?) -> A { + return asValue(value!) +} diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 21cd6e0c..007dca0c 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -768,7 +768,7 @@ public struct Row { } public func get(column: Expression) -> V? { func valueAtIndex(idx: Int) -> V? { - if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } + if let value = self.values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } return nil } @@ -821,8 +821,7 @@ public struct QueryGenerator: GeneratorType { private lazy var columnNames: [String: Int] = { var (columnNames, idx) = ([String: Int](), 0) column: for each in self.query.columns ?? [Expression<()>(literal: "*")] { - // FIXME: rdar://19769314 // split(each.expression.SQL) { $0 == "." } - let pair = split(each.expression.SQL, { $0 == "." }) + let pair = split(each.expression.SQL) { $0 == "." } let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) func expandGlob(namespace: Bool) -> Query -> () { diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 20d149e1..0a190f56 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -28,7 +28,7 @@ internal let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPatter /// A single SQL statement. public final class Statement { - private var handle = COpaquePointer.null() + private var handle: COpaquePointer = nil private let database: Database From 440579e36d5ed83d0f9c103af7ae0c29c268c230 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Feb 2015 09:04:01 -0800 Subject: [PATCH 0221/1046] Enable -whole-module-optimization A feature of Swift 1.2. Slows builds down when the framework first builds and whenever it changes, but performs significantly faster. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 4 ++++ SQLite/SQLite.xcconfig | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index dced7302..0151830b 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -679,6 +679,7 @@ ); INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Debug; }; @@ -688,6 +689,7 @@ buildSettings = { INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Release; }; @@ -744,6 +746,7 @@ ); INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "SQLiteCipher Tests"; + SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Debug; }; @@ -753,6 +756,7 @@ buildSettings = { INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "SQLiteCipher Tests"; + SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Release; }; diff --git a/SQLite/SQLite.xcconfig b/SQLite/SQLite.xcconfig index 8880c751..5e02dd58 100644 --- a/SQLite/SQLite.xcconfig +++ b/SQLite/SQLite.xcconfig @@ -1,3 +1,7 @@ +SWIFT_WHOLE_MODULE_OPTIMIZATION = YES + +// Universal Framework + SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx VALID_ARCHS[sdk=iphone*] = arm64 armv7 armv7s From b1b27d5db5954a076c3c7c4f4dcb5658458a9e0c Mon Sep 17 00:00:00 2001 From: Sam Sherar Date: Fri, 27 Feb 2015 23:04:37 +0000 Subject: [PATCH 0222/1046] Updating documentation for forced downcast --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4edfb708..b6b42cf9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -136,7 +136,7 @@ On iOS, you can create a writable database in your app’s **Documents** directo ``` swift let path = NSSearchPathForDirectoriesInDomains( .DocumentDirectory, .UserDomainMask, true -).first as String +).first as! String let db = Database("\(path)/db.sqlite3") ``` @@ -146,7 +146,7 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( .ApplicationSupportDirectory, .UserDomainMask, true -).first as String + NSBundle.mainBundle().bundleIdentifier! +).first as! String + NSBundle.mainBundle().bundleIdentifier! // create parent directory iff it doesn’t exist NSFileManager.defaultManager().createDirectoryAtPath( From 5ac56d78c0fb8b3521e5058dc1fe1281ca1de100 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 15 Mar 2015 11:32:03 -0400 Subject: [PATCH 0223/1046] Swift 1.2 beta 3 Nested functions no longer require `self`. Signed-off-by: Stephen Celis --- SQLite/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 007dca0c..51a0acda 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -768,7 +768,7 @@ public struct Row { } public func get(column: Expression) -> V? { func valueAtIndex(idx: Int) -> V? { - if let value = self.values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } + if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } return nil } From a06a550ddd81c2be72e1114cfd770532b481ffa3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 11 Mar 2015 08:38:39 -0700 Subject: [PATCH 0224/1046] Custom Tokenizers This brings preliminary support for custom FTS tokenizers, registered using the following incantation: db.register(tokenizer: "name") { input in // ... extract first token and range here return (token, range) // return normalized token and range } Signed-off-by: Stephen Celis --- SQLite Tests/FTSTests.swift | 26 +++++ SQLite.xcodeproj/project.pbxproj | 30 +++-- SQLite/FTS.swift | 46 +++++++- SQLite/SQLite-Bridging.c | 81 ------------- SQLite/SQLite-Bridging.h | 7 ++ SQLite/SQLite-Bridging.m | 194 +++++++++++++++++++++++++++++++ Vendor/fts3_tokenizer.h | 161 +++++++++++++++++++++++++ 7 files changed, 452 insertions(+), 93 deletions(-) delete mode 100644 SQLite/SQLite-Bridging.c create mode 100644 SQLite/SQLite-Bridging.m create mode 100644 Vendor/fts3_tokenizer.h diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift index 972a2660..d3208c14 100644 --- a/SQLite Tests/FTSTests.swift +++ b/SQLite Tests/FTSTests.swift @@ -32,4 +32,30 @@ class FTSTests: SQLiteTestCase { AssertSQL("SELECT * FROM \"emails\" WHERE (\"emails\" MATCH 'hello')", emails.filter(match("hello", emails))) } + func test_registerTokenizer_registersTokenizer() { + let locale = CFLocaleCopyCurrent() + let tokenizer = CFStringTokenizerCreate(nil, "", CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + + db.register(tokenizer: "tokenizer") { string in + CFStringTokenizerSetString(tokenizer, string, CFRangeMake(0, CFStringGetLength(string))) + if CFStringTokenizerAdvanceToNextToken(tokenizer) == .None { + return nil + } + let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) + let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string, range) + var token = CFStringCreateMutableCopy(nil, range.length, input) + CFStringLowercase(token, locale) + CFStringTransform(token, nil, kCFStringTransformStripDiacritics, 0) + return (token as String, string.rangeOfString(input as String)!) + } + + db.create(vtable: emails, using: fts4([subject, body], tokenize: .Custom("tokenizer"))) + + AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" 'tokenizer')") + + emails.insert(subject <- "Aún más cáfe!")! + + XCTAssertEqual(1, emails.filter(match("aun", emails)).count) + } + } diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 0151830b..c33e6f00 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 30C4A0551A8F5ADC00A6F5E8 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; DC0FA83219D87CA3009F3A35 /* SQLite-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.c in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.c */; }; + DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */; }; DC0FA83919D87E0C009F3A35 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Private, ); }; }; DC109CE11A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; @@ -20,6 +20,8 @@ DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC475E9E19F2199900788FBD /* ExpressionTests.swift */; }; DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; + DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; + DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; @@ -34,7 +36,7 @@ DCC6B3721A9191C300734B78 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; DCC6B3731A9191C300734B78 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; DCC6B3741A9191C300734B78 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; - DCC6B3751A9191C300734B78 /* SQLite-Bridging.c in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.c */; }; + DCC6B3751A9191C300734B78 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */; }; DCC6B3771A9191C300734B78 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; DCC6B3791A9191C300734B78 /* SQLite.swift.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.swift.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC6B37A1A9191C300734B78 /* SQLite-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -85,7 +87,7 @@ /* Begin PBXFileReference section */ DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging-Header.h"; sourceTree = ""; }; - DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "SQLite-Bridging.c"; sourceTree = ""; }; + DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; DC109CE01A0C4D970070988E /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Schema.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; @@ -95,15 +97,16 @@ DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DC37740419C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; - DC37743419C8D626004FCF85 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; + DC37743419C8D626004FCF85 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Database.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DC37743719C8D693004FCF85 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Value.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37743A19C8D6C0004FCF85 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; + DC37743A19C8D6C0004FCF85 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DC37744219C8DC91004FCF85 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; DC37744719C8F50B004FCF85 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; DC3F170F1A8127A300C83A2F /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; DC3F17121A814F7000C83A2F /* FunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionsTests.swift; sourceTree = ""; }; DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; DCAAE66D19D8A71B00158FEF /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; @@ -176,7 +179,7 @@ children = ( DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */, DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */, - DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.c */, + DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */, ); name = Bridging; sourceTree = ""; @@ -259,9 +262,18 @@ name = "Supporting Files"; sourceTree = ""; }; + DC9D38991AAD457000780AE7 /* ext */ = { + isa = PBXGroup; + children = ( + DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */, + ); + name = ext; + sourceTree = ""; + }; DCC6B3951A91936500734B78 /* Vendor */ = { isa = PBXGroup; children = ( + DC9D38991AAD457000780AE7 /* ext */, DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */, ); path = Vendor; @@ -298,6 +310,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */, DC3773F919C8CBB3004FCF85 /* SQLite.swift.h in Headers */, DC0FA83219D87CA3009F3A35 /* SQLite-Bridging-Header.h in Headers */, DC0FA83919D87E0C009F3A35 /* SQLite-Bridging.h in Headers */, @@ -308,6 +321,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */, DCC6B3791A9191C300734B78 /* SQLite.swift.h in Headers */, DCC6B37A1A9191C300734B78 /* SQLite-Bridging-Header.h in Headers */, DCC6B37B1A9191C300734B78 /* SQLite-Bridging.h in Headers */, @@ -488,7 +502,7 @@ DCAD429719E2E0F1004A51DF /* Query.swift in Sources */, DC109CE11A0C4D970070988E /* Schema.swift in Sources */, DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, - DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.c in Sources */, + DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -520,7 +534,7 @@ DCC6B3741A9191C300734B78 /* Schema.swift in Sources */, DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */, DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */, - DCC6B3751A9191C300734B78 /* SQLite-Bridging.c in Sources */, + DCC6B3751A9191C300734B78 /* SQLite-Bridging.m in Sources */, DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */, DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */, ); diff --git a/SQLite/FTS.swift b/SQLite/FTS.swift index 25e577dd..8753409c 100644 --- a/SQLite/FTS.swift +++ b/SQLite/FTS.swift @@ -29,7 +29,7 @@ public func fts4(columns: Expression...) -> Expression<()> { // TODO: matchinfo, compress, uncompress public func fts4(columns: [Expression], tokenize tokenizer: Tokenizer? = nil) -> Expression<()> { var options = [String: String]() - options["tokenize"] = tokenizer?.rawValue + options["tokenize"] = tokenizer?.description return fts("fts4", columns, options) } @@ -41,14 +41,52 @@ private func fts(function: String, columns: [Expression], options: [Stri return wrap(function, Expression<()>.join(", ", definitions)) } -public enum Tokenizer: String { +public enum Tokenizer { - case Simple = "simple" + internal static var moduleName = "SQLite.swift" - case Porter = "porter" + case Simple + + case Porter + + case Custom(String) + +} + +extension Tokenizer: Printable { + + public var description: String { + switch self { + case .Simple: + return "simple" + case .Porter: + return "porter" + case .Custom(let tokenizer): + return "\(quote(identifier: Tokenizer.moduleName)) \(quote(literal: tokenizer))" + } + } } public func match(string: String, expression: Query) -> Expression { return infix("MATCH", Expression(expression.tableName), Expression(binding: string)) } + +extension Database { + + public func register(tokenizer submoduleName: String, next: String -> (String, Range)?) { + try { + SQLiteRegisterTokenizer(self.handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + let string = String.fromCString(input)! + if var (token, range) = next(string) { + let view = string.utf8 + offset.memory += count(string.substringToIndex(range.startIndex).utf8) + length.memory = Int32(distance(range.startIndex.samePositionIn(view), range.endIndex.samePositionIn(view))) + return token + } + return nil + } + } + } + +} diff --git a/SQLite/SQLite-Bridging.c b/SQLite/SQLite-Bridging.c deleted file mode 100644 index eccfa431..00000000 --- a/SQLite/SQLite-Bridging.c +++ /dev/null @@ -1,81 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -#include "SQLite-Bridging.h" - -#include - -int _SQLiteBusyHandler(void * context, int tries) { - return ((SQLiteBusyHandlerCallback)context)(tries); -} - -int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback) { - if (callback) { - return sqlite3_busy_handler(handle, _SQLiteBusyHandler, Block_copy(callback)); // FIXME: leak - } else { - return sqlite3_busy_handler(handle, 0, 0); - } -} - -void _SQLiteTrace(void * context, const char * SQL) { - ((SQLiteTraceCallback)context)(SQL); -} - -void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback) { - if (callback) { - sqlite3_trace(handle, _SQLiteTrace, Block_copy(callback)); // FIXME: leak - } else { - sqlite3_trace(handle, 0, 0); - } -} - -void _SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { - ((SQLiteCreateFunctionCallback)sqlite3_user_data(context))(context, argc, argv); -} - -int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback) { - if (callback) { - int flags = SQLITE_UTF8; - if (deterministic) { -#ifdef SQLITE_DETERMINISTIC - flags |= SQLITE_DETERMINISTIC; -#endif - } - return sqlite3_create_function_v2(handle, name, argc, flags, Block_copy(callback), &_SQLiteCreateFunction, 0, 0, 0); // FIXME: leak - } else { - return sqlite3_create_function_v2(handle, name, 0, 0, 0, 0, 0, 0, 0); - } -} - -int _SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { - return ((SQLiteCreateCollationCallback)context)(lhs, rhs); -} - -int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback) { - if (callback) { - return sqlite3_create_collation_v2(handle, name, SQLITE_UTF8, Block_copy(callback), &_SQLiteCreateCollation, 0); // FIXME: leak - } else { - return sqlite3_create_collation_v2(handle, name, 0, 0, 0, 0); - } -} diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index 283254e8..1239c38a 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -22,8 +22,12 @@ // THE SOFTWARE. // +@import Foundation; + #include +#include "fts3_tokenizer.h" + typedef int (^SQLiteBusyHandlerCallback)(int times); int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback); @@ -35,3 +39,6 @@ int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int dete typedef int (^SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback); + +typedef NSString * (^SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int SQLiteRegisterTokenizer(sqlite3 * db, const char * module, const char * tokenizer, SQLiteTokenizerNextCallback callback); diff --git a/SQLite/SQLite-Bridging.m b/SQLite/SQLite-Bridging.m new file mode 100644 index 00000000..ac4e5a02 --- /dev/null +++ b/SQLite/SQLite-Bridging.m @@ -0,0 +1,194 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright (c) 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "SQLite-Bridging.h" + +@import Foundation; + +static int _SQLiteBusyHandler(void * context, int tries) { + return ((__bridge SQLiteBusyHandlerCallback)context)(tries); +} + +int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback) { + if (callback) { + return sqlite3_busy_handler(handle, _SQLiteBusyHandler, (__bridge_retained void *)callback); // FIXME: leak + } else { + return sqlite3_busy_handler(handle, 0, 0); + } +} + +static void _SQLiteTrace(void * context, const char * SQL) { + ((__bridge SQLiteTraceCallback)context)(SQL); +} + +void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback) { + if (callback) { + sqlite3_trace(handle, _SQLiteTrace, (__bridge_retained void *)callback); // FIXME: leak + } else { + sqlite3_trace(handle, 0, 0); + } +} + +static void _SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { + ((__bridge SQLiteCreateFunctionCallback)sqlite3_user_data(context))(context, argc, argv); +} + +int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback) { + if (callback) { + int flags = SQLITE_UTF8; + if (deterministic) { +#ifdef SQLITE_DETERMINISTIC + flags |= SQLITE_DETERMINISTIC; +#endif + } + return sqlite3_create_function_v2(handle, name, -1, flags, (__bridge_retained void *)callback, &_SQLiteCreateFunction, 0, 0, 0); // FIXME: leak + } else { + return sqlite3_create_function_v2(handle, name, 0, 0, 0, 0, 0, 0, 0); + } +} + +static int _SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { + return ((__bridge SQLiteCreateCollationCallback)context)(lhs, rhs); +} + +int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback) { + if (callback) { + return sqlite3_create_collation_v2(handle, name, SQLITE_UTF8, (__bridge_retained void *)callback, &_SQLiteCreateCollation, 0); // FIXME: leak + } else { + return sqlite3_create_collation_v2(handle, name, 0, 0, 0, 0); + } +} + +#pragma mark - FTS + +typedef struct _SQLiteTokenizer { + sqlite3_tokenizer base; + __unsafe_unretained SQLiteTokenizerNextCallback callback; +} _SQLiteTokenizer; + +typedef struct _SQLiteTokenizerCursor { + void * base; + const char * input; + int inputOffset; + int inputLength; + int idx; +} _SQLiteTokenizerCursor; + + +static NSMutableDictionary * _SQLiteTokenizerMap; + +static int _SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_tokenizer ** ppTokenizer) { + _SQLiteTokenizer * tokenizer = (_SQLiteTokenizer *)sqlite3_malloc(sizeof(_SQLiteTokenizer)); + if (!tokenizer) { + return SQLITE_NOMEM; + } + memset(tokenizer, 0, sizeof(* tokenizer)); // FIXME: needed? + + NSString * key = [NSString stringWithUTF8String:argv[0]]; + tokenizer->callback = [_SQLiteTokenizerMap objectForKey:key]; + if (!tokenizer->callback) { + return SQLITE_ERROR; + } + + *ppTokenizer = &tokenizer->base; + return SQLITE_OK; +} + +static int _SQLiteTokenizerDestroy(sqlite3_tokenizer * pTokenizer) { + sqlite3_free(pTokenizer); + return SQLITE_OK; +} + +static int _SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pInput, int nBytes, sqlite3_tokenizer_cursor ** ppCursor) { + _SQLiteTokenizerCursor * cursor = (_SQLiteTokenizerCursor *)sqlite3_malloc(sizeof(_SQLiteTokenizerCursor)); + if (!cursor) { + return SQLITE_NOMEM; + } + + cursor->input = pInput; + cursor->inputOffset = 0; + cursor->inputLength = 0; + cursor->idx = 0; + + *ppCursor = (sqlite3_tokenizer_cursor *)cursor; + return SQLITE_OK; +} + +static int _SQLiteTokenizerClose(sqlite3_tokenizer_cursor * pCursor) { + sqlite3_free(pCursor); + return SQLITE_OK; +} + +static int _SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char ** ppToken, int * pnBytes, int * piStartOffset, int * piEndOffset, int * piPosition) { + _SQLiteTokenizerCursor * cursor = (_SQLiteTokenizerCursor *)pCursor; + _SQLiteTokenizer * tokenizer = (_SQLiteTokenizer *)cursor->base; + + cursor->inputOffset += cursor->inputLength; + const char * input = cursor->input + cursor->inputOffset; + const char * token = [tokenizer->callback(input, &cursor->inputOffset, &cursor->inputLength) cStringUsingEncoding:NSUTF8StringEncoding]; + if (!token) { + return SQLITE_DONE; + } + + *ppToken = token; + *pnBytes = (int)strlen(token); + *piStartOffset = cursor->inputOffset; + *piEndOffset = cursor->inputOffset + cursor->inputLength; + *piPosition = cursor->idx++; + return SQLITE_OK; +} + +static const sqlite3_tokenizer_module _SQLiteTokenizerModule = { + 0, + _SQLiteTokenizerCreate, + _SQLiteTokenizerDestroy, + _SQLiteTokenizerOpen, + _SQLiteTokenizerClose, + _SQLiteTokenizerNext +}; + +int SQLiteRegisterTokenizer(sqlite3 * db, const char * moduleName, const char * submoduleName, SQLiteTokenizerNextCallback callback) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _SQLiteTokenizerMap = [NSMutableDictionary new]; + }); + + sqlite3_stmt * stmt; + int status = sqlite3_prepare_v2(db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); + if (status != SQLITE_OK ){ + return status; + } + const sqlite3_tokenizer_module * pModule = &_SQLiteTokenizerModule; + sqlite3_bind_text(stmt, 1, moduleName, -1, SQLITE_STATIC); + sqlite3_bind_blob(stmt, 2, &pModule, sizeof(pModule), SQLITE_STATIC); + sqlite3_step(stmt); + status = sqlite3_finalize(stmt); + if (status != SQLITE_OK ){ + return status; + } + + [_SQLiteTokenizerMap setObject:[callback copy] forKey:[NSString stringWithUTF8String:submoduleName]]; + + return SQLITE_OK; +} diff --git a/Vendor/fts3_tokenizer.h b/Vendor/fts3_tokenizer.h new file mode 100644 index 00000000..4a40b2b3 --- /dev/null +++ b/Vendor/fts3_tokenizer.h @@ -0,0 +1,161 @@ +/* +** 2006 July 10 +** +** The author disclaims copyright to this source code. +** +************************************************************************* +** Defines the interface to tokenizers used by fulltext-search. There +** are three basic components: +** +** sqlite3_tokenizer_module is a singleton defining the tokenizer +** interface functions. This is essentially the class structure for +** tokenizers. +** +** sqlite3_tokenizer is used to define a particular tokenizer, perhaps +** including customization information defined at creation time. +** +** sqlite3_tokenizer_cursor is generated by a tokenizer to generate +** tokens from a particular input. +*/ +#ifndef _FTS3_TOKENIZER_H_ +#define _FTS3_TOKENIZER_H_ + +/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. +** If tokenizers are to be allowed to call sqlite3_*() functions, then +** we will need a way to register the API consistently. +*/ +#include "sqlite3.h" + +/* +** Structures used by the tokenizer interface. When a new tokenizer +** implementation is registered, the caller provides a pointer to +** an sqlite3_tokenizer_module containing pointers to the callback +** functions that make up an implementation. +** +** When an fts3 table is created, it passes any arguments passed to +** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the +** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer +** implementation. The xCreate() function in turn returns an +** sqlite3_tokenizer structure representing the specific tokenizer to +** be used for the fts3 table (customized by the tokenizer clause arguments). +** +** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() +** method is called. It returns an sqlite3_tokenizer_cursor object +** that may be used to tokenize a specific input buffer based on +** the tokenization rules supplied by a specific sqlite3_tokenizer +** object. +*/ +typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; +typedef struct sqlite3_tokenizer sqlite3_tokenizer; +typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; + +struct sqlite3_tokenizer_module { + + /* + ** Structure version. Should always be set to 0 or 1. + */ + int iVersion; + + /* + ** Create a new tokenizer. The values in the argv[] array are the + ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL + ** TABLE statement that created the fts3 table. For example, if + ** the following SQL is executed: + ** + ** CREATE .. USING fts3( ... , tokenizer arg1 arg2) + ** + ** then argc is set to 2, and the argv[] array contains pointers + ** to the strings "arg1" and "arg2". + ** + ** This method should return either SQLITE_OK (0), or an SQLite error + ** code. If SQLITE_OK is returned, then *ppTokenizer should be set + ** to point at the newly created tokenizer structure. The generic + ** sqlite3_tokenizer.pModule variable should not be initialized by + ** this callback. The caller will do so. + */ + int (*xCreate)( + int argc, /* Size of argv array */ + const char *const*argv, /* Tokenizer argument strings */ + sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ + ); + + /* + ** Destroy an existing tokenizer. The fts3 module calls this method + ** exactly once for each successful call to xCreate(). + */ + int (*xDestroy)(sqlite3_tokenizer *pTokenizer); + + /* + ** Create a tokenizer cursor to tokenize an input buffer. The caller + ** is responsible for ensuring that the input buffer remains valid + ** until the cursor is closed (using the xClose() method). + */ + int (*xOpen)( + sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ + const char *pInput, int nBytes, /* Input buffer */ + sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ + ); + + /* + ** Destroy an existing tokenizer cursor. The fts3 module calls this + ** method exactly once for each successful call to xOpen(). + */ + int (*xClose)(sqlite3_tokenizer_cursor *pCursor); + + /* + ** Retrieve the next token from the tokenizer cursor pCursor. This + ** method should either return SQLITE_OK and set the values of the + ** "OUT" variables identified below, or SQLITE_DONE to indicate that + ** the end of the buffer has been reached, or an SQLite error code. + ** + ** *ppToken should be set to point at a buffer containing the + ** normalized version of the token (i.e. after any case-folding and/or + ** stemming has been performed). *pnBytes should be set to the length + ** of this buffer in bytes. The input text that generated the token is + ** identified by the byte offsets returned in *piStartOffset and + ** *piEndOffset. *piStartOffset should be set to the index of the first + ** byte of the token in the input buffer. *piEndOffset should be set + ** to the index of the first byte just past the end of the token in + ** the input buffer. + ** + ** The buffer *ppToken is set to point at is managed by the tokenizer + ** implementation. It is only required to be valid until the next call + ** to xNext() or xClose(). + */ + /* TODO(shess) current implementation requires pInput to be + ** nul-terminated. This should either be fixed, or pInput/nBytes + ** should be converted to zInput. + */ + int (*xNext)( + sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ + const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ + int *piStartOffset, /* OUT: Byte offset of token in input buffer */ + int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ + int *piPosition /* OUT: Number of tokens returned before this one */ + ); + + /*********************************************************************** + ** Methods below this point are only available if iVersion>=1. + */ + + /* + ** Configure the language id of a tokenizer cursor. + */ + int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid); +}; + +struct sqlite3_tokenizer { + const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ + /* Tokenizer implementations will typically add additional fields */ +}; + +struct sqlite3_tokenizer_cursor { + sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ + /* Tokenizer implementations will typically add additional fields */ +}; + +int fts3_global_term_cnt(int iTerm, int iCol); +int fts3_term_cnt(int iTerm, int iCol); + + +#endif /* _FTS3_TOKENIZER_H_ */ From 628a2893c0de596635d5f215fae3c108df47bbe6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 21 Mar 2015 16:53:00 -0700 Subject: [PATCH 0225/1046] Use an embedded module instead of a bridging header I'd taken advantage of the fact that renaming the umbrella header disabled the framework bridging header check, which is a bug and could stop working at any moment. Instead, let's embed a `sqlite3` framework module that points to the appropriate system header. As soon as the system provides an appropriate `sqlite3` module at the system level, we merely have to delete this internal dependency. Signed-off-by: Stephen Celis --- Documentation/Index.md | 5 +- SQLite.xcodeproj/project.pbxproj | 319 +++++++++++++++++++++------- SQLite/Database.swift | 2 +- SQLite/SQLite-Bridging-Header.h | 1 - SQLite/SQLite-Bridging.h | 5 +- SQLite/SQLite-Bridging.m | 4 +- SQLite/{SQLite.swift.h => SQLite.h} | 2 + SQLite/Statement.swift | 2 + sqlite3/Info.plist | 26 +++ sqlite3/iphoneos.modulemap | 4 + sqlite3/iphonesimulator.modulemap | 4 + sqlite3/macosx.modulemap | 4 + sqlite3/sqlite3.xcconfig | 5 + 13 files changed, 300 insertions(+), 83 deletions(-) delete mode 100644 SQLite/SQLite-Bridging-Header.h rename SQLite/{SQLite.swift.h => SQLite.h} (85%) create mode 100644 sqlite3/Info.plist create mode 100644 sqlite3/iphoneos.modulemap create mode 100644 sqlite3/iphonesimulator.modulemap create mode 100644 sqlite3/macosx.modulemap create mode 100644 sqlite3/sqlite3.xcconfig diff --git a/Documentation/Index.md b/Documentation/Index.md index b6b42cf9..7c25e374 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -102,9 +102,12 @@ It’s possible to use SQLite.swift in a target that doesn’t support framework 2. Copy the SQLite.swift source files (from its **SQLite** directory) into your Xcode project. - 3. Add the following line to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`. + 3. Remove `import sqlite3` (and `@import sqlite3;`) from the SQLite.swift source files that call it. + + 4. Add the following lines to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`. ``` swift + #import #import "SQLite-Bridging.h" ``` diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index c33e6f00..6dddc4eb 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -7,23 +7,33 @@ objects = { /* Begin PBXBuildFile section */ - 30C4A0551A8F5ADC00A6F5E8 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DC0FA83219D87CA3009F3A35 /* SQLite-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */; }; - DC0FA83919D87E0C009F3A35 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Private, ); }; }; DC109CE11A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; - DC3773F919C8CBB3004FCF85 /* SQLite.swift.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.swift.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; + DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; + DC2393D11ABE37B6003FF113 /* sqlite3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + DC2393D31ABE37D9003FF113 /* sqlite3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; + DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; + DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; }; DC37743519C8D626004FCF85 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; DC37743819C8D693004FCF85 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC475E9E19F2199900788FBD /* ExpressionTests.swift */; }; + DC5B12131ABE3298000DA146 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC5B12121ABE3298000DA146 /* libsqlite3.dylib */; }; DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; + DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; + DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; + DC70AC9B1AC2331400371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC5B12121ABE3298000DA146 /* libsqlite3.dylib */; }; DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; + DCAE4D331ABE0C2100EFCE7A /* sqlite3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + DCAE4D371ABE0C4C00EFCE7A /* sqlite3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */; }; @@ -36,12 +46,7 @@ DCC6B3721A9191C300734B78 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; DCC6B3731A9191C300734B78 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; DCC6B3741A9191C300734B78 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; - DCC6B3751A9191C300734B78 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */; }; - DCC6B3771A9191C300734B78 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DCC6B3791A9191C300734B78 /* SQLite.swift.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.swift.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCC6B37A1A9191C300734B78 /* SQLite-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DCC6B37B1A9191C300734B78 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DCC6B39F1A9193E800734B78 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC6B39C1A91938F00734B78 /* libsqlcipher.a */; }; + DCC6B3791A9191C300734B78 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6B3A31A9194A800734B78 /* Cipher.swift */; }; DCC6B3A61A9194FB00734B78 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6B3A51A9194FB00734B78 /* CipherTests.swift */; }; DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F17121A814F7000C83A2F /* FunctionsTests.swift */; }; @@ -55,27 +60,41 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */ = { + DC2393CF1ABE37A5003FF113 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; proxyType = 1; - remoteGlobalIDString = DC3773F219C8CBB3004FCF85; - remoteInfo = SQLite; + remoteGlobalIDString = DCAE4D141ABE0B3300EFCE7A; + remoteInfo = sqlite3; }; - DCC6B39B1A91938F00734B78 /* PBXContainerItemProxy */ = { + DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; proxyType = 2; remoteGlobalIDString = D2AAC046055464E500DB518D; remoteInfo = sqlcipher; }; - DCC6B39D1A9193E100734B78 /* PBXContainerItemProxy */ = { + DC2DD6B11ABE438900C2C71A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; proxyType = 1; remoteGlobalIDString = D2AAC045055464E500DB518D; remoteInfo = sqlcipher; }; + DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC3773F219C8CBB3004FCF85; + remoteInfo = SQLite; + }; + DCAE4D341ABE0C2800EFCE7A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DCAE4D141ABE0B3300EFCE7A; + remoteInfo = sqlite3; + }; DCC6B3AA1A91979100734B78 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; @@ -85,15 +104,40 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + DC2393D21ABE37C4003FF113 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */, + DC2393D31ABE37D9003FF113 /* sqlite3.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + DCAE4D361ABE0C3700EFCE7A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DCAE4D371ABE0C4C00EFCE7A /* sqlite3.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging-Header.h"; sourceTree = ""; }; - DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; DC109CE01A0C4D970070988E /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Schema.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; + DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; DC3773F319C8CBB3004FCF85 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../SQLite/Info.plist; sourceTree = ""; }; - DC3773F819C8CBB3004FCF85 /* SQLite.swift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLite.swift.h; path = ../SQLite/SQLite.swift.h; sourceTree = ""; }; + DC3773F819C8CBB3004FCF85 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLite.h; path = ../SQLite/SQLite.h; sourceTree = ""; }; DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DC37740419C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; @@ -105,18 +149,25 @@ DC3F170F1A8127A300C83A2F /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; DC3F17121A814F7000C83A2F /* FunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionsTests.swift; sourceTree = ""; }; DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; + DC5B12121ABE3298000DA146 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; }; DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; - DCAAE66D19D8A71B00158FEF /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; + DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../Vendor/fts3_tokenizer.h; sourceTree = ""; }; DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; + DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = sqlite3.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DCAE4D181ABE0B3300EFCE7A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = sqlite3.xcconfig; sourceTree = ""; }; + DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = ""; }; + DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = ""; }; + DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = ""; }; DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; + DCBC8C301ABE3CDA002B4631 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; + DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = Vendor/sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; DCC6B3A31A9194A800734B78 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; DCC6B3A51A9194FB00734B78 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseTests.swift; sourceTree = ""; }; @@ -129,7 +180,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 30C4A0551A8F5ADC00A6F5E8 /* libsqlite3.dylib in Frameworks */, + DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */, + DCAE4D331ABE0C2100EFCE7A /* sqlite3.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -141,12 +193,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DCAE4D111ABE0B3300EFCE7A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC5B12131ABE3298000DA146 /* libsqlite3.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DCC6B3761A9191C300734B78 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B39F1A9193E800734B78 /* libsqlcipher.a in Frameworks */, - DCC6B3771A9191C300734B78 /* libsqlite3.dylib in Frameworks */, + DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */, + DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */, + DC2393D11ABE37B6003FF113 /* sqlite3.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -174,16 +235,6 @@ name = "Query Building"; sourceTree = ""; }; - DC0FA83419D87DE3009F3A35 /* Bridging */ = { - isa = PBXGroup; - children = ( - DC0FA83119D87CA3009F3A35 /* SQLite-Bridging-Header.h */, - DC0FA83619D87E0C009F3A35 /* SQLite-Bridging.h */, - DC0FA83519D87E0C009F3A35 /* SQLite-Bridging.m */, - ); - name = Bridging; - sourceTree = ""; - }; DC10500F19C904DD00D8CA30 /* SQLite Tests */ = { isa = PBXGroup; children = ( @@ -201,16 +252,25 @@ path = "SQLite Tests"; sourceTree = ""; }; + DC2DD6A71ABE428E00C2C71A /* Products */ = { + isa = PBXGroup; + children = ( + DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */, + ); + name = Products; + sourceTree = ""; + }; DC3773E919C8CBB3004FCF85 = { isa = PBXGroup; children = ( DC37744719C8F50B004FCF85 /* README.md */, - DCAAE66D19D8A71B00158FEF /* SQLite.playground */, + DCBC8C301ABE3CDA002B4631 /* SQLite.playground */, DC37742D19C8CC90004FCF85 /* SQLite */, DC10500F19C904DD00D8CA30 /* SQLite Tests */, DCC6B3A11A91949C00734B78 /* SQLiteCipher */, DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */, - DCC6B3951A91936500734B78 /* Vendor */, + DCAE4D161ABE0B3300EFCE7A /* sqlite3 */, + DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */, DC3773F419C8CBB3004FCF85 /* Products */, ); indentWidth = 4; @@ -225,6 +285,7 @@ DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */, DCC6B3801A9191C300734B78 /* SQLite.framework */, DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */, + DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */, ); name = Products; sourceTree = ""; @@ -245,7 +306,6 @@ DC37743A19C8D6C0004FCF85 /* Statement.swift */, DC37743719C8D693004FCF85 /* Value.swift */, DC037C0919F0240100959746 /* Query Building */, - DC0FA83419D87DE3009F3A35 /* Bridging */, DC37743319C8CFCE004FCF85 /* Supporting Files */, ); path = SQLite; @@ -254,7 +314,10 @@ DC37743319C8CFCE004FCF85 /* Supporting Files */ = { isa = PBXGroup; children = ( - DC3773F819C8CBB3004FCF85 /* SQLite.swift.h */, + DC3773F819C8CBB3004FCF85 /* SQLite.h */, + DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */, + DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */, + DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */, DC3773F719C8CBB3004FCF85 /* Info.plist */, DC37744219C8DC91004FCF85 /* libsqlite3.dylib */, DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */, @@ -262,29 +325,25 @@ name = "Supporting Files"; sourceTree = ""; }; - DC9D38991AAD457000780AE7 /* ext */ = { + DCAE4D161ABE0B3300EFCE7A /* sqlite3 */ = { isa = PBXGroup; children = ( - DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */, + DCAE4D171ABE0B3300EFCE7A /* Supporting Files */, ); - name = ext; + path = sqlite3; sourceTree = ""; }; - DCC6B3951A91936500734B78 /* Vendor */ = { + DCAE4D171ABE0B3300EFCE7A /* Supporting Files */ = { isa = PBXGroup; children = ( - DC9D38991AAD457000780AE7 /* ext */, - DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */, + DCAE4D181ABE0B3300EFCE7A /* Info.plist */, + DC5B12121ABE3298000DA146 /* libsqlite3.dylib */, + DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */, + DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */, + DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */, + DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */, ); - path = Vendor; - sourceTree = ""; - }; - DCC6B3971A91938F00734B78 /* Products */ = { - isa = PBXGroup; - children = ( - DCC6B39C1A91938F00734B78 /* libsqlcipher.a */, - ); - name = Products; + name = "Supporting Files"; sourceTree = ""; }; DCC6B3A11A91949C00734B78 /* SQLiteCipher */ = { @@ -311,9 +370,15 @@ buildActionMask = 2147483647; files = ( DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */, - DC3773F919C8CBB3004FCF85 /* SQLite.swift.h in Headers */, - DC0FA83219D87CA3009F3A35 /* SQLite-Bridging-Header.h in Headers */, - DC0FA83919D87E0C009F3A35 /* SQLite-Bridging.h in Headers */, + DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */, + DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCAE4D121ABE0B3300EFCE7A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -322,9 +387,8 @@ buildActionMask = 2147483647; files = ( DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */, - DCC6B3791A9191C300734B78 /* SQLite.swift.h in Headers */, - DCC6B37A1A9191C300734B78 /* SQLite-Bridging-Header.h in Headers */, - DCC6B37B1A9191C300734B78 /* SQLite-Bridging.h in Headers */, + DCC6B3791A9191C300734B78 /* SQLite.h in Headers */, + DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -339,10 +403,12 @@ DC3773EF19C8CBB3004FCF85 /* Frameworks */, DC3773F019C8CBB3004FCF85 /* Headers */, DC3773F119C8CBB3004FCF85 /* Resources */, + DCAE4D361ABE0C3700EFCE7A /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + DCAE4D351ABE0C2800EFCE7A /* PBXTargetDependency */, ); name = SQLite; productName = SQLite; @@ -367,6 +433,24 @@ productReference = DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + DCAE4D141ABE0B3300EFCE7A /* sqlite3 */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCAE4D281ABE0B3400EFCE7A /* Build configuration list for PBXNativeTarget "sqlite3" */; + buildPhases = ( + DCAE4D101ABE0B3300EFCE7A /* Sources */, + DCAE4D111ABE0B3300EFCE7A /* Frameworks */, + DCAE4D121ABE0B3300EFCE7A /* Headers */, + DCAE4D131ABE0B3300EFCE7A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = sqlite3; + productName = sqlite3; + productReference = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; + productType = "com.apple.product-type.framework"; + }; DCC6B36D1A9191C300734B78 /* SQLiteCipher */ = { isa = PBXNativeTarget; buildConfigurationList = DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */; @@ -375,11 +459,13 @@ DCC6B3761A9191C300734B78 /* Frameworks */, DCC6B3781A9191C300734B78 /* Headers */, DCC6B37C1A9191C300734B78 /* Resources */, + DC2393D21ABE37C4003FF113 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( - DCC6B39E1A9193E100734B78 /* PBXTargetDependency */, + DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */, + DC2393D01ABE37A5003FF113 /* PBXTargetDependency */, ); name = SQLiteCipher; productName = SQLite; @@ -419,6 +505,9 @@ DC3773FD19C8CBB3004FCF85 = { CreatedOnToolsVersion = 6.1; }; + DCAE4D141ABE0B3300EFCE7A = { + CreatedOnToolsVersion = 6.3; + }; }; }; buildConfigurationList = DC3773ED19C8CBB3004FCF85 /* Build configuration list for PBXProject "SQLite" */; @@ -433,7 +522,7 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = DCC6B3971A91938F00734B78 /* Products */; + ProductGroup = DC2DD6A71ABE428E00C2C71A /* Products */; ProjectRef = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; }, ); @@ -443,16 +532,17 @@ DC3773FD19C8CBB3004FCF85 /* SQLite Tests */, DCC6B36D1A9191C300734B78 /* SQLiteCipher */, DCC6B3821A9191D100734B78 /* SQLiteCipher Tests */, + DCAE4D141ABE0B3300EFCE7A /* sqlite3 */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - DCC6B39C1A91938F00734B78 /* libsqlcipher.a */ = { + DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libsqlcipher.a; - remoteRef = DCC6B39B1A91938F00734B78 /* PBXContainerItemProxy */; + remoteRef = DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ @@ -472,6 +562,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DCAE4D131ABE0B3300EFCE7A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DCC6B37C1A9191C300734B78 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -494,6 +591,7 @@ buildActionMask = 2147483647; files = ( DC37743519C8D626004FCF85 /* Database.swift in Sources */, + DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */, DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */, DC37743819C8D693004FCF85 /* Value.swift in Sources */, @@ -502,7 +600,6 @@ DCAD429719E2E0F1004A51DF /* Query.swift in Sources */, DC109CE11A0C4D970070988E /* Schema.swift in Sources */, DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, - DC0FA83719D87E0C009F3A35 /* SQLite-Bridging.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -522,6 +619,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DCAE4D101ABE0B3300EFCE7A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DCC6B36E1A9191C300734B78 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -534,8 +638,8 @@ DCC6B3741A9191C300734B78 /* Schema.swift in Sources */, DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */, DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */, - DCC6B3751A9191C300734B78 /* SQLite-Bridging.m in Sources */, DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */, + DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -552,15 +656,25 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + DC2393D01ABE37A5003FF113 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DCAE4D141ABE0B3300EFCE7A /* sqlite3 */; + targetProxy = DC2393CF1ABE37A5003FF113 /* PBXContainerItemProxy */; + }; + DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = sqlcipher; + targetProxy = DC2DD6B11ABE438900C2C71A /* PBXContainerItemProxy */; + }; DC37740119C8CBB3004FCF85 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DC3773F219C8CBB3004FCF85 /* SQLite */; targetProxy = DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */; }; - DCC6B39E1A9193E100734B78 /* PBXTargetDependency */ = { + DCAE4D351ABE0C2800EFCE7A /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = sqlcipher; - targetProxy = DCC6B39D1A9193E100734B78 /* PBXContainerItemProxy */; + target = DCAE4D141ABE0B3300EFCE7A /* sqlite3 */; + targetProxy = DCAE4D341ABE0C2800EFCE7A /* PBXContainerItemProxy */; }; DCC6B3AB1A91979100734B78 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -659,9 +773,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "SQLite/SQLite-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -677,9 +791,9 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "SQLite/SQLite-Bridging-Header.h"; }; name = Release; }; @@ -707,6 +821,54 @@ }; name = Release; }; + DCAE4D291ABE0B3400EFCE7A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = sqlite3/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DCAE4D2A1ABE0B3400EFCE7A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; + buildSettings = { + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = sqlite3/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; DCC6B37E1A9191C300734B78 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; @@ -722,9 +884,9 @@ ); INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "SQLite/SQLite-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -744,9 +906,9 @@ ); INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "SQLite/SQLite-Bridging-Header.h"; }; name = Release; }; @@ -804,6 +966,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DCAE4D281ABE0B3400EFCE7A /* Build configuration list for PBXNativeTarget "sqlite3" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCAE4D291ABE0B3400EFCE7A /* Debug */, + DCAE4D2A1ABE0B3400EFCE7A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 6b96a3e5..820cbd76 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -import Foundation +import sqlite3 /// A connection (handle) to a SQLite database. public final class Database { diff --git a/SQLite/SQLite-Bridging-Header.h b/SQLite/SQLite-Bridging-Header.h deleted file mode 100644 index a7b826ce..00000000 --- a/SQLite/SQLite-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#include "SQLite-Bridging.h" diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index 1239c38a..b5d93c4a 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -22,12 +22,9 @@ // THE SOFTWARE. // +@import sqlite3; @import Foundation; -#include - -#include "fts3_tokenizer.h" - typedef int (^SQLiteBusyHandlerCallback)(int times); int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback); diff --git a/SQLite/SQLite-Bridging.m b/SQLite/SQLite-Bridging.m index ac4e5a02..0a115a7e 100644 --- a/SQLite/SQLite-Bridging.m +++ b/SQLite/SQLite-Bridging.m @@ -22,9 +22,9 @@ // THE SOFTWARE. // -#include "SQLite-Bridging.h" +#import "SQLite-Bridging.h" -@import Foundation; +#import "fts3_tokenizer.h" static int _SQLiteBusyHandler(void * context, int tries) { return ((__bridge SQLiteBusyHandlerCallback)context)(tries); diff --git a/SQLite/SQLite.swift.h b/SQLite/SQLite.h similarity index 85% rename from SQLite/SQLite.swift.h rename to SQLite/SQLite.h index b59edcd1..418bb477 100644 --- a/SQLite/SQLite.swift.h +++ b/SQLite/SQLite.h @@ -5,3 +5,5 @@ FOUNDATION_EXPORT double SQLite_VersionNumber; //! Project version string for SQLite. FOUNDATION_EXPORT const unsigned char SQLite_VersionString[]; + +#import diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 0a190f56..812faf2d 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import sqlite3 + internal let SQLITE_STATIC = sqlite3_destructor_type(COpaquePointer(bitPattern: 0)) internal let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern: -1)) diff --git a/sqlite3/Info.plist b/sqlite3/Info.plist new file mode 100644 index 00000000..d9b780a0 --- /dev/null +++ b/sqlite3/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.stephencelis.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/sqlite3/iphoneos.modulemap b/sqlite3/iphoneos.modulemap new file mode 100644 index 00000000..230a1e3e --- /dev/null +++ b/sqlite3/iphoneos.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/iphonesimulator.modulemap b/sqlite3/iphonesimulator.modulemap new file mode 100644 index 00000000..e1f316a5 --- /dev/null +++ b/sqlite3/iphonesimulator.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/macosx.modulemap b/sqlite3/macosx.modulemap new file mode 100644 index 00000000..f48e23a1 --- /dev/null +++ b/sqlite3/macosx.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/sqlite3.xcconfig b/sqlite3/sqlite3.xcconfig new file mode 100644 index 00000000..4b92f889 --- /dev/null +++ b/sqlite3/sqlite3.xcconfig @@ -0,0 +1,5 @@ +#include "../SQLite/SQLite.xcconfig" + +MODULEMAP_FILE[sdk=iphoneos*] = $(SRCROOT)/sqlite3/iphoneos.modulemap +MODULEMAP_FILE[sdk=iphonesimulator*] = $(SRCROOT)/sqlite3/iphonesimulator.modulemap +MODULEMAP_FILE[sdk=macosx*] = $(SRCROOT)/sqlite3/macosx.modulemap From f02b75a93027f584b20e27b784db0958b0567d80 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Mar 2015 10:15:37 -0700 Subject: [PATCH 0226/1046] Mark create(table:) @noescape This removes the `self.` requirement from the block, which should lead to better legibility in some cases. Signed-off-by: Stephen Celis --- SQLite Tests/SchemaTests.swift | 6 ------ SQLite/Schema.swift | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift index 6d8b2d42..b65aed0a 100644 --- a/SQLite Tests/SchemaTests.swift +++ b/SQLite Tests/SchemaTests.swift @@ -92,7 +92,6 @@ class SchemaTests: SQLiteTestCase { } func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { - let users = self.users db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id, references: users[id]) @@ -104,7 +103,6 @@ class SchemaTests: SQLiteTestCase { } func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { - let users = self.users db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id, references: users) @@ -165,7 +163,6 @@ class SchemaTests: SQLiteTestCase { } func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() { - let users = self.users db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id) @@ -179,7 +176,6 @@ class SchemaTests: SQLiteTestCase { } func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() { - let users = self.users db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id) @@ -193,7 +189,6 @@ class SchemaTests: SQLiteTestCase { } func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() { - let users = self.users db.create(table: users) { t in t.column(id, primaryKey: true) t.column(manager_id) @@ -207,7 +202,6 @@ class SchemaTests: SQLiteTestCase { } func test_createTable_foreignKey_withCompositeKey_buildsForeignKeyTableConstraint() { - let users = self.users let manager_id = Expression("manager_id") // required db.create(table: users) { t in diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 8d4e4996..7e19e19d 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -28,7 +28,7 @@ public extension Database { #table: Query, temporary: Bool = false, ifNotExists: Bool = false, - _ block: SchemaBuilder -> () + @noescape _ block: SchemaBuilder -> () ) -> Statement { var builder = SchemaBuilder(table) block(builder) From 1fea119c0a4917989ce080b46b0f306e01de392c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Mar 2015 10:19:32 -0700 Subject: [PATCH 0227/1046] Mark transaction/savepoint block helpers @noescape They execute immediately, so there's no reason to worry about capture semantics. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 820cbd76..de72b79c 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -238,7 +238,7 @@ public final class Database { /// failure. /// /// :returns: The COMMIT or ROLLBACK statement. - public func transaction(_ mode: TransactionMode = .Deferred, _ block: Statement -> TransactionResult) -> Statement { + public func transaction(_ mode: TransactionMode = .Deferred, @noescape _ block: Statement -> TransactionResult) -> Statement { return run(block(transaction(mode)).rawValue) } @@ -312,7 +312,7 @@ public final class Database { /// depending on success or failure. /// /// :returns: The RELEASE or ROLLBACK statement. - public func savepoint(_ savepointName: String? = nil, _ block: Statement -> SavepointResult) -> Statement { + public func savepoint(_ savepointName: String? = nil, @noescape _ block: Statement -> SavepointResult) -> Statement { switch block(savepoint(savepointName)) { case .Release: return release() From 24cd4051696f3131ae57682e2bb111229bcc848f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 23 Mar 2015 09:04:07 -0700 Subject: [PATCH 0228/1046] Plug C block memory leaks The database connection can retain them well enough. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 27 +++++++++++++++++++-------- SQLite/SQLite-Bridging.m | 8 ++++---- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index de72b79c..f9580cfb 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -372,12 +372,16 @@ public final class Database { /// returns true, it will try again. If it returns false, /// no further attempts will be made. public func busy(callback: (Int -> Bool)?) { - if let callback = callback { - try { SQLiteBusyHandler(self.handle) { callback(Int($0)) ? 1 : 0 } } - } else { - try { SQLiteBusyHandler(self.handle, nil) } + try { + if let callback = callback { + self.busy = { callback(Int($0)) ? 1 : 0 } + } else { + self.busy = nil + } + return SQLiteBusyHandler(self.handle, self.busy) } } + private var busy: SQLiteBusyHandlerCallback? /// Sets a handler to call when a statement is executed with the compiled /// SQL. @@ -387,11 +391,13 @@ public final class Database { /// act as a logger. public func trace(callback: (String -> ())?) { if let callback = callback { - SQLiteTrace(handle) { callback(String.fromCString($0)!) } + trace = { callback(String.fromCString($0)!) } } else { - SQLiteTrace(handle, nil) + trace = nil } + SQLiteTrace(handle, trace) } + private var trace: SQLiteTraceCallback? /// Creates or redefines a custom SQL function. /// @@ -412,7 +418,8 @@ public final class Database { /// should return a raw SQL value (or nil). public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { try { - SQLiteCreateFunction(self.handle, function, Int32(argc), deterministic ? 1 : 0) { context, argc, argv in + if self.functions[function] == nil { self.functions[function] = [:] } + self.functions[function]?[argc] = { context, argc, argv in let arguments: [Binding?] = map(0.. ComparisonResult) { try { - SQLiteCreateCollation(self.handle, collation) { lhs, rhs in + self.collations[collation] = { lhs, rhs in return Int32(block(String.fromCString(lhs)!, String.fromCString(rhs)!).rawValue) } + return SQLiteCreateCollation(self.handle, collation, self.collations[collation]) } } + private var collations = [String: SQLiteCreateCollationCallback]() // MARK: - Error Handling diff --git a/SQLite/SQLite-Bridging.m b/SQLite/SQLite-Bridging.m index 0a115a7e..3bc2722b 100644 --- a/SQLite/SQLite-Bridging.m +++ b/SQLite/SQLite-Bridging.m @@ -32,7 +32,7 @@ static int _SQLiteBusyHandler(void * context, int tries) { int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback) { if (callback) { - return sqlite3_busy_handler(handle, _SQLiteBusyHandler, (__bridge_retained void *)callback); // FIXME: leak + return sqlite3_busy_handler(handle, _SQLiteBusyHandler, (__bridge void *)callback); } else { return sqlite3_busy_handler(handle, 0, 0); } @@ -44,7 +44,7 @@ static void _SQLiteTrace(void * context, const char * SQL) { void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback) { if (callback) { - sqlite3_trace(handle, _SQLiteTrace, (__bridge_retained void *)callback); // FIXME: leak + sqlite3_trace(handle, _SQLiteTrace, (__bridge void *)callback); } else { sqlite3_trace(handle, 0, 0); } @@ -62,7 +62,7 @@ int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int dete flags |= SQLITE_DETERMINISTIC; #endif } - return sqlite3_create_function_v2(handle, name, -1, flags, (__bridge_retained void *)callback, &_SQLiteCreateFunction, 0, 0, 0); // FIXME: leak + return sqlite3_create_function_v2(handle, name, -1, flags, (__bridge void *)callback, &_SQLiteCreateFunction, 0, 0, 0); } else { return sqlite3_create_function_v2(handle, name, 0, 0, 0, 0, 0, 0, 0); } @@ -74,7 +74,7 @@ static int _SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback) { if (callback) { - return sqlite3_create_collation_v2(handle, name, SQLITE_UTF8, (__bridge_retained void *)callback, &_SQLiteCreateCollation, 0); // FIXME: leak + return sqlite3_create_collation_v2(handle, name, SQLITE_UTF8, (__bridge void *)callback, &_SQLiteCreateCollation, 0); } else { return sqlite3_create_collation_v2(handle, name, 0, 0, 0, 0); } From 05cf90fbf7e82264e0d20b75a02e1ee016afd915 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Mar 2015 09:57:30 -0700 Subject: [PATCH 0229/1046] Query interrupt support Adds the ability to interrupt a database connection during a long-running query. Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 16 ++++++++++++++++ SQLite Tests/TestHelper.swift | 4 ++++ SQLite/Database.swift | 5 +++++ SQLite/Statement.swift | 2 +- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index 58f3fd2e..fa73f19a 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -264,6 +264,22 @@ class DatabaseTests: SQLiteTestCase { AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) } + func test_interrupt_interruptsLongRunningQuery() { + insertUsers(map("abcdefghijklmnopqrstuvwxyz") { String($0) }) + db.create(function: "sleep") { args in + usleep(UInt32(Double(args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) + return nil + } + + let stmt = db.prepare("SELECT *, sleep(?) FROM users", 0.1) + stmt.run() + XCTAssert(!stmt.failed) + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) + stmt.run() + XCTAssert(stmt.failed) + } + func test_userVersion_getsAndSetsUserVersion() { XCTAssertEqual(0, db.userVersion) db.userVersion = 1 diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift index eff00fd4..9d712c78 100644 --- a/SQLite Tests/TestHelper.swift +++ b/SQLite Tests/TestHelper.swift @@ -40,6 +40,10 @@ class SQLiteTestCase: XCTestCase { } func insertUsers(names: String...) { + insertUsers(names) + } + + func insertUsers(names: [String]) { for name in names { insertUser(name) } } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index f9580cfb..430e59c3 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -343,6 +343,11 @@ public final class Database { return run("ROLLBACK TO SAVEPOINT \(quote(literal: savepointName))") } + /// Interrupts any long-running queries. + public func interrupt() { + sqlite3_interrupt(handle) + } + // MARK: - Configuration public var foreignKeys: Bool { diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 812faf2d..2dc4bd33 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -194,7 +194,7 @@ public final class Statement { self.status = block() if self.failed { self.reason = String.fromCString(sqlite3_errmsg(self.database.handle)) - assert(self.status == SQLITE_CONSTRAINT, "\(self.reason!)") + assert(self.status == SQLITE_CONSTRAINT || self.status == SQLITE_INTERRUPT, "\(self.reason!)") } } } From 540df5941a93dbff34f28257b3a415868ed47bf3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Mar 2015 10:54:23 -0700 Subject: [PATCH 0230/1046] Add parameter names to callback functions Makes things more intelligible when autocompleting functions and their closures. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 430e59c3..105d7032 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -238,8 +238,8 @@ public final class Database { /// failure. /// /// :returns: The COMMIT or ROLLBACK statement. - public func transaction(_ mode: TransactionMode = .Deferred, @noescape _ block: Statement -> TransactionResult) -> Statement { - return run(block(transaction(mode)).rawValue) + public func transaction(_ mode: TransactionMode = .Deferred, @noescape _ block: (txn: Statement) -> TransactionResult) -> Statement { + return run(block(txn: transaction(mode)).rawValue) } /// Commits the current transaction (or, if a savepoint is open, releases @@ -312,8 +312,8 @@ public final class Database { /// depending on success or failure. /// /// :returns: The RELEASE or ROLLBACK statement. - public func savepoint(_ savepointName: String? = nil, @noescape _ block: Statement -> SavepointResult) -> Statement { - switch block(savepoint(savepointName)) { + public func savepoint(_ savepointName: String? = nil, @noescape _ block: (txn: Statement) -> SavepointResult) -> Statement { + switch block(txn: savepoint(savepointName)) { case .Release: return release() case .Rollback: @@ -376,10 +376,10 @@ public final class Database { /// number of times it’s been called for this lock. If it /// returns true, it will try again. If it returns false, /// no further attempts will be made. - public func busy(callback: (Int -> Bool)?) { + public func busy(callback: ((tries: Int) -> Bool)?) { try { if let callback = callback { - self.busy = { callback(Int($0)) ? 1 : 0 } + self.busy = { callback(tries: Int($0)) ? 1 : 0 } } else { self.busy = nil } @@ -391,12 +391,12 @@ public final class Database { /// Sets a handler to call when a statement is executed with the compiled /// SQL. /// - /// :param: callback This block is executed as a statement is executed with - /// the compiled SQL as an argument. E.g., pass println to - /// act as a logger. - public func trace(callback: (String -> ())?) { + /// :param: callback This block is invoked when a statement is executed with + /// the compiled SQL as its argument. E.g., pass `println` + /// to act as a logger. + public func trace(callback: ((SQL: String) -> ())?) { if let callback = callback { - trace = { callback(String.fromCString($0)!) } + trace = { callback(SQL: String.fromCString($0)!) } } else { trace = nil } @@ -421,7 +421,7 @@ public final class Database { /// called. The block is called with an array of raw /// SQL values mapped to the function's parameters and /// should return a raw SQL value (or nil). - public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: [Binding?] -> Binding?) { + public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { try { if self.functions[function] == nil { self.functions[function] = [:] } self.functions[function]?[argc] = { context, argc, argv in @@ -444,7 +444,7 @@ public final class Database { assertionFailure("unsupported value type: \(type)") } } - let result = block(arguments) + let result = block(args: arguments) if let result = result as? Blob { sqlite3_result_blob(context, result.bytes, Int32(result.length), nil) } else if let result = result as? Double { @@ -473,10 +473,10 @@ public final class Database { /// /// :param: block A collation function that takes two strings and /// returns the comparison result. - public func create(#collation: String, _ block: (String, String) -> ComparisonResult) { + public func create(#collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) { try { self.collations[collation] = { lhs, rhs in - return Int32(block(String.fromCString(lhs)!, String.fromCString(rhs)!).rawValue) + return Int32(block(lhs: String.fromCString(lhs)!, rhs: String.fromCString(rhs)!).rawValue) } return SQLiteCreateCollation(self.handle, collation, self.collations[collation]) } From 6abd3f4f3152588f3d2fa4797422823cc6655949 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Mar 2015 10:56:52 -0700 Subject: [PATCH 0231/1046] Update, commit, and rollback hook support Should make it easy, e.g., to create an interface for a table view to register for updates. Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 67 +++++++++++++++++++++++++++++ SQLite/Database.swift | 74 ++++++++++++++++++++++++++++++++ SQLite/SQLite-Bridging.h | 9 ++++ SQLite/SQLite-Bridging.m | 24 +++++++++++ 4 files changed, 174 insertions(+) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index fa73f19a..2a313905 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -292,6 +292,64 @@ class DatabaseTests: SQLiteTestCase { XCTAssertEqual(true, db.foreignKeys) } + func test_updateHook_setsUpdateHook() { + let updateHook = expectationWithDescription("updateHook") + db.updateHook { operation, db, table, rowid in + XCTAssertEqual(.Insert, operation) + XCTAssertEqual("main", db) + XCTAssertEqual("users", table) + XCTAssertEqual(1, rowid) + updateHook.fulfill() + } + executeAndWait { + insertUser("alice") + } + } + + func test_commitHook_setsCommitHook() { + let commitHook = expectationWithDescription("commitHook") + db.commitHook { + commitHook.fulfill() + return .Commit + } + executeAndWait { + self.db.transaction { _ in + self.insertUser("alice") + return .Commit + } + XCTAssertEqual(1, self.db.scalar("SELECT count(*) FROM users") as! Int64) + } + } + + func test_rollbackHook_setsRollbackHook() { + let rollbackHook = expectationWithDescription("commitHook") + db.rollbackHook { + rollbackHook.fulfill() + } + executeAndWait { + self.db.transaction { _ in + self.insertUser("alice") + return .Rollback + } + XCTAssertEqual(0, self.db.scalar("SELECT count(*) FROM users") as! Int64) + } + } + + func test_commitHook_withRollback_rollsBack() { + let rollbackHook = expectationWithDescription("commitHook") + db.commitHook { .Rollback } + db.rollbackHook { + rollbackHook.fulfill() + } + executeAndWait { + self.db.transaction { _ in + self.insertUser("alice") + return .Commit + } + XCTAssertEqual(0, self.db.scalar("SELECT count(*) FROM users") as! Int64) + } + } + func test_createFunction_withArrayArguments() { db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } } @@ -320,4 +378,13 @@ class DatabaseTests: SQLiteTestCase { XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) } + func executeAndWait(block: () -> ()) { + dispatch_async(dispatch_get_main_queue(), block) + waitForExpectationsWithTimeout(5) { error in + if let error = error { + fatalError(error.localizedDescription) + } + } + } + } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 105d7032..5181f072 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -404,6 +404,80 @@ public final class Database { } private var trace: SQLiteTraceCallback? + /// A SQL operation passed to update callbacks. + public enum Operation { + + /// An INSERT operation. + case Insert + + /// An UPDATE operation. + case Update + + /// A DELETE operation. + case Delete + + private static func fromRawValue(rawValue: Int32) -> Operation { + switch rawValue { + case SQLITE_INSERT: + return .Insert + case SQLITE_UPDATE: + return .Update + case SQLITE_DELETE: + return .Delete + default: + fatalError("unhandled operation code: \(rawValue)") + } + } + + } + + /// Registers a callback to be invoked whenever a row is inserted, updated, + /// or deleted in a rowid table. + /// + /// :param: callback A callback invoked with the `Operation` (one + /// of `.Insert`, `.Update`, or `.Delete`), database name, + /// table name, and rowid. + public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> ())?) { + if let callback = callback { + updateHook = { operation, db, table, rowid in + callback( + operation: .fromRawValue(operation), + db: String.fromCString(db)!, + table: String.fromCString(table)!, + rowid: rowid + ) + } + } else { + updateHook = nil + } + SQLiteUpdateHook(handle, updateHook) + } + private var updateHook: SQLiteUpdateHookCallback? + + /// Registers a callback to be invoked whenever a transaction is committed. + /// + /// :param: callback A callback that must return `.Commit` or `.Rollback` to + /// determine whether a transaction should be committed or + /// not. + public func commitHook(callback: (() -> TransactionResult)?) { + if let callback = callback { + commitHook = { callback() == .Commit ? 0 : 1 } + } else { + commitHook = nil + } + SQLiteCommitHook(handle, commitHook) + } + private var commitHook: SQLiteCommitHookCallback? + + /// Registers a callback to be invoked whenever a transaction rolls back. + /// + /// :param: callback A callback invoked when a transaction is rolled back. + public func rollbackHook(callback: (() -> ())?) { + rollbackHook = callback.map { $0 } + SQLiteRollbackHook(handle, rollbackHook) + } + private var rollbackHook: SQLiteRollbackHookCallback? + /// Creates or redefines a custom SQL function. /// /// :param: function The name of the function to create or redefine. diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index b5d93c4a..f4d09c1e 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -31,6 +31,15 @@ int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback); typedef void (^SQLiteTraceCallback)(const char * SQL); void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback); +typedef void (^SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, sqlite3_int64 rowid); +void SQLiteUpdateHook(sqlite3 * handle, SQLiteUpdateHookCallback callback); + +typedef int (^SQLiteCommitHookCallback)(); +void SQLiteCommitHook(sqlite3 * handle, SQLiteCommitHookCallback callback); + +typedef void (^SQLiteRollbackHookCallback)(); +void SQLiteRollbackHook(sqlite3 * handle, SQLiteRollbackHookCallback callback); + typedef void (^SQLiteCreateFunctionCallback)(sqlite3_context * context, int argc, sqlite3_value ** argv); int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback); diff --git a/SQLite/SQLite-Bridging.m b/SQLite/SQLite-Bridging.m index 3bc2722b..4da64891 100644 --- a/SQLite/SQLite-Bridging.m +++ b/SQLite/SQLite-Bridging.m @@ -50,6 +50,30 @@ void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback) { } } +static void _SQLiteUpdateHook(void * context, int operation, const char * db, const char * table, sqlite3_int64 rowid) { + ((__bridge SQLiteUpdateHookCallback)context)(operation, db, table, rowid); +} + +void SQLiteUpdateHook(sqlite3 * handle, SQLiteUpdateHookCallback callback) { + sqlite3_update_hook(handle, _SQLiteUpdateHook, (__bridge void *)callback); +} + +static int _SQLiteCommitHook(void * context) { + return ((__bridge SQLiteCommitHookCallback)context)(); +} + +void SQLiteCommitHook(sqlite3 * handle, SQLiteCommitHookCallback callback) { + sqlite3_commit_hook(handle, _SQLiteCommitHook, (__bridge void *)callback); +} + +static void _SQLiteRollbackHook(void * context) { + ((__bridge SQLiteRollbackHookCallback)context)(); +} + +void SQLiteRollbackHook(sqlite3 * handle, SQLiteRollbackHookCallback callback) { + sqlite3_rollback_hook(handle, _SQLiteRollbackHook, (__bridge void *)callback); +} + static void _SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { ((__bridge SQLiteCreateFunctionCallback)sqlite3_user_data(context))(context, argc, argv); } From 9d83e2f3fa912588f6ed441e3b464060f740aa5e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 24 Mar 2015 10:58:33 -0700 Subject: [PATCH 0232/1046] Remove unnecessary abstraction Signed-off-by: Stephen Celis --- SQLite/Database.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 5181f072..b2922edd 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -289,15 +289,13 @@ public final class Database { private var savepointStack = [String]() - private var uuid: String { return NSUUID().UUIDString } - /// Starts a new transaction with the given savepoint name. /// /// :param: savepointName A unique identifier for the savepoint. /// /// :returns: The SAVEPOINT statement. public func savepoint(_ savepointName: String? = nil) -> Statement { - let name = savepointName ?? uuid + let name = savepointName ?? NSUUID().UUIDString savepointStack.append(name) return run("SAVEPOINT \(quote(literal: name))") } From 550c1df43c834844795ded093450e0a8b5131688 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 25 Mar 2015 00:33:35 -0700 Subject: [PATCH 0233/1046] Ess, queue, el. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 4 ++-- SQLite/Expression.swift | 12 ++++++------ SQLite/Functions.swift | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index b2922edd..14ea9515 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -24,7 +24,7 @@ import sqlite3 -/// A connection (handle) to a SQLite database. +/// A connection (handle) to SQLite. public final class Database { internal var handle: COpaquePointer = nil @@ -402,7 +402,7 @@ public final class Database { } private var trace: SQLiteTraceCallback? - /// A SQL operation passed to update callbacks. + /// An SQL operation passed to update callbacks. public enum Operation { /// An INSERT operation. diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index a01685b0..20eaff54 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -33,31 +33,31 @@ public struct Expression { private var original: Expressible? - /// Builds a SQL expression with a literal string and an optional list of + /// Builds an SQL expression with a literal string and an optional list of /// bindings. /// - /// :param: SQL A SQL string. + /// :param: SQL An SQL string. /// /// :param: bindings Values to be bound to the given SQL. public init(literal SQL: String = "", _ bindings: [Binding?] = []) { (self.SQL, self.bindings) = (SQL, bindings) } - /// Builds a SQL expression with a quoted identifier. + /// Builds an SQL expression with a quoted identifier. /// - /// :param: identifier A SQL identifier (*e.g.*, a column name). + /// :param: identifier An SQL identifier (*e.g.*, a column name). public init(_ identifier: String) { self.init(literal: quote(identifier: identifier)) } - /// Builds a SQL expression with the given value. + /// Builds an SQL expression with the given value. /// /// :param: value An encodable SQL value. public init(value: V?) { self.init(binding: value?.datatypeValue) } - /// Builds a SQL expression with the given value. + /// Builds an SQL expression with the given value. /// /// :param: binding A raw SQL value. internal init(binding: Binding?) { diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift index d4cfbd0b..148ee031 100644 --- a/SQLite/Functions.swift +++ b/SQLite/Functions.swift @@ -35,7 +35,7 @@ public extension Database { /// :param: block A block of code to run when the function is called. /// The assigned types must be explicit. /// - /// :returns: A closure returning a SQL expression to call the function. + /// :returns: A closure returning an SQL expression to call the function. public func create(#function: String, deterministic: Bool = false, _ block: () -> Z) -> (() -> Expression) { return { self.create(function, 0, deterministic) { _ in return block() }([]) } } From ad8ec5844de859f51edbc3b383b9d414cd0b84a6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Mar 2015 13:27:05 -0700 Subject: [PATCH 0234/1046] Conventionally mark Objective-C implementation details private Basically, prefix "_" all the things. Ideally we could hide these implementation details altogether, but it doesn't appear to be possible in a Swift framework (yet). Additionally, by creating and casting our own opaque structs in the "bridging" header, we can avoid exposing the inner sqlite3 module. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 28 ++++----- SQLite/FTS.swift | 2 +- SQLite/SQLite-Bridging.h | 37 ++++++------ SQLite/SQLite-Bridging.m | 121 ++++++++++++++++++++------------------- 4 files changed, 96 insertions(+), 92 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 14ea9515..69f1d444 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -381,10 +381,10 @@ public final class Database { } else { self.busy = nil } - return SQLiteBusyHandler(self.handle, self.busy) + return _SQLiteBusyHandler(self.handle, self.busy) } } - private var busy: SQLiteBusyHandlerCallback? + private var busy: _SQLiteBusyHandlerCallback? /// Sets a handler to call when a statement is executed with the compiled /// SQL. @@ -398,9 +398,9 @@ public final class Database { } else { trace = nil } - SQLiteTrace(handle, trace) + _SQLiteTrace(handle, trace) } - private var trace: SQLiteTraceCallback? + private var trace: _SQLiteTraceCallback? /// An SQL operation passed to update callbacks. public enum Operation { @@ -448,9 +448,9 @@ public final class Database { } else { updateHook = nil } - SQLiteUpdateHook(handle, updateHook) + _SQLiteUpdateHook(handle, updateHook) } - private var updateHook: SQLiteUpdateHookCallback? + private var updateHook: _SQLiteUpdateHookCallback? /// Registers a callback to be invoked whenever a transaction is committed. /// @@ -463,18 +463,18 @@ public final class Database { } else { commitHook = nil } - SQLiteCommitHook(handle, commitHook) + _SQLiteCommitHook(handle, commitHook) } - private var commitHook: SQLiteCommitHookCallback? + private var commitHook: _SQLiteCommitHookCallback? /// Registers a callback to be invoked whenever a transaction rolls back. /// /// :param: callback A callback invoked when a transaction is rolled back. public func rollbackHook(callback: (() -> ())?) { rollbackHook = callback.map { $0 } - SQLiteRollbackHook(handle, rollbackHook) + _SQLiteRollbackHook(handle, rollbackHook) } - private var rollbackHook: SQLiteRollbackHookCallback? + private var rollbackHook: _SQLiteRollbackHookCallback? /// Creates or redefines a custom SQL function. /// @@ -531,10 +531,10 @@ public final class Database { assertionFailure("unsupported result type: \(result)") } } - return SQLiteCreateFunction(self.handle, function, Int32(argc), deterministic ? 1 : 0, self.functions[function]?[argc]) + return _SQLiteCreateFunction(self.handle, function, Int32(argc), deterministic ? 1 : 0, self.functions[function]?[argc]) } } - private var functions = [String: [Int: SQLiteCreateFunctionCallback]]() + private var functions = [String: [Int: _SQLiteCreateFunctionCallback]]() /// The return type of a collation comparison function. public typealias ComparisonResult = NSComparisonResult @@ -550,10 +550,10 @@ public final class Database { self.collations[collation] = { lhs, rhs in return Int32(block(lhs: String.fromCString(lhs)!, rhs: String.fromCString(rhs)!).rawValue) } - return SQLiteCreateCollation(self.handle, collation, self.collations[collation]) + return _SQLiteCreateCollation(self.handle, collation, self.collations[collation]) } } - private var collations = [String: SQLiteCreateCollationCallback]() + private var collations = [String: _SQLiteCreateCollationCallback]() // MARK: - Error Handling diff --git a/SQLite/FTS.swift b/SQLite/FTS.swift index 8753409c..35afb07a 100644 --- a/SQLite/FTS.swift +++ b/SQLite/FTS.swift @@ -76,7 +76,7 @@ extension Database { public func register(tokenizer submoduleName: String, next: String -> (String, Range)?) { try { - SQLiteRegisterTokenizer(self.handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + _SQLiteRegisterTokenizer(self.handle, Tokenizer.moduleName, submoduleName) { input, offset, length in let string = String.fromCString(input)! if var (token, range) = next(string) { let view = string.utf8 diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index f4d09c1e..862ebb10 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -22,29 +22,32 @@ // THE SOFTWARE. // -@import sqlite3; @import Foundation; -typedef int (^SQLiteBusyHandlerCallback)(int times); -int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback); +typedef struct SQLiteHandle SQLiteHandle; +typedef struct SQLiteContext SQLiteContext; +typedef struct SQLiteValue SQLiteValue; -typedef void (^SQLiteTraceCallback)(const char * SQL); -void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback); +typedef int (^_SQLiteBusyHandlerCallback)(int times); +int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback callback); -typedef void (^SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, sqlite3_int64 rowid); -void SQLiteUpdateHook(sqlite3 * handle, SQLiteUpdateHookCallback callback); +typedef void (^_SQLiteTraceCallback)(const char * SQL); +void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback callback); -typedef int (^SQLiteCommitHookCallback)(); -void SQLiteCommitHook(sqlite3 * handle, SQLiteCommitHookCallback callback); +typedef void (^_SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, long long rowid); +void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback callback); -typedef void (^SQLiteRollbackHookCallback)(); -void SQLiteRollbackHook(sqlite3 * handle, SQLiteRollbackHookCallback callback); +typedef int (^_SQLiteCommitHookCallback)(); +void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback callback); -typedef void (^SQLiteCreateFunctionCallback)(sqlite3_context * context, int argc, sqlite3_value ** argv); -int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback); +typedef void (^_SQLiteRollbackHookCallback)(); +void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback callback); -typedef int (^SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); -int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback); +typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue ** argv); +int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback); -typedef NSString * (^SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int SQLiteRegisterTokenizer(sqlite3 * db, const char * module, const char * tokenizer, SQLiteTokenizerNextCallback callback); +typedef int (^_SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); +int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback callback); + +typedef NSString * (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _SQLiteTokenizerNextCallback callback); diff --git a/SQLite/SQLite-Bridging.m b/SQLite/SQLite-Bridging.m index 4da64891..a473091a 100644 --- a/SQLite/SQLite-Bridging.m +++ b/SQLite/SQLite-Bridging.m @@ -24,61 +24,63 @@ #import "SQLite-Bridging.h" +@import sqlite3; + #import "fts3_tokenizer.h" -static int _SQLiteBusyHandler(void * context, int tries) { - return ((__bridge SQLiteBusyHandlerCallback)context)(tries); +static int __SQLiteBusyHandler(void * context, int tries) { + return ((__bridge _SQLiteBusyHandlerCallback)context)(tries); } -int SQLiteBusyHandler(sqlite3 * handle, SQLiteBusyHandlerCallback callback) { +int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback callback) { if (callback) { - return sqlite3_busy_handler(handle, _SQLiteBusyHandler, (__bridge void *)callback); + return sqlite3_busy_handler((sqlite3 *)handle, __SQLiteBusyHandler, (__bridge void *)callback); } else { - return sqlite3_busy_handler(handle, 0, 0); + return sqlite3_busy_handler((sqlite3 *)handle, 0, 0); } } -static void _SQLiteTrace(void * context, const char * SQL) { - ((__bridge SQLiteTraceCallback)context)(SQL); +static void __SQLiteTrace(void * context, const char * SQL) { + ((__bridge _SQLiteTraceCallback)context)(SQL); } -void SQLiteTrace(sqlite3 * handle, SQLiteTraceCallback callback) { +void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback callback) { if (callback) { - sqlite3_trace(handle, _SQLiteTrace, (__bridge void *)callback); + sqlite3_trace((sqlite3 *)handle, __SQLiteTrace, (__bridge void *)callback); } else { - sqlite3_trace(handle, 0, 0); + sqlite3_trace((sqlite3 *)handle, 0, 0); } } -static void _SQLiteUpdateHook(void * context, int operation, const char * db, const char * table, sqlite3_int64 rowid) { - ((__bridge SQLiteUpdateHookCallback)context)(operation, db, table, rowid); +static void __SQLiteUpdateHook(void * context, int operation, const char * db, const char * table, long long rowid) { + ((__bridge _SQLiteUpdateHookCallback)context)(operation, db, table, rowid); } -void SQLiteUpdateHook(sqlite3 * handle, SQLiteUpdateHookCallback callback) { - sqlite3_update_hook(handle, _SQLiteUpdateHook, (__bridge void *)callback); +void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback callback) { + sqlite3_update_hook((sqlite3 *)handle, __SQLiteUpdateHook, (__bridge void *)callback); } -static int _SQLiteCommitHook(void * context) { - return ((__bridge SQLiteCommitHookCallback)context)(); +static int __SQLiteCommitHook(void * context) { + return ((__bridge _SQLiteCommitHookCallback)context)(); } -void SQLiteCommitHook(sqlite3 * handle, SQLiteCommitHookCallback callback) { - sqlite3_commit_hook(handle, _SQLiteCommitHook, (__bridge void *)callback); +void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback callback) { + sqlite3_commit_hook((sqlite3 *)handle, __SQLiteCommitHook, (__bridge void *)callback); } -static void _SQLiteRollbackHook(void * context) { - ((__bridge SQLiteRollbackHookCallback)context)(); +static void __SQLiteRollbackHook(void * context) { + ((__bridge _SQLiteRollbackHookCallback)context)(); } -void SQLiteRollbackHook(sqlite3 * handle, SQLiteRollbackHookCallback callback) { - sqlite3_rollback_hook(handle, _SQLiteRollbackHook, (__bridge void *)callback); +void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback callback) { + sqlite3_rollback_hook((sqlite3 *)handle, __SQLiteRollbackHook, (__bridge void *)callback); } -static void _SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { - ((__bridge SQLiteCreateFunctionCallback)sqlite3_user_data(context))(context, argc, argv); +static void __SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { + ((__bridge _SQLiteCreateFunctionCallback)sqlite3_user_data(context))((SQLiteContext *)context, argc, (SQLiteValue **)argv); } -int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int deterministic, SQLiteCreateFunctionCallback callback) { +int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback) { if (callback) { int flags = SQLITE_UTF8; if (deterministic) { @@ -86,51 +88,50 @@ int SQLiteCreateFunction(sqlite3 * handle, const char * name, int argc, int dete flags |= SQLITE_DETERMINISTIC; #endif } - return sqlite3_create_function_v2(handle, name, -1, flags, (__bridge void *)callback, &_SQLiteCreateFunction, 0, 0, 0); + return sqlite3_create_function_v2((sqlite3 *)handle, name, -1, flags, (__bridge void *)callback, &__SQLiteCreateFunction, 0, 0, 0); } else { - return sqlite3_create_function_v2(handle, name, 0, 0, 0, 0, 0, 0, 0); + return sqlite3_create_function_v2((sqlite3 *)handle, name, 0, 0, 0, 0, 0, 0, 0); } } -static int _SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { - return ((__bridge SQLiteCreateCollationCallback)context)(lhs, rhs); +static int __SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { + return ((__bridge _SQLiteCreateCollationCallback)context)(lhs, rhs); } -int SQLiteCreateCollation(sqlite3 * handle, const char * name, SQLiteCreateCollationCallback callback) { +int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback callback) { if (callback) { - return sqlite3_create_collation_v2(handle, name, SQLITE_UTF8, (__bridge void *)callback, &_SQLiteCreateCollation, 0); + return sqlite3_create_collation_v2((sqlite3 *)handle, name, SQLITE_UTF8, (__bridge void *)callback, &__SQLiteCreateCollation, 0); } else { - return sqlite3_create_collation_v2(handle, name, 0, 0, 0, 0); + return sqlite3_create_collation_v2((sqlite3 *)handle, name, 0, 0, 0, 0); } } #pragma mark - FTS -typedef struct _SQLiteTokenizer { +typedef struct __SQLiteTokenizer { sqlite3_tokenizer base; - __unsafe_unretained SQLiteTokenizerNextCallback callback; -} _SQLiteTokenizer; + __unsafe_unretained _SQLiteTokenizerNextCallback callback; +} __SQLiteTokenizer; -typedef struct _SQLiteTokenizerCursor { +typedef struct __SQLiteTokenizerCursor { void * base; const char * input; int inputOffset; int inputLength; int idx; -} _SQLiteTokenizerCursor; - +} __SQLiteTokenizerCursor; -static NSMutableDictionary * _SQLiteTokenizerMap; +static NSMutableDictionary * __SQLiteTokenizerMap; -static int _SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_tokenizer ** ppTokenizer) { - _SQLiteTokenizer * tokenizer = (_SQLiteTokenizer *)sqlite3_malloc(sizeof(_SQLiteTokenizer)); +static int __SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_tokenizer ** ppTokenizer) { + __SQLiteTokenizer * tokenizer = (__SQLiteTokenizer *)sqlite3_malloc(sizeof(__SQLiteTokenizer)); if (!tokenizer) { return SQLITE_NOMEM; } memset(tokenizer, 0, sizeof(* tokenizer)); // FIXME: needed? NSString * key = [NSString stringWithUTF8String:argv[0]]; - tokenizer->callback = [_SQLiteTokenizerMap objectForKey:key]; + tokenizer->callback = [__SQLiteTokenizerMap objectForKey:key]; if (!tokenizer->callback) { return SQLITE_ERROR; } @@ -139,13 +140,13 @@ static int _SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_t return SQLITE_OK; } -static int _SQLiteTokenizerDestroy(sqlite3_tokenizer * pTokenizer) { +static int __SQLiteTokenizerDestroy(sqlite3_tokenizer * pTokenizer) { sqlite3_free(pTokenizer); return SQLITE_OK; } -static int _SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pInput, int nBytes, sqlite3_tokenizer_cursor ** ppCursor) { - _SQLiteTokenizerCursor * cursor = (_SQLiteTokenizerCursor *)sqlite3_malloc(sizeof(_SQLiteTokenizerCursor)); +static int __SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pInput, int nBytes, sqlite3_tokenizer_cursor ** ppCursor) { + __SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)sqlite3_malloc(sizeof(__SQLiteTokenizerCursor)); if (!cursor) { return SQLITE_NOMEM; } @@ -159,14 +160,14 @@ static int _SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pIn return SQLITE_OK; } -static int _SQLiteTokenizerClose(sqlite3_tokenizer_cursor * pCursor) { +static int __SQLiteTokenizerClose(sqlite3_tokenizer_cursor * pCursor) { sqlite3_free(pCursor); return SQLITE_OK; } -static int _SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char ** ppToken, int * pnBytes, int * piStartOffset, int * piEndOffset, int * piPosition) { - _SQLiteTokenizerCursor * cursor = (_SQLiteTokenizerCursor *)pCursor; - _SQLiteTokenizer * tokenizer = (_SQLiteTokenizer *)cursor->base; +static int __SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char ** ppToken, int * pnBytes, int * piStartOffset, int * piEndOffset, int * piPosition) { + __SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)pCursor; + __SQLiteTokenizer * tokenizer = (__SQLiteTokenizer *)cursor->base; cursor->inputOffset += cursor->inputLength; const char * input = cursor->input + cursor->inputOffset; @@ -183,27 +184,27 @@ static int _SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char * return SQLITE_OK; } -static const sqlite3_tokenizer_module _SQLiteTokenizerModule = { +static const sqlite3_tokenizer_module __SQLiteTokenizerModule = { 0, - _SQLiteTokenizerCreate, - _SQLiteTokenizerDestroy, - _SQLiteTokenizerOpen, - _SQLiteTokenizerClose, - _SQLiteTokenizerNext + __SQLiteTokenizerCreate, + __SQLiteTokenizerDestroy, + __SQLiteTokenizerOpen, + __SQLiteTokenizerClose, + __SQLiteTokenizerNext }; -int SQLiteRegisterTokenizer(sqlite3 * db, const char * moduleName, const char * submoduleName, SQLiteTokenizerNextCallback callback) { +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _SQLiteTokenizerMap = [NSMutableDictionary new]; + __SQLiteTokenizerMap = [NSMutableDictionary new]; }); sqlite3_stmt * stmt; - int status = sqlite3_prepare_v2(db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); + int status = sqlite3_prepare_v2((sqlite3 *)db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); if (status != SQLITE_OK ){ return status; } - const sqlite3_tokenizer_module * pModule = &_SQLiteTokenizerModule; + const sqlite3_tokenizer_module * pModule = &__SQLiteTokenizerModule; sqlite3_bind_text(stmt, 1, moduleName, -1, SQLITE_STATIC); sqlite3_bind_blob(stmt, 2, &pModule, sizeof(pModule), SQLITE_STATIC); sqlite3_step(stmt); @@ -212,7 +213,7 @@ int SQLiteRegisterTokenizer(sqlite3 * db, const char * moduleName, const char * return status; } - [_SQLiteTokenizerMap setObject:[callback copy] forKey:[NSString stringWithUTF8String:submoduleName]]; + [__SQLiteTokenizerMap setObject:[callback copy] forKey:[NSString stringWithUTF8String:submoduleName]]; return SQLITE_OK; } From 0708a2705cbc6bdddb20eed556377ed5a2054625 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 28 Mar 2015 13:45:48 -0700 Subject: [PATCH 0235/1046] Tighten naming conventions with SQLite We should prefer, in the raw interface, at least, the same naming conventions as SQLite. This should make it easier to transition with less of a need to reference the documentation. Signed-off-by: Stephen Celis --- Documentation/Index.md | 14 +++++++------- README.md | 6 +++--- SQLite Tests/DatabaseTests.swift | 18 +++++++++--------- SQLite Tests/QueryTests.swift | 10 +++++----- SQLite/Database.swift | 20 ++++++++++---------- SQLite/Query.swift | 32 ++++++++++++++++---------------- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7c25e374..59f935ff 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -429,12 +429,12 @@ The `insert` function can return several different types that are useful in diff // COMMIT TRANSACTION; ``` - - A tuple of the above [`ROWID`][ROWID] and statement: `(id: Int64?, statement: Statement)`, for flexibility. + - A tuple of the above [`ROWID`][ROWID] and statement: `(rowid: Int64?, statement: Statement)`, for flexibility. ``` swift - let (id, statement) = users.insert(email <- "alice@mac.com") - if let id = id { - println("inserted id: \(id)") + let (rowid, statement) = users.insert(email <- "alice@mac.com") + if let rowid = rowid { + println("inserted id: \(rowid)") } else if statement.failed { println("insertion failed: \(statement.reason)") } @@ -898,11 +898,11 @@ Using the `transaction` and `savepoint` functions, we can run a series of statem ``` swift db.transaction() && users.insert(email <- "betty@icloud.com") - && users.insert(email <- "cathy@icloud.com", manager_id <- db.lastId) + && users.insert(email <- "cathy@icloud.com", manager_id <- db.lastInsertRowid) && db.commit() || db.rollback() ``` -> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastId` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. +> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. ## Altering the Schema @@ -1414,7 +1414,7 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building ``` swift stmt.run("alice@mac.com") - db.lastChanges // -> {Some 1} + db.changes // -> {Some 1} ``` Statements with results may be iterated over. diff --git a/README.md b/README.md index 2b60a0c7..39ef82bc 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,9 @@ for email in ["betty@icloud.com", "cathy@icloud.com"] { stmt.run(email) } -db.totalChanges // 3 -db.lastChanges // 1 -db.lastId // 3 +db.totalChanges // 3 +db.changes // 1 +db.lastInsertRowid // 3 for row in db.prepare("SELECT id, email FROM users") { println("id: \(row[0]), email: \(row[1])") diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index 2a313905..07cd1961 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -18,24 +18,24 @@ class DatabaseTests: SQLiteTestCase { XCTAssert(db.readonly) } - func test_lastId_returnsNilOnNewConnections() { - XCTAssert(db.lastId == nil) + func test_lastInsertRowid_returnsNilOnNewConnections() { + XCTAssert(db.lastInsertRowid == nil) } - func test_lastId_returnsLastIdAfterInserts() { + func test_lastInsertRowid_returnsLastIdAfterInserts() { insertUser("alice") - XCTAssert(db.lastId! == 1) + XCTAssert(db.lastInsertRowid! == 1) } - func test_lastChanges_returnsZeroOnNewConnections() { - XCTAssertEqual(0, db.lastChanges) + func test_changes_returnsZeroOnNewConnections() { + XCTAssertEqual(0, db.changes) } - func test_lastChanges_returnsNumberOfChanges() { + func test_changes_returnsNumberOfChanges() { insertUser("alice") - XCTAssertEqual(1, db.lastChanges) + XCTAssertEqual(1, db.changes) insertUser("betsy") - XCTAssertEqual(1, db.lastChanges) + XCTAssertEqual(1, db.changes) } func test_totalChanges_returnsTotalNumberOfChanges() { diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index 723a8e01..b439316a 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -296,11 +296,11 @@ class QueryTests: SQLiteTestCase { } func test_insert_insertsRows() { - XCTAssertEqual(1, users.insert(email <- "alice@example.com", age <- 30).id!) + XCTAssertEqual(1, users.insert(email <- "alice@example.com", age <- 30).rowid!) AssertSQL("INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - XCTAssert(users.insert(email <- "alice@example.com", age <- 30).id == nil) + XCTAssert(users.insert(email <- "alice@example.com", age <- 30).rowid == nil) } func test_insert_withQuery_insertsRows() { @@ -316,15 +316,15 @@ class QueryTests: SQLiteTestCase { db.execute("CREATE TABLE \"timestamps\" (\"id\" INTEGER PRIMARY KEY, \"timestamp\" TEXT DEFAULT CURRENT_DATETIME)") let table = db["timestamps"] - XCTAssertEqual(1, table.insert().id!) + XCTAssertEqual(1, table.insert().rowid!) AssertSQL("INSERT INTO \"timestamps\" DEFAULT VALUES") } func test_replace_replaceRows() { - XCTAssertEqual(1, users.replace(email <- "alice@example.com", age <- 30).id!) + XCTAssertEqual(1, users.replace(email <- "alice@example.com", age <- 30).rowid!) AssertSQL("INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - XCTAssertEqual(1, users.replace(id <- 1, email <- "betty@example.com", age <- 30).id!) + XCTAssertEqual(1, users.replace(id <- 1, email <- "betty@example.com", age <- 30).rowid!) AssertSQL("INSERT OR REPLACE INTO \"users\" (\"id\", \"email\", \"age\") VALUES (1, 'betty@example.com', 30)") } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 69f1d444..eb925ff2 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -54,14 +54,14 @@ public final class Database { // MARK: - /// The last rowid inserted into the database via this connection. - public var lastId: Int64? { - let lastId = sqlite3_last_insert_rowid(handle) - return lastId == 0 ? nil : lastId + public var lastInsertRowid: Int64? { + let rowid = sqlite3_last_insert_rowid(handle) + return rowid == 0 ? nil : rowid } /// The last number of changes (inserts, updates, or deletes) made to the /// database via this connection. - public var lastChanges: Int { + public var changes: Int { return Int(sqlite3_changes(handle)) } @@ -363,7 +363,7 @@ public final class Database { /// Sets a busy timeout to retry after encountering a busy signal (lock). /// /// :param: ms Milliseconds to wait before retrying. - public func timeout(ms: Int) { + public func busyTimeout(ms: Int) { sqlite3_busy_timeout(handle, Int32(ms)) } @@ -374,17 +374,17 @@ public final class Database { /// number of times it’s been called for this lock. If it /// returns true, it will try again. If it returns false, /// no further attempts will be made. - public func busy(callback: ((tries: Int) -> Bool)?) { + public func busyHandler(callback: ((tries: Int) -> Bool)?) { try { if let callback = callback { - self.busy = { callback(tries: Int($0)) ? 1 : 0 } + self.busyHandler = { callback(tries: Int($0)) ? 1 : 0 } } else { - self.busy = nil + self.busyHandler = nil } - return _SQLiteBusyHandler(self.handle, self.busy) + return _SQLiteBusyHandler(self.handle, self.busyHandler) } } - private var busy: _SQLiteBusyHandlerCallback? + private var busyHandler: _SQLiteBusyHandlerCallback? /// Sets a handler to call when a statement is executed with the compiled /// SQL. diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 51a0acda..9048f3d9 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -444,14 +444,14 @@ public struct Query { /// :param: values A list of values to set. /// /// :returns: The rowid. - public func insert(value: Setter, _ more: Setter...) -> Int64? { return insert([value] + more).id } + public func insert(value: Setter, _ more: Setter...) -> Int64? { return insert([value] + more).rowid } /// Runs an INSERT statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The rowid and statement. - public func insert(value: Setter, _ more: Setter...) -> (id: Int64?, statement: Statement) { + public func insert(value: Setter, _ more: Setter...) -> (rowid: Int64?, statement: Statement) { return insert([value] + more) } @@ -460,16 +460,16 @@ public struct Query { /// :param: values An array of values to set. /// /// :returns: The rowid. - public func insert(values: [Setter]) -> Int64? { return insert(values).id } + public func insert(values: [Setter]) -> Int64? { return insert(values).rowid } /// Runs an INSERT statement against the query. /// /// :param: values An array of values to set. /// /// :returns: The rowid and statement. - public func insert(values: [Setter]) -> (id: Int64?, statement: Statement) { + public func insert(values: [Setter]) -> (rowid: Int64?, statement: Statement) { let statement = insertStatement(values).run() - return (statement.failed ? nil : database.lastId, statement) + return (statement.failed ? nil : database.lastInsertRowid, statement) } public func insert(query: Query) -> Int? { return insert(query).changes } @@ -479,16 +479,16 @@ public struct Query { public func insert(query: Query) -> (changes: Int?, statement: Statement) { let expression = query.selectExpression let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings) - return (statement.failed ? nil : database.lastChanges, statement) + return (statement.failed ? nil : database.changes, statement) } - public func insert() -> Int64? { return insert().id } + public func insert() -> Int64? { return insert().rowid } public func insert() -> Statement { return insert().statement } - public func insert() -> (id: Int64?, statement: Statement) { + public func insert() -> (rowid: Int64?, statement: Statement) { let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) DEFAULT VALUES") - return (statement.failed ? nil : database.lastId, statement) + return (statement.failed ? nil : database.lastInsertRowid, statement) } /// Runs a REPLACE statement against the query. @@ -503,14 +503,14 @@ public struct Query { /// :param: values A list of values to set. /// /// :returns: The rowid. - public func replace(values: Setter...) -> Int64? { return replace(values).id } + public func replace(values: Setter...) -> Int64? { return replace(values).rowid } /// Runs a REPLACE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The rowid and statement. - public func replace(values: Setter...) -> (id: Int64?, statement: Statement) { + public func replace(values: Setter...) -> (rowid: Int64?, statement: Statement) { return replace(values) } @@ -519,16 +519,16 @@ public struct Query { /// :param: values An array of values to set. /// /// :returns: The rowid. - public func replace(values: [Setter]) -> Int64? { return replace(values).id } + public func replace(values: [Setter]) -> Int64? { return replace(values).rowid } /// Runs a REPLACE statement against the query. /// /// :param: values An array of values to set. /// /// :returns: The rowid and statement. - public func replace(values: [Setter]) -> (id: Int64?, statement: Statement) { + public func replace(values: [Setter]) -> (rowid: Int64?, statement: Statement) { let statement = insertStatement(values, or: .Replace).run() - return (statement.failed ? nil : database.lastId, statement) + return (statement.failed ? nil : database.lastInsertRowid, statement) } /// Runs an UPDATE statement against the query. @@ -568,7 +568,7 @@ public struct Query { /// :returns: The number of updated rows and statement. public func update(values: [Setter]) -> (changes: Int?, statement: Statement) { let statement = updateStatement(values).run() - return (statement.failed ? nil : database.lastChanges, statement) + return (statement.failed ? nil : database.changes, statement) } /// Runs a DELETE statement against the query. @@ -586,7 +586,7 @@ public struct Query { /// :returns: The number of deleted rows and statement. public func delete() -> (changes: Int?, statement: Statement) { let statement = deleteStatement.run() - return (statement.failed ? nil : database.lastChanges, statement) + return (statement.failed ? nil : database.changes, statement) } // MARK: - Aggregate Functions From 2cc0a17243bce27da7d754d72a58b737db3dead8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 8 Apr 2015 17:55:58 -0700 Subject: [PATCH 0236/1046] Make assertionFailures fatalErrors Should prevent compilation errors for certain schemes. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 4 ++-- SQLite/Statement.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index eb925ff2..2e03aa88 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -513,7 +513,7 @@ public final class Database { case SQLITE_TEXT: return String.fromCString(UnsafePointer(sqlite3_value_text(value)))! case let type: - assertionFailure("unsupported value type: \(type)") + fatalError("unsupported value type: \(type)") } } let result = block(args: arguments) @@ -528,7 +528,7 @@ public final class Database { } else if result == nil { sqlite3_result_null(context) } else { - assertionFailure("unsupported result type: \(result)") + fatalError("unsupported result type: \(result)") } } return _SQLiteCreateFunction(self.handle, function, Int32(argc), deterministic ? 1 : 0, self.functions[function]?[argc]) diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 2dc4bd33..3d33fe4e 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -105,7 +105,7 @@ public final class Statement { } else if let value = value as? Int { bind(value.datatypeValue, atIndex: idx) } else if let value = value { - assertionFailure("tried to bind unexpected value \(value)") + fatalError("tried to bind unexpected value \(value)") } } @@ -296,7 +296,7 @@ extension Cursor: SequenceType { case SQLITE_TEXT: return self[idx] as String case let type: - assertionFailure("unsupported column type: \(type)") + fatalError("unsupported column type: \(type)") } } From fc086d0db479d3a3a9cd210ee5fabde1a0a0d606 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 8 Apr 2015 18:29:29 -0700 Subject: [PATCH 0237/1046] Swift 1.2 GM Signed-off-by: Stephen Celis --- sqlite3/iphoneos.modulemap | 2 +- sqlite3/iphonesimulator.modulemap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlite3/iphoneos.modulemap b/sqlite3/iphoneos.modulemap index 230a1e3e..e1d16f92 100644 --- a/sqlite3/iphoneos.modulemap +++ b/sqlite3/iphoneos.modulemap @@ -1,4 +1,4 @@ module sqlite3 [system] { - header "/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" export * } diff --git a/sqlite3/iphonesimulator.modulemap b/sqlite3/iphonesimulator.modulemap index e1f316a5..2c033c16 100644 --- a/sqlite3/iphonesimulator.modulemap +++ b/sqlite3/iphonesimulator.modulemap @@ -1,4 +1,4 @@ module sqlite3 [system] { - header "/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" export * } From 849d4efe8632eee1bbee036453a52dc8a7e99426 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 11 Apr 2015 10:27:24 -0400 Subject: [PATCH 0238/1046] Support IN condition against subquery Closes #94 Signed-off-by: Stephen Celis --- SQLite Tests/ExpressionTests.swift | 5 +++++ SQLite/Expression.swift | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index 31e94b57..c69c52cc 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -536,6 +536,11 @@ class ExpressionTests: SQLiteTestCase { AssertSQLContains("(\"age\" IN (20))", contains(Set([20]), age)) } + func test_containsFunction_withValueExpressionAndQuery_buildsInExpression() { + let query = users.select(max(age)).group(id) + AssertSQLContains("(\"id\" IN (SELECT max(\"age\") FROM \"users\" GROUP BY \"id\"))", contains(query, id)) + } + func test_plusEquals_withStringExpression_buildsSetter() { users.update(email += email)! users.update(email += email2)! diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 20eaff54..e076b9af 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -769,7 +769,6 @@ public typealias Star = (Expression?, Expression?) -> Expressi public func * (Expression?, Expression?) -> Expression<()> { return Expression(literal: "*") } - public func contains(values: C, column: Expression) -> Expression { let templates = join(", ", [String](count: count(values), repeatedValue: "?")) return infix("IN", column, Expression(literal: "(\(templates))", map(values) { $0.datatypeValue })) @@ -777,6 +776,9 @@ public func contains(values: C, column: Expression) -> Expression { return contains(values, Expression(column)) } +public func contains(values: Query, column: Expression) -> Expression { + return infix("IN", column, wrap("", values.selectExpression) as Expression<()>) +} // MARK: - Modifying From 31752f03194aa830c9bf41637ef1674817876330 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 12 Apr 2015 13:28:55 -0400 Subject: [PATCH 0239/1046] Update OS X header to point to SDK Signed-off-by: Stephen Celis --- sqlite3/macosx.modulemap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite3/macosx.modulemap b/sqlite3/macosx.modulemap index f48e23a1..2442dd69 100644 --- a/sqlite3/macosx.modulemap +++ b/sqlite3/macosx.modulemap @@ -1,4 +1,4 @@ module sqlite3 [system] { - header "/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" export * } From 8749e2ec2f380106ba688665aa3052e6baf873c2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 12 Apr 2015 14:13:14 -0400 Subject: [PATCH 0240/1046] Don't embed subframework It's primarily there for compilation. A fix for App Store rejections ITMS-90205 and ITMS-90206 (closes #88). Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 6dddc4eb..a84363a0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -13,8 +13,6 @@ DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; - DC2393D11ABE37B6003FF113 /* sqlite3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - DC2393D31ABE37D9003FF113 /* sqlite3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -27,13 +25,10 @@ DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DC70AC9B1AC2331400371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC5B12121ABE3298000DA146 /* libsqlite3.dylib */; }; DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; - DCAE4D331ABE0C2100EFCE7A /* sqlite3.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - DCAE4D371ABE0C4C00EFCE7A /* sqlite3.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */; }; @@ -112,18 +107,6 @@ dstSubfolderSpec = 10; files = ( DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */, - DC2393D31ABE37D9003FF113 /* sqlite3.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; - DCAE4D361ABE0C3700EFCE7A /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - DCAE4D371ABE0C4C00EFCE7A /* sqlite3.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -181,7 +164,6 @@ buildActionMask = 2147483647; files = ( DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */, - DCAE4D331ABE0C2100EFCE7A /* sqlite3.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -207,7 +189,6 @@ files = ( DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */, DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */, - DC2393D11ABE37B6003FF113 /* sqlite3.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -403,7 +384,6 @@ DC3773EF19C8CBB3004FCF85 /* Frameworks */, DC3773F019C8CBB3004FCF85 /* Headers */, DC3773F119C8CBB3004FCF85 /* Resources */, - DCAE4D361ABE0C3700EFCE7A /* Embed Frameworks */, ); buildRules = ( ); From 44820eab1131d0cbcff1c27b18a64ccac1c8e66e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 12 Apr 2015 20:07:32 -0400 Subject: [PATCH 0241/1046] Update SQLCipher Signed-off-by: Stephen Celis --- Vendor/sqlcipher | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vendor/sqlcipher b/Vendor/sqlcipher index e1663ef0..fafb0d05 160000 --- a/Vendor/sqlcipher +++ b/Vendor/sqlcipher @@ -1 +1 @@ -Subproject commit e1663ef0034b7c8ce1b85c37f0c72cbd89134233 +Subproject commit fafb0d05bdae394037ac495fe7261573f5f675eb From 1474f15eb5aeab1ebcf32293440dfd30de80031a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 12 Apr 2015 20:12:43 -0400 Subject: [PATCH 0242/1046] Clarify SQLCipher installation It's no longer hidden away in a "Vendor" group. Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 59f935ff..aadeb466 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -84,7 +84,7 @@ You should now be able to `import SQLite` from any of your target’s source fil To install SQLite.swift with [SQLCipher](http://sqlcipher.net) support: - 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If **sqlcipher.xcodeproj** (in the **Vendor** group) is unavailable (and appears red), go to the **Source Control** menu and select **Check Out sqlcipher…** from the **sqlcipher** menu item. + 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If **sqlcipher.xcodeproj** is unavailable (_i.e._, it appears red), go to the **Source Control** menu and select **Check Out sqlcipher…** from the **sqlcipher** menu item. 2. Follow [the instructions above](#installation) with the **SQLiteCipher** target, instead. diff --git a/README.md b/README.md index 39ef82bc..8280c68b 100644 --- a/README.md +++ b/README.md @@ -140,9 +140,9 @@ To install SQLite.swift: To install SQLite.swift with [SQLCipher][] support: 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If - **sqlcipher.xcodeproj** (in the **Vendor** group) is unavailable - (and appears red), go to the **Source Control** menu and select - **Check Out sqlcipher…** from the **sqlcipher** menu item. + **sqlcipher.xcodeproj** is unavailable (_i.e._, it appears red), go to the + **Source Control** menu and select **Check Out sqlcipher…** from the + **sqlcipher** menu item. 2. Follow [the instructions above](#installation) with the **SQLiteCipher** target, instead. From 3e8f7d3bfd5cea57f71de52f3527515e6f61c24d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 12 Apr 2015 21:36:04 -0400 Subject: [PATCH 0243/1046] Update installation image SQLite.swift is a universal framework now, so let's avoid any confusion. Signed-off-by: Stephen Celis --- Documentation/Resources/installation@2x.png | Bin 270223 -> 270350 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/Resources/installation@2x.png b/Documentation/Resources/installation@2x.png index 6bf073ea2e332d6a0acf9b845cac0b5709e4ff67..7a730e5319e05fa76c2ae5adfedefe20b9c33783 100644 GIT binary patch delta 204701 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 z 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

8%rR^n*qGWryMr3 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 W#6QJPnV Date: Mon, 13 Apr 2015 14:34:49 -0400 Subject: [PATCH 0244/1046] Clarify iOS 7 instructions Signed-off-by: Stephen Celis --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index aadeb466..30129d81 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -104,14 +104,14 @@ It’s possible to use SQLite.swift in a target that doesn’t support framework 3. Remove `import sqlite3` (and `@import sqlite3;`) from the SQLite.swift source files that call it. - 4. Add the following lines to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`. + 4. Add the following lines to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`). ``` swift #import #import "SQLite-Bridging.h" ``` -> _Note:_ Adding SQLite.swift source files directly to your application will both remove the `SQLite` module namespace and expose internal functions and variables. Please [report any namespace collisions and bugs](https://github.com/stephencelis/SQLite.swift/issues/new) you encounter. +> _Note:_ Adding SQLite.swift source files directly to your application will both remove the `SQLite` module namespace (no need—or ability—to `import SQLite`) and expose internal functions and variables. Please [report any namespace collisions and bugs](https://github.com/stephencelis/SQLite.swift/issues/new) you encounter. ## Getting Started From 2b0abb08acaf4423bf95449fa4b56e5bc503b2d1 Mon Sep 17 00:00:00 2001 From: Confile Date: Tue, 14 Apr 2015 16:00:17 +0200 Subject: [PATCH 0245/1046] Insert alternative transaction statement --- Documentation/Index.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 30129d81..2ca1e2a8 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -902,6 +902,16 @@ db.transaction() && db.commit() || db.rollback() ``` +The former statement can also be written as +``` swift +db.transaction { _ in + for obj in objects { + stmt.run(obj.email) + } + return .Commit || .Rollback +} +``` + > _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. From 4def261613d6d5bc5e74c3f0da6c3a7974061351 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 14 Apr 2015 10:38:19 -0400 Subject: [PATCH 0246/1046] Add missing import The early module map refactor left SQLCipher users high and dry :( This should fix #100. Signed-off-by: Stephen Celis --- SQLiteCipher/Cipher.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLiteCipher/Cipher.swift b/SQLiteCipher/Cipher.swift index 3fd2f60c..3766a66c 100644 --- a/SQLiteCipher/Cipher.swift +++ b/SQLiteCipher/Cipher.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import sqlite3 + extension Database { public func key(key: String) { From d287345320ee9dde7eb10d98f0aab57400f39ce7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 16 Apr 2015 11:15:13 -0400 Subject: [PATCH 0247/1046] Upgrade Playground It's not as pretty, but it's the only supported solution. Signed-off-by: Stephen Celis --- Documentation/Resources/playground@2x.png | Bin 92803 -> 124729 bytes SQLite.playground/Contents.swift | 218 ++++++++++++++++++ .../Documentation/fragment-0.html | 19 -- .../Documentation/fragment-1.html | 14 -- .../Documentation/fragment-10.html | 9 - .../Documentation/fragment-11.html | 9 - .../Documentation/fragment-12.html | 12 - .../Documentation/fragment-13.html | 9 - .../Documentation/fragment-14.html | 12 - .../Documentation/fragment-15.html | 21 -- .../Documentation/fragment-16.html | 9 - .../Documentation/fragment-17.html | 9 - .../Documentation/fragment-18.html | 24 -- .../Documentation/fragment-19.html | 9 - .../Documentation/fragment-2.html | 12 - .../Documentation/fragment-20.html | 12 - .../Documentation/fragment-21.html | 13 -- .../Documentation/fragment-3.html | 12 - .../Documentation/fragment-4.html | 10 - .../Documentation/fragment-5.html | 9 - .../Documentation/fragment-6.html | 19 -- .../Documentation/fragment-7.html | 9 - .../Documentation/fragment-8.html | 9 - .../Documentation/fragment-9.html | 19 -- .../Documentation/style-1.1.15.css | 65 ------ SQLite.playground/contents.xcplayground | 47 +--- SQLite.playground/section-10.swift | 3 - SQLite.playground/section-12.swift | 4 - SQLite.playground/section-14.swift | 2 - SQLite.playground/section-16.swift | 4 - SQLite.playground/section-18.swift | 10 - SQLite.playground/section-2.swift | 3 - SQLite.playground/section-20.swift | 5 - SQLite.playground/section-22.swift | 1 - SQLite.playground/section-24.swift | 3 - SQLite.playground/section-26.swift | 4 - SQLite.playground/section-28.swift | 4 - SQLite.playground/section-30.swift | 9 - SQLite.playground/section-32.swift | 1 - SQLite.playground/section-34.swift | 2 - SQLite.playground/section-36.swift | 7 - SQLite.playground/section-38.swift | 4 - SQLite.playground/section-4.swift | 10 - SQLite.playground/section-40.swift | 2 - SQLite.playground/section-42.swift | 2 - SQLite.playground/section-6.swift | 4 - SQLite.playground/section-8.swift | 3 - SQLite.xcodeproj/project.pbxproj | 4 +- 48 files changed, 221 insertions(+), 480 deletions(-) create mode 100644 SQLite.playground/Contents.swift delete mode 100644 SQLite.playground/Documentation/fragment-0.html delete mode 100644 SQLite.playground/Documentation/fragment-1.html delete mode 100644 SQLite.playground/Documentation/fragment-10.html delete mode 100644 SQLite.playground/Documentation/fragment-11.html delete mode 100644 SQLite.playground/Documentation/fragment-12.html delete mode 100644 SQLite.playground/Documentation/fragment-13.html delete mode 100644 SQLite.playground/Documentation/fragment-14.html delete mode 100644 SQLite.playground/Documentation/fragment-15.html delete mode 100644 SQLite.playground/Documentation/fragment-16.html delete mode 100644 SQLite.playground/Documentation/fragment-17.html delete mode 100644 SQLite.playground/Documentation/fragment-18.html delete mode 100644 SQLite.playground/Documentation/fragment-19.html delete mode 100644 SQLite.playground/Documentation/fragment-2.html delete mode 100644 SQLite.playground/Documentation/fragment-20.html delete mode 100644 SQLite.playground/Documentation/fragment-21.html delete mode 100644 SQLite.playground/Documentation/fragment-3.html delete mode 100644 SQLite.playground/Documentation/fragment-4.html delete mode 100644 SQLite.playground/Documentation/fragment-5.html delete mode 100644 SQLite.playground/Documentation/fragment-6.html delete mode 100644 SQLite.playground/Documentation/fragment-7.html delete mode 100644 SQLite.playground/Documentation/fragment-8.html delete mode 100644 SQLite.playground/Documentation/fragment-9.html delete mode 100644 SQLite.playground/Documentation/style-1.1.15.css delete mode 100644 SQLite.playground/section-10.swift delete mode 100644 SQLite.playground/section-12.swift delete mode 100644 SQLite.playground/section-14.swift delete mode 100644 SQLite.playground/section-16.swift delete mode 100644 SQLite.playground/section-18.swift delete mode 100644 SQLite.playground/section-2.swift delete mode 100644 SQLite.playground/section-20.swift delete mode 100644 SQLite.playground/section-22.swift delete mode 100644 SQLite.playground/section-24.swift delete mode 100644 SQLite.playground/section-26.swift delete mode 100644 SQLite.playground/section-28.swift delete mode 100644 SQLite.playground/section-30.swift delete mode 100644 SQLite.playground/section-32.swift delete mode 100644 SQLite.playground/section-34.swift delete mode 100644 SQLite.playground/section-36.swift delete mode 100644 SQLite.playground/section-38.swift delete mode 100644 SQLite.playground/section-4.swift delete mode 100644 SQLite.playground/section-40.swift delete mode 100644 SQLite.playground/section-42.swift delete mode 100644 SQLite.playground/section-6.swift delete mode 100644 SQLite.playground/section-8.swift diff --git a/Documentation/Resources/playground@2x.png b/Documentation/Resources/playground@2x.png index b1c62e2387037f581e5d2ceb7a1a7ac55f30ece5..32646d6aa411b7b5a7163fc692c7252fe8ee8cce 100644 GIT binary patch literal 124729 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

lbErWo0fYUhNnO45# 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%0A`zqOC=oLR_v5FR_8z+($29av1W18O7 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 CF|3yW literal 92803 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 _Note_: This playground must be running inside the Xcode project to run. Build the OS X framework prior to use. (You may have to close and reopen the project after building it.) + +# SQLite.swift + +This playground contains sample code to explore [SQLite.swift](https://github.com/stephencelis/SQLite.swift), a [Swift](https://developer.apple.com/swift) wrapper for [SQLite3](https://sqlite.org). + +Let’s get started by importing the framework and opening a new in-memory database connection using the `Database` class. +*/ +import SQLite + +let db = Database() +/*: +This implicitly opens a database in `":memory:"`. To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*, + + Database("path/to/database.sqlite3") + +Pass `nil` or an empty string (`""`) to open a temporary, disk-backed database, instead. + +Once we instantiate a database connection, we can execute SQL statements directly against it. Let’s create a table. +*/ +db.execute( + "CREATE TABLE users (" + + "id INTEGER PRIMARY KEY, " + + "email TEXT NOT NULL UNIQUE, " + + "age INTEGER, " + + "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users(id)" + + ")" +) +/*: +The `execute` function can run multiple SQL statements at once as a convenience and will throw an assertion failure if an error occurs during execution. This is useful for seeding and migrating databases with well-tested statements that are guaranteed to succeed (or where failure can be graceful and silent). + +It’s generally safer to prepare SQL statements individually. Let’s instantiate a `Statement` object and insert a couple rows. + +*/ +let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") +for (email, admin) in ["alice@acme.com": 1, "betsy@acme.com": 0] { + stmt.run(email, admin) +} +/*: +Prepared statements can bind and escape input values safely. In this case, `email` and `admin` columns are bound with different values over two executions. + +The `Database` class exposes information about recently run queries via several properties: `totalChanges` returns the total number of changes (inserts, updates, and deletes) since the connection was opened; `changes` returns the number of changes from the last statement that modified the database; `lastInsertRowid` returns the rowid of the last insert. +*/ +db.totalChanges +db.changes +db.lastInsertRowid +/*: +## Querying + +`Statement` objects act as both sequences _and_ generators. We can iterate over a select statement’s rows directly using a `for`–`in` loop. +*/ +for row in db.prepare("SELECT id, email FROM users") { + println("id: \(row[0]), email: \(row[1])") +} +/*: +Single, scalar values can be plucked directly from a statement. +*/ +let count = db.prepare("SELECT count(*) FROM users") +count.scalar() + +db.scalar("SELECT email FROM users WHERE id = ?", 1) +/*: +> ### Experiment +> +> Try plucking a single row by taking advantage of the fact that `Statement` conforms to the `GeneratorType` protocol. +> +> Also try using the `Array` initializer to return an array of all rows at once. + +## Transactions & Savepoints + +Using the `transaction` and `savepoint` functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back. In the following example we prepare two statements: one to insert a manager into the database, and one—given a manager’s rowid—to insert a managed user into the database. +*/ +let sr = db.prepare("INSERT INTO users (email, admin) VALUES (?, 1)") +let jr = db.prepare("INSERT INTO users (email, admin, manager_id) VALUES (?, 0, ?)") +/*: +Statements can be chained with other statements using the `&&` and `||` operators. The right-hand side is an auto-closure and therefore has access to database information at the time of execution. In this case, we insert Dolly, a supervisor, and immediately reference her rowid when we insert her assistant, Emery. +*/ +db.transaction() + && sr.run("dolly@acme.com") + && jr.run("emery@acme.com", db.lastInsertRowid) + && db.commit() + || db.rollback() +/*: +Our database has a uniqueness constraint on email address, so let’s see what happens when we insert Fiona, who also claims to be managing Emery. +*/ +let txn = db.transaction() + && sr.run("fiona@acme.com") + && jr.run("emery@acme.com", db.lastInsertRowid) + && db.commit() +txn || db.rollback() + +count.scalar() + +txn.failed +txn.reason +/*: +This time, our transaction fails because Emery has already been added to the database. The addition of Fiona has been rolled back, and we’ll need to get to the bottom of this discrepancy (or make some schematic changes to our database to allow for multiple managers per user). + +> ### Experiment +> +> Transactions can’t be nested, but savepoints can! Try calling the `savepoint` function instead, which shares semantics with `transaction`, but can successfully run in layers. + +## Query Building + +SQLite.swift provides a powerful, type-safe query builder. With only a small amount of boilerplate to map our columns to types, we can ensure the queries we build are valid upon compilation. +*/ +let id = Expression("id") +let email = Expression("email") +let age = Expression("age") +let admin = Expression("admin") +let manager_id = Expression("manager_id") +/*: +The query-building interface is provided via the `Query` struct. We can access this interface by subscripting our database connection with a table name. +*/ +let users = db["users"] +/*: +From here, we can build a variety of queries. For example, we can build and run an `INSERT` statement by calling the query’s `insert` function. Let’s add a few new rows this way. +*/ +users.insert(email <- "giles@acme.com", age <- 42, admin <- true).rowid +users.insert(email <- "haley@acme.com", age <- 30, admin <- true).rowid +users.insert(email <- "inigo@acme.com", age <- 24).rowid +/*: +No room for syntax errors! Try changing an input to the wrong type and see what happens. + +The `insert` function can return a `rowid` (which will be `nil` in the case of failure) and the just-run `statement`. It can also return a `Statement` object directly, making it easy to run in a transaction. +*/ +db.transaction() + && users.insert(email <- "julie@acme.com") + && users.insert(email <- "kelly@acme.com", manager_id <- db.lastInsertRowid) + && db.commit() + || db.rollback() +/*: +`Query` objects can also build `SELECT` statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement. +*/ +// SELECT * FROM users +for user in users { + println(user[email]) +} +/*: +You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given `Row` objects, which can be subscripted with the same expressions we prepared above. This gives us a little more powerful of a mapping to work with and pass around. + +Queries can be used and reused, and can quickly return rows, counts and other aggregate values. +*/ +// SELECT * FROM users LIMIT 1 +users.first + +// SELECT count(*) FROM users +users.count + +users.min(age) +users.max(age) +users.average(age) +/*: +> ### Experiment +> +> In addition to `first`, you can also try plucking the `last` row from the result set in an optimized fashion. +> +> The example above uses the computed variable `count`, but `Query` has a `count` function, as well. (The computed variable is actually a convenience wrapper around `count(*)`.) Try counting the distinct ages in our group of users. +> +> Try calling the `sum` and `total` functions. Note the differences! + +Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset. +*/ +let admins = users.filter(admin) +/*: +Filtered queries will in turn filter their aggregate functions. +*/ +// SELECT count(*) FROM users WHERE admin +admins.count +/*: +Alongside `filter`, we can use the `select`, `join`, `group`, `order`, and `limit` functions to compose rich queries with safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows. +*/ +let ordered = admins.order(email.asc, age.asc).limit(3) + +// SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3 +for admin in ordered { + println(admin[id]) + println(admin[age]) +} +/*: +> ### Experiment +> +> Try using the `select` function to specify which columns are returned. +> +> Try using the `group` function to group users by a column. +> +> Try to return results by a column in descending order. +> +> Try using an alternative `limit` function to add an `OFFSET` clause to the query. + +We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages. +*/ +let agelessAdmins = admins.filter(age == nil) + +// SELECT count(*) FROM users WHERE (admin AND age IS NULL) +agelessAdmins.count +/*: +Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s `update` interface to (temporarily) revoke their privileges while we wait for them to update their profiles. +*/ +// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL) +agelessAdmins.update(admin <- false).changes +/*: +If we ever need to remove rows from our database, we can use the `delete` function, which will be scoped to a query’s filters. **Be careful!** You may just want to archive the records, instead. + +We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, _carefully_ scope a query to match her and delete her record. +*/ +// DELETE FROM users WHERE (email = 'alice@acme.com') +users.filter(email == "alice@acme.com").delete().changes +/*: +And that’s that. + +## & More… + +We’ve only explored the surface to SQLite.swift. Dive into the code to discover more! +*/ diff --git a/SQLite.playground/Documentation/fragment-0.html b/SQLite.playground/Documentation/fragment-0.html deleted file mode 100644 index ef4c0d87..00000000 --- a/SQLite.playground/Documentation/fragment-0.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - -

- -

SQLite.swift

-

- This playground contains sample code to explore SQLite.swift, a Swift wrapper for SQLite3. -

-

- Let’s get started by importing the framework and opening a new in-memory database connection using the Database class. -

-
diff --git a/SQLite.playground/Documentation/fragment-1.html b/SQLite.playground/Documentation/fragment-1.html deleted file mode 100644 index 2d7b55c7..00000000 --- a/SQLite.playground/Documentation/fragment-1.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - -
-

- This implicitly opens a database in ":memory:". To open a database at a specific location, pass the path as a parameter during instantiation, e.g., Database("path/to/database.sqlite3"). Pass nil or an empty string ("") to open a temporary, disk-backed database, instead. -

-

- Once we instantiate a database connection, we can execute SQL statements directly against it. Let’s create a table. -

-
diff --git a/SQLite.playground/Documentation/fragment-10.html b/SQLite.playground/Documentation/fragment-10.html deleted file mode 100644 index 449e18b4..00000000 --- a/SQLite.playground/Documentation/fragment-10.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- The query-building interface is provided via the Query struct. We can access this interface by subscripting our database connection with a table name. -

-
diff --git a/SQLite.playground/Documentation/fragment-11.html b/SQLite.playground/Documentation/fragment-11.html deleted file mode 100644 index f9196504..00000000 --- a/SQLite.playground/Documentation/fragment-11.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- From here, we can build a variety of queries. For example, we can build and run an INSERT statement by calling the query’s insert function. Let’s add a few new rows this way. -

-
diff --git a/SQLite.playground/Documentation/fragment-12.html b/SQLite.playground/Documentation/fragment-12.html deleted file mode 100644 index 4b0633fe..00000000 --- a/SQLite.playground/Documentation/fragment-12.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
-

- No room for syntax errors! Try changing an input to the wrong type and see what happens. -

-

- The insert function can return an ID (which will be nil in the case of failure) and the just-run statement. It can also return a Statement object directly, making it easy to run in a transaction. -

-
diff --git a/SQLite.playground/Documentation/fragment-13.html b/SQLite.playground/Documentation/fragment-13.html deleted file mode 100644 index 0341e633..00000000 --- a/SQLite.playground/Documentation/fragment-13.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Query objects can also build SELECT statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement. -

-
diff --git a/SQLite.playground/Documentation/fragment-14.html b/SQLite.playground/Documentation/fragment-14.html deleted file mode 100644 index 716f2a76..00000000 --- a/SQLite.playground/Documentation/fragment-14.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
-

- You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given Row objects, which can be subscripted with the same expressions we prepared above. This gives us a little more powerful of a mapping to work with and pass around. -

-

- Queries can be used and reused, and can quickly return rows, counts and other aggregate values. -

-
diff --git a/SQLite.playground/Documentation/fragment-15.html b/SQLite.playground/Documentation/fragment-15.html deleted file mode 100644 index 850f2bf8..00000000 --- a/SQLite.playground/Documentation/fragment-15.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - -
- -

- Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset. -

-
diff --git a/SQLite.playground/Documentation/fragment-16.html b/SQLite.playground/Documentation/fragment-16.html deleted file mode 100644 index b19d01d4..00000000 --- a/SQLite.playground/Documentation/fragment-16.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Filtered queries will in turn filter their aggregate functions. -

-
diff --git a/SQLite.playground/Documentation/fragment-17.html b/SQLite.playground/Documentation/fragment-17.html deleted file mode 100644 index 9b4e1bde..00000000 --- a/SQLite.playground/Documentation/fragment-17.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Alongside filter, we can use the select, join, group, order, and limit functions to compose rich queries with safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows. -

-
diff --git a/SQLite.playground/Documentation/fragment-18.html b/SQLite.playground/Documentation/fragment-18.html deleted file mode 100644 index 967cf580..00000000 --- a/SQLite.playground/Documentation/fragment-18.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - -
- -

- We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages. -

-
diff --git a/SQLite.playground/Documentation/fragment-19.html b/SQLite.playground/Documentation/fragment-19.html deleted file mode 100644 index c6724957..00000000 --- a/SQLite.playground/Documentation/fragment-19.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s update interface to (temporarily) revoke their privileges while we wait for them to update their profiles. -

-
diff --git a/SQLite.playground/Documentation/fragment-2.html b/SQLite.playground/Documentation/fragment-2.html deleted file mode 100644 index acf1bb08..00000000 --- a/SQLite.playground/Documentation/fragment-2.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
-

- The execute function can run multiple SQL statements at once as a convenience and will throw an assertion failure if an error occurs during execution. This is useful for seeding and migrating databases with well-tested statements that are guaranteed to succeed (or where failure can be graceful and silent). -

-

- It’s generally safer to prepare SQL statements individually. Let’s instantiate a Statement object and insert a couple rows. -

-
diff --git a/SQLite.playground/Documentation/fragment-20.html b/SQLite.playground/Documentation/fragment-20.html deleted file mode 100644 index c3150e7b..00000000 --- a/SQLite.playground/Documentation/fragment-20.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
-

- If we ever need to remove rows from our database, we can use the delete function, which will be scoped to a query’s filters. Be careful! You may just want to archive the records, instead. -

-

- We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, carefully scope a query to match her and delete her record. -

-
diff --git a/SQLite.playground/Documentation/fragment-21.html b/SQLite.playground/Documentation/fragment-21.html deleted file mode 100644 index bea27da9..00000000 --- a/SQLite.playground/Documentation/fragment-21.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - -
-

- And that’s that. -

-

& More…

-

- We’ve only explored the surface to SQLite.swift. Dive into the code to discover more! -

-
diff --git a/SQLite.playground/Documentation/fragment-3.html b/SQLite.playground/Documentation/fragment-3.html deleted file mode 100644 index 8fff27e2..00000000 --- a/SQLite.playground/Documentation/fragment-3.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
-

- Prepared statements can bind and escape input values safely. In this case, email and admin columns are bound with different values over two executions. -

-

- The Database class exposes information about recently run queries via several properties: totalChanges returns the total number of changes (inserts, updates, and deletes) since the connection was opened; lastChanges returns the number of changes from the last statement that modified the database; lastID returns the row ID of the last insert. -

-
diff --git a/SQLite.playground/Documentation/fragment-4.html b/SQLite.playground/Documentation/fragment-4.html deleted file mode 100644 index 988c0027..00000000 --- a/SQLite.playground/Documentation/fragment-4.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - -
-

Querying

-

- Statement objects act as both sequences and generators. We can iterate over a select statement’s rows directly using a forin loop. -

-
diff --git a/SQLite.playground/Documentation/fragment-5.html b/SQLite.playground/Documentation/fragment-5.html deleted file mode 100644 index a66a3352..00000000 --- a/SQLite.playground/Documentation/fragment-5.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Single, scalar values can be plucked directly from a statement. -

-
diff --git a/SQLite.playground/Documentation/fragment-6.html b/SQLite.playground/Documentation/fragment-6.html deleted file mode 100644 index 496e7aad..00000000 --- a/SQLite.playground/Documentation/fragment-6.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - -
- -

Transactions & Savepoints

-

- Using the transaction and savepoint functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back. In the following example we prepare two statements: one to insert a manager into the database, and one—given a manager’s row ID—to insert a managed user into the database. -

-
diff --git a/SQLite.playground/Documentation/fragment-7.html b/SQLite.playground/Documentation/fragment-7.html deleted file mode 100644 index 740dbb2b..00000000 --- a/SQLite.playground/Documentation/fragment-7.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Arguments sent to transaction are auto-closures and therefore have access to Database information at the time of execution. In this case, we insert Dolly, a supervisor, and immediately reference her row ID when we insert her assistant, Emery. -

-
diff --git a/SQLite.playground/Documentation/fragment-8.html b/SQLite.playground/Documentation/fragment-8.html deleted file mode 100644 index a5e1c9b2..00000000 --- a/SQLite.playground/Documentation/fragment-8.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -
-

- Our database has a uniqueness constraint on email address, so let’s see what happens when we insert Fiona, who also claims to be managing Emery. -

-
diff --git a/SQLite.playground/Documentation/fragment-9.html b/SQLite.playground/Documentation/fragment-9.html deleted file mode 100644 index 2f90332f..00000000 --- a/SQLite.playground/Documentation/fragment-9.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - -
-

- This time, our transaction fails because Emery has already been added to the database. The addition of Fiona has been rolled back, and we’ll need to get to the bottom of this discrepancy (or make some schematic changes to our database to allow for multiple managers per user). -

- -

Query Building

-

- SQLite.swift provides a powerful, type-safe query builder. With only a small amount of boilerplate to map our columns to types, we can ensure the queries we build are valid upon compilation. -

-
diff --git a/SQLite.playground/Documentation/style-1.1.15.css b/SQLite.playground/Documentation/style-1.1.15.css deleted file mode 100644 index 12485e1d..00000000 --- a/SQLite.playground/Documentation/style-1.1.15.css +++ /dev/null @@ -1,65 +0,0 @@ -html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a, -abbr,acronym,address,big,cite,code,del,dfn,em,figure,font,img,ins,kbd,q,s, -samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li, -fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td { - border: 0; - font-size: 100%; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline -} -body { - background-color: rgba(255,255,255,0.65); - color: rgba(0,0,0,1); - font-family: Helvetica,sans-serif; - font-size: 62.5%; - margin-left: 15px; -} -section { - padding: 20px 25px 20px 35px -} -h3 { - color: rgba(128,128,128,1); - display: block; - font-size: 2.2em; - font-weight: 100; - margin-bottom: 15px; - margin-top: 20px; -} -p { - color: rgba(65,65,65,1); - font-size: 1.4em; - line-height: 145%; - margin-bottom: 5px; -} -a { - color: rgba(0,136,204,1); - text-decoration: none -} -code { - color: rgba(128,128,128,1); - font-family: Menlo,monospace; - font-size: .9em; - word-wrap: break-word -} -h4 { - color: rgba(128,128,128,1); - font-size: .6em; - font-weight: normal; - letter-spacing: 2px; - margin-bottom: 8px; - text-transform: uppercase -} -aside { - background-color: rgba(249,249,249,1); - border-left: 5px solid rgba(238,238,238,1); - font-size: 1.2em; - margin: 25px 45px 35px 35px; - padding: 15px 15px 7px; - -} -aside p { - font-size: 1em; - margin-bottom: 8px -} diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index e16a107d..3de2b51b 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -1,49 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/SQLite.playground/section-10.swift b/SQLite.playground/section-10.swift deleted file mode 100644 index 316672a1..00000000 --- a/SQLite.playground/section-10.swift +++ /dev/null @@ -1,3 +0,0 @@ -for row in db.prepare("SELECT id, email FROM users") { - println("id: \(row[0]), email: \(row[1])") -} diff --git a/SQLite.playground/section-12.swift b/SQLite.playground/section-12.swift deleted file mode 100644 index 3f216be3..00000000 --- a/SQLite.playground/section-12.swift +++ /dev/null @@ -1,4 +0,0 @@ -let count = db.prepare("SELECT count(*) FROM users") -count.scalar() - -db.scalar("SELECT email FROM users WHERE id = ?", 1) diff --git a/SQLite.playground/section-14.swift b/SQLite.playground/section-14.swift deleted file mode 100644 index 2d93a99e..00000000 --- a/SQLite.playground/section-14.swift +++ /dev/null @@ -1,2 +0,0 @@ -let sr = db.prepare("INSERT INTO users (email, admin) VALUES (?, 1)") -let jr = db.prepare("INSERT INTO users (email, admin, manager_id) VALUES (?, 0, ?)") diff --git a/SQLite.playground/section-16.swift b/SQLite.playground/section-16.swift deleted file mode 100644 index 5bcfe10d..00000000 --- a/SQLite.playground/section-16.swift +++ /dev/null @@ -1,4 +0,0 @@ -db.transaction() && - sr.run("dolly@acme.com") && - jr.run("emery@acme.com", db.lastId) && - db.commit() || db.rollback() diff --git a/SQLite.playground/section-18.swift b/SQLite.playground/section-18.swift deleted file mode 100644 index 0aff1a5b..00000000 --- a/SQLite.playground/section-18.swift +++ /dev/null @@ -1,10 +0,0 @@ -let txn = db.transaction() && - sr.run("fiona@acme.com") && - jr.run("emery@acme.com", db.lastId) && - db.commit() -txn || db.rollback() - -count.scalar() - -txn.failed -txn.reason diff --git a/SQLite.playground/section-2.swift b/SQLite.playground/section-2.swift deleted file mode 100644 index e033f90b..00000000 --- a/SQLite.playground/section-2.swift +++ /dev/null @@ -1,3 +0,0 @@ -import SQLite - -let db = Database() diff --git a/SQLite.playground/section-20.swift b/SQLite.playground/section-20.swift deleted file mode 100644 index df919ece..00000000 --- a/SQLite.playground/section-20.swift +++ /dev/null @@ -1,5 +0,0 @@ -let id = Expression("id") -let email = Expression("email") -let age = Expression("age") -let admin = Expression("admin") -let manager_id = Expression("manager_id") diff --git a/SQLite.playground/section-22.swift b/SQLite.playground/section-22.swift deleted file mode 100644 index 624a7098..00000000 --- a/SQLite.playground/section-22.swift +++ /dev/null @@ -1 +0,0 @@ -let users = db["users"] diff --git a/SQLite.playground/section-24.swift b/SQLite.playground/section-24.swift deleted file mode 100644 index e4fa4acb..00000000 --- a/SQLite.playground/section-24.swift +++ /dev/null @@ -1,3 +0,0 @@ -users.insert(email <- "giles@acme.com", age <- 42, admin <- true).id -users.insert(email <- "haley@acme.com", age <- 30, admin <- true).id -users.insert(email <- "inigo@acme.com", age <- 24).id diff --git a/SQLite.playground/section-26.swift b/SQLite.playground/section-26.swift deleted file mode 100644 index 58a328f3..00000000 --- a/SQLite.playground/section-26.swift +++ /dev/null @@ -1,4 +0,0 @@ -db.transaction() && - users.insert(email <- "julie@acme.com") && - users.insert(email <- "kelly@acme.com", manager_id <- db.lastId) && - db.commit() || db.rollback() diff --git a/SQLite.playground/section-28.swift b/SQLite.playground/section-28.swift deleted file mode 100644 index 043375a4..00000000 --- a/SQLite.playground/section-28.swift +++ /dev/null @@ -1,4 +0,0 @@ -// SELECT * FROM users -for user in users { - println(user[email]) -} diff --git a/SQLite.playground/section-30.swift b/SQLite.playground/section-30.swift deleted file mode 100644 index 618e1e08..00000000 --- a/SQLite.playground/section-30.swift +++ /dev/null @@ -1,9 +0,0 @@ -// SELECT * FROM users LIMIT 1 -users.first - -// SELECT count(*) FROM users -users.count - -users.min(age) -users.max(age) -users.average(age) diff --git a/SQLite.playground/section-32.swift b/SQLite.playground/section-32.swift deleted file mode 100644 index d6d1597c..00000000 --- a/SQLite.playground/section-32.swift +++ /dev/null @@ -1 +0,0 @@ -let admins = users.filter(admin) diff --git a/SQLite.playground/section-34.swift b/SQLite.playground/section-34.swift deleted file mode 100644 index e6e582f0..00000000 --- a/SQLite.playground/section-34.swift +++ /dev/null @@ -1,2 +0,0 @@ -// SELECT count(*) FROM users WHERE admin -admins.count diff --git a/SQLite.playground/section-36.swift b/SQLite.playground/section-36.swift deleted file mode 100644 index 1dadf623..00000000 --- a/SQLite.playground/section-36.swift +++ /dev/null @@ -1,7 +0,0 @@ -let ordered = admins.order(email.asc, age.asc).limit(3) - -// SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3 -for admin in ordered { - println(admin[id]) - println(admin[age]) -} diff --git a/SQLite.playground/section-38.swift b/SQLite.playground/section-38.swift deleted file mode 100644 index c7c0ec5e..00000000 --- a/SQLite.playground/section-38.swift +++ /dev/null @@ -1,4 +0,0 @@ -let agelessAdmins = admins.filter(age == nil) - -// SELECT count(*) FROM users WHERE (admin AND age IS NULL) -agelessAdmins.count diff --git a/SQLite.playground/section-4.swift b/SQLite.playground/section-4.swift deleted file mode 100644 index f8807857..00000000 --- a/SQLite.playground/section-4.swift +++ /dev/null @@ -1,10 +0,0 @@ -db.execute( - "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" -) diff --git a/SQLite.playground/section-40.swift b/SQLite.playground/section-40.swift deleted file mode 100644 index d1700c48..00000000 --- a/SQLite.playground/section-40.swift +++ /dev/null @@ -1,2 +0,0 @@ -// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL) -agelessAdmins.update(admin <- false).changes diff --git a/SQLite.playground/section-42.swift b/SQLite.playground/section-42.swift deleted file mode 100644 index fa9b1a10..00000000 --- a/SQLite.playground/section-42.swift +++ /dev/null @@ -1,2 +0,0 @@ -// DELETE FROM users WHERE (email = 'alice@acme.com') -users.filter(email == "alice@acme.com").delete().changes diff --git a/SQLite.playground/section-6.swift b/SQLite.playground/section-6.swift deleted file mode 100644 index b9afc572..00000000 --- a/SQLite.playground/section-6.swift +++ /dev/null @@ -1,4 +0,0 @@ -let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") -for (email, admin) in ["alice@acme.com": 1, "betsy@acme.com": 0] { - stmt.run(email, admin) -} diff --git a/SQLite.playground/section-8.swift b/SQLite.playground/section-8.swift deleted file mode 100644 index 6b363d01..00000000 --- a/SQLite.playground/section-8.swift +++ /dev/null @@ -1,3 +0,0 @@ -db.totalChanges -db.lastChanges -db.lastId diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a84363a0..1ccac375 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -134,6 +134,7 @@ DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; DC5B12121ABE3298000DA146 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; }; DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + DC97EC9F1ADE955E00F550A6 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../Vendor/fts3_tokenizer.h; sourceTree = ""; }; DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; @@ -145,7 +146,6 @@ DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = ""; }; DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; - DCBC8C301ABE3CDA002B4631 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -245,7 +245,7 @@ isa = PBXGroup; children = ( DC37744719C8F50B004FCF85 /* README.md */, - DCBC8C301ABE3CDA002B4631 /* SQLite.playground */, + DC97EC9F1ADE955E00F550A6 /* SQLite.playground */, DC37742D19C8CC90004FCF85 /* SQLite */, DC10500F19C904DD00D8CA30 /* SQLite Tests */, DCC6B3A11A91949C00734B78 /* SQLiteCipher */, From 09466f34f238996eec1f4fc9ebd4d1703dac0ab6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 17 Apr 2015 18:52:33 -0400 Subject: [PATCH 0248/1046] Update Apple documentation link Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2ca1e2a8..a184c1e4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -662,7 +662,7 @@ users.filter(verified || balance >= 10_000) We can build our own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). -> _Note:_ SQLite.swift defines `filter` instead of `where` because `where` is [a reserved keyword](https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_906). +> _Note:_ SQLite.swift defines `filter` instead of `where` because `where` is [a reserved keyword](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413). ##### Filter Operators and Functions From 38b50fc11666f5fb5bffc9df95f05d28c8515066 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 17 Apr 2015 19:03:22 -0400 Subject: [PATCH 0249/1046] Add basic contributing guidelines A word-for-word of the README's "Communication" section, for now, but hopefully it'll steer more people looking for general help toward Stack Overflow. Signed-off-by: Stephen Celis --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2aedab94 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing + + - Need **help** or have a **general question**? [Ask on Stack + Overflow][] (tag `sqlite.swift`). + - Found a **bug** or have a **feature request**? [Open an issue][]. + - Want to **contribute**? [Submit a pull request][]. + +[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new +[Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork From 571f9a5a6e44ef053ea00e74145099580298f421 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 19 Apr 2015 08:35:49 -0400 Subject: [PATCH 0250/1046] Don't drop offset when fetching first row Fixes #110. Signed-off-by: Stephen Celis --- SQLite Tests/QueryTests.swift | 7 +++++++ SQLite/Query.swift | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index b439316a..f53b1009 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -285,6 +285,13 @@ class QueryTests: SQLiteTestCase { AssertSQL("SELECT * FROM \"users\" LIMIT 1") } + func test_first_withOffset_retainsOffset() { + insertUsers("alice", "betsy") + + XCTAssertEqual(2, users.limit(2, offset: 1).first![id]) + AssertSQL("SELECT * FROM \"users\" LIMIT 1 OFFSET 1") + } + func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() { XCTAssertTrue(users.isEmpty) diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 9048f3d9..cc668f5b 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -731,7 +731,7 @@ public struct Query { /// The first row (or nil if the query returns no rows). public var first: Row? { - var generator = limit(1).generate() + var generator = limit(to: 1, offset: limit?.offset).generate() return generator.next() } From 60d523c298b85b151a409d15644a78291288264a Mon Sep 17 00:00:00 2001 From: Stefan Arentz Date: Fri, 17 Apr 2015 13:41:26 -0400 Subject: [PATCH 0251/1046] Set Allow App Extension API Only --- SQLite.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 1ccac375..4b97124c 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -746,6 +746,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -764,6 +765,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -805,6 +807,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -831,6 +834,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -853,6 +857,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -875,6 +880,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; From 078a76b6b74bf62606ea5828ea1d6f42a55361a4 Mon Sep 17 00:00:00 2001 From: Stefan Arentz Date: Fri, 17 Apr 2015 13:43:16 -0400 Subject: [PATCH 0252/1046] Change the deployment target to 8.0 --- SQLite.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 4b97124c..78ff976e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -821,7 +821,7 @@ ); INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -844,7 +844,7 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; From 7844bf715a0e8e83f7f6cb9e8b63bccf8d0d96ad Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Apr 2015 09:30:31 -0400 Subject: [PATCH 0253/1046] Move fts3_tokenizer to ease Frameworkless Target install Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 12 ++++++------ {Vendor => SQLite}/fts3_tokenizer.h | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename {Vendor => SQLite}/fts3_tokenizer.h (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 78ff976e..fdfa7d58 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; + DC2F675E1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */; }; + DC2F675F1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */; }; DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; }; DC37743519C8D626004FCF85 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; @@ -25,8 +27,6 @@ DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; - DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */; }; DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; @@ -118,6 +118,7 @@ DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; DC3773F319C8CBB3004FCF85 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../SQLite/Info.plist; sourceTree = ""; }; DC3773F819C8CBB3004FCF85 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLite.h; path = ../SQLite/SQLite.h; sourceTree = ""; }; @@ -135,7 +136,6 @@ DC5B12121ABE3298000DA146 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; }; DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DC97EC9F1ADE955E00F550A6 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; - DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../Vendor/fts3_tokenizer.h; sourceTree = ""; }; DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = sqlite3.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -298,7 +298,7 @@ DC3773F819C8CBB3004FCF85 /* SQLite.h */, DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */, DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */, - DC9D389B1AAD458500780AE7 /* fts3_tokenizer.h */, + DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */, DC3773F719C8CBB3004FCF85 /* Info.plist */, DC37744219C8DC91004FCF85 /* libsqlite3.dylib */, DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */, @@ -350,7 +350,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DC9D389C1AAD458500780AE7 /* fts3_tokenizer.h in Headers */, + DC2F675E1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */, DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */, DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, ); @@ -367,7 +367,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DC9D389D1AAD458500780AE7 /* fts3_tokenizer.h in Headers */, + DC2F675F1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */, DCC6B3791A9191C300734B78 /* SQLite.h in Headers */, DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, ); diff --git a/Vendor/fts3_tokenizer.h b/SQLite/fts3_tokenizer.h similarity index 100% rename from Vendor/fts3_tokenizer.h rename to SQLite/fts3_tokenizer.h From 1db85271f275e8189c6891439cf8168e05d3fa51 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 24 Apr 2015 09:32:50 -0400 Subject: [PATCH 0254/1046] Clarify what kinds of Frameworkless bugs to report Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index a184c1e4..25e735ac 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -111,7 +111,7 @@ It’s possible to use SQLite.swift in a target that doesn’t support framework #import "SQLite-Bridging.h" ``` -> _Note:_ Adding SQLite.swift source files directly to your application will both remove the `SQLite` module namespace (no need—or ability—to `import SQLite`) and expose internal functions and variables. Please [report any namespace collisions and bugs](https://github.com/stephencelis/SQLite.swift/issues/new) you encounter. +> _Note:_ Adding SQLite.swift source files directly to your application will both remove the `SQLite` module namespace (no need—or ability—to `import SQLite`) and expose internal functions and variables. You will need to rename anything that conflicts with code of your own. Please [report any bugs](https://github.com/stephencelis/SQLite.swift/issues/new) (_e.g._, segfaults) you encounter. ## Getting Started From 8a85d80b0e8acf5f29a71e3abedc016e83381571 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Apr 2015 08:49:55 -0400 Subject: [PATCH 0255/1046] DRY up project configuration Let's utilize the xcconfig file where we can. In this case, ensure that the 8.0 deployment target applies to everything. Fixes #119. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 29 +++-------------------------- SQLite/SQLite.xcconfig | 26 ++++++++++++++------------ 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fdfa7d58..59642fc0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -723,7 +723,6 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_NS_ASSERTIONS = NO; @@ -746,7 +745,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -765,7 +763,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -783,10 +780,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; @@ -807,26 +801,18 @@ isa = XCBuildConfiguration; baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -834,7 +820,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -844,12 +829,9 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; @@ -857,7 +839,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -880,7 +861,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -902,10 +882,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); + GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "SQLiteCipher Tests"; SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; diff --git a/SQLite/SQLite.xcconfig b/SQLite/SQLite.xcconfig index 5e02dd58..5abd8549 100644 --- a/SQLite/SQLite.xcconfig +++ b/SQLite/SQLite.xcconfig @@ -1,24 +1,26 @@ -SWIFT_WHOLE_MODULE_OPTIMIZATION = YES +APPLICATION_EXTENSION_API_ONLY = YES +SWIFT_WHOLE_MODULE_OPTIMIZATION = YES // Universal Framework -SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx +SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx -VALID_ARCHS[sdk=iphone*] = arm64 armv7 armv7s -VALID_ARCHS[sdk=macosx*] = i386 x86_64 +VALID_ARCHS[sdk=iphone*] = arm64 armv7 armv7s +VALID_ARCHS[sdk=macosx*] = i386 x86_64 -LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks -LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks -FRAMEWORK_SEARCH_PATHS[sdk=iphone*] = $(inherited) $(SDKROOT)/Developer/Library/Frameworks -FRAMEWORK_SEARCH_PATHS[sdk=macosx*] = $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) +FRAMEWORK_SEARCH_PATHS[sdk=iphone*] = $(inherited) $(SDKROOT)/Developer/Library/Frameworks +FRAMEWORK_SEARCH_PATHS[sdk=macosx*] = $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) // Platform-specific // iOS -CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer -TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer +IPHONEOS_DEPLOYMENT_TARGET[sdk=iphone*] = 8.0 +TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 // OS X -FRAMEWORK_VERSION[sdk=macosx*] = A -COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES \ No newline at end of file +FRAMEWORK_VERSION[sdk=macosx*] = A +COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES \ No newline at end of file From 213920b2f65d7ae92bb2c93cfa02a2e6e19f8ee9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Apr 2015 08:51:06 -0400 Subject: [PATCH 0256/1046] Fix ALTER TABLE documentation Copypasta typically tastes a bit funny. Signed-off-by: Stephen Celis --- Documentation/Index.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 25e735ac..d8d7da44 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -965,11 +965,13 @@ The `alter` function shares several of the same [`column` function parameters](# - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - t.column(email, collate: .Nocase) - // email TEXT NOT NULL COLLATE "NOCASE" + t.alter(table: users, add: email, collate: .Nocase) + // ALTER TABLE "users" + // ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" - t.column(name, collate: .Rtrim) - // name TEXT COLLATE "RTRIM" + t.alter(table: users, add: name, collate: .Rtrim) + // ALTER TABLE "users" + // ADD COLUMN "name" TEXT COLLATE "RTRIM" ``` - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) From 2bf90d49cc982589b6d7ed9585f3f8f41ccf3263 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Apr 2015 09:01:42 -0400 Subject: [PATCH 0257/1046] Use single module map file for sqlite3 shim Per the following: https://twitter.com/jckarter/status/590928617770602496 Fixes #116. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 16 ++++++---------- sqlite3/iphoneos.modulemap | 4 ---- sqlite3/iphonesimulator.modulemap | 4 ---- sqlite3/macosx.modulemap | 4 ---- sqlite3/module.modulemap | 4 ++++ sqlite3/sqlite3.xcconfig | 5 ----- 6 files changed, 10 insertions(+), 27 deletions(-) delete mode 100644 sqlite3/iphoneos.modulemap delete mode 100644 sqlite3/iphonesimulator.modulemap delete mode 100644 sqlite3/macosx.modulemap create mode 100644 sqlite3/module.modulemap delete mode 100644 sqlite3/sqlite3.xcconfig diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 59642fc0..21ff9d4d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -140,10 +140,7 @@ DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = sqlite3.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCAE4D181ABE0B3300EFCE7A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = sqlite3.xcconfig; sourceTree = ""; }; - DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = ""; }; - DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = ""; }; - DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = ""; }; + DCAE4D2F1ABE0B8B00EFCE7A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; @@ -319,10 +316,7 @@ children = ( DCAE4D181ABE0B3300EFCE7A /* Info.plist */, DC5B12121ABE3298000DA146 /* libsqlite3.dylib */, - DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */, - DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */, - DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */, - DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */, + DCAE4D2F1ABE0B8B00EFCE7A /* module.modulemap */, ); name = "Supporting Files"; sourceTree = ""; @@ -799,7 +793,7 @@ }; DCAE4D291ABE0B3400EFCE7A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; + baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -811,6 +805,7 @@ INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "$(SRCROOT)/sqlite3/module.modulemap"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -818,7 +813,7 @@ }; DCAE4D2A1ABE0B3400EFCE7A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; + baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -830,6 +825,7 @@ INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "$(SRCROOT)/sqlite3/module.modulemap"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; diff --git a/sqlite3/iphoneos.modulemap b/sqlite3/iphoneos.modulemap deleted file mode 100644 index e1d16f92..00000000 --- a/sqlite3/iphoneos.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/iphonesimulator.modulemap b/sqlite3/iphonesimulator.modulemap deleted file mode 100644 index 2c033c16..00000000 --- a/sqlite3/iphonesimulator.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/macosx.modulemap b/sqlite3/macosx.modulemap deleted file mode 100644 index 2442dd69..00000000 --- a/sqlite3/macosx.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/module.modulemap b/sqlite3/module.modulemap new file mode 100644 index 00000000..f48e23a1 --- /dev/null +++ b/sqlite3/module.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/sqlite3.xcconfig b/sqlite3/sqlite3.xcconfig deleted file mode 100644 index 4b92f889..00000000 --- a/sqlite3/sqlite3.xcconfig +++ /dev/null @@ -1,5 +0,0 @@ -#include "../SQLite/SQLite.xcconfig" - -MODULEMAP_FILE[sdk=iphoneos*] = $(SRCROOT)/sqlite3/iphoneos.modulemap -MODULEMAP_FILE[sdk=iphonesimulator*] = $(SRCROOT)/sqlite3/iphonesimulator.modulemap -MODULEMAP_FILE[sdk=macosx*] = $(SRCROOT)/sqlite3/macosx.modulemap From cb71a4339a158d5b58c5415c579a644781b817cb Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 27 Apr 2015 09:14:33 -0400 Subject: [PATCH 0258/1046] Disable App Extension requirement for test targets Otherwise we get a warning every time we build them. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 21ff9d4d..96cbffd0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -774,6 +774,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -785,6 +786,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; @@ -878,6 +880,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "SQLiteCipher Tests"; @@ -889,6 +892,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; INFOPLIST_FILE = "SQLite Tests/Info.plist"; PRODUCT_NAME = "SQLiteCipher Tests"; SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; From 036288a8dd64bd74e30105870cfaef449bddbffe Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 28 Apr 2015 08:40:44 -0400 Subject: [PATCH 0259/1046] Revert "Use single module map file for sqlite3 shim" This reverts commit 2bf90d49cc982589b6d7ed9585f3f8f41ccf3263. It looks like the module map's path is not relative to the SDK root after all (as seen by reporters of #120). --- SQLite.xcodeproj/project.pbxproj | 16 ++++++++++------ sqlite3/iphoneos.modulemap | 4 ++++ sqlite3/iphonesimulator.modulemap | 4 ++++ sqlite3/macosx.modulemap | 4 ++++ sqlite3/module.modulemap | 4 ---- sqlite3/sqlite3.xcconfig | 5 +++++ 6 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 sqlite3/iphoneos.modulemap create mode 100644 sqlite3/iphonesimulator.modulemap create mode 100644 sqlite3/macosx.modulemap delete mode 100644 sqlite3/module.modulemap create mode 100644 sqlite3/sqlite3.xcconfig diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 96cbffd0..738db788 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -140,7 +140,10 @@ DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = sqlite3.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DCAE4D181ABE0B3300EFCE7A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DCAE4D2F1ABE0B8B00EFCE7A /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = sqlite3.xcconfig; sourceTree = ""; }; + DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = ""; }; + DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = ""; }; + DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = ""; }; DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; @@ -316,7 +319,10 @@ children = ( DCAE4D181ABE0B3300EFCE7A /* Info.plist */, DC5B12121ABE3298000DA146 /* libsqlite3.dylib */, - DCAE4D2F1ABE0B8B00EFCE7A /* module.modulemap */, + DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */, + DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */, + DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */, + DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */, ); name = "Supporting Files"; sourceTree = ""; @@ -795,7 +801,7 @@ }; DCAE4D291ABE0B3400EFCE7A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -807,7 +813,6 @@ INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "$(SRCROOT)/sqlite3/module.modulemap"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -815,7 +820,7 @@ }; DCAE4D2A1ABE0B3400EFCE7A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; buildSettings = { COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -827,7 +832,6 @@ INFOPLIST_FILE = sqlite3/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MODULEMAP_FILE = "$(SRCROOT)/sqlite3/module.modulemap"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; diff --git a/sqlite3/iphoneos.modulemap b/sqlite3/iphoneos.modulemap new file mode 100644 index 00000000..e1d16f92 --- /dev/null +++ b/sqlite3/iphoneos.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/iphonesimulator.modulemap b/sqlite3/iphonesimulator.modulemap new file mode 100644 index 00000000..2c033c16 --- /dev/null +++ b/sqlite3/iphonesimulator.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/macosx.modulemap b/sqlite3/macosx.modulemap new file mode 100644 index 00000000..2442dd69 --- /dev/null +++ b/sqlite3/macosx.modulemap @@ -0,0 +1,4 @@ +module sqlite3 [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" + export * +} diff --git a/sqlite3/module.modulemap b/sqlite3/module.modulemap deleted file mode 100644 index f48e23a1..00000000 --- a/sqlite3/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/sqlite3.xcconfig b/sqlite3/sqlite3.xcconfig new file mode 100644 index 00000000..4b92f889 --- /dev/null +++ b/sqlite3/sqlite3.xcconfig @@ -0,0 +1,5 @@ +#include "../SQLite/SQLite.xcconfig" + +MODULEMAP_FILE[sdk=iphoneos*] = $(SRCROOT)/sqlite3/iphoneos.modulemap +MODULEMAP_FILE[sdk=iphonesimulator*] = $(SRCROOT)/sqlite3/iphonesimulator.modulemap +MODULEMAP_FILE[sdk=macosx*] = $(SRCROOT)/sqlite3/macosx.modulemap From 610138e7f88920180811e98762adf4e1b353f9df Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 30 Apr 2015 09:34:55 -0400 Subject: [PATCH 0260/1046] UTF-8 the docs Signed-off-by: Stephen Celis --- SQLite/Database.swift | 2 +- SQLite/Expression.swift | 4 ++-- SQLite/Statement.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 2e03aa88..dde88510 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -491,7 +491,7 @@ public final class Database { /// /// :param: block A block of code to run when the function is /// called. The block is called with an array of raw - /// SQL values mapped to the function's parameters and + /// SQL values mapped to the function’s parameters and /// should return a raw SQL value (or nil). public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { try { diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index e076b9af..7ba8bc5f 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -129,7 +129,7 @@ public struct Expression { return expression } - // naïve compiler for statements that can't be bound, e.g., CREATE TABLE + // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE internal func compile() -> String { var idx = 0 return reduce(SQL, "") { SQL, character in @@ -791,7 +791,7 @@ public typealias Setter = (Expressible, Expressible) /// /// :param: value The value the column is being set to. /// -/// :returns: A setter that can be used in a Query's insert and update +/// :returns: A setter that can be used in a Query’s insert and update /// functions. public func set(column: Expression, value: V) -> Setter { return (column, Expression<()>(value: value)) diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 3d33fe4e..ddebf9d5 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -240,7 +240,7 @@ public func || (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement return lhs.failed ? rhs() : lhs } -/// Cursors provide direct access to a statement's current row. +/// Cursors provide direct access to a statement’s current row. public struct Cursor { private let handle: COpaquePointer From 532a3777ccd1164284c3329cb06d671f82386d0e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 30 Apr 2015 09:35:45 -0400 Subject: [PATCH 0261/1046] Make aggregate function shims private Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 39 -------------------- SQLite/Query.swift | 81 +++++++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 7ba8bc5f..43b92948 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -719,45 +719,6 @@ public func total(expression: Expression) public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } -internal func SQLite_count(expression: Expression) -> Expression { return count(expression) } - -internal func SQLite_count(#distinct: Expression) -> Expression { return count(distinct: distinct) } -internal func SQLite_count(#distinct: Expression) -> Expression { return count(distinct: distinct) } - -internal func SQLite_count(star: Star) -> Expression { return count(star) } - -internal func SQLite_max(expression: Expression) -> Expression { - return max(expression) -} -internal func SQLite_max(expression: Expression) -> Expression { - return max(expression) -} - -internal func SQLite_min(expression: Expression) -> Expression { - return min(expression) -} -internal func SQLite_min(expression: Expression) -> Expression { - return min(expression) -} - -internal func SQLite_average(expression: Expression) -> Expression { return average(expression) } -internal func SQLite_average(expression: Expression) -> Expression { return average(expression) } - -internal func SQLite_average(#distinct: Expression) -> Expression { return average(distinct: distinct) } -internal func SQLite_average(#distinct: Expression) -> Expression { return average(distinct: distinct) } - -internal func SQLite_sum(expression: Expression) -> Expression { return sum(expression) } -internal func SQLite_sum(expression: Expression) -> Expression { return sum(expression) } - -internal func SQLite_sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } -internal func SQLite_sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } - -internal func SQLite_total(expression: Expression) -> Expression { return total(expression) } -internal func SQLite_total(expression: Expression) -> Expression { return total(expression) } - -internal func SQLite_total(#distinct: Expression) -> Expression { return total(distinct: distinct) } -internal func SQLite_total(#distinct: Expression) -> Expression { return total(distinct: distinct) } - private func wrapDistinct(function: String, expression: Expression) -> Expression { return wrap(function, Expression<()>.join(" ", [Expression<()>(literal: "DISTINCT"), expression])) } diff --git a/SQLite/Query.swift b/SQLite/Query.swift index cc668f5b..22fcf5c6 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -597,7 +597,7 @@ public struct Query { /// /// :returns: The number of rows matching the given column. public func count(column: Expression) -> Int { - return calculate(SQLite_count(column))! + return calculate(_count(column))! } /// Runs count() with DISTINCT against the query. @@ -606,7 +606,7 @@ public struct Query { /// /// :returns: The number of rows matching the given column. public func count(distinct column: Expression) -> Int { - return calculate(SQLite_count(distinct: column))! + return calculate(_count(distinct: column))! } /// Runs count() with DISTINCT against the query. @@ -615,7 +615,7 @@ public struct Query { /// /// :returns: The number of rows matching the given column. public func count(distinct column: Expression) -> Int { - return calculate(SQLite_count(distinct: column))! + return calculate(_count(distinct: column))! } /// Runs max() against the query. @@ -624,10 +624,10 @@ public struct Query { /// /// :returns: The largest value of the given column. public func max(column: Expression) -> V? { - return calculate(SQLite_max(column)) + return calculate(_max(column)) } public func max(column: Expression) -> V? { - return calculate(SQLite_max(column)) + return calculate(_max(column)) } /// Runs min() against the query. @@ -636,10 +636,10 @@ public struct Query { /// /// :returns: The smallest value of the given column. public func min(column: Expression) -> V? { - return calculate(SQLite_min(column)) + return calculate(_min(column)) } public func min(column: Expression) -> V? { - return calculate(SQLite_min(column)) + return calculate(_min(column)) } /// Runs avg() against the query. @@ -648,10 +648,10 @@ public struct Query { /// /// :returns: The average value of the given column. public func average(column: Expression) -> Double? { - return calculate(SQLite_average(column)) + return calculate(_average(column)) } public func average(column: Expression) -> Double? { - return calculate(SQLite_average(column)) + return calculate(_average(column)) } /// Runs avg() with DISTINCT against the query. @@ -660,10 +660,10 @@ public struct Query { /// /// :returns: The average value of the given column. public func average(distinct column: Expression) -> Double? { - return calculate(SQLite_average(distinct: column)) + return calculate(_average(distinct: column)) } public func average(distinct column: Expression) -> Double? { - return calculate(SQLite_average(distinct: column)) + return calculate(_average(distinct: column)) } /// Runs sum() against the query. @@ -672,10 +672,10 @@ public struct Query { /// /// :returns: The sum of the given column’s values. public func sum(column: Expression) -> V? { - return calculate(SQLite_sum(column)) + return calculate(_sum(column)) } public func sum(column: Expression) -> V? { - return calculate(SQLite_sum(column)) + return calculate(_sum(column)) } /// Runs sum() with DISTINCT against the query. @@ -684,10 +684,10 @@ public struct Query { /// /// :returns: The sum of the given column’s values. public func sum(distinct column: Expression) -> V? { - return calculate(SQLite_sum(distinct: column)) + return calculate(_sum(distinct: column)) } public func sum(distinct column: Expression) -> V? { - return calculate(SQLite_sum(distinct: column)) + return calculate(_sum(distinct: column)) } /// Runs total() against the query. @@ -696,10 +696,10 @@ public struct Query { /// /// :returns: The total of the given column’s values. public func total(column: Expression) -> Double { - return calculate(SQLite_total(column))! + return calculate(_total(column))! } public func total(column: Expression) -> Double { - return calculate(SQLite_total(column))! + return calculate(_total(column))! } /// Runs total() with DISTINCT against the query. @@ -708,10 +708,10 @@ public struct Query { /// /// :returns: The total of the given column’s values. public func total(distinct column: Expression) -> Double { - return calculate(SQLite_total(distinct: column))! + return calculate(_total(distinct: column))! } public func total(distinct column: Expression) -> Double { - return calculate(SQLite_total(distinct: column))! + return calculate(_total(distinct: column))! } private func calculate(expression: Expression) -> V? { @@ -724,7 +724,7 @@ public struct Query { // MARK: - Array /// Runs count(*) against the query and returns it. - public var count: Int { return calculate(SQLite_count(*))! } + public var count: Int { return calculate(_count(*))! } /// Returns true if the query has no rows. public var isEmpty: Bool { return first == nil } @@ -880,3 +880,44 @@ extension Database { } } + +// Private shims provide frameworkless support by avoiding the SQLite module namespace. + +private func _count(expression: Expression) -> Expression { return count(expression) } + +private func _count(#distinct: Expression) -> Expression { return count(distinct: distinct) } +private func _count(#distinct: Expression) -> Expression { return count(distinct: distinct) } + +private func _count(star: Star) -> Expression { return count(star) } + +private func _max(expression: Expression) -> Expression { + return max(expression) +} +private func _max(expression: Expression) -> Expression { + return max(expression) +} + +private func _min(expression: Expression) -> Expression { + return min(expression) +} +private func _min(expression: Expression) -> Expression { + return min(expression) +} + +private func _average(expression: Expression) -> Expression { return average(expression) } +private func _average(expression: Expression) -> Expression { return average(expression) } + +private func _average(#distinct: Expression) -> Expression { return average(distinct: distinct) } +private func _average(#distinct: Expression) -> Expression { return average(distinct: distinct) } + +private func _sum(expression: Expression) -> Expression { return sum(expression) } +private func _sum(expression: Expression) -> Expression { return sum(expression) } + +private func _sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } +private func _sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } + +private func _total(expression: Expression) -> Expression { return total(expression) } +private func _total(expression: Expression) -> Expression { return total(expression) } + +private func _total(#distinct: Expression) -> Expression { return total(distinct: distinct) } +private func _total(#distinct: Expression) -> Expression { return total(distinct: distinct) } From e19813d0a385a0d551d94d75c649b20ad0db64ba Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 30 Apr 2015 09:36:49 -0400 Subject: [PATCH 0262/1046] Expose REGEXP operator Still need to create(function: "regexp") yourself. Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 43b92948..829825db 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -529,18 +529,18 @@ public func ~= ) -> Expression { - return infix("LIKE", expression, Expression(binding: string)) +public func like(pattern: String, expression: Expression) -> Expression { + return infix("LIKE", expression, Expression(binding: pattern)) } -public func like(string: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(binding: string)) +public func like(pattern: String, expression: Expression) -> Expression { + return infix("LIKE", expression, Expression(binding: pattern)) } -public func glob(string: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(binding: string)) +public func glob(pattern: String, expression: Expression) -> Expression { + return infix("GLOB", expression, Expression(binding: pattern)) } -public func glob(string: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(binding: string)) +public func glob(pattern: String, expression: Expression) -> Expression { + return infix("GLOB", expression, Expression(binding: pattern)) } public func match(string: String, expression: Expression) -> Expression { @@ -550,6 +550,13 @@ public func match(string: String, expression: Expression) -> Expression return infix("MATCH", expression, Expression(binding: string)) } +public func regexp(pattern: String, expression: Expression) -> Expression { + return infix("REGEXP", expression, Expression(binding: pattern)) +} +public func regexp(pattern: String, expression: Expression) -> Expression { + return infix("REGEXP", expression, Expression(binding: pattern)) +} + // MARK: Compound public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } From 38a585c470cc1f2c4d88101e94f6a3eb6d6b9908 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 30 Apr 2015 09:37:37 -0400 Subject: [PATCH 0263/1046] Make rowid public Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 829825db..87f0fb74 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -732,6 +732,8 @@ private func wrapDistinct(function: String, expression: Expression) -> // MARK: - Helper +public let rowid = Expression("ROWID") + public typealias Star = (Expression?, Expression?) -> Expression<()> public func * (Expression?, Expression?) -> Expression<()> { @@ -900,8 +902,6 @@ public postfix func -- (column: Expression("ROWID") - internal func transcode(literal: Binding?) -> String { if let literal = literal { if let literal = literal as? Blob { return literal.description } From 14573e4c83c71d7e37a8fa306303e2525e5ba9f5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 30 Apr 2015 09:40:53 -0400 Subject: [PATCH 0264/1046] Remove replace(...) in favor of insert(or:...) It's more flexible to expose all the ON CONFLICT actions. Signed-off-by: Stephen Celis --- Documentation/Index.md | 3 ++ SQLite Tests/QueryTests.swift | 4 +- SQLite/Query.swift | 76 ++++++++++++----------------------- 3 files changed, 30 insertions(+), 53 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index d8d7da44..4690a24d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -398,6 +398,9 @@ We can insert rows into a table by calling a [query’s](#queries) `insert` func ``` swift users.insert(email <- "alice@mac.com", name <- "Alice")? // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') + +users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.") +// INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` The `insert` function can return several different types that are useful in different contexts. diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index f53b1009..e06e8f05 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -328,10 +328,10 @@ class QueryTests: SQLiteTestCase { } func test_replace_replaceRows() { - XCTAssertEqual(1, users.replace(email <- "alice@example.com", age <- 30).rowid!) + XCTAssertEqual(1, users.insert(or: .Replace, email <- "alice@example.com", age <- 30).rowid!) AssertSQL("INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - XCTAssertEqual(1, users.replace(id <- 1, email <- "betty@example.com", age <- 30).rowid!) + XCTAssertEqual(1, users.insert(or: .Replace, id <- 1, email <- "betty@example.com", age <- 30).rowid!) AssertSQL("INSERT OR REPLACE INTO \"users\" (\"id\", \"email\", \"age\") VALUES (1, 'betty@example.com', 30)") } diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 22fcf5c6..15a1902d 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -362,7 +362,7 @@ public struct Query { } - private func insertStatement(values: [Setter], or: OnConflict? = nil) -> Statement { + private func insertStatement(or: OnConflict? = nil, _ values: [Setter]) -> Statement { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(tableName.unaliased.SQL)")] @@ -434,41 +434,55 @@ public struct Query { /// Runs an INSERT statement against the query. /// - /// :param: values A list of values to set. + /// :param: action An action to run in case of a conflict. + /// :param: value A value to set. + /// :param: more A list of additional values to set. /// /// :returns: The statement. - public func insert(value: Setter, _ more: Setter...) -> Statement { return insert([value] + more).statement } + public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Statement { + return insert([value] + more).statement + } /// Runs an INSERT statement against the query. /// - /// :param: values A list of values to set. + /// :param: action An action to run in case of a conflict. + /// :param: value A value to set. + /// :param: more A list of additional values to set. /// /// :returns: The rowid. - public func insert(value: Setter, _ more: Setter...) -> Int64? { return insert([value] + more).rowid } + public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Int64? { + return insert(or: action, [value] + more).rowid + } /// Runs an INSERT statement against the query. /// - /// :param: values A list of values to set. + /// :param: action An action to run in case of a conflict. + /// :param: value A value to set. + /// :param: more A list of additional values to set. /// /// :returns: The rowid and statement. - public func insert(value: Setter, _ more: Setter...) -> (rowid: Int64?, statement: Statement) { - return insert([value] + more) + public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> (rowid: Int64?, statement: Statement) { + return insert(or: action, [value] + more) } /// Runs an INSERT statement against the query. /// + /// :param: action An action to run in case of a conflict. /// :param: values An array of values to set. /// /// :returns: The rowid. - public func insert(values: [Setter]) -> Int64? { return insert(values).rowid } + public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Int64? { + return insert(or: action, values).rowid + } /// Runs an INSERT statement against the query. /// + /// :param: action An action to run in case of a conflict. /// :param: values An array of values to set. /// /// :returns: The rowid and statement. - public func insert(values: [Setter]) -> (rowid: Int64?, statement: Statement) { - let statement = insertStatement(values).run() + public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> (rowid: Int64?, statement: Statement) { + let statement = insertStatement(or: action, values).run() return (statement.failed ? nil : database.lastInsertRowid, statement) } @@ -491,46 +505,6 @@ public struct Query { return (statement.failed ? nil : database.lastInsertRowid, statement) } - /// Runs a REPLACE statement against the query. - /// - /// :param: values A list of values to set. - /// - /// :returns: The statement. - public func replace(values: Setter...) -> Statement { return replace(values).statement } - - /// Runs a REPLACE statement against the query. - /// - /// :param: values A list of values to set. - /// - /// :returns: The rowid. - public func replace(values: Setter...) -> Int64? { return replace(values).rowid } - - /// Runs a REPLACE statement against the query. - /// - /// :param: values A list of values to set. - /// - /// :returns: The rowid and statement. - public func replace(values: Setter...) -> (rowid: Int64?, statement: Statement) { - return replace(values) - } - - /// Runs a REPLACE statement against the query. - /// - /// :param: values An array of values to set. - /// - /// :returns: The rowid. - public func replace(values: [Setter]) -> Int64? { return replace(values).rowid } - - /// Runs a REPLACE statement against the query. - /// - /// :param: values An array of values to set. - /// - /// :returns: The rowid and statement. - public func replace(values: [Setter]) -> (rowid: Int64?, statement: Statement) { - let statement = insertStatement(values, or: .Replace).run() - return (statement.failed ? nil : database.lastInsertRowid, statement) - } - /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. From a5011f4a216fb9e60ee33aa17c84c5831d1db5fa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 2 May 2015 10:37:37 -0400 Subject: [PATCH 0265/1046] Simplify query change interface The fact that `insert()`, `update()`, and `delete()` have a number of overloads that can only be disambiguated by specifying a type, a tuple member, or `!` is a big point of confusion for new users of SQLite.swift. The overloads add some interesting patterns to the mix, but aren't worth the pain points. If we eliminate the overloads, we can insert/update/delete in place. This allows for subtle bugs to be introduced into apps (where the developer doesn't check for a rowid or that an update/delete was successful), but is a tradeoff we'll have to make. It doesn't make sense to enforce a different kind of interface/access at the expense of confusion. Given: let user = email <- "alice@mac.com") The old way: users.insert(user)! let rowid = users.insert(user)! if let rowid = users.insert(user) { /* ... */ } let (rowid, statement) = users.insert(user) // etc. The new way: users.insert(user) let rowid = users.insert(user).rowid! if let rowid = users.insert(user).rowid { /* ... */ } let (rowid, statement) = users.insert(user) // etc. Slightly and rarely more verbose and readable, with less confusing compiler errors and hand-holding. Signed-off-by: Stephen Celis --- Documentation/Index.md | 136 +++++++-------------- SQLite Tests/ExpressionTests.swift | 188 ++++++++++++++--------------- SQLite Tests/FTSTests.swift | 2 +- SQLite Tests/FunctionsTests.swift | 8 +- SQLite Tests/QueryTests.swift | 45 +++++-- SQLite/Query.swift | 120 ++++++------------ 6 files changed, 222 insertions(+), 277 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4690a24d..ad28bd56 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -393,62 +393,33 @@ Additional constraints may be provided outside the scope of a single column usin ## Inserting Rows -We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. +We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. ``` swift -users.insert(email <- "alice@mac.com", name <- "Alice")? +users.insert(email <- "alice@mac.com", name <- "Alice") // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.") // INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` -The `insert` function can return several different types that are useful in different contexts. +The `insert` function returns a tuple with an `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure) and the associated `Statement`. - - An `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure), for simplicity. - - ``` swift - if let insertId = users.insert(email <- "alice@mac.com") { - println("inserted id: \(insertId)") - } - ``` - - If a value is always expected, we can disambiguate with a `!`. - - ``` swift - users.insert(email <- "alice@mac.com")! - ``` - - - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. - - ``` swift - db.transaction() - && users.insert(email <- "alice@mac.com") - && users.insert(email <- "betty@mac.com") - && db.commit() || db.rollback() - // BEGIN DEFERRED TRANSACTION; - // INSERT INTO "users" ("email") VALUES ('alice@mac.com'); - // INSERT INTO "users" ("email") VALUES ('betty@mac.com'); - // COMMIT TRANSACTION; - ``` - - - A tuple of the above [`ROWID`][ROWID] and statement: `(rowid: Int64?, statement: Statement)`, for flexibility. - - ``` swift - let (rowid, statement) = users.insert(email <- "alice@mac.com") - if let rowid = rowid { - println("inserted id: \(rowid)") - } else if statement.failed { - println("insertion failed: \(statement.reason)") - } - ``` +``` swift +let insert = users.insert(email <- "alice@mac.com") +if let rowid = insert.rowid { + println("inserted id: \(rowid)") +} else if insert.statement.failed { + println("insertion failed: \(insert.statement.reason)") +} +``` The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns. > _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values. > > ``` swift -> timestamps.insert()! +> timestamps.insert() > // INSERT INTO "timestamps" DEFAULT VALUES > ``` @@ -816,12 +787,12 @@ users.filter(name != nil).count ## Updating Rows -We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters), typically [typed column expressions](#expressions) and values (which can also be expressions), each joined by the `<-` operator. +We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. When an unscoped query calls `update`, it will update _every_ row in the table. ``` swift -users.update(email <- "alice@me.com")? +users.update(email <- "alice@me.com") // UPDATE "users" SET "email" = 'alice@me.com' ``` @@ -829,29 +800,20 @@ Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) -alice.update(email <- "alice@me.com")? +alice.update(email <- "alice@me.com") // UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1) ``` -Like [`insert`](#inserting-rows) (and [`delete`](#updating-rows)), `update` can return several different types that are useful in different contexts. - - - An `Int?` representing the number of updated rows (or `nil` on failure), for simplicity. - - ``` swift - if alice.update(email <- "alice@me.com") > 0 { - println("updated Alice") - } - ``` +The `update` function returns a tuple with an `Int?` representing the number of updates (or `nil` on failure) and the associated `Statement`. - If a value is always expected, we can disambiguate with a `!`. - - ``` swift - alice.update(email <- "alice@me.com")! - ``` - - - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. - - - A tuple of the above number of updated rows and statement: `(changes: Int?, Statement)`, for flexibility. +``` swift +let update = alice.update(email <- "alice@me.com") +if let changes = update.changes where changes > 0 { + println("updated alice") +} else if update.statement.failed { + println("update failed: \(update.statement.reason)") +} +``` ## Deleting Rows @@ -861,7 +823,7 @@ We can delete rows from a table by calling a [query’s](#queries) `delete` func When an unscoped query calls `delete`, it will delete _every_ row in the table. ``` swift -users.delete()? +users.delete() // DELETE FROM "users" ``` @@ -869,34 +831,25 @@ Be sure to scope `DELETE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) -alice.delete()? +alice.delete() // DELETE FROM "users" WHERE ("id" = 1) ``` -Like [`insert`](#inserting-rows) and [`update`](#updating-rows), `delete` can return several different types that are useful in different contexts. - - - An `Int?` representing the number of deleted rows (or `nil` on failure), for simplicity. - - ``` swift - if alice.delete() > 0 { - println("deleted Alice") - } - ``` - - If a value is always expected, we can disambiguate with a `!`. - - ``` swift - alice.delete()! - ``` - - - A `Statement`, for [the transaction and savepoint helpers](#transactions-and-savepoints) that take a list of statements. +The `delete` function returns a tuple with an `Int?` representing the number of deletes (or `nil` on failure) and the associated `Statement`. - - A tuple of the above number of deleted rows and statement: `(changes: Int?, Statement)`, for flexibility. +``` swift +let delete = delete.update(email <- "alice@me.com") +if let changes = delete.changes where changes > 0 { + println("deleted alice") +} else if delete.statement.failed { + println("delete failed: \(delete.statement.reason)") +} +``` ## Transactions and Savepoints -Using the `transaction` and `savepoint` functions, we can run a series of statements, committing the changes to the database if they all succeed. If a single statement fails, we can bail out early and roll back. +Using the `transaction` and `savepoint` functions, we can run a series of statements chained together (using `&&`). If a single statement fails, we can short-circuit the series (using `||`) and roll back the changes. ``` swift db.transaction() @@ -905,18 +858,21 @@ db.transaction() && db.commit() || db.rollback() ``` -The former statement can also be written as +> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This is why we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. + +For more complex transactions and savepoints, block helpers exist. Using a block helper, the former statement can be written (more verbosely) as follows: + ``` swift -db.transaction { _ in - for obj in objects { - stmt.run(obj.email) +db.transaction { txn in + if let rowid = users.insert(email <- "betty@icloud.com").rowid { + if users.insert(email <- "cathy@icloud.com", manager_id <- db.lastInsertRowid).rowid != nil { + return .Commit + } } - return .Commit || .Rollback + return .Rollback } ``` -> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This means we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. - ## Altering the Schema diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index c69c52cc..e49a39ab 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -542,209 +542,209 @@ class ExpressionTests: SQLiteTestCase { } func test_plusEquals_withStringExpression_buildsSetter() { - users.update(email += email)! - users.update(email += email2)! - users.update(email2 += email)! - users.update(email2 += email2)! + users.update(email += email) + users.update(email += email2) + users.update(email2 += email) + users.update(email2 += email2) AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")", 4) } func test_plusEquals_withStringValue_buildsSetter() { - users.update(email += ".com")! - users.update(email2 += ".com")! + users.update(email += ".com") + users.update(email2 += ".com") AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || '.com')", 2) } func test_plusEquals_withNumberExpression_buildsSetter() { - users.update(age += age)! - users.update(age += age2)! - users.update(age2 += age)! - users.update(age2 += age2)! + users.update(age += age) + users.update(age += age2) + users.update(age2 += age) + users.update(age2 += age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", 4) - users.update(salary += salary)! - users.update(salary += salary2)! - users.update(salary2 += salary)! - users.update(salary2 += salary2)! + users.update(salary += salary) + users.update(salary += salary2) + users.update(salary2 += salary) + users.update(salary2 += salary2) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", 4) } func test_plusEquals_withNumberValue_buildsSetter() { - users.update(age += 1)! - users.update(age2 += 1)! + users.update(age += 1) + users.update(age2 += 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) - users.update(salary += 100)! - users.update(salary2 += 100)! + users.update(salary += 100) + users.update(salary2 += 100) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", 2) } func test_minusEquals_withNumberExpression_buildsSetter() { - users.update(age -= age)! - users.update(age -= age2)! - users.update(age2 -= age)! - users.update(age2 -= age2)! + users.update(age -= age) + users.update(age -= age2) + users.update(age2 -= age) + users.update(age2 -= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", 4) - users.update(salary -= salary)! - users.update(salary -= salary2)! - users.update(salary2 -= salary)! - users.update(salary2 -= salary2)! + users.update(salary -= salary) + users.update(salary -= salary2) + users.update(salary2 -= salary) + users.update(salary2 -= salary2) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", 4) } func test_minusEquals_withNumberValue_buildsSetter() { - users.update(age -= 1)! - users.update(age2 -= 1)! + users.update(age -= 1) + users.update(age2 -= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) - users.update(salary -= 100)! - users.update(salary2 -= 100)! + users.update(salary -= 100) + users.update(salary2 -= 100) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", 2) } func test_timesEquals_withNumberExpression_buildsSetter() { - users.update(age *= age)! - users.update(age *= age2)! - users.update(age2 *= age)! - users.update(age2 *= age2)! + users.update(age *= age) + users.update(age *= age2) + users.update(age2 *= age) + users.update(age2 *= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", 4) - users.update(salary *= salary)! - users.update(salary *= salary2)! - users.update(salary2 *= salary)! - users.update(salary2 *= salary2)! + users.update(salary *= salary) + users.update(salary *= salary2) + users.update(salary2 *= salary) + users.update(salary2 *= salary2) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", 4) } func test_timesEquals_withNumberValue_buildsSetter() { - users.update(age *= 1)! - users.update(age2 *= 1)! + users.update(age *= 1) + users.update(age2 *= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * 1)", 2) - users.update(salary *= 100)! - users.update(salary2 *= 100)! + users.update(salary *= 100) + users.update(salary2 *= 100) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", 2) } func test_divideEquals_withNumberExpression_buildsSetter() { - users.update(age /= age)! - users.update(age /= age2)! - users.update(age2 /= age)! - users.update(age2 /= age2)! + users.update(age /= age) + users.update(age /= age2) + users.update(age2 /= age) + users.update(age2 /= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", 4) - users.update(salary /= salary)! - users.update(salary /= salary2)! - users.update(salary2 /= salary)! - users.update(salary2 /= salary2)! + users.update(salary /= salary) + users.update(salary /= salary2) + users.update(salary2 /= salary) + users.update(salary2 /= salary2) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", 4) } func test_divideEquals_withNumberValue_buildsSetter() { - users.update(age /= 1)! - users.update(age2 /= 1)! + users.update(age /= 1) + users.update(age2 /= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / 1)", 2) - users.update(salary /= 100)! - users.update(salary2 /= 100)! + users.update(salary /= 100) + users.update(salary2 /= 100) AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", 2) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - users.update(age %= age)! - users.update(age %= age2)! - users.update(age2 %= age)! - users.update(age2 %= age2)! + users.update(age %= age) + users.update(age %= age2) + users.update(age2 %= age) + users.update(age2 %= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", 4) } func test_moduloEquals_withIntegerValue_buildsSetter() { - users.update(age %= 10)! - users.update(age2 %= 10)! + users.update(age %= 10) + users.update(age2 %= 10) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % 10)", 2) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - users.update(age >>= age)! - users.update(age >>= age2)! - users.update(age2 >>= age)! - users.update(age2 >>= age2)! + users.update(age >>= age) + users.update(age >>= age2) + users.update(age2 >>= age) + users.update(age2 >>= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", 4) } func test_rightShiftEquals_withIntegerValue_buildsSetter() { - users.update(age >>= 1)! - users.update(age2 >>= 1)! + users.update(age >>= 1) + users.update(age2 >>= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", 2) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - users.update(age <<= age)! - users.update(age <<= age2)! - users.update(age2 <<= age)! - users.update(age2 <<= age2)! + users.update(age <<= age) + users.update(age <<= age2) + users.update(age2 <<= age) + users.update(age2 <<= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", 4) } func test_leftShiftEquals_withIntegerValue_buildsSetter() { - users.update(age <<= 1)! - users.update(age2 <<= 1)! + users.update(age <<= 1) + users.update(age2 <<= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << 1)", 2) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - users.update(age &= age)! - users.update(age &= age2)! - users.update(age2 &= age)! - users.update(age2 &= age2)! + users.update(age &= age) + users.update(age &= age2) + users.update(age2 &= age) + users.update(age2 &= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", 4) } func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - users.update(age &= 1)! - users.update(age2 &= 1)! + users.update(age &= 1) + users.update(age2 &= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & 1)", 2) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - users.update(age |= age)! - users.update(age |= age2)! - users.update(age2 |= age)! - users.update(age2 |= age2)! + users.update(age |= age) + users.update(age |= age2) + users.update(age2 |= age) + users.update(age2 |= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", 4) } func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - users.update(age |= 1)! - users.update(age2 |= 1)! + users.update(age |= 1) + users.update(age2 |= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | 1)", 2) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - users.update(age ^= age)! - users.update(age ^= age2)! - users.update(age2 ^= age)! - users.update(age2 ^= age2)! + users.update(age ^= age) + users.update(age ^= age2) + users.update(age2 ^= age) + users.update(age2 ^= age2) AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", 4) } func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - users.update(age ^= 1)! - users.update(age2 ^= 1)! + users.update(age ^= 1) + users.update(age2 ^= 1) AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", 2) } func test_postfixPlus_withIntegerValue_buildsSetter() { - users.update(age++)! - users.update(age2++)! + users.update(age++) + users.update(age2++) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) } func test_postfixMinus_withIntegerValue_buildsSetter() { - users.update(age--)! - users.update(age2--)! + users.update(age--) + users.update(age2--) AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) } diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift index d3208c14..eaf95b9b 100644 --- a/SQLite Tests/FTSTests.swift +++ b/SQLite Tests/FTSTests.swift @@ -53,7 +53,7 @@ class FTSTests: SQLiteTestCase { AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" 'tokenizer')") - emails.insert(subject <- "Aún más cáfe!")! + emails.insert(subject <- "Aún más cáfe!") XCTAssertEqual(1, emails.filter(match("aun", emails)).count) } diff --git a/SQLite Tests/FunctionsTests.swift b/SQLite Tests/FunctionsTests.swift index 5a2e3386..d49d572e 100644 --- a/SQLite Tests/FunctionsTests.swift +++ b/SQLite Tests/FunctionsTests.swift @@ -9,7 +9,7 @@ class FunctionsTests: SQLiteTestCase { let table = db["table"] db.create(table: table) { $0.column(Expression("id"), primaryKey: true) } - table.insert()! + table.insert().rowid! XCTAssert(table.select(f1()).first![f1()]) AssertSQL("SELECT \"f1\"() FROM \"table\" LIMIT 1") @@ -31,7 +31,7 @@ class FunctionsTests: SQLiteTestCase { t.column(s1) t.column(s2) } - table.insert(s1 <- "s1")! + table.insert(s1 <- "s1").rowid! let null = Expression(value: nil as String?) @@ -60,7 +60,7 @@ class FunctionsTests: SQLiteTestCase { db.create(table: table) { t in t.column(b) } - table.insert(b <- true)! + table.insert(b <- true).rowid! XCTAssert(table.select(f1(b)).first![f1(b)]) AssertSQL("SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1") @@ -74,7 +74,7 @@ class FunctionsTests: SQLiteTestCase { t.column(b1) t.column(b2) } - table.insert(b1 <- true)! + table.insert(b1 <- true).rowid! let f1: (Bool, Expression) -> Expression = db.create(function: "f1") { $0 && $1 } let f2: (Bool?, Expression) -> Expression = db.create(function: "f2") { $0 ?? $1 } diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift index e06e8f05..fcb2e898 100644 --- a/SQLite Tests/QueryTests.swift +++ b/SQLite Tests/QueryTests.swift @@ -83,8 +83,8 @@ class QueryTests: SQLiteTestCase { func test_join_withNamespacedStar_expandsColumnNames() { let managers = db["users"].alias("managers") - let aliceId = users.insert(email <- "alice@example.com")! - users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId))! + let aliceId = users.insert(email <- "alice@example.com").rowid! + users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid! let query = users .select(users[*], managers[*]) @@ -127,8 +127,8 @@ class QueryTests: SQLiteTestCase { } func test_namespacedColumnRowValueAccess() { - let aliceId = users.insert(email <- "alice@example.com")! - let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId))! + let aliceId = users.insert(email <- "alice@example.com").rowid! + let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid! let alice = users.first! XCTAssertEqual(Int64(aliceId), alice[id]) @@ -141,7 +141,7 @@ class QueryTests: SQLiteTestCase { } func test_aliasedColumnRowValueAccess() { - users.insert(email <- "alice@example.com")! + users.insert(email <- "alice@example.com").rowid! let alias = email.alias("user_email") let query = users.select(alias) @@ -315,7 +315,7 @@ class QueryTests: SQLiteTestCase { let emails = db["emails"] let admins = users.select(email).filter(admin == true) - emails.insert(admins)! + emails.insert(admins) AssertSQL("INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)") } @@ -430,14 +430,43 @@ class QueryTests: SQLiteTestCase { } let date = NSDate(timeIntervalSince1970: 0) - touches.insert(timestamp <- date)! + touches.insert(timestamp <- date) XCTAssertEqual(touches.first!.get(timestamp)!, date) - XCTAssertNil(touches.filter(id == Int64(touches.insert()!)).first!.get(timestamp)) + XCTAssertNil(touches.filter(id == Int64(touches.insert().rowid!)).first!.get(timestamp)) XCTAssert(touches.filter(timestamp < NSDate()).first != nil) } + func test_shortCircuitingInserts() { + db.transaction() + && users.insert(email <- "alice@example.com") + && users.insert(email <- "alice@example.com") + && users.insert(email <- "alice@example.com") + && db.commit() + || db.rollback() + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO \"users\" (\"email\") VALUES ('alice@example.com')", 2) + AssertSQL("ROLLBACK TRANSACTION") + AssertSQL("COMMIT TRANSACTION", 0) + } + + func test_shortCircuitingChanges() { + db.transaction() + && users.insert(email <- "foo@example.com") + && users.insert(email <- "bar@example.com") + && users.filter(email == "bar@example.com").update(email <- "foo@example.com") + && users.filter(email == "bar@example.com").update(email <- "foo@example.com") + && db.commit() + || db.rollback() + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("UPDATE \"users\" SET \"email\" = 'foo@example.com' WHERE (\"email\" = 'bar@example.com')") + AssertSQL("ROLLBACK TRANSACTION") + AssertSQL("COMMIT TRANSACTION", 0) + } + } private let formatter: NSDateFormatter = { diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 15a1902d..9f965612 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -432,28 +432,6 @@ public struct Query { // MARK: - Modifying - /// Runs an INSERT statement against the query. - /// - /// :param: action An action to run in case of a conflict. - /// :param: value A value to set. - /// :param: more A list of additional values to set. - /// - /// :returns: The statement. - public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Statement { - return insert([value] + more).statement - } - - /// Runs an INSERT statement against the query. - /// - /// :param: action An action to run in case of a conflict. - /// :param: value A value to set. - /// :param: more A list of additional values to set. - /// - /// :returns: The rowid. - public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Int64? { - return insert(or: action, [value] + more).rowid - } - /// Runs an INSERT statement against the query. /// /// :param: action An action to run in case of a conflict. @@ -461,104 +439,64 @@ public struct Query { /// :param: more A list of additional values to set. /// /// :returns: The rowid and statement. - public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> (rowid: Int64?, statement: Statement) { + public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Insert { return insert(or: action, [value] + more) } - /// Runs an INSERT statement against the query. - /// - /// :param: action An action to run in case of a conflict. - /// :param: values An array of values to set. - /// - /// :returns: The rowid. - public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Int64? { - return insert(or: action, values).rowid - } - /// Runs an INSERT statement against the query. /// /// :param: action An action to run in case of a conflict. /// :param: values An array of values to set. /// /// :returns: The rowid and statement. - public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> (rowid: Int64?, statement: Statement) { + public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Insert { let statement = insertStatement(or: action, values).run() return (statement.failed ? nil : database.lastInsertRowid, statement) } - public func insert(query: Query) -> Int? { return insert(query).changes } - - public func insert(query: Query) -> Statement { return insert(query).statement } - - public func insert(query: Query) -> (changes: Int?, statement: Statement) { - let expression = query.selectExpression - let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings) - return (statement.failed ? nil : database.changes, statement) - } - - public func insert() -> Int64? { return insert().rowid } - - public func insert() -> Statement { return insert().statement } - - public func insert() -> (rowid: Int64?, statement: Statement) { + /// Runs an INSERT statement against the query with DEFAULT VALUES. + /// + /// :returns: The rowid and statement. + public func insert() -> Insert { let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) DEFAULT VALUES") return (statement.failed ? nil : database.lastInsertRowid, statement) } - /// Runs an UPDATE statement against the query. - /// - /// :param: values A list of values to set. + /// Runs an INSERT statement against the query with the results of another + /// query. /// - /// :returns: The statement. - public func update(values: Setter...) -> Statement { return update(values).statement } - - /// Runs an UPDATE statement against the query. + /// :param: query A query to SELECT results from. /// - /// :param: values A list of values to set. - /// - /// :returns: The number of updated rows. - public func update(values: Setter...) -> Int? { return update(values).changes } + /// :returns: The number of updated rows and statement. + public func insert(query: Query) -> Change { + let expression = query.selectExpression + let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings) + return (statement.failed ? nil : database.changes, statement) + } /// Runs an UPDATE statement against the query. /// /// :param: values A list of values to set. /// /// :returns: The number of updated rows and statement. - public func update(values: Setter...) -> (changes: Int?, statement: Statement) { + public func update(values: Setter...) -> Change { return update(values) } - /// Runs an UPDATE statement against the query. - /// - /// :param: values An array of of values to set. - /// - /// :returns: The number of updated rows. - public func update(values: [Setter]) -> Int? { return update(values).changes } - /// Runs an UPDATE statement against the query. /// /// :param: values An array of of values to set. /// /// :returns: The number of updated rows and statement. - public func update(values: [Setter]) -> (changes: Int?, statement: Statement) { + public func update(values: [Setter]) -> Change { let statement = updateStatement(values).run() return (statement.failed ? nil : database.changes, statement) } - /// Runs a DELETE statement against the query. - /// - /// :returns: The statement. - public func delete() -> Statement { return delete().statement } - - /// Runs a DELETE statement against the query. - /// - /// :returns: The number of deleted rows. - public func delete() -> Int? { return delete().changes } - /// Runs a DELETE statement against the query. /// /// :returns: The number of deleted rows and statement. - public func delete() -> (changes: Int?, statement: Statement) { + public func delete() -> Change { let statement = deleteStatement.run() return (statement.failed ? nil : database.changes, statement) } @@ -847,6 +785,28 @@ extension Query: Printable { } +/// The result of an INSERT executed by a query. +public typealias Insert = (rowid: Int64?, statement: Statement) + +/// The result of an UPDATE or DELETE executed by a query. +public typealias Change = (changes: Int?, statement: Statement) + +public func && (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { + return lhs && rhs().statement +} + +public func || (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { + return lhs || rhs().statement +} + +public func && (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { + return lhs && rhs().statement +} + +public func || (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { + return lhs || rhs().statement +} + extension Database { public subscript(tableName: String) -> Query { From b5aabd0881ce33cc6df39bd8cc6a0fde96581e0a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 2 May 2015 10:47:53 -0400 Subject: [PATCH 0266/1046] Consistency Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index ad28bd56..e73a92e4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -170,7 +170,7 @@ let path = NSBundle.mainBundle().pathForResource("db", ofType: "sqlite3")! let db = Database(path, readonly: true) ``` -> _Note_: Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). +> _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). #### In-Memory Databases From 868b379ee3d6cab70b9190177d0b2a94703636a1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 2 May 2015 10:48:14 -0400 Subject: [PATCH 0267/1046] Better examples Signed-off-by: Stephen Celis --- Documentation/Index.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index e73a92e4..46cb96ad 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -537,18 +537,23 @@ let query = users.select(email) // SELECT "email" FROM "users" By default, [`Query`](#queries) objects select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. ``` swift -let query = users.select(id, email) +for user in users.select(id, email) { + println("id: \(user[id]), email: \(user[email])") + // id: 1, email: alice@mac.com +} // SELECT "id", "email" FROM "users" ``` - #### Joining Other Tables From 3a0435ebce11fd31e7a4176eacff5f3350e70782 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 2 May 2015 23:21:54 -0400 Subject: [PATCH 0268/1046] Update README for insert/update/delete changes It's no longer overload soup. Signed-off-by: Stephen Celis --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8280c68b..3d3fdc93 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ db.create(table: users) { t in // ) var alice: Query? -if let insertId = users.insert(name <- "Alice", email <- "alice@mac.com") { - println("inserted id: \(insertId)") +if let rowid = users.insert(name <- "Alice", email <- "alice@mac.com").rowid { + println("inserted id: \(rowid)") // inserted id: 1 - alice = users.filter(id == insertId) + alice = users.filter(id == rowid) } // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') @@ -64,11 +64,11 @@ for user in users { } // SELECT * FROM "users" -alice?.update(email <- replace(email, "mac.com", "me.com"))? +alice?.update(email <- replace(email, "mac.com", "me.com")) // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') // WHERE ("id" = 1) -alice?.delete()? +alice?.delete() // DELETE FROM "users" WHERE ("id" = 1) users.count From effa1bf832c0519cb8fe69b8b6da6aff4152c36a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 6 May 2015 18:19:02 -0400 Subject: [PATCH 0269/1046] Update Contributing Guidelines This should assist some users on their way to submitting an issue or shortly thereafter. Signed-off-by: Stephen Celis --- CONTRIBUTING.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 3 ++ 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2aedab94..01793a0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,130 @@ # Contributing - - Need **help** or have a **general question**? [Ask on Stack - Overflow][] (tag `sqlite.swift`). - - Found a **bug** or have a **feature request**? [Open an issue][]. - - Want to **contribute**? [Submit a pull request][]. +The where and when to open an [issue](#issues) or [pull +request](#pull-requests). + + +## Issues + +Issues are used to track **bugs** and **feature requests**. Need **help** or +have a **general question**? [Ask on Stack Overflow][] (tag `sqlite.swift`). + +Before reporting a bug or requesting a feature, [run a few searches][Search] to +see if a similar issue has already been opened and ensure you’re not submitting +a duplicate. + +If you find a similar issue, read the existing conversation and see if it +addresses everything. If it doesn’t, continue the conversation there. + +If your searches return empty, see the [bug](#bugs) or [feature +request](#feature-requests) guidelines below. [Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Search]: https://github.com/stephencelis/SQLite.swift/search?type=Issues + + +### Bugs + +Think you’ve discovered a new **bug**? Let’s try troubleshooting a few things +first. + + - **Is it an installation issue?** + + If this is your first time building SQLite.swift in your project, you may + encounter a build error, _e.g._: + + No such module 'SQLite' + + Please carefully re-read the [installation instructions][] to make sure + everything is in order. + + - **Have you read the documentation?** + + If you can’t seem to get something working, check + [the documentation][See Documentation] to see if the solution is there. + + - **Are you up-to-date?** + + If you’re perusing [the documentation][See Documentation] online and find + that an example is just not working, please upgrade to the latest version + of SQLite.swift and try again before continuing. + + - **Is it an unhelpful build error?** + + While Swift error messaging is improving with each release, complex + expressions still lend themselves to misleading errors. If you encounter an + error on a complex line, breaking it down into smaller pieces generally + yields a more understandable error. _E.g._: + + ``` swift + users.insert(email <- "alice@mac.com" <- managerId <- db.lastInsertRowid) + // Cannot invoke 'insert' with an argument list of type '(Setter, Setter)' + ``` + + Not very helpful! If we break the expression down into smaller parts, + however, the real error materializes on the appropriate line. + + ``` swift + let emailSetter = email <- "alice@mac.com" + let managerIdSetter = managerId <- db.lastInsertRowId + // Binary operator '<-' cannot be applied to operands of type 'Expression' and 'Int64' + users.insert(emailSetter, managerIdSetter) + ``` + + The problem turns out to be a simple type mismatch. The fix is elsewhere: + + ``` diff + -let managerId = Expression("manager_id") + +let managerId = Expression("manager_id") + ``` + + - **Is it an _even more_ unhelpful build error?** + + Have you updated Xcode recently? Did your project stop building out of the + blue? + + Hold down the **option** key and select **Clean Build Folder…** from the + **Product** menu (⌥⇧⌘K). + +Made it through everything above and still having trouble? Sorry! +[Open an issue][]! And _please_: + + - Be as descriptive as possible. + - Provide as much information needed to _reliably reproduce_ the issue. + - Attach screenshots if possible. + - Better yet: attach GIFs or link to video. + - Even better: link to a sample project exhibiting the issue. + - Include the SQLite.swift commit or branch experiencing the issue. + - Include devices and operating systems affected. + - Include build information: the Xcode and OS X versions affected. + +[installation instructions]: Documentation/Index.md#installation +[See Documentation]: Documentation/Index.md#sqliteswift-documentation [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new + + +### Feature Requests + +Have an innovative **feature request**? [Open an issue][]! Be thorough! Provide +context and examples. Be open to discussion. + + +## Pull Requests + +Interested in contributing but don’t know where to start? Try the [`help +wanted`][help wanted] label. + +Ready to submit a fix or a feature? [Submit a pull request][]! And _please_: + + - If code changes, run the tests and make sure everything still works. + - Write new tests for new functionality. + - Update documentation comments where applicable. + - Maintain the existing style. + - Don’t forget to have fun. + +While we cannot guarantee a merge to every pull request, we do read each one +and love your input. + + +[help wanted]: https://github.com/stephencelis/SQLite.swift/labels/help%20wanted [Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork diff --git a/README.md b/README.md index 3d3fdc93..d3f5e948 100644 --- a/README.md +++ b/README.md @@ -162,11 +162,14 @@ To install SQLite.swift with [SQLCipher][] support: ## Communication +[Read the contributing guidelines][]. The _TL;DR_ (but please; _R_): + - Need **help** or have a **general question**? [Ask on Stack Overflow][] (tag `sqlite.swift`). - Found a **bug** or have a **feature request**? [Open an issue][]. - Want to **contribute**? [Submit a pull request][]. +[Read the contributing guidelines]: ./CONTRIBUTING.md#contributing [Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new [Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork From e7fbda4ebd5554d61397454217a9d5e71a197892 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 6 May 2015 18:27:08 -0400 Subject: [PATCH 0270/1046] Update requirements in documentation This is why you keep it DRY. Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 46cb96ad..d7c65ffc 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -63,7 +63,7 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 1.1 (and [Xcode 6.1](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode 6.3](https://developer.apple.com/xcode/downloads/)) or greater. To install SQLite.swift as an Xcode sub-project: From 533e47e6d57cdf8e1d6729b34f830639c41f2f8b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 6 May 2015 18:27:39 -0400 Subject: [PATCH 0271/1046] Swift prefers camelCase Pulling snake_case over for an interface isn't necessary. Signed-off-by: Stephen Celis --- Documentation/Index.md | 6 +++--- SQLite.playground/Contents.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index d7c65ffc..1a3b6359 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -601,7 +601,7 @@ Occasionally, we need to join a table to itself, in which case we must alias the ``` swift let managers = users.alias("managers") -let query = users.join(managers, on: managers[id] == users[manager_id]) +let query = users.join(managers, on: managers[id] == users[managerId]) // SELECT * FROM "users" // INNER JOIN ("users") AS "managers" ON ("managers"."id" = "users"."manager_id") ``` @@ -859,7 +859,7 @@ Using the `transaction` and `savepoint` functions, we can run a series of statem ``` swift db.transaction() && users.insert(email <- "betty@icloud.com") - && users.insert(email <- "cathy@icloud.com", manager_id <- db.lastInsertRowid) + && users.insert(email <- "cathy@icloud.com", managerId <- db.lastInsertRowid) && db.commit() || db.rollback() ``` @@ -870,7 +870,7 @@ For more complex transactions and savepoints, block helpers exist. Using a block ``` swift db.transaction { txn in if let rowid = users.insert(email <- "betty@icloud.com").rowid { - if users.insert(email <- "cathy@icloud.com", manager_id <- db.lastInsertRowid).rowid != nil { + if users.insert(email <- "cathy@icloud.com", managerId <- db.lastInsertRowid).rowid != nil { return .Commit } } diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 8e67c25d..87f0315a 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -111,7 +111,7 @@ let id = Expression("id") let email = Expression("email") let age = Expression("age") let admin = Expression("admin") -let manager_id = Expression("manager_id") +let managerId = Expression("manager_id") /*: The query-building interface is provided via the `Query` struct. We can access this interface by subscripting our database connection with a table name. */ @@ -129,7 +129,7 @@ The `insert` function can return a `rowid` (which will be `nil` in the case of f */ db.transaction() && users.insert(email <- "julie@acme.com") - && users.insert(email <- "kelly@acme.com", manager_id <- db.lastInsertRowid) + && users.insert(email <- "kelly@acme.com", managerId <- db.lastInsertRowid) && db.commit() || db.rollback() /*: From 5794555c918f7022001282f62fe2c43cbfb97b3d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 May 2015 18:48:29 -0400 Subject: [PATCH 0272/1046] Standardize void types Let's follow the Apple convention: - () for parameters - Void for return types https://devforums.apple.com/message/1133616#1133616 Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 2 +- SQLite Tests/StatementTests.swift | 4 +- SQLite.xcodeproj/project.pbxproj | 2 +- SQLite/Database.swift | 8 ++-- SQLite/Expression.swift | 76 +++++++++++++++---------------- SQLite/FTS.swift | 10 ++-- SQLite/Query.swift | 60 ++++++++++++------------ SQLite/RTree.swift | 4 +- SQLite/Schema.swift | 64 +++++++++++++------------- SQLite/Value.swift | 4 +- 10 files changed, 117 insertions(+), 117 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index 07cd1961..3ffae90b 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -378,7 +378,7 @@ class DatabaseTests: SQLiteTestCase { XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) } - func executeAndWait(block: () -> ()) { + func executeAndWait(block: () -> Void) { dispatch_async(dispatch_get_main_queue(), block) waitForExpectationsWithTimeout(5) { error in if let error = error { diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift index 5370aedc..1015282a 100644 --- a/SQLite Tests/StatementTests.swift +++ b/SQLite Tests/StatementTests.swift @@ -150,10 +150,10 @@ class StatementTests: SQLiteTestCase { } -func withBlob(block: Blob -> ()) { +func withBlob(block: Blob -> Void) { let length = 1 let buflen = Int(length) + 1 - let buffer = UnsafeMutablePointer<()>.alloc(buflen) + let buffer = UnsafeMutablePointer.alloc(buflen) memcpy(buffer, "4", length) block(Blob(bytes: buffer, length: length)) buffer.dealloc(buflen) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 738db788..50bae729 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -154,7 +154,7 @@ DCC6B3A31A9194A800734B78 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; DCC6B3A51A9194FB00734B78 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseTests.swift; sourceTree = ""; }; - DCF37F8419DDAF3F001534AA /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; + DCF37F8419DDAF3F001534AA /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = StatementTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCF37F8719DDAF79001534AA /* TestHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = ""; }; /* End PBXFileReference section */ diff --git a/SQLite/Database.swift b/SQLite/Database.swift index dde88510..3bf17d8c 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -392,7 +392,7 @@ public final class Database { /// :param: callback This block is invoked when a statement is executed with /// the compiled SQL as its argument. E.g., pass `println` /// to act as a logger. - public func trace(callback: ((SQL: String) -> ())?) { + public func trace(callback: ((SQL: String) -> Void)?) { if let callback = callback { trace = { callback(SQL: String.fromCString($0)!) } } else { @@ -435,7 +435,7 @@ public final class Database { /// :param: callback A callback invoked with the `Operation` (one /// of `.Insert`, `.Update`, or `.Delete`), database name, /// table name, and rowid. - public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> ())?) { + public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { if let callback = callback { updateHook = { operation, db, table, rowid in callback( @@ -470,7 +470,7 @@ public final class Database { /// Registers a callback to be invoked whenever a transaction rolls back. /// /// :param: callback A callback invoked when a transaction is rolled back. - public func rollbackHook(callback: (() -> ())?) { + public func rollbackHook(callback: (() -> Void)?) { rollbackHook = callback.map { $0 } _SQLiteRollbackHook(handle, rollbackHook) } @@ -570,7 +570,7 @@ public final class Database { private let queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) - internal func perform(block: () -> ()) { dispatch_sync(queue, block) } + internal func perform(block: () -> Void) { dispatch_sync(queue, block) } } diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 87f0fb74..4b1526c4 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -90,14 +90,14 @@ public struct Expression { return expression } - internal static func join(separator: String, _ expressions: [Expressible]) -> Expression<()> { + internal static func join(separator: String, _ expressions: [Expressible]) -> Expression { var (SQL, bindings) = ([String](), [Binding?]()) for expressible in expressions { let expression = expressible.expression SQL.append(expression.SQL) bindings.extend(expression.bindings) } - return Expression<()>(literal: Swift.join(separator, SQL), bindings) + return Expression(literal: Swift.join(separator, SQL), bindings) } internal init(_ expression: Expression) { @@ -105,11 +105,11 @@ public struct Expression { (ascending, original) = (expression.ascending, expression.original) } - internal var ordered: Expression<()> { + internal var ordered: Expression { if let ascending = ascending { return Expression.join(" ", [self, Expression(literal: ascending ? "ASC" : "DESC")]) } - return Expression<()>(self) + return Expression(self) } internal var aliased: Expression { @@ -142,13 +142,13 @@ public struct Expression { public protocol Expressible { - var expression: Expression<()> { get } + var expression: Expression { get } } extension Blob: Expressible { - public var expression: Expression<()> { + public var expression: Expression { return Expression(binding: self) } @@ -156,7 +156,7 @@ extension Blob: Expressible { extension Bool: Expressible { - public var expression: Expression<()> { + public var expression: Expression { return Expression(value: self) } @@ -164,7 +164,7 @@ extension Bool: Expressible { extension Double: Expressible { - public var expression: Expression<()> { + public var expression: Expression { return Expression(binding: self) } @@ -172,7 +172,7 @@ extension Double: Expressible { extension Int: Expressible { - public var expression: Expression<()> { + public var expression: Expression { // FIXME: rdar://TODO segfaults during archive // return Expression(value: self) return Expression(binding: datatypeValue) } @@ -181,7 +181,7 @@ extension Int: Expressible { extension Int64: Expressible { - public var expression: Expression<()> { + public var expression: Expression { return Expression(binding: self) } @@ -189,7 +189,7 @@ extension Int64: Expressible { extension String: Expressible { - public var expression: Expression<()> { + public var expression: Expression { return Expression(binding: self) } @@ -197,15 +197,15 @@ extension String: Expressible { extension Expression: Expressible { - public var expression: Expression<()> { - return Expression<()>(self) + public var expression: Expression { + return Expression(self) } } extension Query: Expressible { - public var expression: Expression<()> { + public var expression: Expression { if tableName.original != nil { return selectExpression.alias(tableName) } @@ -587,17 +587,17 @@ public func abs(expression: Expression) - // FIXME: support Expression..., Expression when Swift supports inner variadic signatures public func coalesce(expressions: Expression...) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", expressions.map { $0 } as [Expressible])) + return wrap(__FUNCTION__, Expression.join(", ", expressions.map { $0 } as [Expressible])) } public func ifnull(expression: Expression, defaultValue: V) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, defaultValue])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) } public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, defaultValue])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) } public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, defaultValue])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) } public func ?? (expression: Expression, defaultValue: V) -> Expression { return ifnull(expression, defaultValue) @@ -619,51 +619,51 @@ public func ltrim(expression: Expression) -> Expression { return public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func ltrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) } public func ltrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) } public func random() -> Expression { return Expression(literal: __FUNCTION__) } public func replace(expression: Expression, match: String, subtitute: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, match, subtitute])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, match, subtitute])) } public func replace(expression: Expression, match: String, subtitute: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, match, subtitute])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, match, subtitute])) } public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func round(expression: Expression, precision: Int) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, precision])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, precision])) } public func round(expression: Expression, precision: Int) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, precision])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, precision])) } public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func rtrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) } public func rtrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) } public func substr(expression: Expression, startIndex: Int) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, startIndex])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, startIndex])) } public func substr(expression: Expression, startIndex: Int) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, startIndex])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, startIndex])) } public func substr(expression: Expression, position: Int, length: Int) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, position, length])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, position, length])) } public func substr(expression: Expression, position: Int, length: Int) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, position, length])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, position, length])) } public func substr(expression: Expression, subRange: Range) -> Expression { @@ -676,10 +676,10 @@ public func substr(expression: Expression, subRange: Range) -> Exp public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } public func trim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) } public func trim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression<()>.join(", ", [expression, characters])) + return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) } public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } @@ -727,16 +727,16 @@ public func total(#distinct: Expression) - public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } private func wrapDistinct(function: String, expression: Expression) -> Expression { - return wrap(function, Expression<()>.join(" ", [Expression<()>(literal: "DISTINCT"), expression])) + return wrap(function, Expression.join(" ", [Expression(literal: "DISTINCT"), expression])) } // MARK: - Helper public let rowid = Expression("ROWID") -public typealias Star = (Expression?, Expression?) -> Expression<()> +public typealias Star = (Expression?, Expression?) -> Expression -public func * (Expression?, Expression?) -> Expression<()> { +public func * (Expression?, Expression?) -> Expression { return Expression(literal: "*") } public func contains(values: C, column: Expression) -> Expression { @@ -747,7 +747,7 @@ public func contains(column)) } public func contains(values: Query, column: Expression) -> Expression { - return infix("IN", column, wrap("", values.selectExpression) as Expression<()>) + return infix("IN", column, wrap("", values.selectExpression) as Expression) } // MARK: - Modifying @@ -764,10 +764,10 @@ public typealias Setter = (Expressible, Expressible) /// :returns: A setter that can be used in a Query’s insert and update /// functions. public func set(column: Expression, value: V) -> Setter { - return (column, Expression<()>(value: value)) + return (column, Expression(value: value)) } public func set(column: Expression, value: V?) -> Setter { - return (column, Expression<()>(value: value)) + return (column, Expression(value: value)) } public func set(column: Expression, value: Expression) -> Setter { return (column, value) } public func set(column: Expression, value: Expression) -> Setter { return (column, value) } diff --git a/SQLite/FTS.swift b/SQLite/FTS.swift index 35afb07a..30dfccbe 100644 --- a/SQLite/FTS.swift +++ b/SQLite/FTS.swift @@ -22,23 +22,23 @@ // THE SOFTWARE. // -public func fts4(columns: Expression...) -> Expression<()> { +public func fts4(columns: Expression...) -> Expression { return fts4(columns) } // TODO: matchinfo, compress, uncompress -public func fts4(columns: [Expression], tokenize tokenizer: Tokenizer? = nil) -> Expression<()> { +public func fts4(columns: [Expression], tokenize tokenizer: Tokenizer? = nil) -> Expression { var options = [String: String]() options["tokenize"] = tokenizer?.description return fts("fts4", columns, options) } -private func fts(function: String, columns: [Expression], options: [String: String]) -> Expression<()> { +private func fts(function: String, columns: [Expression], options: [String: String]) -> Expression { var definitions: [Expressible] = columns.map { $0.expression } for (key, value) in options { - definitions.append(Expression<()>(literal: "\(key)=\(value)")) + definitions.append(Expression(literal: "\(key)=\(value)")) } - return wrap(function, Expression<()>.join(", ", definitions)) + return wrap(function, Expression.join(", ", definitions)) } public enum Tokenizer { diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 9f965612..57dd32f3 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -32,7 +32,7 @@ public struct Query { (self.database, self.tableName) = (database, Expression(tableName)) } - private init(_ database: Database, _ tableName: Expression<()>) { + private init(_ database: Database, _ tableName: Expression) { (self.database, self.tableName) = (database, tableName) } @@ -54,7 +54,7 @@ public struct Query { private var columns: [Expressible]? private var distinct: Bool = false - internal var tableName: Expression<()> + internal var tableName: Expression private var joins: [(type: JoinType, table: Query, condition: Expression)] = [] private var filter: Expression? private var group: Expressible? @@ -220,8 +220,8 @@ public struct Query { /// :returns: A query with the given GROUP BY clause applied. public func group(by: [Expressible], having: Expression? = nil) -> Query { var query = self - var group = Expression<()>.join(" ", [Expression<()>(literal: "GROUP BY"), Expression<()>.join(", ", by)]) - if let having = having { group = Expression<()>.join(" ", [group, Expression<()>(literal: "HAVING"), having]) } + var group = Expression.join(" ", [Expression(literal: "GROUP BY"), Expression.join(", ", by)]) + if let having = having { group = Expression.join(" ", [group, Expression(literal: "HAVING"), having]) } query.group = group return query } @@ -326,7 +326,7 @@ public struct Query { /// /// :returns: A * expression namespaced with the query’s table name or /// alias. - public subscript(star: Star) -> Expression<()> { + public subscript(star: Star) -> Expression { return namespace(star(nil, nil)) } @@ -337,14 +337,14 @@ public struct Query { return database.prepare(expression.SQL, expression.bindings) } - internal var selectExpression: Expression<()> { + internal var selectExpression: Expression { var expressions = [selectClause] joinClause.map(expressions.append) whereClause.map(expressions.append) group.map(expressions.append) orderClause.map(expressions.append) limitClause.map(expressions.append) - return Expression<()>.join(" ", expressions) + return Expression.join(" ", expressions) } /// ON CONFLICT resolutions. @@ -365,65 +365,65 @@ public struct Query { private func insertStatement(or: OnConflict? = nil, _ values: [Setter]) -> Statement { var insertClause = "INSERT" if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } - var expressions: [Expressible] = [Expression<()>(literal: "\(insertClause) INTO \(tableName.unaliased.SQL)")] - let (c, v) = (Expression<()>.join(", ", values.map { $0.0 }), Expression<()>.join(", ", values.map { $0.1 })) - expressions.append(Expression<()>(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) + var expressions: [Expressible] = [Expression(literal: "\(insertClause) INTO \(tableName.unaliased.SQL)")] + let (c, v) = (Expression.join(", ", values.map { $0.0 }), Expression.join(", ", values.map { $0.1 })) + expressions.append(Expression(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) whereClause.map(expressions.append) - let expression = Expression<()>.join(" ", expressions) + let expression = Expression.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) } private func updateStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression<()>(literal: "UPDATE \(tableName.unaliased.SQL) SET")] - expressions.append(Expression<()>.join(", ", values.map { Expression<()>.join(" = ", [$0, $1]) })) + var expressions: [Expressible] = [Expression(literal: "UPDATE \(tableName.unaliased.SQL) SET")] + expressions.append(Expression.join(", ", values.map { Expression.join(" = ", [$0, $1]) })) whereClause.map(expressions.append) - let expression = Expression<()>.join(" ", expressions) + let expression = Expression.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) } private var deleteStatement: Statement { - var expressions: [Expressible] = [Expression<()>(literal: "DELETE FROM \(tableName.unaliased.SQL)")] + var expressions: [Expressible] = [Expression(literal: "DELETE FROM \(tableName.unaliased.SQL)")] whereClause.map(expressions.append) - let expression = Expression<()>.join(" ", expressions) + let expression = Expression.join(" ", expressions) return database.prepare(expression.SQL, expression.bindings) } // MARK: - private var selectClause: Expressible { - var expressions: [Expressible] = [Expression<()>(literal: "SELECT")] - if distinct { expressions.append(Expression<()>(literal: "DISTINCT")) } - expressions.append(Expression<()>.join(", ", (columns ?? [Expression<()>(literal: "*")]).map { $0.expression.aliased })) - expressions.append(Expression<()>(literal: "FROM \(tableName.aliased.SQL)")) - return Expression<()>.join(" ", expressions) + var expressions: [Expressible] = [Expression(literal: "SELECT")] + if distinct { expressions.append(Expression(literal: "DISTINCT")) } + expressions.append(Expression.join(", ", (columns ?? [Expression(literal: "*")]).map { $0.expression.aliased })) + expressions.append(Expression(literal: "FROM \(tableName.aliased.SQL)")) + return Expression.join(" ", expressions) } private var joinClause: Expressible? { if joins.count == 0 { return nil } - return Expression<()>.join(" ", joins.map { type, table, condition in + return Expression.join(" ", joins.map { type, table, condition in let join = (table.columns == nil ? table.tableName : table.expression).aliased - return Expression<()>(literal: "\(type.rawValue) JOIN \(join.SQL) ON \(condition.SQL)", join.bindings + condition.bindings) + return Expression(literal: "\(type.rawValue) JOIN \(join.SQL) ON \(condition.SQL)", join.bindings + condition.bindings) }) } internal var whereClause: Expressible? { if let filter = filter { - return Expression<()>(literal: "WHERE \(filter.SQL)", filter.bindings) + return Expression(literal: "WHERE \(filter.SQL)", filter.bindings) } return nil } private var orderClause: Expressible? { if order.count == 0 { return nil } - let clause = Expression<()>.join(", ", order.map { $0.expression.ordered }) - return Expression<()>(literal: "ORDER BY \(clause.SQL)", clause.bindings) + let clause = Expression.join(", ", order.map { $0.expression.ordered }) + return Expression(literal: "ORDER BY \(clause.SQL)", clause.bindings) } private var limitClause: Expressible? { if let limit = limit { - var clause = Expression<()>(literal: "LIMIT \(limit.to)") + var clause = Expression(literal: "LIMIT \(limit.to)") if let offset = limit.offset { - clause = Expression<()>.join(" ", [clause, Expression<()>(literal: "OFFSET \(offset)")]) + clause = Expression.join(" ", [clause, Expression(literal: "OFFSET \(offset)")]) } return clause } @@ -732,11 +732,11 @@ public struct QueryGenerator: GeneratorType { private lazy var columnNames: [String: Int] = { var (columnNames, idx) = ([String: Int](), 0) - column: for each in self.query.columns ?? [Expression<()>(literal: "*")] { + column: for each in self.query.columns ?? [Expression(literal: "*")] { let pair = split(each.expression.SQL) { $0 == "." } let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) - func expandGlob(namespace: Bool) -> Query -> () { + func expandGlob(namespace: Bool) -> Query -> Void { return { table in var query = Query(table.database, table.tableName.unaliased) if let columns = table.columns { query.columns = columns } diff --git a/SQLite/RTree.swift b/SQLite/RTree.swift index 13866f60..9ff0f6c3 100644 --- a/SQLite/RTree.swift +++ b/SQLite/RTree.swift @@ -22,8 +22,8 @@ // THE SOFTWARE. // -public func rtree(primaryKey: Expression, columns: Expression...) -> Expression<()> { +public func rtree(primaryKey: Expression, columns: Expression...) -> Expression { var definitions: [Expressible] = [primaryKey] definitions.extend(columns.map { $0 }) - return wrap(__FUNCTION__, Expression<()>.join(", ", definitions)) + return wrap(__FUNCTION__, Expression.join(", ", definitions)) } diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift index 7e19e19d..a445e676 100644 --- a/SQLite/Schema.swift +++ b/SQLite/Schema.swift @@ -28,12 +28,12 @@ public extension Database { #table: Query, temporary: Bool = false, ifNotExists: Bool = false, - @noescape _ block: SchemaBuilder -> () + @noescape _ block: SchemaBuilder -> Void ) -> Statement { var builder = SchemaBuilder(table) block(builder) let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) - let columns = Expression<()>.join(", ", builder.columns).compile() + let columns = Expression.join(", ", builder.columns).compile() return run("\(create) (\(columns))") } @@ -43,7 +43,7 @@ public extension Database { return run("\(create) AS \(expression.SQL)", expression.bindings) } - public func create(#vtable: Query, ifNotExists: Bool = false, using: Expression<()>) -> Statement { + public func create(#vtable: Query, ifNotExists: Bool = false, using: Expression) -> Statement { let create = createSQL("VIRTUAL TABLE", false, false, ifNotExists, vtable.tableName.unaliased.SQL) return run("\(create) USING \(using.SQL)") } @@ -78,7 +78,7 @@ public extension Database { references: Expression ) -> Statement { return alter(table, define(Expression(column), nil, true, false, check, nil, [ - Expression<()>(literal: "REFERENCES"), namespace(references) + Expression(literal: "REFERENCES"), namespace(references) ])) } @@ -90,7 +90,7 @@ public extension Database { collate: Collation ) -> Statement { return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ - Expression<()>(literal: "COLLATE"), Expression<()>(collate.description) + Expression(literal: "COLLATE"), Expression(collate.description) ])) } @@ -103,7 +103,7 @@ public extension Database { ) -> Statement { let value = defaultValue.map { Expression(value: $0) } return alter(table, define(Expression(column), nil, true, false, check, value, [ - Expression<()>(literal: "COLLATE"), Expression<()>(collate.description) + Expression(literal: "COLLATE"), Expression(collate.description) ])) } @@ -123,7 +123,7 @@ public extension Database { ) -> Statement { let tableName = table.tableName.unaliased.SQL let create = createSQL("INDEX", false, unique, ifNotExists, indexName(tableName, on: columns)) - let joined = Expression<()>.join(", ", columns.map { $0.expression.ordered }) + let joined = Expression.join(", ", columns.map { $0.expression.ordered }) return run("\(create) ON \(tableName) (\(joined.compile()))") } @@ -237,7 +237,7 @@ public final class SchemaBuilder { references: Expression ) { assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression<()>(literal: "REFERENCES"), namespace(references)] + let expressions: [Expressible] = [Expression(literal: "REFERENCES"), namespace(references)] column(name, nil, false, unique, check, nil, expressions) } @@ -262,7 +262,7 @@ public final class SchemaBuilder { references: Expression ) { assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression<()>(literal: "REFERENCES"), namespace(references)] + let expressions: [Expressible] = [Expression(literal: "REFERENCES"), namespace(references)] column(Expression(name), nil, true, unique, check, nil, expressions) } @@ -289,7 +289,7 @@ public final class SchemaBuilder { defaultValue value: Expression?, collate: Collation ) { - let expressions: [Expressible] = [Expression<()>(literal: "COLLATE \(collate)")] + let expressions: [Expressible] = [Expression(literal: "COLLATE \(collate)")] column(name, nil, false, unique, check, value, expressions) } @@ -353,17 +353,17 @@ public final class SchemaBuilder { } private func primaryKey(composite: [Expressible]) { - let primaryKey = Expression<()>.join(", ", composite) - columns.append(Expression<()>(literal: "PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) + let primaryKey = Expression.join(", ", composite) + columns.append(Expression(literal: "PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) } public func unique(column: Expressible...) { - let unique = Expression<()>.join(", ", column) - columns.append(Expression<()>(literal: "UNIQUE(\(unique.SQL))", unique.bindings)) + let unique = Expression.join(", ", column) + columns.append(Expression(literal: "UNIQUE(\(unique.SQL))", unique.bindings)) } public func check(condition: Expression) { - columns.append(Expression<()>(literal: "CHECK (\(condition.SQL))", condition.bindings)) + columns.append(Expression(literal: "CHECK (\(condition.SQL))", condition.bindings)) } public enum Dependency: String { @@ -387,11 +387,11 @@ public final class SchemaBuilder { delete: Dependency? = nil ) { assertForeignKeysEnabled() - var parts: [Expressible] = [Expression<()>(literal: "FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] + var parts: [Expressible] = [Expression(literal: "FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] parts.append(namespace(references)) - if let update = update { parts.append(Expression<()>(literal: "ON UPDATE \(update.rawValue)")) } - if let delete = delete { parts.append(Expression<()>(literal: "ON DELETE \(delete.rawValue)")) } - columns.append(Expression<()>.join(" ", parts)) + if let update = update { parts.append(Expression(literal: "ON UPDATE \(update.rawValue)")) } + if let delete = delete { parts.append(Expression(literal: "ON DELETE \(delete.rawValue)")) } + columns.append(Expression.join(" ", parts)) } public func foreignKey( @@ -410,8 +410,8 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { - let compositeA = Expression(Expression<()>.join(", ", [columns.0, columns.1])) - let compositeB = Expression(Expression<()>.join(", ", [references.0, references.1])) + let compositeA = Expression(Expression.join(", ", [columns.0, columns.1])) + let compositeB = Expression(Expression.join(", ", [references.0, references.1])) foreignKey(compositeA, references: compositeB, update: update, delete: delete) } @@ -421,8 +421,8 @@ public final class SchemaBuilder { update: Dependency? = nil, delete: Dependency? = nil ) { - let compositeA = Expression(Expression<()>.join(", ", [columns.0, columns.1, columns.2])) - let compositeB = Expression(Expression<()>.join(", ", [references.0, references.1, references.2])) + let compositeA = Expression(Expression.join(", ", [columns.0, columns.1, columns.2])) + let compositeB = Expression(Expression.join(", ", [references.0, references.1, references.2])) foreignKey(compositeA, references: compositeB, update: update, delete: delete) } @@ -439,7 +439,7 @@ private func namespace(column: Expressible) -> Expressible { let string = String(character) return SQL + (string == "." ? "(" : string) } - return Expression<()>(literal: "\(reference))", expression.bindings) + return Expression(literal: "\(reference))", expression.bindings) } private func define( @@ -451,17 +451,17 @@ private func define( defaultValue: Expression?, expressions: [Expressible]? ) -> Expressible { - var parts: [Expressible] = [Expression<()>(column), Expression<()>(literal: V.declaredDatatype)] + var parts: [Expressible] = [Expression(column), Expression(literal: V.declaredDatatype)] if let primaryKey = primaryKey { - parts.append(Expression<()>(literal: "PRIMARY KEY")) - if primaryKey == .Autoincrement { parts.append(Expression<()>(literal: "AUTOINCREMENT")) } + parts.append(Expression(literal: "PRIMARY KEY")) + if primaryKey == .Autoincrement { parts.append(Expression(literal: "AUTOINCREMENT")) } } - if !null { parts.append(Expression<()>(literal: "NOT NULL")) } - if unique { parts.append(Expression<()>(literal: "UNIQUE")) } - if let check = check { parts.append(Expression<()>(literal: "CHECK (\(check.SQL))", check.bindings)) } - if let value = defaultValue { parts.append(Expression<()>(literal: "DEFAULT \(value.SQL)", value.bindings)) } + if !null { parts.append(Expression(literal: "NOT NULL")) } + if unique { parts.append(Expression(literal: "UNIQUE")) } + if let check = check { parts.append(Expression(literal: "CHECK (\(check.SQL))", check.bindings)) } + if let value = defaultValue { parts.append(Expression(literal: "DEFAULT \(value.SQL)", value.bindings)) } if let expressions = expressions { parts += expressions } - return Expression<()>.join(" ", parts) + return Expression.join(" ", parts) } private func createSQL( diff --git a/SQLite/Value.swift b/SQLite/Value.swift index af4383a8..aefb44c1 100644 --- a/SQLite/Value.swift +++ b/SQLite/Value.swift @@ -51,7 +51,7 @@ public struct Blob { private let data: NSData - public var bytes: UnsafePointer<()> { + public var bytes: UnsafePointer { return data.bytes } @@ -59,7 +59,7 @@ public struct Blob { return data.length } - public init(bytes: UnsafePointer<()>, length: Int) { + public init(bytes: UnsafePointer, length: Int) { data = NSData(bytes: bytes, length: length) } From ef9c07c1d2571eda15beeb5df6fec118d2ec660b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:35:39 -0400 Subject: [PATCH 0273/1046] Simplify asynchronous testing Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 71 ++++++++++++-------------------- SQLite Tests/TestHelper.swift | 6 +++ 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index 3ffae90b..e7c1cf29 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -293,60 +293,52 @@ class DatabaseTests: SQLiteTestCase { } func test_updateHook_setsUpdateHook() { - let updateHook = expectationWithDescription("updateHook") - db.updateHook { operation, db, table, rowid in - XCTAssertEqual(.Insert, operation) - XCTAssertEqual("main", db) - XCTAssertEqual("users", table) - XCTAssertEqual(1, rowid) - updateHook.fulfill() - } - executeAndWait { + async { done in + db.updateHook { operation, db, table, rowid in + XCTAssertEqual(.Insert, operation) + XCTAssertEqual("main", db) + XCTAssertEqual("users", table) + XCTAssertEqual(1, rowid) + done() + } insertUser("alice") } } func test_commitHook_setsCommitHook() { - let commitHook = expectationWithDescription("commitHook") - db.commitHook { - commitHook.fulfill() - return .Commit - } - executeAndWait { - self.db.transaction { _ in - self.insertUser("alice") + async { done in + db.commitHook { + done() return .Commit } - XCTAssertEqual(1, self.db.scalar("SELECT count(*) FROM users") as! Int64) + db.transaction { _ in + insertUser("alice") + return .Commit + } + XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as! Int64) } } func test_rollbackHook_setsRollbackHook() { - let rollbackHook = expectationWithDescription("commitHook") - db.rollbackHook { - rollbackHook.fulfill() - } - executeAndWait { - self.db.transaction { _ in - self.insertUser("alice") + async { done in + db.rollbackHook(done) + db.transaction { _ in + insertUser("alice") return .Rollback } - XCTAssertEqual(0, self.db.scalar("SELECT count(*) FROM users") as! Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as! Int64) } } func test_commitHook_withRollback_rollsBack() { - let rollbackHook = expectationWithDescription("commitHook") - db.commitHook { .Rollback } - db.rollbackHook { - rollbackHook.fulfill() - } - executeAndWait { - self.db.transaction { _ in - self.insertUser("alice") + async { done in + db.commitHook { .Rollback } + db.rollbackHook(done) + db.transaction { _ in + insertUser("alice") return .Commit } - XCTAssertEqual(0, self.db.scalar("SELECT count(*) FROM users") as! Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as! Int64) } } @@ -378,13 +370,4 @@ class DatabaseTests: SQLiteTestCase { XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) } - func executeAndWait(block: () -> Void) { - dispatch_async(dispatch_get_main_queue(), block) - waitForExpectationsWithTimeout(5) { error in - if let error = error { - fatalError(error.localizedDescription) - } - } - } - } diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift index 9d712c78..41600001 100644 --- a/SQLite Tests/TestHelper.swift +++ b/SQLite Tests/TestHelper.swift @@ -74,4 +74,10 @@ class SQLiteTestCase: XCTestCase { if let count = trace[SQL] { trace[SQL] = count - 1 } } + func async(expect description: String = "async", timeout: Double = 5, @noescape block: (() -> Void) -> Void) { + let expectation = expectationWithDescription(description) + block(expectation.fulfill) + waitForExpectationsWithTimeout(timeout, handler: nil) + } + } From c4cba1d83364d45f258dddff1564a74f60d00c37 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:37:59 -0400 Subject: [PATCH 0274/1046] Make Database initialization more Swift-like Carrying over ":memory:" and "" seems less-than-ideal. Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 26 ++++++++++++ SQLite/Database.swift | 71 ++++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index e7c1cf29..e177195c 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -9,6 +9,32 @@ class DatabaseTests: SQLiteTestCase { createUsersTable() } + func test_init_withInMemory_returnsInMemoryConnection() { + let db = Database(.InMemory) + XCTAssertEqual("", db.description) + XCTAssertEqual(":memory:", Database.Location.InMemory.description) + } + + func test_init_returnsInMemoryByDefault() { + let db = Database() + XCTAssertEqual("", db.description) + } + + func test_init_withTemporary_returnsTemporaryConnection() { + let db = Database(.Temporary) + XCTAssertEqual("", db.description) + } + + func test_init_withURI_returnsURIConnection() { + let db = Database(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + } + + func test_init_withString_returnsURIConnection() { + let db = Database("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + } + func test_readonly_returnsFalseOnReadWriteConnections() { XCTAssert(!db.readonly) } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 3bf17d8c..0b8377b8 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -27,26 +27,64 @@ import sqlite3 /// A connection (handle) to SQLite. public final class Database { + /// The location of a SQLite database. + public enum Location { + + /// An in-memory database (equivalent to `.URI(":memory:")`). + /// + /// See: + case InMemory + + /// A temporary, file-backed database (equivalent to `.URI("")`). + /// + /// See: + case Temporary + + /// A database located at the given URI filename (or path). + /// + /// See: + /// + /// :param: filename A URI filename + case URI(String) + + } + internal var handle: COpaquePointer = nil /// Whether or not the database was opened in a read-only state. public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } - /// Instantiates a new connection to a database. + /// Initializes a new connection to a database. /// - /// :param: path The path to the database. Creates a new database if it - /// doesn’t already exist (unless in read-only mode). Pass - /// ":memory:" (or nothing) to open a new, in-memory - /// database. Pass "" (or nil) to open a temporary, - /// file-backed database. Default: ":memory:". + /// :param: location The location of the database. Creates a new database if + /// it doesn’t already exist (unless in read-only mode). + /// + /// Default: `.InMemory`. /// /// :param: readonly Whether or not to open the database in a read-only - /// state. Default: false. + /// state. + /// + /// Default: `false`. /// /// :returns: A new database connection. - public init(_ path: String? = ":memory:", readonly: Bool = false) { + public init(_ location: Location = .InMemory, readonly: Bool = false) { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try { sqlite3_open_v2(path ?? "", &self.handle, flags | SQLITE_OPEN_FULLMUTEX, nil) } + try { sqlite3_open_v2(location.description, &self.handle, flags | SQLITE_OPEN_FULLMUTEX, nil) } + } + + /// Initializes a new connection to a database. + /// + /// :param: filename The location of the database. Creates a new database if + /// it doesn’t already exist (unless in read-only mode). + /// + /// :param: readonly Whether or not to open the database in a read-only + /// state. + /// + /// Default: `false`. + /// + /// :returns: A new database connection. + public convenience init(_ filename: String, readonly: Bool = false) { + self.init(.URI(filename), readonly: readonly) } deinit { try { sqlite3_close(self.handle) } } // sqlite3_close_v2 in Yosemite/iOS 8? @@ -583,6 +621,21 @@ extension Database: Printable { } +extension Database.Location: Printable { + + public var description: String { + switch self { + case .InMemory: + return ":memory:" + case .Temporary: + return "" + case .URI(let URI): + return URI + } + } + +} + internal func quote(#literal: String) -> String { return quote(literal, "'") } From 3203dfdf5319cd699adde77045bf10e7cd34cc1c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:39:25 -0400 Subject: [PATCH 0275/1046] Better document defaults And fix the documentation for the create function shims while we're at it. Signed-off-by: Stephen Celis --- SQLite/Database.swift | 26 +++++++++++++++++--------- SQLite/Functions.swift | 12 +++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 0b8377b8..7e3dff95 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -257,8 +257,9 @@ public final class Database { /// Starts a new transaction with the given mode. /// - /// :param: mode The mode in which a transaction acquires a lock. (Default: - /// .Deferred.) + /// :param: mode The mode in which a transaction acquires a lock. + /// + /// Default: `.Deferred` /// /// :returns: The BEGIN TRANSACTION statement. public func transaction(_ mode: TransactionMode = .Deferred) -> Statement { @@ -268,8 +269,9 @@ public final class Database { /// Runs a transaction with the given savepoint name (if omitted, it will /// generate a UUID). /// - /// :param: mode The mode in which a transaction acquires a lock. (Default: - /// .Deferred.) + /// :param: mode The mode in which a transaction acquires a lock. + /// + /// Default: `.Deferred` /// /// :param: block A closure to run SQL statements within the transaction. /// Should return a TransactionResult depending on success or @@ -285,7 +287,8 @@ public final class Database { /// /// :param: all Only applicable if a savepoint is open. If true, commits all /// open savepoints, otherwise releases the current savepoint. - /// (Default: false.) + /// + /// Default: `false` /// /// :returns: The COMMIT (or RELEASE) statement. public func commit(all: Bool = false) -> Statement { @@ -301,7 +304,9 @@ public final class Database { /// /// :param: all Only applicable if a savepoint is open. If true, rolls back /// all open savepoints, otherwise rolls back the current - /// savepoint. (Default: false.) + /// savepoint. + /// + /// Default: `false` /// /// :returns: The ROLLBACK statement. public func rollback(all: Bool = false) -> Statement { @@ -519,13 +524,16 @@ public final class Database { /// :param: function The name of the function to create or redefine. /// /// :param: argc The number of arguments that the function takes. - /// If this parameter is -1, then the SQL function may - /// take any number of arguments. (Default: -1.) + /// If this parameter is `-1`, then the SQL function + /// may take any number of arguments. + /// + /// Default: `-1` /// /// :param: deterministic Whether or not the function is deterministic. If /// the function always returns the same result for a /// given input, SQLite can make optimizations. - /// (Default: false.) + /// + /// Default: `false` /// /// :param: block A block of code to run when the function is /// called. The block is called with an array of raw diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift index 148ee031..ef83b57e 100644 --- a/SQLite/Functions.swift +++ b/SQLite/Functions.swift @@ -30,10 +30,16 @@ public extension Database { /// Creates or redefines a custom SQL function. /// - /// :param: function The name of the function to create or redefine. + /// :param: function The name of the function to create or redefine. /// - /// :param: block A block of code to run when the function is called. - /// The assigned types must be explicit. + /// :param: deterministic Whether or not the function is deterministic. If + /// the function always returns the same result for a + /// given input, SQLite can make optimizations. + /// + /// Default: `false` + /// + /// :param: block A block of code to run when the function is + /// called. The assigned types must be explicit. /// /// :returns: A closure returning an SQL expression to call the function. public func create(#function: String, deterministic: Bool = false, _ block: () -> Z) -> (() -> Expression) { From 7749e3ee7923e79177b1c94b54d3439c2a32aed5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:41:39 -0400 Subject: [PATCH 0276/1046] Make `lastError` optional Return it only when the result code is an error code. Signed-off-by: Stephen Celis --- SQLite Tests/DatabaseTests.swift | 23 +++++++++++++++++++++++ SQLite/Database.swift | 9 +++++++-- SQLite/Statement.swift | 8 ++++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift index e177195c..b535bc76 100644 --- a/SQLite Tests/DatabaseTests.swift +++ b/SQLite Tests/DatabaseTests.swift @@ -396,4 +396,27 @@ class DatabaseTests: SQLiteTestCase { XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) } + func test_lastError_withOK_returnsNil() { + XCTAssertNil(db.lastError) + } + + func test_lastError_withRow_returnsNil() { + insertUsers("alice", "betty") + db.prepare("SELECT * FROM users").step() + + XCTAssertNil(db.lastError) + } + + func test_lastError_withDone_returnsNil() { + db.prepare("SELECT * FROM users").step() + + XCTAssertNil(db.lastError) + } + + func test_lastError_withError_returnsError() { + insertUsers("alice", "alice") + + XCTAssertNotNil(db.lastError) + } + } diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 7e3dff95..84180ec4 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -604,12 +604,15 @@ public final class Database { // MARK: - Error Handling /// Returns the last error produced on this connection. - public var lastError: String { + public var lastError: String? { + if successCodes.contains(sqlite3_errcode(handle)) { + return nil + } return String.fromCString(sqlite3_errmsg(handle))! } internal func try(block: () -> Int32) { - perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError)") } } + perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError!)") } } } // MARK: - Threading @@ -644,6 +647,8 @@ extension Database.Location: Printable { } +internal let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + internal func quote(#literal: String) -> String { return quote(literal, "'") } diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index ddebf9d5..12ebe5c3 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -180,7 +180,7 @@ public final class Statement { /// :returns: Whether or not a statement has produced an error. public var failed: Bool { - return !(status == SQLITE_OK || status == SQLITE_ROW || status == SQLITE_DONE) + return reason != nil } /// :returns: The reason for an error. @@ -192,9 +192,9 @@ public final class Statement { if failed { return } database.perform { self.status = block() - if self.failed { - self.reason = String.fromCString(sqlite3_errmsg(self.database.handle)) - assert(self.status == SQLITE_CONSTRAINT || self.status == SQLITE_INTERRUPT, "\(self.reason!)") + if let error = self.database.lastError { + self.reason = error + assert(self.status == SQLITE_CONSTRAINT || self.status == SQLITE_INTERRUPT, "\(error)") } } } From 895282da9d9c3c7b873fe5e7d44923198aa6148b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:42:32 -0400 Subject: [PATCH 0277/1046] Remove unnecessary default There's no reason to initialize an expression literal without a SQL string. Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 4b1526c4..844b4613 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -38,8 +38,8 @@ public struct Expression { /// /// :param: SQL An SQL string. /// - /// :param: bindings Values to be bound to the given SQL. - public init(literal SQL: String = "", _ bindings: [Binding?] = []) { + /// :param: bindings An optional list of values to be bound to the SQL. + public init(literal SQL: String, _ bindings: [Binding?] = []) { (self.SQL, self.bindings) = (SQL, bindings) } From 6bc2aed199bf40dae42aeba618a093f14689eb5e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:43:24 -0400 Subject: [PATCH 0278/1046] Document the collation enumeration It deserves it. Signed-off-by: Stephen Celis --- SQLite/Expression.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift index 844b4613..1262f76b 100644 --- a/SQLite/Expression.swift +++ b/SQLite/Expression.swift @@ -318,14 +318,23 @@ public func ^ (lhs: V, rhs: Expression) public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } +/// A collating function used to compare to strings. +/// +/// See: public enum Collation { + /// Compares string by raw data. case Binary + /// Like binary, but folds uppercase ASCII letters into their lowercase + /// equivalents. case Nocase + /// Like binary, but strips trailing space. case Rtrim + /// A custom collating sequence identified by the given string, registered + /// using `Database.create(collation:…)` case Custom(String) } From 6d9fb5c6ea626f73c4bf37b79940c902d29f46b7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:44:04 -0400 Subject: [PATCH 0279/1046] Remove private group() function It's used as a funnel and shouldn't be called directly. After all, `having: nil` makes no sense. Signed-off-by: Stephen Celis --- SQLite/Query.swift | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 57dd32f3..b0775c3b 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -218,7 +218,11 @@ public struct Query { /// :param: having A condition determining which groups are returned. /// /// :returns: A query with the given GROUP BY clause applied. - public func group(by: [Expressible], having: Expression? = nil) -> Query { + public func group(by: [Expressible], having: Expression) -> Query { + return group(by, having: Expression(having)) + } + + private func group(by: [Expressible], having: Expression? = nil) -> Query { var query = self var group = Expression.join(" ", [Expression(literal: "GROUP BY"), Expression.join(", ", by)]) if let having = having { group = Expression.join(" ", [group, Expression(literal: "HAVING"), having]) } @@ -226,17 +230,6 @@ public struct Query { return query } - /// Sets a GROUP BY-HAVING clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: [Expressible], having: Expression) -> Query { - return group(by, having: Expression(having)) - } - /// Sets an ORDER BY clause on the query. /// /// :param: by An ordered list of columns and directions to sort by. From 17e0f96d8b3fbc50f557108f52c35b28859b5e2e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:45:08 -0400 Subject: [PATCH 0280/1046] Simplify limit() logic At least for the functional mindset. A short one-liner should be more readable than an if-else. Signed-off-by: Stephen Celis --- SQLite/Query.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/SQLite/Query.swift b/SQLite/Query.swift index b0775c3b..ca836f5f 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -273,11 +273,7 @@ public struct Query { // prevents limit(nil, offset: 5) private func limit(#to: Int?, offset: Int? = nil) -> Query { var query = self - if let to = to { - query.limit = (to, offset) - } else { - query.limit = nil - } + query.limit = to.map { ($0, offset) } return query } From cd5567b95ed3aac18461d3a86f5e854a73b7dbe5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:46:21 -0400 Subject: [PATCH 0281/1046] Documentation improvements Generally: formatting is better than none. And documentation is better than none. Signed-off-by: Stephen Celis --- SQLite/Query.swift | 56 +++++++++++++++++++++++++++--------------- SQLite/Statement.swift | 4 +++ 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/SQLite/Query.swift b/SQLite/Query.swift index ca836f5f..8365e630 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -311,9 +311,9 @@ public struct Query { /// Prefixes a star with the query’s table name or alias. /// - /// :param: star A literal *. + /// :param: star A literal `*`. /// - /// :returns: A * expression namespaced with the query’s table name or + /// :returns: A `*` expression namespaced with the query’s table name or /// alias. public subscript(star: Star) -> Expression { return namespace(star(nil, nil)) @@ -423,7 +423,7 @@ public struct Query { /// Runs an INSERT statement against the query. /// - /// :param: action An action to run in case of a conflict. + /// :param: action An optional action to run in case of a conflict. /// :param: value A value to set. /// :param: more A list of additional values to set. /// @@ -434,7 +434,7 @@ public struct Query { /// Runs an INSERT statement against the query. /// - /// :param: action An action to run in case of a conflict. + /// :param: action An optional action to run in case of a conflict. /// :param: values An array of values to set. /// /// :returns: The rowid and statement. @@ -492,7 +492,7 @@ public struct Query { // MARK: - Aggregate Functions - /// Runs count() against the query. + /// Runs `count()` against the query. /// /// :param: column The column used for the calculation. /// @@ -501,7 +501,7 @@ public struct Query { return calculate(_count(column))! } - /// Runs count() with DISTINCT against the query. + /// Runs `count()` with DISTINCT against the query. /// /// :param: column The column used for the calculation. /// @@ -510,7 +510,7 @@ public struct Query { return calculate(_count(distinct: column))! } - /// Runs count() with DISTINCT against the query. + /// Runs `count()` with DISTINCT against the query. /// /// :param: column The column used for the calculation. /// @@ -519,7 +519,7 @@ public struct Query { return calculate(_count(distinct: column))! } - /// Runs max() against the query. + /// Runs `max()` against the query. /// /// :param: column The column used for the calculation. /// @@ -531,7 +531,7 @@ public struct Query { return calculate(_max(column)) } - /// Runs min() against the query. + /// Runs `min()` against the query. /// /// :param: column The column used for the calculation. /// @@ -543,7 +543,7 @@ public struct Query { return calculate(_min(column)) } - /// Runs avg() against the query. + /// Runs `avg()` against the query. /// /// :param: column The column used for the calculation. /// @@ -555,7 +555,7 @@ public struct Query { return calculate(_average(column)) } - /// Runs avg() with DISTINCT against the query. + /// Runs `avg()` with DISTINCT against the query. /// /// :param: column The column used for the calculation. /// @@ -567,7 +567,7 @@ public struct Query { return calculate(_average(distinct: column)) } - /// Runs sum() against the query. + /// Runs `sum()` against the query. /// /// :param: column The column used for the calculation. /// @@ -579,7 +579,7 @@ public struct Query { return calculate(_sum(column)) } - /// Runs sum() with DISTINCT against the query. + /// Runs `sum()` with DISTINCT against the query. /// /// :param: column The column used for the calculation. /// @@ -591,7 +591,7 @@ public struct Query { return calculate(_sum(distinct: column)) } - /// Runs total() against the query. + /// Runs `total()` against the query. /// /// :param: column The column used for the calculation. /// @@ -603,7 +603,7 @@ public struct Query { return calculate(_total(column))! } - /// Runs total() with DISTINCT against the query. + /// Runs `total()` with DISTINCT against the query. /// /// :param: column The column used for the calculation. /// @@ -624,19 +624,19 @@ public struct Query { // MARK: - Array - /// Runs count(*) against the query and returns it. + /// Runs `count(*)` against the query and returns the number of rows. public var count: Int { return calculate(_count(*))! } - /// Returns true if the query has no rows. + /// Returns `true` iff the query has no rows. public var isEmpty: Bool { return first == nil } - /// The first row (or nil if the query returns no rows). + /// The first row (or `nil` if the query returns no rows). public var first: Row? { var generator = limit(to: 1, offset: limit?.offset).generate() return generator.next() } - /// The last row (or nil if the query returns no rows). + /// The last row (or `nil` if the query returns no rows). public var last: Row? { return reverse().first } @@ -775,23 +775,39 @@ extension Query: Printable { } /// The result of an INSERT executed by a query. +/// +/// :param: rowid The insert rowid of the result (or `nil` on failure). +/// +/// :param: statement The associated statement. public typealias Insert = (rowid: Int64?, statement: Statement) -/// The result of an UPDATE or DELETE executed by a query. +/// The result of an bulk INSERT, UPDATE or DELETE executed by a query. +/// +/// :param: changes The number of rows affected (or `nil` on failure). +/// +/// :param: statement The associated statement. public typealias Change = (changes: Int?, statement: Statement) +/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its +/// associated statement. public func && (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { return lhs && rhs().statement } +/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its +/// associated statement. public func || (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { return lhs || rhs().statement } +/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its +/// associated statement. public func && (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { return lhs && rhs().statement } +/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its +/// associated statement. public func || (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { return lhs || rhs().statement } diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 12ebe5c3..7d90e671 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -230,11 +230,15 @@ extension Statement: Printable { } +/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its +/// associated statement. public func && (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { if lhs.status == SQLITE_OK { lhs.run() } return lhs.failed ? lhs : rhs() } +/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its +/// associated statement. public func || (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { if lhs.status == SQLITE_OK { lhs.run() } return lhs.failed ? rhs() : lhs From 27f0bf8cafb3dd2f512c310b21857b3f98c9c53d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:46:56 -0400 Subject: [PATCH 0282/1046] Add nullability attributes to Objective-C code Just a small improvement for the code internally. Signed-off-by: Stephen Celis --- SQLite/SQLite-Bridging.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h index 862ebb10..6bb9f2de 100644 --- a/SQLite/SQLite-Bridging.h +++ b/SQLite/SQLite-Bridging.h @@ -28,26 +28,29 @@ typedef struct SQLiteHandle SQLiteHandle; typedef struct SQLiteContext SQLiteContext; typedef struct SQLiteValue SQLiteValue; +NS_ASSUME_NONNULL_BEGIN typedef int (^_SQLiteBusyHandlerCallback)(int times); -int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback callback); +int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback __nullable callback); typedef void (^_SQLiteTraceCallback)(const char * SQL); -void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback callback); +void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback __nullable callback); typedef void (^_SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, long long rowid); -void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback callback); +void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback __nullable callback); typedef int (^_SQLiteCommitHookCallback)(); -void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback callback); +void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback __nullable callback); typedef void (^_SQLiteRollbackHookCallback)(); -void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback callback); +void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback __nullable callback); -typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue ** argv); -int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback); +typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue * __nonnull * __nonnull argv); +int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback __nullable callback); typedef int (^_SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); -int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback callback); +int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback __nullable callback); + +typedef NSString * __nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, __nullable _SQLiteTokenizerNextCallback callback); +NS_ASSUME_NONNULL_END -typedef NSString * (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _SQLiteTokenizerNextCallback callback); From 6e71ca9c149c6997a43b3b4134bc9186f8a25d22 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 May 2015 19:47:59 -0400 Subject: [PATCH 0283/1046] Simplify lazy variables No need for a closure for one-liners. Signed-off-by: Stephen Celis --- SQLite/Statement.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index 7d90e671..ddf891fe 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -34,7 +34,8 @@ public final class Statement { private let database: Database - public lazy var row: Cursor = { Cursor(self) }() + /// A cursor pointing to the current row. + public lazy var row: Cursor = Cursor(self) internal init(_ database: Database, _ SQL: String) { self.database = database @@ -43,11 +44,11 @@ public final class Statement { deinit { sqlite3_finalize(handle) } - public lazy var columnCount: Int = { Int(sqlite3_column_count(self.handle)) }() + public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) - public lazy var columnNames: [String] = { - (0.. Date: Sat, 16 May 2015 18:24:48 -0400 Subject: [PATCH 0284/1046] Stop nagging me, Xcode Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 2 +- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme | 2 +- SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 50bae729..55a6b848 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -476,7 +476,7 @@ DC3773EA19C8CBB3004FCF85 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0630; + LastUpgradeCheck = 0640; ORGANIZATIONNAME = "Stephen Celis"; TargetAttributes = { DC3773F219C8CBB3004FCF85 = { diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme index 0e27d856..b41d3602 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme @@ -1,6 +1,6 @@ Date: Sun, 17 May 2015 19:08:23 -0400 Subject: [PATCH 0285/1046] Remove dummy framework We can load the SDK header from our custom module map, instead. Let's also link to the iphoneos SDK header, since it shouldn't matter: it's always identical to the iphonesimulator header, and at worst a version behind the macosx header. Signed-off-by: Stephen Celis --- Documentation/Index.md | 4 +- SQLite.xcodeproj/project.pbxproj | 165 ++---------------------------- SQLite/Database.swift | 2 - SQLite/SQLite-Bridging.m | 2 - SQLite/SQLite.xcconfig | 2 + SQLite/Statement.swift | 2 - SQLite/module.modulemap | 11 ++ SQLiteCipher/Cipher.swift | 2 - sqlite3/Info.plist | 26 ----- sqlite3/iphoneos.modulemap | 4 - sqlite3/iphonesimulator.modulemap | 4 - sqlite3/macosx.modulemap | 4 - sqlite3/sqlite3.xcconfig | 5 - 13 files changed, 20 insertions(+), 213 deletions(-) create mode 100644 SQLite/module.modulemap delete mode 100644 sqlite3/Info.plist delete mode 100644 sqlite3/iphoneos.modulemap delete mode 100644 sqlite3/iphonesimulator.modulemap delete mode 100644 sqlite3/macosx.modulemap delete mode 100644 sqlite3/sqlite3.xcconfig diff --git a/Documentation/Index.md b/Documentation/Index.md index 1a3b6359..61ccb1da 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -102,9 +102,7 @@ It’s possible to use SQLite.swift in a target that doesn’t support framework 2. Copy the SQLite.swift source files (from its **SQLite** directory) into your Xcode project. - 3. Remove `import sqlite3` (and `@import sqlite3;`) from the SQLite.swift source files that call it. - - 4. Add the following lines to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`). + 3. Add the following lines to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`). ``` swift #import diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 55a6b848..52ee9ba2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ DC37743819C8D693004FCF85 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC475E9E19F2199900788FBD /* ExpressionTests.swift */; }; - DC5B12131ABE3298000DA146 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC5B12121ABE3298000DA146 /* libsqlite3.dylib */; }; DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; @@ -55,13 +54,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - DC2393CF1ABE37A5003FF113 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; - proxyType = 1; - remoteGlobalIDString = DCAE4D141ABE0B3300EFCE7A; - remoteInfo = sqlite3; - }; DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; @@ -83,13 +75,6 @@ remoteGlobalIDString = DC3773F219C8CBB3004FCF85; remoteInfo = SQLite; }; - DCAE4D341ABE0C2800EFCE7A /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; - proxyType = 1; - remoteGlobalIDString = DCAE4D141ABE0B3300EFCE7A; - remoteInfo = sqlite3; - }; DCC6B3AA1A91979100734B78 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; @@ -133,17 +118,11 @@ DC3F170F1A8127A300C83A2F /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; DC3F17121A814F7000C83A2F /* FunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionsTests.swift; sourceTree = ""; }; DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - DC5B12121ABE3298000DA146 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; }; DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + DC74B0421B095575007E1138 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; DC97EC9F1ADE955E00F550A6 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = sqlite3.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCAE4D181ABE0B3300EFCE7A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = sqlite3.xcconfig; sourceTree = ""; }; - DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = ""; }; - DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = ""; }; - DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = ""; }; DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; @@ -175,14 +154,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCAE4D111ABE0B3300EFCE7A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DC5B12131ABE3298000DA146 /* libsqlite3.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; DCC6B3761A9191C300734B78 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -250,7 +221,6 @@ DC10500F19C904DD00D8CA30 /* SQLite Tests */, DCC6B3A11A91949C00734B78 /* SQLiteCipher */, DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */, - DCAE4D161ABE0B3300EFCE7A /* sqlite3 */, DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */, DC3773F419C8CBB3004FCF85 /* Products */, ); @@ -266,7 +236,6 @@ DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */, DCC6B3801A9191C300734B78 /* SQLite.framework */, DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */, - DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */, ); name = Products; sourceTree = ""; @@ -302,27 +271,7 @@ DC3773F719C8CBB3004FCF85 /* Info.plist */, DC37744219C8DC91004FCF85 /* libsqlite3.dylib */, DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - DCAE4D161ABE0B3300EFCE7A /* sqlite3 */ = { - isa = PBXGroup; - children = ( - DCAE4D171ABE0B3300EFCE7A /* Supporting Files */, - ); - path = sqlite3; - sourceTree = ""; - }; - DCAE4D171ABE0B3300EFCE7A /* Supporting Files */ = { - isa = PBXGroup; - children = ( - DCAE4D181ABE0B3300EFCE7A /* Info.plist */, - DC5B12121ABE3298000DA146 /* libsqlite3.dylib */, - DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */, - DCAE4D2F1ABE0B8B00EFCE7A /* iphoneos.modulemap */, - DCAE4D301ABE0B8B00EFCE7A /* iphonesimulator.modulemap */, - DCAE4D311ABE0B8B00EFCE7A /* macosx.modulemap */, + DC74B0421B095575007E1138 /* module.modulemap */, ); name = "Supporting Files"; sourceTree = ""; @@ -356,13 +305,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCAE4D121ABE0B3300EFCE7A /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DCC6B3781A9191C300734B78 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -388,7 +330,6 @@ buildRules = ( ); dependencies = ( - DCAE4D351ABE0C2800EFCE7A /* PBXTargetDependency */, ); name = SQLite; productName = SQLite; @@ -413,24 +354,6 @@ productReference = DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - DCAE4D141ABE0B3300EFCE7A /* sqlite3 */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCAE4D281ABE0B3400EFCE7A /* Build configuration list for PBXNativeTarget "sqlite3" */; - buildPhases = ( - DCAE4D101ABE0B3300EFCE7A /* Sources */, - DCAE4D111ABE0B3300EFCE7A /* Frameworks */, - DCAE4D121ABE0B3300EFCE7A /* Headers */, - DCAE4D131ABE0B3300EFCE7A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = sqlite3; - productName = sqlite3; - productReference = DCAE4D151ABE0B3300EFCE7A /* sqlite3.framework */; - productType = "com.apple.product-type.framework"; - }; DCC6B36D1A9191C300734B78 /* SQLiteCipher */ = { isa = PBXNativeTarget; buildConfigurationList = DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */; @@ -445,7 +368,6 @@ ); dependencies = ( DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */, - DC2393D01ABE37A5003FF113 /* PBXTargetDependency */, ); name = SQLiteCipher; productName = SQLite; @@ -485,9 +407,6 @@ DC3773FD19C8CBB3004FCF85 = { CreatedOnToolsVersion = 6.1; }; - DCAE4D141ABE0B3300EFCE7A = { - CreatedOnToolsVersion = 6.3; - }; }; }; buildConfigurationList = DC3773ED19C8CBB3004FCF85 /* Build configuration list for PBXProject "SQLite" */; @@ -512,7 +431,6 @@ DC3773FD19C8CBB3004FCF85 /* SQLite Tests */, DCC6B36D1A9191C300734B78 /* SQLiteCipher */, DCC6B3821A9191D100734B78 /* SQLiteCipher Tests */, - DCAE4D141ABE0B3300EFCE7A /* sqlite3 */, ); }; /* End PBXProject section */ @@ -542,13 +460,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCAE4D131ABE0B3300EFCE7A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DCC6B37C1A9191C300734B78 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -571,15 +482,15 @@ buildActionMask = 2147483647; files = ( DC37743519C8D626004FCF85 /* Database.swift in Sources */, - DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, - DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */, DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */, DC37743819C8D693004FCF85 /* Value.swift in Sources */, DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */, - DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */, + DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, DCAD429719E2E0F1004A51DF /* Query.swift in Sources */, + DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */, + DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */, DC109CE11A0C4D970070988E /* Schema.swift in Sources */, - DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, + DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -599,13 +510,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - DCAE4D101ABE0B3300EFCE7A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; DCC6B36E1A9191C300734B78 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -636,11 +540,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - DC2393D01ABE37A5003FF113 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DCAE4D141ABE0B3300EFCE7A /* sqlite3 */; - targetProxy = DC2393CF1ABE37A5003FF113 /* PBXContainerItemProxy */; - }; DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = sqlcipher; @@ -651,11 +550,6 @@ target = DC3773F219C8CBB3004FCF85 /* SQLite */; targetProxy = DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */; }; - DCAE4D351ABE0C2800EFCE7A /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DCAE4D141ABE0B3300EFCE7A /* sqlite3 */; - targetProxy = DCAE4D341ABE0C2800EFCE7A /* PBXContainerItemProxy */; - }; DCC6B3AB1A91979100734B78 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DCC6B36D1A9191C300734B78 /* SQLiteCipher */; @@ -799,44 +693,6 @@ }; name = Release; }; - DCAE4D291ABE0B3400EFCE7A /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; - buildSettings = { - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = sqlite3/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - DCAE4D2A1ABE0B3400EFCE7A /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCAE4D2E1ABE0B6300EFCE7A /* sqlite3.xcconfig */; - buildSettings = { - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = sqlite3/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; DCC6B37E1A9191C300734B78 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; @@ -933,15 +789,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCAE4D281ABE0B3400EFCE7A /* Build configuration list for PBXNativeTarget "sqlite3" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DCAE4D291ABE0B3400EFCE7A /* Debug */, - DCAE4D2A1ABE0B3400EFCE7A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/SQLite/Database.swift b/SQLite/Database.swift index 84180ec4..e59d3166 100644 --- a/SQLite/Database.swift +++ b/SQLite/Database.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import sqlite3 - /// A connection (handle) to SQLite. public final class Database { diff --git a/SQLite/SQLite-Bridging.m b/SQLite/SQLite-Bridging.m index a473091a..26b6897d 100644 --- a/SQLite/SQLite-Bridging.m +++ b/SQLite/SQLite-Bridging.m @@ -24,8 +24,6 @@ #import "SQLite-Bridging.h" -@import sqlite3; - #import "fts3_tokenizer.h" static int __SQLiteBusyHandler(void * context, int tries) { diff --git a/SQLite/SQLite.xcconfig b/SQLite/SQLite.xcconfig index 5abd8549..d021a766 100644 --- a/SQLite/SQLite.xcconfig +++ b/SQLite/SQLite.xcconfig @@ -1,6 +1,8 @@ APPLICATION_EXTENSION_API_ONLY = YES SWIFT_WHOLE_MODULE_OPTIMIZATION = YES +MODULEMAP_FILE = $(SRCROOT)/SQLite/module.modulemap + // Universal Framework SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift index ddf891fe..a819a43d 100644 --- a/SQLite/Statement.swift +++ b/SQLite/Statement.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import sqlite3 - internal let SQLITE_STATIC = sqlite3_destructor_type(COpaquePointer(bitPattern: 0)) internal let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern: -1)) diff --git a/SQLite/module.modulemap b/SQLite/module.modulemap new file mode 100644 index 00000000..f0492e3a --- /dev/null +++ b/SQLite/module.modulemap @@ -0,0 +1,11 @@ +framework module SQLite { + umbrella header "SQLite.h" + + // Load the SDK header alongside SQLite.swift. Alternative headers: + // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + + export * + module * { export * } +} diff --git a/SQLiteCipher/Cipher.swift b/SQLiteCipher/Cipher.swift index 3766a66c..3fd2f60c 100644 --- a/SQLiteCipher/Cipher.swift +++ b/SQLiteCipher/Cipher.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import sqlite3 - extension Database { public func key(key: String) { diff --git a/sqlite3/Info.plist b/sqlite3/Info.plist deleted file mode 100644 index d9b780a0..00000000 --- a/sqlite3/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.stephencelis.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/sqlite3/iphoneos.modulemap b/sqlite3/iphoneos.modulemap deleted file mode 100644 index e1d16f92..00000000 --- a/sqlite3/iphoneos.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/iphonesimulator.modulemap b/sqlite3/iphonesimulator.modulemap deleted file mode 100644 index 2c033c16..00000000 --- a/sqlite3/iphonesimulator.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/macosx.modulemap b/sqlite3/macosx.modulemap deleted file mode 100644 index 2442dd69..00000000 --- a/sqlite3/macosx.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module sqlite3 [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" - export * -} diff --git a/sqlite3/sqlite3.xcconfig b/sqlite3/sqlite3.xcconfig deleted file mode 100644 index 4b92f889..00000000 --- a/sqlite3/sqlite3.xcconfig +++ /dev/null @@ -1,5 +0,0 @@ -#include "../SQLite/SQLite.xcconfig" - -MODULEMAP_FILE[sdk=iphoneos*] = $(SRCROOT)/sqlite3/iphoneos.modulemap -MODULEMAP_FILE[sdk=iphonesimulator*] = $(SRCROOT)/sqlite3/iphonesimulator.modulemap -MODULEMAP_FILE[sdk=macosx*] = $(SRCROOT)/sqlite3/macosx.modulemap From 7627523bd56c882add575ac69cd5f8c6a45ef4fa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 17 May 2015 19:27:53 -0400 Subject: [PATCH 0286/1046] CocoaPods support With 0.37, we get custom module map support, and with that we can cobble together a working framework! Signed-off-by: Stephen Celis --- Documentation/Index.md | 24 ++++++++++++++++++++++++ README.md | 26 +++++++++++++++++++++++++- SQLite.swift.podspec | 29 +++++++++++++++++++++++++++++ SQLite/module.modulemap | 3 ++- 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 SQLite.swift.podspec diff --git a/Documentation/Index.md b/Documentation/Index.md index 61ccb1da..a072de8c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1,6 +1,8 @@ # SQLite.swift Documentation - [Installation](#installation) + - [CocoaPods](#cocoapods) + - [Manual](#manual) - [SQLCipher](#sqlcipher) - [Frameworkless Targets](#frameworkless-targets) - [Getting Started](#getting-started) @@ -65,6 +67,28 @@ > _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode 6.3](https://developer.apple.com/xcode/downloads/)) or greater. + +### CocoaPods + +[CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: + + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 0.37 or greater). + + 2. Update your Podfile to include the following: + + ``` ruby + use_frameworks! + pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' + ``` + + 3. Run `pod install`. + +[CocoaPods]: https://cocoapods.org +[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started + + +### Manual + To install SQLite.swift as an Xcode sub-project: 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.) diff --git a/README.md b/README.md index d3f5e948..c0c85261 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,31 @@ interactively, from the Xcode project’s playground. > tool, please read the [Frameworkless Targets][] section of the > documentation. -To install SQLite.swift: + +### CocoaPods + +[CocoaPods][] is a dependency manager for Cocoa projects. To install +SQLite.swift with CocoaPods: + + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift + requires version 0.37 or greater). + + 2. Update your Podfile to include the following: + + ``` ruby + use_frameworks! + pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' + ``` + + 3. Run `pod install`. + +[CocoaPods]: https://cocoapods.org +[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started + + +### Manual + +To install SQLite.swift as an Xcode sub-project: 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule][], clone, or [download][] the project first.) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec new file mode 100644 index 00000000..5ae85476 --- /dev/null +++ b/SQLite.swift.podspec @@ -0,0 +1,29 @@ +Pod::Spec.new do |s| + s.name = 'SQLite.swift' + s.module_name = 'SQLite' + s.version = '0.1.0.pre' + s.summary = 'A type-safe, Swift-language layer over SQLite3.' + + s.description = <<-DESC + SQLite.swift provides compile-time confidence in SQL statement syntax and + intent. + DESC + + s.homepage = 'https://github.com/stephencelis/SQLite.swift' + s.license = { type: 'MIT', file: 'LICENSE.txt' } + + s.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } + s.social_media_url = 'https://twitter.com/stephencelis' + + s.library = 'sqlite3' + + s.source = { + git: 'https://github.com/stephencelis/SQLite.swift.git', + tag: s.version + } + + s.source_files = 'SQLite/**/*.{swift,c,h,m}' + s.private_header_files = 'SQLite/fts3_tokenizer.h' + + s.module_map = 'SQLite/module.modulemap' +end diff --git a/SQLite/module.modulemap b/SQLite/module.modulemap index f0492e3a..122acd2e 100644 --- a/SQLite/module.modulemap +++ b/SQLite/module.modulemap @@ -1,7 +1,8 @@ framework module SQLite { umbrella header "SQLite.h" - // Load the SDK header alongside SQLite.swift. Alternative headers: + // Load the SDK header alongside SQLite.swift. Alternate headers: + // // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" From 9bfae5c0b5070290662a560c553649bd87c581e3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 17 May 2015 21:50:42 -0400 Subject: [PATCH 0287/1046] Playground updates Document the new way of initializing database connections. And stop saying "instantiate" all the time. Signed-off-by: Stephen Celis --- SQLite.playground/Contents.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 87f0315a..bf7f843b 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -11,13 +11,13 @@ import SQLite let db = Database() /*: -This implicitly opens a database in `":memory:"`. To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*, +This implicitly opens a database in memory (using `.InMemory`). To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*, Database("path/to/database.sqlite3") -Pass `nil` or an empty string (`""`) to open a temporary, disk-backed database, instead. +Pass `.Temporary` to open a temporary, disk-backed database, instead. -Once we instantiate a database connection, we can execute SQL statements directly against it. Let’s create a table. +Once we initialize a database connection, we can execute SQL statements directly against it. Let’s create a table. */ db.execute( "CREATE TABLE users (" + @@ -32,7 +32,7 @@ db.execute( /*: The `execute` function can run multiple SQL statements at once as a convenience and will throw an assertion failure if an error occurs during execution. This is useful for seeding and migrating databases with well-tested statements that are guaranteed to succeed (or where failure can be graceful and silent). -It’s generally safer to prepare SQL statements individually. Let’s instantiate a `Statement` object and insert a couple rows. +It’s generally safer to prepare SQL statements individually. Let’s build a `Statement` object and insert a couple rows. */ let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") From e1258056429861066c3fa3f4902f8c1e58dbe0a1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 19 May 2015 22:20:00 -0400 Subject: [PATCH 0288/1046] SQLCipher CocoaPods support Using a sub-spec, it's easy to use SQLCipher, as well: pod 'SQLite.swift/Cipher', git: # ... Signed-off-by: Stephen Celis --- Documentation/Index.md | 5 +++-- README.md | 21 +++++++++++---------- SQLite.swift.podspec | 29 ++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index a072de8c..59028d6b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -79,6 +79,7 @@ ``` ruby use_frameworks! pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' + # pod 'SQLite.swift/Cipher', git: ... # instead, for SQLCipher support ``` 3. Run `pod install`. @@ -104,13 +105,13 @@ To install SQLite.swift as an Xcode sub-project: You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift. -### SQLCipher +#### SQLCipher To install SQLite.swift with [SQLCipher](http://sqlcipher.net) support: 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If **sqlcipher.xcodeproj** is unavailable (_i.e._, it appears red), go to the **Source Control** menu and select **Check Out sqlcipher…** from the **sqlcipher** menu item. - 2. Follow [the instructions above](#installation) with the **SQLiteCipher** target, instead. + 2. Follow [the instructions above](#manual) with the **SQLiteCipher** target, instead. > _Note:_ By default, SQLCipher compiles [without support for full-text search](https://github.com/sqlcipher/sqlcipher/issues/102). If you intend to use [FTS4](#full-text-search), make sure you add the following to **Other C Flags** in the **Build Settings** of the **sqlcipher** target (in the **sqlcipher.xcodeproj** project): > diff --git a/README.md b/README.md index c0c85261..1807bd79 100644 --- a/README.md +++ b/README.md @@ -119,17 +119,18 @@ interactively, from the Xcode project’s playground. [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift - requires version 0.37 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift + requires version 0.37 or greater). - 2. Update your Podfile to include the following: + 2. Update your Podfile to include the following: - ``` ruby - use_frameworks! - pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' - ``` + ``` ruby + use_frameworks! + pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' + # pod 'SQLite.swift/Cipher', git: ... # instead, for SQLCipher support + ``` - 3. Run `pod install`. + 3. Run `pod install`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started @@ -168,8 +169,8 @@ To install SQLite.swift with [SQLCipher][] support: **Source Control** menu and select **Check Out sqlcipher…** from the **sqlcipher** menu item. - 2. Follow [the instructions above](#installation) with the - **SQLiteCipher** target, instead. + 2. Follow [the instructions above](#manual) with the **SQLiteCipher** target, + instead. > _Note:_ By default, SQLCipher compiles [without support for full-text > search][]. If you intend to use [FTS4][], make sure you add the diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 5ae85476..5aad0d24 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -15,15 +15,34 @@ Pod::Spec.new do |s| s.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } s.social_media_url = 'https://twitter.com/stephencelis' - s.library = 'sqlite3' - s.source = { git: 'https://github.com/stephencelis/SQLite.swift.git', tag: s.version } - s.source_files = 'SQLite/**/*.{swift,c,h,m}' - s.private_header_files = 'SQLite/fts3_tokenizer.h' - s.module_map = 'SQLite/module.modulemap' + + s.default_subspec = 'Library' + + s.subspec 'Core' do |ss| + ss.source_files = 'SQLite/**/*.{swift,c,h,m}' + ss.private_header_files = 'SQLite/fts3_tokenizer.h' + end + + s.subspec 'Library' do |ss| + ss.dependency 'SQLite.swift/Core' + + ss.library = 'sqlite3' + end + + s.subspec 'Cipher' do |ss| + ss.dependency 'SQLCipher' + ss.dependency 'SQLite.swift/Core' + + ss.source_files = 'SQLiteCipher/**/*.{swift,c,h,m}' + + ss.xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' + } + end end From 9dd044657d74b3c3fbe4bf8bf9484431e3e476f1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 19 May 2015 22:36:41 -0400 Subject: [PATCH 0289/1046] More direction for CocoaPods users Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 59028d6b..61048201 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -107,6 +107,8 @@ You should now be able to `import SQLite` from any of your target’s source fil #### SQLCipher +> _Note_: To install with CocoaPods, [see above](#cocoapods). + To install SQLite.swift with [SQLCipher](http://sqlcipher.net) support: 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If **sqlcipher.xcodeproj** is unavailable (_i.e._, it appears red), go to the **Source Control** menu and select **Check Out sqlcipher…** from the **sqlcipher** menu item. diff --git a/README.md b/README.md index 1807bd79..90ca417b 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ To install SQLite.swift as an Xcode sub-project: ### SQLCipher +> _Note_: To install with CocoaPods, [see above](#cocoapods). + To install SQLite.swift with [SQLCipher][] support: 1. Make sure the **sqlcipher** working copy is checked out in Xcode. If From 3f08767c857c99bb76c2bf3b203bf6fa019ebfd5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 30 May 2015 21:15:04 -0400 Subject: [PATCH 0290/1046] Restore Travis Finally ;) Xcode 6.3 support: http://blog.travis-ci.com/2015-05-26-xcode-63-beta-general-availability/ Signed-off-by: Stephen Celis --- .travis.yml | 9 +++++++++ Makefile | 6 ++++++ README.md | 4 +++- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d8897e27 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: objective-c +env: + - BUILD_SDK=iphonesimulator ONLY_ACTIVE_ARCH=NO + - BUILD_SDK=macosx +before_install: + - gem install xcpretty --no-document +script: + - make test +osx_image: beta-xcode6.3 diff --git a/Makefile b/Makefile index 70142cb1..64856b8c 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,19 @@ BUILD_TOOL = xcodebuild BUILD_SDK = macosx BUILD_ARGUMENTS = -scheme SQLite -sdk $(BUILD_SDK) +XCPRETTY := $(shell command -v xcpretty) + default: test build: $(BUILD_TOOL) $(BUILD_ARGUMENTS) test: +ifdef XCPRETTY + @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) test | $(XCPRETTY) -c +else $(BUILD_TOOL) $(BUILD_ARGUMENTS) test +endif clean: $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean diff --git a/README.md b/README.md index 90ca417b..d6fc1d44 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# SQLite.swift +# SQLite.swift [![Build Status][Badge]][Travis] A type-safe, [Swift][]-language layer over [SQLite3][]. [SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. +[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat +[Travis]: https://travis-ci.org/stephencelis/SQLite.swift [Swift]: https://developer.apple.com/swift/ [SQLite3]: http://www.sqlite.org [SQLite.swift]: https://github.com/stephencelis/SQLite.swift From 0fcd4af08f9386c07968d2454a4db6cc915ed5b8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 30 May 2015 21:33:01 -0400 Subject: [PATCH 0291/1046] Indent manual SQLCipher instructions under manual Signed-off-by: Stephen Celis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6fc1d44..d80951f3 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ To install SQLite.swift as an Xcode sub-project: [download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip -### SQLCipher +#### SQLCipher > _Note_: To install with CocoaPods, [see above](#cocoapods). From 743530d1e56c86b5cd4df5153839a98ecb05bf5f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 31 May 2015 08:48:46 -0400 Subject: [PATCH 0292/1046] Update Installation image Let's not let things go stale. Signed-off-by: Stephen Celis --- Documentation/Resources/installation@2x.png | Bin 270350 -> 270696 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/Resources/installation@2x.png b/Documentation/Resources/installation@2x.png index 7a730e5319e05fa76c2ae5adfedefe20b9c33783..29cb14a8b590f4817cd3735ec502dc126c45a7c5 100644 GIT binary patch delta 248738 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

    X2tmlH5Ze} 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 delta 248270 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%0D>EM|;NUtiw|u?>G_{)c5& zzshC+%zO;Z($lD&gI`Tz`J2YL>c-HFmwf#x4FF+M72D?Ama0~$C)JJz^B~h#l;TYu zYB?b_lj1p?WGQFES2RZ(j=eOo7F3bLL<9sD^fm1v*~5iexh%SILosir+EqNzz5+bI zBa3U0kln$k#_X5~+ML79)rUgj?&<2e^GB!A}-PblR<1Y-+x z3?SHU%m;m;=O8|?zkkd?erdu$tTWJ`SCgoF!3x{^qor|AHTZ#0D*7n_!DBsl6Npn+ zhKSEOiG%XSk9c!Ks~EtvsA;#kn>W23T~l4{3{#XiTl%U~qJK2)-d9~ygWpeVYFLaG z)6e$mHiDUj9BZnUlZY&lISN6)h+3ca0bQDM@`a};SvT(>RvrU4kr7_8w{cKQN|Fv+ z?%JVbZg;F!R>1AD&{GiMw~X+QGNUL#>0e7#XrAwq0-Ur}$BDBvP4JXANX zC7x0GBMoDMT#=N^_*10Z4a7Uy(~lk;Chi-$j{M&}TvoN2SgIE}m(^fRW5jX*X`48I zA6>>zK3Z~-oe0a%t`rkF;NqBjqTU5=xw4WH$3LHFKn-3|{KbKD(g3QIEzO~LjrR0*%NRim{EuA`oz zo%cb`yDt2y;6tRhbTZeY^6tEqy#{LDHvFbOmxV^)p(oMC-nwQ-`^I%DB>XfkpTj`K%(wZjJrCWf5N?)p)g5WnBO4mXTb;WF*{tgolm5WZv6ly+_8^J5Mv%JRRzu=2Ja5jIi^n_i^Gf<3qJcmaY94C8= zrx%qqo`XvSFe*3w4MKO0sD)=dhU`o0{%jsq|5`AM z5LfbPe8O-G&;J||J3+72qehg#%H=gH_l4xidk>d3CEiB^cY8o6MFBpa&DG0u!o!W< zq1GCGdAR;dkuSN{qA#>}=^-otd~t%+HFOicM;I+Gu8`87KFGEPNm2CCKGr&-%?m#K z-DLzkwUpJ;nv{z>o5k>iROK%_)+zjmoaycQW~8uelvLq*vm91(h2Uk0A`9gF)UVX^ z*Z$%{0#%=}pWn%>Jn+zadt`3PIjPoP=V|^}$iJdbmmlD?LoRk(s;==TiKLpsDPCD$ zm+j@&&3fEbFIv=!x0sDrcqH`Ui28P96tK{3>!Y_Lsx*6r^Tns^Yo*s+pMg8g9dG+N z@}usRA}Q{%^7*@~Cb1QRo|{+skTy}5& z)v@<D zBzMa4&61h%X6npe8AkS(Vz#mmsQbu=FJ!XSSEP%=h$}xT(vzeIXLE z+7!LLU5GF^zl`I*L&HSH`ve%#r>u%xQYcTw*M$8Y?QO>GkGfi4Oy7hh6fI4TKkrEI zt{XH|OAxG&c~Lz&*fCX@P!l+^s@o-^2nc?cuV0|Jc13!sf__ zJsM(bK3EBH3)AIbwYB571cy1SXBh(@4z$lMXQ33`&}A*rb+aNR4ppACut_ayEz_y4 zPx6^1Z%xfbvPR`jitW(#bvi!_Pf%H@a2c)q#CU#l&;n~NynpiMr2ljGnHNxl5QCEQ zzmsaz`1Y>l$VoSOw<|ukSieZh?elo!zn7r|W990WL{DjH`G;qHY-HVvD@Ie`Y85*o zA;(PXKjo@j63Z@d`tc$OZd>F6%=vCRDIh4roaA-3IZ^%0jtd?Ku{sRD9l)=Xv8yyG z(dRH*pg}P-&nLkLAoYRyYEB!<0r~qDDDWal@Ur^#M_rD%;7Y#it{}eQF3W2vquCWC zth&CL#G#Ttt8v;PxfQ9mN0y+8T9lR?t9)v#kZygI9D0KHzKP-qKg0?HT08csA0>+j zz%K$XFC<@ZY>Llb=GNED@CxBmC%VS-gU@V3#_Z$7vmP6ON+KZsFqIFgTFhBz#c{V$ zTY8LiYf8w+InHwNhO!>Qqie@D@YR;>LbH8Aw1I`zU(oH#9=fnUh1=Fvp`P(|N#x;a zV$z2pgz0C5^@)*k|Ao5p7;2{-t_0pk0xa7s8Y{Z;NJFV|XRR)^Y89W3PjpWEwGA+h9)Aj%C>N$TBOho*BlaVu9E7@~%ky6RGRfg)63Pytfs)YM?`nUL~ ze*yyeTOV`;R|eY2hXZ|)v(F72CPXn!{ItBl{0ynYTp-qQ>?2Y7n<-S_C3)FRV?Qm2 zaWd~S)=IlfL*9~cWneyX9bJgk)?A;YfaQE<2yvDC90OQFOjzyC>2St~K)i zi2CceD4*|r9LGRZ5RjA*>DZ;aQA)aN36WTE36XAYMFDAvT^gjjyH$FZ?nWAQGtp&XNvlpHy6O>Qp+_tD8XHJJ=7eMe_@lQ?Z@4yua zMke07M`TmaLkpYq!sTJB_FRa4HS0D#RHr-0_o2Z>l!5}2-(+Mym#U|AXuEyzTZ~gl z%^=lQpBClx*88}I2D51FX%$pQOHz?e4#k-h~hV_}VT#O%e6 zP11*KCoX<%lz3E$HtvyDv{2B@BtgW)y&wl>`Zn)zVHj}r_9yo^!cDUPplw$GN zt8MWEE6HP7-)7*Dvv0v~rs;k0J>1!$(pJ4D$?)*t_XdWGo1g#pk?u8bb44-vIcT~L zwy2=UNr-g0Yb-viB2$eSkB-QbcWOMAa+EMfU9fRWnvd=E#+<$yKLY~;J3BiAC4I@Z z%dLN`I~#tgrbl3pgiJhpQV!m+9ZMRl{10;uxK;!i6vPMpJT%RXAY;{5+Q_miu3I9n zrb8FT@#iv*R|QgZXuwO_zL>`+Sx~erBKd(JF!^~*O}(rtEtNHGU}AD|a&&aX-ae&v zeg0MHgoxbO<4jUTX#6dd2b7qi&ZpvAw-0NKiOd`st4u1ns2Z$7?cUF7yAsj_RFz&} zOETD6w-p>rn5pV*gi(j$zQn>BT|mh2-&dL?Q&tZi{rdD~&VnoGsB1Wc$Kh0>ntqeQ zXerRZ%fEx{KR=*AoT}QYoFcA1CI0OgAz~f{0VLO}f?a-yS|%8EwaW`mKT=oBtpfQ% zKA=D8`pCYjok{5~Pt5$q@EYi!^Ij+k3`C`P75j4|xj8ne2$ZHO?P@ugcl41=+xrPVKTrxVrDr z^rqC$x@c`}4MfIwhYKb)?u{P%Ug{RSoi3=p`>tc~Rz$5-C_O*{ytZ}$^JQ<{nN=PK zO+91#+B!P{c@!{Y>cFb|zO4rm{9?E;Nt#sustLm@9%@nyt7BDqoNr_24O(s92%0P% zrA?DW@Pi>rO*3?>ESE31mp&&jw4BYZ`TJJxZ!l@Q{jIIq@GS!;zF9LE|Nngs0vB&b z1LFJIo(p}a6)Cwhu5PYBWBKV&AD&I-706TeRTVr7tx)MHPexMTa=>WdyiOcq?Ot^X zXfrFFNcz_xIsAJ##qG_+wCMZ(?Z6UFD)h+M;NNq>8MiR1(uMEPAso#5A=CSSj+bDh z4&19_VP6a?x+lV095&h9o|~F*)2V&~*zEK0w(sFb8sP2mp2)(6MXt(w!SXY=g-G&3 z8TvdTvad#+*(IRE^>e%Zs)8v3E}2ozQZ(o1)><%EyYE^5yteC@jO=$D5St^DlO$u4 zlX(j`%9(LN|H}z0F!@P{=~2{mCCTHp|8Dnw*bae;1y}U(sEcPNyEce_b$;-T(G!$^ z2eky_s)1yhdgmq`s&jdtJ*lEi$f7GeS;!$X+qCDyWP3Gu*`2h=5T_PTNXefk;$_Nd zA|oS1|1;w@NeNuCVd8YCPuh}1?CWn#SfNQ8*+SX92P&&RZ@g9GGGE2&22=`nIk{OE z*-WN@gX>}F8+F76Vr1AOyE2=X4$lQWnoaSa{atNvZIikwIxb{dCE%VM8!R(l$;)2u z3Oi{C<&Lq6OwK+dt8-GiQ?}_+ zdK{#nYf^B9Bb;i%mVLCL5JPQ_XlQI~{C5q%y5ySj0+J*#DE#w_txr&Wk|Zlv0sbP2 z;RdJnug6qR4$pl2($v+9kJ7w+vP*T0yy;v1OGv@`IQ{KmIbPb@Q&ZiMG3>4t0|b?l zNDF~#)n@E@qrOc?AzfENpqRxF6E4VZLd_}nIYZVs_L-VSCc##2kcx!nr_N5U*AW-u zBnMSMb#>93vE1Tf#MMRM6RIYOxp|H5=Xj7f=!P4P`24r75pYJQatAJ2d}nZ;;GgUi zaty8?R<~F3NYoAE5R4KR$>Om4f-vR(86=8*!a_s!BjHy9gVOyy<-@f}?A3PYb5(1X zq3m^oUtxYGdOCGlQA8$So?Y3MDOi6EM6cek_`ZA9EEX$SVM^cr_`&BWYA6I$X(E;i z-mF=DrM`MM`HHgU7sE#iz3Xm5a^5he1BgV|Fw0RDI}c!(_g@B4-kns4gsByVbi#0T z)qt$VBiJ*jS5xIMG&fexRlToVRzy>;2|HP!{=5?hR0U#N#K|6;kRd0 z@0(-Q200Xs(k4KM>RKN1yYOiNlWy>XVu;zBsZh*LMTA!h=5$`M$Ycvd=|;Y;|5uhb zvv>Y;Kx_=E!EKzQY*2QoFlF|ULjM?}4PuKa7!`fRRcE%qL}5#=!)=-;>N6k)Cm<=yvcL~uUBOosF$hm__6cJ_epF?Ki=XBYKE<0NvFB@r@7@D_LTlGHM zlk}yyrvi70fqI~GnJy+(ZZirg`Bbb<30*!ci4>Q9YuE~Xb;~$|g>~FsPKPQAq<;p@ zsz{G5_&TKuiLCJaoInjnHq^Zsj|1zQgq%*YS2`PqZhnO2B0U2 z%@w;$Uq!4-3@;&ti-&t$g^(32=xEZ&iY!TlYAloLjJ*n`JS#-6hIawye}3TYxLEXp z4ElphE$3hRkWncuvJOxGViT*i4{+yzX0V%C)z}!4w{<(+s`dXpdQho$UP64`RDL+w z3|gzcraVg-Wa(n1T!{C?7mhAsPDuvw?YSzdJzb<`anh z(OS%@Vn#bzM!@A`;lTe_#xd`1>X~e6;jf;!rNYIf@?H}|B;ZG-oL7)>tWzT&^I)3) z1zhG3GaO=d@+VUS#b5qR%P7OCT{`+ij*tmcYO!w`T+t${cKzTk*2l*N)i$Kzw>|le z|Aa<-a*^riS$uARmqd3H+-(#qs6ffE@}(xGFErLQiAjAZOCsf$d7QV*7qCLc#u_Dp-rKWa`)#?cYmD}h>)@LVB z+7Y_t;b0c)|GiqB=Ot5=1)Ur7BTb|(oOM-jHaB5K=xgYMV~@^x4I4&DXcK{y7ymsU z@9A26MS6AA3vx<2Ka#*OqaIi(Bo%vQ6ixS*e~FG_g|b7UtP~sd{>z31e^M5ZTc89b zma_Rq!rVbwt)m9(jQcKpmlRk$lc+Gxsm}bQYTmEMSN#9#qGGLn&{AhlIHrrCsh(Gr z)>0ce%{5=Bs7wsFw8*EgQJ69%fT=8#7#sLdSe>FP>D zuf1e)?^G&dLs!^%qiq(3{%>l&Ll~vmp`XEA^cdXl1(_*2x(?>Z>xw61QM`yg*my49 zZa|K#2CRTx6`ZLn;1q5C8ax!-J|7E~Dtw+W8myISP@ZKK`HYZP`mMJ2rA&tnuert4 z*JMJ<_d^$$RA67Pc_@@Ocmw+EUBXS)AEn6wYMr7OB^s}4f>kQc=eb?EX;<({-!9e7uL|;WX=LRo$se_yhY)sTW3H{Gj zg3VQT>z;DZR!R)d7rWfSZH;T)Lb@nXk!LIsyx!wD*(Ry@;jBB}&(Jq{uWuVkN;cfY z3FGySV^4CsrL(zxK8lFD8oR@WjYuK+kjgwyuMkGAS5p%!p8DD7Ne@AL0xIBQ-eB^D zGRcHz-d`F6rP87zlZr1r?mbG=>D%??6wUuH)BXqJF@GxW4h?86U+fg;0*3ig3=Jcsw%WYKoP3%UL6w=i{G5kO{sYf>;8409o0L9ak8TJhz6AGLY`&E=dqfBXX%WoZd7s^a z`)8Q&OHmuCSTsmZq~td-sHVkg%hN!}=gjW=tBN1W)J62)0!L~Y`p9iy>uhJ$c55gB zH#N6Pu7TG zA&5<4QD@;oHksiG{_pkQHVFWwvL&J*mR~+$cAnQXf(Z|wgB7plqo2IR3S|HSq36H> zLDKXr0k?G^b%Yqwbu@pHvXQ@Cq)<*@={fuT>2^rp@2}#qylXSVc`cYNXr#MlknzO{ zV^dJ1ST1`4S+{)%8&}iHQ%146Y0?8?&mt>i#rt@<&_n@FM~`;_LH}D}>NNTzw67^1 z7GkaY*jHL<1}7Xc>i*X@D^22e!5&EStch$dLt#cm2aeh*haO4!3jOO#OzmGShHjsq zMp2rmse5>2%`(%M*p52+;GE<25WiaWD(oG^{fNk=r%2GAuH~{1^hcls@7>bRa{Gfc z6OlZH7XVQ$|KHSt2y`NmFlo7OD9mYSNAEhkA_Q6-MFrpN-Hb+7%Jx&wM$+Mc_ZzOP zf-St)<~@~ny4`a$Su4=V9QroM+y&BGPx<51F9y}VzhR%rYvC7JN`Q>+P#~*AR^2a# zhY-yV8fpj|f`aLdXk;lp*dEuG{Mx}2FtYjK{&I6sSQ9<^Ykqg_$5Cw#*mNAz{?8)! zHguxZYGlQ&FYuBEq?Z8<47KAb;QbRiEeXla77V)M)Ey2BcQHB_)WaZg^gnE}DJBej zoSAD>z1K_(=1L0Y5RGDgex#?Vj_pYDc6Fli2O=;QhC|fbTf+Of(AbmoRuDk9*b`?5X)! zPZT2DxKR%qrwgMCAUs|+0B8qXWv3#K%Y>E~1D>kytdmfvU#i8s;GP7%%}=PIXL;L= zDX^9sM; zlFyp2qI90T=a5P3jxJRob|V^Tw0!hAO0yh+`if%+Kknnz<5%w5LnKp!P2-ZPlvSv$ zClCgRT0QcI{@CVnK!|A5|5OSs#0RB zIoE~)73E_}U}eGlB;nUkCAd~NAKF1p8YRX6Ve3-jZuQn{NhzQNUVsu`k(3<}#>c3X zJqGm}Cv_azyGIumcThcvEKIHO6s8Dc$BU76yq+oDrQGg^&AiV4ksN;eW+D6v!&Ra; z^xJUQOqo;U&bNF_UOL_YW8bgSU*I7$KqM|T)84iYRW^0f&>rZmR?ijqGvNF-nvd`vq4N%mrZc*0zw~|{# z6cpf+pasZi>Rs2MEJ3>1?=}&`hs*{EQ%K45E`2INyN!*F!8e;Dx#~C9SBH_B^4j;@ zj03x`c1D;*#l&W2W~w&$_dz-hsT%=r3TM)$|;QgkucP<_9| zY|znULPO4yzP)+~t`r7>Zs!*W_Br~Z$>qyzgPxmA8! zV7ztGjTK1sru`|tjFa;ys14)+v7IP6_7Il|!e2!TX!yplWR12oP3s$6(%YJ@w?Em5 z4h~~9TB(~eDFMlBUp70EO49M15?t5s{;6y0O;)#A&zer|WV^7h)Yl)5Q%XD^+X)9f zRf6J&+Lf6Q2_@d{-}l3<{$)U?&FicX`s zs-Loxk_=dSqKT)=%($@I~p6@@-8q4(+;|=6QGduYyp{_o%Wu?be*f$l3 z1_)OA9&C3dlqNMcMAw93TVNSZGAT2=_f~v(E#&?+apfhZ$^etGPX+qeD&oEUj=9Mz z6NiyZU@YX`r@x5CjkM7O?6<%Q zb#r0k5NA-rlp8bYzsk90u3p0hEzjGYrye!l^Ont2dPkFCnIzx!N_YU-*8XYznXO|h zzv^TCs#0JYl<`YueZ@5NLS&^pU zM1htfrs1sJ?PVc~E7NW=Ty-N$Za@dT%B{-Wq6P?J3*F`PK(GlI9L&xRIJs{Ve3Cuh zGK~NON6$^l^1x6TxNGMO0wI|uo(&Op;s}@L{cgB zD6Fv+i8X$^=&NHtFThM<1q!A-2MhObRu;6Cz5;1p1f17YDAU^+el%tJWj~>b_ux^b zB{XPs+e!7_RRo3IzIEJkbut4|Tug{rWCy2xJ(g763ImWgY@ngUtn|cy7xZz8nDQpB zkGyXl8@xh^1XT)#Ik{;k&N=0q;vpQ{45tef4X<@4)W2_wOYt7k82+OKg@qqOVVgZi z{<_svjg{o!+=riBL%E8pdj5&>O``SngYIL{5Mj|KujbiUOFyp%da4y{HrrrnQ7A0hUuKm({=(f1^54cj=vtYmI8XHW4Jw1XK*h+X1|L$|n)t@C!$^xi?Qa30o{%Ot6FE3wW_P zz#0-#htWfD!@3u*gUCi--)N85rpY1nmfCxE`IWOC82&m^x!uI63_GJnbC&oJ*63)C zMBF@y;6c%S+*bq{M6HjB>SIqHQ-GMuZnVXK&4Jqt@8Pd9)j3;65k17rZ2c2!;7A^3 zHU7OqM9*bbim7OZe!oSlnc^E$t6pq%t5&r8TGj}3X5gx*E(qKfA4zdODe4!>MNmVb z`OgJ;vz}=vlM`f^{^`XM^9*bc4gR2fL-GlHQGpfOj-C!+awMJl`!opA%&Jp^!0aZ= zL?8j%aqDAc1Jt5~sbNe%E5@Kvt{{v?m06$8H(zfe@6@M&ega1HiR@Nie25V5k~_bA zyV~#?|0CsD^|upjb<45(3kx{sCw_3`@!(nETt~)Ag+yk}z-FkU+h;C1lg5c%^Vjq4 zf;e?W(7RCtL|E+%OcKk=pQDPvpXjV@h%&^YiA_Pakm?I>+MP}hdIq#oYC<=7&1%W( zDf|j<2$ua-eXk86^-&y)%No=aA_qeI!&y4gF*(RHpisKec(~k2dXN28C?U}=#S&lc5 z^`%OBYtC%+c&kv3rE!@cuQL>=c=|C9)R56QHhlM|OD6Nw>hfpI{#R^VDg#X9UqMkx_P80p zG^nYr9ZhN)=PXl-4ZX~-PECR4!=JDrX#%SK?j2%7pM26s&6mH^z*G)O=f8wvhgotC z5ma2Hdx2IM-juQHBKv0oXepY}5fk_^ zfrcQ4prA6GDPANh86lGU_vh~rK!S(GkGgg{z?}@_*nRzNbH})Ovju z(DefNH-uy57SxtwEVsczk8u=wk{EAkpuL^v)R%m7)gdr|6FQp%!_BWfeCa(yv33pV z{+GBO+)M!jnI9~9C6j@-L(Px3MUOUi#52N>^T{$i7ZwR+i%K)*D4|*>rDxr~F-vg*Jd6OS-buPq7RPyA7~nb zJ>3Aen#Y2gPaP6#u47TCwcRdAJ)naMfK{f$t-`m@1al(zyH8($zCq54FoYuoJx#~3 z(@=8{{Q~gWOUSK$w2)`PDW>?wY3JsFzQo~mM&{{rDO${(R3RtntDOq2R@fX3(XWMB zpnYRewd81DO`2*NReIqz*KqP}W^Ool;aj+%&4CpKF%$KM@x|DLtyn`f0>JzZ7osuz zu+jR`di_CmVq#+S=NH9~L9asVA~BJ^Tti=f{Q^kkw5PAL=}U}w@@r?@RQ;W4^>(3y z9*)028gLee<0%)QvFu*{I>Wm6LX!Fl!p(|oh67#W%N(Jzv3ff{thNhR-GC)-i+O)cJz7_q3$^$J2#QC~UIPTj#u$Bg7H&|f@FS?c|Fm*etyzItmp*WseC*#Q&y zna1bnHO930^54vLek;v9_i`>v?;x4C#XnBdxh=APV40pb3T&-6UvNt#+y?rTfcHP% zuCF6XT}okp*SChpi4b{}@9Xw|1Q$<_`p50|8g}0*AFIWq^fOP zI|;3|vtRLE|GV%gq4uZpt_xDCi2gRFB^u`$>eQ}kYPqe(ueHXm$QIChct_W-ats1H z-qOIgAI}P^^6eXUz-6)^km1k(`n|HFR*j}I9P1}m+mjHW8Xyr)#yF-W8873A+6ey~ zSnzR#t=!xrnDw+s1GZm@5sas@d+p}SJ^$33(3*Nxk@~i}DuQx0E6upHe(%C`_Og@w z0uR^p+L`{R4Z_~uX1I%S+i$5R^oGYBZZ6_5=f23ae;*@ZsbHt+G?fOFo_CmVadzvD zmblsvtsUk*ae1~eOS`muQ2d6MYZE&8ZOZ) z*D7HxTWUi%s9?;>?X)#+T7=RM`s%LFMU3~xN% zwQXZceX>SkjeLGCEj&m7Oi!vzA7TTaK-(=!z84I)!ueS6j15!tUW2!s1a@#2u6o)< zge*?sw{H^b5_BwzLf)&%@0k&cvQUIi`RScyy15K1Q6kEsJ8sXO!q;cZ)oGF92M<-B zO8wHTf1|z?sq2_Nn5dr?U!uxy=k>e&l^@$Geh{_Kgc!jiksAOBGfkrT^r-q1Bq zT48S=M8qM2%cisUA>dx^)vhZ!jZjfG9f{55M2fzuH?TO8+HnH|^Om+b?y>k@@im&k z`(+}bASdJN-Bbhk)p2X@-{jLkx$R6lzpAVdne=cx|( zQ6LmBsR>Rc6u;RBv%B*9*V=Tb4`O#HYOlUfpDg)Fg)kW;-mw7O>kdjxFEdwx`Sk)zsmNkbbBlYs z`#}A6ghQr7^|s%3R(cdu@fq(l{^Ii%_rHR-oY`3Xjwb+CE5oBlP(sv4)`wqNA3xqL zE_N*yH9UP+I5(Z`Hf?CMQ*&}tqiZzNP+i<;xK-diip}}KKZMqZP(k6>)t-~0f;2!> zq4@jK({pRp>9ok3(|EV*ka{MIvB%-Eoj8OgjMPe;RZvSId_LyyVk5D%Z+xz$qwud( znFkQV9PUyBf9F13Ny4MlvuA;zp~PY~85uulF?sL^c1`$P3@?;!M_UZt)bQ<@u^Bu` zTRsxe_RdsG!Y#l5HGFLTHyKG3{SF|)h}l+W7Q+wcHG7MCBB=%{Agpb4HzBlnpvx(c z*L1d8W}i!nSw}-pi$1F7jAp*r{5*dG0^Qpc_695?KfLJMnM=Y`#XlT2CZ693J~4HW zkol(XK>DrOLiSFG!>r{Wsu=m&P0>2_)2zD5wVkf!A-`lN@A@XjWofeJ-#`?1wo$C! zQn(o2E>+d}%haYN%>Y%Ku82N!39oNG@<|W~??4JC5oVG{o!Y(DT;2a0VS55}idamj zSfCF?@()u&ExgsdPperbS#@TKxg2POLos+iq9xe7p{zb1PW7fyrsE_D8ervTb^K5g z9Rgfuf_IdPI&i}1$5R(Ts@Nl!O4A!^0!0wr<4uEVLQ< zmkDB8Zljt9ll7mu1}?TJ`QC0U?YC~c=+L`v28%pOrbm8lqf_KGU6EhuY5q*LZ#&yU zzTUHeX-cx@M5!Z6W1^~s(eZ=(y2_D8iGjecLGm<}*kI%x?hfs;GgS+VwtI#5lo{iU zTn>*Kbhjc?TM%Ki{!*jqEPPATJ$y!f&b@Lt`wN zmZ)j)iqOCAy|2Z+x(#mub%Ur+e;eo*iyzJ(>iQ}$6L1dMC)*uVnUG`^Uvsj zSr=U;tT!iRSWrSa1XmjTv21W5mcMgZeBi^A@1zGR|zfM0g%!pdsj& zg0#2%xlc!)2kTA%B_221*Lc4SzHj_l+i~xzw$0#SR4`@}NM#;?+O5U4MmG6wp5@=D zy%2L`<9l7+@OSWqvU+kutJCIvhv@I^+cg6B_>5avf>=wPh~rNuS)#4w#pFAfE?;J;T!{!OFYwv6enb-s^(RXN#0@U;kfXwGj^E>^1p74~yw_;7IzN)TBjY}zdyo^=3_t6PXkD~+7(-ed`d@l1_IAt&#jZF<=e}t zJ)&1F(aq@Z#&YsW*oI-K>@w{7=~9k?4x{RmfV1iDXlvHV+NXcYl(iVLrGWLoV0UMtZBl73(ShEdPDADt;|4QLJR>w{-wW0jE2w;;_}Zdz+pXG5(fF(%#IP5Kd#Z zDEA4Kkc@V_uSKveGWj60{puN*+xn7y%+`*SV}Ik$J?z9nh*Ai|k~%%_1aZNdUo@^a zch!0vqRO!F=HgYH6`I9Qk>(9P(^tD>fcwcJrnV*WE{I$%IMBQ}-a<06h1!)8w>w=8 z=^NI$thV2e#FU$o5%85%K4sUm4AoE{urf>C_~_**OmkloF>xzcLF@6b`&p^Bgu`p5 zg6xVByo+6ds|*|qk^EYIs}HiC^Wwx@#FyW*jOkYvVb^#$zL|pc`H3Q>)KhBU0AZFJ zGnslmp2(ShDPy23;d5K{4gYx?efD#zk38fUI6wWc zo>xK=@U)oWk?8&}T_AaQR$eRd3+%Ao&ApZi|Lbr%Cs48i(j8(_e%Y%OIJVoGv6#@aVHTBOZ_3h%^3rjk4p>BEkqh; z<*IjT>4!TTd3AGCROgVTn~E&#-_+um>;aFRDN$155P4xwD``j9$qy zG#(6sV0|oZjY1G~I;G~C4V6&O=AXq$)wv%g?j3xd!3&262J2+)D)@dsewVxI&|1BG z0JOys^cC8bU@}#A(Z1u;a6t-}%dIVqzg)iKc27|*AWC{EHXjE9*L~3MRc~O- z##w54uWS{bt(9-!?xt2>M|(NX5#U|lp*7+Z8`1%Xo9CN*sUcvm){eJooiNoSy*i8u z2}bOKv0v-5fZwT3(2LoEs7hn>N;-RKtlHm;<6feAD`qA;=7iG<<#-igd0unDCN+ly zC@Lu~biBsNG2l-zZv$Hgy4}VheAXN7Wo1nsw=o_&Zv?|~l@r0N{Amu|V=GKB zq8DiO|6+w*NRZ~Qcjo|I%?dt&cghKcbF zSWvk>#AWHM+;#3em^!2O3QPiPWP*zWE7P>d##mRQ#3zN0)Ovf;2ifw@$$^(vq_=SG z!Ta|>szl(?$yqeo6^yf0*zi{Del|9%VlCLLTiF>t7y zgMrgls{HRiFzSw)@hxY?up_@vz0&Yg*F6be;5xmzG>d2@U#c||;IECO5~-B=y68x) z`=z}~Droh?mp-vTVgNo2<4P1TSvOHv@6S;;C1bzvbn*E%FI{u=Gk@&>-?%%CvV@Bk zQeO#gTT~Jfa_j$b$x>4)`pTo0n!m)`;^eC6@Mc@W`*o*JoF=eIRI>?^(ym?0=TeXW zY4b-jaGoir!HvPBOZ+UpOd=7@$dQ{&awo0R9Z^qZXCjpn*7t*7Q|S_|4Pc#8jVIP5 zhybxOx?q_HZdwEGto80LW2sxZ^I#Qc@|~+gQ@L6FzXmZq(#&8{PN}Lb4X>89-Nj;} zyhI+F{W)K;>-ucuQ=yuf!TFGp^crKJs=Fexbk&j$W`iv5ZhLXP=4VEW+uCt3A+6k+H~Qqu<#^R~R|KCGMdC;4 zSc%m>gy>ww&yg$QoLCPhzo2>!;NbOwr*^EZR&|PS{jrOZ{%7pg@oHs^Pzhbfjxh%v zQyKNN$F>tjJU29sn+v0lL#U=u^<(qroB9hLCy)C2z-mhlekUXGSszZ|9>bG+!qfG_3l(|0$nj}EnHQ19u-545PxFWO@2)0jI_>{{0c4X=-qihuG<1xa0A)N>)kQJD zdwsdG`;urzxEx|e|9Reg$@bbs^-UgwflL3kKO@WPfUft8yf&T?>xp^^rKzm2h`gTC zZNtonw~o3~vyIUO#Q1dW6iNC!dJjD6{!pSzp3hXU?3mftZ$#?@O1<{sK?vX`JLK)q zs!lZyo#)>LQx>P#9yDo>((p;y)1p83=)@W=GI{;8-z!-q4yNcU7pRobaxv2DIC?E$ zAesQ#g1#ERl9e@CVRZD`EF0XAtIE~+Q-ycYmGbkS1)O6HHQwF2i-xkO#q3>CrOtM4!X&d>oIB*8&Zt;(AvmX&Nk5al& zNclB^-cqrV^`X|zZ2btDBoL3n{IXO~PqvAgnYQ8P1lYL>cW65eH)?2Z2)JxM0IuGc zk-yjz*{IJTdXxq&Wt^3hp8I6HXnzSDI^Tp;M894oqL6!Z|Ba+%>MSL>rRubJ&Bl5c z=x!+`c>WN6^Ttn_%RR+Dx|3_NWUkmlGKvc*zetGAcuWzb-2ITiG#w|1b9h-a>Cvwo zpQFB5N&va!84&Jw6~ki6#sw)M^f*9c*fhU?Gn(n3k0ckgZW|ZLspbe;mim{i9w@1O zou}1Fx4}&G^$X3>1t+fFDTXbpF4ZL>5O-5@_2#+=X4euqUXB&_-MhlspC*kzzvACT zn$bmW(6oaBS;S6R0*`?ZKX?CGgg^<9GM38J5}|3u!hO%T3SM=M%kc zSt_56To4PqDvU=AAzW8Tv^Z}FQAarSG>5@UcX`K)V3VMd72*S`n|~8Fc0q*x-%S6l zr)hPJ0)9htIH&Jd<+zj%2e#+kIA5!EJM2k0TKk;lG`&m{s}yv;nhB%Qxi+;@#$I#lssAa@lGlyd3ecI zOz=vX{CmcVW;peI`%8koXN$@XuJDP5%u`@8K7rPAuzTf$Rg>j-j}&Pi)bAVDd$uF~ za{pI6nzMZMywW~fyKSg_74DX!pAx!X72LQAg@+<3^qA^_r01{4c&AN=B>J~ixwP$; z7hE^KkEgr1$^I-RF4*07m)|~9_>^C)ALZ+I6;Ti-D@Ojp@P%m zy32d%cknlAKZFpQgSG$@FWE;D%9J|*Pm1=@Nz0W#vLU9S{>}7zR@gfb)?p9unyaWY z`*&Efy&Jmf*$w$Fw~w^#HTWKFyM7p7oA(1hlSW&%>|N83lQ#LThw;*q4Nkiy{Y4-D z)s@EtG4m4;5Dq6{N>f@h44@l4UbIv{Gq45scngx;Hoz2U3b~>}&4YJ>Kiam5XAgSH zO-L_S-t@2vB!|wUR`!MOI;gJ!D+L*G9tAo4gOp#Ixb=Bz@*Q(i?Lc2X&(uzu@vxks zr`|ND*&(xEhU6)`Rbk!9{x-TZC8!MiLqr5t-_Dck|La|i+1^}T8@`kG2QlRoM1MH7 z+v-)=rAl*?5x=fa%e;7DvM`Tldra}MOXTa^f%W)dFXI+(xs#qT^g{sUKA{;i^^k1j z&TO-f&J644U~$b{7O$YQL2(*n_lMoPSi&?DWWn(wyFVVZk=XGK{}kx7h+>vh?;dO{ z`Rh7_9g=S(iz^zbMr3I+_LnI- z4;2tXB5=Sd_5ed;Q4EAtb_qj3NU4VUm_F*}=w!rFpjn1=km?@^7W^taMTxhePJJoyh@?v*aX6ltk z!Q0G78BJOx0-eeJaX`Ee;ym z#3)(otIz?OiC!&RAuLi{1G*#+Y$m5VGB*_}-e5>=h(t{CRUGWIF z<7Q$bJ*iE!>ZpP&p^M3vYeOFIKJQRw{XwXpAU8+#$KL+#xLQQJrw2=rqzVnkw47zC zI{CUK+0){?!9Y;0gJg!0fH1St54T8axzmT+-`?hVD^xWErH?~f>U@voR-G~>GYxTc zCO$HJum=5*f2Nqykc`ZXB>BJ~fe;!&PD&OC)=QJcX->8{Jb1kdb!gdIzu2zrOhM2L zZNw_OE?qKntegSa8lAw&ck0U?v(4*>_%&kfV9@XR2oU>(YKAvD1=rud^W$e4Uf{g0 z3SIZ&Lsn@Or)F7!G*4!I@tlpJt44GBUuR+Of90t13;yPg3^ZSY)OX>pQBH$dc{odF zIA3k%SuNVQBX^~~$-e%p3vFhg+7}QqcPBo>*+3h*73=e;zL8C;lra`$$!=!ThPAPH3iy$Hmf|#!q zJz)JxGkm!FBB+!Hvy_pf-#;kTYm>&@Bhk`6+Mo(g#&F*`yyV-LKXEDl;FttrI#iQ^-?y(mcn~5}SQ_Jw%j&(EwcY$w_@so08*zFBjwM-0Nr(pq>YetU z(kWhES{_aIhmSd&cO9obO>x;WoZjgzDoPdy1czBTo4xHOE`D7Td&_z(-HL;|j(!{w6h|KUt(z%CAYh7sF>2AU%^gIg_s^$)>I^(CV~QgaCnV-EWQqly zyyEj_l|i?`Zq&3uEDEX9E%GKXt;Cu*+MCVpK8CB|^7nl4XkCf=D<3ngq{ zqxOrrx`Q&-EkiFknk&&f80*!-U*N0!&Fbo!^}N!$|QtDyt?sr#DA4J_H`qwQEiHQ$()AfEZ z3f}x+{rEY%jp9Z0&}1P+46CJdgFKVo2oBf0{xZrc2J0G`tM9$%tH^hOw+Ym#DF`oZ zJHBP1E8gD8*>OmE95v1?JAQutT2zJ(6ybNX43a^7tmEUO5?3hxYl)L?Xi>hUUg?MQNjw8`(blOgInOieo!{+%sg2_$|8nDL&pxS6c`j+; zn-bc-X9Pa{n|yASj_&$w%?`vdKUL|OqcM>L)C*Tc$oo+?O)Ek+%;wjmX_AgI&^w4u zFF8lIH+~!6ES4AK82sh*`Iu4=DUj6a%|zXwg9dA_v@PdXV>XXo(f(q%yLt2AOAxV2 z*-q;X?*?i`UWlN0mnKA>nGvyGv_&JCz8G?Tk4l1vRjpjw&1QXwr~}a2J%0{d*9Y&C zp8t`7;;C&2hz|vA_6+;q<>|Kt?SgZq^-@-VSQP$%j<4^kOJ91a-T3?zOY-UaYRs$x zi)x{KxKfns>?3=Z6A{5!coHYP%3d*d^QYR-&C^@3CYEgiO|10UWhA_rK%e3L5Loy z{F&|EaPe22sWwygbDZcLsXd6NlX2oPIoMgpt*TiTP}Hzw1nOPi9;QP(%-JeUdgf%r zPN5kL{*%ltClWO1M+CU))T&%DN}DGfJ@A5rVuLMaqybeHX!GCy9O|q4v-^~q<_CYe z(Mw|2$&lZ&f&_Uju2V%O(G}j?Fz@!n-Soucs2B5<`dRdWXcb6&2lYlbb4Y22`^~f; zq_Iw=-;%B$P!sFNH$8mF=S@qCg=G+FyMDxxVjEgiIfcz;-TYZul$9wrVPas4i>N`}550@XfH&kD_;J@lFp_z(u)7gT$VXgu7FBIX7o4yw$baoe(3oI9r1)MaiNd3wf*|JZD_po*u&7yNbkcc+|;PcVdRpNAv5)I!@-VOGh%cBLtg%*vQ$* zBR&8);G0tzsmw1QPJSF>DC@nThj%ygE;t9DF^|IV7A@Dg9hvtwo~56pQ%|3-p<+%G zb{#z~$J)NY=45UbJ+^}}QhUAnDi9Ar8z(0Z^W@()iSe;3S=*Db8O#G9d zT9$T+5+FeE;O-jS3GVLh?(VFF;2PZB-Q5%1-6gmWZiC$+`<#9LhuaVHFl)M}R!env z)mL9p>=e{TaxLM*Q1j}28GxWxAf+*1qH1`4DRo?jN3(EK>-BfWr%%%+r+Yr){IAPw zm$Wj-ctoW3k=D(b1tZ8)w8H1rA*n&h?`zS_D|Q3B;XT0SS8ozN=*)Lg8ZL@LTbvBS zTh3Sx&dUG?W$ZZD18N|angU16m74HK6}shKm}AY#E=(I|5y1kUBveqrqTgd5SN+AG z0uQzl7L2x5;o(!V;{ho6+JMUKm><#3$wZaHH(8HMg@eR%ZlWzv#Rlg7A#Yc|hfFBV zP)iqPYN(Z-BPkL_tru@NVu?~23V$j4j_=LVSeYj-LjHcv=V*g&@dYd{A6{sLz0w?9XGe1 zj_*O?o5%PbVdR83C1emzlNty_jQaU=Ju#3F9~&>e?SLTrhxC&1nNt3|I$EJhX>|Rx z)yt2Y*zm{JM)ahF>nl+jGOveL_6Q!%jcUf`pz}cqS9)W~ad;h~a%8tVHyA4=bWfF^ zbhJ#kL|sT9tW@=mZRAw)`@i?0xVgc&;iOJLK}9rp-8p2}@4vO?ok_a8h*amQ$lM0F z6h#j8RC8CTq3fT>Dm?!jOjK2Mm42^JcN<4nJeCKKvAhvqa&_QyB*W`w{_%G%{vq=H zB{X|9dVvh4+)$nsDs%eK>K9^)h3Q?16i9w~QA31+plwGV!;1RhF){kX!3KxqYkFZ! zmh*gU?#`in_xT!Mshdj6H}*&AZy@O2#KKuqgN4m1Hj`EVvKIDuo%bMbO=W^fb*fz> ztuo(Y=wkUgR{~XTbiWwL=?h!P>0`#IGx7-Ix&GIMRuDuYJ@PBJkLXa})D3*y%pi^p zs7mCy+L0rqL89SCxI-g*nru!jedX_BnkG& zy58rM#2I=|yGhj0B(54}7*($9)8~UcjrCkb^i^olL@a(H(8L^L7&AvbAXFH$0^>g- zKz!2L8yXEi-&o3c~5Jbhg*dml@z6ie}#kkW^RSnUEP+9UUqQdo}ap_3`4p#^Z7LHN0_D0AgtPke)t8=p-dM zujBs&2Vxst#HM^)myR(=YP_|ztQpYNHLW`xig$>`%i>8!ATQ2YY>^-rpG0dO`aYiq zGdfS*6?ymHsAedrp%LDJMHVkX&U%{olaoEJaku`6sT`*aSASx)oOk`4JepSWP=nQu zy0RUeIW_VMY2G{ zzjeO7540TXKIW_-FuukUTH(eIWz-;2wQ#okXec_qoFkb$ir>>K zMf0oJ4BcFysFae~g#Kkw|5M1b5e&>%TWXJKHFx3Ie~u#e5goCuWom1rO{;n(a0Su* z(tBsw&OiIC;;2O-dBPQ&R-I2n86FI+STOJ@?f)#u&3xOu5nESaasP=@9iwHjo$k7s z=_IIS)<}C5$8}-pJT@3Qjd}GbSNQm3uq%x7(1wI51%H1}#vt@P;o&XG3*P3RQt6xW z{R)yut{8DXe`9VHTxmd6H-*kDM_`MgH-G$6`PlbbT@w^=tHnL%cI}jr@JLEdJc=r3J2G(~gj?c@U#O#C!DKv4?7bBT?SO2ZhXX-6vO3B4Vf)Y!bATI}H3m&T!#|J0`@;Hf!4^ZX)lkGeD|DUb2 zJ+>%Q|J4CLFoLo?G7?TA4lZ?7%+f8!m^a?R4t{@~2kw|z(-8Brg}hQB6F1&i7YEko z;B72^+!XYQX)5CRcP>Zc2(8F-`)9eEV|gAyk5s}OV8P!q(8&LanrVZXE>rnxHK^B( zrqB{0(r|st`D+U&bG83Bbg0Huh#4vi34qc%idJ3|qq(ZyfqXcO5UL+Usa^UC5eraYBXK!hQznT-^&f^l*&B5|9Z{ajI?>5fVE=**B(EU zP29%m7ntqO{K@}Whk`xWPr>EzfdmAWywb5b7 z`uEAbtfy^Pe}we0MnE-m5w%Kr)`i7tj_Ieh>-{yZniDzjn+V(C{x00R*>K~iP&f!$ zzK`ff8_DHD!y3+PUDHl8^>e9O)wau4CV}YtDl0YP%CAO7Mq*-^L0<{}I5ZvYQM-tt zrw6~RlKjnPhxNm)1Xz#P1i+0kh5(U)aQYy>Dk`bCWJ{hJ&4cm!Q2W~BESrE)pMV{z z>_Gml3)V%P^L|En%9L2wH~u$E7aX(38zQ&J%EErweM|(bM2Xs}Iy$jQNjZ(P!%yaC z*eNLFG>>+*a$GsEeRwLGDwr?bcHj%cPcvEXDsAoQx8{&EAGJ0F1h1FmaJ*}_bPmIWjJHnoeyk5 zV|6QElnYt zVn;G1V!+`E6n==g{a!*OFFYh`jUu2t`mkV}Fh;u~bSC|>1$1hiQ9$k? zU&B`P{TM?-Ev{^!*v>1?lUKMQtL(wNu;NwIP+ z?T6Gp@e?zjJlywVg*0^N@-DK)vJ#+2iC=nG^NT4b|I#u-y<#VfVD-VL z?6KAtAC+|BX+JhYHck}5!*%~6ZTc{8Bh0n8JzD0cAs^V@poPK3*nM;xuwC_j zR!Ub9E9ya+T65Pg4Eo*FeiFzQ4>D0zQO0q%|Ao}7lu>a{H`>?6eRIxcdkEUYxbOD8 zV3;v*=G-UoA%C=;A1c0%vrv4iP*DVA7mzfO@V`qKUN z8z$Fytc|o59b6@irf{RLy(f6B#SbhYcXcDN-4e`w5Ahd0Hf?(jBwKDFp6oD{lu%I_ zqh8DjfbfP@_v38G42ab%`hc{HbSmE-7+Ba#C9h4g8daK9NRrLNgAs|7bT6A(BIUQC z_()ydZbo+nDrL{AOe`VMyT+3H?-1K(^R(lHw6YuLH(Mw>CRDdl0qfqSV!H@Nehn}m zbw);qvVi4$aJ^J^&^4`LMK)G^;jw*TfC~W`f}KpwEJpV#1=1F z(WEQ`la7OC3Opzk_nXLF-QmQO#*YvBUA5!I_Z0YYZC7lS`+zyu`n()gZAlvdh z-D?nVtDRcGB`ogZbx(@Dhjsfaa0LP1uR}Rwxc^tFZs} z8N8Nl)#I>cy6Ua3CQ8;-eQ(L<>3QLw9WuR!uyU~E`moy}ON2n{{`8E1VfO}_?!JRk z$1kM?y5eD0ne{UIa(c-&@`L5} z)bGQCJ98K4Bh2v@*vC_rZ$?b_Lx`kPZ)WDpNA~@*m!7>jEnl@c9Gb4(Sl^iEVvHqI zDaKmeu;EwT*W;d_rzqm1G&}BjvjO*`i!uJnXrfOyq9;8lT%T#O`Yr8DpEG38O+dhk zr>n>uS+O}@1)foWDU$L^b5E!p^2<4E{--AHwq&y#7>dl9chbf?mhl4GNi)M~!&m<^ zB%BfIYnpbX@mLbW`ho>YuZ)||$a+i2&FxsYO-0S!ylZ^>7x%9hL3UXQ_XD_^^Kmr^ zG)BZ~wS~D^x@}AW((b?c_Afi~ZU7nUrrvI6B0S*A|GW6nX0;oyzASJm>?$kSo)rpL z4iZDEff>bH6;H}komZr9?+;KM)g4)f1ewzDF_gkMVs!pqkt8_1M70y291-k~pguoE zt>kjx`O!C=KE)f2uJlva#9nX^&)784<@fhs23nxP^!|4IE1&vm6MP`t9uOBfq9jsj zmTiK|3qHf;oeJ0&r8m)3H$MAOqltR&K_Yecr^!d4hq!UiTXgKP+4fV7m+@x)VIHy| z>6X2lb^yF8&iHhUD(pOb?qmz_9LG;j67ES9G}L4MvWMyk#_MS7hrxVZ@o93g6Nr^$ zhA}?jIr#H=Gv?)p)Hwl=G&MD`lm`l$dV}?M8R$oxLDB(y3ssaYGzketTKz<*Sy49f zyvZpnAcjWU@trsB=OLnKQV0I;to(9;XBQ3K(Wb69BBXfQ-mA+Syo1e<^rHNcKV|r= z7vL|Tq&c?BqVav%T^hz&(mLIEMqd&u5DT`)rHw$-E;wyd9$mQrwA`tRg`)h zHp^Ph(~CWRGR=gLkYUGy&~8ST`mYf!npNa0PH*jZB(A$hrOS${IYW~m6cPgm6@oYs z&-1(K+^72|Z{-!#g|VmzrBUa~T6S$khe}vaIC-j9jZ=N+Fu@51-F|8#Kl)iXun+Sk)7`(g>EY@qTpOR&@z&Bi<;Jq$6LevIwFXV zdh7mLHobGc+-vRj?t*Msr!`(DetTuU8i;t(pABR_<~)uw*VbUJZY3;TcoxhLmMr|hhTzjYH4#TX zD0AE*&qy#j_xP*p@zR6;f_#Z(V-xk|T2@&G#_FyH)iZCXMVRk`HPIEDKhKu|=0zTE z?Mog?=>9$*Aiv6d<_ykQZW45qjS)bKRXPNR8T2tDohPrg+@<4Ww+>2Y;*M5%xd0LE zamWy&wFB^Ue7J0GI*&W}5-5<#7*geaq~0Ap6Sl?8cse^r3MPyvO>y_2=lV#^TVF+* z7b1CiFrXPBS8E8MnNU2~Kk&%1ZEqsV$keJ&&GI zL-LvA%aBweix^;j3vRvi78OKMTTUs&N|^F{O$3yb>TM7UW0c4Cx4?1Wgg11Dyy6wA znWg1kSsvUvvdaj=cE#)Mfi@>|Am7e?8v2?(%XTy9%cT*SVvu8EMF{T4zpHv# za@g%lV_GH(S!Kz$$r<0CyE#tk5mEP0>jZj{LcB^#83{`mrk|p#C3;@Lg~L?j4f}qZ z&EdR%cB(#w0Q;bWmM4>v>-Uy;Q41jS08rmXnzCTR2<;tJ@9N-?Xr@{|Hb+mTHYfAd z;gU`omjp4r@OE{=*s=e*#T(gw2LVO{ZxUcx)|#E|R^)q5^DFhppop!I$9g$c%9d4i zlzHgHs7B{2&q8K}lc^ZuQ|`VxJ}&TRvgmrerQEGtYQM@2D{e2P9%An}dl(4|yDar( zENg+n1L4ek%cAB&A4|PE>i&|kl2HAf^%psVSLc>{PBNomG+w@`MZDazF{SJcAZwUs ze52a-2*&Kmvykifa`ptuXUk#8b|zs`DYl!zFJPXNl$c7-U^hOP4qn4=v^I{LIetF8 zX{lR!1|3u^1i+tf%sHe^J9a~ge?^a&*^r(cc zNA`?ApFeJjUVY5O=$@M!yE~6OG9X(%XE;2FPB1C3)SZV!{p?sLHQk=eS}!|*o5QlH zs6!&ko(~Q?7+KgZV5NPiIT+ zEEYIuLP_xfJ4`q3HfYCGD*ISU$kr_WJel$`<1=wm>MgI}y|cDm&2ea^Lv*aD02|07 zU9gwZW&z&XHh2*{lhLN@gc*lzMbk-VtOGf6GBV@nQ?8z9e zs52S{hdoLEssPfbP~Uj;IQggkP^v@0ua~;4Fs;lo!N2GUX68wWDopG{&3&ZF{Y_Qq zQT65&0+J58^ox?~mMM((EWDzrUH`j4#Exi*pRN%=d6K(RcvRTN{WbY@IYH1bUG(;| z&@5{HXujLLhMUpE4Czn1&kvCFHf!eX_qQeV-FH-d0x+!)s9d@9K9=bGrx;~A*mKo$ z-!hef-GP(z#oNK6@I{!5z$*)zN+V@Ip)=8{jYG}d;YE!LZ??#YP#`g9bkV7;V2rOe z!8{dE1bkd2TIlSFOAG@;D)yOV&{a@DpviIj9hM+!Gu|D4eOf6>@&s(i zRv{y*nywfB2HGV8b%W%uZiJR1|Deb8*;pAr;46XHtt4;{kDhtJ ztPJPaut{q?XdW30hvQ<(0R)*DLC+Phe>9m?or(jVq@P_OiG6NyVaB1-cs%2uY#kgJ zlV3x9)Zvv=8c8^KE}oz-DK4Jhrh0evTAD7Q<{x9o?$#CwDd|jsN-+f`6<-$TRA4>R_-B<#gNC?J)oU23FZPD=h2hZ5=mi0SE%n` zn{tcR9HpF7Q4|NLhWopeQ2g+^f|GIPl;r-G>sV8oF_(ss< zMo^(qzXikn=52bs7MjCd`zwB#nE)H^v2O(VbUHdNMb*Xnw54@gI@K2mX;NpL@HT{7 zSz|9(w)tRTewyX8w+wbB%zGWs#M;0OvF|jk>kruD#?$YAjZ=2DlP%;O_jlh~X^uO% zIUXH;CE93x{P=gCV_*DGYaD{nyL{-J!$=5+ifXnm4Y&>tZ2#+)k;ArMPm9@=sUG5w z>|iID#kYQ`q{p}Ow(3*+Ylb@^e)dk5nV=5+dLGF4P*CH4(2@a$tD*v`4?1E_OQ5u5 z!_R*$Z`w)d-M@IWpqTV6y1*AfG~E!!DMQFRI>m5#Kmv_?RQ};wfESW zSZ-qu$4xg3js*Wc{<^~OsXLqDV@cPqM-L~DmMa>k|Cx267?P-hgw3D70&n~Ye&DVz zTXYtDByxj-Qdy|C))W_qS$tVXkdm3T&dDvh-0P~*H~`8-`DXQ}EzE*InFw288{)R! z>sYb_mR|E^KLc_nMAwJJ+e)V zlRJA5NTA3rwKP;c3fxT}ej{CD5S`bIhXOAo4UYvFy>$doen>KKgsT3~qD z!>#B&#}dYX3UO5}M=6Q0Q?@`MNOBC=F9sY#pF|Xou5iZc!ZpY0x^Fl z*f#_==2zb~3Df0dImT+gvq%ce-oApeWuCaOp?(j&;jn zZuqZFmhxjk@0Hf4ibtOoy1`ym+Ph-t@jDphlD7bNpHc?HV~0^f zGpz^ZKw#4^+9zA_DJ&$K#R*+q>AgB;Z0o;Fsej%+DOVn8_mw0lTI!9KzZW-U2%Gxt zR{DNfky11b;;6ftD${HLK>+PlhJ9OGMy{@|mN7B8t(a`;j>3n*6+ClOXBW@M_a?)RSKbWK#o(~>HE4q52lWRS^#&*!r} zhz5@4eWS%z90n+g{1(l0W{Ai$BgIxJWEhD8vXO>m9oKC>U zn(jS}zJKogS*-C;)^nWgX{l+;7=cjq}1hF4Hg4SAHn3ytFf9|49 zGgC1V^@{>`wpuNY&xCDBs&YnFLlj7=z>w)S1RpyUI7i(-#zw#mKp^~ubzO>2Y84t@ z1)9tmnH1vS^gE2Wl3pKIs~N`uWo2c0-WkB?umF)#WoIY$vqbnB1x$w3-(igCeqX{& ztUSv4KGdHe-ux4Sy|4_vL#JM8z+zFU>^od&sBiLGTCL89vwM4H;N+#Tv9V~9E!Fm< zX7NW0P&sb#2LSyym&N#jqwXC^m8>(-?6Zf(Lamx{=GWA-C12Hiy2YY2tqsu>A8%7B z&26;n95`Y>-1$Bho|^@QvsP7AOP>cs&BSVvyUcvt3QvT4k-OD+nuQ<+$)?3Gzz5z7t zThPo|MR`+k6P?d5jKg+Mrmih9^eZ`qEO~EVvxluxL=zj$%p=+zQ`h}SMDnn^!royw z;<;3{FF=;#*DF{>&Nuu@4dfFyLIY2ugXv1u9*rj-djx+4HRk!N>LQuja5&wC)BCQ5 zVGoEXV5Cu`&m{$mta4O4XpEUUrN2dgUUog?^;EBU*k4Kr!+lD2xkJIQ)#h?_Yc)}% zNo8Gsv{rSZ;&568Je%&U3s;w@gVwV>XtZvs&vq1fg9>?fH;Tc<`6@YA!5Xotv`&kWD#O(|oSN9=Y3v z9DtZol6*{Svybn{rMA$2wz+n9wiF4rD7`ZtgLlcgy?izosZ7_bJW9aFh-6?{kG`t7 zk`Ija;Jvw9T$}UZ{vvY)nv)%So|}$!No7xSVUph8NH@NS)>$dD@tS!ECo>`EZC^LM zBGhrbH4y0yC${7RluTIwYgm0{)A3z>+LH@+t?b)zM=YdL{TjZf>!mRhvO?hO`S$!q zYFQ@wDMsXN$%$x3sbqzG`}^&d)WDya+w`vp4scayqgMlb4=5cN9fS{`+rbXRxMkYV4DUs5h#@E&!c14*u z#3Vx8hsNpy_8nI}U!9YRCcZ%Heap?XCLL!iUj;eS%<6Q}n{2k^-i8m{@$kPrYMBQ zTljbDOZDAxqq)K|4b?-sg4`#1;CDPcPZAg_!zhAl@L6%Pz&BqY%dWmPd)r~D!FDZ6 z5W1!SW%@xbA+dHL=dfUU(@kfAvn3C(sAuVLK}moI%y(FOPC(Jiu)gCVn!_5mMyb_S zsrkeSUdweS_g1Hy5;E_Q_641Ap0TR-jC+z%d%}F>v53E~;wBy$Ymg$}U!JMb?&5R1 z$ve|l73-3m=?>Tqp!X^uuesF+o?8ljQ+Rp^(&F7#C`(a{H*-{4D*|3}V~j7r^Y) zEBoqH+z^bA%CH*~VV#VX(OuR0C!m-lc>PI>Cqal?r*al%HsIEzAENxzD(YEc^TkMh z6=42f?ALwVTGc|+FWfb-t7Njdb8$T*<8m3`km@YKktYBma8EDZt4?CpQ6&|tMTfyb8G6@4!r;xOk8sV?|}AN>wCuo zx8PC=XW*%@TNbf;4!Oi<{s|S5bweU)Cb2`jCSP7 z{Ij>X8$sP)9zo^J<(lv8oplnn05fPx7q_NS?bTx8Es;P-4U5P_&PdGF)#>_t(BgIa z{eXqiG651)qwwHIKkI#C;v2+}!=#4Qlgyl(2kb~zfj8{T6xbWU=L{6h7nNo+p`xbc z#cQ`Whr7=+uGU2#zB(i$Jl}7Yzgbm9BqOG?)|7sxKu6(&(5O5my8RZuP}a_mKL9c^ zP630n$>PDIAx%@)#MU1M8c*G#)O2GH?WE~Gf8kYs-ZGmaDOC_E5!a@Q^7xQJPWwPo z^ym_DBhLxyr!I&REEK0ejrjbh09r4c}(fTqUr>XU4+~$gw?+>H%cw&`)ch2&AYezi{j(Z-!tY>awaRN3R^RK9pl^#M}-CvF7K4fBrOY z-3UR}N#PQHJm>dm0y*P#a3*#VzYPHBp(VqO2RB(1Xwi*$eww$D7Pbu|mkp)9EyMV@ z!yr%gxQ@%=G}y3KNV7!N(xH0sC%uuLjhahlivK?ET|vD!pQizRS{Pl0H?@6t2$3Y$ z8}sup3Nd?G;5$Hor?F2Q#H7bo_EmGhyK-|Af|KdcyrZHXOlqN@&ajNS6 zP*eIbFumiST?=#~^)LE#E=1yoyo36qrbX8vnLwwQFBKdSQBOtaa}*SOfI&%|p&M0l zRey9^Og}^K5hghq9G50D1-RvYl6Ny2ve`BmYp1Q(FzFg(#VkQ-;=%{b%3oB$RIS1d&Eqrty-|RZRh_BbL$z31ppI`}a!90DGIZUdzhfb`{&) zjMOdJ!zCCMJFuJ3v}%v^k0!k{D2MH7s<;T8tz3|V@1q2yc*W)Qig-#Vnu+xz{m$;@ z&d+jkqVEQ9MJvpmigI+Fw$H?(pPv1c^H#r?DZR7iutki^mLCcswjza_jWC5JrIu=O z`yzU9GtYE!qo^bf0*+%^=31FrJuYW5L_dzc>Ag5pQYEiYT8N}jZY%czBx*SD;)`Fa z8d=iQ_3HmM%1l=!Bhi>f7|sk4peti=Xrxo_2gMVl)$L<(b{8IueRn!Ah%$k=3EAr) z=TyM)=4>UT>I5FC2Sq|&S0=-4@) z*t(;09+aBaS_c8i{AgN3teb@l(c8DeE?5lfF}fY%3|SQ6OC&`lG3mce2(9=kJ1n+0 z=J*!+T*_0KhIHM5$b%&jp&9~S-tNDf^})}qFKRN_eH$Bz>LbOSi2J$QuSLI>X13!3 zGO%qQp*q_i9v=22Ei6TK%{Lp;9sX>_&2$z2&iE+tBsAty<02)a6b8Lf>*2oe%{b)r zqj2c~S}LQx2>IA~?Y1RCNrQHqp3#W=rOqLmhg~n%@f${7wOW}rhvnpfsF{=x_id+S zRmYOykhecs>P{``!1wHVF#9voKK3yfFnrqfZ@`uLG&sSh+i(Yc?oXEOWFNrZ6$J#NnyeABU*3SbRAsLsDe3RwkR|(O(LpAawRzV!c|wxAWk8 zMfmv~Y}@T{9hA#GGiiDpHkTc={AOsbm`(GP8O1l`Xgrc&{;T2OW|!>K>HVs`8dFvo8)rSFTG3=QFn5F<4*i+6;<9Jk?HTv zv$+7XP64uM$o4wmUC{>#H_5r=axDCmS$YqZ=`OMP_AOLp9}9KD(m%E?&9L@K>6S6i zmIjm`0d2tWA2Pv?mdEW+SI&-yn0&NLXuOJ;&=Tr3+`^7arn6c=^5-(3bl`eB!)DHF zJ_|P-^Ehn3FpQV$V6=u%bCzgx* zHgXKn9F;s8Qox7ZZ29H-s!{}wL8yGVCXnKX#9N~bblRWPF$UWVN#6fSV6{o?> z0i`whWB#H9moM7QP7xxs9Sny7B+&+>Kr)2w#WkJG279okWey2dN~KmwUJNMZ^X9>_ zjcdPs-~R^^QSmJ}J3BjHDrquL5`#;qQtemP3GN7Y(-}*6!K6oQ7T;_j_-ZZ7bmcAlumdMCmrbHZnWT3cD$jx=IxN+VZzONfc$AftRbEyBf<4&@j!_zMH=62D-!a;q11Dvd0BwpjlC|lLJ4QPX-=&97K1PKyUXM<% zf)?;^aOCjel<(kcTNU(f<|J&7%H$b@MYW%5IrkQ92FH>Df%II(QdZ)4t2Q@18@3rs z;F+=n4&i~l0n*eauZHLSa7&a37{6d<9>UE_6j{!Z>+@RWVTx~s1AOQR$*B<7&r0uA zf7d&4V{ms{{`cA_cViLJ#rUY*N1Q-w`1H~KpfM|AFv{V&o3drsxMWu`YUdb&OHuQF zP}whaw#bY^fMs!hMg6RoF3e}x3w(SKfj0X#n$9En%*Mgd)K&kjk%In^UxtI}$)EhA z*)+RUH%t57BeXvn#HI@Si2W|OmK-TIHR)*LJFW`LZJ;DqMq~jBAg4x@!-imkvy~HP zH}N2{D(cI&94w!WcaDm`- zY72nPUZU@5!f2^i)E>#pLs5OVwK*YhvZ3u!t&c5NwyUBQ=|z#epX94dAUSP?O`z=UVbFrI`S=I*bAhj zadaZ2M?;lYt(uW)KTijtLnz;=KBlpHh)^Hze6XT|HF~2ebp(t5kKaiz3e22Fu#N04 zFQZ|-Z1dtotE2=dDU>wFLo?SThBGF_;%8%&-k4u z{UlEIDfw11Slv2UFu%1q!te!Q1wrC*aGx~UpIBN9AWZw)JD1`{vy`WhkT;zS~RpBIs zj!-Gh_!lbHtrT?U0Z*;e2l`sgXW4%9EfTCq$Xd|eFX%5QIhDXeV>y$%K=DC$s4(H~ z?_!UU196b(?w5y91o-LF$u&Ssw!!LW6Gpa=+|El3fcx2w<7NVPlS3NMh{eXCIy~+_ zEr|^KaPeCP#mcFyeXX946Zj`@GZX=!a3aF%#~M)Cn1*~~>)ZQtcXd1E z{P|YWD$&;0f?1sHN#8)DURrY&{fx3sflf8rD@%wdpJF>SvB1_SnKS$`)sGtlA%VBu z)FmaMVtE@$9PsJ!B%3w zcwc3Q(RKb6y+s7VM)9{xPysQ$-I-sis26fq4-!TPd$S67C_Um{ZanD(RzifjG8eZN zJ=ep5su{)~&ucIyL#vI(YzFI`HdYYAZ%LBuZcfQ{Z7@6#)R@Tidb;Ti6E)M>18p@= zNVX_rvf2FTpQx!wcVW}W%O9FGiB)YjXzv|-KV9uDJx#C2NaT3cB?CT>kJtu*J{a0g@e;3XZ)G?=Y=tND+;!{<_L>#F_& z>%RFjY#zU*W>pYC*M@BPP?^hr(q43no-lbYYqUvM_`VzHtna=l@`W>Kj;I1(XwRnBGAEL_?`mj)CJO2`e_p)dbdq;UgL|6HyUVvAfy%Vy@lulBMS%c zgXqXnHRrgmh76cjF)+*-bZaubdRR$7sT-V1iF8R@c}`0$@Q`i?yNfu=igMm-;3y=#J$?^TTyy=*a#Vr0TKcB<+Ez6|R$Xe$sG3C=WJpd?xH$h; zsdYLeZV#yh>8Ts${GEMJUkiZb7G1$>p~LfIcChPgN}p=dZH!}PMoD-$No5%y4-@#I zP6?j&N~N3FV=~Y3CmdfNhTt7bbA{%!@ICqyhy~}4f-qf!(FX}~X5Okujz zQkC4qMq&7HOUt?NY}JqB&7!?*1M?GhhZA7@z%=wKc z@aV2fCM@#ko}_NLb7;P11Lo}}c$0Wv1_R{UStC}}~i1>G#lCHc-(TS%d;L!!Xn~_JWq|$Gk zm-|RY+ylEU*Uz=sY2$jaEaRWEVoy()XYYJq zqt|mEzgk#pxp^lW=5R#$KO%mo*GzKaRQx`d>j*7uXTRLsK(6F6U-u$^X}suH0)8!` z{=~pYyhl%tC|*aisbYi%F|`ru#v<$Kvf=(E#Dk?Z!m#(|Xd8D|JSgbab7`I}(>5#f z9WuDO^t;o>YftgEKGM7w=YYDfN&H|p`plK(0mb@ua*;ActA)z%mjvNZP$hE71n2?! zaU2k}Qu=;1{pC9q8I2M`u4RcP(+gYi%Z1M~<7<+|Tr9-G(u=jEHfa%0j&n9GbAA&9 zWxi&0=O}!~Mu@21JLT%*`7-b}?Tp*yT>Ra;j$jPn#6B9xOaabXZv)wy{Or9B7yLDg zh<(H7$_L%-kCUPatYTx&03K2=@P=l0@vt z0-ezRiU$23`DP>O9T06SB*`~&vSZ&8Uc+A(lQGjc-yr4!mRt0AOx*h|wd+T#lZY6HvRQ^Ag&y2RmoyIBglX5hAzpXY4jy|DfAkq# zhVTRK@AMV6Gh>h)#g)yG;P^I)^$Lc5s&vw|p4MJaW#xE=EML!m`cKbr`>NnPL-meG zO)lUOPVZ@q@J05@_L{c}TWk4c`iwwfOa}-_k)}%Y5j}vIZ?Lbz25BrRo5DHXH&|?C zsqJ`js==a8BZ{!a!RYsqu8bpQEUa*Of|1Bc8fN>2yTki58F!R6chK&0bpf4qz>UND zZe#c$xQQuJCs?JJvP{C~W*M|^DN|WuHJK-D4X)^K=RcvNb0XTgEjI2*lLEvNrsA^T zmci1EVFSC%y=!m&+r0xe-wAz#K_#JzmUePZLj&^Q++hQTEkQaeQ62Bq70q2MECuG`K@> zf=h6B39iAVfl3JO?(XjH?(R;|#@+QbAT&e)Jdy} zwsJBuK{UONhsv{HE0*Sm?`n0vrG-zgW($S#p-6#skalR)xY$!LA!>63T~;lNn#e$N z%4XH7+Nhh#@xXC-lj(1|;jS!$SfVq{Y3kR@w`ciMb+6wKuhd@+UmKiIQ0P- znJmsz7#ob=kN2vUWY_Kror9JeNC(Kl?bRR7Kd|197uEVDmRhd~|C*Iy{gpV}mblA( zkfX*S)fUBDi=0^J;-tLWmjBNfCPs(WkG4v&!Sg0xT}?uD8T0Qfqr~}TDcGGz7(sPs z()lWssQq-v!DM8=C)C0T0G~PzxsuWB-urTLi4PJnsd&tY_Ca^eQ7uhvD4=ylf0?91 zQX$vatxPDt&=54q^ApZlr%)3n^ZnRNn*_fA$%>;*-9{>TvK`s}80@0^C%yV$`ah@o z1d?I>VHmo;yfoQclgEJ;A^}Mo+p4e9UhTam-2b}V@G{-0k zfmL+s@axenMn|Rc=TAc@8HV(A=yT;MY+wG2#2c59UlMJ%eB~^gKWS}D-5-)-%=E!R zw$K|0dY$7Eu>6OGg@ssj?9ds9%Xuv1&)9%*C91{P5y$OBd`B-UgpuTl3Jbl_(51_T zp5Io?OXRN4Hvh8a<+AAW$t#Mqd!34AX5|EITgE1c!FNwReJrh@JStKk2EzJUHbdQp z?cZrB+`+(0spYbRoF8{^X{wAe-g z9A5=WS_uEV$1Ztxb9(v#$f6=-a_-6)@3nK+QK`lVOAD8kqObF(44@T!nA z_&nv$mh_V6@BH%zUd9onzwIU26m~1&U)$Q3YAUBoKOhcfnk z&&>3K#gu1z_d!|bxq3EQ*Xc6Ddb@PQth3EeRQ=^d__X^DB^lk>X#! z7BJMysuj5RmKBP}R$-x1<*5EZX!ZLI`@};1yUB}ffH+!Lm$-tW21udGfEoFD!y2c?8zeXlnfB^8>F&Rd5^^2|HVW%^(%HCp|~J4LII4I zjsou6_LtA~YI+T2Ju88Wsu8AP@#gM5o(JZ<2l(;9m1T!{IMwYjsn8F@tltr@R_D~d z?ib>c=^-Hn6$DT=S-TlzK(~IzjA8_9U2OaqY?`c#6yf?vsEZY$q+>t2Kc!f0O}@}q z(LYWv?81($le1;|^lIZr%Hoe!hqUUEIf0Lz1ZP`Id>v~TYmuBU=0#*ii%VD7P*h{d z*`D9Vfz&<7>rLg}i7^leY7L(UE_PjvSm;0aNn(Mlq+;v*5CJ{+FG-xS3 zGW&?SI{E5;IRsQGUSr^2^`LWEU3@$U5rlqQ*b_BI((FblE-E^Hism$;q9z*Fq&P^DjtPP#xI`^ z07b{=esiL;xe9pS=9S_ywWqrAolM(M$WpoPsu;+5m#*aRHK@#z(^zcv>qN<+Ir&jv zzq#&3b=Dn5nN$HTo13C0x^XhEhkl>R-anA4W^mn3Aqb}!!Kqa#%$Or4k= ztj%?$dj(ck_x@Y489r5PUBD)S6k;a1=3)$sK@qn*$;pNN+NSV*rPD$iznEXHsDT&- zC?M{ZUY?@XePo%Fj{e|lQoAq-YsTKTr-arPk@7`RQc_Xf3uPesOj4@u7CYdaruYFAs6-;f-6xI;0@W&N!d7+m0);f6>eA^GPsY%>LORM~(# zxqDlsaZaD*N2YR-D<8<6g!)|ks`yHy*(^sVo>E)jbEN!_WgYXKFR;+~=uPon4ABZj((PI>!itoecj(-^VeO68Om`FO@L(arq!5mYZnLzp>@JINLcP_ur27 zR(!x6bEyqZ{FxTf4Ypfg?7YY#PLdwPO8w;;k_5FI5hq&RkRfm25U?^%8 zy0f{N)1U1&w)>Xz1l%606E+K7+VKRH5Se*Yy)0C8vzWGGe>Ge+J2uI9zZe56chZm3 z-$k8b8umUpqPnGuU^wnhN6j}7Q6royAAH!9kfV^a)U4woOc;*hrH(Z)1Bmu4M}a%e{rgxe%TE- z0Fc3 z#ZH?!scmGeaR2|xXb8EE05X+MNcy+T;KCr7M;lrUs3}Z@SN0*RN zxC&*hj$yPt9w=9KL!%h*rJh8G6C|YBz?gBk2tl&D-w~ViUHJL8Rdp9^M`^q7qrIYy zm)BE-MB=UU+&S40nJD}2?INr@nQvhjQ!srzE=8-cR4`dp2Fl zz>+H6q1jB>Ke6Z^OjaQpH6n=G(R{heuLjC&cI?yNodODLkL%19-B}N2GkavwIj!J6 zv;+>W!gg@`BjK)}#-cwK1Wc^UtGF!q_dr~*l3avJR%p0m*R;!}f>Us(5w42vdG_sf z2H^_tLcd8meOcFx_NBJV8`5<8NR*9CfHG2Lm~H>dtqD=}ZS0aR>;+`*19_}p5@<+U z8w6^vumj9c>z-0iY3t_+SHBGbr-kh+-yEOcOkv9#<>WZDwCkjcx4}maCuGos!gCM=#FM0`h%1)DE6IR-rF7;-qg2T<5el*!2Ok^l94z5K1b!Bh5|2`ol# z)AW8ix3z3|ee^G)Dfqcuo-{GJQ~isnNH@XVzC|+>V5n2XX?GvTE@mxkQ~~d)G-%Rg zO0{xbn6l*z0S%iav0c|=42iJMWN2x zD)9r=4fRi0MHRk`C;YmFFnBCt6u2Z1SRU?9ydBL|8F0)1SiH4D7( zn787yKc4R1jR))7^64$7IA9s*DGVGd^t*AyhR|exw(E=EDqQwp^oIkpJm8-Za9pN* zm53b`wn#yEdJDWu090qH?Qw(!`mAe!Nd&_ku@i2+%&^Z2Z6- zNl5|z#Mvn@RhG2abU{bT{SX+IcJ`tqX1|^`i{hP0<%z^zA~R!_P?>j{s-Ux%Opl*7 zzZGMhbB{+gZ>I^tt4&e)BMw89~a&`0FvKMsN8V{|9{K%U_#crUT@zk)ydT#nAC2jSN z131+Bz;ZaQ+o0`(_}xk`rI@j?19Wd(jf%vB`9Mko^8ppfz^TR^@z*o=PnVqXaf1`B zpRXt=d_zSTSfCu>(`kSD9O`FMzEpdfq~RjD#OhKpU^tH095<_$(QK+^x4dKn!)iEO zQvC36Npw@(GGw?iWJloe_7B4mF=vrSs$4WJvZ?v!v8lrAIO_Zxreebe{G^P7j!siD z2jdJ5cViz9$Ft` z&(6c>>+BT1TgVvg%8vV*Pb_3=Rh&nCFtnJox!Yvck!(;OEeUu__he>2ip#X31p^OL`m%#EHZGvveV_iAZ+{BM z)C|1k!K50^awXeZ{@xYP3Vy+h0pT|>-#AE1qd+8h@SM77ISt`n%Wuvy6!SO6RHHt9 z9XDc9=POh+G%#p#KBu6gE0ML2RwRigq3l5Wr7p=f8FGREJCnn(k!y^tV#1tEOa3{Y zP$5hD^vWZy#4cp+#7`kGU_u@-I9rjT^8yG$`-P~aYVFl0x%0UaeN#o|--rZFd97+* zkBCGx<>WgO%=y`Q@hey@1^ek@ElA_$xp-an>RdZEI}-_GBCcVwsnhd_XSy5W&TLD9 zq-AO50~~ZF0LX4Lo^m+Hnusl%3fBDWAAB|JSge68|WQ*@TgSnyS=yoRRuZ})kC6R z)zcX8cat~YKv^>snF(L}Z zOBG+k9f10?{qe$Z@5a~DkY>a{Qu7)@Z;0T;{!Q`}J|Q9FX3sDG?SZ%TG_P)_R?+i3 zdfK}W^x(?D5!uyBO}n^vIH)MO7L}(bU9D`l%85rOfqtT}30&$Of*2SCR;$C^40U++ zc6I0GYnr7v0@#(Mk;_JDqTwPEhalEyrqwl+r}-~rOIh!`k*~}XEO|Sfn~N(4g^7e( zz~BWzTz-bJxm+Ek3kUn1he6hgyWF3zuA!9WegKKx)9hLgHMnjLT%6~3qf4|06C{i` z26vGHW8+}yu%^_DO+^ibbftd8PJb%FT45ZMfn3)3&ZmI?;+ zoMYcJw9;P2Ek7ii7Sh!DWhR+u2z1I`4(-!#ybX7@?8M+lp}aq?#p|{}BP1+W{WGJp z-?1@CC)0k={>mpd)OL+9+6?F9Rs?7WeIB@=#0IkZDk?aMl)J>q-?o>T=qWfFesovp zsz)J&Oy5YRAlz;4=J#54tH+LKfVf|M)LD-CDkmxxQT66U4hQ&0g6BmjuXKLSqcJXm zOQA@zHA;kK;d0V7vwHL%kA-x30s`YDEuL0^A9fO&fNy!ZFhMexMPG!~TGlrWXMUNY zz5LyR}~^BXdIM2dn^)g+EdvaPjce zDoyN;R|U~eVPIg!X~~z^BdQIqT+KMst+pygsa-V}7qOZi8gw@M+;lXS5_a}<6Cbxr zODTTYgh%=-`QiDz^URJ<(KU^5vY$rNu--aSlvEd^7IzGP=U@t6#n-~8Q8aE7f_U=Z z@$#XN)BGCps(l&C`c)k;kVWj+0Hq7G<(~?F>X6zysyVz!NUScgv1^Ea4rdGB`|cce z`#t4?OpG=vGTSMqPq+|dW%a018-hH)p;oP^aX8epwzdvs!^6Yd9_XazF`2ebr&pF> z)#9dSh^Z%7Tz4uDMlPN{4Y3&AmH;^SILD^1Dr3vQqf*HLF>yW|-=D3O$&{vQ*P!2~ zcB&sZphYBR*k?~Cjf05}J4p>kV!3u+Upe8x%rlJZQ5>`^EG=;h!!f+(&>Ey6-x+~; zO=Og(C&DX#K5=_It*v83k@Kp~B~@f9DEMt#X~=-hP9c|xh2h>3Gccx?VMad2p~ zGu7FSz%n=uuw017Ep=@?;ME&z!Bl@ByUm`$Ig<0eK((qb!7iL+8?s=SG={S*+KI}} zA~rBUqfi)XqAvSYHz_XyP1bmeenE|_tqp37E_rG zevsDYN)JM1K={S0y&B!TWoEm%8d+3(={keoKCuTeu0W*{s;uLx($Lb6S*Rr;9Ha}y zKAmq^sv1g?aA&tQs6aMZC&FM-Yq-ps3n9BV;34lLFYUTU- z$wpPV@$R} zsc;aEfz(jqWe=7_rk~N|*o{k?VD)y#<+648<=u5nvPFZ^4iCs}>aqId_zHRF-6F1c z8}fk!=;hRWIYGVsLKOJt^+6C2E8)Ik@kRP(k!#-W(U>`bvmmz-Zl8@k%Tfd> zF)kz>XjDs}5nW^}4FA+J?shiKadDuGwv(-Ov-rnzK}BLyu${lq=V5+$G04}+)~%b6 zpB%wqfkF?XS}L)Jqk3f|GFys(TVURPD|-8tczJbAW!(8LQaWJ5igd5v9i#=^SEfhL ztiRbDHJn@ROt%>%;t0~HwU+c5O|S#FSJ5U-SP!z3uXpSymfD^(y0)<)#EGf9tO6EO zC(BS*g(|NHJM$MT=1&CxKLXW-0fL&p78dTFDpb>IaHL<(JR@Z17wyA%|Aq|Ju2DmR zok8)T-t8BV`XPB)uE3<@?E*G?j;T#{i|@jM@9CvC{`D8biQMgjTctNQaN83{2d%%B z2!zUP9D?R*U9MyaWc*9m&Z|7j`3Hy0Id839c#t*kCLgyCYlXi(Txp;O&kZNAg_}xN z%13VBr@B}>=5SlHtT#Kj5rBs?-n=#6o;~<|1Z=GXzl(v!3&HM>peCF918A#+esWve z(~F{&{l=rS(~QC3aGz=8a~zBYHhD`6{3(+AI43Ypyd1gs`EoAk$76{$p_tuO|NcM- zRheWoD!;d%9pb`w)mJv}W{ieLcUKy>(1e(5&JIokEs7q;Y8X#VQ9I6F48Gh&++;Db zFE`%@_RiOi9-LxCY%e?NpC_$QRDQ>fRPDIp5G7XTdtd$B@7SN)k@X{3rjPvh2!ABj ziKW8dzu2Ae*m#-z779u~ThJdJA^yZo2jntJ ztNJuXf7A|VSJqn^x1!ufGnMYSlUo(Y;!+R6b>}X&@EVN)!rkZC$Vg_Zd|`up+0?GH z>*<-|tQH)$Mk~S(uZ#F&kK~gJrgIc$T{DMJ;L>Fy@^o40yy;)DTg}hr3&OPX*4YE; zps7_BBkJZXPy82ORu})y7NliKILs^lz$(^81no|Qm=3ql=xnRy?CB6dz#G$-zdjNZ zuaBhi*KUq8ZVQr7GlGbx2w;l(vY2f85;r<6Pn&9ULr?_L}QsOGi zgTHpHEPgxL>m)Pw$fpCTNHwzqUEYf{MYFW)d76b*&6yn;v@?$t_7-gQ7CD~>Tf##j3xWOaGn%{O()naBm63L@Ad zsKRV-x^~?UAeDUGP!R``CR;_}&<>E#>vN)IsQuM-12Z)VgPLf{O5!U5*!MRq&q%P! z$!BX2=+3Vs(8}hH9ceuGO`uA&yBtuN20on;RZt0&)FpmY_~YTUBFp@9(v9H{Wa89P zH<_=nULLeu10+OE;`HK zW~sXv?yG=-6q61q5s~1#ag!vaV7a^w%5I0lh5B3XeZxR4JBRLpEGr~YLt!vMmuwd2 zL^6p?n)R$DuGkpwcl%k!vUCfipxH~@f$ZVeFWdK_pJ!J96qQ5~SR3^QHtKa(sTfK} zS&v*PgAb$|y=+pFfj83E_P!?XrQ*pp5>$wIwq&BNu5N`Ybz)U%X)2kxLPAfq)@Lgt z4pyuD#~Q@q-~jtU%>wS~bG7yk5dX_W*tV;z-%9umAKhKf%9W)fsKXiUR!9SBIS-VOmXfbA@Fos`$OgJ@OT(P3E>|bBMfu`gENZ3T z_ja#X&Q9$GX`f3rk`PLFeOMJ01Kj)?aDb2YFO0S~1!b~COT zwCZeqCCA~M%WOA9VTPC9SKZ4kPK?qsb9CT8y2&F*k@4X>{J8EY-KG6d=5=l^^oYr9 z0x(8>oGP`k_oTs@wzC0Yk@lSJ4NuAqZIw?I>BO6IH6vf^n|0^z$Ks4+F3++kJVKMY zJ9ZDEY+C`A&RV(xL-k`4H+QeHt@oBx5#OdaSq$FYw19YAdM0U19+{e7@Raq2i05Xj zB3K5V7OUVT{w!(6l z;`fLZwC7?99xL|l-1tb2U{%k5I)yXfV9R2Ct-!Mi^pxVK(Wu24<6Udn%&{_y;_^ns z3bFzVDiz^0JPJx_SyRiKd`Pg)S<#45A(FfvC5V+%CKbEP&6q%-Z~~w?#sa!oD8v7H zuC9@&;TjjD0ek{IM5Gh2>ceauEnS-}!rVNXRN4PP{J?F$H?xA*z@u!|Q*Und*wCJu zW%uKd!~P-6seSJiH_JksMbAaBYgxHSeL!;Pso=f0mt&OS^2v_d87SufNCxFU5}A(y za}~zzx6D&8j$3XqQ$SnN0rN2a&8Ht=tg2l`!~5;iY=@#)-FL0ndIEsn;5k&6?3wX&+|>LeKky-%B}MZN}>~bulh*AbpT@s~}&?yH8!43K(?k^YbjQ3{5`nxfBs3@TitsefwzvJwF=xJ-BE&ie2MoK~F|M?Kn zs-Ttkb28!~JD~cFbdJ6!Q#PNy<9)XvHZr7Z{qyDI_t;`i<3n1S4$UR5!eSsv@Dt^Q z(B~kR&2XYJ8PkE(h|}gFld#?L`n8$;Xe|resA`n7`-J5xs%>M=?8}7Zq(?3yhYVq0 zqUN?_@9||5!JX#u!M*vM#;!=4rt}f+SZ*h+-xjrv_olN#GP$Wmy6`4P0{vuC65@w|AyE;~20@%9>+TcbABXL9mV6;;0{8{BElL=O)n2oyF_H~~u$ zEa)r%52VzOFEMgy*K`jVRSvql8YA*g2>K&0Sh#Pvm1J%C{5UP&M5-sng{Ed%FG(fK z8tP^AZPY1KWGWhD?2wX+iB9j|oY$981%3if;=v1I_yRnk`g#}>aJKqeW}(0|5rIJW zrkV~q&U2FkGv^3ZDT)Q(U%fi$`h)JwpCrt=M0xtns47}`O;m-j>A3vTsoxc88aeQG zyc@HvRJm+=8`~TVhYb?9v1CiZ*omR+z=_i6Ci!*a-~1ZA2Bx zpDx%~>7eDj?VlEfXa0JW2@El7Bv>yANM9dzS5epMC~!T3z`<4zwkyTqvXiz3)2cKV zZ17Qy0SYr>uSKV`Shvy$e9xUKOcY$*I?p}X_csvgtiJ6gm`+-wfweH~3_ehkE3{Te zitX_d~` zIag&B8Op{)+eD?aGT5D8tbjb+%WVt>uOO zAzS-TeummKx4VZB4&s5Bhr5kab7jbi3qrxfqWtdSj9@aM8qxN{J)8Gv%h!<7HV190 zSWD{tqT~pJRPeVreN1ARJUf}p=A6LRZ;^jome6!@VHx69mX2xA+gkk>v*)`edH}Tam&MwNW3!K-qQlV zsl=^iW4-XCU2ygluXn2N)zu=O1sh1fhg;5E$08&j1>CN z{a3FQZgzR!S^QEQb>+pdX*$2Ke*KAZg9b7^*B21Xh`pnyEEGp!BN#dx%!!Juu{5!T zPZXkhNV*O-+KqaohQG;?Br#ctZ=|%lL_51Ru{K>0AAu*y&N2w$%Z%ezQ_MEhGpWo) z1XgvC2$kiHDKx8SUm+|)hQ8ShNI|N_R917Vdx5O*fD9l*R+g{Jn>3p5T^}|} zmljSQ6(8y{E8J@he#7`!{Zst@%Tc%RPTx44#NDKXVNH2JFpxNbdiZLWzl%2ci;0Ml zk@EXf%Y_arbkadc>^v?OCr!zII4sp$pE+#W&Q(q3VWgXsJB(JKim)Rl@-%TVUL0(W zo-zb>w{_K54owxVBa<^}6eiv<3zKx^%g(L2~ z#?46KUo=o;<7=CCs;mKFCdg-RC^Zj!H<1$3sKK)oO9x7xEp2~dtE`I@gn5SyG~a|Z zCQ(geD2hhdO@s<~%cmKHZRWD8Z018aSK=#-r%cS_83|*k^Es&Vd5VWM4!3@3O?fW+ ztF@O6p8i*nikMh2V@9&+#hlsbs!n~U^VMq%jCO|8x>g_JrO6gtVvDS-QP>p>hiiI! zw%cI<=!*T>T#b}u%ioy%S=H~;DCt-13IZeo@UYuhod=ME!JI$NytAVZNqi=#%teuq zyuV?$ERKalJ*<*fM_DY!;ebo+5SD5TqY1?&j$*#3*EKP~3+|m$mVIN!(%ghI49tgjM%y8{C#YN3NlN-Ot-pM3Q7x~j#`ghs8_&VTS zHO32Wq}lL=LRj+T8;ja*M|emdYbDic@3g|`neN~xq(k<^cY|UqD}it`16y@r20yy% zAM7i8(#{cS%0v+${ltHQAU{(E$>t`?$Kc6@iQP)Y;@Q~^VANh*M;cwnT)zEE&?LT3 znpET@F5Q#p!7?x!(=zmZuNnZ|@-*7eukJ(wG>g8joitMH^_GK~KGE^hZ^tq1%Zd=l zj}f>`$zEqAJH%Qs+_`#>Uy`wSTjWDmG23u^L^AONDCTrxgX z1|0B~a<7>?b)^;PWB3&(n$&6?i1U|I^b`(-pDt#X%Y3um{u*ex?E3|?F?H?#F<;jqo0~zkke5-yzsdR1|B&;SB7`b!`BDB}VprFlplp@~dFIL8 zy0YR!PQ#=ybid2Js)9CDROA7lUMr8{n6SJILDLo8)BXBfyR&3B+dQC6ii0C6?cHTd zxUzo8x@-XP_?cm#q@toaOOJ#DOzaeI#)o-Q5g)HBXomENLMAMybRG&q_vFX?JD75_ zn>jd2H^AuD<`*?o3I+>R5TUmy76&TmTi)oR2HhtK^PZ^do4H@{y@HBMQ< z>PY*Bq=lg&eVu+{9=Hu5z^0|W#E%5AruTi%4~@(Vo{^%>D=B^=P(txqvDHrL1>;2; z!;?;|Uuq(gQVDd?zQ5kc%!w9b&Xk2%C(k$Td_ zELst|JqK<#n7#^m4&z3jIu6{Qj$A-MRIkLns)nQ*s5!(Gz#R znCNuy$QAcgidJ7peyE)O5?s?Nymrg9K1MkG6NR?*!H|M|-Xr)D^Drg)-Fd>7%k>z~ zV`!`Ik8`NE(;sMEffGj_m`~AJlr|UZ86xfhGtDf7hJRHC^r3WeAD(CS+B6U}Q}B~} zXA-iqq+H4lFEj#muL8jEM*RuohzQC@#-c&nsF&wK@%v)8_tIgxxr-~SNQEx7h`5`r z(r}l1+5Dz;Sp^F{5AVwnxD$Q1cKzlgCr522fdG8*E_7fv1HwuevfSm_U!+h!~=2^XnS)>wY1x}E}MJ) zx)i0ALPJVrGD$XQzq|{-r0Y$XzM5UxU0v_OVx{rp<6nY-FxG!17y$5`(5e3$!(c>= zgP2GmN)Xmu-nww6wO2C65?@frq)t6I>vQSM(2{1`FzpzBZlq}01XX1mZG=c-=4-*< z?{XPDmVRG5xPynh^cF*JiguiH${m7;kd+!Lk}(CAw00Cqjt8DJIIzZd@{L^KiNz&<->5 zy&T;OToS5nLX_xFV!T(P`O8E{Do;a#HtJr6r2XD9-^t9JsQJDhyU)6RUVY8GT6VSE zaC34KN&rPX&!0rlpd3{subyspZ%+(T96+|8vJ`G8`2{oyQID@x1WF2WnyImxpuFy( z7oP^lzne6sA9hEZryM4s^31BXoBYO1oSxI=J8q|c?EsY$HU?9gx1c|ghVw11ma{}L zBXYks@*EGc#uF*G^wVyC`~OhzUDW|5(ot3H0wpQLAU+aMx_ps+Xk;13(aLjk?fHn2 zSOgj&SPXivFbP~9^AtcWW+R{W2hdQ$IxD`PmOR+T8Mu|1z%S(DNTyDiM~A)fy5 z3Q~eUMkxrEY31k|ddVl}phikxNk$_#YS9C(Q<3N^!?WE%z(WwYo*Ovs8@by+1{NTYrNC6%wIx zq-DI~C^7`A|7ZIdM!EYDa+fgOsU%Nx5@_iy4OK*N{TX(!#NnOfgBAhg(D$~_ww%vj z;LVNHKLGNG#4@(#$Sqr-(epffe=QLfB}DTIyZ15vu54tlqRUfX`~wPacJ>c=_}Y_|tenLnxlBun@TzkIYR?{5$<$Xrng5Q+nnl(Z2W? z<*6P~*oR7`j>QURyvkY9rhz;v@^t=m?c}d|g^E6-4u_eLykq+T$7-oXCTc&)WIfwH zqhw2Xy+SZ<;9FgU7`e`>`aH4&Ep;)hPvQ|lmf1N9bsd>EXlljq74Bte&!l6hl$q}O zgJfE*14CM-Br?FpVI|up4VHn@VtJ!x>C=+r)^od{#C{WAS7qq+-b;w|ABC(1Ht&Qt zEjj-#NlkN@m*_a_sqLNV|@fDoyL>z;Y>s=jUoFVq$S#-R0SoYn!B~>vGuqiQ7}tXE__IAA2Kz zS>zH*AxlT70)Mp&sEpwznd(Kv*8{|YjnH_qcC!RwkqrSV)5xO*yQ>xzCu}f zx_u>lp=4E5*M~3khhmdjiQ2)yS?GzQFLH2DLg}xhabPK!NDYL|@OeDV5Gfv$e`aEz z_0e0Vbs!;Q3vtE&(NC;bCG4~#Qp>?~-9${{q_H;@F*MR)bFsOaN;N!vs5U8HP-X{5 zc$#|d%5Z4f_kq!A)aUZPTrKqM5BNym(0Xc?(QyPz4yJC`U(BD4+3W-`sr$4tKXuEY zC!BPE`(W^}DV}ll+nl3&4DbBnVCBDHEn{5mjUA#r;mvio_jj~JXcJwg#xr&k4abd% zj{E`jjDKY9Pyx@f_Wqc^vi3f-X}Sj$_S@;&uhha8LxHX%CtX z!2><{UN&?kjr7^p#2fySZeVFrAq_MUW&Ide3oJO#~I57a+Ux4jIHV^Wk97n-clvAW)CL`yf+(VGvbyF&YKV_UdA z0U^|!850h<&nO5Fm^}ody~x8+&RRI$4;aKsid%XfY=9CM5*%OsU(;HJ?CuJY#fI_u z`+6&xp`CH&LqamIi+tL$%Fnt&4Q8mx8`hrEAO*}nl7rTA^D5i(ZvZRlkFdLwt}-hG z$a_i8?VI$@$JP1b%$FTA9`*Qg{LBJzFE$1h0>LG5b|$z@Z%tLO*qk;_c%x#i4=h3f z{Qs?%9wMdpFOsr0F?mBSpSNA^Z09VLr5OL=xVvikp<%Enr?wg$og}Qeq2k9b3Hv9D zk<)rXB9nQ4NNqCPxC}H?^5t{hFQ*^UZY{?YEj@`F+9$kL;xVtvU!)pNBTH#jh>D^- zs~do2&mo)D+v3&PbC%un*?^KE*-uiD$0sd2P8>93#u#8=ZrV!FNZf$Fc!XPi-RAQ^ z@xS~4CUdgb?##bbM}AomV&4v{|{0S zpxT3~OlBtYDYONAa6-BYk+7HwEy&6ls}{Ir>V(SI3do=^g?)w1>xmX`Sjb*K&&Tx| zeciei3J!ml64ko`mGjP9YX_4O+Uuc-`T=ov`#T*25`IpJW^Hg1cPGB-ULIavp7f7h61c27 zV^lQmf=0_4jrK1cz>So`fT85AxmuwreO?vUDIEpQuZ;1L>$Q2%*mk_|aQ_VZuCE8- zVhCMvOH;YGZO*QeQl6x|tUN^c0b{8@c4{6UpiEciAK_{p38{cv_73^ffta(#MUWWF z&w&3KRpH_<)*tbJGaE*M$UxG7@yJ=bpzY!RY$y<%%Y|*XV&4EMB50n}TG|+X91xx_ zk+Q%N8I{cD=XVtToXCGD(#fo4APl^;JjiP3kiDQ3B6xwl@~xJy+{tXdLwzC=l98R= zjXn*|E{DB_(CDAhtXcbP&WvY8b!~)-?w1KzyRLPoa2934#kAN*iuz~&G2{wF+ zIy|OJ)fc;h-i|IJhjgy7A>-b!Y;SuFM zKsc9svU<62_~_o^(@H{5&IN)|x%K3PQbyrapI}M8-hpVjD=L*j|7OR0Kw1Q)pQ1=s zu9mc?ESIB5SLvmb1&VOwA%v^)w|FTrAtrxpepP>g&x3wRN;FN_9(dx&3BH7ZNuG5P zwc4FXpEz(sYU>#%+q$ljsAti$JnoKY2lwM^UAyiMA+X{{A6+X&+%_afzy1OnX$I>( z3^vs78!}TX-#25@0c_6e&9SBfc6lMLE`^9+jbIrlTt?IQ;1NE2sRu8TcjGhI)w&+P zzi%J1KcM?)J5eGj$(e+>9Ho&P?EOHmEyS#o>?PPG+?{d7YDaEC`O*cqjI34-RhIjF z(Rx`@=3^9WhnHEMMMI6jIl(ST&(T;Iu zOE~!2@DVRIJ&YNrfO~t($7}~0A@#%A}7FMxbGkFkpcYq7akJRmt&C%4WH7p-f zmfcOtg>$;y;$};GtErMpG_#qaSdMD>{K(`hV=_?77Nf(UJByvkkJCti++iE{IFqdr z3_+y+DEcjYlQTcKk1RlzK%-0AzVb&d%XaAnFeiV?~|fgnD;LYlilw zdOlrj-a~QAX3 zd3#3xLsD7T>(X7kyzS`(O5|g$gpn5v z;Y$a(2|kI+)VnQi3v@+t@f0jdkTukwouDGT#h$6ZLp^+ZGl9~Y#&r$O;C5Fy`6Fg` zShuG|GF_B{6d3y^;qHEO9`}1EVbuUU0sxWV80ovKCPqG-ok|(C1KfrFjI*%B=kazi zJf_4Pk!OVT5m1s%&?&RCIOCnN8}cjx>i;VNf>>|}vUWrW=?p*B93?^R=VFr|D3Y<| zJqx~oHb}e0Qj7$J^2BgvDNK|YTz~H z(<`(MX@45j%hnn%K7f0%ZNF=ALF7No>IQrp`wPF{FzX8`HU`d==o~`;pdK>6|gg)m=xr5ZhB zAgo%?0gJdlElk>4k6cqE(h*G5U!3H*r)mHQVcCPrwrI|t3ap=%C;OZ&hNnFA9)snl zYTRtiosKe>zKsbp-eguQ*w|kezFfI(wJwn4EGOOA=IDWoq-umsN`GM{SE|1sxM=+2 zJo^b9UurHz!F&}^rh?#ZQrMCCxV|3MhS!FxM-s2Bmnw&OBO_Hr_$y2fklaP54?C-E z^S|$6HvCZUY0#2n+>K+S0jCz+f>Zb5>{}EseIdR#!&_o=HM&z>39qM&7W11Td?oF0 zE9q8~bCuTTGSon1fpew_!V7=Wk6k}l8;6?m`b29#GGajV%x&Eue+LS|e~XRk!Y_;Q zN`b=Tu}iYxOB)nx<2nXIDM>%#9-n(tB5Q-wU7o7U_?X2Xf`7p+s$`PjjXsAr#RXGlcdmdqbUb)B`B3Z8n!rai((bX=?R5r<`}(fH(8G3D9B(s=m8g zv`tZ8#Yf8Rh*>8Hz&sj`O^bK^FxF(QJWj(TINL-Ud}C|10=|0K0wuehi&jIevAJM9 zvMm?jkI7R-Ga`GXO~kq9?d-IB`U_msOd@*_O5)YGEM68{1?QS#e(O;%AD(h;nm_y9 zTuwtO&++5Wus_+9P4790l1z08z{WGQPfi${t_blKNm!l&=IRxSI5oT_mrn-i$fX<* z1aNSAM~)OUa5mqAC!CMgdw>6iOOK~H5e#!t)7hPJ8H`eJjhkjn>J6Vrta035rwM#( zv(-nnPVmKWZoE^rsNCoP_R96{bIWAnxcm*`)&mNu^r{pVL3^d72TN znv<77JLNJsBZOc)c(rp!YiUK2`urcF-ZCJL<@p-Uy#$gVArRc%Ex5b8yE}`!Gl2ks z;O_43Zb27!*Wm8%&xZT|z3+V8nc1GM?&_-Q)90Aj>va5o^}x^XQl%^y7#y5aR`J=IFXj6p+zW!dKHc1+N6dkYMt1)PG4+u zEhfnqAl(fUDS zv>b&pgm|L88Yafe;TPXd-xtZ#%MJKu?HCc!QnKvNpRT?ln72as&tLV(K2#W zed);L=gV-tXT)||;BXhD!z=4da>h*Zs1wbaQgEGUEkdCn|BT12ULcEGr%9VIq=A$q z-M7=t5cm(Ma_(zjWN65Z%w)Vm%ikiUx(qALbXU$-{qIP`-*!MQ`O#%qq!Rc!aq1|a zPGxD<%!BS&feadI%8;TxEo2!8==cA>{=_S;6QR+vwlYeW`huVb0pU(6C%>Zp>e%LC z^9=_UqOG@+-2Z!kBJdY#l{sclA$;EZ8NB@Y@7dn@wJRxv?Is77bnu)lr+s<4+8?0b zs#5=ZW-tI2rHZO^(}9}5u3 zeb7waa)=_vZzSIT8I|J=9JrRSC+`3;d~I zI$<3crCV3gs3m{STfqaW{26aK+T}5w*g@+KYZPFR5-M0)07KR7yl?lT=L~_Kr@uy% z9_#JrmGPUGyPLn=@ZD@sucw$17Ojwyfpo8Dy4$%n)fO)wN6}>bAIWnb)$qChI@6U7 zm3BX~h8u;3ew$j4TTlXq!c|3NIKYF_1QlNV7k08ZjKO={U=PXgsw7R-Tn>yrlV!jU zQ3MDg_h$z?N(SQhee2ckS|P#>xop1ljL*RVmz481Hu^Uo?BvN;YF}v`FkEaDz9YBL z7F?hhLJso-#fJ8Y>#Y@=2HU8U)3zV{ZO0#92ehif({fLachGRL#Z1c^Hb-HxBHj|A z0@MkGPxyTR@bdY5|dlwqkru(J0n@_E#T2=bLcqNIY5*Z$uS^qq3- z(6;ILz6*_{RJp!=?0K&KgrC?Gox_*>oJ#154VdBDLSZ#OHG88^4GFLTxgz8R6NW#@ z?gO*a5mfq*ZkW=|Cfr-<@o1gm*oMwg;nWTi{rF$1;N}QkImU~Q?gPVDh*<{ z9EEzGrA7#Pf0KaY1piOHrvq8GsgUKEf zc^w$oH|ik%UeE4upJX+gZ_aJnSqZRc*P}X4*UK4-gC2Sn7dri^58UPj^#LEsY=EE7 zc+NDRIpZU8I5ioeL+j@(m)DC*j((wy9h?#27)_PlQTRE;z1EmfS!fDpWs+A^ zsg|IwS^aI8ofzgXbrtEWES37L-)e~mYiS{6;tN#jU)(=!QUAT+yVm~dLv1>YGj16X zWMUKxd219^zz>V*Y#-id6LA7)=dxfT6%p`-p+w(>c|i6`=KNvuy>e*sN^X2 zBh)Y5A%#F^a_Aj^+<;b5vwa<-@A*voN#5x<-G8nCd9blLcYahg@T5boZO&QllAV00 zX8iEWf=R&z0Vn5CU~e<`I%B7oWF#^I@wj<3gn#`AQXzs&mSgw$fkjXN zG7#lX7x^P|dlJ?!oYWjC)ktzuKhx84vVm~o{!sgudM?$TdT-;43CkX3XZZ4Vj(!u( zPc7(@GiI84^GYK#$D`-xlp0vtuL(5LzoYF9#q1sa(Ho2zbx*Qy$*=&|+ z@yaupou0)ayl-!dKL`zSTM8}iP8EQW84Jd8!inO0!W@b#2#<#_neL?54xh!Nq^G2I z+v6E*j(3i_viv;RGxG);xo^4B-~@WRk^Nvoj`=GZ4$1;h@tdMsqR8nztN{rW8a5W% zQyE{JDOYN9_t@&5-dk9?feomz?EPWYzHa@}=tK2%Pb{H9LGXn_XLc?QDosg5FP#fk zJZ}3fx}=wr7^dwR5EEdx&K3QYt>4gOdJrl86vgXj7ZE$e!0*od6NpsnJhtSr^k?kM z!^(_EHQ!ZA>r2{&#~Y>px1Y-G`eb{4K)!_LRgV#k%qnyG@Eb-@*6U?_di8it)=H~S zW9QJ!XWGJR?kJu9^VRGJ7IQ7SYp0~5=HrbYj~C|p)Ycpi%XA5wF-WGJ@{eY!C4XQ= z&P_ki6m_b4$2|tn^~d33=Q04RA1q$~1alN43HNBV0o)oNHW z<%);d%JxjI3rX)JIt{lW+Nbpf3>gR`Bc^mF$uF`B?8R`592S_; zOg59iP%Q3Z@-dp>NHv36c`~bReVmUSTz#I~eH?avSg<5|Zwy2hv<9KhFtwyFEJ5XS zw`aaR1}T~cYsqhg3Vh+Go+Df+6VRlp%ysOUZ?wI!;X)v*Slh z71+XpsLVQrTuTh8n6q8FuIQWJk^<{h<#`MM<{k&;q1naKK=@)X>&JXaIEj%>V5Abv$O=>F(Dg9 znV4cv-233dn~Ra|r5M|zJ0a0t$w~*dmQCxHFFVr z$vM9|_^lcd8AXk(8y9wZuI?w~5CH&49ZrU~ew_w=>xdbchuT!>0wrI4FxPk!$DE|) zZ9VhxS0;{#1#MoL4W?pQd))qX{!WxJ{+&L}myJtils3<>TP3!G>ndq|_oTN9k*Tt- zp>v$f@V}xDhNFS8IH;0it@(hNP^y$0=~r;fOQ|mGhITr8X!Pm%k3xN0^bxQWAGJ)i zwHoLbV%`+pa1@H4b+om9v~@FykuuR*Wpg^oCn%cNt2iZwYRlbfL#$EzlK8k2g>rz2=LLyAWyGce85>fng?C3UmFE2MEAeHv1QD=MeZ1`bDvi#o;ys&4>SNY(^}^=o zot=3mSeg*DT>(_*pCA}oIh*k&{%$Px{GQ{R*B_-Y9C6Dq8O(H^g!qKo$>6m3(Z$fR zud`yOWDtih^;+CYKI_nXwrbhG@qjKgBH<2aN(W3^<+nXTZ_m?1kqbaP&eN^|R($u2 zswAd@vtg-Wb~sU{6m-b0=;vprg6&=;W_}^^>nOg(W|FcKmPFfh(Lr;Oc!tEB81po7=&$q|*gVU&5C1*I+ zOveve?whF3Q(xhG9P>3hEnpp|v*t7P8fGy|nd ze>l<*YT)00S0C}2zGG12eTcSl|NLt>if%$UnzO!Exf^RfSv|)f`B$_0q(xhWUZ`2jK0~U zX`xvy$r6Svtu?abupTfsQ*8PsI)d{|(!2&L@{ zGJ~~r%NCIbW`1vU0yWf|6Y<*0A8`~ECezPxyNmK|s|+Bt zO=JFOFZ<|LW^bQ?%W$a*9GZyQ#FqGDY8qC+@E^2oQ8nu-^)fZ-MIkQ*`K8nyl1ZZr?!Ac#P41%D2^@e%41jmm#d?00m9g>F)@HfU!1bV$7GKJtZxXD> zm#hY>C*^c_g4I*sAg3}YnSq6_8`&~P$4r|TOoYv+db#2z>ha7V>-K!{(Y;PYbfP3W z+JSH0JgJg2mT=g707tcZ^0cOz*6@KQ`_@ZrvWKCro*%WQg2h61EcGF{{1HI*SQ9nu zN1VZ6gUFdrpFYNN+%K4Yy~|_$vILyTCbYzWF?QtWFCaxCK0qD zbk^F;me9v&qTU!N3K@Mk?TCNO7v_p;i%@7t`aem%D1a=mCGhq~T`I=b7*aDWQ45R| zWP=>YW*DzObf>P#s7kUE(hB@jXpsR9u;9IotCK8K(k$X*KHVU#n|NT8*C|(D(hfp%!I<#Z|Pm4J-XlYg~ ziU;o%B3Z4lDaOJ;4!7}ByBwixMggb0@VU;RPPK&2baxZnuJdZFmjXgsJ{ScHTW>7~ zU`J=$ZPzk?IE(|#z|MnneA5%Rd=#?8Aw0=6L8Pl@4`YjCxj*O;y%}?xtY;cMbigmQ z2K8=t;9^?zR=oUgLMhe62T4bwSzX-ZE}CMVD3`^0^Q}+kskg9bVwgWEtd8y6OBRJa z>I#WllZ6?|{6YiDIqTW3%kiJ*(hVX^2WMc(6j3JWa0UVG_|muwZqKA8TG_%W{|5=d zSRuLxHnjnl{r6BA>?64S)zIxPaYU;$G*F2teb(&sDnAZ-n}_$vy{9{#Fc)Hr)`g@^ zF9zk7Pm2WY)MLJ%gi`Ke@lYohSSU3GR$bT|CZicIR2E4S+k9uqZHm=8*ofT~ z2+sUlJybrxqERGd$;uSNl}XK+Tj_MBRZanLa~bs+yZiNwbI1sK0!LD2jNx&?K}ps)S~nxZ%KHbe@f4gx665 zRBc8w6swU}Zwwc8n>qAXei2L#)ilq~5a$#YJqSCbOhA^y*LWO#HCzeL>Os-1)DVh<1Qb5d9A#Dq5}OH z--*&=Zonl!ZF@KgfC)cwDK1N!8RIKYPqYc@pjh$@*J_n*68`c9N{4e_#I=uVEed*b z=3`c>kAd1h#AKEh4h0)lG$FwbrGRFqlP$?69}?)EZGmbvozXsm2HiBq)bQLMQ-CJN z9|=-bt)6_*e46`N$%ppymGAH1_zg89(c|X_J?Qyf-tj<$dIUkZM3!3UEusG|He4UK zRqNY@Wg~2mqEQFhI8!;HKX~eK4Y_6UF%7e8HJun{3CaB^8O3&s03Hd_PcvMI9{IXKc-176&8nZ zQ_aJCuf5aO-J-}U86Fm%2rmj7qgp_)<);f$rj(Bg$Th1@3xq5J{Mo{E+OmE`(<4FK zhCbl+L!WYfPA8ckHiRp;Shru}MgrmQkJu)U4JI{P>6n#yxMKnMQ^{4b*)8K0Pd=(! zreCm*i$zjZ0kPdgDY?MNDX-Gg^BGjl`4Q;EloVAu64qO3PVS>+16>u<3quXa4AZ$ks8AVxJEolInE0 zK!jpZjaHezq~VNqQLfzbe+E$@HS8cy4i8r6ZZVR_SWL}c+z6N)s3g-+dZ16mpwJ{( zqID@nOJv6}$3%sXl$9xOtSUD(hb0Yi)rcE^S!(B)8=>KaX4o!kN#%Q$*y;ObTG%`I z(G1Q4fX7QGGTz2xg?9QyAY7EjMrCr=w|E1>baFa!m~3Hj1f(XL>B7!xWSA|-`CE>P zO2rbQjf53SEe88t>2<-ymFpyBf2u@2DWRQE86feOh|GToiG65fIX|Im#gxIf8$bd(Xy2<8-0ha2B4Im$QtROHb zpBF_n0sXfuEl*>XuyS4G#z2-I+#z(AS%6NDy-_NR$*AZ^a}oKGcRJF8Wkvc4RQC&W zs3X6%b~xH`o&V@VjTrr!#NJK?Vm>b;d$!e3Qk&20<;I`AsI^gAtcmG*b=-mHIeS{+ ziYlxcz#RxnUx(b8lQIDsfIeR%nQY++4pm$uU)I)J=v6BL^@>|K$fVa}wMj#+R2i4l_!*8wQC+8UR!e4bwUIzN!U!W@ ziznNVMogCRH5H|YFDVs!Cdr#B(4X?Y4<18-Yjgg39QNuu@1(EM4MpD92wpQV7-dce zSScgzcdsIhFPwAO82pZdl<*=YE6_ zO3-4@30-Fo+|IFsCpS`mSs66oe#6+yRjPErn$-LvKq)<#d#Qz)u0l>ojF+mjSXhL) z<@r0^20Onhq9(~3iff$6&{Cu38`PdQQ5NpjaI@+_GqBz^ zmayOOJS*`c+-L~rSogny7AE61e$F#dx`!teZ~5fWcSxpYI6h9Q!J^Oz(mffZh>Du8 zuL*bzGv{{e2nBR_>fLQuj-L0#@u355itOgf-gQ$Th#jfjg7DKPqF*oiX18Hrdl}jylxqY=(xqJtBcrXhnln2 zy=V_`w$$_q8 z4{9;mSWU{hAcw?&uQ%O>1crYn)Nf)HogRndLk8Y<{%g={*NsL`RwM=Jx8}MD6yC#Y+ecrjfOBxK6#2}^hl?3r%1`%35zws@ z-zw6Vu}BK!5Df8=of#b^Pw4EA`O_`UaPoLJ*LMBS4$&Jep;@P%z1sP?o}3!W?7sc5 z^j@Ve2w28C5=+PKY+oLT@0wd;*3}TO@ocpt@U%y2Ej=}z=$@WXdn6mb#+U#=L z^v?7aEP`4QdlD`$ zQUldXDYVp1X@rHEmHGVdQ=im{6W}Gl zAJ5ZA`F1z$tC#IisVSo&!=pTJw~Wn&?a?Ba6@EpAY?@~i8YLXG)c(q!`Mi0Dhhzo> z$&r)_gS|xyk(keSSNEmgF1OJosuX2>*-9MfvnD zg|I;(Ygn8h_0{zo&sAXr?(>;rpmGLrth(!T>f2w|n0Q{eq-Dc*!#XyxO9C1%e3F<| zNAUQ5rhkuXeJ7`yk)}D=+5T}01Nt((+pZEut=_Gf>Ra@8TFUQ#{CK+9DPs5N3zd=h z+!@GIa0OmHK4{&;J-4jk2keiJs_G-^hrl$;c23VuDX^$`&&xB7Gl+;3u^_O9-V<)a zo$S&ywnhq@3&5G#cB~E55RjJ%9JaO+3e}bmWzZ8{x!ZS-+45~U4Sc86LLRM1lSa;U zJ{U%$oSM7!+8|RZ%XvX|erz#&c>o#e6)bBacd-(N3-b*xhWYG0#`*=Yk?xqH$X>>kkqGJ44Fi(TW#iq*{M~pKx=BhkQ** zT>UaJN|_qAcP>VKMz11Ub7}S(u3V~8zGztu<7_wc=^V>svJH!Pa;B$*8u?vlZEmcV zM+uA6%7GP;TK@vwZj`=F`T9DY^VVsHQ=fQHc(A^J@728y^p0n&xlHR7v%YGwgSIJv zNva@?jgXvFXsWk;UTzN>$l;*fSLsHehn7|}iN`N6Ttz8!rp=Q-{@nO$J|l|_i&gO$ zaGwTbaJw^d;=W^vg!=IL>=XiF3ZQ&fC{M#t#0C!n%DBmYP8VZuIEY3Pk`wcY*^&uw z79f0@h<}X6^78QrNmYjz#zrCvHVK@Tnj87`JE&hDyqxFUeRS$_7m?asY``5DI-SzY zO-z0{=MQ?upp;jy8rK(>kZ|9(dyS5p)+9O&g%CwXOnZBTd%m#iO)!VvX%uGQr$m8x z&5(`&W%^W$k~}pg8ZhH_dM|5yb44r@ZFi!+Aq$l;RhxxgJR;mV2M8&Z8j2a-DA_zH zdAx)p-e1~%qJ*1X3;a>Z#n=Yd`J93=h;^#k$KQ0XtY9(;2NZKVF9?EFVzdqVOf<5J zdR>WHnOe^;k;^D8iSLBf3563oOvE0{0oOeXw>Ln^^hecFbpa}{gI}%ol%J&%fZ*$= zlA{&hgeqb%uKah7QHX6V1~qrRr!1SbMyX@XEoaT$_|>yZE~)mJR8fo>B)m+;-kSsa zx#BAZm4%wB<-(D^3L|vM6FU$?4<*D{Ci-g1$CWq~T&|rQL($Sr4M)2)KJR)F;7KJKGJ((Ej zbgq33E$yiE$*c?~K-$qCz!2mZ{8aK$5z_#||MRBB^zM&+t@5#@da?q1>ek?@;7m0o z<-_kXYl(wzkwptKZ;_dNaG@1!Je%8i6^X%sJ#V}j7YUl&(0V?s4*Wcv91cPW~3FXCZy34mK0oKHey%!N=lK}4tZo++aI5e6+5s#m7274$ZQYk5A)m38E0|wMXNt6{pdV31; zRiWA?Fv4U)VsGdmEt|3+73e<9@v35vN1@JQMU}pl|L|c{ZNwvX{?+NC1if+KA!ld~ z(eWzdyu{G$5oA9Tmi`OgaS`U7_|O2r=6vtRdG;ma_F4-B$}~?@p7~DKO338RaWm3C*B!*FT$SfCyrH@NcyRXGp(^z;q!uu@VZ^&}oC_X*A?uby)^ zqt{IqMvGUrm=s3=CUu&W@MbarVgypkhCTGOLR9UJR=ltcZT zsRRFkSJJjf)S8{+b|scGv+fk(Dnnl~{05&#M{X1x<((WVk~)Wav|>wOusvKc=aM#~ zQRW{rfH^u?Nt2bz5m968K%Jq;Y>Dxm{!L2J4fqouq^@$LVNs-tl8c&QqQYRF^zgt^ ze=<`@&fh8ce~pu0=B=aWTs_kPUu@}qBb~1Lc|H5P@2yVfxZ=b7DgJbDEtSZX^n!3~%mA_yG{;-`f2@VKpM4Wvi ze^IaDHw^furl!u$RbJuhLv{G>s}rX2;t=Knl2gu9>_Ss57DErGgop#|`DTkk$vnS+ zzft%fOb%&zjpv>NcCI|2k4nAP^h$mH4Ot2K@q19N(YKAQh9wxplZC)0m*eCC<4JR7 zA0n!_UnUwDZ)fV$&lP*RE=|_5OrrjU`q2nM9v!H+*`i;`L7s(j zovdm;K0XN8tjbhz1_lOCo~EW`dCFy~LJ)pG$e$$uDAb1Q|LoG{Rr4fP;H>-udDX{p zm$%nI@I$gC5E(dDE4PE`qTF1PmmJ-S3dr)+V6&nPzlAAUGDC&(tF@Io84>>NpqZgP zxnXAv1#WF=j3JffITa3tdLc1?_uo&3Ts9omOZ922)|ylKGJN-2UnP2eMMSItEv~i@ zuFA>DNjkmu)YMcQ6qH=WqFBj1=Eq-RWHFQ|u9ofH*;$*&5i6NFXl!hppPz@kvbMGsvhZp*J5jQ;&x7j7 z$jPz8L;(PZlk63n0)o!)O&BrXKHwdF=f6Fxf;_2qUjOLngA5}Cpbeb=XBhc_{pF_1 z%P~IScE$C>`}h6Z7m#J?GcM=H90bYR(>37FZ{Od)e^*vAgh4@6GqdEyFHpPx-2VLA zvNJRe=|5-(obSrfCPs-@RoTn;Sv&Ea3H-P+=D zJ>l^=8?AHX!REm~UGZgyZKmsNcVRl?CiZjsa#~!q`&j~}PiH%nyj>?~`mjA+pArsv zaYd)}_7KX&IdJh^FF@k4lUaWh5diwX@e<3yy^yiBl+NK#2eAD;)p1|MZuzT&v38e-X zTY8!4_YUC=z9VnJQRh7~+?#?Uba647tIb*CIdxD-7{iz9@{OQb*X&px26DhqRuj)pd=Ps`P2+S5KQ|yE-sY6Hr18 zF(^_CW}Dr5SDoeD!3>0!PNOEB$J1u5{du}bi7aNnJk8qOJiK=#uB}-=JEUFB7!n`d*tL zY;3$F_K&{;5U|>q^V?7a_QA-82r86R*3P-e5AJk?Qhpy31k{trN=0%s18@OQ?R&97 zLZ-%=N~$2>wxq{9bGXV<*4Ni}VPOH1*-A=DZSoz6{Ivk2`4BSfbHbmWaUXUw0FSFZ zBrGf-8A6ha9<@dNN%Vc&n-5rhhcx8cfj~x~WbVw!Qua^TzvKCT2hqEkF}3VIoszkr zpTz~c^w_i9(y*rsW)QT0uS^Cf`Hx7T{f^AOCzZmP1%*~~mBV`Vj&2X;H&pZvN!#w$ zmUg=r2Q{@+k#eyzRm?zaTAB`|!}j)oJzOrA+}zwVB*;*9I$3UNc0NoVF%93mgVTr) zcvA)Zj{FV@PI)8BcxJLHQ~#e@%D;`_M|DmpWEmoWaKyn7Ke9P4zECwn>O&GdX23bU zf#PLoY$2TNH&_GyH22Mmx|AeUY6XjKY}Qg8rw^2)r7a%UlBVHNUy=p^JzN}hYNFu| z0?QDE0wzX>Jkgx|X*bgIjWBr2O_wsk*m{vChskms*(M8Cdr52g?m`$WXsz0({MOJR z2{k8GA~g%poHa`6x%EH%pnA7({qcQiwBku~Wd}0)OU;b{OA?yFcDck|SY!j0KC3_^ zD)Rvu9&fS*@hvQC`4v&%8mgBQ%TXyNx&k|(%hGbL0)JyS`wTDiH?~ZI5mTm-FULCl zAx1x@QT$`O%h(`a%HQZ?k#GjJkvajx{lqPMWt3g&? zT-dm=esF~V(*3khi3tg=5JtJ>W$if0Jb4O=JkuJDf+-D}cu4>6$)<5WzdW9LZQb47 zL7di4d>{3a8B-IJ^NF6muKu^xnHLHP?mHvqbvDsEgtrwye!FT}e5_uRcuSUeyRYq} znQP*-Ici9DE`}hFhnsx=PccUcYp+iI&;BRQwiaWTB~723NdGduwi>6j362C99wJS?K^rmVWNEyS!YgYCLbxCU;XhWA@}+B zuQL6<1wK6Q7bW+*Ahz_<*OL}%T*G41;aB#(9zf34yM5|>(KAEqtpB^|O+ zAtGSk!{7Lio^2`~(T;gtKE2o3M4LaM_U#zkaJ;5cVJ{rwYqar>%=1X}IhMU#zJD3@ znnv?N(HZka@SH9d=1NaWGddAD$4NkuV|g$*$BZs{x}fT$?>xe+qn>_7f*9WB+tD!_ z86aj6r*b$Oxubvy4-qs5cSym;YuD1gPM9wq5~~jy%{OICSH18YC1e512u@$U>zL-L|cDs$hFX{}2Fp z0k!|mDVxa@pj~>(-SXC>Q+Mr(|0S)SF&_JL8Kbl&?u3zrT_fA}aON28Mm*&|Q%owe z_O1MWFM$4~-Q4lEa61B8=@=F*L!?nu++b<30`3!eud<%P@R(LFZavVCUQ-}N6^?65 zW&Z8J|F*WQA{aKFhDeZ#L@yy=jE|I3{UYe$-gj#k`t(F@;BxXKd=6-#CgIMCcwM_# zqj@ma<0hAxM*pj9-d-NRqwb_HNmlN&C|DeJcD2H^J-I?3HTG9i{lyCA&UvIb}pNMd0d0m+PxtWis^9TH=n1w$jC3eWcUdGr9|?EpixL2 zMd*FSG8H#fg@L?sB~@s}xPql@#9KZ@A;cD~5SID^bUhR~hWaf#oaM|RUikWkX9!w-Qd06#?yjo}NQO@x-}}Ht<(tuZ zR9WK+boaM;i3lIwLe-IW^(J)hp% z6G)!_?((${k*b-~@YOS;xddogT(9%0(BGTBU2QA#_LdCm7z1qf(~T+Z9v&S(G=y(O ze17J>4YPeACE+;-$=-6l#A+IG~|_?!UZ_J(*}Ab)C7BGT3%;r|9lS&yPb+v^iI9`N+e_W2bGyLxqi`UE z1F;sNSTd$cJzj<4eWyyY4CxRURHrDZLKUZ5F}LqDF)@MfeT9U-nkx}AJ}&=4z{RDt zhz7^`mUK$){rvr&)hj(VAXuBF)HBo1TTKoUM;#ruycuuzdT!%&O`Dz znY8Bfu#>hh76$xTRoejo4gjp(vwv{5L%m5<*99ZRsG;A%B^H!!;{nPiFoWpQjFNqt zY6_G~qWHwk-RaD=_N^8Pu-iThwknzV_6_3CwyAZG=gO>&-6~11|J-JCvbg<{X!`b=@2OYc#K=$fbTO%zC6^&_TZZ!4|P0!82ktL5sMU|cQM z3|6!xTBp~ckPN`s?Nam_>N&1m)vmub!PZhQBPiP7^qZ@cZ-bnth+?JN;xcQW3rRVL zS))&;I`J70hh+Uh^dtJwqUSUbED8F}X^mO)X-19Wp9P`UPf^(LAS5brG5eG`DI`-G zFEz!=_fyW89IxAtK#yqa&2#+}-hmC`72KH=&m-6<+E0X7@5L!DSO1fo*3-Xv2X_|J z(P$KN)6KSh!!`=sNNiIV#p5f4GTn+0ri2f%lBD<)2}AdGFY{KcyE{7&diC3b8EP^z zG6-=VHT7Wz@WKJn1cD?U?FN5C2YJKd3uzinI)$NHW;dWvmo?s`SX!>+ra^JxJ2n*0 zn_V-Jk<|-krW+D?qY|QG-EPi0f7qXY7}J)FlEqzVyD8H7z6mLr zSA&!pr6#qB+Fm9*@8ExkXhzHC8#A%^V|=FY{Cqf5*e_igPc=&kZSq6GRgA3`xGM8wc2Jj&eU2%PH>R$aUC{^ADErsE z&dy>JhelXX#6aI*B0WE{z3v=`beQ5Os6{yfcW0=Af?!#^cIV#3THv+C08=q#Wa0J^ z^JU{isM$h&7BnJ@vhk(kbHt5JwlfWX+leduFLm@3(9G@u&-BV@=S&56{h|x^)pmms z777h}(b*hE&ku;!^5%(NN*E4juF~$T40gST_xT2utL3~Jig6EX+^R;E=Nb6ID|Y}; zhYi@+8A-9Quy_LZ^z?8#9~91j9_#)Z>Rr2{i3rVF`XnQ1<2z+E*Cwj#S`a|}Up^;4 zqVVvO*%EuCF3=BYfR=S%zT$GPXG`^`Gq=i|%-MXXe z6}6nL`O&8_dy(ZQ7zwA9M623x?{4-!y%4WWW$nq*7+F?#;^$8?T**3Q%?`O&Ua)HE zfd1D}J+_~PW2ZCc3ndS?<|JrZ<*)`4#Vnbs$JvLZSpGVtiI=aqGd#ee%yIjP$U>9D z%H7@o;ReW)yEwDnb{LDzXcdcS*qN)rhxQeDsl=cnc#7=CN}~m|f*x&bq6a@-E-tYV zuUMp9G6N}>LGlvQ8V-8;h)wn+X_uYhq~T#{3oV+*VcHfvRdMW^5$G~rp@)F zzcGXS@9+OW6}v*00Qo^GqY*3X2)@7eEeb#KYrU0=lwlZ0U(OF6`7kYF)X+OHwoe(F z2I(tB8rCm6EU#aTm^PfX`_^j~W_|xz4Z|p#y`nGRtD@AC0L?aug#^lIHdyCqXO;iAAp4-nN)k zjc?6HX;iL20g6gQZXAp|#EXFKMRTHnge$55!HHs(5=bTj^Zvc~5aLDDwgX2>M6Nyu ztR{(GI~9o`>L5DNUq7M03z0UNX%APC@984e{GP5FN9{KO!%6#h@ba5oWgd=7&x%!1%CuRN{B z#c$#nX;}^Z{|eRmAINwJpbT(&%Yv_+Q^}1-r4}=c>1=c2M4+Xt-xv8BXFZ=DR9-%I zgFhcV(mxX6Pu9svy|h`6>28T1O+ftw6$Sj{!WF%OM=6j{07XeB5LzwIkjtxPMk;`S zNcg!%9jVn^5mspG(B^w@sWlwz-#R>Pk45jhaI~LS^(OSLtaIaFlakDC5I>(Ro4tQc z78I9V-0lD#LHqRQPYWY0a%v(-L)TyAv=!u&b0PT!%|=%Q&CmtdfCwv06vFR$0V&KZ zm>CN6{H^)4l2B8ZkSWKM8E(_w69bz0p_{%32q-l2+gMoEZ}F_W;=draXx6xgjc0r% z+JFL54Vn7hi(fXz+W{G9*JpPxF1}zZ@zvIoTnK5T)4$ktA;_w0Qm;F^-r?(gsY-I; zfK!+Mc}B0Uv4+gg_K|yjrHj7#7&hFTw1uh{&0dad(xOZ@H>3BS`ZajK^S!H3*F#D?x8=@er#aZE4G1v zdjqT{o2-KvwhKG2IAU@qy`N?qFjWow=7L8q^QGOrp1e=>SeD+4Si3#1M+`R?WDx|I zrnTo%s}hwEi3`RGX2}B~<`?S`+YU`6+6y9YneDrbw6J33`AOdFGX0;QUNqvM4f&f^ zkQ=GTAu=+Mi;A#V^B8}RV)(i$*E)^+Iq%Ce3eii9BH-*zLcTGR;3VGI-4ZZF&c!lP+l_frJ;Qt)MrraKG{-LGTe$m^3YEY-BJYFJt zKKch6uUdQE`r&aZ~rCiyPr=%Hc%GE;a zzOFhlbshr;$d0Lk`}`>mgLpA4iM2LsIsmPVYY?CL2!q(P^|cMrr`3sg%CIUCp*iN& z=0PR3UA4nf3jaYdF9DAY3KJmByr3r+w;pRdk41}h9}~o-eh;rY9`j#ManO`tvK;Laded^;x>|HQ1$ zKkwGQ?>c%k`Sj|*Jp&q!J#l_NFj65q#+HtAby2M@ILYVzya32~m$8b4u0B`JXz!pp zB{@g&+lCSlCd?i1wf3$Z`kTN%bM^@r`?f^3C=W%|qO`fc#Mg*tym6T%Ws{{qyz5 zsTA{6x_$exTEx{PD=Aj~#2#WbiK!pTB14ybu0sGm<~VN$eD*sb;``{~q%TGhvnMy0 zaArJsMfNno2lb!i!jDikvi$48|0Ms;H$3MSGs4giF9ascbydf8sdigUe6#6G>{Rdq zB19w2Cp;^i#!Jkw<75P)8p2Nqp+BFfsWXFEwt$Rwwl-`3DLGFl>?;rJ3AUS+;^mpP z-yeZNKP-xAg|Q9}1#af@rxW@l8m9}hA^f&7&nziFts5Wfi9rDwp^|NH5-HfnM^^U8K{Z;^Sd&r^7|x7VnUjA+yISejwW`g!bhSoUM^y$u_Js8oV^N~8;8-- zq5kO-7TC1#K>rWT%dsK!jxHX7qsCD~alV9ERPF(s*LkIXeV0FScLFO|zY;)B>Hv~Z z=Zlgx<>* znH+-ro!|IHh%-$yP`8n%laJ+ZyQc4 zhKrj1!w$B3NemV}9a&5QGn3`&Ol0xZOh@WJUm)|DXi1REo;01U*`}%?wO-b&jw@cy z!pm31en0&F`OlsP^D`x9S|dsoEcXSCya~m?eI3s!CTcb10!CDAbC)OV7Z9ZAwfhdT z7O=jvv|efq=CGcqs0bTzM`_ZZ+F8v0>_I%OUQTrNebF>}0@#6O?+wriZA#IYeEV0v zp!}d?i-K=@wTd^5(zF3Kjz}b}gMMbEnce=Q9h9MvEpU27XsNrni|ORBJ!-8woG8+3 z!3h~D$ZNA)$Z2l_+?Tqy5pluC{qh$zSeiI6#^P9gp8SH_PQy-9-Sfix30?9nr<(r= zJ7l0*T$4){cVw#seTcz(J)$m3+bP)5S^pFyM9VY}lD0BdtXK$zk;cX}oe$<3j8?k{ zbE^0ISlv%^dc?$pGSMI4XlUEmd%H4(-}*;ve>owrN>8w=8x(O zPb4CaPnn`mQ+IM<1e1zc*P)_kO-p=YIhQKa--?b-ib+Vxsz*(fI?P87w9pcZPFJxi zOB|Zn3~Cz*P38#gB?*=SLMi_Pcq#o>M-_$1+*pzd0@Ett?x^*Dgok=M%pup(Dv_pw47K zJJH^uGkwc!dsn@(yq%Rvh;xV2ObMVdfea3WHhVeblDL~fO32FeWK1UxySx*m)|NW7 zjmr5Lymmg?8MI&4VKfsTbDcN*BrQ6z5;coY)&GL=bOeAglRG-$l%G?-m#Y{gDxnBS zd-Q|-p#532`MJG)NrYPmyrDe;?D^6r$gHP(LWOmkARh`KH)Dc*Ej#gYGD1W=B+n2;P zgqE@WSoz*;VYjC3UNK{LayZ?y`WLGJ0C*5;5?x~w0R z@$kQX)uRD2K<*_ZzWBwRGBfMPe_!-BA!=oize<+35{0ynO7rUi#MHM+Ws6Vhz`y;P z&ReUUIVNE)x6tQHP2EHLbh2@_MJ zJixwS7D_t0Y5CCp0t?`;{~{oh}R_Z58^i!QVu zDe~eg`V{fW44^l0d%W)=>9q7t(upEMTN9;CpiETWlcva%ZZUGgHYD3I%usyEeNK68 ze?82&ryGvzwYGRg>4PyN{}u@4Q#Hphwki(!I!9`XKuINP^g3q}K1WoSVcHWoWL`h_9B>*`w44s_j=x?kdd%#h6&q;R7DH_htb2dL@1 zds*>9lV${-HdOjgv@OqLi!%057rf+{s_dE6Z~t_PzcfNFQ``Z^PEzH*j1?wWN({|v zwk78R{2glP)J>cDb!>YL_r@6KuhfRgP@-n%%c<_D?<^N@A8E&KGOvXjpwwcvx)2xU%s+D&CoLb0Tp$Sw##v@ou$- zM=9j5g#G`|jml97bE5+P@uSk~C7z?>@UyI!D~(o;_E|4+df7mV!pU^iIO}yM|Crg} zBqFQU-&e8tN3cVW4wgs|bf0KB&y)l3YEm+`4~RZ5QmTMUpFw2 z0KW93@0F;*JrU7KSAA#3Nh`O7oGGq$g67BoD)1+xi-hzjQWbQ!rR5iiD!F%kIEGAw zx6gP{3)6@u?IGtXM3n5UPoR4(aM?TO*3)f4(~|bqNjLnf z;o5McVQJQc;tuI<+-l%j=&y?gQQMP9$+IGCHV4W$Fe->@^$o4+z`-!83FT2U$-lkq z8K!^H=F4q>{||WO-SxWiSQwynVKGik*v@tR-Mo?005ROl0W)yg zd(eRn*yLPv^pjXwg1zo~7EWVO<6WzJZbD|Me_w>QZA?i+@kNj@PMx{rN>gj#UOJtk zMq%IrlQ&vQ7P587^2uvcX8W#k42>l>;!r&2QXR7S+uiFSKel(#o$kOTt_{S~b~1l- zfMg?`sZE^V6O}}10lPNc`cl~OSi&!zN&PjdYB5u_U9qxppW}wxa9Q1Ji$&bdl>6`d z^YOhkg=fl!;;5_42*r9>R}xR(zrd`Fpx>WnoJMZR29=Up4|NW%{%NxOU}A0D_6dmw z+IZW5aYbioUBGsu_vdNpi?f$6h}AvUd`?RV*Lm9x!a?x@lmwgQ*;}gl<0Q8CGcyf@ zOyOqx@2>-0lP5X&_j5lY9jTeG}Ach3k|O@mE5k5z^5|qL*{O-^mX~agc0Hh zBr$Y0muw>8uN=qf{-(Cfz4}g(J45gDx0nV$998Gws}*` z1j{3P-R%N#S?6w>Zhm}AC6k8Ro9Ze1UkfRi##Fotxw${#0T$E7x}5G3C0Fz2rd+<3 zM~+fdk~%T;yPXS+42f2M?LY0&q&`T2)ZWBF~4#Yh|;) z-PYeTnEI6GldQOZq^gpfcFU@5yp{c4T+k#U>Wp4u|6$27dRU;uiDVTHInQbMSGqGV zFdT@0^bYT>SE!gOI*+DRjyhK^a-i32sFqVx8XV2xDwjCC>WF$R&5_;D z@6i9ROIkaV4FeyX!09}r{gYY9OoFHFv~sphnZ3dJwB+(UuPu(+gdx*jwH!fyFht#0 zwe9w@Zc-g3zNohqI9YiKeX~_&n_>modqwuDo6$x?-gXz!+wQ6yls z*N{pTWq=nPseskBL4NceGW?;?!jdq=#$w&S+~brfo2c!knZ_eDXYXXQ#Z#%B5dXoU z#HBUNxt-r!E2rI(F$VJKoT2e7H8(kxUYso5(Dk5A!&Y9@W2M z0myPjpw^@KpjRN;$Ital*&B~RuhBhhOzHea`P+R4IzAjX_R*6`pwxPr%$^g;-kqGV z7lBYaJ5cmlBvo?%Q&eI&RkxD-9!;JGU3Z2O-45ME4XLAx&+EHS#Qn-DUCbd;+1M@m zqwjeP0lTdbW1^#}Dxr*Y>7wx|GoJ5X7PC|-`Ya`|-@&}eF}Ax=@(J<|SE2PYUeyL! ztd+ZKp!D`8hipuB&0?XC;<9a|QoIr?kWFvLk(CzNB^yb~uWWf6>FFsq)$1BGy(Nm} z{C8`n*Y!I&_A~czYd^gTP=?HIr{1%B41ae+me1Y z0*{_$)w~hF&cxn1&VY?n8WB3?x|2P`wm$U4oX8k_>;3J89OEDFb~ytr={>s(zJ3Di)B54;jxo=!PDV!q4R zyS=Jy#OtVgUf{O+eE1T!F2a~9)mtEtB&sPh<54;b)RNz(>oy&EkC$ptH`2?gIOkiX z*SLMWl(n{G|C;dKR{V;#DL8AUXCnMKHzg;qHQI-{{>ilKu84c;~?=Wm$(yd9?SqNU$mM`};Sk zlDrYdUfoU>56tH%kAY)9vK1=FBfKd=4n4Wb<;1gI+$HvsPy#^xth9BJy{#PvmdQ%# zG}%d-Wvb)K5yms5ZsqS!z;K&OVm|Lg@hziG7T-LY5vdkk3E8)R7dw7OR^zoZE*p77 znv2b~pH%iIE!;8l?xb34$T9Zr_GaE_&~jEV1F}~ey|<{n^(3^FI1V9EnmnZ`Nk)Fp zzS(()|5WYf0@$~J3&vj3AI>iit2T-kkK(wiC5{_OH~G^b>v{q1AZ~|lY#JN$-fuiy zrbw1Jy1Mb5b!tZ6Rx(Unp+8VzdkVK_=8sv-C+8_lQ*X}m@yy2{wcq>*{r0IDm(OT3 zs?V`GvTgI5upz|&*>3Abp!U)oLLOnpAMs9{z@86qm5=FIo)Ns(&|`x~=I_TLTFPd9 z=KqKcB*Dr5nX9I1#;_vg(@we{mGC6f(YlA)pS9eI2gd8`kEI2-=I$?tIbC!gGZD03 z?OGG#)m@o#*jhvgafH=j3vr`>un z^A>IraFX~oH5y2g=3!XX25HIiA2|3ZLqRc6xqXIdR(nLK`g;He<5v_3hx^W5^MgFL9_Lxl zlZBrAcqaE2dxkgm2mNJV!iik?G13T~%%%kvfVD-}o|yLHS{hBxp(sXyjBOsrlzEQ1 z9eRs-&nc2)La}OA3Q>yK*c@DBMzhDx>YG^dQ40gj6RNl8>L05+{9<`)v-4>75J1Al z`Es_&NJq@#U!g-H`ia2iXOkkPf%LmKyVh|)7u>J@Ut?Ze*X!PWw7OHQg2GWfeSovY zeBJST9?Uq>$$JQOdjW~hZ<%=)`R?7&Vj0yoKK4j!6Z1@4-Vi3!`P$l#yKS{2+h3!$ zH*w~|y^xay<@dZx;HjKTb=$Y!K;;k0iPM0M*`v0sB&-jcIB0*(n8!vIt1Y(8qM}BV z|D=94Fir8)NI0OBj?B%AUTUHWcGjnTZOkE>)|rD}&Ka`<{iCp+M%5?9rL&Otz$@ zbX`{~XIFBX9LceYnU2E`pO25A4%=A1vA9TKI~$bjgrO3e3KLDf)%T@9i;`vAx7t&> zAca1QYq#YiBzakSZO*pOJ;*Nx?9UMiZkKL5_;netPu~ksM^BtJ-`=RWeeS5&B8b!A zJvEPyQc#MEpvpnYR~te@v_W~aGFg=o^kP+mPgy|~^cHYv^uNcSkjC45M#irL!{hTHT>RPF$e;-&BmTDHa z)|)nRGq#{n!5PP!mWJVbuPg1>2JI5hUxbQ5Fy(FmU2PWn(mKw|lS2B&EpWT)c6czW z^~MGcx>DPNUv=mWLk{%?v=~Ca{3Z|75RH#(iFqT@?wFVg{Fx%BXhn45I8Tu8z7cQK z@+qa8OSPCJOf)$ute?Pmw>t!j0Za_&@jp32T zc#fz2J3wlTR%svpwIXjz!@6Bt&xQ4(gNf2MV0!Z>Nh0=aw!CsUEcmkrIl;c-#C6Ej zVIKEwdiE@3__0!DvU51pt0*DR`ubLlM-M-}uWx=O0v!Xx-p&s0#dG{-Js&lT0}0|n zz5K*|NoSPzepLYyYp_88o0P0{2zt0nT7MJyf7(_Y6ujNXl5t!l5St(ke~MB~Km~3@ z_{PO+PJ@I~p<_#?yX7r>bll{>MZ4u`C0U}&O8`jpP}4VY2-rq zQ~gCUmh12~Q7ajASC`OV7WGIi6Ce+$4F>&X0*t@qe`{;nI9rt-^5WK)e%*|mE=X{b zls0#{O9wd~PUkABFHfcuiiz9UPUES$-XH06yjVOLwOmHfP8mxjIYZbCH?OOAwbYYT zUG2(|1Th$$>;L>=!6#B-^CM|U{&W8$s+A%B*QR@Y<%U&<<0Lk3 z;|xaV``H6b$+kj3f(=>5kD9E0%3We&Vg(&%~%uQX|M~w_b2l* z>lw^kE%P&Vlh&^IW+uC(7`=j4m@ymQK2IfQ##s*Z6XTydU(o zX?Z5aw-Ij#W;W%0503<2R(NF#e{Q+dXuq9B{VXXn7>8*>07irj%6j%g!dw>9@LuIX zF&ywJo;I5|Ee-SX&KozGLxkl_kfUa1X3omaJ~V=#exp{eF}#^%|WmUaSC-O0^JUc4M-{ zoM)Ty@hQqdMsxpHt|By}00k;6LgmMNS`+JGLYoC_aH$Igzuj0oE+ z1xrh{o8ttXz4Md-c=vAW{ksp|hRs>kvYZ(xZhv8Y_AFy{Zb+0%*e@j7fPeGd}BAL>;Ivo+`0DIUvbHAc`=@8w^k zJmdv_WHE}Z5j;(g$6L>SFqp?nAU7)_U+5Impfb;5YM{`4mart3z4DvUzCZHJ)y zRF|jAf{HytXq@kh#I4?#y+nJ8n)~-ZCY^(tdef)2>^H^L<~#BgDuzZNSVr6br@$Y1 zu&6G?s(0OlEONpc3s~+;_(kJ&^9LWk0Wr|DbZP6SJSzWroW5!|LCsILmyrBONaIs} zTTp?}bm1oz7PA4-`P=Jx@a3b9Tn1vqfPuKUcwu27EKcK%Z`K0%LuCKZ5FH5#2{rXs z+SsgT|LRS@Ha#13STCdqJup(7rC(E1^0DkHsv_Ul2T?oFBR<;S-(c>yUDYxz`Qm9S zE35nadtRb&^5P-~c}!6%Mq1h?;K55nLjxA%ba!_r7xJ%`TP_!Eul$c9%EWsL_VE18 z)@^hp*|XtOJQE%)*-?BpOg(}E6R&q?`*^=Ua=J(t7ZOfWCl`Wk zA}-q85ey%jH}^$B0bNuc7ObLIsYZ=?cI}~%Uo@B)CXuT^8|!eRS#LE?w>wvvU7M^l zRHn;md$$vqaP{lXBg)1eDR_tNB=RQ^&Of2!y)F;`t}LA&U{d|PnlL;YW2W`eo5wjM z;!VR%A}-?g=!%s6yFK;{dqGcwJWjIv{&^APvBKkspDV&dVNY{`RTEhgzfSpCa8>yM zXVPF$xCjmV#45?Y%UcUC)5jkB5zEZNqFG}?3JcoO`;h-SOBzKemcH+9yOKvBq{!e=ma|~E z@X%mHR?O+Cvtx9l0B{nuzo+~9RntBO590MjEPk2QNDOKp`jdumQe*E<{O>e9Rg*7D zng$p&ys*Tqrr9=kuomoHr!$C(lcN)b8ymt6?EuW;6G#)m)t1#onQi9-1rKQCFa^z} zS$7sz;@m=&Ve#6_E? z5Ml$YxIfV{M_xZmJ*C|86$dxXgg+zXq7#uy0?nyWqU{tZS}mHdZzV$}2V(Hb)M(i) z2}FR5JZiS@t7Q6nYi}v3dNfK^{;q8vZ+}m(&&Deq*DQ;^{cC*heBIBIA5?qIVLim^ zlcR-nTS&{um`Po!Hq63_yQ4?L$Ve&vQ;nW^K}1}~Zm%n=Qz0zcerV?T5&BORc`YT0 zKtMYLn}aldXim8>xT*})kiQxTCnLjfr|H%lB<-9ja`};`JR?J{YqN>7+3ofw=$b&Q z6xJcn{zgEwZWbIpt$J37lgPrRF~jr>{b!DXG0^BujHqBrV}jA0t2SnY9kj*ik>vi0 zmOoZkSFF7#5uWEJwSH3w$c5_^+qU!&2HIF?az8ROC!A0Rn~@W#L2taLPhZChO0YQe z#8E^yUB`|^9H)SemfurN&#CHAUfx&SI`z=j^2I~X2BJOZV&1hM|JkNPk!9N%!Uu(Z zeCuPFsxkf0n7$Lp1koj0D4ICBI3iF#zf3l_k~ie~j+brJHhWtG#8$q9jbL`)lrfqISB!@O3swMQhf{vksikl4EjJo}6y{Sr;>xw{dNhO*6^+!-zSBB^SvPG%p>RgGz1vE)AFR$j4u)e ziH3g~K(P*t#hszu+=h5aKPd1uW$Bs=8P2!YUB^^!ddUBceuzXX0Xt7%rd%)DgRT-|{}I6$T}~w(+peLzh~tr*BB_$1p~Y3xirn#`ybS_DvjyVtK#hPIlREm1nvm|i()goWNM83{A*Rc zwu6Vo*bkn3NH|ZsF6Rm?|Jt*XRhRL1Me=n*%J&mEIhKpxH6C(f zwm*&p^12Z3hp+Ab5#ad(fGX&!!Y zeowxxA<_*o2JIX%&E#u6tV<3Pl3AaRPD)iXl<6La6YIawP39nsk7bJS`VPDmGMiri z0y!z8r>bn`sK!V?@XD8sVBtBO^a96&_{*Cq_-fZdO72Y^61uFG3)NV8(uwTHKz+2u zlFZf8hLxkb^TBGnqv#<7M%E+P>mb04!n{mq`1XY=@`7%c=e8A*&wD87Q_fibs1;a5 zMI+1><5Q-f+RU#0{*J9RKNkt-2eqP6d4&S{N4x8Xj}U4xO`*OZ()Wtjd`dG#b+$M^ zR_7mYNu!Mw(qEGO)pUhdwE>v6SfP?!o5^CD__gSE$K0;+vak4HOQRmAE}M%J6LEIT z3bc15UkTiu>+afw8}*X~`%@xJfcKiUZiw%C{&0UsH}jmz4~ecgp#LcSA$(xq*BBAv zM~!y#sRGy3#1!hFSSOX0!QnT)n^+9(s{C5hL%It4v1Y7Q-S$!qqd?*0ACxq#uAoSA z>)#s#DzlKgFYn%b(yDV*!gu@dAfr&JBH8W;nF@86?rpsUZ{!k}b9wA}m>GH1Q|aFB zO_uY!nC3)uOA|k?CY%e)a-S^&ghO(^v#n3$9Y6yjIM@D!jC#QCRXqQx>u($5gfq|> z5pk`g7O^R^=MR4m+-;MtF57RM*wD!C6n{X-zhv<6HA0jeJ3&CPCD|(SFyir)GBSCv zslEzP{TrdM_EA^kex-H4pm1tdv;Rs!b_xH^03p$NGrmh|sC0gvM=8y@EaTNAnr?zK z5;v7bbc)v6q`*UPGh4N)aWjoYcs@2i{sFyQaiR{UE3dFRP-1(Df?$n9UAA~-Og@0Y zId1-^CeWmd1%)yeb4#tAC^`*^21^rZqk8{Zd3H6QBXZ%F$t8kbo-LXN!g-_PJ4&mm71ga>RvzYX5G|fvpMGv9 z9`s@sarCn06)O6Sjr2R7)iM~dfE!8?R zOQujNSYz$Ty!hap;(iQhv+Qg&&(|MKzdatV1vcI;A1q-2W!xCimy<=N3f)g;@b5}1 zKfEiyXA~$S$9GkhaKS}-&_7aRHg|@wbB@Uw+;{zSy_?!~V$bt_s6w##nk?ar^4kv! z^$w&6wN(*~vyz`1zVAZYDK6pZ0BnOF49&PC(!d~ z7E+N^gKNK7PE4S$TPkYi#BizCya>@cTy+3=~+LvH}q>~1@`HmyJXm9bruDqp%cfa$|sAEp{T`&G7`8hZbNIzn>tk7_g zAh^FWap)jr<}0qqij7@c?IDamrwaR&W!oB~}Qf7BG3a{AW zu*(ychMQUZxrM`aZ>t)zA*C=M`mz{)bWvn%swmy#tnyj3G(9ex^{p>vZW77VSvSD( zHl;qT(67KK{P4&yLV|f`cQ%n{;1}FYzTF-;Y{JZpR-P;$@ZEjgb>niAa)=WZyqgk( zy~2IJzESV=aep?1a6aERo0xDL`{&WH^&`BOzcoZ7k!pK<7vxKK77{HQp4wkGt#LcE zc*{gSxN!#WJnGqYG6ILMd~h8#18qrMdGQWt^?>YN>G=jR9&{e$2ypCv>CL(meyL?k z^R!Xv1BK(T!!DLNuYzS8wItOqZG!?^cEOpQ!AiGHrPP@>(q~bRXZG8!?Q=;3~roN$Y98PU{!{uh$Jm&mYe} zzbn;1=)tV+#efitD4XyieYGXG6I}6(%`#j5&|6sJ4-%AU8R+S!_NE-vooL)_-6&KT zew$x|olo1#JqmFQHALkE*v*&Is)?n{j5({Pv#nDWPN%2RzUZ6yBB#}_{>jb%pe;4? zap!^!zujfjCM>US>iAMCVR~Sh9>BMxV%+PPQdFw;6Xn*7K8=Iw&Oa;$1!{-BHh6Oc z0S`()gdo?(x50UOcRIG4qlP=6$MpQ-Bsf*1Q$a!IFx#Fo-}8Q@=+2zZtvt0t215e7 zsLGv@N;ua@Td9~HPS^yZ@g1Mzb79&^*PC*=-$=uW$X;7r2V1sE3&(VR2dY}f^F>|gp_Aecc9-+aA*=5IP2{4R?B@90gMs!Xa z`9daI;kDG5e$VB6q($)jhtbYjPcV24#xELhC#Pr=`?2rL5gc1l=m zARC@E40F)a%c`!n{|bElD$QOTXJ}-!jZ+A7b96XE%y*CFm8rkd3^B-2Pr;3;uaU+k z{NmW)(R~Z-R~Y<>Y6XqGC7l}7N>u1qq(dZ4ow7sr9aIx2gKQ&u3kBDEaEvacULRi^gqU$p{mq=T-?{zEmm&iryreD6ru7x38{2qV+&(8cd;Y5Q zw$65So}LQ5Fd7c$iPy}@OBD2D^KazO-VpsrY1e9{QVe`n3^L2QS0WWjL?T778WW_b z(W9WCw7Emdv>2UXKeo1L2u0T7iZBREO%0}Fk*`p=)3Y1P+1W;`KLY?xBE5m?)j!MP zU*-2Mr171$cK5E=(}}3JY~&44fi;P4gCN5Vg zE^bF&KV4c{+Ia>i#$~#&smn*n=&VJT}`?PN2rLhSw>x+GDZj;{9*2( z{816kM59~JHX!WfmNutLPni>i$i7qNbZ!h-qruJ;C`w^E-DN%*w|U3fRTMhImv?Xl z9WS1>$}uu9+JaSEVWo5dvNy}}Tl!b$6G-yWp9I^ddvpPRFpWcMrqlwT!dsN>7NdPW zZQ`*y(@W?#Vop_7Um10FGQ=~Rp0~=GInvr1A2YVTb`TPJsoyP zfqHM<=5J87Pt_B={8ace7J}wstmz0@AbI+w`=D3-x3RHXam_T*7$~t#w+_L&-hYcX zSpe|bD6mblS&`$DDSOM37df}=fhd}xJj^OV$r2+s%Ktt)hb3dba!q+tkXFS{=$rks zx*$;t`_knRb^p(gpt1|KcV0jhKc*0y*HkCbm#(T0?$aJd{-KrBi$@ld?I!>8By7=7 zW3@p=3grdx-Wqb`N^8Wb^N4c)EtFb4vuI%c?@NEU`NXM7s~r*zN7pTWF!ecL%}e@H zfmiYgdpd$A80mk*-0?&|zpniAc1Q-7VA0BOKHhx8Nf2$2q!4N@XtN!Lrto~$10zXc{$lPyhTs7* zcxce5H5B>PTbNs5!dmcwKT@w+S-q@PVUI`kp2rRVi!6oSHrjEu`prB)bb}CcR_GcB zYnBK!y=Y`We#Bg#$vj$0&k9r#^n^SMat!0MHyXnN&05wCJWFb3aO<6wEOAH?;dvPz zM`s2pIkb5-|1%f(*QD=+^l|uRB$lICEbU?Qb+GKdS@c+mn&g8N7o<;?=UBOoIeZ?l zW=j8)UAtOR(j9Hxe(Bn@O5b>Aw$)Lb|ISP7RqK7vBJ{Io(P*Yb8hF~<_rb^%dDe!H z<=pKc@Iv>d^?o|&-jWZmq``_ntKMq66^6tQy=cY#m#&RXb@^|%DguJBRoo1oBdwdrT0S6_AAgh?C> z1(t-&?kgn(|t2k}g^zru7thwIa5yD0yu zsVDy)w>as+%A(BRj*haW=@r#0WgTGl^lpozaM*S>^$dCPfSuMnrsjKtY7K;9%c z-}c98=7N8zj?IJUiBwmh-@-exDT%I}cAep^(F{6?AIRIdGCjL6RW}NJ)4>W2F7=&> z|5_G0`BzhfCm_$?;bd49qk~!U`zgT}I3$4mu|2@=pRRR;m!ngoO%e{BgSAu$+G5`O zE7v-ZOq-5v`xe(c(DP}N&=tGXC3t~zfjUvu`;kTFP`|VWXK*>3b}jGC?&i6e1M5LS z1i7oW-&i7E@2`%j`F!*$T1$ys2IXoD{XPf>fnYGXuTQ!sjh3pa*U85*kB-g*aPn+f zPY=-BLq&;G98x7&7;BeU(xBEMr8qd{ep*>MdoIBiw5FG+RhB2U4=P&IYt&LHF+bis z7^j8Fz%5D}wDsH#E4c;NTy;0Qo+%79XVd0!h>Q9c%~pP*8jUWkz+swxn2+z*rxOdSwsu|%c;~3GFQ|6FUzLym z>(KRbU$IB(FV)R%uFnhY;mKr<|hL(nIPli76-(=#o-1f)4>rFupeO&8g3`+Cj zs}b8cTDnlz+}8ueztaa9=z`d`Q$cX0l;v)8d+RtLalN+Hz0=-@&})lpCU8DRoCiy@ zYEOmEU}rZ+9yDAAgV$?UbqpK=wH$EgS@ZeAsLfWM&o6D;d%+anh7q01KCE-~$2e}7 zc-`tx=fTY?@{r1*9A1ts53LCp6**5k>K4PMf9D!UPh~ix88B_K}Q~pVn@Ju zr+eMh!Mki*_N=dH=7%jT#S4?N8qStnwBOU4Ohu@8wDrWg_yd;@Hp&lX%#@hb7=Y)E zgx6rO;%f-_!pRWdF_OIeyrMAGA2(MOySp6<#>2sYLJIXVz=5$Df`{w*Wtm8tY zLs1j&&3zd01UOm`Qa^tD!^g$tlP1A=HQDlZq!&U}HC`(Tl({&seo-W6 z)==nK$(m!b?%CF&CZDUpByrzwEDx7mXmuaA$GD2Zm47z#Sojeno3|AX&OMlTIEND_q)B zgRS=NZZxG59bQw}nkL$Io7j)k&Z^gHeH_4TXEh6DSaKD3-P(!piiwK_l(R1oq(H0< zLB#J!x}O1Q4C1mGHketw%-GEGk0L+7}>c3c_A=?>oBnD zE}DCTrNi$a5^tfF|E$ti6Gx&EvWR5?XZ|N8chSjGyVdRRazsGt;6P@KCh&H%d)dP+ zi}Ua;eh0d5*p1cXOl|S(eWmBnLmsnbW6fYT==ggt5nUq?9r{gFpfW+{UFtcZ=8Wuk zsaT}ThE66P&YLG5pNW-AXzFuTpx3NKgWPbkb{>Yuas?CQqDymcP@cTvejHpT>muJD(&;1FD(9J&%k0Q6eG8d=` zSh~A_@C=HzDd_NdMtGq~X%_!A*73BJ3a{c!gn=6jRQ5Z89b@Gwz(l@9o_q4qF|@*9 zs|8Dfd39Z5U`=&OKRd7$gl;fVxc~06^#DrHIFFH5O zM{lKx4$Q#X3PN_AW91$f5hlcx&lW^go@6ec&4PLB9(yc9#_C|PFrAV2;&y)5lP**r zZ4f48Qhiemq9bO|$64j%syx5qwbDKYefFHecPFlq^2;lqGf1B< zI}*1zo$hW95gkjlGq@!Tk22AShw;Vd%T<@gH@uK(OBnqmq}33~zf2Nvu|XGofkvOC zDF+ejNzw0FfC9RD?wG@pE!{QyS$D6Uk*gBwv*00f3@G?BJvMF8)?*i%^)WF;h-F{1 zBPr4V$y{TzU?hcd9clTPTKV-r5sMw!R7bATuNWWDs)_F)=gk-oD7Y*em804VwU&gZ zK(Wf|@Ka}`;qe9d8mT#r?!6RY`{|i!=AoeA3TC#Jn%{{-HFggTV5-UE{|MXJ_d-TIi%h89iS4Rwf4)6zTI8K2ede5mxYnY{|Bnw z&l2G!%A}v+bcg>{k2Dp&xN(4kCSnqeAhT5JzfRW5U?pEV%v-Z>L$taCt?bAoirNbb z{y#B9KR8v;Ex0v)90DSjtEy?;Kcjf3stznj&98vkyKPVvJJ5*K%{QJ61)p94hJZs4oyC? zRJ3kc#!JD%BhHU5B;aVLw^qEDSf}0XLPkR6`**}>TPhrm^cljeEN@S#8)TKG(B>p{ z`j8!~e<54aZkBZ>@fk#97i-abHGYgi=EKHg#zoCz(pMJ|F2;-N=Q(sEg&!U?Y4k+}Su^ zZFk3aSgKID&)HMRB81MIbuL=&o;E{rLUzNDc1s|WDyEdj|FR0TJB!+JIIev5unqO1 zd%|~}=Y0(R*a(%)f&DT3tw-ZVpFa3a)Dr=A4+Boj!44yte|6!Ou^31rD!d+8PXO9A7@0^N9k}0f&J!tSPxF+lJ)h<&qDs zU;>t^==3?2MFpItX3Y4ns2`50@!_JY)=&<3lPzlQJ6qn~IBo}hEPyC}&Zcdv$#SvT zJD_;gx;%|Xd6cbh197Qe3j^z zlxWz%o^qf1LyahN(>uA;jL{vTg}tB1#@OAImtj`De6vWlc;HxCFT=Hf@?>MtXq90I z?RcvtE#8j#$?i^1fGLC8s#WJQd>XbK!@oN&mV9y83@;BeGTjNxh_psQz5o<&+8Ve2 z?2E%uUCuu6;6aBn>W(OP*^12eR$pwhf94Kk*O8COyK~)JNys!Z&!9-TSA}T4U)#2_ zrO>dnJy=y#w;S07%14QDXjqXDuYBQR)0_RO`SO@F7Dk44g8x!fwV<=9?j0#O)`>E3 zzeB>XXjFDPIoTv7!qe9EUIn1sx5`aF{P=ozPYYk6m0=-|vL-Dt41vN2)1t?=Z+m zg$@Ho^Z;x*glB&NDAT9JDwbY;Sa|Ic)R^QyaEe%XJ)AFYe3=4Cn_J#!FKzl6T+eyEaSXpE z9n2v>wrb>&=FK>f6@2SO=XEe3=B;f}Bh=%Y+TLRnd83E;4vdDHd!siX%Ux~AtFNPI z4b5f|SOE5VmnTi*#KuD~QbC<4gjHe@TFS+@0}_r8n8lI)CZJtw;2Ec(eZGu|l5j`P z10Cs%4SbbLiCBgH&ZE)C^-CAp<)PdFPEezksfjUVR1xrXspX=b{2VTEz%V>(?spMu z#b;EZ^qNNehmcQFdGsOa9G+Vx>XYp}ZsiLW5PSjew21V@o9R7)oxtCu**+q7gA+Wi z_1_k=5AXL&7D$w6WShW!xjf?Hi0|w+2*MinpXK4I&t!s_jOpIugwI!)V5gS$31muM z^`?cxgzW%zH*V>k-WCL$@*k@#D@jvH^KL@Ic9qz(WQ_LDe)Q&vFKQBXn9W;GrQoi^ zI~>19!yB)h{n3}6$kKv;3;kboePetb&HH!SG)tZQQbr30bLb9oykmSK_MG-NHWbTl0uEj>*;4s9Al ziIQm+VXp!3;m~DhsB`zI_e(1aIHknRMapb%ux#-|U0^S0s3Zk0vVFZZfv>3Y@mIr8 zQRIrORK@tS!J(zA=h-mkGuY%^nyOFH#6d52<5be~(^A#GRgLLHv4K+0hgSS3FPp~^ zkeps}Vgkej;;Y9x7+@XsIMr%I>aYke(!_BslLneLO={|GvO2CH!UFlG`(<+pniaI8 zvV67Ra5jLwl&0%Hj5YZk2B+z#89Zu>jbS8!BSA%ZG$J!GrTMZB-9wr)dOh}a&J-k4 z^lD9dqFNi_%Vn(|O#M~rxs^AErr_ka%eftQx|*V-9n6WM_*IU zmkfk4<<|1cg|nC|2a5tb0eH9Tw+?5-_z_6j)>c(*reg4yAdy-%i=N2qntS73S}KE@ zN?IQ*<}jzl&5>b_yOwA)j#kn{?hNKx+I+ps%7mN=tGW)+F{ZcsonVyhuo-ZmMDm_g zRAemiBLePJXhsKWmT99k3#@X1JmYTmNTRl$;UNck(`PvHa|~if92ig=940=J$lp(c zION%uwbl*j6a6J;^FYp=TgTwzhLZBxzs$~21@965(!}{D;INdQdmL=k(=XzsmZ+KO z=)Tbf@*tN(6wVNnJyV!(14La61{i8QjP2iu=-Hxmhf2kVZqHxb`7Uel#Rza3uN^+8 z{4j|r+SCCN?_LnZ%UpCnMqy(eM*6F`?fX7GC)l18gnSMc3-(e~ZkE{my|BS^bF<~_ z7qZc^bxyz+6?LPUIRDqH6EEuF5=j-k?e&W9Ugbkd$Ln_A@!0fD3h+9QRzY|ZO`a~s z#3W38g6M`PH&$g@wDk>Oj7Hm=bi4n9w`Z`K44-^4j^|*|y zUfZvm$Xis=FFF8|6Y62<6GaF8ioxZgp z%#YE(!Rj=gQJg%c+nF?)`-4;M?663BEUUOxQ9}KeC^<^}v0zxFzXngj4yweM|!{yEwU#_{# zs+GAA&aI^@wtH#1=J);uJv)91vIwmG`^eDB`{J(aj|s@0n_iVnvRp-~4b;rwc03iS zm)(AbGvljqeDRKbyDsz9c|O6qW~%y70iu7kUei))RRhI^pfEf??5?!;(0!3cpZ2=K zJZ3{gs0eAlBop`_ZvpKoiDsUMD)%{4^I=M?&a;NuL(pN)Ys%&U%Qg3UhiIJ}egkm% zs_YCf9_*}`%mpPHf$CYL|DeNmHhbUt5uM&RaYkr2AHC^*!ocMTF`u(K!Q}HrKkWL@ z+N>SjvQd%3FULswK}Up3oY_s=ubAeUIj z6o&v;Yff>7k|FVURHCnIHsHv-;3ZwF?ngN+tgcmkn(uaJPse(mrdLCNk%i5`)Ai}C zuRm$o0mYFHj)-5(FDF-|^gLjt|Di&q2vuy=>%^(Of0TZ3c$kiI8d3%I z+uu7q-dFK|H`Kmzr~a%Ft=K}##U>|m!2-vzu{1ZcCPiC&v8&wC3tW{E%6hl3Gjs}* z?qOmoo#`rO3j6{EBd!4LUh3#+WyAtEN+WK3CaMtxITIcCyV4>xZ0v67uE~5`{D28ULPS$&z z(SJLh6QL|F8(IP;5F{4c-JMevF`@1|-`)beLT5+xfV9Qp!c%+>UttD06s58bPy2f3iL*|)e450i{?IR=t@;(<~3$@QmypUzXhL|(0r z1*gVf?23Kc?WfzroFk`}4;evRlYmx0iVVi_akKQdEJgs{E#iq_w^OIt?fy$34WC)0 zAc{YJu^izxptv@Q)^2)km~WF_Tc9gZ^%)^nABFZ~)mg65%kcSa#{J>PoaK!=xnCdY z)gkh(Obuk(&5Bf;$7N@prg)jtb$W-8E>*ynj3!HKIcJRW=4Xd`FUzD)i@AX^JxEHb z1*}$F+yN|^q%uQUrH*rA4_jW}uAH2lQ}Y~_F#W^Nq>68Z8|vJ-&ecEAX9X6E9sq^St0dPQBdV3dMQC_CyVEf`?8Ncwu>T;7?j|yb%iTYXEsV{-AI`(=< zS|L6&nYg2QDsF_oWLhIre4i(&!UriD15lQ~+7l75*|+vGbPhjU{_SjcTjrfp6~?;v zk#l#$VWvoJKc5>k3H+{MYrE%6%U$tJBC(TL&crD)$pmp|Sn2XE3q3x6ZZ%+T@|7$%_yCj;`}mOGvYByks=mFF^WxKYmrj>SSD{AC)7|r zd8Ox4Sx{=%hp1kBmxN@ZQd&ZMom$(BPgG8xP+U@pCG6{KE3Esj2%w}<6H%TQaC=<~ zS5pk@)!m$m?a=OChAxeUmehJm>HDC=qg|XJSnG{&mXX(XrZY@LNS;rb>nAKc9SOS8 z97M+)pr3A6t{yo4Xje;Ox`TdbU-Vq|( zp9`19jq*A@S2of30JxTuVp5xHMrlYCNgJO)51t;L?hlRjRP1*fGApJ%eQR3AzR*Rg z;RWx#=-a7tmGa*SS3{+)uECkON2*;?yoUf%5k-1F8akrJqp$hUA^lGExbBYp;3+so zO#CfDkD!I=trj(UB<~Z8#8rfqyQ-VD+RShcB$8gq`GCIp0xZUpe1T@w?&xKxAJ`Un z_xAkg7M426?M+G1O+!d5Ny@0x9)mD6+ivfn`%2%-(wghDe{?D)1X5P~ni@dUV+?Cjz`v6pGCQ!=YD%=S5hv-sE)Lm)6PF!{> zv3azT$D5X0N3O+1?mlh*SU_!Ba<-NAJ~iS82a2J#1&x)>&Q?$TtU^*Cr{fGEwg3Bw zT&g7rD%KJ_OUZC}bU04`p(RUzuP*(XC%{B5hX$UYhC`X#yZ8%p#J#yAA=>i6IqL@7 zY)04^2{3dbwcyCZ0mA@XcbS?|y?BJ9QRU0z59VG)kLQ9CXUK zn(1)V(CT;Pf|jtEkim;hahjifq$>F7Kb1Ow{Qx>#wa3MX<@!^@P!qjJYnsl!YqYX( z^(W%v{<*797)?=H7>(xJm7rg|ZhMWFN@4k9X^qxPwTI`&Fj0zOj~}sv=$K1w3*JFR zjcg@GNfAKdwJR?2w&HWScnt7S!OfWYkR6&ulMTAK{K;mO{vx_XSd9Cuj7^wU{zEzi zz;)ZoOa*&U#&R;L@K()R4-%i&=huQ^6zrj1R z@3CyMj0`ue1;uHun)xJRk$H)gMpr@EuF-vtY2_{zlOacvtC=RKBC_cmRb?LwgXkhO z!r7ov&@^kq>GjvrewY+&bX~(^<0;_p+-G?Dt|B`kT8c&1<2tNzU$lE+p~0Jgjh0h6 zIg*(RQP%|0mOme6vR29?ykYD|zOGF9Bt0~hJl;E;)*~xWMw5)MCHhz{&ZfGMQhStx zJtPBxlg{oeZ>LwO(Me>Y*H7E(v8QQT1976EGQJ-tT=dAXF1K*8u*e`x1nQ5ldRLpN z&I#5WwnRGw`)-;Tq&O}idIQji3hFcq{pBTEmyDWkJImg@;Ul{cW4Lr9D4EYb3Ny*B zCTpKn@62*s0b|Go;~1;O>Op5MUnpL(BGcps_+rX_Sd(4Yo^0yf_twnyRf~DmY-2YZ zejN*nvL#r&vGWe{lc4Sd2)+JJ)LFYs6?swE?KS1wI72Dzl3W|TExHkjjVm*ub4UcU zo(?st?8+|}hBwjg4?k4!orX${=Q@pl2`y0E$=6-Eg`Y|O7M~NMKhi}cp+0ei_u6qT zSSVc?vpJg&^zQLf;o7e`*9g*Hj7B~Y7ALIdpVpE(~OU1D?^y^;9#@se+%@bFdX z(FM_kW%06@m3!}?dgHg%0@J63RNZzL3iYXX1fgPwt{;NCW5Y6gYd($)V8CKRN|d}7 zw9fVuTcQZ~c%7Gke&*$xcX(3wv76*);_Dl|&J5O;wJj94XdB+MA5QxGGT*bA#`qud zNbFvo-8g-$GVCp_x~X~~&bb5@X35aJ$5~uyZj9y+7pbp9oAwXh9n$1ZPEI|Cu#?7< zm@GaPGnmaoWOkPmFsWwmehQ(uq_LN{AK$qbiZ$n2QQYeSTo$R_W!o3Ox^``D3QqfP zTI=FNwTkUBSAAyK9Y6*`w+Ob~!3iqhgp!&Sw!Wh`>D2w~#zY~7ln_ zu9+-S0$^uC>{A<6p31?^wX8SUsojezx|%|( zq1Y-W;<08#;@2G_P8b2L@@qzXMPx3&u-LTCrS@`Uy?72?0QU$?^2iD*o6u3YL`ex0rceJaI*jVc9 z*j2G!Vy{mrf?JE|mlP%o{g|e}LYLHHUYF(G(z)E|qfq#noiB-*^KBL?brjH`SPFCPRsa@l zK_y9c39^%l7Re-%8AlO%+plRZ=2Z%Va8fYDm4X6`jtvbZL-OP^k6A&ev=ypW5Al7n z$DUSG2?+@hap-!hg(#nO1gNN}j#j(%HVD`jg0}6QGe|Rb7_RyEpU4_oQP9X)63Xs- zC{SMP{{BdqqeF}B3VRVn zHdATeiY@FhywFh^mfH-D5L9k$oN%X%6bgk8BfPRS@Z4k1=3!T+=%Qt_@A6TwB~ zH8dE(J)4d#va+&pfy5*vJqrt%fuH|Q9>8mq&DGjekK$^gs6<7ZJcvO#rSKHYw`B3Ny6rjQ!)9*jId`zDM%=Ld;}*5lniyQl{Ffj1Uv#JK3; z`qZJDdIiyY$IqBYe3e{N89<}aFAyd@rUP<_K4h8USL*0v{A5xyk&Uo%q-A(0q;VpK zhZsDuFI6y|j%P}11>tt#sfmhy^dSO)Kr@&P9scq_e7|~t2F0%fW@M;8DE2+UkwCMN zmp6d~<##*n!SV;;S<{33SH*-FCX>(CN-8?vT@+MBRYf&WU~i8%gU7F>=gdOoKWQ!@ zlqO}d4BJ#?peOr70`z~!NvWnCUdgmv+6`rK)}CXFV9w;&7#$lM8y_Fvj~@w{{)Qjy zzX3Rrn}9xz7PT(MvtdYhaR+0&%Y3IeNOAn-C{``ax^$|zL z7<$K$`sC><&Gf5Tpm!V#1lm7-$Z0E;f3umfbe2=dnnSPU;NCAf@_Co*ixj&CsLUR7 zIECo5Fu3>sE8)w;dHgJ`HEzX0F{v>L;#KV9eJup26Ne!lybuFn2yK>;fMv_yU|^8^TC#V6$O6aPTA*%E8~Dg;&E39m}qIE`oX zvJLe70tEQO@d?9UAd6f6l=%$`t<&Qi&g+j=JgC`&R@M|&utgMtsslnyBBMqc7;fxZ zUgYjyOJIKFGKo_1C=pdss}M_jGLCXq8J^^rxg`hywwwn?xet)r_M-c5z+^AgEarVv zSpOQ%=u0b$_2c}Tb9?;Z6Y=(?d!k4+a_{?Uu!+@o9UJ74e3hh4;khZ=8?t|#9o5pJ zZy1&Ykx{ko?BXMzk7^LDqs>04*et|_=nITRe^g0}hMWbJ8dQpFny3~=ooa?1RC|Mp zhxpIqm;zY*nbfhAWI!2Cc`i6f>KgbBgCC#4_}b!noKL1(jXa0;I+_|@FQvY#l=B}8 ze$skLLyET4A*j#tXpXov_Ab`?uTQ-tNEzWubom6WEZDG%u0==3h#e1da?}Zg4n!HC zsq(A1bVC!bWtg}JN(KG2vJ~qBypiF%*~7DT;EJgxWH@-RwADrxeTp{RfbROI>YN>9 zHvb`Sbv?D*=cxBUE*n>YgF9_L%Hds3Xp&Ij2rehL5juw=R+m90i1_z#>XlD8Y!lN> zUEi}B7eiB2%UHCLo>V)tB~FIz5$0hlk+=LzU3lg>O-xRU7opG%R`HAb#L*2(5LAKF z|ELRNkurYjjg^+O`|c9P@xZHHWAGS=+*}?5ZOMJVUS=T-*XG}K(vI%(;NiMhU1n;3 zBb!|0_|D3&g-2>CJX+Abq!|}BoZyu{lU%(maUuOF4ttKCXlwF`Uvt)c|5<=a5$!ei zU)mkLdTohE?!4DEOotr?O&&l)UkNq5M?7}$Oi(%@4ue5~3HTY16cdwqdfJ}xW(9g$ zZ#*FfNw9)_yo1*6_hi6hqh8F{Ybhx7FzFJ&=QjcKCDcPr_mIsR|3YRc(shGK5I$7Z z_s?IypMQ~hy99QCmQwN1ESUW*iLek`=;TlRzyA8&6O*JhBfnhbCk~mLHe>Q_(7UQZGcD$1&OIP4lmI-)tAQLL3#6*P{arGZ zy)?ho>L1S0s*EK37gt62h!XyaH%m45B3U@uFi5YuYQOjO=2X$J~<;X%s zsSR_KA=vS?Q^^nkK&Q@^&p-*2ueSdILt_bGm(u;$J{%{dp^NGYF@n{XyLwZUL)D>P zY=iVs=1&J?YZc1^QU9F;MVZ>JJ|zeTr~V8s9@CJhk2_X>{z2>eH!aI85X>Bq^@pe- zDD(#cyi~ie!8Asahjh2*29~BZRE+ondDs?>N?i=#j9yw-yQDVFA?%ZEo&{Tf+Yao2MAB@rIA!6sXEte{zsl2id3fz z$QuiX8w15vy<+~(LDv?H;S&oTM}t$H4g8Q7L8Omm_TPhkiy$>K?85QRkO-numnS#8 z-Em09{P>N-DBfL9p_d_IGxmR!l}Xnb_St}L2Nzn0AawM_X^e2C+FsKX#WBI}0#Z}y zT@IR#P*A<)73`*Lgd;@;5=9|^^kg^6igrVRK9x|w=098`6qMo2`tApbq9$f>2CI2v zbvt-Bo#y;~t&gqi85-B(maE3YLP7k%KNTdXpRo1UCcpuWwj8;?TM#aq z^ak?HgRGfqdBgdGcLexlJufBW4X8gTXDm<;wB1a{j+f@iyMUbWf8PnHNs9`@uam{y z&a@I|ibv||XqAFWweQUYuUP`p1mQn^OhWCYG>lWlRq)3^fvR6iAyWt__|sN43I4mM z&hCv`ZVyAF9-Ai(oWB3hWdsTtT_vb5nZKt{i_QMmET%h@xWeYU|G6^!96ur!rtP3z zs1eNglsj8vZ_Y;JTSh}WRs$!Y(M=ckClHzrkJ#!-*ovnof(A;wdb$7fe47#WibF-5 zUXKX;ffj8wnLb~Ws;RsCbN_#R{BvDuK)9SfHUZGrqyMHE+zk7E%_h5t3^w3DRef=q z*&GDuds&42k}c`7`$Yu?@VMr4;I%=2O{ncv zn${6up^+>?7K=`8%lVLs0VVE&M&adOt2k6o6F|H}gO>U;Fqcvn1x~d*@B_-17f3JO zaFd#+&f`Ro`}2+zFO(kk5?*RUk8c~DA|}rrUa{lTj`%Top_hJ%;u;+eTl1H`^_iSG z4%U=VJ)roxf9SgFk{OQX7a6|D5K*Fi|g z90Cq1kK({lYOHhmqRR9(Zo;JK$M2V+@4C_djR0qjV~1H4W8nGZLiH+IF1{zttKDKJ z9B!CxK#;Qe1WSMjA3jiK2u@sEpoON`Gjmx$o0Ev&z?OaRiq(XTJKHUFq>>yY6VchMIB+WeZ8!^Jf_$J z^oqv`NAGwt@f{@28Ku)jU=+~srIt}?a=Yl0Yh$eQwQ@{hRvgr)0)hnWp~Sbbpb+26 zwT&1Hz0rNi1z_MyAx1>}69QI@S5DQ+_&&`}O-?|jz3x)FZP6H9&?#9lr@VeEYzjMe zxU=G?_Pjo^uUxQj7{~?|aW!NFJO7QX17XVE%-AMyP8%QW#A^HXZ!Q9j>VvM}JG(mI zhq3xBX#B1iLI>EVdzh&$BJj2z^!p2bO4WO-2fKGx+o{TtGU3AysloS#6~HJoCy?>Vd263eC=;FIW6|y47{{S4RjZ=K zGB8~CXt<O>HOJ7eMm>%~L^$EId34MW%+$ zcjsIzbsiS&FvxdKF^NSQ}RM_30;QHa{kfd-Lr#+P~lM0K?y`jsts>nw`rvIpLdoN|hnDnzc78>ohE4tq)COidK=xG1+V6$g7I!Wh)FXpxE zL3tra8Pa^EyD5HB!?KTjNz$W-d_VaL#`lPs_wW?_yTlKaNsJP_5b_i*YtTB02{t!C z-iAJ`dDso}cxP_)*C|!>94la-3O+W=X820Hhd&=Oa#sLT`FjSHH!#i&SrJ=Cd%lJL z^ovRSQ+e}ZIvtyf+g^KViC*Bx^-u|?J$2@NuC&uuZ>N*2Dhv}n)~mZ-X4Z(wm|pX2BytM{m6Ohw5~jR3dr_kP-yR=oI0h_2_p@<* zwxlFZ=+hN{I51xQCPhMJ)6{SnAIfWiLs{F6t8L8RPvFjfmL%L-M%v&~xK&!}BCY20x+1ArvrzDdVcD*ToX4QndOxJDqZA^Db$j9yd7ekG>Qnab=IsyC;t*@d+y&KMAE+ttU2@JVhoi*0I z586rLKlJ?9WV5tVT?1V{CdwAiExn93ZJ4Dt@BP_fSgd3bS-$2J6i0-pn1v5H)B?jy zsp>jT^8a?*o(K{=t9`b9$p-WsL8IJ3c2l}eZFgff1UCG=eoZ2FLWxJ;=NsCMDf=vZ zemYN#M!6uO(PmTdD-!7mp!|!STDQtOQ57xK()&SNV_`E0h6bfC9`Bw{n}@9P zM~KFy4cwg`Gtl=Jm^8}~rWR6#<-+(qCV7{tKE^S}JXekg+5$}^HBsn!&&WuScXC}U zv!GoQzE{6~Ut!2Qu+YiMedTlqZPYSSD#(no@Sy2x@vAvZe|v(ZplYSZarbo#;u~ z!$yrhCN9LSEIFqPn(DnGVe)7hG$GKLVa!RD<;rM1_iiBf1r_q$H2xK+N$H^98K^R@gSqndK3I~U1wD6nIPf#fuAkDWO=o6 zXL42?xSBk;9ZnVSZlfB%`&l%=t12^ zfYl7u^xAb`D*@1!0@qmfaZI(mbff`O5@u~mu)FAh`UGy^cP^dEiM+T zgUxA)al6;BK{c`*;cSl2gEfE$?t>{JYdWjCks`^?Hg(`}N$G`$HS?1>vf;vPO>^{X zVyI`u#JNMw3Uzucy%BdvE@Ry)BN?)}bk%4ohtLoI$KqyWTuq;}Mvy9KlMD&6{qXRC z+cLA`RWO;p?1bl--0^YsM+Ztq^;2}iOZPEccjf#%J;tIJg&T_eiH?7LsjMu{gTpXa zgUkZ{qzT*aRL5ZP=u~>=p z2};TDCI`DVysfCf7Fp{DB=Nm0wOxgh*FKFr&NGQn>;!hFJLOy4J?(TH!#|F=$~A8s z{_f1)Q%I2^Xl;9Fj7R&NV${H(qxqB__{Qz5J!J;$iOJmofTRARrUVqoRrK=O?5eS3 z+1-55D~{9QO5%mLnLGUHVx)8!mGx8nsN^V@9VImz744F$E}>w+uGBDRxbh{MbucTRnao0UzIv>5K~ zUal@e&q2`b9GN(*pcGgq$n!2wQ2fAZWPh|i8 zNVA_?>I#?}kUgU&v>+XaD`=BUCT43}bU!ol zPLTGmGy7JQUxAV0n?Z-J8GHOD?rP1=ea53!TZqNkdXRkFyhX9ZcVK%p$GtK#1Mty)xO_AY>4zlBVo zjLDpf@1re3&q`5Ag$6v&3P#~#CbRPTS-S8$KKsCICw=t0vrS9HSX^x9GTnzDsjY)~ z6ZPLe=WJoZ162Og>^N9mFKkx+R;9@l0g>tftyQMt+_k&S-H*-j+?PK-$vB{u|6W6v zqH!Yuo_w4*ATb+<9mhn=x85b4uD;JRv6b(Acq#-f^F+CwMH`p~M)3>Vtc@~<{@{ZP zf%05m1*EgD`#+ufxiHSQY>_l3e4OgP zlcQtZk`X4H4y28j`+F2dchK5pzDb242y{~P;BFgVr2XJIFVd}2O$w2dP+ZszTr751 zt`|l9!g7yr*i{0M0|7bDRXm&eW<<0cfWx}S%~8fE*WpRZq`bQ3)~umfK6*x6{(~?& z^I;&)gu_ocgeotUkHuOt3-ua|M37uxn%=6{b(IDW0~5ZO{%aQ*p&zDBi0{zn`LPxx z8x|MS$+!5Xx_N!Jq0Q1A{EH-_3q&3nBij0eZ-Rx*?(yp2xv97C%7H`Yalr+oZns00gb}eRBgCof$e>j+b z&qx+_AU9e(@RNJRQ?7pB$6v;00Q@E|eG=LK4Nk9V4!>;^Q?O24OT~ z3)MW)EW;Lpv^=V7Yxklx8anO#i088+3?7%uUjn8$_GVTRwZ=M8^e~qg1RyD_%YW4CB@)Q*Gh?DCHWq9gs6T=Asv{ls7JU)Uy8Lq_?=TOE35x+z;j@n1OFTGR#nlh=jrUON zRebKFt3JtX-$uo;AP7pzF0C@3DgVyn^X0M*qm5D>KcQvzx2hnl3dg;pK(FyPu{trV zP+;-pvTMreUgt5Dr21Ii9+Q5eC<`y;-gqwl_F-q{Y8VC#A#$FkG%4?GfF|rmUfHOH z$)s4a`A*t}VVQ^HYqZ(r3(O zDh_+?@)&z1yh&DcR1T6hTky;}kMVeoxt-69mw_7H1pM*B*KJjO(5U@jZUcfWZ&h zWB=tlN%12uhI6)cpFiGD>mG#5eR%lX1sh*|+$rG*+kPW($pv#M^{X~KPkR&jJIu+s zn5_+CLZ5D>5^C)n#}7Y?1%-=Vj011|txUw90;QG|Y*0j$hV!?J{9g83d3^%TJAFLV zy`Ffl*Ag*1rdE-q^=0D<^$i2)fqqFcAN)tahk*f|$BC!OnQ%Yta{k5c8KW&lk(}9e zgR9<>MszqAd^YlQ>vlGg`SRrRRwoggzI=}2^U$tc(rmYRE?QZ?;r(};T9q1IBrfj0 z^EkY>Kwb;+$Lrzv#%zl3^ZX+}yNLSX?kJ<%M_gkgf=CO2$?WLPLG8t#*^| zupF>~n}c)ww_!iv-$w;L^Ch7rBiwkl6GRWpEgu2rIz3ykEPTGv8mxUXT1mTYyWIDk z*Q}Vxn0T1x_7ilZdM5PfjOsm)DU-O#lUrSn4Tp7>eN_lbSrsdDc#V!3ys>|y!iEN@sSgmj!i>r8yz;vhkgT)g|Ay49b%|zwg=9WWIjbH?1Ejk?XAW zdXmG+Ci>+1jNe?cL=+3lt@EPvg{A~NF|KKZ{(;)kJVn!i!IeR0@j;sA{2+}^CQVZ~ zW9a3gXY9E)Kzjx#TYen36|lQN1^r#XWxfiiItxDMy86Z^hKS1Mb?5{;4_B&k#>BDk zd0h4F0HtkO4v!?_B-uEz#LxffQKF|WzCX`n3|`F|V_GfKO|t{JEGelZx~YRq7)nE1 z2}AYt?h0juK=mRYo1`Us(`D61ZSKb9PV64+`VCJU?UEQ+dkpk1!no8s3C0=MSZrhU zY@(DAqc!Mx8jX7tJMPoITWeS+3ZwRaRB-Tyf4o_`DlKaS_r*&sEUdjmnj4(%kAjHa>H)IW61R-+Zx_6Lh*FrHBeU2ZLJ#RKRhkpT zCddZ#y92<*JIf*B-xK%DPuE$|&&n)!&`k$_0WHn2LKvEFBsDl6(!bJMqCXUmk9y{Y=XF!f9m zN=Wz~!rtWi8~B8wWfxQak8ox%{54}hc*(pq z47cTRwlhRFTED4yeS0Z~d%R{DDGLjy${;oeUn-J4Guwd+f@$8iATPo65SQG>$I`mF+7(rF?M8$bB%~^pO~E60W(d{PHT~e zd3Q@c67obha5MF4QSUHKxjp&1tnEK*H*so~*~G7R^Y<0k7_`i;bE$g;h^4k^Yb#oQ zrvIMnTZla`_mEr|osj}s{$^pJOF?5JRxp>b7+n5I1bx7KQ2x_XkL>Bg!JtyDZC|b% z((64!todD{N+c7$g{%Ex2Y z1fIqi92VJN>eU5;WScFL9q_Xl-#(bb--9jPPOrjdDJSHo+2zOkG_ynY>lEh?8bWoG zzFKclaSn=E+H6E+sqh!(6%$n9-xQ_sV&2EE{x z<<1)}0MyU-QTd4=@oiiR(@Tl67|eYAe4{hA_Rh>caGRPY+OS&#(Xk#TcHOKY`rV2T zHHJ~8U=rdK@rhJ7fL0@jG{zU7xMRAKH|H20YsS_p%cn=!x@P@+X$Lk9qHMh&7$`4F zW$hib>bT;pm{6mvy(ByzBn}mnLSMdTT|53PlD(B(_Bo|RQDKxJY*3oe8rhlSdG}}! z9}d+M!}rkF3&$;XizbllHuV)*yUDs-k8-&nHssqlY>XEH!hWfv@UOh%8iW4cuJS;G)&zKoff&{=NZpM z@Qm6Eu^kjR8Lb1F_QPY7A!f302Q(AlYj@JbInkcwIa%}^;g4cEmL)ru#xh1K%{KXb zyS$2bA%N)U7wzZZb4N;DS=cavxEHudIq2&Kk-EA8UZXqgSB_ zZDBe5uB@9m&Am@bJep)xK3=|NB#)F^YIDD*emv6t<{E8USv+V@k^g!FWaaoeRa9y5 z62ujI-U&sqY`S?Jr8m#=E2gs!ac#`EzgA<}F7JwxM21nK?aS^-y#rq{Ybg#*6^jh` zmchbONMb~dc9vgD6D1mZwM#)X*Rwmw`xXi3%|&l!4!iBl!nfz8yb_&cehXCZhkTEF zCxb9RgfZ*VDwKP^WM)crCMC z44fkS0n#6YV5>V;I_>$|_y02SP$fSvpBT7gi*hbM`_JBI9%Vf-^?U=utS0o_h;`C? znr@G5M)R_?Zav#o^KvY3AYr}2ug|SaRH_Ez3&)(Kzw%@2ao$0-;u-ik7fJu|EBP;f zle%v59cErUFZWuRDWW_*NraH5Jnq-SNR%MR_1W2v0w%`gqe>i?+v%D5=I=3x{8 zDT8j5lwCr)K|qv7YFWCwk*-TpX^EvvI+pGh>F#b2kd|(E7rpQM|Ge{s5B&C;IdkSr zoHLP__oIB!28aASVsU%SKy9X^3PwJlavluYAOc zWoqIfI%SHkXGI~2e7*=7bT!%hI_-k!ni;V8EO=*nRej5mDLzd^yknZL6CK&5_|(Sn zOUv3Lx}l{n?bqmFm?EXG(Utdd$$7@mJ3uc}zuxnEt|jy6XIZf8*7i-c8Xuw)W{I_c z#b^ECRmKbE`2vMe1wTRm>QYe=y>E|HXO;T;FgmzQHJ;)Ns z;NABmXvnQCd`#5q@+jN9zQ=H+m4%EE$@|OrHpMf^69vVk_leJ2tj@6ve!tf7FVgfs}L12xK;3d5G$60L84`deO z{!?8wbt{LrRwX}4RHoJUd~U}gh&)LT?5k=r@zL8)L)s65@sssMU$LB(vWA-6kvO~u zc#5xgycVicr-0h*UzT>_Oa-eMbz@*?I{MUQb~>$|J%(U`=9=m6CgNgE-Gtt>wB}%eA%b*Eas1sUD02=UU)oH$_9V?h`iug<#9)uj~CCM_m*E-tf~08 zsuyor_>zq4&F!j#6r;7;O}Q~uy@&Z^?OIRrHDlO8IL}PH&f!LW{NjTrv?clsh{-c( zpmw_m*bkbuSerIvI>1C!?DS&oJp}T@#tPxgD}Em(-JzW}oRPR?@aT8(ENpjLHcbv% zNi8J>s+;Tk6O*wq(I0x#$w|3alp+6mK#ixB8b8Fb149X~u=C@UYf@Iwg653m>psV8 ze~Uip;No-t&J@^?jgJx2xq7g-Pt2$x3`) zuz<$(eRoW1M;>+&tu>g<9$)RBR z(LD#&oxcog;yZ18un*?A(cQib7}r~1-Q@@yBut9UZAYdC2H1iqb895j#mS3x(W~t` zuRq$$j@&$2UQ$k4A`tQR4RX@9i=3|sPt8duf&Ml|k6gS8=MR9_lsl}o(pIAlJS$j@ zCAeL;%UQ>_y_+JVbbEd10y>X6W>D&P3(0#o+Y9NwxdV|oy^O}qCQPP3BHk|F<56MJ zJebsA@GI2^m?QdVBjnhHtq$#VVw+TMe1pT0Kp+>JY*c?*u62>nuB>-kwPJSKJgt;g zd~`elZVH96h9;|R??q!hdkxjSZc~VTR9K66+a8;kh6df`vkY}7&pSipTwX;}vFVj! zul=YHHDZn{;t7bUPvW7q@e4ZYz1o*2#%p|GS4(cp!|bu&_aHn8j$7}lH+`wgR7I=?Q1IS}$P|~d=l6O=~>8n1x9z9$mj(yOHjLWhav;teZ7(3lAdqxH z=2K{rYIqy*YUWR#OvMEv;fJcLSN!UXRNE!YzP?_(vtySF4&5t&<2f?2rjU=9)&w9G zb4h-FT0l=W?l$k&m=m6B@Rej34*+z7Vl#X`gO1~ERXI$zN@gtRAjz&|+nv!aztdxX z+eAMDGwE<*QdX$-rjS?MVzuJaHR%aV!xPwR)3#W>m_))l$RP?5>SrqbTj~dLyM>rN z&CRcZKi=(TC!ldLphioItxgsHpwB2c43(E1@VdoPnmX7bo>tvTR}%vlYOgBraXIf1 z-B|sQ*h@~Wz{pSBDyucJpxdU~9+F7j>Xr^R!CoFLQ>mgy{^ohprJy6^F?MQXRDANX z)E%vi_7RNtWj_lsq{1!9E%QmLdZyceI>54Z^h-A(_--xZqcaPL8Q*hCz52yBKEDVE#}oGDi+2h;WPF9^ z7wLXd=f_G{`!2t7t>nonFWz17#pF*?2qo~>?go~r8r`MM3B${7 zPlO31A2-(=ZxeTbgOtnVdlswHbQI{Xl zHdXi>y41vIM^51VB8M>aF^rgb! z1%|x`DHxO0yPWsr;%4sr@?P0D6IhPTWsAuNt|C8Rl2VOhzgo$iZF@G)wcYbwSJW1p z&GO{qH^7)Wnm==C`Q;Zu+n;$^5asFP^YComhPcdlyMyn?e<@Hq0yV>+SK0BuG z^UV@5ZZ-u+-Ur6RHFX7CF6IpyWAjk zFmSarSZP$u^Yel^0Lqi8JMH6RR7SevzD-*Qwo}?Ezg}VF8Gciv$N?0&dchyr`-au;X|N#X|2)UCHSq_4{Q_$tQzjDW6k1^hkNPN!eY2C2zT?O3@Vbz1dhi zw%p9>Qsg(s9^578d(3b^wTpe15$*Xu0m>D#3{tI?c|NU6K;xnhufj9i{M^TZAt&YLhk4E0C;O4B2BMml{wr~!nA*5y2*F3I~0PGd; zSCh(OP~U-qN;bVB)Gdvw$)J_a8^xQk)BMx;qe&Q*P$n4R}_*Rt*|`KYrfY|_Zd=-}*o6HkAt zMLmo0>c!_0Z0k*$Dk;_oFfEgIGf-B`=tx!MwlhRH4%fGxTh+SKKIt%qM>^F-a8414 z8wxPDa2sJcPr9_q-QI-8q1RTaPsdo4+>S7&9C#cg8h#Ga*+pw}Pb{<<1JB_G7hb32 z4?gvi6#c0y-iq>Z+b7+?!eyp)*1^8rWf?rKk>v3z^EGh$tBSM$Hm+^vN20Aqrnvvi z_eewV8;{10V)`5exHf&O&QH}D3mZUY#zfMvM85Mnzd{v9jU5<3yfF62_=(IxY}!xP zJ5#1KPJ@^j7twAT-3+W=>?wA6Fs1y?{mkg z#Y`-*CnkkVOKBIw(R(dsy+6=op`bQ4$=`bF9>14yk#Fu-MEC?v{BBu1fZTk~qpQI( zW%}b6Qj9~f!tjK~0bmA4d#JmG`~-jwRXn)A_uYMYs3135p`OvrMgj{%0L90^?bj6}0rDoglai0$3=MLN3yZ`V1VC|%AEoEpLKFIA#6jg^+)mAIN* zCF_4$#Fj7Bf6B4GA~3@?<$=H&zvU!xY`Gj2Wf<5M!f`k`!#l} z-|;>jlDD*$!E<$MHJLAU?#67U=d7rANSp=S$5W5p1ScOr@{~27^4@kxpJxjy46>YQ z*+Q9`XrREcTZa(HifQO z($PP@?ByIGD+?sBb^I94s@@oT;SuZn{!IQ$Sc*z^_vDM%<=)aHq_0r9<^0iLwunr6P z@dyo*Xy5izh<4PmCe^re_*0?bPdn~UO;|qCMC)j>`Jl2co!{Q}&m-Ry_DdS8t<) zUyhs)Li8+ogGR(wQ}H@6Oj9m#Tc>8;y1sS!=un!_7TZM(5>r)Z2KDkGvD{hkMEhQn zx4Fk8TsPaX+fIn63{28328G(WFIX*xh-v-i;vwbA@nx8 z_D!%pYzvT<1*lbi;rOscCv4Ekx;jgPO-+e*KvQ>wC;%2nRB57fJs!`BZdcB-XWw!} z6Ql;3ek4O*lecYZ%Fm>|_r=X@x45d(O!CE1I*(31GfMWFbf#0 z*ru4Ltj=^7d1%I=nW2?4p0AS6m7`bcclHcEG^q<)HGwY{-%lEOf=D1;%M73AI(7g0%T>H`fyrvlk+aT2_fLw7bwdZ`RQD8DX(n!((E zHq=a`(#EolBHV4u`|NxN6i7~QuW?@PD_S*Rv!K&dk!xgfu<;Q#l|}hA#2b0^Lo3qW?17v>@xM zGbuQ8J>#h$;mymMq!-T@d?Yua(Ve#)iP}M@mE(R(!0%^4kA$Q{WpF+|NA~)derQQR z{O!vFq_2ujoO89>SH~R4XQd?^R}CXOOE5%UA(qCfMQSms9z`rkA0~qG3c~PFh1py% zL*GR14>W^z6iz{~B^U&E#+x#EJCnW61E*eVpVJ~{l+K)UM{O-NN!nbM@j1ME#0>3U2 z!3xHyjgbA3-SD}H>KS5ko&@h8JkRSC?FNin9)OpJQLbW6qDGRmEo80zVSMiv>FPA$ zlH9H00*#3Ip3^f|%zD!u1h=uaf2#px5aj?#&?Wk7kdJnZYRSL3vgcF;dk+|B1gVMV zyW#?O3sG9v-YANLqcCJ!RVSD z-?;958|=UCXht|=XUp| z>~YD9SJNN3ET3dNWMx93(fCq7`LS!!m;l$0KVO&jg-`^~Hd@fhGuofXc#;TwX}iu4 zq!=Slq14GWHp?4cS-m`#YhUx9xg4$o;)%fmv5yqmaJ;Yoq{rOT*Y)_-YZ3X)AMeIr z*v(iGS_?%6uCBfg8Rj&m2j+7QKX^wj(fep;g(L`jQ~B|$kMOZQ025z;nlOjb@0Ykn zN>}lcK&3R;iErePikTc9@2Y*N*dm@dqxEv2Z6y*Jwuk8wH1FHBKrq@1; zp+WouxjAT?-$6c^yJdtM3tStr5l>ij?@PLl7YNFJM?M#4d@_IQx%pW9<7=mN?(dozMh^>3Mytj>b#Duy#OW(f*3YkUGc^G zx0dkUk65FC+Yj;oCOL*U^W6nKBV05fdbcAMg1Trz{yV&KS!UZqy}OuivuqS6waWjPrJXD~wsT~iQE-eYLv zuBXwr1I%K<{z4WuHGw_-wAR%oYe3Jy_9;(t%g;|={yR4Jf;PU{`oOMvB^vE3>(V*; zk9kI1yTLyHJsqjsy$|k^xkGfxqcjY>QVuDZKCloEah%$FG5r-PJAt%v=^i~83raPFy!igH^p3DU+?do-XRz9w= z3-2PC!%p}9mg2A?1l7{)H;`+k+RSaH>HZ!Wza$=+RPu+T0&uB9{pm0#fdM}Lny)X+ z&GxQoKK(&@|8W56tKD@o(r&Uwu~oO@*Md`C_f%=;*}X8EQr}sg7P%MCUp=8B41Dnz z{e|Ds19V>D2Md91?26|oUEzhO~=m5~C;&kUo8GMdRiUU-UGYcvjqOcl7j5f#*F{%PMVK z7vOtu36YR&^JLYFqD%H*Mw^pB~i2Rp1)cVg_o#HL4ppasHbSQR#haGvutPrPX+L zxgG1pwBLBpu_yW((N5qVVo@TXq+)wVa|Wlr*`r@f>eiXYw(DM0C5qcF0=tk?XEyUy z$z5Ew+Yjamc}(IB!7tt#JwW>Y(`E>*XwT)!fU^D=>~@twxm)C~dtY3Ia0f6S20A|Z z`Ax@J^S~iuwwh{SH_e5WHLZG@)P@!Z&J8V#Y!OB9;?qg>Nn_j0&BZ{Dkl@D(;T!t9 zDeK=mI=5RHl()fr#oBw7 zJ9N$Ue)Ik>{>&prLSn|GnNXjj_oT%KvMOOA$uKN=CBQ5^gke&1sp04ct?;I*)qW!m zC(H&W!}aRon}a9qgwlPYx>*YI-NI^TIHjQ5@jT__$5?mX`tE&ZJ?EG6_~b1As79_Q zI2NvZEkz~p($-`?U+8G=rF1xw0VXA#{7>nXWtKIUGKt)4te<`(mEfVR8h;zz+D!3Fe116F<7L`khD>b8M1hNK@Y~( zkr%37JTI0Fpl5P+s@iV>T(}h5G?7of)+ESz5yrNd$Ce2sqm%`P2#J~0@y^Z11(sbm^V02PcyHz(FU-K#VI>~A> zs^Wsk$x8m-EJz!h$AL2={DC{YeYqnt>e8a(;^vdsyB>hP`7lwnx>`DEZx=WKOJlja zJ60y1f+r_CCE#yF{t>rcZwdGWBksHs0ZFCQ^;n&rMx@5PS`JJl1+ylN#cnobt zb|1Rc%Y#%lay9$kt!NpP<{g@PvP&UtPR`Q3*wAE&5B6e4*fBI{2W|GdpH~hZAxgFv z1>KEGQ|1od3!kX2+5d=hQks{oN#sTOSJIx-K%xNQlzL=M;=T%2I~50S6$2V!d~wr4 zfYhRR+57q6@l1sOenc~nA(35;7nb%GvS#99t0RCYnE!gIaq3+}H__kUKbj-o+9`Z{ z1srs;tl?u)l7wDhU$O+!@g9xhM%od=HURo+9LV`5D;F zS#&-1xME+FwdWq6_j4X?YAOgEmt@?#Z+C*wG$`I}Ao}Xz*&1lHKy;zeqQ4u+Cy>!A ziWKWffuJfjsdV>-;;m$pH#KVS#}RGz<#>c{4mFrlR3+H%j-nq-5v+IJt-QNo7p}kQ z26hq6(~bY4*~8F(9`M%(u(f+pI`>Q6AmaR$8JRHKU9lCT)AZv)#pWPEaY6qjBjF&#ka&`tGN{(O7868PDgp{)bnH;c(MvUTnK(9nW9d5L61JCNx56gf1L@&yYRU@j zUuv!uU!ZXfYb!p*BR2;n1|iP;i-SQ2XNv91%gfW#)0?wJ;B;p?n2@FE;0Rb9zdHjE z!;`DId6xG*f1b=AYrEezV76|JpzGf|5(r>^PF0FWW~T~BCs!Ccl^vSDGLe06%^A9p zpdeUaY9@`UbycPF`k+jQ6UX?fq`?P+p*YK#(qC_F6pL^6Ssa2-8O?!|zkEvTLVN~O zg@MZ%i@PI2uY<-!dX2>2)>8F>yV+;gFG|D4OS=B_I6+uP1XmT|J4HUuzp~a#WawIs zbx*s1CM8vQ^V==KpF2KOPgkW`jGW))TNmx-W~RGWHEw?YAt8Y9-j07!mDcq@CnqQ6 zQv_UZuP?yG4fZ$P@?L8;cO${u+q*K3-0}0#PPntOHr(CJs09T9;xvDQlkc=XZ6uNT z1m)-1MfM#i1&Ya~GvjS$fr7Z6`HG2mKw)5y`r77fhqLID>P>B6f15;NeDnqF`8)h)Lj2_SB0)o%5{FHiO;l16cD(a6N-#SRwe! zI4#ax3;ADnpr#8THA0t@Idvs;R<+0Y0;L|YD9|H7IZB8V%wy8v`p9Y+3UvO4b7v*j z^Qkh6@ZJSyo)zSl;~eF(xiq#UESMf6@cz8B_UarTA7>@no-Q{jUa-4bPi#Jr2F`X( z5Jl0g^N*ZH&zH(mca0IKcYc)A5i$PwcaI}F8CxhmrdD4tKzMpFu14%i@q_$5QOQ>w zy5a=Q;0a9bFM+djL0gaPw-;=Z1-QnX@XY49A{syaMsLI9Sj0fXIl?-ZY=q&F4xguA zygJSSR`;(Pn~y2)T0h_ZMmW_-yMdOD4kb3EGqvM95xArDSQoy_8N4oD)N<8dyx!Sp zIxQDIJ8q@i(=Vxe>x1ahq(#-jj1>I(U!P%3Pa2+eW@{9|8k?|770gFBK*0`N&Po^C zIRt*gfYGuS^-`QRxHEMYrogG@O)x|3MF#bjdF=R7&gIR~)B`PU^__koOx<_&JD1;w zsZZ;^XCvg{cBB63yx<&MpYlQ%L!S|v;2OrCA`ILfZ;mpf0v6o3SpS?9!6gwXIIY+-_1l6v zcy%-5mA>LA`Tq3XOe#BBmICBP`(&4Vy!z)Hg$2L9%n>w^6FSmlB=hs;s6lewPj5P* zhlit8cew(gqYs{<(udQsG-%N{=0bVj=g_>ovxx{~Qemkr~q z5lUTvEQ|tt`ith?_(t(BOdlL?axc29S_ogXKlMC16FN9429CpTOU8^F5wcY+bkHsA zcGSP`wKaOL8Ru^tJBPI#FSkrf!lITb{tDR~@?K#Z&gA>@cd?Ms0y5L+rCku7roDn9 zVa9>VJg{5Bg-_>Y@gnYch_d>)PEc;PxD3%Pqdhx(cDh>c9Wlzj2vy4m)l86MjVDpe z&kciy70VMut~2H zz~MLHTj65Q4gO+}efPoJoh$DBCik0`tMEktxI5k2x_KXt7)8){^QS7-- zO?k1`DRjGnIL&QSpKYx91f3jI=Zoiw!yRkGl>X|KPwwA6qoBQY1+NR_U>tFA3ibs` zvZ|qNu&7qa4JLo#k4wFvg&gbnB){Q=QUmZ(WJa!{8Ah}sv z@tCPPq7`wl--zjlP}zq7TLKe%gUlTHxN`eVS9=Jfx=?R$)*7s;eQ`@KbTI!^yKLLt z-5u;q27_|XWbj*42)eCV=kj|yj)qR(lw6gU8`T?kYCrf_I+|DY+I{i*pgfEeLuwTc%iiWx^4vQQ4_M1W}d&< zJ!f;*U)dbLbKai~0`G^GRGqx`nEmiXif-VZJmCnPDW~Q*k$-1G z7~lwmv~8hohK#Wi6|k#x3fEKeg$=7JfB(mDbZVBgsVnRBOWx2MQ97#Y9^>vM$9#O%C;b^}m`kzr5R`DJu&{fbt(`#eR>f${xvqPnSl_x{!h z=@VMld*RFB(81qSv@yyQ1>~CR{ZfSbjq|IkpIga3^#&G1QoSkNWzcsv4= ze4zsAGluamgg~o6rSsUD3oPxuUi2I!QrDie?#dxy?SYSIKfcmMkfbXf2I%K0DJA!8 zV4ZcRAIOx7eE z7uu?h=#x)0!@_v-8;OEOZ|jDF)~BC}(OOQlAQW^E0da+ zLQE4<$AN^htxdN77|;9VScY0jGO9JSu3SpZ9lf$w{RoFKy3*rdul@phQ6F0;S9TJY`qDq3Mle@6uoR(~N1)uTemkq9rf)tYO44tppHHIYlPocGfVd(02fh)=4h&gaxT7auHV7N9J$CP-5#{V56{7{g$1T&GK9>T*oOkSf#M-my@9Pm9WR% zJ=p|UVKkpdCBJIf4#x&CI;GR;L1$KXL+TGn;P6FJlLx?m7>s$!+tlCdNG`M5VXI~r zw?!xvifS^M6pmn+-|nV_Sd6{>^||S-25Prn0Ig#VV}E&gcTX<-*OR4E<5aBfNUj2w zvjGE6TZJR6I1aaX9$DCg+MKlCKV}?(D&V2%wTo#j)qbYI_)Bew%~8smO^)90T1Q8{X3V z-P?QW2@I?PX)PRM4qM|Zd=y{bCd-;-7b8lViJj9o{A^E7bY+gY8OL3|8V)mR7;UyF zdct5uk@#~UaNWMHGEiK2+oW016ItHiBq~A_(>xU2cdm9*CT$!ry?L)T{sCPjIDM&M z`)w8~n6iqdsO9MsIs$q5d}e19yES5tiIC^>CSj{=YO5aZ@#p!H{IVNA4{sZjBs?zO z|1dzQ?m6tGDW$F!4UMc@^e@v4Pra(AQ%N;NmfQqX5v7%bq^7hNM*rSDAuxWQfJ*9H zP3W$12eorr+LZGU$iN3gyXRD3#@y?)to#xVh41?Mt4uOcm!Scp#5{@LzUL{aX?b-K zjZ%OsAZqbJ5JgJZ+IIEJS=tQ}NW)@=5lzZb#5ul8fjy5e zRcE`a;*g+=0|sbc#j^_JJ@nNt?|E2WcNZFl8~Swjw%EJp@Ux-+DG1ysT2?IS1gldS zK%=+b=2Scd4T<6#TuOY?jcH+zP6?sMl1`fR{~tLEHc|_$tY`r-kvVdCs;m^z;JNU| zSI;q>7GD)S0}p~*Q&@+~hK$#c%U6C=17b!foYs<=;7B9wn_ey^(K1m382;1B_7&63 z)#FowJa|75V;&!KOFFvV5;kVgG}oQG56M>0G#3A5=Sm6C3Epkb@Z&H~D3i zp<8pIg2!@JD-}+0)lXLC(QPwfEjWX(SDwtg znp7={V4nns66Q>MvT-Bmc3H5bH5|_jg0Xf<{_a}tycU)+F|?6sh}o4 zGnm-Nnr#EboH^8Y3Kdxi7&s(0>z zw9-#r`cr=?_UHa8nro)< zOWhL=?^s%;mV=FhXbRE}#L+Q$N7EE9nV5lYXY&R-GCknE&w=$<}E` z0&eV{#^&yEP(=tR3RO3Oy2%g{nq`iXc|xwX!gOtNTSGqHxxP=g z;8(Ju6g_r03Ro|V`?W=S*$OK~UtU?=`&R*9li)E99~7Gkw@W@qK%h+ZX>BM?m$IGR zfqDm?xb4>u-O8VXqJ~4FXS9Xg*Fz19Q17qF{{b#>ZT-RupinJ&xu>|;r+x{-1*9<~ z8M?>~ekhV=ey(a?MVhCh+&2}mdeWYWWj_Vabr@EeU>MfT>j}z3?lDmy-8eTp`U?L# zsZjk=6W=+yX4+XTxkY_ttd=L=w0Zy7wPS}{!&sB;e~?zad#7J$Q<7Pd0S0ufe`&h` z#p1^*j>LsurM=>k&1K(HmR>W558xYQ>*e*2yp6~!LzU|NptiXeP$lL0^;E1(R|1lB z6$)>hXCJ6+d$Hi7q=FD87C1Fr3UhEo23Er|yt2Uae~89UB>isp!z z>n^_pL1*#`n{XD@CAtNpQ1*dDDTl?DIA#!S<2QXbYy&V1|Gi0Qn4fn-kCf-L-A(fK zQezk+Ct|SkyxmET>Fs01Lszp%!JKy_w`BcaWb2FF%nurV?O5EqJpaJ=7zqiuN;NBC zp9hQ;T6l;?@T*{YuZPl_7IP6USeo`($y7Wmk0gb*P&p<95ZcSeV^@GQek5K8jnn4K zON0zZX_ZMzK)gFaO*splnQPRWcO8KTU*iq^YV%E{4LKek)SjGXjL{6)Z#bBfqHP@J!SgC+5*R?nev&A%GgcJ1x@mBRO=TCV)x8;whzBJIMLo1&h zEPq%+vmcdztTLe`>08M*@LXeYW0Ym}n?apXpOzfi#8+@~I=WPg0i}ux@s)(Z2AQX} zdB+VpUPLX#^nkFs>o8)o%hpL5^GK94*DYr6CfqYsJ*irhT`Y!+{|&eAX_z&@59Fjr z0z>Poj4nZjSTR$xJx)lFZ%vV9Dma}!V=VTrsme8o0l*}HVCzZwlk6Ym>4(qe;&VJ{ zBIkKDm@Q@))dVaQU@)x$c#nN<9@t=D*6_Kn$}vna=X1IGr0DmN?bLC9l5a@oz{YX_Hk0 zq|B&N%laeZk?s?t1PoEf%ucrNpt|iqbRSgykZ~n*$79M$Owmz^W%qClTp%SLG<&Qu1OPi z#GHvRHhKFmiak|iy!j60-+&Cacf@X1BSy3L~ zBj_qB3)qYxSTS*nrY0|xU0q@>U2h-ma$B9=yvY{% zPs*pbFcIiO6;fNIY(QZChGDjM`AFqhG1`3K#E~Hhfm~~G(lysi2NgkCgJoM*L9f1J zd31PmU>Qi)F;zZDK^Bb&$b~N?_1hM-xnXG`3Q-~Muu9Up;Fn-kHQ%rBmWQE5Yo~jI z;EmSurk==b7P13Rs9}k~3`2;licAv-go7S~;&;+yvXZO#W#T_C=F{$wy1vzdZv5qz z!vpDUumZDmBNZZG>@jFUO*v&K=VWD5Af?;l0^`Q50Sao3trRe$N>^8%j0wgN-)gdN zy1ph%S;`KR46iN9imEBX@h4Q&+MlM4h0w8WH=LA0_SggWRm`$5j%hfP8*l&P z2vYEfvVM01p3k=_-Oa=?1@g{ka&Al@$H{uOOOWRS$363yN|31|)#c~lO{KT3aCn^xeQRU9|aCOqnr!Sq-U=*wPA?liiDibB%lLr3&Dv+!T?LJ*%J!pCp zj7$>6gdQhJ3j<6ND1Cp!^beG9&Pc+Iuppu<%EXAltGtdMx4?_0n+1}nhGWY0tf8^{ ztGv}SR3x$nD-<2icFWHJkZ#sTEINY<4CZ$(7W6~1}T z6~)MfNq!OuII#SNiWKTsb@@hbLQP6~&{%HW5+W^)sYG_Msn_xTy|YHp@(D95qHQLi zrEb>K+PEZ=bWP<_N|W3@wI@Uzb7LXA${s!2x8hWYmJ(3^S*2zym>=DvXnzCCg_~BO z*YzB_z=s9K^~S%wL+jv0HOL@sTtJ3cmDuY+i*!EfYj7(XtHCAR4f11JdgkJIJZJwm zPED-%3JoS=9Ht5-LDOY;2{m77>H84q>*dMWsl`NMjUP*7ew}yBmXgPdECyoh2BL6m zajrR(-x^PY;$<{el!Z=&qAJox0x;f97e<^awnhJ<0RL2(D4atvXqR@;E7U8=JU7jY zh&;4uG}I2;ddXnR*`9O=;^{Yf^HP0rI$^-FQAJ zBT7^~4j9-mnW6&5TLoK!7FX&X)wC0WsLLOgK{cIHK$1$S8blWY>eLPF!C1A0tNE8X zRdW~{6#DQhd?V4J-8>FfTrPDhPl?j9HQOO#WVmLQ=vI9vuwfrtb?C8oJu#&KS@Bx_ zmjXn<8|4~r4D~An9&6n1m=dMMJCi&J*kR- z9X6@?(su8cW;Qc*pNTcLU|fD^t+rO+WZ9tip87YoK!g@JFdXUiiorT~_dyJ*MHyl8 zW?4fM&du2lA7NhG#kj3Lu-kpf-GiF~>*08n#f#cLX|E0j=j91}Q{H;fX)*}W&)N8x zw!@|ka-1rP%v$i*t1d?~HbTbGAYrtS8Pz2g4wr=)UXj>?qntTNn>kINMUB_oKMwrX zy*Saib0qj{rYK(MT(1yv3$^M8swhacKYm>%R28i2U)qunyq0fy{wXW?RW&NB8uf6AhV27u(X z;B@Y1J%F+Zb3ZKcu_{s2Vq?`9C6*p`6$APGAg<-qc4W}}T}O9i44a|S(E-y|SSG?S z`V@VG$iz$N2zliwczDuHL5=}99r)QJ%aBtZgO9Yncf;0DDG}GniH19ie8xfd>@nTI z)wgo8_(`Y*0ugEF+9_Fqj|8PZ1NRrQXalErvtoZl=+oywRejfo)MLzUogJuPrAJ_z zs?N%r?Mx?i6`k6-aX1N-RZvtQnKLJ@N6x4+Zhe})lbB|G^pT2fE~B8L6kVgdRT)OU zYI`pkJz*p)q>N3pOmUrwG7>*vVc=KQhg$eZn-iFX5&aviMXILO2e9-ONzHN1y2PX* z+5bzzJsqEzzQ#gR3_n2#8&~lL4%virvxLfW6aJFw;qo@NuS z$<5x*Wz|2Re!hFPSE*Nz?;$4ckx(EZ=ZDlm{_lz*kt zq&lP-YHq7e0(X4?Ws6thT3eDqWJ(3C#d>+){L=JUchNFKhu1W06@mue+{NYPek*D1lwE#y%ipHpoLS~tv6J`#Fd5rkbH5F;&+x*xU zjs$}Op{OfD!hHV|v<84mL0B;Wrb8AHBl_kPmd!9@X@^aIafpNVwCBc}bBgCl>1Bc8 zJ0)3`{>MmVRuIwjS3V$UJo2t_eLZeYi(=jvuxB?OB?0|Y5sr*xRG|AwYd4LNXj#K} z;c?&R_5P@f!N~>)E&lp7N>&?4+o+34p@~3|A_bP5GsgYPI>m7E|EO8=prL`3Bf-ZW zICd)c$(jUieT|7g-0UiA#n(Q7uGG9?Wqx7E_F!P~W7Ydt=u5*aa9Dp(0whARJ$K=e z2go8}$OahMF+%7bHLe5@Czv=8?w1`|uai-EoMXz^jkte1erV>b?DL#;z1cO=HRXDr zfWcgsB!0)8T6L_)D~DW%zvtz9LRKAy+v-qulQiKgs{eNDJK{;7F~cg50j5w8q9wU^(E0-E7tj+t9Z>< zZnE%c-X=wj+&mvOX%cu36}0jJRLB!%o!lM6b>~!}ryCOX_UtD>Qxg&hRWWPf1XS`pNv)9>-ad}gX!LU12@ z&B9{5X+)l#LnU%|j|2MhOcc}jaqNNq|3#!zFM`_C5y8mMkzbM=d^yfG^Qh7zQBgSa zM%J{mz=3g-6bDshwJu~7S%LHwiGjPAQ+m9(Ef1VLjBSz~4z`xa73%#3v~W-J-oFqUjlSsIy;HHH~WlrR`3 z#*(d&vNX1`Q%Lq~Ma7%m^qYEnzrDZn=Uj7LpL5Q0&U2pozMtouab>?#Nq0SyAvl`q z+vH0#ZFI$wrR)9*JV}{R)|W8`iPIN*Ychl(t6@8EOxfE%Y$aP_P%3)iTV{w39q>?8 z3<4wgPlY97^E&;GN0Ff~Jz_4wM!JP+v##DLw3Y5z)e*n`5$SjAzV|+RjzgoDJYsH; z%iWX9^X2*UZn=BZaGBen#0`|Bm}YKbAQ^l+-r36(pb@x_Hu;#o;GV zG~SX^m^}&a<(WF@2l^v7F&r`E@|9tvN65sit9(8@DU~h^5QU4lESHjQTB~lN&ut=^ z?Affq(C7YpzTPJ#RnqnYZj3b{gba}yQNcsj&(J_Oh9FvQ{5}VM=^qTU1UZxo=Q%1l z>p6V5Gwp)0l|^1UvVb@E@7|mz?u+s_B0X%Hw^qxkR||#;&XZc(*A8!l*{x`|h!ew< ztXYh1`jJAFoEKAPW4i}yxVF2KvW*x)QkdppV#|C~-KuUxxU28(Q+4q+{|LDqkE5Fs zr_|BF&%QT1R|!U>lbgampKJ?->9_w>Aw>BhPgLb*(~=r)j{*?w+K=91Ve%p9pshx; zkAb>B?gio6v;@zqyh2T&K@Z(l&LWi-jJ3UTAMlBa?+kZu$pX03vp{K|VhON4s*8lTH)Bf*!-sK zssC)}EY_Lyh~p`z&LL|-qK611P%~7R{)`Qh*%q{5194tbQ?0{P(F-Z~k6Cur5ak|q z7yEBIpjCbm4d&UbeyDJ#ibbHXtRNL43pNF^nHxTimLLhR^kZ!YG+_{@Be_RXFN3pB*jrDL1NFeqX)ASMv%9<9dG)5Q%4p4~A9$zz7@H-}CvIA!E((KX&UTvBylt(Toqk~tmFmw@l`9f* z*JnPC1)d$Dr_vdjE==ykLfncfK|V1~uAm8NfpDBT`tU^)#)lz$eHhP>%}P1_1s8A| z8Ylu$$t;*j{$GIw8$${buki-k2aOiU$n(12h8$uGqh_>jUenS0dSIP9dCv()yL=RE zVYODvE3&N@dCH7zlJCg_B;$K+Fe+M$CNjyLuv_lQW+PY=3zlCM?qRgYbf3`857ODr zkv+ywIg{+cmx6yW^+7zY4aB+!PYYdv1`n7!+Y9!%F(!8oxBinQ`EzZ&IY&mTC1S6; zF}%4^VG90I<=1x5=(Fudc5L#r*>pfJb08Mb6V?Hz zwryp+2VMI|od2~tbBVR>IW7#@F9vX>=h%3a4fj<{qML~bJ#dhrd@?3qIQZ~J{*i~_ zdo7Tv1;@*!Nd1h%lJ&aTyb{5NiiHGbo_|`_jZWE%ZZpbZ=DuW&_(}M!pyu3Ul9MGz zX@Y$(Mf2qY?qk`vp7L}%bWEQ1>8W;38{d1MPO3cn?y4DZzry@mKzgCts*}ivY*pgI z-|edC>gClx?jOq=9>4Iq#pddHa^$;h0D`on#*I1y%XDR|QHb({Su>wS+Fw+qzl%T_ ztCwtn$u|pjRII=*8yZPpxWfPJ-C|Zc%On9wvMVpob~V0vCGRdBt}# zaSm^>n|NK<{qqUao@la6jU=8Ur7S#s%osr=>)KpqME797LNb@~eoFtpTNKaM_(->^ ziXxFrn6iXwqDj+XP*wEMi7F6r1jL5mls0#~Y0p#t;8P4Ox(kPwD-D}#FOV|+J}4>T z3JF=p#>{`bUi;?ce)=E?%nKozijI3!T$BJO1@XluaoIR#nzDP?2r7r`h6!rz`a5^j zyf$@?8DYUT)|VbxOSe1%c8UJ(%uoO83yfifS%mSZ22rVx9s5GJQ9K07SV^OfPdL7N@p3^ zGiXPPRol-P&MGT_L^GRlus#(JhWNKJ(p^6&TMw;L_8Y>@)3P0nZ&*U|lLYK(bY)mU z=0^FghGb486>4XVp`FK&AQv>sTs;;6rXB=&JqeB`j=Nrs>5fB!)*QW@@4u<;Wb+g5 z?vEsI!?NqoZ7kEzmQSdlry}>8u~g>qjzznNJi#wXnga_jo1s6W_55u*f+c&3<8%>?`N z@oREi9vzot0&S~AQ{+p2@Zs8RPha+(3=RvVF~Q{aXHS+)LH12shZgx8Xc?*>5}ve? z*2N(BAUoFVNNBch`?TW3*QERH4ar0C3vb2pBuI{xJu2w#C{niHbv6oX zmab`~%UykHSMN=jRx4sZLhiXMAkDZdouRdWdruh1bo7J%|(I2xM~7bIW32+L{dT;LJp z{S&Hd#^jj4Qb(874scV{v=wWJxq5c}Sd1GX;qhSoIk7e?Ih7nA-#!MDew*;EySgL@li^OvKw}6rrC9MdS{B zYUiaMsN6O#C~2|Tf}}^4Tre5EOr__8^<`I;b&O3=+6z|P_MGj?skn*!`ZhnfdD;Gf8X;7ji65A^^b_?~v-COPE*;F0S1HsI>8OI;gj z9_}tgc-M!U(j6YBKUOHTgg)JRXZ@)9)SNA^&Ozcmq>{XWNXlK!OU=R3ex};J? z*hDq#LZ%2wLwwj&S^IFITDm3ZPW?8*G+B1zswdeYc7fu z(>3#=mzI+{03r)7SYl8S$Uz|0!G40B?@&htF=W?<6^DlIU`rv?k0ZUZ>AOSX z#3hGxG1y@7`0n5Y4?(;RhO=#gi<8&5)PClq{cbZ-X=&(1Xy9wCK*2jdbkkQOv~xyj z4`+}V{V9sxixhs0qcpER2LmQne>Ot;DB290z|~pe>TU<%t0h+y`;8L!%X_@&#lelENSDZWT{XV|^G@%*|o~D&J z7)=TzH4Y+_JJY~Pk<;4ouF8I>ug~wcau_|Zdm!EHSs`nCjJS~M?IQ+BJMwQS9jikD z94yf9UpWdl@p#1L_=1k}2z* zaE_(Ig6d|t7-D>BBC)No`h4+Wls^nefR+jQQE$1M`2?ETjOK+5r61dFy{sZz*d^OI zC`Hy*sg02DxHj6cll&qqpB^sw&@X;B=$2>vcR0t!Zgj=Yfpo{(QMOt?Gyt38TjAAiw%i=`fNY{O#qp(Q!cwXAqyJGJ22IpZZ(cbLaoz9Dmxb?mQ@O2B8$9y9b$gqm0g$Mw7<5M!{C^ zlO04L10|xI?u5HHI2X>VePce2})&pql+xE>7fVfroF6Yj_*7TxhtWeD$cKmKV2rw9T|NCHZ_ zt7QuKg*1}ts#mc1Ilh;yBYqjZ`18-V}ov5uP3vV3h@yoOVN98HGK``z zYKk2!J{gM~%N@3n60#N;gnD~5WE9x${`LGuI{IOLDgFD>bKYsB_ePvUS@^1Ry@&t< zD5CPM(?1V zi<|BXjynq|2ZC$YYWvnDxglN&oBUe+Mo|SgHbwnRiSbYWeU?(gY@!k#FO&ENh?WBx3=<^x}B*|GFwR*o6 z?ZTg+(QwiT1pd4g)hRr7{p=%)@-MauNv}wx{K4Edl5f~^OD~3Oee~>&%Kvyy?A|XQQexeH_LgxOVP4K;jjcl=dHt~eUAdNnALzr?_ zsDA%iw3PMxmHDdkWl0p3Jrg3n=WaS;l{S6a#PiOMfrjgn6o?jpQ1lpJTJL zTSP@z>QFNQUt{PA?-=}B)oxIaz)M%q^VvGlfv#1RzUo|%RsKW{$HJtCoS3C&$gE`M z&qF;MZLjd@RHo3;yJs>6zBf?HMF;#XLyzdU!#NGCk>`^ zK#p>mv9%8^)yM%J1#F0LQ;IxE_oBe}o}(o{)uf7*G75DF`Mq|S0;8)P^E!o^*=Png zmw+p1G$_bRGOsYCqlmX3R(Xcp3oX?W&V0Ld%RMH&oAg+-se3T-Q1{+QdLY>=Ig`M!D$6#!{W~Z@87NAFfS0%8UAXIjh?E7j#s$(_jce*b`g;u zqJ(y=$Ueas03r#YYB%Eb`o?1n_b1EKyN}*G?(O2cq z^3a5x2KGos32dGBVyTCb@{RAmQuu$_fT1W6F4`!~AzSs*m*YVUH)Vb>TJhAU zV+7P0j)fP)eXjB5k4OX(#s97ET?aT*|Q#F?vv>z~nQr$Ujgi6LHb? zE6^n#bPH~nFOE6*{rSp8_zHoC@<@~n@G*)M0NT6U>jJG*-#Y1VQn{P}&1C73F+A^0 zBZEHbsATH|e2kYaSWBhn&!5bNzTVVHI(9ts4nj@cRK5sI@U)IY5{p|*5XB`dLg{7)aSEb3^1>uFHTiFua%M$;$Z32tua!ZRo@0iuT(ed{!O7C%gi zSTElH(uP_@N!$UBjcDpx^=f9-S{LS@_P=6@@m_rT?ucuQyOG*;tbF?XxelsK&ui*o z`uCQGmh0p|t69GjY8L!g!t>O;D&)u)o&P8TQEDU#I%R4kQpV!&vrcY`?+dJ-JK-hT z(Z#T3x?Fgj46Ik22S<`cz5IxeYFnQDE(&Lv`zeJLg7^X}Y=~UyXqR|l`av;-RD?w&iap_|9pIC84fJ5kf)S@V4 zeJr;v2y$+PSdo#Yn{{qn?H&X-xze7}J~1W@2N;=obley`TWE>8=rN_AM*yy0N$c{| z63G&%RWogEBWfrK`)p);q(gnK1$&l3SQ?-31DmNXS7S#VR;D4B19brdQuf(3vNB{8 zEqG^Yek}R^nNa(Ax!jUJ$|KKa@d>oZ+hgEoJo!&!L=FObxS!_O#3-6c32U-wd<)rn zRU%jXgx|Y50749cJAKL{0mmDOi}QG!6S!=3qih6(U_ynBuOD^``r-)kIpR+(RySqG z;VWDL?o)?ikE4pJm`POWQYJD1z4x9h7V1w2=!U6}@MNCf`&pKgkvwe>ZEsnKztop;8vVLV~>=~5RTCH}`6dwNk6U@l` KLdAJ(^#1`L+ui&C From fde8c3391b265eb34b6407e3665160fb981b6857 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 7 Jun 2015 12:21:53 -0400 Subject: [PATCH 0293/1046] SwiftCov support 84.8% coverage so far. Signed-off-by: Stephen Celis --- Makefile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Makefile b/Makefile index 64856b8c..7fe0d757 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ BUILD_SDK = macosx BUILD_ARGUMENTS = -scheme SQLite -sdk $(BUILD_SDK) XCPRETTY := $(shell command -v xcpretty) +SWIFTCOV := $(shell command -v swiftcov) +GCOVR := $(shell command -v gcovr) default: test @@ -16,8 +18,30 @@ else $(BUILD_TOOL) $(BUILD_ARGUMENTS) test endif +coverage: +ifdef SWIFTCOV + $(SWIFTCOV) generate --output coverage \ + $(BUILD_TOOL) $(BUILD_ARGUMENTS) -configuration Release test \ + -- ./SQLite/*.swift +ifdef GCOVR + $(GCOVR) \ + --root . \ + --use-gcov-files \ + --html \ + --html-details \ + --output coverage/index.html \ + --keep +else + @echo gcovr must be installed for HTML output: https://github.com/gcovr/gcovr +endif +else + @echo swiftcov must be installed for coverage: https://github.com/realm/SwiftCov + @exit 1 +endif + clean: $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean + rm -r coverage repl: @$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ @@ -26,3 +50,4 @@ repl: sloc: @zsh -c "grep -vE '^ *//|^$$' SQLite/*.{swift,h,c} | wc -l" +.PHONY: test coverage clean repl sloc From f791d36a6019e244be47d8b5bf997745585b1b91 Mon Sep 17 00:00:00 2001 From: huynhpl Date: Wed, 15 Jul 2015 09:41:56 +0700 Subject: [PATCH 0294/1046] Add select functions with array param for query builder --- SQLite/Query.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/SQLite/Query.swift b/SQLite/Query.swift index 8365e630..8e2f37a2 100644 --- a/SQLite/Query.swift +++ b/SQLite/Query.swift @@ -73,6 +73,15 @@ public struct Query { /// /// :returns: A query with the given SELECT clause applied. public func select(all: Expressible...) -> Query { + return select(all) + } + + /// Sets the SELECT clause on the query. + /// + /// :param: all A list of expressions to select. + /// + /// :returns: A query with the given SELECT clause applied. + public func select(all: [Expressible]) -> Query { var query = self (query.distinct, query.columns) = (false, all) return query @@ -84,6 +93,15 @@ public struct Query { /// /// :returns: A query with the given SELECT DISTINCT clause applied. public func select(distinct columns: Expressible...) -> Query { + return select(distinct: columns) + } + + /// Sets the SELECT DISTINCT clause on the query. + /// + /// :param: columns A list of expressions to select. + /// + /// :returns: A query with the given SELECT DISTINCT clause applied. + public func select(distinct columns: [Expressible]) -> Query { var query = self (query.distinct, query.columns) = (true, columns) return query From 043137e1322ffbdec34db894b10cb0d8bfd6b9e8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 8 Jun 2015 07:30:53 -0400 Subject: [PATCH 0295/1046] Coverage improvements Because "age" is already an Expression, many test paths weren't being hit. Signed-off-by: Stephen Celis --- SQLite Tests/ExpressionTests.swift | 2 +- SQLite Tests/FTSTests.swift | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift index e49a39ab..27d329b5 100644 --- a/SQLite Tests/ExpressionTests.swift +++ b/SQLite Tests/ExpressionTests.swift @@ -454,7 +454,7 @@ class ExpressionTests: SQLiteTestCase { } let email2 = Expression("email") - let age2 = Expression("age") + let age2 = Expression("age") let salary2 = Expression("salary") let admin2 = Expression("admin") diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift index eaf95b9b..6093f859 100644 --- a/SQLite Tests/FTSTests.swift +++ b/SQLite Tests/FTSTests.swift @@ -14,6 +14,12 @@ class FTSTests: SQLiteTestCase { AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\")") } + func test_createVtable_usingFts4_withSimpleTokenizer_createsVirtualTableWithTokenizer() { + db.create(vtable: emails, using: fts4([subject, body], tokenize: .Simple)) + + AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=simple)") + } + func test_createVtable_usingFts4_withPorterTokenizer_createsVirtualTableWithTokenizer() { db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) From 03b27f9a5fb5eba46d3d4d5c856d670d66c92332 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 18 Aug 2015 19:16:28 -0700 Subject: [PATCH 0296/1046] Fix General Query.delete() documentation Fixes #179. Signed-off-by: Stephen Celis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 61048201..3f815ca9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -868,7 +868,7 @@ alice.delete() The `delete` function returns a tuple with an `Int?` representing the number of deletes (or `nil` on failure) and the associated `Statement`. ``` swift -let delete = delete.update(email <- "alice@me.com") +let delete = alice.delete() if let changes = delete.changes where changes > 0 { println("deleted alice") } else if delete.statement.failed { From ec47dd393ba38fc646b17f0bc5231aa125ca7552 Mon Sep 17 00:00:00 2001 From: Luke Scott Date: Thu, 27 Aug 2015 10:55:15 -0700 Subject: [PATCH 0297/1046] Change Blob internal value from NSData to [UInt8] --- SQLite/Value.swift | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/SQLite/Value.swift b/SQLite/Value.swift index aefb44c1..1541e908 100644 --- a/SQLite/Value.swift +++ b/SQLite/Value.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import Foundation - /// Binding is a protocol that SQLite.swift uses internally to directly map /// SQLite types to Swift types. /// @@ -47,30 +45,37 @@ public protocol Value { } -public struct Blob { - - private let data: NSData +public struct Blob: Equatable { - public var bytes: UnsafePointer { - return data.bytes + public let data: [UInt8] + + public init(data: [UInt8]) { + self.data = data } - - public var length: Int { - return data.length - } - + public init(bytes: UnsafePointer, length: Int) { - data = NSData(bytes: bytes, length: length) + self.data = [UInt8](UnsafeBufferPointer( + start: UnsafePointer(bytes), + count: length + )) } } -extension Blob: Equatable {} - public func ==(lhs: Blob, rhs: Blob) -> Bool { return lhs.data == rhs.data } +extension Blob { + public var bytes: UnsafePointer { + return UnsafePointer(data) + } + + public var length: Int { + return data.count + } +} + extension Blob: Binding, Value { public static var declaredDatatype = "BLOB" From bea4c9868f4b783317729d97d0474ea08ddbc884 Mon Sep 17 00:00:00 2001 From: Luke Scott Date: Thu, 27 Aug 2015 14:55:55 -0700 Subject: [PATCH 0298/1046] Replace Foundation String format with Swift version --- SQLite Tests/ValueTests.swift | 13 +++++++++++++ SQLite.xcodeproj/project.pbxproj | 4 ++++ SQLite/Value.swift | 31 ++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 SQLite Tests/ValueTests.swift diff --git a/SQLite Tests/ValueTests.swift b/SQLite Tests/ValueTests.swift new file mode 100644 index 00000000..edd16ab1 --- /dev/null +++ b/SQLite Tests/ValueTests.swift @@ -0,0 +1,13 @@ +import XCTest +import SQLite + +class ValueTests: SQLiteTestCase { + + func test_blob_toHex() { + let blob = Blob( + data:[0,10,20,30,40,50,60,70,80,90,100,150,250,255] as [UInt8] + ) + XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") + } + +} diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 52ee9ba2..dabac2e8 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8E7D32B61B8FB67C003C1892 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */; }; DC109CE11A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -99,6 +100,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; DC109CE01A0C4D970070988E /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Schema.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; @@ -199,6 +201,7 @@ DC109CE31A0C4F5D0070988E /* SchemaTests.swift */, DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */, DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */, + 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */, DC37740319C8CBB3004FCF85 /* Supporting Files */, ); path = "SQLite Tests"; @@ -505,6 +508,7 @@ DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */, DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */, DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */, + 8E7D32B61B8FB67C003C1892 /* ValueTests.swift in Sources */, DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */, DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */, ); diff --git a/SQLite/Value.swift b/SQLite/Value.swift index 1541e908..567320bf 100644 --- a/SQLite/Value.swift +++ b/SQLite/Value.swift @@ -45,7 +45,9 @@ public protocol Value { } -public struct Blob: Equatable { +private let hexChars: [Character] = Array("0123456789abcdef") + +public struct Blob: Equatable, Printable { public let data: [UInt8] @@ -59,6 +61,22 @@ public struct Blob: Equatable { count: length )) } + + public func toHex() -> String { + var string: String = "" + string.reserveCapacity(data.count*2) + for byte in data { + let a = hexChars[Int(byte >> 4)] + let b = hexChars[Int(byte & 0xF)] + string.append(a) + string.append(b) + } + return string + } + + public var description: String { + return "x'\(toHex())'" + } } @@ -66,6 +84,7 @@ public func ==(lhs: Blob, rhs: Blob) -> Bool { return lhs.data == rhs.data } + extension Blob { public var bytes: UnsafePointer { return UnsafePointer(data) @@ -90,16 +109,6 @@ extension Blob: Binding, Value { } -extension Blob: Printable { - - public var description: String { - let buf = UnsafeBufferPointer(start: UnsafePointer(bytes), count: length) - let hex = join("", map(buf) { String(format: "%02x", $0) }) - return "x'\(hex)'" - } - -} - extension Double: Number, Value { public static var declaredDatatype = "REAL" From 288b6c7788d8c2a7386d7e24ef28c37a7cb0cb1f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 21 Jul 2015 07:59:59 -0400 Subject: [PATCH 0299/1046] Swift 2 Support Swift 2 is an opportunity to revamp the SQLite.swift API alongside the changes the latest version brings to the table. This mostly includes error handling and protocol extensions (most helpful for the expression layer), but also lets us rethink the general project structure. Signed-off-by: Stephen Celis --- .travis.yml | 6 +- CONTRIBUTING.md | 24 +- Documentation/Index.md | 366 +++-- Makefile | 4 +- README.md | 65 +- SQLite Tests/DatabaseTests.swift | 422 ------ SQLite Tests/ExpressionTests.swift | 757 ---------- SQLite Tests/FTSTests.swift | 67 - SQLite Tests/FunctionsTests.swift | 164 -- SQLite Tests/QueryTests.swift | 492 ------ SQLite Tests/RTreeTests.swift | 19 - SQLite Tests/SchemaTests.swift | 372 ----- SQLite Tests/StatementTests.swift | 160 -- SQLite Tests/TestHelper.swift | 83 - SQLite Tests/ValueTests.swift | 13 - SQLite.playground/Contents.swift | 229 +-- SQLite.playground/contents.xcplayground | 2 +- SQLite.swift.podspec | 48 +- SQLite.xcodeproj/project.pbxproj | 1341 ++++++++++++----- .../xcshareddata/SQLite.xccheckout | 53 - .../xcshareddata/SQLite.xcscmblueprint | 30 + .../{SQLite.xcscheme => SQLite Mac.xcscheme} | 48 +- ...iteCipher.xcscheme => SQLite iOS.xcscheme} | 28 +- .../xcschemes/SQLiteCipher Mac.xcscheme | 80 + .../xcschemes/SQLiteCipher iOS.xcscheme | 100 ++ SQLite/Database.swift | 663 -------- SQLite/Expression.swift | 931 ------------ SQLite/FTS.swift | 92 -- SQLite/Functions.swift | 188 --- SQLite/Query.swift | 880 ----------- SQLite/SQLite.xcconfig | 28 - SQLite/Schema.swift | 488 ------ SQLite/Statement.swift | 313 ---- SQLiteCipher Tests/CipherTests.swift | 23 - SQLiteCipher.swift.podspec | 18 + {SQLiteCipher => Source/Cipher}/Cipher.swift | 12 +- Source/Core/Blob.swift | 61 + Source/Core/Connection.swift | 639 ++++++++ {SQLite => Source/Core}/SQLite-Bridging.h | 27 +- {SQLite => Source/Core}/SQLite-Bridging.m | 46 +- Source/Core/Statement.swift | 279 ++++ {SQLite => Source/Core}/Value.swift | 102 +- {SQLite => Source/Core}/fts3_tokenizer.h | 0 Source/Extensions/FTS4.swift | 156 ++ .../Extensions/R*Tree.swift | 18 +- Source/Foundation.swift | 104 ++ Source/Helpers.swift | 122 ++ Source/Typed/AggregateFunctions.swift | 251 +++ Source/Typed/Collation.swift | 69 + Source/Typed/CoreFunctions.swift | 680 +++++++++ Source/Typed/CustomFunctions.swift | 136 ++ Source/Typed/Expression.swift | 139 ++ Source/Typed/Operators.swift | 541 +++++++ Source/Typed/Query.swift | 1121 ++++++++++++++ Source/Typed/Schema.swift | 513 +++++++ Source/Typed/Setter.swift | 275 ++++ Supporting Files/Base.xcconfig | 5 + .../SQLite}/Info.plist | 6 +- {SQLite => Supporting Files/SQLite}/SQLite.h | 4 +- Supporting Files/SQLite/SQLite.xcconfig | 5 + .../SQLite}/module.modulemap | 0 Supporting Files/SQLiteCipher/Info.plist | 26 + Supporting Files/SQLiteCipher/SQLiteCipher.h | 9 + .../SQLiteCipher/SQLiteCipher.xcconfig | 5 + .../SQLiteCipher/module.modulemap | 12 + Supporting Files/Test.xcconfig | 1 + Supporting Files/podspec.rb | 25 + Tests/AggregateFunctionsTests.swift | 64 + Tests/BlobTests.swift | 12 + Tests/CipherTests.swift | 26 + Tests/ConnectionTests.swift | 317 ++++ Tests/CoreFunctionsTests.swift | 136 ++ Tests/CustomFunctionsTests.swift | 6 + Tests/ExpressionTests.swift | 6 + Tests/FTS4Tests.swift | 77 + {SQLite Tests => Tests}/Info.plist | 2 +- Tests/OperatorsTests.swift | 286 ++++ Tests/QueryTests.swift | 324 ++++ Tests/R*TreeTests.swift | 17 + Tests/SchemaTests.swift | 767 ++++++++++ Tests/SetterTests.swift | 137 ++ Tests/StatementTests.swift | 6 + Tests/TestHelpers.swift | 115 ++ Tests/ValueTests.swift | 6 + Vendor/sqlcipher | 2 +- 85 files changed, 9066 insertions(+), 7226 deletions(-) delete mode 100644 SQLite Tests/DatabaseTests.swift delete mode 100644 SQLite Tests/ExpressionTests.swift delete mode 100644 SQLite Tests/FTSTests.swift delete mode 100644 SQLite Tests/FunctionsTests.swift delete mode 100644 SQLite Tests/QueryTests.swift delete mode 100644 SQLite Tests/RTreeTests.swift delete mode 100644 SQLite Tests/SchemaTests.swift delete mode 100644 SQLite Tests/StatementTests.swift delete mode 100644 SQLite Tests/TestHelper.swift delete mode 100644 SQLite Tests/ValueTests.swift delete mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout create mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint rename SQLite.xcodeproj/xcshareddata/xcschemes/{SQLite.xcscheme => SQLite Mac.xcscheme} (70%) rename SQLite.xcodeproj/xcshareddata/xcschemes/{SQLiteCipher.xcscheme => SQLite iOS.xcscheme} (79%) create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme delete mode 100644 SQLite/Database.swift delete mode 100644 SQLite/Expression.swift delete mode 100644 SQLite/FTS.swift delete mode 100644 SQLite/Functions.swift delete mode 100644 SQLite/Query.swift delete mode 100644 SQLite/SQLite.xcconfig delete mode 100644 SQLite/Schema.swift delete mode 100644 SQLite/Statement.swift delete mode 100644 SQLiteCipher Tests/CipherTests.swift create mode 100644 SQLiteCipher.swift.podspec rename {SQLiteCipher => Source/Cipher}/Cipher.swift (80%) create mode 100644 Source/Core/Blob.swift create mode 100644 Source/Core/Connection.swift rename {SQLite => Source/Core}/SQLite-Bridging.h (63%) rename {SQLite => Source/Core}/SQLite-Bridging.m (77%) create mode 100644 Source/Core/Statement.swift rename {SQLite => Source/Core}/Value.swift (59%) rename {SQLite => Source/Core}/fts3_tokenizer.h (100%) create mode 100644 Source/Extensions/FTS4.swift rename SQLite/RTree.swift => Source/Extensions/R*Tree.swift (70%) create mode 100644 Source/Foundation.swift create mode 100644 Source/Helpers.swift create mode 100644 Source/Typed/AggregateFunctions.swift create mode 100644 Source/Typed/Collation.swift create mode 100644 Source/Typed/CoreFunctions.swift create mode 100644 Source/Typed/CustomFunctions.swift create mode 100644 Source/Typed/Expression.swift create mode 100644 Source/Typed/Operators.swift create mode 100644 Source/Typed/Query.swift create mode 100644 Source/Typed/Schema.swift create mode 100644 Source/Typed/Setter.swift create mode 100644 Supporting Files/Base.xcconfig rename {SQLite => Supporting Files/SQLite}/Info.plist (79%) rename {SQLite => Supporting Files/SQLite}/SQLite.h (55%) create mode 100644 Supporting Files/SQLite/SQLite.xcconfig rename {SQLite => Supporting Files/SQLite}/module.modulemap (100%) create mode 100644 Supporting Files/SQLiteCipher/Info.plist create mode 100644 Supporting Files/SQLiteCipher/SQLiteCipher.h create mode 100644 Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig create mode 100644 Supporting Files/SQLiteCipher/module.modulemap create mode 100644 Supporting Files/Test.xcconfig create mode 100644 Supporting Files/podspec.rb create mode 100644 Tests/AggregateFunctionsTests.swift create mode 100644 Tests/BlobTests.swift create mode 100644 Tests/CipherTests.swift create mode 100644 Tests/ConnectionTests.swift create mode 100644 Tests/CoreFunctionsTests.swift create mode 100644 Tests/CustomFunctionsTests.swift create mode 100644 Tests/ExpressionTests.swift create mode 100644 Tests/FTS4Tests.swift rename {SQLite Tests => Tests}/Info.plist (90%) create mode 100644 Tests/OperatorsTests.swift create mode 100644 Tests/QueryTests.swift create mode 100644 Tests/R*TreeTests.swift create mode 100644 Tests/SchemaTests.swift create mode 100644 Tests/SetterTests.swift create mode 100644 Tests/StatementTests.swift create mode 100644 Tests/TestHelpers.swift create mode 100644 Tests/ValueTests.swift diff --git a/.travis.yml b/.travis.yml index d8897e27..37785da9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: objective-c env: - - BUILD_SDK=iphonesimulator ONLY_ACTIVE_ARCH=NO - - BUILD_SDK=macosx + - BUILD_SCHEME="SQLite iOS" + - BUILD_SCHEME="SQLite Mac" before_install: - gem install xcpretty --no-document script: - make test -osx_image: beta-xcode6.3 +osx_image: xcode7 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01793a0c..60c18370 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,29 +54,7 @@ first. While Swift error messaging is improving with each release, complex expressions still lend themselves to misleading errors. If you encounter an error on a complex line, breaking it down into smaller pieces generally - yields a more understandable error. _E.g._: - - ``` swift - users.insert(email <- "alice@mac.com" <- managerId <- db.lastInsertRowid) - // Cannot invoke 'insert' with an argument list of type '(Setter, Setter)' - ``` - - Not very helpful! If we break the expression down into smaller parts, - however, the real error materializes on the appropriate line. - - ``` swift - let emailSetter = email <- "alice@mac.com" - let managerIdSetter = managerId <- db.lastInsertRowId - // Binary operator '<-' cannot be applied to operands of type 'Expression' and 'Int64' - users.insert(emailSetter, managerIdSetter) - ``` - - The problem turns out to be a simple type mismatch. The fix is elsewhere: - - ``` diff - -let managerId = Expression("manager_id") - +let managerId = Expression("manager_id") - ``` + yields a more understandable error. - **Is it an _even more_ unhelpful build error?** diff --git a/Documentation/Index.md b/Documentation/Index.md index 3f815ca9..4b1b6c67 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -65,7 +65,7 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode 6.3](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 2 (and [Xcode 7](https://developer.apple.com/xcode/downloads/)) or greater. ### CocoaPods @@ -78,8 +78,13 @@ ``` ruby use_frameworks! - pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' - # pod 'SQLite.swift/Cipher', git: ... # instead, for SQLCipher support + + pod 'SQLite.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' + + # instead, for SQLCipher support + pod 'SQLiteCipher.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' ``` 3. Run `pod install`. @@ -153,7 +158,7 @@ import SQLite Database connections are established using the `Database` class. A database is initialized with a path. SQLite will attempt to create the database file if it does not already exist. ``` swift -let db = Database("path/to/db.sqlite3") +let db = try Connection("path/to/db.sqlite3") ``` @@ -164,9 +169,9 @@ On iOS, you can create a writable database in your app’s **Documents** directo ``` swift let path = NSSearchPathForDirectoriesInDomains( .DocumentDirectory, .UserDomainMask, true -).first as! String +).first! -let db = Database("\(path)/db.sqlite3") +let db = try Connection("\(path)/db.sqlite3") ``` On OS X, you can use your app’s **Application Support** directory: @@ -174,14 +179,14 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( .ApplicationSupportDirectory, .UserDomainMask, true -).first as! String + NSBundle.mainBundle().bundleIdentifier! +).first! + NSBundle.mainBundle().bundleIdentifier! // create parent directory iff it doesn’t exist -NSFileManager.defaultManager().createDirectoryAtPath( - path, withIntermediateDirectories: true, attributes: nil, error: nil +try NSFileManager.defaultManager().createDirectoryAtPath( + path, withIntermediateDirectories: true, attributes: nil ) -let db = Database("\(path)/db.sqlite3") +let db = try Connection("\(path)/db.sqlite3") ``` @@ -192,7 +197,7 @@ If you bundle a database with your app (_i.e._, you’ve copied a database file ``` swift let path = NSBundle.mainBundle().pathForResource("db", ofType: "sqlite3")! -let db = Database(path, readonly: true) +let db = try Connection(path, readonly: true) ``` > _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). @@ -203,13 +208,13 @@ let db = Database(path, readonly: true) If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). ``` swift -let db = Database() // equivalent to `Database(":memory:")` +let db = try Connection() // equivalent to `Connection(.InMemory)` ``` To create a temporary, disk-backed database, pass an empty file name. ``` swift -let db = Database("") +let db = try Connection(.Temporary) ``` In-memory databases are automatically deleted when the database connection is closed. @@ -217,7 +222,7 @@ In-memory databases are automatically deleted when the database connection is cl ### A Note on Thread-Safety -> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints, however, do not block other threads from executing statements within the transaction. +> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. ## Building Type-Safe SQL @@ -240,7 +245,7 @@ SQLite.swift comes with a typed expression layer that directly maps [Swift types > > See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed layer and execute raw SQL, instead. -These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`Query`](#queries)), can create and execute SQL statements. +These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`QueryType`](#queries)), can create and execute SQL statements. ### Expressions @@ -260,7 +265,7 @@ Use optional generics for expressions that can evaluate to `NULL`. let name = Expression("name") ``` -> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. +> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. ### Compound Expressions @@ -270,10 +275,10 @@ Expressions can be combined with other expressions and types using [filter opera ### Queries -Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a `Query` by subscripting a database connection with a table name. +Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a query by initializing a `Table`, `View`, or `VirtualTable`. ``` swift -let users = db["users"] +let users = Table("users") ``` Assuming [the table exists](#creating-a-table), we can immediately [insert](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and [delete](#deleting-rows) rows. @@ -281,14 +286,14 @@ Assuming [the table exists](#creating-a-table), we can immediately [insert](#ins ## Creating a Table -We can run [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create(table:)` function on a database connection. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. +We can build [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create` function on a `Table`. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. ``` swift -db.create(table: users) { t in // CREATE TABLE "users" ( +try db.run(users.create { t in // CREATE TABLE "users" ( t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL, t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL, t.column(name) // "name" TEXT -} // ) +}) // ) ``` > _Note:_ `Expression` structures (in this case, the `id` and `email` columns), generate `NOT NULL` constraints automatically, while `Expression` structures (`name`) do not. @@ -296,19 +301,19 @@ db.create(table: users) { t in // CREATE TABLE "users" ( ### Create Table Options -The `create(table:)` function has several default parameters we can override. +The `Table.create` function has several default parameters we can override. - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to create a temporary table that will automatically drop when the database connection closes). Default: `false`. ``` swift - db.create(table: users, temporary: true) { t in /* ... */ } + try db.run(users.create(temporary: true) { t in /* ... */ }) // CREATE TEMPORARY TABLE "users" -- ... ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift - db.create(table: users, ifNotExists: true) { t in /* ... */ } + try db.run(users.create(ifNotExists: true) { t in /* ... */ }) // CREATE TABLE "users" IF NOT EXISTS -- ... ``` @@ -342,7 +347,7 @@ The `column` function is used for a single column definition. It takes an [expre - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).) ``` swift - t.column(email, check: like("%@%", email)) + t.column(email, check: email.like("%@%")) // "email" TEXT NOT NULL CHECK ("email" LIKE '%@%') ``` @@ -365,16 +370,18 @@ The `column` function is used for a single column definition. It takes an [expre // "name" TEXT COLLATE "RTRIM" ``` - - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`SchemaType`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift - t.column(user_id, references: users[id]) - // "user_id" INTEGER REFERENCES "users"("id") + t.column(user_id, references: users, id) + // "user_id" INTEGER REFERENCES "users" ("id") + > _Note:_ The `references` parameter cannot be used alongside `primaryKey` and `defaultValue`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). @@ -404,10 +411,10 @@ Additional constraints may be provided outside the scope of a single column usin // CHECK ("balance" >= 0.0) ``` - - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, and both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. + - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. ``` swift - t.foreignKey(user_id, on: users[id], delete: .SetNull) + t.foreignKey(user_id, references: users, id, delete: .SetNull) // FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL ``` @@ -421,21 +428,21 @@ Additional constraints may be provided outside the scope of a single column usin We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. ``` swift -users.insert(email <- "alice@mac.com", name <- "Alice") +try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') -users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.") +try db.run(users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.")) // INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` -The `insert` function returns a tuple with an `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure) and the associated `Statement`. +The `insert` function, when run successfully, returns an `Int64` representing the inserted row’s [`ROWID`][ROWID]. ``` swift -let insert = users.insert(email <- "alice@mac.com") -if let rowid = insert.rowid { - println("inserted id: \(rowid)") -} else if insert.statement.failed { - println("insertion failed: \(insert.statement.reason)") +do { + let rowid = try db.run(users.insert(email <- "alice@mac.com")) + print("inserted id: \(rowid)") +} catch { + print("insertion failed: \(error)") } ``` @@ -444,7 +451,7 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s > _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values. > > ``` swift -> timestamps.insert() +> try db.run(timestamps.insert()) > // INSERT INTO "timestamps" DEFAULT VALUES > ``` @@ -454,8 +461,8 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s SQLite.swift typically uses the `<-` operator to set values during [inserts](#inserting-rows) and [updates](#updating-rows). ``` swift -views.update(count <- 0) -// UPDATE "views" SET "count" = 0 WHERE ("id" = 1) +try db.run(counter.update(count <- 0)) +// UPDATE "counters" SET "count" = 0 WHERE ("id" = 1) ``` There are also a number of convenience setters that take the existing value into account using native Swift operators. @@ -463,22 +470,22 @@ There are also a number of convenience setters that take the existing value into For example, to atomically increment a column, we can use `++`: ``` swift -views.update(count++) // equivalent to `views.update(count -> count + 1)` -// UPDATE "views" SET "count" = "count" + 1 WHERE ("id" = 1) +try db.run(counter.update(count++)) // equivalent to `counter.update(count -> count + 1)` +// UPDATE "counters" SET "count" = "count" + 1 WHERE ("id" = 1) ``` To take an amount and “move” it via transaction, we can use `-=` and `+=`: ``` swift let amount = 100.0 -db.transaction() - && alice.update(balance -= amount) - && betty.update(balance += amount) - && db.commit() || db.rollback() -// BEGIN DEFERRED TRANSACTION; -// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1); -// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2); -// COMMIT TRANSACTION; +try db.transaction { + try db.run(alice.update(balance -= amount)) + try db.run(betty.update(balance += amount)) +} +// BEGIN DEFERRED TRANSACTION +// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1) +// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2) +// COMMIT TRANSACTION ``` @@ -510,16 +517,16 @@ db.transaction() ## Selecting Rows -[`Query` structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. +[Query structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. ### Iterating and Accessing Values -[Queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. +Prepared [queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. ``` swift -for user in users { - println("id: \(user[id]), email: \(user[email]), name: \(user[name])") +for user in try db.prepare(users) { + print("id: \(user[id]), email: \(user[email]), name: \(user[name])") // id: 1, email: alice@mac.com, name: Optional("Alice") } // SELECT * FROM "users" @@ -530,24 +537,24 @@ for user in users { ### Plucking Rows -We can pluck the first row by calling the `first` computed property on [`Query`](#queries). +We can pluck the first row by passing a query to the `pluck` function on a database connection. ``` swift -if let user = users.first { /* ... */ } // Row +if let user = try db.pluck(users) { /* ... */ } // Row // SELECT * FROM "users" LIMIT 1 ``` To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea). ``` swift -let all = Array(users) +let all = Array(try db.prepare(users)) // SELECT * FROM "users" ``` ### Building Complex Queries -[`Query`](#queries) structures have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. +[Queries](#queries) have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. ``` swift let query = users.select(email) // SELECT "email" FROM "users" @@ -559,11 +566,11 @@ let query = users.select(email) // SELECT "email" FROM "users" #### Selecting Columns -By default, [`Query`](#queries) objects select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. +By default, [queries](#queries) select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. ``` swift -for user in users.select(id, email) { - println("id: \(user[id]), email: \(user[email])") +for user in try db.prepare(users.select(id, email)) { + print("id: \(user[id]), email: \(user[email])") // id: 1, email: alice@mac.com } // SELECT "id", "email" FROM "users" @@ -574,7 +581,7 @@ We can access the results of more complex expressions by holding onto a referenc ``` swift let sentence = name + " is " + cast(age) as Expression + " years old!" for user in users.select(sentence) { - println(user[sentence]) + print(user[sentence]) // Optional("Alice is 30 years old!") } // SELECT ((("name" || ' is ') || CAST ("age" AS TEXT)) || ' years old!') FROM "users" @@ -634,7 +641,7 @@ let query = users.join(managers, on: managers[id] == users[managerId]) If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. ``` swift -let user = query.first! +let user = try db.pluck(query) user[id] // fatal error: ambiguous column 'id' // (please disambiguate: ["users"."id", "managers"."id"]) @@ -651,13 +658,13 @@ SQLite.swift filters rows using a [query’s](#queries) `filter` function with a users.filter(id == 1) // SELECT * FROM "users" WHERE ("id" = 1) -users.filter(contains([1, 2, 3, 4, 5], id)) +users.filter([1, 2, 3, 4, 5].contains(id)) // SELECT * FROM "users" WHERE ("id" IN (1, 2, 3, 4, 5)) -users.filter(like("%@mac.com", email)) +users.filter(email.like("%@mac.com")) // SELECT * FROM "users" WHERE ("email" LIKE '%@mac.com') -users.filter(verified && lower(name) == "alice") +users.filter(verified && name.lowercaseString == "alice") // SELECT * FROM "users" WHERE ("verified" AND (lower("name") == 'alice')) users.filter(verified || balance >= 10_000) @@ -749,68 +756,68 @@ users.limit(5, offset: 5) #### Aggregation -[`Query`](#queries) structures come with a number of functions that quickly return aggregate values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. +[Queries](#queries) come with a number of functions that quickly return aggregate scalar values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. ``` swift -users.count +let count = try db.scalar(users.count) // SELECT count(*) FROM "users" ``` Filtered queries will appropriately filter aggregate values. ``` swift -users.filter(name != nil).count +let count = try db.scalar(users.filter(name != nil).count) // SELECT count(*) FROM "users" WHERE "name" IS NOT NULL ``` - - `count` as a computed property (see examples above) returns the total number of rows matching the query. + - `count` as a computed property on a query (see examples above) returns the total number of rows matching the query. - `count` as a function takes a [column name](#expressions) and returns the total number of rows where that column is not `NULL`. + `count` as a computed property on a column expression returns the total number of rows where that column is not `NULL`. ``` swift - users.count(name) // -> Int + let count = try db.scalar(users.select(name.count)) // -> Int // SELECT count("name") FROM "users" ``` - `max` takes a comparable column expression and returns the largest value if any exists. ``` swift - users.max(id) // -> Int64? + let max = try db.scalar(users.select(id.max)) // -> Int64? // SELECT max("id") FROM "users" ``` - `min` takes a comparable column expression and returns the smallest value if any exists. ``` swift - users.min(id) // -> Int64? + let min = try db.scalar(users.select(id.min)) // -> Int64? // SELECT min("id") FROM "users" ``` - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists. ``` swift - users.average(balance) // -> Double? + let average = try db.scalar(users.select(balance.average)) // -> Double? // SELECT avg("balance") FROM "users" ``` - `sum` takes a numeric column expression and returns the sum total of all rows if any exist. ``` swift - users.sum(balance) // -> Double? + let sum = try db.scalar(users.select(balance.sum)) // -> Double? // SELECT sum("balance") FROM "users" ``` - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query. ``` swift - users.total(balance) // -> Double + let total = try db.scalar(users.select(balance.total)) // -> Double // SELECT total("balance") FROM "users" ``` -> _Note:_ Most of the above aggregate functions (except `max` and `min`) can be called with a `distinct` parameter to aggregate `DISTINCT` values only. +> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the `distinct` computed property. > > ``` swift -> users.count(distinct: name) +> let count = try db.scalar(users.select(name.distinct.count) // -> Int > // SELECT count(DISTINCT "name") FROM "users" > ``` @@ -822,7 +829,7 @@ We can update a table’s rows by calling a [query’s](#queries) `update` funct When an unscoped query calls `update`, it will update _every_ row in the table. ``` swift -users.update(email <- "alice@me.com") +try db.run(users.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' ``` @@ -830,18 +837,21 @@ Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) -alice.update(email <- "alice@me.com") +try db.run(alice.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1) ``` -The `update` function returns a tuple with an `Int?` representing the number of updates (or `nil` on failure) and the associated `Statement`. +The `update` function returns an `Int` representing the number of updated rows. ``` swift -let update = alice.update(email <- "alice@me.com") -if let changes = update.changes where changes > 0 { - println("updated alice") -} else if update.statement.failed { - println("update failed: \(update.statement.reason)") +do { + if try db.run(alice.update(email <- "alice@me.com")) > 0 { + print("updated alice") + } else { + print("alice not found") + } +} catch { + print("update failed: \(error)") } ``` @@ -853,7 +863,7 @@ We can delete rows from a table by calling a [query’s](#queries) `delete` func When an unscoped query calls `delete`, it will delete _every_ row in the table. ``` swift -users.delete() +try db.run(users.delete()) // DELETE FROM "users" ``` @@ -861,91 +871,83 @@ Be sure to scope `DELETE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) -alice.delete() +try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) ``` -The `delete` function returns a tuple with an `Int?` representing the number of deletes (or `nil` on failure) and the associated `Statement`. +The `delete` function returns an `Int` representing the number of deleted rows. ``` swift -let delete = alice.delete() -if let changes = delete.changes where changes > 0 { - println("deleted alice") -} else if delete.statement.failed { - println("delete failed: \(delete.statement.reason)") +do { + if try db.run(alice.delete()) > 0 { + print("deleted alice") + } else { + print("alice not found") + } +} catch { + print("delete failed: \(error)") } ``` ## Transactions and Savepoints -Using the `transaction` and `savepoint` functions, we can run a series of statements chained together (using `&&`). If a single statement fails, we can short-circuit the series (using `||`) and roll back the changes. +Using the `transaction` and `savepoint` functions, we can run a series of statements in a transaction. If a single statement fails or the block throws an error, the changes will be rolled back. ``` swift -db.transaction() - && users.insert(email <- "betty@icloud.com") - && users.insert(email <- "cathy@icloud.com", managerId <- db.lastInsertRowid) - && db.commit() || db.rollback() -``` - -> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This is why we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. - -For more complex transactions and savepoints, block helpers exist. Using a block helper, the former statement can be written (more verbosely) as follows: - -``` swift -db.transaction { txn in - if let rowid = users.insert(email <- "betty@icloud.com").rowid { - if users.insert(email <- "cathy@icloud.com", managerId <- db.lastInsertRowid).rowid != nil { - return .Commit - } - } - return .Rollback +try db.transaction { + let rowid = try db.run(users.insert(email <- "betty@icloud.com")) + try db.run(users.insert(email <- "cathy@icloud.com", managerId <- rowid)) } +// BEGIN DEFERRED TRANSACTION +// INSERT INTO "users" ("email") VALUES ('betty@icloud.com') +// INSERT INTO "users" ("email", "manager_id") VALUES ('cathy@icloud.com', 2) +// COMMIT TRANSACTION ``` +> _Note:_ Transactions run in a serial queue. + ## Altering the Schema -SQLite.swift comes with several functions (in addition to `create(table:)`) for altering a database schema in a type-safe manner. +SQLite.swift comes with several functions (in addition to `Table.create`) for altering a database schema in a type-safe manner. ### Renaming Tables -We can rename a table by calling the `rename(table:to:)` function on a database connection. +We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` function on a `Table` or `VirtualTable`. ``` swift -db.rename(users, to: "users_old") +try db.run(users.rename(Table("users_old")) // ALTER TABLE "users" RENAME TO "users_old" ``` ### Adding Columns -We can add columns to a table by calling `alter` function on a database connection. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. +We can add columns to a table by calling `addColumn` function on a `Table`. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. ``` swift -db.alter(table: users, add: suffix) +try db.run(users.addColumn(suffix)) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` #### Added Column Constraints -The `alter` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). +The `addColumn` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). (See also the `check` function under [Table Constraints](#table-constraints).) ``` swift - let check = contains(["JR", "SR"], suffix) - db.alter(table: users, add: suffix, check: check) - // ALTER TABLE "users" - // ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) + try db.run(users.addColumn(suffix, check: ["JR", "SR"].contains(suffix))) + // ALTER TABLE "users" ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) ``` - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). ``` swift - db.alter(table: users, add: suffix, defaultValue: "SR") + try db.run(users.addColumn(suffix, defaultValue: "SR")) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT DEFAULT 'SR' ``` @@ -954,24 +956,18 @@ The `alter` function shares several of the same [`column` function parameters](# - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - t.alter(table: users, add: email, collate: .Nocase) - // ALTER TABLE "users" - // ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" + try db.run(users.addColumn(email, collate: .Nocase)) + // ALTER TABLE "users" ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" - t.alter(table: users, add: name, collate: .Rtrim) - // ALTER TABLE "users" - // ADD COLUMN "name" TEXT COLLATE "RTRIM" + try db.run(users.addColumn(name, collate: .Rtrim)) + // ALTER TABLE "users" ADD COLUMN "name" TEXT COLLATE "RTRIM" ``` - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift - db.alter(table: posts, add: user_id, references: users[id]) - // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users"("id") - - db.alter(table: posts, add: user_id, references: users) - // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" - // -- assumes "users" has a PRIMARY KEY + try db.run(posts.addColumn(userId, references: users, id) + // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` @@ -980,67 +976,67 @@ The `alter` function shares several of the same [`column` function parameters](# #### Creating Indexes -We can run [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `create(index:)` function on a database connection. +We can build [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `createIndex` function on a `SchemaType`. ``` swift -db.create(index: users, on: email) +try db.run(users.createIndex(email)) // CREATE INDEX "index_users_on_email" ON "users" ("email") ``` The index name is generated automatically based on the table and column names. -The `create(index:)` function has a couple default parameters we can override. +The `createIndex` function has a couple default parameters we can override. - `unique` adds a `UNIQUE` constraint to the index. Default: `false`. ``` swift - db.create(index: users, on: email, unique: true) + try db.run(users.createIndex(email, unique: true)) // CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email") ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift - db.create(index: users, on: email, ifNotExists: true) + try db.run(users.createIndex(email, ifNotExists: true)) // CREATE INDEX IF NOT EXISTS "index_users_on_email" ON "users" ("email") ``` #### Dropping Indexes -We can run [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `drop(index:)` function on a database connection. +We can build [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `dropIndex` function on a `SchemaType`. ``` swift -db.drop(index: users, on: email) +try db.run(users.dropIndex(email)) // DROP INDEX "index_users_on_email" ``` -The `drop(index:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `dropIndex` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. ``` swift -db.drop(index: users, on: email, ifExists: true) +try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` ### Dropping Tables -We can run [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `drop(table:)` function on a database connection. +We can build [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `dropTable` function on a `SchemaType`. ``` swift -db.drop(table: users) +try db.run(users.drop()) // DROP TABLE "users" ``` -The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `drop` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. ``` swift -db.drop(table: users, ifExists: true) +try db.run(users.drop(ifExists: true)) // DROP TABLE IF EXISTS "users" ``` -### Migrations and Schema Versioning + ## Custom Types @@ -1267,16 +1263,16 @@ Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.htm ## Custom SQL Functions -We can create custom SQL functions by calling `create(function:)` on a database connection. +We can create custom SQL functions by calling `createFunction` on a database connection. For example, to give queries access to [`MobileCoreServices.UTTypeConformsTo`](https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo), we can write the following: ``` swift import MobileCoreServices -let typeConformsTo: (String, Expression) -> Expression = ( - db.create(function: "typeConformsTo", deterministic: true) { UTI, conformsToUTI in - return UTTypeConformsTo(UTI, conformsToUTI) != 0 +let typeConformsTo: (Expression, String) -> Expression = ( + try db.createFunction("typeConformsTo", deterministic: true) { UTI, conformsToUTI in + return UTTypeConformsTo(UTI, conformsToUTI) } ) ``` @@ -1289,7 +1285,7 @@ Note `typeConformsTo`’s signature: (Expression, String) -> Expression ``` -Because of this, `create(function:)` expects a block with the following signature: +Because of this, `createFunction` expects a block with the following signature: ``` swift (String, String) -> Bool @@ -1298,10 +1294,10 @@ Because of this, `create(function:)` expects a block with the following signatur Once assigned, the closure can be called wherever boolean expressions are accepted. ``` swift -let attachments = db["attachments"] +let attachments = Table("attachments") let UTI = Expression("UTI") -attachments.filter(typeConformsTo(UTI, kUTTypeImage)) +let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage)) // SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` @@ -1310,28 +1306,26 @@ attachments.filter(typeConformsTo(UTI, kUTTypeImage)) We can create loosely-typed functions by handling an array of raw arguments, instead. ``` swift -db.create(function: "typeConformsTo", deterministic: true) { args in - if let UTI = args[0] as? String, conformsToUTI = args[1] as? String { - return Int(UTTypeConformsTo(UTI, conformsToUTI)) - } - return nil +db.createFunction("typeConformsTo", deterministic: true) { args in + guard let UTI = args[0] as? String, conformsToUTI = args[1] as? String else { return nil } + return UTTypeConformsTo(UTI, conformsToUTI) } ``` Creating a loosely-typed function cannot return a closure and instead must be wrapped manually or executed [using raw SQL](#executing-arbitrary-sql). ``` swift -let stmt = db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") +let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` ## Custom Collations -We can create custom collating sequences by calling `create(collation:)` on a database connection. +We can create custom collating sequences by calling `createCollation` on a database connection. ``` swift -db.create(collation: "NODIACRITIC") { lhs, rhs in +try db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } ``` @@ -1346,36 +1340,36 @@ restaurants.order(collate(.Custom("NODIACRITIC"), name)) ## Full-text Search -We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create(vtable:)` on a database connection. +We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create` on a `VirtualTable`. ``` swift -let emails = db["emails"] +let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") -db.create(vtable: emails, using: fts4(subject, body)) +try db.run(emails.create(.FTS4(subject, body))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body") ``` We can specify a [tokenizer](http://www.sqlite.org/fts3.html#tokenizer) using the `tokenize` parameter. ``` swift -db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) +try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) ``` Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. ``` swift -emails.insert( +try db.run(emails.insert( subject <- "Just Checking In", body <- "Hey, I was just wondering...did you get my last email?" -)! +)) -emails.filter(match(emails, "wonder*")) +let wonderfulEmails = emails.match("wonder*") // SELECT * FROM "emails" WHERE "emails" MATCH 'wonder*' -emails.filter(match(subject, "Re:*")) +let replies = emails.filter(subject.match("Re:*")) // SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*' ``` @@ -1387,7 +1381,7 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `execute` runs an arbitrary number of SQL statements as a convenience. ``` swift - db.execute( + try db.execute( "BEGIN TRANSACTION;" + "CREATE TABLE users (" + "id INTEGER PRIMARY KEY NOT NULL," + @@ -1408,22 +1402,22 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `prepare` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), and returns the statement for deferred execution. ``` swift - let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") ``` Once prepared, statements may be executed using `run`, binding any unbound parameters. ``` swift - stmt.run("alice@mac.com") + try stmt.run("alice@mac.com") db.changes // -> {Some 1} ``` Statements with results may be iterated over. ``` swift - let stmt = db.prepare("SELECT id, email FROM users") + let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { - println("id: \(row[0]), email: \(row[1])") + print("id: \(row[0]), email: \(row[1])") // id: Optional(1), email: Optional("alice@mac.com") } ``` @@ -1431,20 +1425,20 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. ``` swift - db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") + try db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") ``` - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. ``` swift - db.scalar("SELECT count(*) FROM users") as! Int64 + let count = try db.scalar("SELECT count(*) FROM users") as! Int64 ``` Statements also have a `scalar` function, which can optionally re-bind values at execution. ``` swift - let stmt = db.prepare("SELECT count (*) FROM users") - stmt.scalar() as! Int64 + let stmt = try db.prepare("SELECT count (*) FROM users") + let count = try stmt.scalar() as! Int64 ``` @@ -1454,7 +1448,7 @@ We can log SQL using the database’s `trace` function. ``` swift #if DEBUG - db.trace(println) + db.trace(print) #endif ``` diff --git a/Makefile b/Makefile index 7fe0d757..ff0a2054 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild -BUILD_SDK = macosx -BUILD_ARGUMENTS = -scheme SQLite -sdk $(BUILD_SDK) +BUILD_SCHEME = SQLite Mac +BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" XCPRETTY := $(shell command -v xcpretty) SWIFTCOV := $(shell command -v swiftcov) diff --git a/README.md b/README.md index d80951f3..a155e0cd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A type-safe, [Swift][]-language layer over [SQLite3][]. [SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. -[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat +[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/swift-2.svg?style=flat [Travis]: https://travis-ci.org/stephencelis/SQLite.swift [Swift]: https://developer.apple.com/swift/ [SQLite3]: http://www.sqlite.org @@ -34,46 +34,44 @@ syntax _and_ intent. ``` swift import SQLite -let db = Database("path/to/db.sqlite3") +let db = try Connection("path/to/db.sqlite3") -let users = db["users"] +let users = Table("users") let id = Expression("id") let name = Expression("name") let email = Expression("email") -db.create(table: users) { t in +try db.run(users.create { t in t.column(id, primaryKey: true) t.column(name) t.column(email, unique: true) -} +}) // CREATE TABLE "users" ( // "id" INTEGER PRIMARY KEY NOT NULL, // "name" TEXT, // "email" TEXT NOT NULL UNIQUE // ) -var alice: Query? -if let rowid = users.insert(name <- "Alice", email <- "alice@mac.com").rowid { - println("inserted id: \(rowid)") - // inserted id: 1 - alice = users.filter(id == rowid) -} +let insert = users.insert(name <- "Alice", email <- "alice@mac.com") +let rowid = try db.run(insert) // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') -for user in users { +for user in db.prepare(users) { println("id: \(user[id]), name: \(user[name]), email: \(user[email])") // id: 1, name: Optional("Alice"), email: alice@mac.com } // SELECT * FROM "users" -alice?.update(email <- replace(email, "mac.com", "me.com")) +let alice = users.filter(id == rowid) + +try db.run(alice.update(email <- email.replace("mac.com", "me.com"))) // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') // WHERE ("id" = 1) -alice?.delete() +try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) -users.count +db.scalar(users.count) // 0 // SELECT count(*) FROM "users" ``` @@ -107,8 +105,7 @@ interactively, from the Xcode project’s playground. ## Installation -> _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode][] 6.3) or -> greater. +> _Note:_ SQLite.swift requires Swift 2 (and [Xcode][] 7) or greater. > > The following instructions apply to targets that support embedded > Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line @@ -116,20 +113,46 @@ interactively, from the Xcode project’s playground. > documentation. +### Carthage + +[Carthage][] is a simple, decentralized dependency manager for Cocoa. To +install SQLite.swift with Carthage: + + 1. Make sure Carthage is [installed][Carthage Installation]. + + 2. Update your Cartfile to include the following: + + ``` + github "stephencelis/SQLite.swift" "swift-2" + ``` + + 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. + + +[Carthage]: https://github.com/Carthage/Carthage +[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage +[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application + + ### CocoaPods [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift - requires version 0.37 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift + requires version 0.37 or greater.) 2. Update your Podfile to include the following: ``` ruby use_frameworks! - pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' - # pod 'SQLite.swift/Cipher', git: ... # instead, for SQLCipher support + + pod 'SQLite.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' + + # instead, for SQLCipher support + pod 'SQLiteCipher.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' ``` 3. Run `pod install`. diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift deleted file mode 100644 index b535bc76..00000000 --- a/SQLite Tests/DatabaseTests.swift +++ /dev/null @@ -1,422 +0,0 @@ -import XCTest -import SQLite - -class DatabaseTests: SQLiteTestCase { - - override func setUp() { - super.setUp() - - createUsersTable() - } - - func test_init_withInMemory_returnsInMemoryConnection() { - let db = Database(.InMemory) - XCTAssertEqual("", db.description) - XCTAssertEqual(":memory:", Database.Location.InMemory.description) - } - - func test_init_returnsInMemoryByDefault() { - let db = Database() - XCTAssertEqual("", db.description) - } - - func test_init_withTemporary_returnsTemporaryConnection() { - let db = Database(.Temporary) - XCTAssertEqual("", db.description) - } - - func test_init_withURI_returnsURIConnection() { - let db = Database(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) - } - - func test_init_withString_returnsURIConnection() { - let db = Database("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) - } - - func test_readonly_returnsFalseOnReadWriteConnections() { - XCTAssert(!db.readonly) - } - - func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = Database(readonly: true) - XCTAssert(db.readonly) - } - - func test_lastInsertRowid_returnsNilOnNewConnections() { - XCTAssert(db.lastInsertRowid == nil) - } - - func test_lastInsertRowid_returnsLastIdAfterInserts() { - insertUser("alice") - XCTAssert(db.lastInsertRowid! == 1) - } - - func test_changes_returnsZeroOnNewConnections() { - XCTAssertEqual(0, db.changes) - } - - func test_changes_returnsNumberOfChanges() { - insertUser("alice") - XCTAssertEqual(1, db.changes) - insertUser("betsy") - XCTAssertEqual(1, db.changes) - } - - func test_totalChanges_returnsTotalNumberOfChanges() { - XCTAssertEqual(0, db.totalChanges) - insertUser("alice") - XCTAssertEqual(1, db.totalChanges) - insertUser("betsy") - XCTAssertEqual(2, db.totalChanges) - } - - func test_prepare_preparesAndReturnsStatements() { - db.prepare("SELECT * FROM users WHERE admin = 0") - db.prepare("SELECT * FROM users WHERE admin = ?", 0) - db.prepare("SELECT * FROM users WHERE admin = ?", [0]) - db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) - // no-op assert-nothing-asserted - } - - func test_run_preparesRunsAndReturnsStatements() { - db.run("SELECT * FROM users WHERE admin = 0") - db.run("SELECT * FROM users WHERE admin = ?", 0) - db.run("SELECT * FROM users WHERE admin = ?", [0]) - db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) - AssertSQL("SELECT * FROM users WHERE admin = 0", 4) - } - - func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as! Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as! Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as! Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as! Int64) - AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) - } - - func test_transaction_executesBeginDeferred() { - db.transaction(.Deferred) { _ in .Commit } - - AssertSQL("BEGIN DEFERRED TRANSACTION") - } - - func test_transaction_executesBeginImmediate() { - db.transaction(.Immediate) { _ in .Commit } - - AssertSQL("BEGIN IMMEDIATE TRANSACTION") - } - - func test_transaction_executesBeginExclusive() { - db.transaction(.Exclusive) { _ in .Commit } - - AssertSQL("BEGIN EXCLUSIVE TRANSACTION") - } - - func test_commit_commitsTransaction() { - db.transaction() - - db.commit() - - AssertSQL("COMMIT TRANSACTION") - } - - func test_rollback_rollsTransactionBack() { - db.transaction() - - db.rollback() - - AssertSQL("ROLLBACK TRANSACTION") - } - - func test_transaction_beginsAndCommitsTransactions() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.transaction { _ in stmt.run().failed ? .Rollback : .Commit } - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)") - AssertSQL("COMMIT TRANSACTION") - AssertSQL("ROLLBACK TRANSACTION", 0) - } - - func test_transaction_beginsAndRollsTransactionsBack() { - let stmt = db.run("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.transaction { _ in stmt.run().failed ? .Rollback : .Commit } - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - - func test_transaction_withOperators_allowsForFlowControl() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - let txn = db.transaction() && - stmt.bind("alice@example.com", 1) && - stmt.bind("alice@example.com", 1) && - stmt.bind("alice@example.com", 1) && - db.commit() - txn || db.rollback() - - XCTAssertTrue(txn.failed) - XCTAssert(txn.reason!.lowercaseString.rangeOfString("unique") != nil) - - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - - func test_savepoint_quotesSavepointNames() { - db.savepoint("That's all, Folks!") - - AssertSQL("SAVEPOINT 'That''s all, Folks!'") - } - - func test_release_quotesSavepointNames() { - let savepointName = "That's all, Folks!" - db.savepoint(savepointName) - - db.release(savepointName) - - AssertSQL("RELEASE SAVEPOINT 'That''s all, Folks!'") - } - - func test_release_defaultsToCurrentSavepointName() { - db.savepoint("Hello, World!") - - db.release() - - AssertSQL("RELEASE SAVEPOINT 'Hello, World!'") - } - - func test_release_maintainsTheSavepointNameStack() { - db.savepoint("1") - db.savepoint("2") - db.savepoint("3") - - db.release("2") - db.release() - - AssertSQL("RELEASE SAVEPOINT '2'") - AssertSQL("RELEASE SAVEPOINT '1'") - AssertSQL("RELEASE SAVEPOINT '3'", 0) - } - - func test_rollback_quotesSavepointNames() { - let savepointName = "That's all, Folks!" - db.savepoint(savepointName) - - db.rollback(savepointName) - - AssertSQL("ROLLBACK TO SAVEPOINT 'That''s all, Folks!'") - } - - func test_rollback_defaultsToCurrentSavepointName() { - db.savepoint("Hello, World!") - - db.rollback() - - AssertSQL("ROLLBACK TO SAVEPOINT 'Hello, World!'") - } - - func test_rollback_maintainsTheSavepointNameStack() { - db.savepoint("1") - db.savepoint("2") - db.savepoint("3") - - db.rollback("2") - db.rollback() - - AssertSQL("ROLLBACK TO SAVEPOINT '2'") - AssertSQL("ROLLBACK TO SAVEPOINT '1'") - AssertSQL("ROLLBACK TO SAVEPOINT '3'", 0) - } - - func test_savepoint_beginsAndReleasesTransactions() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.savepoint("1") { _ in stmt.run().failed ? .Rollback : .Release } - - AssertSQL("SAVEPOINT '1'") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)") - AssertSQL("RELEASE SAVEPOINT '1'") - AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) - } - - func test_savepoint_beginsAndRollsTransactionsBack() { - let stmt = db.run("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.savepoint("1") { _ in stmt.run().failed ? .Rollback : .Release } - - AssertSQL("SAVEPOINT '1'") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - AssertSQL("ROLLBACK TO SAVEPOINT '1'", 1) - AssertSQL("RELEASE SAVEPOINT '1'", 0) - } - - func test_savepoint_withOperators_allowsForFlowControl() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - - var txn = db.savepoint("1") - - txn = txn && ( - db.savepoint("2") && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - db.release() - ) - txn || db.rollback() - - txn = txn && ( - db.savepoint("2") && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - db.release() - ) - txn || db.rollback() - - txn && db.release() || db.rollback() - - AssertSQL("SAVEPOINT '1'") - AssertSQL("SAVEPOINT '2'") - AssertSQL("RELEASE SAVEPOINT '2'", 0) - AssertSQL("RELEASE SAVEPOINT '1'", 0) - AssertSQL("ROLLBACK TO SAVEPOINT '1'") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - } - - func test_interrupt_interruptsLongRunningQuery() { - insertUsers(map("abcdefghijklmnopqrstuvwxyz") { String($0) }) - db.create(function: "sleep") { args in - usleep(UInt32(Double(args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) - return nil - } - - let stmt = db.prepare("SELECT *, sleep(?) FROM users", 0.1) - stmt.run() - XCTAssert(!stmt.failed) - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) - stmt.run() - XCTAssert(stmt.failed) - } - - func test_userVersion_getsAndSetsUserVersion() { - XCTAssertEqual(0, db.userVersion) - db.userVersion = 1 - XCTAssertEqual(1, db.userVersion) - } - - func test_foreignKeys_getsAndSetsForeignKeys() { - XCTAssertEqual(false, db.foreignKeys) - db.foreignKeys = true - XCTAssertEqual(true, db.foreignKeys) - } - - func test_updateHook_setsUpdateHook() { - async { done in - db.updateHook { operation, db, table, rowid in - XCTAssertEqual(.Insert, operation) - XCTAssertEqual("main", db) - XCTAssertEqual("users", table) - XCTAssertEqual(1, rowid) - done() - } - insertUser("alice") - } - } - - func test_commitHook_setsCommitHook() { - async { done in - db.commitHook { - done() - return .Commit - } - db.transaction { _ in - insertUser("alice") - return .Commit - } - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as! Int64) - } - } - - func test_rollbackHook_setsRollbackHook() { - async { done in - db.rollbackHook(done) - db.transaction { _ in - insertUser("alice") - return .Rollback - } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as! Int64) - } - } - - func test_commitHook_withRollback_rollsBack() { - async { done in - db.commitHook { .Rollback } - db.rollbackHook(done) - db.transaction { _ in - insertUser("alice") - return .Commit - } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as! Int64) - } - } - - func test_createFunction_withArrayArguments() { - db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } } - - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as! String) - XCTAssert(db.scalar("SELECT hello(NULL)") == nil) - } - - func test_createFunction_createsQuotableFunction() { - db.create(function: "hello world") { $0[0].map { "Hello, \($0)!" } } - - XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as! String) - XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) - } - - func test_createCollation_createsCollation() { - db.create(collation: "NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) - } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as! Int64) - } - - func test_createCollation_createsQuotableCollation() { - db.create(collation: "NO DIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) - } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) - } - - func test_lastError_withOK_returnsNil() { - XCTAssertNil(db.lastError) - } - - func test_lastError_withRow_returnsNil() { - insertUsers("alice", "betty") - db.prepare("SELECT * FROM users").step() - - XCTAssertNil(db.lastError) - } - - func test_lastError_withDone_returnsNil() { - db.prepare("SELECT * FROM users").step() - - XCTAssertNil(db.lastError) - } - - func test_lastError_withError_returnsError() { - insertUsers("alice", "alice") - - XCTAssertNotNil(db.lastError) - } - -} diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift deleted file mode 100644 index 27d329b5..00000000 --- a/SQLite Tests/ExpressionTests.swift +++ /dev/null @@ -1,757 +0,0 @@ -import XCTest -import SQLite - -let stringA = Expression(value: "A") -let stringB = Expression(value: "B") - -let int1 = Expression(value: 1) -let int2 = Expression(value: 2) - -let double1 = Expression(value: 1.5) -let double2 = Expression(value: 2.5) - -let bool0 = Expression(value: false) -let bool1 = Expression(value: true) - -class ExpressionTests: SQLiteTestCase { - - func AssertSQLContains(SQL: String, _ expression: Expression, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - AssertSQL("SELECT \(SQL) FROM \"users\"", users.select(expression), file: file, line: line) - } - - override func setUp() { - createUsersTable() - - super.setUp() - } - - func test_alias_aliasesExpression() { - let aliased = stringA.alias("string_a") - AssertSQLContains("('A') AS \"string_a\"", aliased) - } - - func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { - AssertSQLContains("('A' || 'A')", stringA + stringA) - AssertSQLContains("('A' || 'B')", stringA + stringB) - AssertSQLContains("('B' || 'A')", stringB + stringA) - AssertSQLContains("('B' || 'B')", stringB + stringB) - AssertSQLContains("('A' || 'B')", stringA + "B") - AssertSQLContains("('B' || 'A')", stringB + "A") - AssertSQLContains("('B' || 'A')", "B" + stringA) - AssertSQLContains("('A' || 'B')", "A" + stringB) - } - - func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() { - AssertSQLContains("(1 + 1)", int1 + int1) - AssertSQLContains("(1 + 2)", int1 + int2) - AssertSQLContains("(2 + 1)", int2 + int1) - AssertSQLContains("(2 + 2)", int2 + int2) - AssertSQLContains("(1 + 2)", int1 + 2) - AssertSQLContains("(2 + 1)", int2 + 1) - AssertSQLContains("(2 + 1)", 2 + int1) - AssertSQLContains("(1 + 2)", 1 + int2) - } - - func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() { - AssertSQLContains("(1.5 + 1.5)", double1 + double1) - AssertSQLContains("(1.5 + 2.5)", double1 + double2) - AssertSQLContains("(2.5 + 1.5)", double2 + double1) - AssertSQLContains("(2.5 + 2.5)", double2 + double2) - AssertSQLContains("(1.5 + 2.5)", double1 + 2.5) - AssertSQLContains("(2.5 + 1.5)", double2 + 1.5) - AssertSQLContains("(2.5 + 1.5)", 2.5 + double1) - AssertSQLContains("(1.5 + 2.5)", 1.5 + double2) - } - - func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() { - AssertSQLContains("(1 - 1)", int1 - int1) - AssertSQLContains("(1 - 2)", int1 - int2) - AssertSQLContains("(2 - 1)", int2 - int1) - AssertSQLContains("(2 - 2)", int2 - int2) - AssertSQLContains("(1 - 2)", int1 - 2) - AssertSQLContains("(2 - 1)", int2 - 1) - AssertSQLContains("(2 - 1)", 2 - int1) - AssertSQLContains("(1 - 2)", 1 - int2) - } - - func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() { - AssertSQLContains("(1.5 - 1.5)", double1 - double1) - AssertSQLContains("(1.5 - 2.5)", double1 - double2) - AssertSQLContains("(2.5 - 1.5)", double2 - double1) - AssertSQLContains("(2.5 - 2.5)", double2 - double2) - AssertSQLContains("(1.5 - 2.5)", double1 - 2.5) - AssertSQLContains("(2.5 - 1.5)", double2 - 1.5) - AssertSQLContains("(2.5 - 1.5)", 2.5 - double1) - AssertSQLContains("(1.5 - 2.5)", 1.5 - double2) - } - - func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() { - AssertSQLContains("(1 * 1)", int1 * int1) - AssertSQLContains("(1 * 2)", int1 * int2) - AssertSQLContains("(2 * 1)", int2 * int1) - AssertSQLContains("(2 * 2)", int2 * int2) - AssertSQLContains("(1 * 2)", int1 * 2) - AssertSQLContains("(2 * 1)", int2 * 1) - AssertSQLContains("(2 * 1)", 2 * int1) - AssertSQLContains("(1 * 2)", 1 * int2) - } - - func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() { - AssertSQLContains("(1.5 * 1.5)", double1 * double1) - AssertSQLContains("(1.5 * 2.5)", double1 * double2) - AssertSQLContains("(2.5 * 1.5)", double2 * double1) - AssertSQLContains("(2.5 * 2.5)", double2 * double2) - AssertSQLContains("(1.5 * 2.5)", double1 * 2.5) - AssertSQLContains("(2.5 * 1.5)", double2 * 1.5) - AssertSQLContains("(2.5 * 1.5)", 2.5 * double1) - AssertSQLContains("(1.5 * 2.5)", 1.5 * double2) - } - - func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() { - AssertSQLContains("(1 / 1)", int1 / int1) - AssertSQLContains("(1 / 2)", int1 / int2) - AssertSQLContains("(2 / 1)", int2 / int1) - AssertSQLContains("(2 / 2)", int2 / int2) - AssertSQLContains("(1 / 2)", int1 / 2) - AssertSQLContains("(2 / 1)", int2 / 1) - AssertSQLContains("(2 / 1)", 2 / int1) - AssertSQLContains("(1 / 2)", 1 / int2) - } - - func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() { - AssertSQLContains("(1.5 / 1.5)", double1 / double1) - AssertSQLContains("(1.5 / 2.5)", double1 / double2) - AssertSQLContains("(2.5 / 1.5)", double2 / double1) - AssertSQLContains("(2.5 / 2.5)", double2 / double2) - AssertSQLContains("(1.5 / 2.5)", double1 / 2.5) - AssertSQLContains("(2.5 / 1.5)", double2 / 1.5) - AssertSQLContains("(2.5 / 1.5)", 2.5 / double1) - AssertSQLContains("(1.5 / 2.5)", 1.5 / double2) - } - - func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { - AssertSQLContains("(1 % 1)", int1 % int1) - AssertSQLContains("(1 % 2)", int1 % int2) - AssertSQLContains("(2 % 1)", int2 % int1) - AssertSQLContains("(2 % 2)", int2 % int2) - AssertSQLContains("(1 % 2)", int1 % 2) - AssertSQLContains("(2 % 1)", int2 % 1) - AssertSQLContains("(2 % 1)", 2 % int1) - AssertSQLContains("(1 % 2)", 1 % int2) - } - - func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { - AssertSQLContains("(1 << 1)", int1 << int1) - AssertSQLContains("(1 << 2)", int1 << int2) - AssertSQLContains("(2 << 1)", int2 << int1) - AssertSQLContains("(2 << 2)", int2 << int2) - AssertSQLContains("(1 << 2)", int1 << 2) - AssertSQLContains("(2 << 1)", int2 << 1) - AssertSQLContains("(2 << 1)", 2 << int1) - AssertSQLContains("(1 << 2)", 1 << int2) - } - - func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { - AssertSQLContains("(1 >> 1)", int1 >> int1) - AssertSQLContains("(1 >> 2)", int1 >> int2) - AssertSQLContains("(2 >> 1)", int2 >> int1) - AssertSQLContains("(2 >> 2)", int2 >> int2) - AssertSQLContains("(1 >> 2)", int1 >> 2) - AssertSQLContains("(2 >> 1)", int2 >> 1) - AssertSQLContains("(2 >> 1)", 2 >> int1) - AssertSQLContains("(1 >> 2)", 1 >> int2) - } - - func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { - AssertSQLContains("(1 & 1)", int1 & int1) - AssertSQLContains("(1 & 2)", int1 & int2) - AssertSQLContains("(2 & 1)", int2 & int1) - AssertSQLContains("(2 & 2)", int2 & int2) - AssertSQLContains("(1 & 2)", int1 & 2) - AssertSQLContains("(2 & 1)", int2 & 1) - AssertSQLContains("(2 & 1)", 2 & int1) - AssertSQLContains("(1 & 2)", 1 & int2) - } - - func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQLContains("(1 | 1)", int1 | int1) - AssertSQLContains("(1 | 2)", int1 | int2) - AssertSQLContains("(2 | 1)", int2 | int1) - AssertSQLContains("(2 | 2)", int2 | int2) - AssertSQLContains("(1 | 2)", int1 | 2) - AssertSQLContains("(2 | 1)", int2 | 1) - AssertSQLContains("(2 | 1)", 2 | int1) - AssertSQLContains("(1 | 2)", 1 | int2) - } - - func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQLContains("(~((1 & 1)) & (1 | 1))", int1 ^ int1) - AssertSQLContains("(~((1 & 2)) & (1 | 2))", int1 ^ int2) - AssertSQLContains("(~((2 & 1)) & (2 | 1))", int2 ^ int1) - AssertSQLContains("(~((2 & 2)) & (2 | 2))", int2 ^ int2) - AssertSQLContains("(~((1 & 2)) & (1 | 2))", int1 ^ 2) - AssertSQLContains("(~((2 & 1)) & (2 | 1))", int2 ^ 1) - AssertSQLContains("(~((2 & 1)) & (2 | 1))", 2 ^ int1) - AssertSQLContains("(~((1 & 2)) & (1 | 2))", 1 ^ int2) - } - - func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { - AssertSQLContains("~(1)", ~int1) - AssertSQLContains("~(2)", ~int2) - } - - func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQLContains("(0 = 0)", bool0 == bool0) - AssertSQLContains("(0 = 1)", bool0 == bool1) - AssertSQLContains("(1 = 0)", bool1 == bool0) - AssertSQLContains("(1 = 1)", bool1 == bool1) - AssertSQLContains("(0 = 1)", bool0 == true) - AssertSQLContains("(1 = 0)", bool1 == false) - AssertSQLContains("(1 = 0)", true == bool0) - AssertSQLContains("(0 = 1)", false == bool1) - } - - func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQLContains("(0 != 0)", bool0 != bool0) - AssertSQLContains("(0 != 1)", bool0 != bool1) - AssertSQLContains("(1 != 0)", bool1 != bool0) - AssertSQLContains("(1 != 1)", bool1 != bool1) - AssertSQLContains("(0 != 1)", bool0 != true) - AssertSQLContains("(1 != 0)", bool1 != false) - AssertSQLContains("(1 != 0)", true != bool0) - AssertSQLContains("(0 != 1)", false != bool1) - } - - func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 > 1)", int1 > int1) - AssertSQLContains("(1 > 2)", int1 > int2) - AssertSQLContains("(2 > 1)", int2 > int1) - AssertSQLContains("(2 > 2)", int2 > int2) - AssertSQLContains("(1 > 2)", int1 > 2) - AssertSQLContains("(2 > 1)", int2 > 1) - AssertSQLContains("(2 > 1)", 2 > int1) - AssertSQLContains("(1 > 2)", 1 > int2) - } - - func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 >= 1)", int1 >= int1) - AssertSQLContains("(1 >= 2)", int1 >= int2) - AssertSQLContains("(2 >= 1)", int2 >= int1) - AssertSQLContains("(2 >= 2)", int2 >= int2) - AssertSQLContains("(1 >= 2)", int1 >= 2) - AssertSQLContains("(2 >= 1)", int2 >= 1) - AssertSQLContains("(2 >= 1)", 2 >= int1) - AssertSQLContains("(1 >= 2)", 1 >= int2) - } - - func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 < 1)", int1 < int1) - AssertSQLContains("(1 < 2)", int1 < int2) - AssertSQLContains("(2 < 1)", int2 < int1) - AssertSQLContains("(2 < 2)", int2 < int2) - AssertSQLContains("(1 < 2)", int1 < 2) - AssertSQLContains("(2 < 1)", int2 < 1) - AssertSQLContains("(2 < 1)", 2 < int1) - AssertSQLContains("(1 < 2)", 1 < int2) - } - - func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 <= 1)", int1 <= int1) - AssertSQLContains("(1 <= 2)", int1 <= int2) - AssertSQLContains("(2 <= 1)", int2 <= int1) - AssertSQLContains("(2 <= 2)", int2 <= int2) - AssertSQLContains("(1 <= 2)", int1 <= 2) - AssertSQLContains("(2 <= 1)", int2 <= 1) - AssertSQLContains("(2 <= 1)", 2 <= int1) - AssertSQLContains("(1 <= 2)", 1 <= int2) - } - - func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() { - AssertSQLContains("-(1)", -int1) - AssertSQLContains("-(2)", -int2) - } - - func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() { - AssertSQLContains("-(1.5)", -double1) - AssertSQLContains("-(2.5)", -double2) - } - - func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() { - AssertSQLContains("1 BETWEEN 0 AND 5", 0...5 ~= int1) - AssertSQLContains("2 BETWEEN 0 AND 5", 0...5 ~= int2) - } - - func test_likeOperator_withStringExpression_buildsLikeExpression() { - AssertSQLContains("('A' LIKE 'B%')", like("B%", stringA)) - AssertSQLContains("('B' LIKE 'A%')", like("A%", stringB)) - } - - func test_globOperator_withStringExpression_buildsGlobExpression() { - AssertSQLContains("('A' GLOB 'B*')", glob("B*", stringA)) - AssertSQLContains("('B' GLOB 'A*')", glob("A*", stringB)) - } - - func test_matchOperator_withStringExpression_buildsMatchExpression() { - AssertSQLContains("('A' MATCH 'B')", match("B", stringA)) - AssertSQLContains("('B' MATCH 'A')", match("A", stringB)) - } - - func test_collateOperator_withStringExpression_buildsCollationExpression() { - AssertSQLContains("('A' COLLATE \"BINARY\")", collate(.Binary, stringA)) - AssertSQLContains("('B' COLLATE \"NOCASE\")", collate(.Nocase, stringB)) - AssertSQLContains("('A' COLLATE \"RTRIM\")", collate(.Rtrim, stringA)) - - db.create(collation: "NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) - } - AssertSQLContains("('A' COLLATE \"NODIACRITIC\")", collate(.Custom("NODIACRITIC"), stringA)) - } - - func test_cast_buildsCastingExpressions() { - let string1 = Expression(value: "10") - let string2 = Expression(value: "10") - AssertSQLContains("CAST ('10' AS REAL)", cast(string1) as Expression) - AssertSQLContains("CAST ('10' AS INTEGER)", cast(string1) as Expression) - AssertSQLContains("CAST ('10' AS TEXT)", cast(string1) as Expression) - AssertSQLContains("CAST ('10' AS REAL)", cast(string2) as Expression) - AssertSQLContains("CAST ('10' AS INTEGER)", cast(string2) as Expression) - AssertSQLContains("CAST ('10' AS TEXT)", cast(string2) as Expression) - } - - func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQLContains("(0 AND 0)", bool0 && bool0) - AssertSQLContains("(0 AND 1)", bool0 && bool1) - AssertSQLContains("(1 AND 0)", bool1 && bool0) - AssertSQLContains("(1 AND 1)", bool1 && bool1) - AssertSQLContains("(0 AND 1)", bool0 && true) - AssertSQLContains("(1 AND 0)", bool1 && false) - AssertSQLContains("(1 AND 0)", true && bool0) - AssertSQLContains("(0 AND 1)", false && bool1) - } - - func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQLContains("(0 OR 0)", bool0 || bool0) - AssertSQLContains("(0 OR 1)", bool0 || bool1) - AssertSQLContains("(1 OR 0)", bool1 || bool0) - AssertSQLContains("(1 OR 1)", bool1 || bool1) - AssertSQLContains("(0 OR 1)", bool0 || true) - AssertSQLContains("(1 OR 0)", bool1 || false) - AssertSQLContains("(1 OR 0)", true || bool0) - AssertSQLContains("(0 OR 1)", false || bool1) - } - - func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { - AssertSQLContains("NOT (0)", !bool0) - AssertSQLContains("NOT (1)", !bool1) - } - - func test_absFunction_withNumberExpressions_buildsAbsExpression() { - let int1 = Expression(value: -1) - let int2 = Expression(value: -2) - - AssertSQLContains("abs(-1)", abs(int1)) - AssertSQLContains("abs(-2)", abs(int2)) - } - - func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { - let int1 = Expression(value: nil as Int?) - let int2 = Expression(value: nil as Int?) - let int3 = Expression(value: 3) - - AssertSQLContains("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3)) - } - - func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { - let int1 = Expression(value: nil as Int?) - let int2 = Expression(value: 2) - let int3 = Expression(value: 3) - - AssertSQLContains("ifnull(NULL, 1)", ifnull(int1, 1)) - AssertSQLContains("ifnull(NULL, 1)", int1 ?? 1) - AssertSQLContains("ifnull(NULL, 2)", ifnull(int1, int2)) - AssertSQLContains("ifnull(NULL, 2)", int1 ?? int2) - AssertSQLContains("ifnull(NULL, 3)", ifnull(int1, int3)) - AssertSQLContains("ifnull(NULL, 3)", int1 ?? int3) - } - - func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { - AssertSQLContains("length('A')", length(stringA)) - AssertSQLContains("length('B')", length(stringB)) - } - - func test_lowerFunction_withStringExpression_buildsLowerStringExpression() { - AssertSQLContains("lower('A')", lower(stringA)) - AssertSQLContains("lower('B')", lower(stringB)) - } - - func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() { - AssertSQLContains("ltrim('A')", ltrim(stringA)) - AssertSQLContains("ltrim('B')", ltrim(stringB)) - } - - func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - AssertSQLContains("ltrim('A', 'A?')", ltrim(stringA, "A?")) - AssertSQLContains("ltrim('B', 'B?')", ltrim(stringB, "B?")) - } - - func test_randomFunction_buildsRandomIntExpression() { - AssertSQLContains("random()", random()) - } - - func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() { - AssertSQLContains("replace('A', 'A', 'B')", replace(stringA, "A", "B")) - AssertSQLContains("replace('B', 'B', 'A')", replace(stringB, "B", "A")) - } - - func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() { - AssertSQLContains("round(1.5)", round(double1)) - AssertSQLContains("round(2.5)", round(double2)) - } - - func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() { - AssertSQLContains("round(1.5, 1)", round(double1, 1)) - AssertSQLContains("round(2.5, 1)", round(double2, 1)) - } - - func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() { - AssertSQLContains("rtrim('A')", rtrim(stringA)) - AssertSQLContains("rtrim('B')", rtrim(stringB)) - } - - func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - AssertSQLContains("rtrim('A', 'A?')", rtrim(stringA, "A?")) - AssertSQLContains("rtrim('B', 'B?')", rtrim(stringB, "B?")) - } - - func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() { - AssertSQLContains("substr('A', 1)", substr(stringA, 1)) - AssertSQLContains("substr('B', 1)", substr(stringB, 1)) - } - - func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() { - AssertSQLContains("substr('A', 1, 2)", substr(stringA, 1, 2)) - AssertSQLContains("substr('B', 1, 2)", substr(stringB, 1, 2)) - } - - func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() { - AssertSQLContains("substr('A', 1, 2)", substr(stringA, 1..<3)) - AssertSQLContains("substr('B', 1, 2)", substr(stringB, 1..<3)) - } - - func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() { - AssertSQLContains("trim('A')", trim(stringA)) - AssertSQLContains("trim('B')", trim(stringB)) - } - - func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - AssertSQLContains("trim('A', 'A?')", trim(stringA, "A?")) - AssertSQLContains("trim('B', 'B?')", trim(stringB, "B?")) - } - - func test_upperFunction_withStringExpression_buildsLowerStringExpression() { - AssertSQLContains("upper('A')", upper(stringA)) - AssertSQLContains("upper('B')", upper(stringB)) - } - - let email2 = Expression("email") - let age2 = Expression("age") - let salary2 = Expression("salary") - let admin2 = Expression("admin") - - func test_countFunction_withExpression_buildsCountExpression() { - AssertSQLContains("count(\"age\")", count(age)) - AssertSQLContains("count(\"email\")", count(email2)) - AssertSQLContains("count(\"salary\")", count(salary2)) - AssertSQLContains("count(\"admin\")", count(admin2)) - AssertSQLContains("count(DISTINCT \"id\")", count(distinct: id)) - AssertSQLContains("count(DISTINCT \"age\")", count(distinct: age)) - AssertSQLContains("count(DISTINCT \"email\")", count(distinct: email)) - AssertSQLContains("count(DISTINCT \"email\")", count(distinct: email2)) - AssertSQLContains("count(DISTINCT \"salary\")", count(distinct: salary)) - AssertSQLContains("count(DISTINCT \"salary\")", count(distinct: salary2)) - AssertSQLContains("count(DISTINCT \"admin\")", count(distinct: admin)) - AssertSQLContains("count(DISTINCT \"admin\")", count(distinct: admin2)) - } - - func test_countFunction_withStar_buildsCountExpression() { - AssertSQLContains("count(*)", count(*)) - } - - func test_maxFunction_withExpression_buildsMaxExpression() { - AssertSQLContains("max(\"id\")", max(id)) - AssertSQLContains("max(\"age\")", max(age)) - AssertSQLContains("max(\"email\")", max(email)) - AssertSQLContains("max(\"email\")", max(email2)) - AssertSQLContains("max(\"salary\")", max(salary)) - AssertSQLContains("max(\"salary\")", max(salary2)) - } - - func test_minFunction_withExpression_buildsMinExpression() { - AssertSQLContains("min(\"id\")", min(id)) - AssertSQLContains("min(\"age\")", min(age)) - AssertSQLContains("min(\"email\")", min(email)) - AssertSQLContains("min(\"email\")", min(email2)) - AssertSQLContains("min(\"salary\")", min(salary)) - AssertSQLContains("min(\"salary\")", min(salary2)) - } - - func test_averageFunction_withExpression_buildsAverageExpression() { - AssertSQLContains("avg(\"id\")", average(id)) - AssertSQLContains("avg(\"age\")", average(age)) - AssertSQLContains("avg(\"salary\")", average(salary)) - AssertSQLContains("avg(\"salary\")", average(salary2)) - AssertSQLContains("avg(DISTINCT \"id\")", average(distinct: id)) - AssertSQLContains("avg(DISTINCT \"age\")", average(distinct: age)) - AssertSQLContains("avg(DISTINCT \"salary\")", average(distinct: salary)) - AssertSQLContains("avg(DISTINCT \"salary\")", average(distinct: salary2)) - } - - func test_sumFunction_withExpression_buildsSumExpression() { - AssertSQLContains("sum(\"id\")", sum(id)) - AssertSQLContains("sum(\"age\")", sum(age)) - AssertSQLContains("sum(\"salary\")", sum(salary)) - AssertSQLContains("sum(\"salary\")", sum(salary2)) - AssertSQLContains("sum(DISTINCT \"id\")", sum(distinct: id)) - AssertSQLContains("sum(DISTINCT \"age\")", sum(distinct: age)) - AssertSQLContains("sum(DISTINCT \"salary\")", sum(distinct: salary)) - AssertSQLContains("sum(DISTINCT \"salary\")", sum(distinct: salary2)) - } - - func test_totalFunction_withExpression_buildsTotalExpression() { - AssertSQLContains("total(\"id\")", total(id)) - AssertSQLContains("total(\"age\")", total(age)) - AssertSQLContains("total(\"salary\")", total(salary)) - AssertSQLContains("total(\"salary\")", total(salary2)) - AssertSQLContains("total(DISTINCT \"id\")", total(distinct: id)) - AssertSQLContains("total(DISTINCT \"age\")", total(distinct: age)) - AssertSQLContains("total(DISTINCT \"salary\")", total(distinct: salary)) - AssertSQLContains("total(DISTINCT \"salary\")", total(distinct: salary2)) - } - - func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - AssertSQLContains("(\"id\" IN (1, 2, 3))", contains([1, 2, 3], id)) - AssertSQLContains("(\"age\" IN (20, 30, 40))", contains([20, 30, 40], age)) - - AssertSQLContains("(\"id\" IN (1))", contains(Set([1]), id)) - AssertSQLContains("(\"age\" IN (20))", contains(Set([20]), age)) - } - - func test_containsFunction_withValueExpressionAndQuery_buildsInExpression() { - let query = users.select(max(age)).group(id) - AssertSQLContains("(\"id\" IN (SELECT max(\"age\") FROM \"users\" GROUP BY \"id\"))", contains(query, id)) - } - - func test_plusEquals_withStringExpression_buildsSetter() { - users.update(email += email) - users.update(email += email2) - users.update(email2 += email) - users.update(email2 += email2) - AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")", 4) - } - - func test_plusEquals_withStringValue_buildsSetter() { - users.update(email += ".com") - users.update(email2 += ".com") - - AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || '.com')", 2) - } - - func test_plusEquals_withNumberExpression_buildsSetter() { - users.update(age += age) - users.update(age += age2) - users.update(age2 += age) - users.update(age2 += age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", 4) - - users.update(salary += salary) - users.update(salary += salary2) - users.update(salary2 += salary) - users.update(salary2 += salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", 4) - } - - func test_plusEquals_withNumberValue_buildsSetter() { - users.update(age += 1) - users.update(age2 += 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) - - users.update(salary += 100) - users.update(salary2 += 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", 2) - } - - func test_minusEquals_withNumberExpression_buildsSetter() { - users.update(age -= age) - users.update(age -= age2) - users.update(age2 -= age) - users.update(age2 -= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", 4) - - users.update(salary -= salary) - users.update(salary -= salary2) - users.update(salary2 -= salary) - users.update(salary2 -= salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", 4) - } - - func test_minusEquals_withNumberValue_buildsSetter() { - users.update(age -= 1) - users.update(age2 -= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) - - users.update(salary -= 100) - users.update(salary2 -= 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", 2) - } - - func test_timesEquals_withNumberExpression_buildsSetter() { - users.update(age *= age) - users.update(age *= age2) - users.update(age2 *= age) - users.update(age2 *= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", 4) - - users.update(salary *= salary) - users.update(salary *= salary2) - users.update(salary2 *= salary) - users.update(salary2 *= salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", 4) - } - - func test_timesEquals_withNumberValue_buildsSetter() { - users.update(age *= 1) - users.update(age2 *= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * 1)", 2) - - users.update(salary *= 100) - users.update(salary2 *= 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", 2) - } - - func test_divideEquals_withNumberExpression_buildsSetter() { - users.update(age /= age) - users.update(age /= age2) - users.update(age2 /= age) - users.update(age2 /= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", 4) - - users.update(salary /= salary) - users.update(salary /= salary2) - users.update(salary2 /= salary) - users.update(salary2 /= salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", 4) - } - - func test_divideEquals_withNumberValue_buildsSetter() { - users.update(age /= 1) - users.update(age2 /= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / 1)", 2) - - users.update(salary /= 100) - users.update(salary2 /= 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", 2) - } - - func test_moduloEquals_withIntegerExpression_buildsSetter() { - users.update(age %= age) - users.update(age %= age2) - users.update(age2 %= age) - users.update(age2 %= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", 4) - } - - func test_moduloEquals_withIntegerValue_buildsSetter() { - users.update(age %= 10) - users.update(age2 %= 10) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % 10)", 2) - } - - func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - users.update(age >>= age) - users.update(age >>= age2) - users.update(age2 >>= age) - users.update(age2 >>= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", 4) - } - - func test_rightShiftEquals_withIntegerValue_buildsSetter() { - users.update(age >>= 1) - users.update(age2 >>= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", 2) - } - - func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - users.update(age <<= age) - users.update(age <<= age2) - users.update(age2 <<= age) - users.update(age2 <<= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", 4) - } - - func test_leftShiftEquals_withIntegerValue_buildsSetter() { - users.update(age <<= 1) - users.update(age2 <<= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << 1)", 2) - } - - func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - users.update(age &= age) - users.update(age &= age2) - users.update(age2 &= age) - users.update(age2 &= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", 4) - } - - func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - users.update(age &= 1) - users.update(age2 &= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & 1)", 2) - } - - func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - users.update(age |= age) - users.update(age |= age2) - users.update(age2 |= age) - users.update(age2 |= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", 4) - } - - func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - users.update(age |= 1) - users.update(age2 |= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | 1)", 2) - } - - func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - users.update(age ^= age) - users.update(age ^= age2) - users.update(age2 ^= age) - users.update(age2 ^= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", 4) - } - - func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - users.update(age ^= 1) - users.update(age2 ^= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", 2) - } - - func test_postfixPlus_withIntegerValue_buildsSetter() { - users.update(age++) - users.update(age2++) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) - } - - func test_postfixMinus_withIntegerValue_buildsSetter() { - users.update(age--) - users.update(age2--) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) - } - - func test_precedencePreserved() { - let n = Expression(value: 1) - AssertSQLContains("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) - AssertSQLContains("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) - } - -} diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift deleted file mode 100644 index 6093f859..00000000 --- a/SQLite Tests/FTSTests.swift +++ /dev/null @@ -1,67 +0,0 @@ -import XCTest -import SQLite - -let subject = Expression("subject") -let body = Expression("body") - -class FTSTests: SQLiteTestCase { - - var emails: Query { return db["emails"] } - - func test_createVtable_usingFts4_createsVirtualTable() { - db.create(vtable: emails, using: fts4(subject, body)) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\")") - } - - func test_createVtable_usingFts4_withSimpleTokenizer_createsVirtualTableWithTokenizer() { - db.create(vtable: emails, using: fts4([subject, body], tokenize: .Simple)) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=simple)") - } - - func test_createVtable_usingFts4_withPorterTokenizer_createsVirtualTableWithTokenizer() { - db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=porter)") - } - - func test_match_withColumnExpression_buildsMatchExpressionWithColumnIdentifier() { - db.create(vtable: emails, using: fts4(subject, body)) - - AssertSQL("SELECT * FROM \"emails\" WHERE (\"subject\" MATCH 'hello')", emails.filter(match("hello", subject))) - } - - func test_match_withQuery_buildsMatchExpressionWithTableIdentifier() { - db.create(vtable: emails, using: fts4(subject, body)) - - AssertSQL("SELECT * FROM \"emails\" WHERE (\"emails\" MATCH 'hello')", emails.filter(match("hello", emails))) - } - - func test_registerTokenizer_registersTokenizer() { - let locale = CFLocaleCopyCurrent() - let tokenizer = CFStringTokenizerCreate(nil, "", CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) - - db.register(tokenizer: "tokenizer") { string in - CFStringTokenizerSetString(tokenizer, string, CFRangeMake(0, CFStringGetLength(string))) - if CFStringTokenizerAdvanceToNextToken(tokenizer) == .None { - return nil - } - let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) - let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string, range) - var token = CFStringCreateMutableCopy(nil, range.length, input) - CFStringLowercase(token, locale) - CFStringTransform(token, nil, kCFStringTransformStripDiacritics, 0) - return (token as String, string.rangeOfString(input as String)!) - } - - db.create(vtable: emails, using: fts4([subject, body], tokenize: .Custom("tokenizer"))) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" 'tokenizer')") - - emails.insert(subject <- "Aún más cáfe!") - - XCTAssertEqual(1, emails.filter(match("aun", emails)).count) - } - -} diff --git a/SQLite Tests/FunctionsTests.swift b/SQLite Tests/FunctionsTests.swift deleted file mode 100644 index d49d572e..00000000 --- a/SQLite Tests/FunctionsTests.swift +++ /dev/null @@ -1,164 +0,0 @@ -import XCTest -import SQLite - -class FunctionsTests: SQLiteTestCase { - - func test_createFunction_withZeroArguments() { - let f1: () -> Expression = db.create(function: "f1") { true } - let f2: () -> Expression = db.create(function: "f2") { nil } - - let table = db["table"] - db.create(table: table) { $0.column(Expression("id"), primaryKey: true) } - table.insert().rowid! - - XCTAssert(table.select(f1()).first![f1()]) - AssertSQL("SELECT \"f1\"() FROM \"table\" LIMIT 1") - - XCTAssertNil(table.select(f2()).first![f2()]) - AssertSQL("SELECT \"f2\"() FROM \"table\" LIMIT 1") - } - - func test_createFunction_withOneArgument() { - let f1: Expression -> Expression = db.create(function: "f1") { _ in return true } - let f2: Expression -> Expression = db.create(function: "f2") { _ in return false } - let f3: Expression -> Expression = db.create(function: "f3") { _ in return true } - let f4: Expression -> Expression = db.create(function: "f4") { _ in return nil } - - let table = db["table"] - let s1 = Expression("s1") - let s2 = Expression("s2") - db.create(table: table) { t in - t.column(s1) - t.column(s2) - } - table.insert(s1 <- "s1").rowid! - - let null = Expression(value: nil as String?) - - XCTAssert(table.select(f1(s1)).first![f1(s1)]) - AssertSQL("SELECT \"f1\"(\"s1\") FROM \"table\" LIMIT 1") - - XCTAssert(!table.select(f2(s2)).first![f2(s2)]) - AssertSQL("SELECT \"f2\"(\"s2\") FROM \"table\" LIMIT 1") - - XCTAssert(table.select(f3(s1)).first![f3(s1)]!) - AssertSQL("SELECT \"f3\"(\"s1\") FROM \"table\" LIMIT 1") - - XCTAssertNil(table.select(f4(null)).first![f4(null)]) - AssertSQL("SELECT \"f4\"(NULL) FROM \"table\" LIMIT 1") - } - - func test_createFunction_withValueArgument() { - let f1: Expression -> Expression = ( - db.create(function: "f1") { (a: Bool) -> Bool in - return a - } - ) - - let table = db["table"] - let b = Expression("b") - db.create(table: table) { t in - t.column(b) - } - table.insert(b <- true).rowid! - - XCTAssert(table.select(f1(b)).first![f1(b)]) - AssertSQL("SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1") - } - - func test_createFunction_withTwoArguments() { - let table = db["table"] - let b1 = Expression("b1") - let b2 = Expression("b2") - db.create(table: table) { t in - t.column(b1) - t.column(b2) - } - table.insert(b1 <- true).rowid! - - let f1: (Bool, Expression) -> Expression = db.create(function: "f1") { $0 && $1 } - let f2: (Bool?, Expression) -> Expression = db.create(function: "f2") { $0 ?? $1 } - let f3: (Bool, Expression) -> Expression = db.create(function: "f3") { $0 && $1 != nil } - let f4: (Bool?, Expression) -> Expression = db.create(function: "f4") { $0 ?? $1 != nil } - - XCTAssert(table.select(f1(true, b1)).first![f1(true, b1)]) - AssertSQL("SELECT \"f1\"(1, \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f2(nil, b1)).first![f2(nil, b1)]) - AssertSQL("SELECT \"f2\"(NULL, \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f3(false, b2)).first![f3(false, b2)]) - AssertSQL("SELECT \"f3\"(0, \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f4(nil, b2)).first![f4(nil, b2)]) - AssertSQL("SELECT \"f4\"(NULL, \"b2\") FROM \"table\" LIMIT 1") - - let f5: (Bool, Expression) -> Expression = db.create(function: "f5") { $0 && $1 } - let f6: (Bool?, Expression) -> Expression = db.create(function: "f6") { $0 ?? $1 } - let f7: (Bool, Expression) -> Expression = db.create(function: "f7") { $0 && $1 != nil } - let f8: (Bool?, Expression) -> Expression = db.create(function: "f8") { $0 ?? $1 != nil } - - XCTAssert(table.select(f5(true, b1)).first![f5(true, b1)]!) - AssertSQL("SELECT \"f5\"(1, \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f6(nil, b1)).first![f6(nil, b1)]!) - AssertSQL("SELECT \"f6\"(NULL, \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f7(false, b2)).first![f7(false, b2)]!) - AssertSQL("SELECT \"f7\"(0, \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f8(nil, b2)).first![f8(nil, b2)]!) - AssertSQL("SELECT \"f8\"(NULL, \"b2\") FROM \"table\" LIMIT 1") - - let f9: (Expression, Expression) -> Expression = db.create(function: "f9") { $0 && $1 } - let f10: (Expression, Expression) -> Expression = db.create(function: "f10") { $0 ?? $1 } - let f11: (Expression, Expression) -> Expression = db.create(function: "f11") { $0 && $1 != nil } - let f12: (Expression, Expression) -> Expression = db.create(function: "f12") { $0 ?? $1 != nil } - - XCTAssert(table.select(f9(b1, b1)).first![f9(b1, b1)]) - AssertSQL("SELECT \"f9\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f10(b2, b1)).first![f10(b2, b1)]) - AssertSQL("SELECT \"f10\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f11(b1, b2)).first![f11(b1, b2)]) - AssertSQL("SELECT \"f11\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f12(b2, b2)).first![f12(b2, b2)]) - AssertSQL("SELECT \"f12\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1") - - let f13: (Expression, Expression) -> Expression = db.create(function: "f13") { $0 && $1 } - let f14: (Expression, Expression) -> Expression = db.create(function: "f14") { $0 ?? $1 } - let f15: (Expression, Expression) -> Expression = db.create(function: "f15") { $0 && $1 != nil } - let f16: (Expression, Expression) -> Expression = db.create(function: "f16") { $0 ?? $1 != nil } - - XCTAssert(table.select(f13(b1, b1)).first![f13(b1, b1)]!) - AssertSQL("SELECT \"f13\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f14(b2, b1)).first![f14(b2, b1)]!) - AssertSQL("SELECT \"f14\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f15(b1, b2)).first![f15(b1, b2)]!) - AssertSQL("SELECT \"f15\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f16(b2, b2)).first![f16(b2, b2)]!) - AssertSQL("SELECT \"f16\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1") - - let f17: (Expression, Bool) -> Expression = db.create(function: "f17") { $0 && $1 } - let f18: (Expression, Bool) -> Expression = db.create(function: "f18") { $0 ?? $1 } - let f19: (Expression, Bool?) -> Expression = db.create(function: "f19") { $0 && $1 != nil } - let f20: (Expression, Bool?) -> Expression = db.create(function: "f20") { $0 ?? $1 != nil } - - XCTAssert(table.select(f17(b1, true)).first![f17(b1, true)]) - AssertSQL("SELECT \"f17\"(\"b1\", 1) FROM \"table\" LIMIT 1") - XCTAssert(table.select(f18(b2, true)).first![f18(b2, true)]) - AssertSQL("SELECT \"f18\"(\"b2\", 1) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f19(b1, nil)).first![f19(b1, nil)]) - AssertSQL("SELECT \"f19\"(\"b1\", NULL) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f20(b2, nil)).first![f20(b2, nil)]) - AssertSQL("SELECT \"f20\"(\"b2\", NULL) FROM \"table\" LIMIT 1") - - let f21: (Expression, Bool) -> Expression = db.create(function: "f21") { $0 && $1 } - let f22: (Expression, Bool) -> Expression = db.create(function: "f22") { $0 ?? $1 } - let f23: (Expression, Bool?) -> Expression = db.create(function: "f23") { $0 && $1 != nil } - let f24: (Expression, Bool?) -> Expression = db.create(function: "f24") { $0 ?? $1 != nil } - - XCTAssert(table.select(f21(b1, true)).first![f21(b1, true)]!) - AssertSQL("SELECT \"f21\"(\"b1\", 1) FROM \"table\" LIMIT 1") - XCTAssert(table.select(f22(b2, true)).first![f22(b2, true)]!) - AssertSQL("SELECT \"f22\"(\"b2\", 1) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f23(b1, nil)).first![f23(b1, nil)]!) - AssertSQL("SELECT \"f23\"(\"b1\", NULL) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f24(b2, nil)).first![f24(b2, nil)]!) - AssertSQL("SELECT \"f24\"(\"b2\", NULL) FROM \"table\" LIMIT 1") - } - -} diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift deleted file mode 100644 index fcb2e898..00000000 --- a/SQLite Tests/QueryTests.swift +++ /dev/null @@ -1,492 +0,0 @@ -import XCTest -import SQLite - -class QueryTests: SQLiteTestCase { - - override func setUp() { - createUsersTable() - - super.setUp() - } - - func test_select_withExpression_compilesSelectClause() { - AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) - } - - func test_select_withVariadicExpressions_compilesSelectClause() { - AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) - } - - func test_select_withStar_resetsSelectClause() { - let query = users.select(email) - - AssertSQL("SELECT * FROM \"users\"", query.select(*)) - } - - func test_selectDistinct_withExpression_compilesSelectClause() { - AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) - } - - func test_selectDistinct_withStar_compilesSelectClause() { - AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) - } - - func test_select_withSubquery() { - let subquery = users.select(id) - - AssertSQL("SELECT (SELECT \"id\" FROM \"users\") FROM \"users\"", users.select(subquery)) - AssertSQL("SELECT (SELECT \"id\" FROM (\"users\") AS \"u\") AS \"u\" FROM \"users\"", - users.select(subquery.alias("u"))) - } - - func test_join_compilesJoinClause() { - let managers = db["users"].alias("managers") - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - AssertSQL(SQL, users.join(managers, on: managers[id] == users[manager_id])) - } - - func test_join_withExplicitType_compilesJoinClauseWithType() { - let managers = db["users"].alias("managers") - - let SQL = "SELECT * FROM \"users\" " + - "LEFT OUTER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - AssertSQL(SQL, users.join(.LeftOuter, managers, on: managers[id] == users[manager_id])) - } - - func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { - var managers = db["users"].alias("managers") - managers = managers.filter(managers[admin]) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" " + - "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") " + - "AND \"managers\".\"admin\")" - AssertSQL(SQL, users.join(managers, on: managers[id] == users[manager_id])) - } - - func test_join_whenChained_compilesAggregateJoinClause() { - let managers = users.alias("managers") - let managed = users.alias("managed") - - let middleManagers = users - .join(managers, on: managers[id] == users[manager_id]) - .join(managed, on: managed[manager_id] == users[id]) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " + - "INNER JOIN (\"users\") AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")" - AssertSQL(SQL, middleManagers) - } - - func test_join_withNamespacedStar_expandsColumnNames() { - let managers = db["users"].alias("managers") - - let aliceId = users.insert(email <- "alice@example.com").rowid! - users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid! - - let query = users - .select(users[*], managers[*]) - .join(managers, on: managers[id] == users[manager_id]) - - let SQL = "SELECT \"users\".*, \"managers\".* FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" " + - "ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - AssertSQL(SQL, query) - } - - func test_join_withSubquery_joinsSubquery() { - insertUser("alice", age: 20) - - let maxId = max(id).alias("max_id") - let subquery = users.select(maxId).group(age) - let query = users.join(subquery, on: maxId == id) - - XCTAssertEqual(Int64(1), query.first![maxId]!) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM \"users\" GROUP BY \"age\") " + - "ON (\"max_id\" = \"id\") LIMIT 1" - AssertSQL(SQL, query) - } - - func test_join_withAliasedSubquery_joinsSubquery() { - insertUser("alice", age: 20) - - let maxId = max(id).alias("max_id") - let subquery = users.select(maxId).group(age).alias("u") - let query = users.join(subquery, on: subquery[maxId] == id) - - XCTAssertEqual(Int64(1), query.first![subquery[maxId]]!) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM (\"users\") AS \"u\" GROUP BY \"age\") AS \"u\" " + - "ON (\"u\".\"max_id\" = \"id\") LIMIT 1" - AssertSQL(SQL, query) - } - - func test_namespacedColumnRowValueAccess() { - let aliceId = users.insert(email <- "alice@example.com").rowid! - let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid! - - let alice = users.first! - XCTAssertEqual(Int64(aliceId), alice[id]) - - let managers = db["users"].alias("managers") - let query = users.join(managers, on: managers[id] == users[manager_id]) - - let betty = query.first! - XCTAssertEqual(alice[email], betty[managers[email]]) - } - - func test_aliasedColumnRowValueAccess() { - users.insert(email <- "alice@example.com").rowid! - - let alias = email.alias("user_email") - let query = users.select(alias) - let alice = query.first! - - XCTAssertEqual("alice@example.com", alice[alias]) - } - - func test_filter_compilesWhereClause() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) - } - - func test_filter_whenChained_compilesAggregateWhereClause() { - let query = users - .filter(email == "alice@example.com") - .filter(age >= 21) - - let SQL = "SELECT * FROM \"users\" " + - "WHERE ((\"email\" = 'alice@example.com') " + - "AND (\"age\" >= 21))" - AssertSQL(SQL, query) - } - - func test_group_withSingleExpressionName_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", - users.group(age)) - } - - func test_group_withVariadicExpressionNames_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) - } - - func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) - } - - func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", - users.group([age, admin], having: age >= 30)) - } - - func test_order_withSingleExpressionName_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) - } - - func test_order_withVariadicExpressionNames_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) - } - - func test_order_withExpressionAndSortDirection_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc)) - } - - func test_order_whenChained_overridesOrder() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) - } - - func test_reverse_withoutOrder_ordersByRowIdDescending() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC", users.reverse()) - } - - func test_reverse_withOrder_reversesOrder() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age, email.desc).reverse()) - } - - func test_limit_compilesLimitClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) - } - - func test_limit_withOffset_compilesOffsetClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) - } - - func test_limit_whenChained_overridesLimit() { - let query = users.limit(5) - - AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) - } - - func test_limit_whenChained_withOffset_overridesOffset() { - let query = users.limit(5, offset: 5) - - AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 10", query.limit(10, offset: 10)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) - } - - func test_alias_compilesAliasInSelectClause() { - AssertSQL("SELECT * FROM (\"users\") AS \"managers\"", users.alias("managers")) - } - - func test_subscript_withExpression_returnsNamespacedExpression() { - AssertSQL("SELECT \"users\".\"admin\" FROM \"users\"", users.select(users[admin])) - AssertSQL("SELECT \"users\".\"salary\" FROM \"users\"", users.select(users[salary])) - AssertSQL("SELECT \"users\".\"age\" FROM \"users\"", users.select(users[age])) - AssertSQL("SELECT \"users\".\"email\" FROM \"users\"", users.select(users[email])) - AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) - } - - func test_subscript_withAliasAndExpression_returnsAliasedExpression() { - let managers = users.alias("managers") - AssertSQL("SELECT \"managers\".\"admin\" FROM (\"users\") AS \"managers\"", managers.select(managers[admin])) - AssertSQL("SELECT \"managers\".\"salary\" FROM (\"users\") AS \"managers\"", managers.select(managers[salary])) - AssertSQL("SELECT \"managers\".\"age\" FROM (\"users\") AS \"managers\"", managers.select(managers[age])) - AssertSQL("SELECT \"managers\".\"email\" FROM (\"users\") AS \"managers\"", managers.select(managers[email])) - AssertSQL("SELECT \"managers\".* FROM (\"users\") AS \"managers\"", managers.select(managers[*])) - } - - func test_SQL_compilesProperly() { - var managers = users.alias("managers") - // TODO: automatically namespace in the future? - managers = managers.filter(managers[admin] == true) - - let query = users - .select(users[email], count(users[age])) - .join(.LeftOuter, managers, on: managers[id] == users[manager_id]) - .filter(21..<32 ~= users[age]) - .group(users[age], having: count(users[age]) > 1) - .order(users[email].desc) - .limit(1, offset: 2) - - let SQL = "SELECT \"users\".\"email\", count(\"users\".\"age\") FROM \"users\" " + - "LEFT OUTER JOIN (\"users\") AS \"managers\" " + - "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") AND (\"managers\".\"admin\" = 1)) " + - "WHERE \"users\".\"age\" BETWEEN 21 AND 32 " + - "GROUP BY \"users\".\"age\" HAVING (count(\"users\".\"age\") > 1) " + - "ORDER BY \"users\".\"email\" DESC " + - "LIMIT 1 " + - "OFFSET 2" - AssertSQL(SQL, query) - } - - func test_first_withAnEmptyQuery_returnsNil() { - XCTAssert(users.first == nil) - } - - func test_first_returnsTheFirstRow() { - insertUsers("alice", "betsy") - - XCTAssertEqual(1, users.first![id]) - AssertSQL("SELECT * FROM \"users\" LIMIT 1") - } - - func test_first_withOffset_retainsOffset() { - insertUsers("alice", "betsy") - - XCTAssertEqual(2, users.limit(2, offset: 1).first![id]) - AssertSQL("SELECT * FROM \"users\" LIMIT 1 OFFSET 1") - } - - func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() { - XCTAssertTrue(users.isEmpty) - - insertUser("alice") - - XCTAssertFalse(users.isEmpty) - - AssertSQL("SELECT * FROM \"users\" LIMIT 1", 2) - } - - func test_insert_insertsRows() { - XCTAssertEqual(1, users.insert(email <- "alice@example.com", age <- 30).rowid!) - - AssertSQL("INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - - XCTAssert(users.insert(email <- "alice@example.com", age <- 30).rowid == nil) - } - - func test_insert_withQuery_insertsRows() { - db.execute("CREATE TABLE \"emails\" (\"email\" TEXT)") - let emails = db["emails"] - let admins = users.select(email).filter(admin == true) - - emails.insert(admins) - AssertSQL("INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)") - } - - func test_insert_insertsDefaultRow() { - db.execute("CREATE TABLE \"timestamps\" (\"id\" INTEGER PRIMARY KEY, \"timestamp\" TEXT DEFAULT CURRENT_DATETIME)") - let table = db["timestamps"] - - XCTAssertEqual(1, table.insert().rowid!) - AssertSQL("INSERT INTO \"timestamps\" DEFAULT VALUES") - } - - func test_replace_replaceRows() { - XCTAssertEqual(1, users.insert(or: .Replace, email <- "alice@example.com", age <- 30).rowid!) - AssertSQL("INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - - XCTAssertEqual(1, users.insert(or: .Replace, id <- 1, email <- "betty@example.com", age <- 30).rowid!) - AssertSQL("INSERT OR REPLACE INTO \"users\" (\"id\", \"email\", \"age\") VALUES (1, 'betty@example.com', 30)") - } - - func test_update_updatesRows() { - insertUsers("alice", "betsy") - insertUser("dolly", admin: true) - - XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes!) - XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes!) - } - - func test_delete_deletesRows() { - insertUser("alice", age: 20) - XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes!) - - insertUser("betsy", age: 30) - XCTAssertEqual(2, users.delete().changes!) - XCTAssertEqual(0, users.delete().changes!) - } - - func test_count_returnsCount() { - XCTAssertEqual(0, users.count) - - insertUser("alice") - XCTAssertEqual(1, users.count) - XCTAssertEqual(0, users.filter(age != nil).count) - } - - func test_count_withExpression_returnsCount() { - insertUser("alice", age: 20) - insertUser("betsy", age: 20) - insertUser("cindy") - - XCTAssertEqual(2, users.count(age)) - XCTAssertEqual(1, users.count(distinct: age)) - } - - func test_max_withInt_returnsMaximumInt() { - XCTAssert(users.max(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - XCTAssertEqual(30, users.max(age)!) - } - - func test_min_withInt_returnsMinimumInt() { - XCTAssert(users.min(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - XCTAssertEqual(20, users.min(age)!) - } - - func test_averageWithInt_returnsDouble() { - XCTAssert(users.average(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 50) - insertUser("cindy", age: 50) - XCTAssertEqual(40.0, users.average(age)!) - XCTAssertEqual(35.0, users.average(distinct: age)!) - } - - func test_sum_returnsSum() { - XCTAssert(users.sum(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - insertUser("cindy", age: 30) - XCTAssertEqual(80, users.sum(age)!) - XCTAssertEqual(50, users.sum(distinct: age)!) - } - - func test_total_returnsTotal() { - XCTAssertEqual(0.0, users.total(age)) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - insertUser("cindy", age: 30) - XCTAssertEqual(80.0, users.total(age)) - XCTAssertEqual(50.0, users.total(distinct: age)) - } - - func test_row_withBoundColumn_returnsValue() { - insertUser("alice", age: 20) - XCTAssertEqual(21, users.select(age + 1).first![age + 1]!) - } - - func test_valueExtension_serializesAndDeserializes() { - let id = Expression("id") - let timestamp = Expression("timestamp") - let touches = db["touches"] - db.create(table: touches) { t in - t.column(id, primaryKey: true) - t.column(timestamp) - } - - let date = NSDate(timeIntervalSince1970: 0) - touches.insert(timestamp <- date) - XCTAssertEqual(touches.first!.get(timestamp)!, date) - - XCTAssertNil(touches.filter(id == Int64(touches.insert().rowid!)).first!.get(timestamp)) - - XCTAssert(touches.filter(timestamp < NSDate()).first != nil) - } - - func test_shortCircuitingInserts() { - db.transaction() - && users.insert(email <- "alice@example.com") - && users.insert(email <- "alice@example.com") - && users.insert(email <- "alice@example.com") - && db.commit() - || db.rollback() - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO \"users\" (\"email\") VALUES ('alice@example.com')", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - - func test_shortCircuitingChanges() { - db.transaction() - && users.insert(email <- "foo@example.com") - && users.insert(email <- "bar@example.com") - && users.filter(email == "bar@example.com").update(email <- "foo@example.com") - && users.filter(email == "bar@example.com").update(email <- "foo@example.com") - && db.commit() - || db.rollback() - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("UPDATE \"users\" SET \"email\" = 'foo@example.com' WHERE (\"email\" = 'bar@example.com')") - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - -} - -private let formatter: NSDateFormatter = { - let formatter = NSDateFormatter() - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") - formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) - return formatter -}() - -extension NSDate: Value { - - public class var declaredDatatype: String { return String.declaredDatatype } - - public class func fromDatatypeValue(datatypeValue: String) -> NSDate { - return formatter.dateFromString(datatypeValue)! - } - - public var datatypeValue: String { - return formatter.stringFromDate(self) - } - -} diff --git a/SQLite Tests/RTreeTests.swift b/SQLite Tests/RTreeTests.swift deleted file mode 100644 index fe5feb57..00000000 --- a/SQLite Tests/RTreeTests.swift +++ /dev/null @@ -1,19 +0,0 @@ -import XCTest -import SQLite - -let minX = Expression("minX") -let maxX = Expression("maxX") -let minY = Expression("minY") -let maxY = Expression("maxY") - -class RTreeTests: SQLiteTestCase { - - var index: Query { return db["index"] } - - func test_createVtable_usingRtree_createsVirtualTable() { - db.create(vtable: index, using: rtree(id, minX, maxX, minY, maxY)) - - AssertSQL("CREATE VIRTUAL TABLE \"index\" USING rtree(\"id\", \"minX\", \"maxX\", \"minY\", \"maxY\")") - } - -} diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift deleted file mode 100644 index b65aed0a..00000000 --- a/SQLite Tests/SchemaTests.swift +++ /dev/null @@ -1,372 +0,0 @@ -import XCTest -import SQLite - -class SchemaTests: SQLiteTestCase { - - override func setUp() { - db.foreignKeys = true - - super.setUp() - } - - func test_createTable_createsTable() { - db.create(table: users) { $0.column(age) } - - AssertSQL("CREATE TABLE \"users\" (\"age\" INTEGER)") - } - - func test_createTable_temporary_createsTemporaryTable() { - db.create(table: users, temporary: true) { $0.column(age) } - - AssertSQL("CREATE TEMPORARY TABLE \"users\" (\"age\" INTEGER)") - } - - func test_createTable_ifNotExists_createsTableIfNotExists() { - db.create(table: users, ifNotExists: true) { $0.column(age) } - - AssertSQL("CREATE TABLE IF NOT EXISTS \"users\" (\"age\" INTEGER)") - } - - func test_createTable_column_buildsColumnDefinition() { - db.create(table: users) { $0.column(email) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)") - } - - func test_createTable_column_nonIntegerPrimaryKey() { - db.create(table: users) { $0.column(email, primaryKey: true) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT PRIMARY KEY NOT NULL)") - } - - func test_createTable_column_nonIntegerPrimaryKey_withDefaultValue() { - let uuid = Expression("uuid") - let uuidgen: () -> Expression = db.create(function: "uuidgen") { - return NSUUID().UUIDString - } - db.create(table: users) { $0.column(uuid, primaryKey: true, defaultValue: uuidgen()) } - - AssertSQL("CREATE TABLE \"users\" (\"uuid\" TEXT PRIMARY KEY NOT NULL DEFAULT (\"uuidgen\"()))") - } - - func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { - db.create(table: users) { $0.column(id, primaryKey: true) } - - AssertSQL("CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)") - } - - func test_createTable_column_withPrimaryKey_buildsPrimaryKeyAutoincrementClause() { - db.create(table: users) { $0.column(id, primaryKey: .Autoincrement) } - - AssertSQL("CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") - } - - func test_createTable_column_withNullFalse_buildsNotNullClause() { - db.create(table: users) { $0 .column(email) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)") - } - - func test_createTable_column_withUnique_buildsUniqueClause() { - db.create(table: users) { $0.column(email, unique: true) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL UNIQUE)") - } - - func test_createTable_column_withCheck_buildsCheckClause() { - db.create(table: users) { $0.column(admin, check: contains([false, true], admin)) } - - AssertSQL("CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL CHECK ((\"admin\" IN (0, 1))))") - } - - func test_createTable_column_withDefaultValue_buildsDefaultClause() { - db.create(table: users) { $0.column(salary, defaultValue: 0) } - - AssertSQL("CREATE TABLE \"users\" (\"salary\" REAL NOT NULL DEFAULT 0.0)") - } - - func test_createTable_stringColumn_collation_buildsCollateClause() { - db.create(table: users) { $0.column(email, collate: .Nocase) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)") - } - - func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id, references: users[id]) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER REFERENCES \"users\"(\"id\"))") - } - - func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id, references: users) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER REFERENCES \"users\")") - } - - func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { - db.create(table: users) { t in - t.column(email) - t.primaryKey(email) - } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, PRIMARY KEY(\"email\"))") - } - - func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { - db.create(table: users) { t in - t.column(id) - t.column(email) - t.primaryKey(id, email) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, PRIMARY KEY(\"id\", \"email\"))") - } - - func test_createTable_unique_buildsUniqueTableConstraint() { - db.create(table: users) { t in - t.column(email) - t.unique(email) - } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, UNIQUE(\"email\"))") - } - - func test_createTable_unique_buildsCompositeUniqueTableConstraint() { - db.create(table: users) { t in - t.column(id) - t.column(email) - t.unique(id, email) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, UNIQUE(\"id\", \"email\"))") - } - - func test_createTable_check_buildsCheckTableConstraint() { - db.create(table: users) { t in - t.column(admin) - t.check(contains([false, true], admin)) - } - - AssertSQL("CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL, CHECK ((\"admin\" IN (0, 1))))") - } - - func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id]) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\"))") - } - - func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id], update: .Cascade) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON UPDATE CASCADE)") - } - - func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id], delete: .Cascade) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON DELETE CASCADE)") - } - - func test_createTable_foreignKey_withCompositeKey_buildsForeignKeyTableConstraint() { - let manager_id = Expression("manager_id") // required - - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.column(email) - t.foreignKey((manager_id, email), references: (users[id], email)) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER NOT NULL, " + - "\"email\" TEXT NOT NULL, " + - "FOREIGN KEY(\"manager_id\", \"email\") REFERENCES \"users\"(\"id\", \"email\"))") - } - - func test_createTable_withQuery_createsTableWithQuery() { - createUsersTable() - - db.create(table: db["emails"], from: users.select(email)) - AssertSQL("CREATE TABLE \"emails\" AS SELECT \"email\" FROM \"users\"") - - db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) - AssertSQL("CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"") - } - - func test_alterTable_renamesTable() { - createUsersTable() - let people = db["people"] - - db.rename(table: "users", to: people) - AssertSQL("ALTER TABLE \"users\" RENAME TO \"people\"") - } - - func test_alterTable_addsNotNullColumn() { - createUsersTable() - let column = Expression("bonus") - - db.alter(table: users, add: column, defaultValue: 0) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL NOT NULL DEFAULT 0.0") - } - - func test_alterTable_addsRegularColumn() { - createUsersTable() - let column = Expression("bonus") - - db.alter(table: users, add: column) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL") - } - - func test_alterTable_withDefaultValue_addsRegularColumn() { - createUsersTable() - let column = Expression("bonus") - - db.alter(table: users, add: column, defaultValue: 0) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL DEFAULT 0.0") - } - - func test_alterTable_withForeignKey_addsRegularColumn() { - createUsersTable() - let column = Expression("parent_id") - - db.alter(table: users, add: column, references: users[id]) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")") - } - - func test_alterTable_stringColumn_collation_buildsCollateClause() { - createUsersTable() - let columnA = Expression("column_a") - let columnB = Expression("column_b") - - db.alter(table: users, add: columnA, defaultValue: "", collate: .Nocase) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE \"NOCASE\"") - - db.alter(table: users, add: columnB, collate: .Nocase) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE \"NOCASE\"") - } - - func test_dropTable_dropsTable() { - createUsersTable() - - db.drop(table: users) - AssertSQL("DROP TABLE \"users\"") - - db.drop(table: users, ifExists: true) - AssertSQL("DROP TABLE IF EXISTS \"users\"") - } - - func test_index_executesIndexStatement() { - createUsersTable() - - db.create(index: users, on: email) - AssertSQL("CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")") - } - - func test_index_withUniqueness_executesUniqueIndexStatement() { - createUsersTable() - - db.create(index: users, unique: true, on: email) - AssertSQL("CREATE UNIQUE INDEX \"index_users_on_email\" ON \"users\" (\"email\")") - } - - func test_index_ifNotExists_executesIndexStatement() { - createUsersTable() - - db.create(index: users, ifNotExists: true, on: email) - AssertSQL("CREATE INDEX IF NOT EXISTS \"index_users_on_email\" ON \"users\" (\"email\")") - } - - func test_index_withMultipleColumns_executesCompoundIndexStatement() { - createUsersTable() - - db.create(index: users, on: age.desc, email) - AssertSQL("CREATE INDEX \"index_users_on_age_email\" ON \"users\" (\"age\" DESC, \"email\")") - } - -// func test_index_withFilter_executesPartialIndexStatementWithWhereClause() { -// if SQLITE_VERSION >= "3.8" { -// CreateUsersTable(db) -// ExpectExecution(db, -// "CREATE INDEX index_users_on_age ON \"users\" (age) WHERE admin", -// db.create(index: users.filter(admin), on: age) -// ) -// } -// } - - func test_dropIndex_dropsIndex() { - createUsersTable() - db.create(index: users, on: email) - - db.drop(index: users, on: email) - AssertSQL("DROP INDEX \"index_users_on_email\"") - - db.drop(index: users, ifExists: true, on: email) - AssertSQL("DROP INDEX IF EXISTS \"index_users_on_email\"") - } - - func test_createView_withQuery_createsViewWithQuery() { - createUsersTable() - - db.create(view: db["emails"], from: users.select(email)) - AssertSQL("CREATE VIEW \"emails\" AS SELECT \"email\" FROM \"users\"") - - db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) - AssertSQL("CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"") - } - - func test_dropView_dropsView() { - createUsersTable() - let emails = db["emails"] - db.create(view: emails, from: users.select(email)) - - db.drop(view: emails) - AssertSQL("DROP VIEW \"emails\"") - - db.drop(view: emails, ifExists: true) - AssertSQL("DROP VIEW IF EXISTS \"emails\"") - } - - func test_quotedIdentifiers() { - let table = db["table"] - let column = Expression("My lil' primary key, \"Kiwi\"") - - db.create(table: table) { $0.column(column) } - AssertSQL("CREATE TABLE \"table\" (\"My lil' primary key, \"\"Kiwi\"\"\" INTEGER NOT NULL)") - } - -} diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift deleted file mode 100644 index 1015282a..00000000 --- a/SQLite Tests/StatementTests.swift +++ /dev/null @@ -1,160 +0,0 @@ -import XCTest -import SQLite - -class StatementTests: SQLiteTestCase { - - override func setUp() { - createUsersTable() - - super.setUp() - } - - func test_bind_withVariadicParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?") - withBlob { stmt.bind(1, 2.0, "3", $0) } - AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) - } - - func test_bind_withArrayOfParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?") - withBlob { stmt.bind([1, 2.0, "3", $0]) } - AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) - } - - func test_bind_withNamedParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4") - withBlob { stmt.bind(["?1": 1, "?2": 2.0, "?3": "3", "?4": $0]) } - AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) - } - - func test_bind_withBlob_bindsBlob() { - let stmt = db.prepare("SELECT ?") - withBlob { stmt.bind($0) } - AssertSQL("SELECT x'34'", stmt) - } - - func test_bind_withDouble_bindsDouble() { - AssertSQL("SELECT 2.0", db.prepare("SELECT ?").bind(2.0)) - } - - func test_bind_withInt_bindsInt() { - AssertSQL("SELECT 3", db.prepare("SELECT ?").bind(3)) - } - - func test_bind_withString() { - AssertSQL("SELECT '4'", db.prepare("SELECT ?").bind("4")) - } - - func test_run_withNoParameters() { - db.prepare("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)").run() - XCTAssertEqual(1, db.totalChanges) - } - - func test_run_withVariadicParameters() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - stmt.run("alice@example.com", 1) - XCTAssertEqual(1, db.totalChanges) - } - - func test_run_withArrayOfParameters() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - stmt.run(["alice@example.com", 1]) - XCTAssertEqual(1, db.totalChanges) - } - - func test_run_withNamedParameters() { - let stmt = db.prepare( - "INSERT INTO users (email, admin) VALUES ($email, $admin)" - ) - stmt.run(["$email": "alice@example.com", "$admin": 1]) - XCTAssertEqual(1, db.totalChanges) - } - - func test_scalar_withNoParameters() { - let zero = db.prepare("SELECT 0") - XCTAssertEqual(0, zero.scalar() as! Int64) - } - - func test_scalar_withNoParameters_retainsBindings() { - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?", 21) - XCTAssertEqual(0, count.scalar() as! Int64) - - insertUser("alice", age: 21) - XCTAssertEqual(1, count.scalar() as! Int64) - } - - func test_scalar_withVariadicParameters() { - insertUser( "alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar(21) as! Int64) - } - - func test_scalar_withArrayOfParameters() { - insertUser( "alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar([21]) as! Int64) - } - - func test_scalar_withNamedParameters() { - insertUser("alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= $age") - XCTAssertEqual(1, count.scalar(["$age": 21]) as! Int64) - } - - func test_scalar_withParameters_updatesBindings() { - insertUser("alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar(21) as! Int64) - XCTAssertEqual(0, count.scalar(22) as! Int64) - } - - func test_scalar_doubleReturnValue() { - XCTAssertEqual(2.0, db.scalar("SELECT 2.0") as! Double) - } - - func test_scalar_intReturnValue() { - XCTAssertEqual(3, db.scalar("SELECT 3") as! Int64) - } - - func test_scalar_stringReturnValue() { - XCTAssertEqual("4", db.scalar("SELECT '4'") as! String) - } - - func test_generate_allowsIteration() { - insertUsers("alice", "betsy", "cindy") - var count = 0 - for row in db.prepare("SELECT id FROM users") { - XCTAssertEqual(1, row.count) - count++ - } - XCTAssertEqual(3, count) - } - - func test_generate_allowsReuse() { - insertUsers("alice", "betsy", "cindy") - var count = 0 - let stmt = db.prepare("SELECT id FROM users") - for row in stmt { count++ } - for row in stmt { count++ } - XCTAssertEqual(6, count) - } - - func test_row_returnsValues() { - insertUser("alice") - let stmt = db.prepare("SELECT id, email FROM users") - stmt.step() - - XCTAssertEqual(1, stmt.row[0] as Int64) - XCTAssertEqual("alice@example.com", stmt.row[1] as String) - } - -} - -func withBlob(block: Blob -> Void) { - let length = 1 - let buflen = Int(length) + 1 - let buffer = UnsafeMutablePointer.alloc(buflen) - memcpy(buffer, "4", length) - block(Blob(bytes: buffer, length: length)) - buffer.dealloc(buflen) -} diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift deleted file mode 100644 index 41600001..00000000 --- a/SQLite Tests/TestHelper.swift +++ /dev/null @@ -1,83 +0,0 @@ -import SQLite -import XCTest - -let id = Expression("id") -let email = Expression("email") -let age = Expression("age") -let salary = Expression("salary") -let admin = Expression("admin") -let manager_id = Expression("manager_id") - -class SQLiteTestCase: XCTestCase { - - var trace = [String: Int]() - - let db = Database() - - var users: Query { return db["users"] } - - override func setUp() { - super.setUp() - - db.trace { SQL in - println(SQL) - self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 - } - } - - func createUsersTable() { - db.execute( - "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "salary REAL, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" - ) - } - - func insertUsers(names: String...) { - insertUsers(names) - } - - func insertUsers(names: [String]) { - for name in names { insertUser(name) } - } - - func insertUser(name: String, age: Int? = nil, admin: Bool = false) -> Statement { - return db.run( - "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - ["\(name)@example.com", age, admin.datatypeValue] - ) - } - - func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - XCTAssertEqual( - executions, trace[SQL] ?? 0, - message ?? SQL, - file: file, line: line - ) - } - - func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - statement.run() - AssertSQL(SQL, 1, message, file: file, line: line) - if let count = trace[SQL] { trace[SQL] = count - 1 } - } - - func AssertSQL(SQL: String, _ query: Query, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - for _ in query {} - AssertSQL(SQL, 1, message, file: file, line: line) - if let count = trace[SQL] { trace[SQL] = count - 1 } - } - - func async(expect description: String = "async", timeout: Double = 5, @noescape block: (() -> Void) -> Void) { - let expectation = expectationWithDescription(description) - block(expectation.fulfill) - waitForExpectationsWithTimeout(timeout, handler: nil) - } - -} diff --git a/SQLite Tests/ValueTests.swift b/SQLite Tests/ValueTests.swift deleted file mode 100644 index edd16ab1..00000000 --- a/SQLite Tests/ValueTests.swift +++ /dev/null @@ -1,13 +0,0 @@ -import XCTest -import SQLite - -class ValueTests: SQLiteTestCase { - - func test_blob_toHex() { - let blob = Blob( - data:[0,10,20,30,40,50,60,70,80,90,100,150,250,255] as [UInt8] - ) - XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") - } - -} diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index bf7f843b..3a685679 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -1,218 +1,43 @@ -/*: -> _Note_: This playground must be running inside the Xcode project to run. Build the OS X framework prior to use. (You may have to close and reopen the project after building it.) - -# SQLite.swift - -This playground contains sample code to explore [SQLite.swift](https://github.com/stephencelis/SQLite.swift), a [Swift](https://developer.apple.com/swift) wrapper for [SQLite3](https://sqlite.org). - -Let’s get started by importing the framework and opening a new in-memory database connection using the `Database` class. -*/ import SQLite -let db = Database() -/*: -This implicitly opens a database in memory (using `.InMemory`). To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*, - - Database("path/to/database.sqlite3") - -Pass `.Temporary` to open a temporary, disk-backed database, instead. - -Once we initialize a database connection, we can execute SQL statements directly against it. Let’s create a table. -*/ -db.execute( - "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" -) -/*: -The `execute` function can run multiple SQL statements at once as a convenience and will throw an assertion failure if an error occurs during execution. This is useful for seeding and migrating databases with well-tested statements that are guaranteed to succeed (or where failure can be graceful and silent). - -It’s generally safer to prepare SQL statements individually. Let’s build a `Statement` object and insert a couple rows. - -*/ -let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") -for (email, admin) in ["alice@acme.com": 1, "betsy@acme.com": 0] { - stmt.run(email, admin) -} -/*: -Prepared statements can bind and escape input values safely. In this case, `email` and `admin` columns are bound with different values over two executions. - -The `Database` class exposes information about recently run queries via several properties: `totalChanges` returns the total number of changes (inserts, updates, and deletes) since the connection was opened; `changes` returns the number of changes from the last statement that modified the database; `lastInsertRowid` returns the rowid of the last insert. -*/ -db.totalChanges -db.changes -db.lastInsertRowid -/*: -## Querying - -`Statement` objects act as both sequences _and_ generators. We can iterate over a select statement’s rows directly using a `for`–`in` loop. -*/ -for row in db.prepare("SELECT id, email FROM users") { - println("id: \(row[0]), email: \(row[1])") -} -/*: -Single, scalar values can be plucked directly from a statement. -*/ -let count = db.prepare("SELECT count(*) FROM users") -count.scalar() +let db = try! Connection() -db.scalar("SELECT email FROM users WHERE id = ?", 1) -/*: -> ### Experiment -> -> Try plucking a single row by taking advantage of the fact that `Statement` conforms to the `GeneratorType` protocol. -> -> Also try using the `Array` initializer to return an array of all rows at once. +db.trace(print) -## Transactions & Savepoints +let users = Table("users") -Using the `transaction` and `savepoint` functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back. In the following example we prepare two statements: one to insert a manager into the database, and one—given a manager’s rowid—to insert a managed user into the database. -*/ -let sr = db.prepare("INSERT INTO users (email, admin) VALUES (?, 1)") -let jr = db.prepare("INSERT INTO users (email, admin, manager_id) VALUES (?, 0, ?)") -/*: -Statements can be chained with other statements using the `&&` and `||` operators. The right-hand side is an auto-closure and therefore has access to database information at the time of execution. In this case, we insert Dolly, a supervisor, and immediately reference her rowid when we insert her assistant, Emery. -*/ -db.transaction() - && sr.run("dolly@acme.com") - && jr.run("emery@acme.com", db.lastInsertRowid) - && db.commit() - || db.rollback() -/*: -Our database has a uniqueness constraint on email address, so let’s see what happens when we insert Fiona, who also claims to be managing Emery. -*/ -let txn = db.transaction() - && sr.run("fiona@acme.com") - && jr.run("emery@acme.com", db.lastInsertRowid) - && db.commit() -txn || db.rollback() - -count.scalar() - -txn.failed -txn.reason -/*: -This time, our transaction fails because Emery has already been added to the database. The addition of Fiona has been rolled back, and we’ll need to get to the bottom of this discrepancy (or make some schematic changes to our database to allow for multiple managers per user). - -> ### Experiment -> -> Transactions can’t be nested, but savepoints can! Try calling the `savepoint` function instead, which shares semantics with `transaction`, but can successfully run in layers. - -## Query Building - -SQLite.swift provides a powerful, type-safe query builder. With only a small amount of boilerplate to map our columns to types, we can ensure the queries we build are valid upon compilation. -*/ let id = Expression("id") let email = Expression("email") -let age = Expression("age") -let admin = Expression("admin") -let managerId = Expression("manager_id") -/*: -The query-building interface is provided via the `Query` struct. We can access this interface by subscripting our database connection with a table name. -*/ -let users = db["users"] -/*: -From here, we can build a variety of queries. For example, we can build and run an `INSERT` statement by calling the query’s `insert` function. Let’s add a few new rows this way. -*/ -users.insert(email <- "giles@acme.com", age <- 42, admin <- true).rowid -users.insert(email <- "haley@acme.com", age <- 30, admin <- true).rowid -users.insert(email <- "inigo@acme.com", age <- 24).rowid -/*: -No room for syntax errors! Try changing an input to the wrong type and see what happens. +let name = Expression("name") -The `insert` function can return a `rowid` (which will be `nil` in the case of failure) and the just-run `statement`. It can also return a `Statement` object directly, making it easy to run in a transaction. -*/ -db.transaction() - && users.insert(email <- "julie@acme.com") - && users.insert(email <- "kelly@acme.com", managerId <- db.lastInsertRowid) - && db.commit() - || db.rollback() -/*: -`Query` objects can also build `SELECT` statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement. -*/ -// SELECT * FROM users -for user in users { - println(user[email]) -} -/*: -You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given `Row` objects, which can be subscripted with the same expressions we prepared above. This gives us a little more powerful of a mapping to work with and pass around. +try! db.run(users.create { t in + t.column(id, primaryKey: true) + t.column(email, unique: true, check: email.like("%@%")) + t.column(name) +}) -Queries can be used and reused, and can quickly return rows, counts and other aggregate values. -*/ -// SELECT * FROM users LIMIT 1 -users.first +let rowid = try! db.run(users.insert(email <- "alice@mac.com")) +let alice = users.filter(id == rowid) -// SELECT count(*) FROM users -users.count - -users.min(age) -users.max(age) -users.average(age) -/*: -> ### Experiment -> -> In addition to `first`, you can also try plucking the `last` row from the result set in an optimized fashion. -> -> The example above uses the computed variable `count`, but `Query` has a `count` function, as well. (The computed variable is actually a convenience wrapper around `count(*)`.) Try counting the distinct ages in our group of users. -> -> Try calling the `sum` and `total` functions. Note the differences! - -Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset. -*/ -let admins = users.filter(admin) -/*: -Filtered queries will in turn filter their aggregate functions. -*/ -// SELECT count(*) FROM users WHERE admin -admins.count -/*: -Alongside `filter`, we can use the `select`, `join`, `group`, `order`, and `limit` functions to compose rich queries with safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows. -*/ -let ordered = admins.order(email.asc, age.asc).limit(3) - -// SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3 -for admin in ordered { - println(admin[id]) - println(admin[age]) +for user in db.prepare(users) { + print("id: \(user[id]), email: \(user[email])") } -/*: -> ### Experiment -> -> Try using the `select` function to specify which columns are returned. -> -> Try using the `group` function to group users by a column. -> -> Try to return results by a column in descending order. -> -> Try using an alternative `limit` function to add an `OFFSET` clause to the query. -We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages. -*/ -let agelessAdmins = admins.filter(age == nil) +let emails = VirtualTable("emails") -// SELECT count(*) FROM users WHERE (admin AND age IS NULL) -agelessAdmins.count -/*: -Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s `update` interface to (temporarily) revoke their privileges while we wait for them to update their profiles. -*/ -// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL) -agelessAdmins.update(admin <- false).changes -/*: -If we ever need to remove rows from our database, we can use the `delete` function, which will be scoped to a query’s filters. **Be careful!** You may just want to archive the records, instead. +let subject = Expression("subject") +let body = Expression("body") -We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, _carefully_ scope a query to match her and delete her record. -*/ -// DELETE FROM users WHERE (email = 'alice@acme.com') -users.filter(email == "alice@acme.com").delete().changes -/*: -And that’s that. +try! db.run(emails.create(.FTS4(subject, body))) -## & More… +try! db.run(emails.insert( + subject <- "Hello, world!", + body <- "This is a hello world message." +)) -We’ve only explored the surface to SQLite.swift. Dive into the code to discover more! -*/ +let row = db.pluck(emails.match("hello")) + +let query = db.prepare(emails.match("hello")) +for row in query { + print(row[subject]) +} diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index 3de2b51b..59b4af3e 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 5aad0d24..39e910cb 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,48 +1,16 @@ +require_relative 'Supporting Files/podspec.rb' + Pod::Spec.new do |s| - s.name = 'SQLite.swift' - s.module_name = 'SQLite' - s.version = '0.1.0.pre' - s.summary = 'A type-safe, Swift-language layer over SQLite3.' + spec.name = 'SQLite.swift' + spec.summary = 'A type-safe, Swift-language layer over SQLite3.' - s.description = <<-DESC + spec.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and intent. DESC - s.homepage = 'https://github.com/stephencelis/SQLite.swift' - s.license = { type: 'MIT', file: 'LICENSE.txt' } - - s.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } - s.social_media_url = 'https://twitter.com/stephencelis' - - s.source = { - git: 'https://github.com/stephencelis/SQLite.swift.git', - tag: s.version - } - - s.module_map = 'SQLite/module.modulemap' - - s.default_subspec = 'Library' - - s.subspec 'Core' do |ss| - ss.source_files = 'SQLite/**/*.{swift,c,h,m}' - ss.private_header_files = 'SQLite/fts3_tokenizer.h' - end - - s.subspec 'Library' do |ss| - ss.dependency 'SQLite.swift/Core' - - ss.library = 'sqlite3' - end - - s.subspec 'Cipher' do |ss| - ss.dependency 'SQLCipher' - ss.dependency 'SQLite.swift/Core' - - ss.source_files = 'SQLiteCipher/**/*.{swift,c,h,m}' + apply_shared_config spec, 'SQLite' - ss.xcconfig = { - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' - } - end + spec.exclude_files = 'Source/Cipher/Cipher.swift' + spec.library = 'sqlite3' end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index dabac2e8..cc0edaaf 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -3,474 +3,794 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 47; objects = { /* Begin PBXBuildFile section */ - 8E7D32B61B8FB67C003C1892 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */; }; - DC109CE11A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; - DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; - DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; - DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; - DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; - DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; - DC2F675E1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */; }; - DC2F675F1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */; }; - DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; }; - DC37743519C8D626004FCF85 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; - DC37743819C8D693004FCF85 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; - DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; - DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC475E9E19F2199900788FBD /* ExpressionTests.swift */; }; - DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; - DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; - DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; - DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; - DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; - DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */; }; - DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28401ABDF18F0042A3FC /* RTree.swift */; }; - DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28401ABDF18F0042A3FC /* RTree.swift */; }; - DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */; }; - DCC6B36F1A9191C300734B78 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; - DCC6B3701A9191C300734B78 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; - DCC6B3711A9191C300734B78 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; - DCC6B3721A9191C300734B78 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; - DCC6B3731A9191C300734B78 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; - DCC6B3741A9191C300734B78 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; - DCC6B3791A9191C300734B78 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6B3A31A9194A800734B78 /* Cipher.swift */; }; - DCC6B3A61A9194FB00734B78 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6B3A51A9194FB00734B78 /* CipherTests.swift */; }; - DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F17121A814F7000C83A2F /* FunctionsTests.swift */; }; - DCC6B3A81A91975700734B78 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F170F1A8127A300C83A2F /* Functions.swift */; }; - DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F170F1A8127A300C83A2F /* Functions.swift */; }; - DCC6B3AC1A91979F00734B78 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC6B3801A9191C300734B78 /* SQLite.framework */; }; - DCC6B3AD1A9197B500734B78 /* TestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8719DDAF79001534AA /* TestHelper.swift */; }; - DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */; }; - DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8419DDAF3F001534AA /* StatementTests.swift */; }; - DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8719DDAF79001534AA /* TestHelper.swift */; }; + DC0B8BAB1B485B620084D886 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC0B8BAD1B497A400084D886 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC2609C01B47504200C3E6B5 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC2609C21B475C9900C3E6B5 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC2609C41B475CA500C3E6B5 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC2B29ED1B26F718001C60EA /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2B29E21B26F718001C60EA /* SQLite.framework */; }; + DC2B2A161B26FE7F001C60EA /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC2D38BE1B2C7A73003EE2F2 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC303D6E1B2CFA1F00469AEA /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC303D701B2CFA4700469AEA /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC303D731B2E2ECD00469AEA /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D711B2E2E8600469AEA /* ConnectionTests.swift */; }; + DC303D761B2E306000469AEA /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D751B2E306000469AEA /* TestHelpers.swift */; }; + DC32A7861B575FD7002FFBE2 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC32A7871B575FD7002FFBE2 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC32A7961B576715002FFBE2 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DC32A7971B57672E002FFBE2 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC32A7981B57672E002FFBE2 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC32A7991B57672E002FFBE2 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC32A79A1B57672E002FFBE2 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC32A79B1B57672E002FFBE2 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC32A79C1B57672E002FFBE2 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC32A79D1B57672E002FFBE2 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC32A79E1B57672E002FFBE2 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC32A79F1B57672E002FFBE2 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DC32A7A01B57672E002FFBE2 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC32A7A11B57672E002FFBE2 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC32A7A21B57672E002FFBE2 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC32A7A31B57672E002FFBE2 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC32A7A41B57672E002FFBE2 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC32A7A51B57672E002FFBE2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DC32A7A91B5AAE75002FFBE2 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC32A7AA1B5AAE76002FFBE2 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC32A7AE1B5AC2C5002FFBE2 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A78D1B576209002FFBE2 /* StatementTests.swift */; }; + DC32A7AF1B5AC2C5002FFBE2 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7901B576219002FFBE2 /* ValueTests.swift */; }; + DC32A7B01B5AC2C5002FFBE2 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7931B57622C002FFBE2 /* BlobTests.swift */; }; + DC32A7B11B5AC2C5002FFBE2 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */; }; + DC32A7B21B5AC2C5002FFBE2 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B81B4A012A0010A7E9 /* QueryTests.swift */; }; + DC32A7B31B5AC2C5002FFBE2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A91B53216000B12E66 /* SchemaTests.swift */; }; + DC32A7B41B5AC2C5002FFBE2 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */; }; + DC32A7B51B5AC2C5002FFBE2 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */; }; + DC32A7B61B5AC2C5002FFBE2 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */; }; + DC32A7B71B5AC2C5002FFBE2 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B61B49DCE10010A7E9 /* SetterTests.swift */; }; + DC32A7B81B5AC2C5002FFBE2 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */; }; + DC32A7B91B5AC2C5002FFBE2 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */; }; + DC32A7BA1B5C1769002FFBE2 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A78D1B576209002FFBE2 /* StatementTests.swift */; }; + DC32A7BB1B5C1769002FFBE2 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7901B576219002FFBE2 /* ValueTests.swift */; }; + DC32A7BC1B5C1769002FFBE2 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7931B57622C002FFBE2 /* BlobTests.swift */; }; + DC32A7BD1B5C1769002FFBE2 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */; }; + DC32A7BE1B5C1769002FFBE2 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B81B4A012A0010A7E9 /* QueryTests.swift */; }; + DC32A7BF1B5C1769002FFBE2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A91B53216000B12E66 /* SchemaTests.swift */; }; + DC32A7C01B5C1769002FFBE2 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */; }; + DC32A7C11B5C1769002FFBE2 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */; }; + DC32A7C21B5C1769002FFBE2 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */; }; + DC32A7C31B5C1769002FFBE2 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B61B49DCE10010A7E9 /* SetterTests.swift */; }; + DC32A7C41B5C1769002FFBE2 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */; }; + DC32A7C51B5C1769002FFBE2 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */; }; + DC32A7C91B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */; }; + DC32A7CA1B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */; }; + DC461BB81B2E687900E9DABD /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC461BB91B2E687900E9DABD /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC461BBB1B2E68BC00E9DABD /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC47D0661B52C9D200B12E66 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC47D0671B52C9D200B12E66 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC47D0A61B52F5B900B12E66 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC47D0A71B52F5B900B12E66 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC6C03541B74451F00EFC04E /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BB721B7434B70040D364 /* Cipher.swift */; }; + DC6C03551B74453300EFC04E /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC6C03561B74453300EFC04E /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC6C03571B74453300EFC04E /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC6C03581B74453300EFC04E /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC6C03591B74454100EFC04E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC6C035A1B74454100EFC04E /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC6C035B1B74454100EFC04E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DC6C035C1B74454100EFC04E /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DC6C035D1B74454100EFC04E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC6C035E1B74454100EFC04E /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC6C035F1B74454100EFC04E /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC6C03601B74454100EFC04E /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC6C03611B74454100EFC04E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC6C03621B74454100EFC04E /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC6C03631B74454100EFC04E /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC6C03641B74454100EFC04E /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC6C03651B74454100EFC04E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DC6C03661B74454D00EFC04E /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC6C03671B74454D00EFC04E /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC6C03681B74454D00EFC04E /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC6C03691B74454D00EFC04E /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC6C036A1B74454D00EFC04E /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC6C036B1B74454D00EFC04E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC6C036C1B74454D00EFC04E /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC6C036D1B74454D00EFC04E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DC6C036E1B74454D00EFC04E /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DC6C036F1B74454D00EFC04E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC6C03701B74454D00EFC04E /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC6C03711B74454D00EFC04E /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC6C03721B74454D00EFC04E /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC6C03731B74454D00EFC04E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC6C03741B74454D00EFC04E /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC6C03751B74454D00EFC04E /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC6C03761B74454D00EFC04E /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC6C03771B74454D00EFC04E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DC6C03791B74459C00EFC04E /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037A1B74459F00EFC04E /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037B1B7445A100EFC04E /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC6C037C1B7445AC00EFC04E /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037D1B7445B200EFC04E /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037E1B7445B400EFC04E /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC6C037F1B74461300EFC04E /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC72349E1B49D3AB007F513E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC7253B61B52ADEC009B38B1 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC7253AC1B52ADEB009B38B1 /* SQLite.framework */; }; + DC7489A41B69A66C0032CF28 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC7489A51B69A66C0032CF28 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC795C561B55AC8100DA0EF9 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D751B2E306000469AEA /* TestHelpers.swift */; }; + DC795C571B55AC8100DA0EF9 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D711B2E2E8600469AEA /* ConnectionTests.swift */; }; + DCA7B9E81B4D4F30006A9F04 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DCAA22861B4A9CB2005E00A2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DCDF55B31B49ACBC00E1A168 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DCE7BB841B7434F90040D364 /* SQLiteCipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */; }; + DCE7BB911B74350E0040D364 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BB721B7434B70040D364 /* Cipher.swift */; }; + DCE7BB9C1B7435E90040D364 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BB971B7435E90040D364 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BB9D1B7435E90040D364 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BB971B7435E90040D364 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BBA21B7436140040D364 /* SQLiteCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BBA01B7436140040D364 /* SQLiteCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BBBA1B7438620040D364 /* SQLiteCipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */; }; + DCE7BBC71B7438740040D364 /* SQLiteCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BBA01B7436140040D364 /* SQLiteCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BBC91B743A320040D364 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BBC81B743A320040D364 /* CipherTests.swift */; }; + DCE7BBCA1B743A320040D364 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BBC81B743A320040D364 /* CipherTests.swift */; }; + DCE7BBD41B743C970040D364 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB711B7434310040D364 /* libsqlcipher.a */; }; + DCE7BBD51B743C9C0040D364 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB711B7434310040D364 /* libsqlcipher.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */ = { + DC2B29EE1B26F718001C60EA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC2B29E11B26F718001C60EA; + remoteInfo = SQLite; + }; + DC7253B71B52ADEC009B38B1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC7253AB1B52ADEB009B38B1; + remoteInfo = "SQLite Mac"; + }; + DCE7BB701B7434310040D364 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; proxyType = 2; remoteGlobalIDString = D2AAC046055464E500DB518D; remoteInfo = sqlcipher; }; - DC2DD6B11ABE438900C2C71A /* PBXContainerItemProxy */ = { + DCE7BB851B7434F90040D364 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + proxyType = 1; + remoteGlobalIDString = DCE7BB791B7434F90040D364; + remoteInfo = SQLiteCipher; + }; + DCE7BBA51B7436D10040D364 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; proxyType = 1; remoteGlobalIDString = D2AAC045055464E500DB518D; remoteInfo = sqlcipher; }; - DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */ = { + DCE7BBBB1B7438620040D364 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; proxyType = 1; - remoteGlobalIDString = DC3773F219C8CBB3004FCF85; - remoteInfo = SQLite; + remoteGlobalIDString = DCE7BBAF1B7438610040D364; + remoteInfo = "SQLiteCipher Mac"; }; - DCC6B3AA1A91979100734B78 /* PBXContainerItemProxy */ = { + DCE7BBD01B743C220040D364 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; + containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; proxyType = 1; - remoteGlobalIDString = DCC6B36D1A9191C300734B78; - remoteInfo = SQLiteCipher; + remoteGlobalIDString = D2AAC045055464E500DB518D; + remoteInfo = sqlcipher; }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - DC2393D21ABE37C4003FF113 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; +/* Begin PBXFileReference section */ + DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; + DC0B8BAC1B497A400084D886 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; + DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; + DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; + DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; + DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; + DC2609C11B475C9900C3E6B5 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; + DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; + DC2763B61B49DCE10010A7E9 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; + DC2763B81B4A012A0010A7E9 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; + DC2B29E21B26F718001C60EA /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DC2B2A011B26FD44001C60EA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; + DC2B2A151B26FE7F001C60EA /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; + DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; + DC303D6D1B2CFA1F00469AEA /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; + DC303D6F1B2CFA4700469AEA /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; + DC303D711B2E2E8600469AEA /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; + DC303D751B2E306000469AEA /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; + DC32A7851B575FD7002FFBE2 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; + DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; + DC32A78D1B576209002FFBE2 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; + DC32A7901B576219002FFBE2 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; + DC32A7931B57622C002FFBE2 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; + DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; + DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*TreeTests.swift"; sourceTree = ""; }; + DC32A7C61B5C26A6002FFBE2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; + DC40A4C61B7D2B3C005BA12D /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; + DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; + DC47D0651B52C9D200B12E66 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; + DC47D0A51B52F5B900B12E66 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; + DC47D0A91B53216000B12E66 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; + DC72349D1B49D3AB007F513E /* Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setter.swift; sourceTree = ""; }; + DC7253AC1B52ADEB009B38B1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Mac Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DC7489A31B69A66C0032CF28 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; + DCA7B9E71B4D4F30006A9F04 /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; + DCAA22851B4A9CB2005E00A2 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; + DCDF55B21B49ACBC00E1A168 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; + DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = Vendor/sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; + DCE7BB721B7434B70040D364 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; + DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BB951B7435E90040D364 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; + DCE7BB961B7435E90040D364 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DCE7BB971B7435E90040D364 /* SQLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; + DCE7BB9F1B7436140040D364 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DCE7BBA01B7436140040D364 /* SQLiteCipher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLiteCipher.h; sourceTree = ""; }; + DCE7BBA91B7437780040D364 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; + DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SQLiteCipher.xcconfig; sourceTree = ""; }; + DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Mac Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BBC81B743A320040D364 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + DC2B29DE1B26F718001C60EA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; files = ( - DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */, ); - name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; - DC109CE01A0C4D970070988E /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Schema.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; - DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; - DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; - DC3773F319C8CBB3004FCF85 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../SQLite/Info.plist; sourceTree = ""; }; - DC3773F819C8CBB3004FCF85 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLite.h; path = ../SQLite/SQLite.h; sourceTree = ""; }; - DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DC37740419C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; - DC37743419C8D626004FCF85 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Database.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37743719C8D693004FCF85 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Value.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37743A19C8D6C0004FCF85 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37744219C8DC91004FCF85 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; - DC37744719C8F50B004FCF85 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - DC3F170F1A8127A300C83A2F /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; - DC3F17121A814F7000C83A2F /* FunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionsTests.swift; sourceTree = ""; }; - DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC74B0421B095575007E1138 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - DC97EC9F1ADE955E00F550A6 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; - DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; - DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; - DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; - DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; - DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = Vendor/sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; - DCC6B3A31A9194A800734B78 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; - DCC6B3A51A9194FB00734B78 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; - DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseTests.swift; sourceTree = ""; }; - DCF37F8419DDAF3F001534AA /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = StatementTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DCF37F8719DDAF79001534AA /* TestHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - DC3773EF19C8CBB3004FCF85 /* Frameworks */ = { + DC2B29E91B26F718001C60EA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC2B29ED1B26F718001C60EA /* SQLite.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253A81B52ADEB009B38B1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253B21B52ADEC009B38B1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */, + DC7253B61B52ADEC009B38B1 /* SQLite.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DC3773FB19C8CBB3004FCF85 /* Frameworks */ = { + DCE7BB761B7434F90040D364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */, + DCE7BBD41B743C970040D364 /* libsqlcipher.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B3761A9191C300734B78 /* Frameworks */ = { + DCE7BB801B7434F90040D364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */, - DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */, + DCE7BB841B7434F90040D364 /* SQLiteCipher.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B38C1A9191D100734B78 /* Frameworks */ = { + DCE7BBAC1B7438610040D364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B3AC1A91979F00734B78 /* SQLite.framework in Frameworks */, + DCE7BBD51B743C9C0040D364 /* libsqlcipher.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBB61B7438620040D364 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBBA1B7438620040D364 /* SQLiteCipher.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - DC037C0919F0240100959746 /* Query Building */ = { + DC2B29D81B26F718001C60EA = { isa = PBXGroup; children = ( - DC650B9519F0CDC3002FBE91 /* Expression.swift */, - DC3F170F1A8127A300C83A2F /* Functions.swift */, - DCAD429619E2E0F1004A51DF /* Query.swift */, - DC109CE01A0C4D970070988E /* Schema.swift */, - DCAFEAD21AABC818000C21A1 /* FTS.swift */, - DCBE28401ABDF18F0042A3FC /* RTree.swift */, - ); - name = "Query Building"; + DC32A7C61B5C26A6002FFBE2 /* README.md */, + DC40A4C61B7D2B3C005BA12D /* SQLite.playground */, + DC2B29FF1B26FD44001C60EA /* Source */, + DC2B2A001B26FD44001C60EA /* Tests */, + DC2B2A0C1B26FDEF001C60EA /* Supporting Files */, + DC2B29E31B26F718001C60EA /* Products */, + DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */, + ); sourceTree = ""; }; - DC10500F19C904DD00D8CA30 /* SQLite Tests */ = { + DC2B29E31B26F718001C60EA /* Products */ = { isa = PBXGroup; children = ( - DCF37F8719DDAF79001534AA /* TestHelper.swift */, - DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */, - DCF37F8419DDAF3F001534AA /* StatementTests.swift */, - DC475E9E19F2199900788FBD /* ExpressionTests.swift */, - DC3F17121A814F7000C83A2F /* FunctionsTests.swift */, - DCAD429919E2EE50004A51DF /* QueryTests.swift */, - DC109CE31A0C4F5D0070988E /* SchemaTests.swift */, - DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */, - DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */, - 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */, - DC37740319C8CBB3004FCF85 /* Supporting Files */, - ); - path = "SQLite Tests"; + DC2B29E21B26F718001C60EA /* SQLite.framework */, + DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */, + DC7253AC1B52ADEB009B38B1 /* SQLite.framework */, + DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */, + DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */, + DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */, + DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */, + DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */, + ); + name = Products; sourceTree = ""; }; - DC2DD6A71ABE428E00C2C71A /* Products */ = { + DC2B29FF1B26FD44001C60EA /* Source */ = { isa = PBXGroup; children = ( - DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */, + DC2B2A141B26FE5C001C60EA /* Core */, + DC2D38BC1B2C7A39003EE2F2 /* Typed */, + DC2D38BA1B2C7A39003EE2F2 /* Extensions */, + DC2D38B91B2C7A39003EE2F2 /* Cipher */, + DC47D0A51B52F5B900B12E66 /* Foundation.swift */, + DCAA22851B4A9CB2005E00A2 /* Helpers.swift */, ); - name = Products; + path = Source; sourceTree = ""; }; - DC3773E919C8CBB3004FCF85 = { + DC2B2A001B26FD44001C60EA /* Tests */ = { isa = PBXGroup; children = ( - DC37744719C8F50B004FCF85 /* README.md */, - DC97EC9F1ADE955E00F550A6 /* SQLite.playground */, - DC37742D19C8CC90004FCF85 /* SQLite */, - DC10500F19C904DD00D8CA30 /* SQLite Tests */, - DCC6B3A11A91949C00734B78 /* SQLiteCipher */, - DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */, - DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */, - DC3773F419C8CBB3004FCF85 /* Products */, - ); - indentWidth = 4; + DC2B2A011B26FD44001C60EA /* Info.plist */, + DC303D751B2E306000469AEA /* TestHelpers.swift */, + DC303D711B2E2E8600469AEA /* ConnectionTests.swift */, + DC32A78D1B576209002FFBE2 /* StatementTests.swift */, + DC32A7901B576219002FFBE2 /* ValueTests.swift */, + DC32A7931B57622C002FFBE2 /* BlobTests.swift */, + DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */, + DC2763B81B4A012A0010A7E9 /* QueryTests.swift */, + DC47D0A91B53216000B12E66 /* SchemaTests.swift */, + DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */, + DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */, + DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */, + DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */, + DC2763B61B49DCE10010A7E9 /* SetterTests.swift */, + DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */, + DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */, + DCE7BBC81B743A320040D364 /* CipherTests.swift */, + ); + path = Tests; sourceTree = ""; - tabWidth = 4; - usesTabs = 0; }; - DC3773F419C8CBB3004FCF85 /* Products */ = { + DC2B2A0C1B26FDEF001C60EA /* Supporting Files */ = { isa = PBXGroup; children = ( - DC3773F319C8CBB3004FCF85 /* SQLite.framework */, - DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */, - DCC6B3801A9191C300734B78 /* SQLite.framework */, - DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */, + DCE7BBA91B7437780040D364 /* Base.xcconfig */, + DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */, + DCE7BB941B7435E90040D364 /* SQLite */, + DCE7BB9E1B7436140040D364 /* SQLiteCipher */, ); - name = Products; + path = "Supporting Files"; sourceTree = ""; }; - DC37740319C8CBB3004FCF85 /* Supporting Files */ = { + DC2B2A141B26FE5C001C60EA /* Core */ = { isa = PBXGroup; children = ( - DC37740419C8CBB3004FCF85 /* Info.plist */, + DC2B2A151B26FE7F001C60EA /* Connection.swift */, + DC303D6D1B2CFA1F00469AEA /* Statement.swift */, + DC303D6F1B2CFA4700469AEA /* Value.swift */, + DC47D0651B52C9D200B12E66 /* Blob.swift */, + DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */, + DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */, + DC7489A31B69A66C0032CF28 /* sqlite3.h */, + DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */, ); - name = "Supporting Files"; - path = "../SQLite Tests"; + path = Core; sourceTree = ""; }; - DC37742D19C8CC90004FCF85 /* SQLite */ = { + DC2D38B91B2C7A39003EE2F2 /* Cipher */ = { isa = PBXGroup; children = ( - DC37743419C8D626004FCF85 /* Database.swift */, - DC37743A19C8D6C0004FCF85 /* Statement.swift */, - DC37743719C8D693004FCF85 /* Value.swift */, - DC037C0919F0240100959746 /* Query Building */, - DC37743319C8CFCE004FCF85 /* Supporting Files */, + DCE7BB721B7434B70040D364 /* Cipher.swift */, ); - path = SQLite; + path = Cipher; sourceTree = ""; }; - DC37743319C8CFCE004FCF85 /* Supporting Files */ = { + DC2D38BA1B2C7A39003EE2F2 /* Extensions */ = { isa = PBXGroup; children = ( - DC3773F819C8CBB3004FCF85 /* SQLite.h */, - DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */, - DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */, - DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */, - DC3773F719C8CBB3004FCF85 /* Info.plist */, - DC37744219C8DC91004FCF85 /* libsqlite3.dylib */, - DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */, - DC74B0421B095575007E1138 /* module.modulemap */, - ); - name = "Supporting Files"; + DC32A7851B575FD7002FFBE2 /* FTS4.swift */, + DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */, + ); + path = Extensions; sourceTree = ""; }; - DCC6B3A11A91949C00734B78 /* SQLiteCipher */ = { + DC2D38BC1B2C7A39003EE2F2 /* Typed */ = { isa = PBXGroup; children = ( - DCC6B3A31A9194A800734B78 /* Cipher.swift */, + DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */, + DC0B8BAC1B497A400084D886 /* Query.swift */, + DCA7B9E71B4D4F30006A9F04 /* Schema.swift */, + DCDF55B21B49ACBC00E1A168 /* Collation.swift */, + DC2609C11B475C9900C3E6B5 /* Operators.swift */, + DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */, + DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */, + DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */, + DC72349D1B49D3AB007F513E /* Setter.swift */, ); - path = SQLiteCipher; + path = Typed; sourceTree = ""; }; - DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */ = { + DCE7BB6C1B7434310040D364 /* Products */ = { isa = PBXGroup; children = ( - DCC6B3A51A9194FB00734B78 /* CipherTests.swift */, + DCE7BB711B7434310040D364 /* libsqlcipher.a */, ); - path = "SQLiteCipher Tests"; + name = Products; + sourceTree = ""; + }; + DCE7BB941B7435E90040D364 /* SQLite */ = { + isa = PBXGroup; + children = ( + DCE7BB971B7435E90040D364 /* SQLite.h */, + DCE7BB961B7435E90040D364 /* Info.plist */, + DCE7BB951B7435E90040D364 /* SQLite.xcconfig */, + ); + path = SQLite; + sourceTree = ""; + }; + DCE7BB9E1B7436140040D364 /* SQLiteCipher */ = { + isa = PBXGroup; + children = ( + DCE7BBA01B7436140040D364 /* SQLiteCipher.h */, + DCE7BB9F1B7436140040D364 /* Info.plist */, + DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */, + ); + path = SQLiteCipher; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - DC3773F019C8CBB3004FCF85 /* Headers */ = { + DC2B29DF1B26F718001C60EA /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DC2F675E1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */, - DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */, - DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, + DCE7BB9C1B7435E90040D364 /* SQLite.h in Headers */, + DC461BB81B2E687900E9DABD /* SQLite-Bridging.h in Headers */, + DC7489A41B69A66C0032CF28 /* sqlite3.h in Headers */, + DC461BBB1B2E68BC00E9DABD /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B3781A9191C300734B78 /* Headers */ = { + DC7253A91B52ADEB009B38B1 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DC2F675F1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */, - DCC6B3791A9191C300734B78 /* SQLite.h in Headers */, - DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, + DCE7BB9D1B7435E90040D364 /* SQLite.h in Headers */, + DC32A79A1B57672E002FFBE2 /* SQLite-Bridging.h in Headers */, + DC32A79C1B57672E002FFBE2 /* fts3_tokenizer.h in Headers */, + DC7489A51B69A66C0032CF28 /* sqlite3.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BB771B7434F90040D364 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBA21B7436140040D364 /* SQLiteCipher.h in Headers */, + DC6C03791B74459C00EFC04E /* SQLite-Bridging.h in Headers */, + DC6C037A1B74459F00EFC04E /* sqlite3.h in Headers */, + DC6C037B1B7445A100EFC04E /* fts3_tokenizer.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBAD1B7438610040D364 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBC71B7438740040D364 /* SQLiteCipher.h in Headers */, + DC6C037C1B7445AC00EFC04E /* SQLite-Bridging.h in Headers */, + DC6C037D1B7445B200EFC04E /* sqlite3.h in Headers */, + DC6C037E1B7445B400EFC04E /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - DC3773F219C8CBB3004FCF85 /* SQLite */ = { + DC2B29E11B26F718001C60EA /* SQLite iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = DC37740919C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite" */; + buildConfigurationList = DC2B29F61B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS" */; buildPhases = ( - DC3773EE19C8CBB3004FCF85 /* Sources */, - DC3773EF19C8CBB3004FCF85 /* Frameworks */, - DC3773F019C8CBB3004FCF85 /* Headers */, - DC3773F119C8CBB3004FCF85 /* Resources */, + DC2B29DD1B26F718001C60EA /* Sources */, + DC2B29DE1B26F718001C60EA /* Frameworks */, + DC2B29DF1B26F718001C60EA /* Headers */, + DC2B29E01B26F718001C60EA /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = SQLite; + name = "SQLite iOS"; productName = SQLite; - productReference = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; + productReference = DC2B29E21B26F718001C60EA /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; - DC3773FD19C8CBB3004FCF85 /* SQLite Tests */ = { + DC2B29EB1B26F718001C60EA /* SQLite iOS Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = DC37740C19C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite Tests" */; + buildConfigurationList = DC2B29F91B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS Tests" */; buildPhases = ( - DC3773FA19C8CBB3004FCF85 /* Sources */, - DC3773FB19C8CBB3004FCF85 /* Frameworks */, - DC3773FC19C8CBB3004FCF85 /* Resources */, + DC2B29E81B26F718001C60EA /* Sources */, + DC2B29E91B26F718001C60EA /* Frameworks */, + DC2B29EA1B26F718001C60EA /* Resources */, ); buildRules = ( ); dependencies = ( - DC37740119C8CBB3004FCF85 /* PBXTargetDependency */, + DC2B29EF1B26F718001C60EA /* PBXTargetDependency */, ); - name = "SQLite Tests"; - productName = "SQLite Tests"; - productReference = DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */; + name = "SQLite iOS Tests"; + productName = SQLiteTests; + productReference = DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - DCC6B36D1A9191C300734B78 /* SQLiteCipher */ = { + DC7253AB1B52ADEB009B38B1 /* SQLite Mac */ = { isa = PBXNativeTarget; - buildConfigurationList = DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */; + buildConfigurationList = DC7253C11B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac" */; buildPhases = ( - DCC6B36E1A9191C300734B78 /* Sources */, - DCC6B3761A9191C300734B78 /* Frameworks */, - DCC6B3781A9191C300734B78 /* Headers */, - DCC6B37C1A9191C300734B78 /* Resources */, - DC2393D21ABE37C4003FF113 /* Embed Frameworks */, + DC7253A71B52ADEB009B38B1 /* Sources */, + DC7253A81B52ADEB009B38B1 /* Frameworks */, + DC7253A91B52ADEB009B38B1 /* Headers */, + DC7253AA1B52ADEB009B38B1 /* Resources */, ); buildRules = ( ); dependencies = ( - DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */, ); - name = SQLiteCipher; - productName = SQLite; - productReference = DCC6B3801A9191C300734B78 /* SQLite.framework */; + name = "SQLite Mac"; + productName = "SQLite Mac"; + productReference = DC7253AC1B52ADEB009B38B1 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; - DCC6B3821A9191D100734B78 /* SQLiteCipher Tests */ = { + DC7253B41B52ADEC009B38B1 /* SQLite Mac Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = DCC6B38F1A9191D100734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher Tests" */; + buildConfigurationList = DC7253C21B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac Tests" */; buildPhases = ( - DCC6B3851A9191D100734B78 /* Sources */, - DCC6B38C1A9191D100734B78 /* Frameworks */, - DCC6B38E1A9191D100734B78 /* Resources */, + DC7253B11B52ADEC009B38B1 /* Sources */, + DC7253B21B52ADEC009B38B1 /* Frameworks */, + DC7253B31B52ADEC009B38B1 /* Resources */, ); buildRules = ( ); dependencies = ( - DCC6B3AB1A91979100734B78 /* PBXTargetDependency */, + DC7253B81B52ADEC009B38B1 /* PBXTargetDependency */, ); - name = "SQLiteCipher Tests"; - productName = "SQLite Tests"; - productReference = DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */; + name = "SQLite Mac Tests"; + productName = "SQLite MacTests"; + productReference = DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BB8B1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS" */; + buildPhases = ( + DCE7BB751B7434F90040D364 /* Sources */, + DCE7BB761B7434F90040D364 /* Frameworks */, + DCE7BB771B7434F90040D364 /* Headers */, + DCE7BB781B7434F90040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BBA61B7436D10040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher iOS"; + productName = SQLiteCipher; + productReference = DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */; + productType = "com.apple.product-type.framework"; + }; + DCE7BB821B7434F90040D364 /* SQLiteCipher iOS Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BB8E1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS Tests" */; + buildPhases = ( + DCE7BB7F1B7434F90040D364 /* Sources */, + DCE7BB801B7434F90040D364 /* Frameworks */, + DCE7BB811B7434F90040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BB861B7434F90040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher iOS Tests"; + productName = SQLiteCipherTests; + productReference = DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BBC11B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac" */; + buildPhases = ( + DCE7BBAB1B7438610040D364 /* Sources */, + DCE7BBAC1B7438610040D364 /* Frameworks */, + DCE7BBAD1B7438610040D364 /* Headers */, + DCE7BBAE1B7438610040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BBD11B743C220040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher Mac"; + productName = "SQLiteCipher Mac"; + productReference = DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */; + productType = "com.apple.product-type.framework"; + }; + DCE7BBB81B7438620040D364 /* SQLiteCipher Mac Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BBC41B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac Tests" */; + buildPhases = ( + DCE7BBB51B7438620040D364 /* Sources */, + DCE7BBB61B7438620040D364 /* Frameworks */, + DCE7BBB71B7438620040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BBBC1B7438620040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher Mac Tests"; + productName = "SQLiteCipher MacTests"; + productReference = DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - DC3773EA19C8CBB3004FCF85 /* Project object */ = { + DC2B29D91B26F718001C60EA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0640; - ORGANIZATIONNAME = "Stephen Celis"; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = stephencelis; TargetAttributes = { - DC3773F219C8CBB3004FCF85 = { - CreatedOnToolsVersion = 6.1; + DC2B29E11B26F718001C60EA = { + CreatedOnToolsVersion = 7.0; + }; + DC2B29EB1B26F718001C60EA = { + CreatedOnToolsVersion = 7.0; + }; + DC7253AB1B52ADEB009B38B1 = { + CreatedOnToolsVersion = 7.0; + }; + DC7253B41B52ADEC009B38B1 = { + CreatedOnToolsVersion = 7.0; + }; + DCE7BB791B7434F90040D364 = { + CreatedOnToolsVersion = 7.0; + }; + DCE7BB821B7434F90040D364 = { + CreatedOnToolsVersion = 7.0; + }; + DCE7BBAF1B7438610040D364 = { + CreatedOnToolsVersion = 7.0; }; - DC3773FD19C8CBB3004FCF85 = { - CreatedOnToolsVersion = 6.1; + DCE7BBB81B7438620040D364 = { + CreatedOnToolsVersion = 7.0; }; }; }; - buildConfigurationList = DC3773ED19C8CBB3004FCF85 /* Build configuration list for PBXProject "SQLite" */; - compatibilityVersion = "Xcode 3.2"; + buildConfigurationList = DC2B29DC1B26F718001C60EA /* Build configuration list for PBXProject "SQLite" */; + compatibilityVersion = "Xcode 6.3"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); - mainGroup = DC3773E919C8CBB3004FCF85; - productRefGroup = DC3773F419C8CBB3004FCF85 /* Products */; + mainGroup = DC2B29D81B26F718001C60EA; + productRefGroup = DC2B29E31B26F718001C60EA /* Products */; projectDirPath = ""; projectReferences = ( { - ProductGroup = DC2DD6A71ABE428E00C2C71A /* Products */; - ProjectRef = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; + ProductGroup = DCE7BB6C1B7434310040D364 /* Products */; + ProjectRef = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; }, ); projectRoot = ""; targets = ( - DC3773F219C8CBB3004FCF85 /* SQLite */, - DC3773FD19C8CBB3004FCF85 /* SQLite Tests */, - DCC6B36D1A9191C300734B78 /* SQLiteCipher */, - DCC6B3821A9191D100734B78 /* SQLiteCipher Tests */, + DC2B29E11B26F718001C60EA /* SQLite iOS */, + DC2B29EB1B26F718001C60EA /* SQLite iOS Tests */, + DC7253AB1B52ADEB009B38B1 /* SQLite Mac */, + DC7253B41B52ADEC009B38B1 /* SQLite Mac Tests */, + DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */, + DCE7BB821B7434F90040D364 /* SQLiteCipher iOS Tests */, + DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */, + DCE7BBB81B7438620040D364 /* SQLiteCipher Mac Tests */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */ = { + DCE7BB711B7434310040D364 /* libsqlcipher.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libsqlcipher.a; - remoteRef = DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */; + remoteRef = DCE7BB701B7434310040D364 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - DC3773F119C8CBB3004FCF85 /* Resources */ = { + DC2B29E01B26F718001C60EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC2B29EA1B26F718001C60EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253AA1B52ADEB009B38B1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253B31B52ADEC009B38B1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DC3773FC19C8CBB3004FCF85 /* Resources */ = { + DCE7BB781B7434F90040D364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B37C1A9191C300734B78 /* Resources */ = { + DCE7BB811B7434F90040D364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B38E1A9191D100734B78 /* Resources */ = { + DCE7BBAE1B7438610040D364 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBB71B7438620040D364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -480,89 +800,205 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - DC3773EE19C8CBB3004FCF85 /* Sources */ = { + DC2B29DD1B26F718001C60EA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DC37743519C8D626004FCF85 /* Database.swift in Sources */, - DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */, - DC37743819C8D693004FCF85 /* Value.swift in Sources */, - DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */, - DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, - DCAD429719E2E0F1004A51DF /* Query.swift in Sources */, - DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */, - DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */, - DC109CE11A0C4D970070988E /* Schema.swift in Sources */, - DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, + DC2B2A161B26FE7F001C60EA /* Connection.swift in Sources */, + DC303D6E1B2CFA1F00469AEA /* Statement.swift in Sources */, + DC303D701B2CFA4700469AEA /* Value.swift in Sources */, + DC47D0661B52C9D200B12E66 /* Blob.swift in Sources */, + DC461BB91B2E687900E9DABD /* SQLite-Bridging.m in Sources */, + DC2D38BE1B2C7A73003EE2F2 /* Expression.swift in Sources */, + DC0B8BAD1B497A400084D886 /* Query.swift in Sources */, + DCA7B9E81B4D4F30006A9F04 /* Schema.swift in Sources */, + DCDF55B31B49ACBC00E1A168 /* Collation.swift in Sources */, + DC2609C21B475C9900C3E6B5 /* Operators.swift in Sources */, + DC0B8BAB1B485B620084D886 /* AggregateFunctions.swift in Sources */, + DC2609C41B475CA500C3E6B5 /* CoreFunctions.swift in Sources */, + DC2609C01B47504200C3E6B5 /* CustomFunctions.swift in Sources */, + DC72349E1B49D3AB007F513E /* Setter.swift in Sources */, + DC32A7861B575FD7002FFBE2 /* FTS4.swift in Sources */, + DC32A7AA1B5AAE76002FFBE2 /* R*Tree.swift in Sources */, + DC47D0A61B52F5B900B12E66 /* Foundation.swift in Sources */, + DCAA22861B4A9CB2005E00A2 /* Helpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DC3773FA19C8CBB3004FCF85 /* Sources */ = { + DC2B29E81B26F718001C60EA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */, - DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */, - DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */, - DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */, - DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */, - DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */, - DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */, - 8E7D32B61B8FB67C003C1892 /* ValueTests.swift in Sources */, - DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */, - DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */, + DC32A7BF1B5C1769002FFBE2 /* SchemaTests.swift in Sources */, + DC32A7C51B5C1769002FFBE2 /* R*TreeTests.swift in Sources */, + DC303D761B2E306000469AEA /* TestHelpers.swift in Sources */, + DC32A7BE1B5C1769002FFBE2 /* QueryTests.swift in Sources */, + DC303D731B2E2ECD00469AEA /* ConnectionTests.swift in Sources */, + DC32A7C21B5C1769002FFBE2 /* CoreFunctionsTests.swift in Sources */, + DC32A7BA1B5C1769002FFBE2 /* StatementTests.swift in Sources */, + DC32A7BB1B5C1769002FFBE2 /* ValueTests.swift in Sources */, + DC32A7C91B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */, + DC32A7C41B5C1769002FFBE2 /* FTS4Tests.swift in Sources */, + DC32A7BC1B5C1769002FFBE2 /* BlobTests.swift in Sources */, + DC32A7C11B5C1769002FFBE2 /* AggregateFunctionsTests.swift in Sources */, + DC32A7BD1B5C1769002FFBE2 /* ExpressionTests.swift in Sources */, + DC32A7C31B5C1769002FFBE2 /* SetterTests.swift in Sources */, + DC32A7C01B5C1769002FFBE2 /* OperatorsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B36E1A9191C300734B78 /* Sources */ = { + DC7253A71B52ADEB009B38B1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B3721A9191C300734B78 /* Database.swift in Sources */, - DCC6B3701A9191C300734B78 /* Statement.swift in Sources */, - DCC6B3731A9191C300734B78 /* Value.swift in Sources */, - DCC6B3711A9191C300734B78 /* Expression.swift in Sources */, - DCC6B36F1A9191C300734B78 /* Query.swift in Sources */, - DCC6B3741A9191C300734B78 /* Schema.swift in Sources */, - DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */, - DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */, - DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */, - DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, - DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */, + DC32A7971B57672E002FFBE2 /* Connection.swift in Sources */, + DC32A7981B57672E002FFBE2 /* Statement.swift in Sources */, + DC32A7991B57672E002FFBE2 /* Value.swift in Sources */, + DC47D0671B52C9D200B12E66 /* Blob.swift in Sources */, + DC32A79B1B57672E002FFBE2 /* SQLite-Bridging.m in Sources */, + DC32A79D1B57672E002FFBE2 /* Expression.swift in Sources */, + DC32A79E1B57672E002FFBE2 /* Query.swift in Sources */, + DC32A7961B576715002FFBE2 /* Schema.swift in Sources */, + DC32A79F1B57672E002FFBE2 /* Collation.swift in Sources */, + DC32A7A01B57672E002FFBE2 /* Operators.swift in Sources */, + DC32A7A11B57672E002FFBE2 /* AggregateFunctions.swift in Sources */, + DC32A7A21B57672E002FFBE2 /* CoreFunctions.swift in Sources */, + DC32A7A31B57672E002FFBE2 /* CustomFunctions.swift in Sources */, + DC32A7A41B57672E002FFBE2 /* Setter.swift in Sources */, + DC32A7871B575FD7002FFBE2 /* FTS4.swift in Sources */, + DC32A7A91B5AAE75002FFBE2 /* R*Tree.swift in Sources */, + DC47D0A71B52F5B900B12E66 /* Foundation.swift in Sources */, + DC32A7A51B57672E002FFBE2 /* Helpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B3851A9191D100734B78 /* Sources */ = { + DC7253B11B52ADEC009B38B1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B3AD1A9197B500734B78 /* TestHelper.swift in Sources */, - DCC6B3A61A9194FB00734B78 /* CipherTests.swift in Sources */, + DC32A7B31B5AC2C5002FFBE2 /* SchemaTests.swift in Sources */, + DC32A7B91B5AC2C5002FFBE2 /* R*TreeTests.swift in Sources */, + DC795C561B55AC8100DA0EF9 /* TestHelpers.swift in Sources */, + DC32A7B21B5AC2C5002FFBE2 /* QueryTests.swift in Sources */, + DC795C571B55AC8100DA0EF9 /* ConnectionTests.swift in Sources */, + DC32A7B61B5AC2C5002FFBE2 /* CoreFunctionsTests.swift in Sources */, + DC32A7AE1B5AC2C5002FFBE2 /* StatementTests.swift in Sources */, + DC32A7AF1B5AC2C5002FFBE2 /* ValueTests.swift in Sources */, + DC32A7CA1B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */, + DC32A7B81B5AC2C5002FFBE2 /* FTS4Tests.swift in Sources */, + DC32A7B01B5AC2C5002FFBE2 /* BlobTests.swift in Sources */, + DC32A7B51B5AC2C5002FFBE2 /* AggregateFunctionsTests.swift in Sources */, + DC32A7B11B5AC2C5002FFBE2 /* ExpressionTests.swift in Sources */, + DC32A7B71B5AC2C5002FFBE2 /* SetterTests.swift in Sources */, + DC32A7B41B5AC2C5002FFBE2 /* OperatorsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BB751B7434F90040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC6C03551B74453300EFC04E /* Connection.swift in Sources */, + DC6C03561B74453300EFC04E /* Statement.swift in Sources */, + DC6C03571B74453300EFC04E /* Value.swift in Sources */, + DC6C037F1B74461300EFC04E /* SQLite-Bridging.m in Sources */, + DC6C03581B74453300EFC04E /* Blob.swift in Sources */, + DC6C03591B74454100EFC04E /* Expression.swift in Sources */, + DC6C035A1B74454100EFC04E /* Query.swift in Sources */, + DC6C035B1B74454100EFC04E /* Schema.swift in Sources */, + DC6C035C1B74454100EFC04E /* Collation.swift in Sources */, + DC6C035D1B74454100EFC04E /* Operators.swift in Sources */, + DC6C035E1B74454100EFC04E /* AggregateFunctions.swift in Sources */, + DC6C035F1B74454100EFC04E /* CoreFunctions.swift in Sources */, + DC6C03601B74454100EFC04E /* CustomFunctions.swift in Sources */, + DC6C03611B74454100EFC04E /* Setter.swift in Sources */, + DC6C03621B74454100EFC04E /* FTS4.swift in Sources */, + DC6C03631B74454100EFC04E /* R*Tree.swift in Sources */, + DC6C03641B74454100EFC04E /* Foundation.swift in Sources */, + DC6C03651B74454100EFC04E /* Helpers.swift in Sources */, + DCE7BB911B74350E0040D364 /* Cipher.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BB7F1B7434F90040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBC91B743A320040D364 /* CipherTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBAB1B7438610040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC6C03661B74454D00EFC04E /* Connection.swift in Sources */, + DC6C03671B74454D00EFC04E /* Statement.swift in Sources */, + DC6C03681B74454D00EFC04E /* Value.swift in Sources */, + DC6C03691B74454D00EFC04E /* Blob.swift in Sources */, + DC6C036A1B74454D00EFC04E /* SQLite-Bridging.m in Sources */, + DC6C036B1B74454D00EFC04E /* Expression.swift in Sources */, + DC6C036C1B74454D00EFC04E /* Query.swift in Sources */, + DC6C036D1B74454D00EFC04E /* Schema.swift in Sources */, + DC6C036E1B74454D00EFC04E /* Collation.swift in Sources */, + DC6C036F1B74454D00EFC04E /* Operators.swift in Sources */, + DC6C03701B74454D00EFC04E /* AggregateFunctions.swift in Sources */, + DC6C03711B74454D00EFC04E /* CoreFunctions.swift in Sources */, + DC6C03721B74454D00EFC04E /* CustomFunctions.swift in Sources */, + DC6C03731B74454D00EFC04E /* Setter.swift in Sources */, + DC6C03741B74454D00EFC04E /* FTS4.swift in Sources */, + DC6C03751B74454D00EFC04E /* R*Tree.swift in Sources */, + DC6C03761B74454D00EFC04E /* Foundation.swift in Sources */, + DC6C03771B74454D00EFC04E /* Helpers.swift in Sources */, + DC6C03541B74451F00EFC04E /* Cipher.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBB51B7438620040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBCA1B743A320040D364 /* CipherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */ = { + DC2B29EF1B26F718001C60EA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DC2B29E11B26F718001C60EA /* SQLite iOS */; + targetProxy = DC2B29EE1B26F718001C60EA /* PBXContainerItemProxy */; + }; + DC7253B81B52ADEC009B38B1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DC7253AB1B52ADEB009B38B1 /* SQLite Mac */; + targetProxy = DC7253B71B52ADEC009B38B1 /* PBXContainerItemProxy */; + }; + DCE7BB861B7434F90040D364 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */; + targetProxy = DCE7BB851B7434F90040D364 /* PBXContainerItemProxy */; + }; + DCE7BBA61B7436D10040D364 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = sqlcipher; - targetProxy = DC2DD6B11ABE438900C2C71A /* PBXContainerItemProxy */; + targetProxy = DCE7BBA51B7436D10040D364 /* PBXContainerItemProxy */; }; - DC37740119C8CBB3004FCF85 /* PBXTargetDependency */ = { + DCE7BBBC1B7438620040D364 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DC3773F219C8CBB3004FCF85 /* SQLite */; - targetProxy = DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */; + target = DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */; + targetProxy = DCE7BBBB1B7438620040D364 /* PBXContainerItemProxy */; }; - DCC6B3AB1A91979100734B78 /* PBXTargetDependency */ = { + DCE7BBD11B743C220040D364 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DCC6B36D1A9191C300734B78 /* SQLiteCipher */; - targetProxy = DCC6B3AA1A91979100734B78 /* PBXContainerItemProxy */; + name = sqlcipher; + targetProxy = DCE7BBD01B743C220040D364 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - DC37740719C8CBB3004FCF85 /* Debug */ = { + DC2B29F41B26F718001C60EA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -579,32 +1015,38 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - DC37740819C8CBB3004FCF85 /* Release */ = { + DC2B29F51B26F718001C60EA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -621,197 +1063,352 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; - DC37740A19C8CBB3004FCF85 /* Debug */ = { + DC2B29F71B26F718001C60EA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DC37740B19C8CBB3004FCF85 /* Release */ = { + DC2B29F81B26F718001C60EA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; name = Release; }; - DC37740D19C8CBB3004FCF85 /* Debug */ = { + DC2B29FA1B26F718001C60EA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Debug; }; - DC37740E19C8CBB3004FCF85 /* Release */ = { + DC2B29FB1B26F718001C60EA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Release; }; - DCC6B37E1A9191C300734B78 /* Debug */ = { + DC7253BD1B52ADEC009B38B1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "SQLITE_HAS_CODEC=1", - ); - INFOPLIST_FILE = SQLite/Info.plist; + FRAMEWORK_VERSION = A; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = SQLite; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DCC6B37F1A9191C300734B78 /* Release */ = { + DC7253BE1B52ADEC009B38B1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; + DC7253BF1B52ADEC009B38B1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + DC7253C01B52ADEC009B38B1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; + DCE7BB8C1B7434F90040D364 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + DCE7BB8D1B7434F90040D364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "SQLITE_HAS_CODEC=1", - ); - INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; }; name = Release; }; - DCC6B3901A9191D100734B78 /* Debug */ = { + DCE7BB8F1B7434F90040D364 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; - PRODUCT_NAME = "SQLiteCipher Tests"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; + CLANG_ENABLE_MODULES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteCipherTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DCC6B3911A9191D100734B78 /* Release */ = { + DCE7BB901B7434F90040D364 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; - PRODUCT_NAME = "SQLiteCipher Tests"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; + CLANG_ENABLE_MODULES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteCipherTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + DCE7BBC21B7438620040D364 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + DCE7BBC31B7438620040D364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; + DCE7BBC51B7438620040D364 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.stephencelis.SQLiteCipher-MacTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + DCE7BBC61B7438620040D364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.stephencelis.SQLiteCipher-MacTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - DC3773ED19C8CBB3004FCF85 /* Build configuration list for PBXProject "SQLite" */ = { + DC2B29DC1B26F718001C60EA /* Build configuration list for PBXProject "SQLite" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC2B29F41B26F718001C60EA /* Debug */, + DC2B29F51B26F718001C60EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC2B29F61B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC2B29F71B26F718001C60EA /* Debug */, + DC2B29F81B26F718001C60EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC2B29F91B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC2B29FA1B26F718001C60EA /* Debug */, + DC2B29FB1B26F718001C60EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC7253C11B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC7253BD1B52ADEC009B38B1 /* Debug */, + DC7253BE1B52ADEC009B38B1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC7253C21B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC37740719C8CBB3004FCF85 /* Debug */, - DC37740819C8CBB3004FCF85 /* Release */, + DC7253BF1B52ADEC009B38B1 /* Debug */, + DC7253C01B52ADEC009B38B1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DC37740919C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite" */ = { + DCE7BB8B1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC37740A19C8CBB3004FCF85 /* Debug */, - DC37740B19C8CBB3004FCF85 /* Release */, + DCE7BB8C1B7434F90040D364 /* Debug */, + DCE7BB8D1B7434F90040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DC37740C19C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite Tests" */ = { + DCE7BB8E1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC37740D19C8CBB3004FCF85 /* Debug */, - DC37740E19C8CBB3004FCF85 /* Release */, + DCE7BB8F1B7434F90040D364 /* Debug */, + DCE7BB901B7434F90040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */ = { + DCE7BBC11B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCC6B37E1A9191C300734B78 /* Debug */, - DCC6B37F1A9191C300734B78 /* Release */, + DCE7BBC21B7438620040D364 /* Debug */, + DCE7BBC31B7438620040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCC6B38F1A9191D100734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher Tests" */ = { + DCE7BBC41B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCC6B3901A9191D100734B78 /* Debug */, - DCC6B3911A9191D100734B78 /* Release */, + DCE7BBC51B7438620040D364 /* Debug */, + DCE7BBC61B7438620040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = DC3773EA19C8CBB3004FCF85 /* Project object */; + rootObject = DC2B29D91B26F718001C60EA /* Project object */; } diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout deleted file mode 100644 index 60ff51e5..00000000 --- a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout +++ /dev/null @@ -1,53 +0,0 @@ - - - - - IDESourceControlProjectFavoriteDictionaryKey - - IDESourceControlProjectIdentifier - E427014D-8915-4E3B-859D-5EB72D455321 - IDESourceControlProjectName - SQLite - IDESourceControlProjectOriginsDictionary - - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - github.com:stephencelis/SQLite.swift.git - 55011AEBD444630C0E9DF47BD2E18FDBBDCC285D - https://github.com/sqlcipher/sqlcipher.git - - IDESourceControlProjectPath - SQLite.xcodeproj - IDESourceControlProjectRelativeInstallPathDictionary - - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - ../.. - 55011AEBD444630C0E9DF47BD2E18FDBBDCC285D - ../../Vendor/sqlcipher - - IDESourceControlProjectURL - github.com:stephencelis/SQLite.swift.git - IDESourceControlProjectVersion - 111 - IDESourceControlProjectWCCIdentifier - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - IDESourceControlProjectWCConfigurations - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - 55011AEBD444630C0E9DF47BD2E18FDBBDCC285D - IDESourceControlWCCName - sqlcipher - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - IDESourceControlWCCName - SQLite - - - - diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint new file mode 100644 index 00000000..9029cbd1 --- /dev/null +++ b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" : 0, + "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7DE16DEF-6846-4DBA-8E4B-370D97AACD60", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" : "SQLite.swift\/Vendor\/sqlcipher\/", + "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" : "SQLite.swift\/" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "SQLite", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SQLite.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:stephencelis\/SQLite.swift.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/sqlcipher\/sqlcipher.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" + } + ] +} \ No newline at end of file diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme similarity index 70% rename from SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme rename to SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index b41d3602..d4751729 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ - - - - + codeCoverageEnabled = "YES"> @@ -56,28 +43,31 @@ + + @@ -85,17 +75,17 @@ diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme similarity index 79% rename from SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme rename to SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme index d464a46e..4c110773 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -1,6 +1,6 @@ @@ -26,15 +26,16 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" buildConfiguration = "Debug"> @@ -42,12 +43,14 @@ + + @@ -79,9 +83,9 @@ diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme new file mode 100644 index 00000000..8ff655e1 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme new file mode 100644 index 00000000..3e5b96b9 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLite/Database.swift b/SQLite/Database.swift deleted file mode 100644 index e59d3166..00000000 --- a/SQLite/Database.swift +++ /dev/null @@ -1,663 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A connection (handle) to SQLite. -public final class Database { - - /// The location of a SQLite database. - public enum Location { - - /// An in-memory database (equivalent to `.URI(":memory:")`). - /// - /// See: - case InMemory - - /// A temporary, file-backed database (equivalent to `.URI("")`). - /// - /// See: - case Temporary - - /// A database located at the given URI filename (or path). - /// - /// See: - /// - /// :param: filename A URI filename - case URI(String) - - } - - internal var handle: COpaquePointer = nil - - /// Whether or not the database was opened in a read-only state. - public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } - - /// Initializes a new connection to a database. - /// - /// :param: location The location of the database. Creates a new database if - /// it doesn’t already exist (unless in read-only mode). - /// - /// Default: `.InMemory`. - /// - /// :param: readonly Whether or not to open the database in a read-only - /// state. - /// - /// Default: `false`. - /// - /// :returns: A new database connection. - public init(_ location: Location = .InMemory, readonly: Bool = false) { - let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try { sqlite3_open_v2(location.description, &self.handle, flags | SQLITE_OPEN_FULLMUTEX, nil) } - } - - /// Initializes a new connection to a database. - /// - /// :param: filename The location of the database. Creates a new database if - /// it doesn’t already exist (unless in read-only mode). - /// - /// :param: readonly Whether or not to open the database in a read-only - /// state. - /// - /// Default: `false`. - /// - /// :returns: A new database connection. - public convenience init(_ filename: String, readonly: Bool = false) { - self.init(.URI(filename), readonly: readonly) - } - - deinit { try { sqlite3_close(self.handle) } } // sqlite3_close_v2 in Yosemite/iOS 8? - - // MARK: - - - /// The last rowid inserted into the database via this connection. - public var lastInsertRowid: Int64? { - let rowid = sqlite3_last_insert_rowid(handle) - return rowid == 0 ? nil : rowid - } - - /// The last number of changes (inserts, updates, or deletes) made to the - /// database via this connection. - public var changes: Int { - return Int(sqlite3_changes(handle)) - } - - /// The total number of changes (inserts, updates, or deletes) made to the - /// database via this connection. - public var totalChanges: Int { return Int(sqlite3_total_changes(handle)) } - - // MARK: - Execute - - /// Executes a batch of SQL statements. - /// - /// :param: SQL A batch of zero or more semicolon-separated SQL statements. - public func execute(SQL: String) { - try { sqlite3_exec(self.handle, SQL, nil, nil, nil) } - } - - // MARK: - Prepare - - /// Prepares a single SQL statement (with optional parameter bindings). - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: Binding?...) -> Statement { - if !bindings.isEmpty { return prepare(statement, bindings) } - return Statement(self, statement) - } - - /// Prepares a single SQL statement and binds parameters to it. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [Binding?]) -> Statement { - return prepare(statement).bind(bindings) - } - - /// Prepares a single SQL statement and binds parameters to it. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement { - return prepare(statement).bind(bindings) - } - - // MARK: - Run - - /// Runs a single SQL statement (with optional parameter bindings). - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement. - public func run(statement: String, _ bindings: Binding?...) -> Statement { - return run(statement, bindings) - } - - /// Prepares, binds, and runs a single SQL statement. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement. - public func run(statement: String, _ bindings: [Binding?]) -> Statement { - return prepare(statement).run(bindings) - } - - /// Prepares, binds, and runs a single SQL statement. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The statement. - public func run(statement: String, _ bindings: [String: Binding?]) -> Statement { - return prepare(statement).run(bindings) - } - - // MARK: - Scalar - - /// Runs a single SQL statement (with optional parameter bindings), - /// returning the first value of the first row. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { - return scalar(statement, bindings) - } - - /// Prepares, binds, and runs a single SQL statement, returning the first - /// value of the first row. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { - return prepare(statement).scalar(bindings) - } - - /// Prepares, binds, and runs a single SQL statement, returning the first - /// value of the first row. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { - return prepare(statement).scalar(bindings) - } - - // MARK: - Transactions - - /// The mode in which a transaction acquires a lock. - public enum TransactionMode: String { - - /// Defers locking the database till the first read/write executes. - case Deferred = "DEFERRED" - - /// Immediately acquires a reserved lock on the database. - case Immediate = "IMMEDIATE" - - /// Immediately acquires an exclusive lock on all databases. - case Exclusive = "EXCLUSIVE" - - } - - /// The result of a transaction. - public enum TransactionResult: String { - - /// Commits a transaction. - case Commit = "COMMIT TRANSACTION" - - /// Rolls a transaction back. - case Rollback = "ROLLBACK TRANSACTION" - - } - - /// Starts a new transaction with the given mode. - /// - /// :param: mode The mode in which a transaction acquires a lock. - /// - /// Default: `.Deferred` - /// - /// :returns: The BEGIN TRANSACTION statement. - public func transaction(_ mode: TransactionMode = .Deferred) -> Statement { - return run("BEGIN \(mode.rawValue) TRANSACTION") - } - - /// Runs a transaction with the given savepoint name (if omitted, it will - /// generate a UUID). - /// - /// :param: mode The mode in which a transaction acquires a lock. - /// - /// Default: `.Deferred` - /// - /// :param: block A closure to run SQL statements within the transaction. - /// Should return a TransactionResult depending on success or - /// failure. - /// - /// :returns: The COMMIT or ROLLBACK statement. - public func transaction(_ mode: TransactionMode = .Deferred, @noescape _ block: (txn: Statement) -> TransactionResult) -> Statement { - return run(block(txn: transaction(mode)).rawValue) - } - - /// Commits the current transaction (or, if a savepoint is open, releases - /// the current savepoint). - /// - /// :param: all Only applicable if a savepoint is open. If true, commits all - /// open savepoints, otherwise releases the current savepoint. - /// - /// Default: `false` - /// - /// :returns: The COMMIT (or RELEASE) statement. - public func commit(all: Bool = false) -> Statement { - if !savepointStack.isEmpty && !all { - return release() - } - savepointStack.removeAll() - return run(TransactionResult.Commit.rawValue) - } - - /// Rolls back the current transaction (or, if a savepoint is open, the - /// current savepoint). - /// - /// :param: all Only applicable if a savepoint is open. If true, rolls back - /// all open savepoints, otherwise rolls back the current - /// savepoint. - /// - /// Default: `false` - /// - /// :returns: The ROLLBACK statement. - public func rollback(all: Bool = false) -> Statement { - if !savepointStack.isEmpty && !all { - return rollback(savepointStack.removeLast()) - } - savepointStack.removeAll() - return run(TransactionResult.Rollback.rawValue) - } - - // MARK: - Savepoints - - /// The result of a savepoint. - public enum SavepointResult { - - /// Releases a savepoint. - case Release - - /// Rolls a savepoint back. - case Rollback - - } - - private var savepointStack = [String]() - - /// Starts a new transaction with the given savepoint name. - /// - /// :param: savepointName A unique identifier for the savepoint. - /// - /// :returns: The SAVEPOINT statement. - public func savepoint(_ savepointName: String? = nil) -> Statement { - let name = savepointName ?? NSUUID().UUIDString - savepointStack.append(name) - return run("SAVEPOINT \(quote(literal: name))") - } - - /// Runs a transaction with the given savepoint name (if omitted, it will - /// generate a UUID). - /// - /// :param: savepointName A unique identifier for the savepoint (optional). - /// - /// :param: block A closure to run SQL statements within the - /// transaction. Should return a SavepointResult - /// depending on success or failure. - /// - /// :returns: The RELEASE or ROLLBACK statement. - public func savepoint(_ savepointName: String? = nil, @noescape _ block: (txn: Statement) -> SavepointResult) -> Statement { - switch block(txn: savepoint(savepointName)) { - case .Release: - return release() - case .Rollback: - return rollback() - } - } - - /// Releases a savepoint with the given savepoint name (or the most - /// recently-opened savepoint). - /// - /// :param: savepointName A unique identifier for the savepoint (optional). - /// - /// :returns: The RELEASE SAVEPOINT statement. - public func release(_ savepointName: String? = nil) -> Statement { - let name = savepointName ?? savepointStack.removeLast() - if let idx = find(savepointStack, name) { savepointStack.removeRange(idx.. Statement { - if let idx = find(savepointStack, savepointName) { savepointStack.removeRange(idx.. Bool)?) { - try { - if let callback = callback { - self.busyHandler = { callback(tries: Int($0)) ? 1 : 0 } - } else { - self.busyHandler = nil - } - return _SQLiteBusyHandler(self.handle, self.busyHandler) - } - } - private var busyHandler: _SQLiteBusyHandlerCallback? - - /// Sets a handler to call when a statement is executed with the compiled - /// SQL. - /// - /// :param: callback This block is invoked when a statement is executed with - /// the compiled SQL as its argument. E.g., pass `println` - /// to act as a logger. - public func trace(callback: ((SQL: String) -> Void)?) { - if let callback = callback { - trace = { callback(SQL: String.fromCString($0)!) } - } else { - trace = nil - } - _SQLiteTrace(handle, trace) - } - private var trace: _SQLiteTraceCallback? - - /// An SQL operation passed to update callbacks. - public enum Operation { - - /// An INSERT operation. - case Insert - - /// An UPDATE operation. - case Update - - /// A DELETE operation. - case Delete - - private static func fromRawValue(rawValue: Int32) -> Operation { - switch rawValue { - case SQLITE_INSERT: - return .Insert - case SQLITE_UPDATE: - return .Update - case SQLITE_DELETE: - return .Delete - default: - fatalError("unhandled operation code: \(rawValue)") - } - } - - } - - /// Registers a callback to be invoked whenever a row is inserted, updated, - /// or deleted in a rowid table. - /// - /// :param: callback A callback invoked with the `Operation` (one - /// of `.Insert`, `.Update`, or `.Delete`), database name, - /// table name, and rowid. - public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { - if let callback = callback { - updateHook = { operation, db, table, rowid in - callback( - operation: .fromRawValue(operation), - db: String.fromCString(db)!, - table: String.fromCString(table)!, - rowid: rowid - ) - } - } else { - updateHook = nil - } - _SQLiteUpdateHook(handle, updateHook) - } - private var updateHook: _SQLiteUpdateHookCallback? - - /// Registers a callback to be invoked whenever a transaction is committed. - /// - /// :param: callback A callback that must return `.Commit` or `.Rollback` to - /// determine whether a transaction should be committed or - /// not. - public func commitHook(callback: (() -> TransactionResult)?) { - if let callback = callback { - commitHook = { callback() == .Commit ? 0 : 1 } - } else { - commitHook = nil - } - _SQLiteCommitHook(handle, commitHook) - } - private var commitHook: _SQLiteCommitHookCallback? - - /// Registers a callback to be invoked whenever a transaction rolls back. - /// - /// :param: callback A callback invoked when a transaction is rolled back. - public func rollbackHook(callback: (() -> Void)?) { - rollbackHook = callback.map { $0 } - _SQLiteRollbackHook(handle, rollbackHook) - } - private var rollbackHook: _SQLiteRollbackHookCallback? - - /// Creates or redefines a custom SQL function. - /// - /// :param: function The name of the function to create or redefine. - /// - /// :param: argc The number of arguments that the function takes. - /// If this parameter is `-1`, then the SQL function - /// may take any number of arguments. - /// - /// Default: `-1` - /// - /// :param: deterministic Whether or not the function is deterministic. If - /// the function always returns the same result for a - /// given input, SQLite can make optimizations. - /// - /// Default: `false` - /// - /// :param: block A block of code to run when the function is - /// called. The block is called with an array of raw - /// SQL values mapped to the function’s parameters and - /// should return a raw SQL value (or nil). - public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { - try { - if self.functions[function] == nil { self.functions[function] = [:] } - self.functions[function]?[argc] = { context, argc, argv in - let arguments: [Binding?] = map(0.. ComparisonResult) { - try { - self.collations[collation] = { lhs, rhs in - return Int32(block(lhs: String.fromCString(lhs)!, rhs: String.fromCString(rhs)!).rawValue) - } - return _SQLiteCreateCollation(self.handle, collation, self.collations[collation]) - } - } - private var collations = [String: _SQLiteCreateCollationCallback]() - - // MARK: - Error Handling - - /// Returns the last error produced on this connection. - public var lastError: String? { - if successCodes.contains(sqlite3_errcode(handle)) { - return nil - } - return String.fromCString(sqlite3_errmsg(handle))! - } - - internal func try(block: () -> Int32) { - perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError!)") } } - } - - // MARK: - Threading - - private let queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) - - internal func perform(block: () -> Void) { dispatch_sync(queue, block) } - -} - -// MARK: - Printable -extension Database: Printable { - - public var description: String { - return String.fromCString(sqlite3_db_filename(handle, nil))! - } - -} - -extension Database.Location: Printable { - - public var description: String { - switch self { - case .InMemory: - return ":memory:" - case .Temporary: - return "" - case .URI(let URI): - return URI - } - } - -} - -internal let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] - -internal func quote(#literal: String) -> String { - return quote(literal, "'") -} - -internal func quote(#identifier: String) -> String { - return quote(identifier, "\"") -} - -private func quote(string: String, mark: Character) -> String { - let escaped = reduce(string, "") { string, character in - string + (character == mark ? "\(mark)\(mark)" : "\(character)") - } - return "\(mark)\(escaped)\(mark)" -} diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift deleted file mode 100644 index 1262f76b..00000000 --- a/SQLite/Expression.swift +++ /dev/null @@ -1,931 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A typed SQL expression that wraps a raw string and bindings. -public struct Expression { - - public let SQL: String - - public let bindings: [Binding?] - - private var ascending: Bool? - - private var original: Expressible? - - /// Builds an SQL expression with a literal string and an optional list of - /// bindings. - /// - /// :param: SQL An SQL string. - /// - /// :param: bindings An optional list of values to be bound to the SQL. - public init(literal SQL: String, _ bindings: [Binding?] = []) { - (self.SQL, self.bindings) = (SQL, bindings) - } - - /// Builds an SQL expression with a quoted identifier. - /// - /// :param: identifier An SQL identifier (*e.g.*, a column name). - public init(_ identifier: String) { - self.init(literal: quote(identifier: identifier)) - } - - /// Builds an SQL expression with the given value. - /// - /// :param: value An encodable SQL value. - public init(value: V?) { - self.init(binding: value?.datatypeValue) - } - - /// Builds an SQL expression with the given value. - /// - /// :param: binding A raw SQL value. - internal init(binding: Binding?) { - self.init(literal: "?", [binding]) - } - - /// Returns an ascending sort version of the expression. - public var asc: Expression { - var expression = self - expression.ascending = true - return expression - } - - /// Returns an descending sort version of the expression. - public var desc: Expression { - var expression = self - expression.ascending = false - return expression - } - - /// Returns an aliased version of the expression using AS. The expression is - /// expanded contextually (e.g., in SELECT clauses). - public func alias(alias: String) -> Expression { - return self.alias(Expression(alias)) - } - - private func alias(alias: Expression) -> Expression { - var expression = Expression(alias) - expression.original = self - return expression - } - - internal static func join(separator: String, _ expressions: [Expressible]) -> Expression { - var (SQL, bindings) = ([String](), [Binding?]()) - for expressible in expressions { - let expression = expressible.expression - SQL.append(expression.SQL) - bindings.extend(expression.bindings) - } - return Expression(literal: Swift.join(separator, SQL), bindings) - } - - internal init(_ expression: Expression) { - self.init(literal: expression.SQL, expression.bindings) - (ascending, original) = (expression.ascending, expression.original) - } - - internal var ordered: Expression { - if let ascending = ascending { - return Expression.join(" ", [self, Expression(literal: ascending ? "ASC" : "DESC")]) - } - return Expression(self) - } - - internal var aliased: Expression { - if let aliased = original?.expression { - return Expression(literal: "(\(aliased.SQL)) AS \(SQL)", aliased.bindings + bindings) - } - return self - } - - internal var unaliased: Expression { - return original.map { Expression($0.expression) } ?? self - } - - internal func reverse() -> Expression { - var expression = self - expression.ascending = expression.ascending.map(!) ?? false - return expression - } - - // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - internal func compile() -> String { - var idx = 0 - return reduce(SQL, "") { SQL, character in - let string = String(character) - return SQL + (string == "?" ? transcode(self.bindings[idx++]) : string) - } - } - -} - -public protocol Expressible { - - var expression: Expression { get } - -} - -extension Blob: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension Bool: Expressible { - - public var expression: Expression { - return Expression(value: self) - } - -} - -extension Double: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension Int: Expressible { - - public var expression: Expression { - // FIXME: rdar://TODO segfaults during archive // return Expression(value: self) - return Expression(binding: datatypeValue) - } - -} - -extension Int64: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension String: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension Expression: Expressible { - - public var expression: Expression { - return Expression(self) - } - -} - -extension Query: Expressible { - - public var expression: Expression { - if tableName.original != nil { - return selectExpression.alias(tableName) - } - return wrap("", selectExpression) - } - -} - -// MARK: - Expressions - -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) } -public func + (lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) } -public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } -public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } - -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } -public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } -public func + (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func + (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } - -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } -public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } -public func - (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } -public func - (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } - -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } -public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } -public func * (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } -public func * (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } - -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } -public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } -public func / (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } -public func / (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } - -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: V) -> Expression { return lhs % Expression(value: rhs) } -public func % (lhs: Expression, rhs: V) -> Expression { return lhs % Expression(value: rhs) } -public func % (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } -public func % (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } - -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: V) -> Expression { return lhs << Expression(value: rhs) } -public func << (lhs: Expression, rhs: V) -> Expression { return lhs << Expression(value: rhs) } -public func << (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } -public func << (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } - -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: V) -> Expression { return lhs >> Expression(value: rhs) } -public func >> (lhs: Expression, rhs: V) -> Expression { return lhs >> Expression(value: rhs) } -public func >> (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } -public func >> (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } - -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: V) -> Expression { return lhs & Expression(value: rhs) } -public func & (lhs: Expression, rhs: V) -> Expression { return lhs & Expression(value: rhs) } -public func & (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } -public func & (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } - -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: V) -> Expression { return lhs | Expression(value: rhs) } -public func | (lhs: Expression, rhs: V) -> Expression { return lhs | Expression(value: rhs) } -public func | (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } -public func | (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } - -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: V) -> Expression { return lhs ^ Expression(value: rhs) } -public func ^ (lhs: Expression, rhs: V) -> Expression { return lhs ^ Expression(value: rhs) } -public func ^ (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } -public func ^ (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } - -public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } - -/// A collating function used to compare to strings. -/// -/// See: -public enum Collation { - - /// Compares string by raw data. - case Binary - - /// Like binary, but folds uppercase ASCII letters into their lowercase - /// equivalents. - case Nocase - - /// Like binary, but strips trailing space. - case Rtrim - - /// A custom collating sequence identified by the given string, registered - /// using `Database.create(collation:…)` - case Custom(String) - -} - -extension Collation: Printable { - - public var description: String { - switch self { - case Binary: - return "BINARY" - case Nocase: - return "NOCASE" - case Rtrim: - return "RTRIM" - case Custom(let collation): - return collation - } - } - -} - -public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.description)) -} -public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.description)) -} - -public func cast(expression: Expression) -> Expression { - return Expression(literal: "CAST (\(expression.SQL) AS \(U.declaredDatatype))", expression.bindings) -} -public func cast(expression: Expression) -> Expression { - return Expression(literal: "CAST (\(expression.SQL) AS \(U.declaredDatatype))", expression.bindings) -} - -// MARK: - Predicates - -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: V) -> Expression { - return lhs == Expression(value: rhs) -} -public func == (lhs: Expression, rhs: V?) -> Expression { - if let rhs = rhs { return lhs == Expression(value: rhs) } - return Expression(literal: "\(lhs.SQL) IS ?", lhs.bindings + [nil]) -} -public func == (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) == rhs -} -public func == (lhs: V?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs) == rhs } - return Expression(literal: "? IS \(rhs.SQL)", [nil] + rhs.bindings) -} - -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: V) -> Expression { - return lhs != Expression(value: rhs) -} -public func != (lhs: Expression, rhs: V?) -> Expression { - if let rhs = rhs { return lhs != Expression(value: rhs) } - return Expression(literal: "\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) -} -public func != (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) != rhs -} -public func != (lhs: V?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs) != rhs } - return Expression(literal: "? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) -} - -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: V) -> Expression { - return lhs > Expression(value: rhs) -} -public func > (lhs: Expression, rhs: V) -> Expression { - return lhs > Expression(value: rhs) -} -public func > (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) > rhs -} -public func > (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) > rhs -} - -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: V) -> Expression { - return lhs >= Expression(value: rhs) -} -public func >= (lhs: Expression, rhs: V) -> Expression { - return lhs >= Expression(value: rhs) -} -public func >= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) >= rhs -} -public func >= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) >= rhs -} - -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: V) -> Expression { - return lhs < Expression(value: rhs) -} -public func < (lhs: Expression, rhs: V) -> Expression { - return lhs < Expression(value: rhs) -} -public func < (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) < rhs -} -public func < (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) < rhs -} - -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: V) -> Expression { - return lhs <= Expression(value: rhs) -} -public func <= (lhs: Expression, rhs: V) -> Expression { - return lhs <= Expression(value: rhs) -} -public func <= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) <= rhs -} -public func <= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) <= rhs -} - -public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } - -public func ~= , V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression(literal: "\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) -} -public func ~= , V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression(lhs ~= Expression(rhs)) -} - -// MARK: Operators - -public func like(pattern: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(binding: pattern)) -} -public func like(pattern: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(binding: pattern)) -} - -public func glob(pattern: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(binding: pattern)) -} -public func glob(pattern: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(binding: pattern)) -} - -public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(binding: string)) -} -public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(binding: string)) -} - -public func regexp(pattern: String, expression: Expression) -> Expression { - return infix("REGEXP", expression, Expression(binding: pattern)) -} -public func regexp(pattern: String, expression: Expression) -> Expression { - return infix("REGEXP", expression, Expression(binding: pattern)) -} - -// MARK: Compound - -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } - -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } - -public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } -public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } - -// MARK: - Core Functions - -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -// FIXME: support Expression..., Expression when Swift supports inner variadic signatures -public func coalesce(expressions: Expression...) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", expressions.map { $0 } as [Expressible])) -} - -public func ifnull(expression: Expression, defaultValue: V) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) -} -public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) -} -public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) -} -public func ?? (expression: Expression, defaultValue: V) -> Expression { - return ifnull(expression, defaultValue) -} -public func ?? (expression: Expression, defaultValue: Expression) -> Expression { - return ifnull(expression, defaultValue) -} -public func ?? (expression: Expression, defaultValue: Expression) -> Expression { - return ifnull(expression, defaultValue) -} - -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func ltrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} -public func ltrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} - -public func random() -> Expression { return Expression(literal: __FUNCTION__) } - -public func replace(expression: Expression, match: String, subtitute: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, match, subtitute])) -} -public func replace(expression: Expression, match: String, subtitute: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, match, subtitute])) -} - -public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func round(expression: Expression, precision: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, precision])) -} -public func round(expression: Expression, precision: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, precision])) -} - -public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func rtrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} -public func rtrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} - -public func substr(expression: Expression, startIndex: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, startIndex])) -} -public func substr(expression: Expression, startIndex: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, startIndex])) -} - -public func substr(expression: Expression, position: Int, length: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, position, length])) -} -public func substr(expression: Expression, position: Int, length: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, position, length])) -} - -public func substr(expression: Expression, subRange: Range) -> Expression { - return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) -} -public func substr(expression: Expression, subRange: Range) -> Expression { - return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) -} - -public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func trim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} -public func trim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} - -public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -// MARK: - Aggregate Functions - -public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func count(#distinct: Expression) -> Expression { return wrapDistinct("count", distinct) } -public func count(#distinct: Expression) -> Expression { return wrapDistinct("count", distinct) } - -public func count(star: Star) -> Expression { return wrap(__FUNCTION__, star(nil, nil)) } - -public func max(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} -public func max(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} - -public func min(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} -public func min(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} - -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } - -public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } -public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } - -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } -public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } - -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } -public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } - -private func wrapDistinct(function: String, expression: Expression) -> Expression { - return wrap(function, Expression.join(" ", [Expression(literal: "DISTINCT"), expression])) -} - -// MARK: - Helper - -public let rowid = Expression("ROWID") - -public typealias Star = (Expression?, Expression?) -> Expression - -public func * (Expression?, Expression?) -> Expression { - return Expression(literal: "*") -} -public func contains(values: C, column: Expression) -> Expression { - let templates = join(", ", [String](count: count(values), repeatedValue: "?")) - return infix("IN", column, Expression(literal: "(\(templates))", map(values) { $0.datatypeValue })) -} -public func contains(values: C, column: Expression) -> Expression { - return contains(values, Expression(column)) -} -public func contains(values: Query, column: Expression) -> Expression { - return infix("IN", column, wrap("", values.selectExpression) as Expression) -} - -// MARK: - Modifying - -/// A pair of expressions used to set values in INSERT and UPDATE statements. -public typealias Setter = (Expressible, Expressible) - -/// Returns a setter to be used with INSERT and UPDATE statements. -/// -/// :param: column The column being set. -/// -/// :param: value The value the column is being set to. -/// -/// :returns: A setter that can be used in a Query’s insert and update -/// functions. -public func set(column: Expression, value: V) -> Setter { - return (column, Expression(value: value)) -} -public func set(column: Expression, value: V?) -> Setter { - return (column, Expression(value: value)) -} -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } - -infix operator <- { associativity left precedence 140 } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: V) -> Setter { return set(column, value) } -public func <- (column: Expression, value: V?) -> Setter { return set(column, value) } - -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: String) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: String) -> Setter { return set(column, column + value) } - -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: V) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: V) -> Setter { return set(column, column + value) } - -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: V) -> Setter { return set(column, column - value) } -public func -= (column: Expression, value: V) -> Setter { return set(column, column - value) } - -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: V) -> Setter { return set(column, column * value) } -public func *= (column: Expression, value: V) -> Setter { return set(column, column * value) } - -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: V) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: V) -> Setter { - return set(column, column / value) -} - -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: V) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: V) -> Setter { return set(column, column % value) } - -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: V) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: V) -> Setter { return set(column, column << value) } - -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: V) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: V) -> Setter { return set(column, column >> value) } - -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: V) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: V) -> Setter { return set(column, column & value) } - -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: V) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: V) -> Setter { return set(column, column | value) } - -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } - -public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } -public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } -public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } -public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } - -// MARK: - Internal - -internal func transcode(literal: Binding?) -> String { - if let literal = literal { - if let literal = literal as? Blob { return literal.description } - if let literal = literal as? String { return quote(literal: literal) } - return "\(literal)" - } - return "NULL" -} - -internal func wrap(function: String, expression: Expression) -> Expression { - return Expression(literal: "\(function)\(surround(expression.SQL))", expression.bindings) -} - -internal func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { - return Expression(literal: surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) -} - -private func surround(expression: String) -> String { return "(\(expression))" } diff --git a/SQLite/FTS.swift b/SQLite/FTS.swift deleted file mode 100644 index 30dfccbe..00000000 --- a/SQLite/FTS.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public func fts4(columns: Expression...) -> Expression { - return fts4(columns) -} - -// TODO: matchinfo, compress, uncompress -public func fts4(columns: [Expression], tokenize tokenizer: Tokenizer? = nil) -> Expression { - var options = [String: String]() - options["tokenize"] = tokenizer?.description - return fts("fts4", columns, options) -} - -private func fts(function: String, columns: [Expression], options: [String: String]) -> Expression { - var definitions: [Expressible] = columns.map { $0.expression } - for (key, value) in options { - definitions.append(Expression(literal: "\(key)=\(value)")) - } - return wrap(function, Expression.join(", ", definitions)) -} - -public enum Tokenizer { - - internal static var moduleName = "SQLite.swift" - - case Simple - - case Porter - - case Custom(String) - -} - -extension Tokenizer: Printable { - - public var description: String { - switch self { - case .Simple: - return "simple" - case .Porter: - return "porter" - case .Custom(let tokenizer): - return "\(quote(identifier: Tokenizer.moduleName)) \(quote(literal: tokenizer))" - } - } - -} - -public func match(string: String, expression: Query) -> Expression { - return infix("MATCH", Expression(expression.tableName), Expression(binding: string)) -} - -extension Database { - - public func register(tokenizer submoduleName: String, next: String -> (String, Range)?) { - try { - _SQLiteRegisterTokenizer(self.handle, Tokenizer.moduleName, submoduleName) { input, offset, length in - let string = String.fromCString(input)! - if var (token, range) = next(string) { - let view = string.utf8 - offset.memory += count(string.substringToIndex(range.startIndex).utf8) - length.memory = Int32(distance(range.startIndex.samePositionIn(view), range.endIndex.samePositionIn(view))) - return token - } - return nil - } - } - } - -} diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift deleted file mode 100644 index ef83b57e..00000000 --- a/SQLite/Functions.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public extension Database { - - // MARK: - Type-Safe Function Creation Shims - - // MARK: 0 Arguments - - /// Creates or redefines a custom SQL function. - /// - /// :param: function The name of the function to create or redefine. - /// - /// :param: deterministic Whether or not the function is deterministic. If - /// the function always returns the same result for a - /// given input, SQLite can make optimizations. - /// - /// Default: `false` - /// - /// :param: block A block of code to run when the function is - /// called. The assigned types must be explicit. - /// - /// :returns: A closure returning an SQL expression to call the function. - public func create(#function: String, deterministic: Bool = false, _ block: () -> Z) -> (() -> Expression) { - return { self.create(function, 0, deterministic) { _ in return block() }([]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: () -> Z?) -> (() -> Expression) { - return { self.create(function, 0, deterministic) { _ in return block() }([]) } - } - - // MARK: 1 Argument - - public func create(#function: String, deterministic: Bool = false, _ block: A -> Z) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block(asValue($0[0])) }([$0]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: A? -> Z) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block($0[0].map(asValue)) }([$0]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: A -> Z?) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block(asValue($0[0])) }([$0]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: A? -> Z?) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block($0[0].map(asValue)) }([$0]) } - } - - // MARK: 2 Arguments - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - // MARK: - - - private func create(function: String, _ argc: Int, _ deterministic: Bool, _ block: [Binding?] -> Z) -> ([Expressible] -> Expression) { - return { Expression(self.create(function, argc, deterministic) { (arguments: [Binding?]) -> Z? in block(arguments) }($0)) } - } - - private func create(function: String, _ argc: Int, _ deterministic: Bool, _ block: [Binding?] -> Z?) -> ([Expressible] -> Expression) { - create(function: function, argc: argc, deterministic: deterministic) { block($0)?.datatypeValue } - return { arguments in wrap(quote(identifier: function), Expression.join(", ", arguments)) } - } - -} - -private func asValue(value: Binding) -> A { - return A.fromDatatypeValue(value as! A.Datatype) as! A -} - -private func asValue(value: Binding?) -> A { - return asValue(value!) -} diff --git a/SQLite/Query.swift b/SQLite/Query.swift deleted file mode 100644 index 8e2f37a2..00000000 --- a/SQLite/Query.swift +++ /dev/null @@ -1,880 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A query object. Used to build SQL statements with a collection of chainable -/// helper functions. -public struct Query { - - internal var database: Database - - internal init(_ database: Database, _ tableName: String) { - (self.database, self.tableName) = (database, Expression(tableName)) - } - - private init(_ database: Database, _ tableName: Expression) { - (self.database, self.tableName) = (database, tableName) - } - - // MARK: - Keywords - - /// Determines the join operator for a query’s JOIN clause. - public enum JoinType: String { - - /// A CROSS JOIN. - case Cross = "CROSS" - - /// An INNER JOIN. - case Inner = "INNER" - - /// A LEFT OUTER JOIN. - case LeftOuter = "LEFT OUTER" - - } - - private var columns: [Expressible]? - private var distinct: Bool = false - internal var tableName: Expression - private var joins: [(type: JoinType, table: Query, condition: Expression)] = [] - private var filter: Expression? - private var group: Expressible? - private var order = [Expressible]() - private var limit: (to: Int, offset: Int?)? = nil - - public func alias(alias: String) -> Query { - var query = self - query.tableName = query.tableName.alias(alias) - return query - } - - /// Sets the SELECT clause on the query. - /// - /// :param: all A list of expressions to select. - /// - /// :returns: A query with the given SELECT clause applied. - public func select(all: Expressible...) -> Query { - return select(all) - } - - /// Sets the SELECT clause on the query. - /// - /// :param: all A list of expressions to select. - /// - /// :returns: A query with the given SELECT clause applied. - public func select(all: [Expressible]) -> Query { - var query = self - (query.distinct, query.columns) = (false, all) - return query - } - - /// Sets the SELECT DISTINCT clause on the query. - /// - /// :param: columns A list of expressions to select. - /// - /// :returns: A query with the given SELECT DISTINCT clause applied. - public func select(distinct columns: Expressible...) -> Query { - return select(distinct: columns) - } - - /// Sets the SELECT DISTINCT clause on the query. - /// - /// :param: columns A list of expressions to select. - /// - /// :returns: A query with the given SELECT DISTINCT clause applied. - public func select(distinct columns: [Expressible]) -> Query { - var query = self - (query.distinct, query.columns) = (true, columns) - return query - } - - /// Sets the SELECT clause on the query. - /// - /// :param: star A literal *. - /// - /// :returns: A query with SELECT * applied. - public func select(star: Star) -> Query { - var query = self - (query.distinct, query.columns) = (false, nil) - return query - } - - /// Sets the SELECT DISTINCT * clause on the query. - /// - /// :param: star A literal *. - /// - /// :returns: A query with SELECT * applied. - public func select(distinct star: Star) -> Query { - return select(distinct: star(nil, nil)) - } - - /// Adds an INNER JOIN clause to the query. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given INNER JOIN clause applied. - public func join(table: Query, on: Expression) -> Query { - return join(.Inner, table, on: on) - } - - /// Adds an INNER JOIN clause to the query. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given INNER JOIN clause applied. - public func join(table: Query, on: Expression) -> Query { - return join(.Inner, table, on: on) - } - - /// Adds a JOIN clause to the query. - /// - /// :param: type The JOIN operator. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given JOIN clause applied. - public func join(type: JoinType, _ table: Query, on: Expression) -> Query { - var query = self - let join = (type: type, table: table, condition: table.filter.map { on && $0 } ?? on) - query.joins.append(join) - return query - } - - /// Adds a JOIN clause to the query. - /// - /// :param: type The JOIN operator. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given JOIN clause applied. - public func join(type: JoinType, _ table: Query, on: Expression) -> Query { - return join(type, table, on: Expression(on)) - } - - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A boolean expression to filter on. - /// - /// :returns: A query with the given WHERE clause applied. - public func filter(condition: Expression) -> Query { - var query = self - query.filter = filter.map { $0 && condition } ?? condition - return query - } - - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A boolean expression to filter on. - /// - /// :returns: A query with the given WHERE clause applied. - public func filter(condition: Expression) -> Query { - return filter(Expression(condition)) - } - - /// Sets a GROUP BY clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: Expressible...) -> Query { - return group(by) - } - - /// Sets a GROUP BY clause (with optional HAVING) on the query. - /// - /// :param: by A column to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: Expressible, having: Expression) -> Query { - return group([by], having: having) - } - - /// Sets a GROUP BY clause (with optional HAVING) on the query. - /// - /// :param: by A column to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: Expressible, having: Expression) -> Query { - return group([by], having: having) - } - - /// Sets a GROUP BY-HAVING clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: [Expressible], having: Expression) -> Query { - return group(by, having: Expression(having)) - } - - private func group(by: [Expressible], having: Expression? = nil) -> Query { - var query = self - var group = Expression.join(" ", [Expression(literal: "GROUP BY"), Expression.join(", ", by)]) - if let having = having { group = Expression.join(" ", [group, Expression(literal: "HAVING"), having]) } - query.group = group - return query - } - - /// Sets an ORDER BY clause on the query. - /// - /// :param: by An ordered list of columns and directions to sort by. - /// - /// :returns: A query with the given ORDER BY clause applied. - public func order(by: Expressible...) -> Query { - return order(by) - } - - /// Sets an ORDER BY clause on the query. - /// - /// :param: by An ordered list of columns and directions to sort by. - /// - /// :returns: A query with the given ORDER BY clause applied. - public func order(by: [Expressible]) -> Query { - var query = self - query.order = by - return query - } - - /// Sets the LIMIT clause (and resets any OFFSET clause) on the query. - /// - /// :param: to The maximum number of rows to return. - /// - /// :returns: A query with the given LIMIT clause applied. - public func limit(to: Int?) -> Query { - return limit(to: to, offset: nil) - } - - /// Sets LIMIT and OFFSET clauses on the query. - /// - /// :param: to The maximum number of rows to return. - /// - /// :param: offset The number of rows to skip. - /// - /// :returns: A query with the given LIMIT and OFFSET clauses applied. - public func limit(to: Int, offset: Int) -> Query { - return limit(to: to, offset: offset) - } - - // prevents limit(nil, offset: 5) - private func limit(#to: Int?, offset: Int? = nil) -> Query { - var query = self - query.limit = to.map { ($0, offset) } - return query - } - - // MARK: - Namespacing - - /// Prefixes a column expression with the query’s table name or alias. - /// - /// :param: column A column expression. - /// - /// :returns: A column expression namespaced with the query’s table name or - /// alias. - public func namespace(column: Expression) -> Expression { - return Expression(.join(".", [tableName, column])) - } - - // FIXME: rdar://18673897 // ... subscript(expression: Expression) -> Expression - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - /// Prefixes a star with the query’s table name or alias. - /// - /// :param: star A literal `*`. - /// - /// :returns: A `*` expression namespaced with the query’s table name or - /// alias. - public subscript(star: Star) -> Expression { - return namespace(star(nil, nil)) - } - - // MARK: - Compiling Statements - - internal var selectStatement: Statement { - let expression = selectExpression - return database.prepare(expression.SQL, expression.bindings) - } - - internal var selectExpression: Expression { - var expressions = [selectClause] - joinClause.map(expressions.append) - whereClause.map(expressions.append) - group.map(expressions.append) - orderClause.map(expressions.append) - limitClause.map(expressions.append) - return Expression.join(" ", expressions) - } - - /// ON CONFLICT resolutions. - public enum OnConflict: String { - - case Replace = "REPLACE" - - case Rollback = "ROLLBACK" - - case Abort = "ABORT" - - case Fail = "FAIL" - - case Ignore = "IGNORE" - - } - - private func insertStatement(or: OnConflict? = nil, _ values: [Setter]) -> Statement { - var insertClause = "INSERT" - if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } - var expressions: [Expressible] = [Expression(literal: "\(insertClause) INTO \(tableName.unaliased.SQL)")] - let (c, v) = (Expression.join(", ", values.map { $0.0 }), Expression.join(", ", values.map { $0.1 })) - expressions.append(Expression(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) - whereClause.map(expressions.append) - let expression = Expression.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - private func updateStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression(literal: "UPDATE \(tableName.unaliased.SQL) SET")] - expressions.append(Expression.join(", ", values.map { Expression.join(" = ", [$0, $1]) })) - whereClause.map(expressions.append) - let expression = Expression.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - private var deleteStatement: Statement { - var expressions: [Expressible] = [Expression(literal: "DELETE FROM \(tableName.unaliased.SQL)")] - whereClause.map(expressions.append) - let expression = Expression.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - // MARK: - - - private var selectClause: Expressible { - var expressions: [Expressible] = [Expression(literal: "SELECT")] - if distinct { expressions.append(Expression(literal: "DISTINCT")) } - expressions.append(Expression.join(", ", (columns ?? [Expression(literal: "*")]).map { $0.expression.aliased })) - expressions.append(Expression(literal: "FROM \(tableName.aliased.SQL)")) - return Expression.join(" ", expressions) - } - - private var joinClause: Expressible? { - if joins.count == 0 { return nil } - return Expression.join(" ", joins.map { type, table, condition in - let join = (table.columns == nil ? table.tableName : table.expression).aliased - return Expression(literal: "\(type.rawValue) JOIN \(join.SQL) ON \(condition.SQL)", join.bindings + condition.bindings) - }) - } - - internal var whereClause: Expressible? { - if let filter = filter { - return Expression(literal: "WHERE \(filter.SQL)", filter.bindings) - } - return nil - } - - private var orderClause: Expressible? { - if order.count == 0 { return nil } - let clause = Expression.join(", ", order.map { $0.expression.ordered }) - return Expression(literal: "ORDER BY \(clause.SQL)", clause.bindings) - } - - private var limitClause: Expressible? { - if let limit = limit { - var clause = Expression(literal: "LIMIT \(limit.to)") - if let offset = limit.offset { - clause = Expression.join(" ", [clause, Expression(literal: "OFFSET \(offset)")]) - } - return clause - } - return nil - } - - // MARK: - Modifying - - /// Runs an INSERT statement against the query. - /// - /// :param: action An optional action to run in case of a conflict. - /// :param: value A value to set. - /// :param: more A list of additional values to set. - /// - /// :returns: The rowid and statement. - public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Insert { - return insert(or: action, [value] + more) - } - - /// Runs an INSERT statement against the query. - /// - /// :param: action An optional action to run in case of a conflict. - /// :param: values An array of values to set. - /// - /// :returns: The rowid and statement. - public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Insert { - let statement = insertStatement(or: action, values).run() - return (statement.failed ? nil : database.lastInsertRowid, statement) - } - - /// Runs an INSERT statement against the query with DEFAULT VALUES. - /// - /// :returns: The rowid and statement. - public func insert() -> Insert { - let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) DEFAULT VALUES") - return (statement.failed ? nil : database.lastInsertRowid, statement) - } - - /// Runs an INSERT statement against the query with the results of another - /// query. - /// - /// :param: query A query to SELECT results from. - /// - /// :returns: The number of updated rows and statement. - public func insert(query: Query) -> Change { - let expression = query.selectExpression - let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings) - return (statement.failed ? nil : database.changes, statement) - } - - /// Runs an UPDATE statement against the query. - /// - /// :param: values A list of values to set. - /// - /// :returns: The number of updated rows and statement. - public func update(values: Setter...) -> Change { - return update(values) - } - - /// Runs an UPDATE statement against the query. - /// - /// :param: values An array of of values to set. - /// - /// :returns: The number of updated rows and statement. - public func update(values: [Setter]) -> Change { - let statement = updateStatement(values).run() - return (statement.failed ? nil : database.changes, statement) - } - - /// Runs a DELETE statement against the query. - /// - /// :returns: The number of deleted rows and statement. - public func delete() -> Change { - let statement = deleteStatement.run() - return (statement.failed ? nil : database.changes, statement) - } - - // MARK: - Aggregate Functions - - /// Runs `count()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The number of rows matching the given column. - public func count(column: Expression) -> Int { - return calculate(_count(column))! - } - - /// Runs `count()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The number of rows matching the given column. - public func count(distinct column: Expression) -> Int { - return calculate(_count(distinct: column))! - } - - /// Runs `count()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The number of rows matching the given column. - public func count(distinct column: Expression) -> Int { - return calculate(_count(distinct: column))! - } - - /// Runs `max()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The largest value of the given column. - public func max(column: Expression) -> V? { - return calculate(_max(column)) - } - public func max(column: Expression) -> V? { - return calculate(_max(column)) - } - - /// Runs `min()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The smallest value of the given column. - public func min(column: Expression) -> V? { - return calculate(_min(column)) - } - public func min(column: Expression) -> V? { - return calculate(_min(column)) - } - - /// Runs `avg()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The average value of the given column. - public func average(column: Expression) -> Double? { - return calculate(_average(column)) - } - public func average(column: Expression) -> Double? { - return calculate(_average(column)) - } - - /// Runs `avg()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The average value of the given column. - public func average(distinct column: Expression) -> Double? { - return calculate(_average(distinct: column)) - } - public func average(distinct column: Expression) -> Double? { - return calculate(_average(distinct: column)) - } - - /// Runs `sum()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The sum of the given column’s values. - public func sum(column: Expression) -> V? { - return calculate(_sum(column)) - } - public func sum(column: Expression) -> V? { - return calculate(_sum(column)) - } - - /// Runs `sum()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The sum of the given column’s values. - public func sum(distinct column: Expression) -> V? { - return calculate(_sum(distinct: column)) - } - public func sum(distinct column: Expression) -> V? { - return calculate(_sum(distinct: column)) - } - - /// Runs `total()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The total of the given column’s values. - public func total(column: Expression) -> Double { - return calculate(_total(column))! - } - public func total(column: Expression) -> Double { - return calculate(_total(column))! - } - - /// Runs `total()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The total of the given column’s values. - public func total(distinct column: Expression) -> Double { - return calculate(_total(distinct: column))! - } - public func total(distinct column: Expression) -> Double { - return calculate(_total(distinct: column))! - } - - private func calculate(expression: Expression) -> V? { - return (select(expression).selectStatement.scalar() as? V.Datatype).map(V.fromDatatypeValue) as? V - } - private func calculate(expression: Expression) -> V? { - return (select(expression).selectStatement.scalar() as? V.Datatype).map(V.fromDatatypeValue) as? V - } - - // MARK: - Array - - /// Runs `count(*)` against the query and returns the number of rows. - public var count: Int { return calculate(_count(*))! } - - /// Returns `true` iff the query has no rows. - public var isEmpty: Bool { return first == nil } - - /// The first row (or `nil` if the query returns no rows). - public var first: Row? { - var generator = limit(to: 1, offset: limit?.offset).generate() - return generator.next() - } - - /// The last row (or `nil` if the query returns no rows). - public var last: Row? { - return reverse().first - } - - /// A query in reverse order. - public func reverse() -> Query { - return order(order.isEmpty ? [rowid.desc] : order.map { $0.expression.reverse() }) - } - -} - -/// A row object. Returned by iterating over a Query. Provides typed subscript -/// access to a row’s values. -public struct Row { - - private let columnNames: [String: Int] - private let values: [Binding?] - - private init(_ columnNames: [String: Int], _ values: [Binding?]) { - (self.columnNames, self.values) = (columnNames, values) - } - - /// Returns a row’s value for the given column. - /// - /// :param: column An expression representing a column selected in a Query. - /// - /// returns The value for the given column. - public func get(column: Expression) -> V { - return get(Expression(column))! - } - public func get(column: Expression) -> V? { - func valueAtIndex(idx: Int) -> V? { - if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } - return nil - } - - if let idx = columnNames[column.SQL] { return valueAtIndex(idx) } - - let similar = filter(columnNames.keys) { $0.hasSuffix(".\(column.SQL)") } - if similar.count == 1 { return valueAtIndex(columnNames[similar[0]]!) } - - if similar.count > 1 { - fatalError("ambiguous column \(quote(literal: column.SQL)) (please disambiguate: \(similar))") - } - fatalError("no such column \(quote(literal: column.SQL)) in columns: \(sorted(columnNames.keys))") - } - - // FIXME: rdar://18673897 // ... subscript(expression: Expression) -> Expression - - public subscript(column: Expression) -> Blob { return get(column) } - public subscript(column: Expression) -> Blob? { return get(column) } - - public subscript(column: Expression) -> Bool { return get(column) } - public subscript(column: Expression) -> Bool? { return get(column) } - - public subscript(column: Expression) -> Double { return get(column) } - public subscript(column: Expression) -> Double? { return get(column) } - - public subscript(column: Expression) -> Int { return get(column) } - public subscript(column: Expression) -> Int? { return get(column) } - - public subscript(column: Expression) -> Int64 { return get(column) } - public subscript(column: Expression) -> Int64? { return get(column) } - - public subscript(column: Expression) -> String { return get(column) } - public subscript(column: Expression) -> String? { return get(column) } - -} - -// MARK: - SequenceType -extension Query: SequenceType { - - public func generate() -> QueryGenerator { return QueryGenerator(self) } - -} - -// MARK: - GeneratorType -public struct QueryGenerator: GeneratorType { - - private let query: Query - private let statement: Statement - - private lazy var columnNames: [String: Int] = { - var (columnNames, idx) = ([String: Int](), 0) - column: for each in self.query.columns ?? [Expression(literal: "*")] { - let pair = split(each.expression.SQL) { $0 == "." } - let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) - - func expandGlob(namespace: Bool) -> Query -> Void { - return { table in - var query = Query(table.database, table.tableName.unaliased) - if let columns = table.columns { query.columns = columns } - var names = query.selectStatement.columnNames.map { quote(identifier: $0) } - if namespace { names = names.map { "\(table.tableName.SQL).\($0)" } } - for name in names { columnNames[name] = idx++ } - } - } - - if column == "*" { - let tables = [self.query.select(*)] + self.query.joins.map { $0.table } - if let tableName = tableName { - for table in tables { - if table.tableName.SQL == tableName { - expandGlob(true)(table) - continue column - } - } - assertionFailure("no such table: \(tableName)") - } - tables.map(expandGlob(self.query.joins.count > 0)) - continue - } - - columnNames[each.expression.SQL] = idx++ - } - return columnNames - }() - - private init(_ query: Query) { - (self.query, self.statement) = (query, query.selectStatement) - } - - public mutating func next() -> Row? { - return statement.next().map { Row(self.columnNames, $0) } - } - -} - -// MARK: - Printable -extension Query: Printable { - - public var description: String { - return tableName.SQL - } - -} - -/// The result of an INSERT executed by a query. -/// -/// :param: rowid The insert rowid of the result (or `nil` on failure). -/// -/// :param: statement The associated statement. -public typealias Insert = (rowid: Int64?, statement: Statement) - -/// The result of an bulk INSERT, UPDATE or DELETE executed by a query. -/// -/// :param: changes The number of rows affected (or `nil` on failure). -/// -/// :param: statement The associated statement. -public typealias Change = (changes: Int?, statement: Statement) - -/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func && (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { - return lhs && rhs().statement -} - -/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func || (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { - return lhs || rhs().statement -} - -/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func && (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { - return lhs && rhs().statement -} - -/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func || (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { - return lhs || rhs().statement -} - -extension Database { - - public subscript(tableName: String) -> Query { - return Query(self, tableName) - } - -} - -// Private shims provide frameworkless support by avoiding the SQLite module namespace. - -private func _count(expression: Expression) -> Expression { return count(expression) } - -private func _count(#distinct: Expression) -> Expression { return count(distinct: distinct) } -private func _count(#distinct: Expression) -> Expression { return count(distinct: distinct) } - -private func _count(star: Star) -> Expression { return count(star) } - -private func _max(expression: Expression) -> Expression { - return max(expression) -} -private func _max(expression: Expression) -> Expression { - return max(expression) -} - -private func _min(expression: Expression) -> Expression { - return min(expression) -} -private func _min(expression: Expression) -> Expression { - return min(expression) -} - -private func _average(expression: Expression) -> Expression { return average(expression) } -private func _average(expression: Expression) -> Expression { return average(expression) } - -private func _average(#distinct: Expression) -> Expression { return average(distinct: distinct) } -private func _average(#distinct: Expression) -> Expression { return average(distinct: distinct) } - -private func _sum(expression: Expression) -> Expression { return sum(expression) } -private func _sum(expression: Expression) -> Expression { return sum(expression) } - -private func _sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } -private func _sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } - -private func _total(expression: Expression) -> Expression { return total(expression) } -private func _total(expression: Expression) -> Expression { return total(expression) } - -private func _total(#distinct: Expression) -> Expression { return total(distinct: distinct) } -private func _total(#distinct: Expression) -> Expression { return total(distinct: distinct) } diff --git a/SQLite/SQLite.xcconfig b/SQLite/SQLite.xcconfig deleted file mode 100644 index d021a766..00000000 --- a/SQLite/SQLite.xcconfig +++ /dev/null @@ -1,28 +0,0 @@ -APPLICATION_EXTENSION_API_ONLY = YES -SWIFT_WHOLE_MODULE_OPTIMIZATION = YES - -MODULEMAP_FILE = $(SRCROOT)/SQLite/module.modulemap - -// Universal Framework - -SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx - -VALID_ARCHS[sdk=iphone*] = arm64 armv7 armv7s -VALID_ARCHS[sdk=macosx*] = i386 x86_64 - -LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks -LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks - -FRAMEWORK_SEARCH_PATHS[sdk=iphone*] = $(inherited) $(SDKROOT)/Developer/Library/Frameworks -FRAMEWORK_SEARCH_PATHS[sdk=macosx*] = $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) - -// Platform-specific - -// iOS -CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer -IPHONEOS_DEPLOYMENT_TARGET[sdk=iphone*] = 8.0 -TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 - -// OS X -FRAMEWORK_VERSION[sdk=macosx*] = A -COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES \ No newline at end of file diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift deleted file mode 100644 index a445e676..00000000 --- a/SQLite/Schema.swift +++ /dev/null @@ -1,488 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public extension Database { - - public func create( - #table: Query, - temporary: Bool = false, - ifNotExists: Bool = false, - @noescape _ block: SchemaBuilder -> Void - ) -> Statement { - var builder = SchemaBuilder(table) - block(builder) - let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) - let columns = Expression.join(", ", builder.columns).compile() - return run("\(create) (\(columns))") - } - - public func create(#table: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { - let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) - let expression = from.selectExpression - return run("\(create) AS \(expression.SQL)", expression.bindings) - } - - public func create(#vtable: Query, ifNotExists: Bool = false, using: Expression) -> Statement { - let create = createSQL("VIRTUAL TABLE", false, false, ifNotExists, vtable.tableName.unaliased.SQL) - return run("\(create) USING \(using.SQL)") - } - - public func rename(table tableName: String, to table: Query) -> Statement { - return run("ALTER TABLE \(quote(identifier: tableName)) RENAME TO \(table.tableName.unaliased.SQL)") - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V - ) -> Statement { - return alter(table, define(column, nil, false, false, check, Expression(value: defaultValue), nil)) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V? = nil - ) -> Statement { - let value = defaultValue.map { Expression(value: $0) } - return alter(table, define(Expression(column), nil, true, false, check, value, nil)) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - references: Expression - ) -> Statement { - return alter(table, define(Expression(column), nil, true, false, check, nil, [ - Expression(literal: "REFERENCES"), namespace(references) - ])) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V, - collate: Collation - ) -> Statement { - return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ - Expression(literal: "COLLATE"), Expression(collate.description) - ])) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V? = nil, - collate: Collation - ) -> Statement { - let value = defaultValue.map { Expression(value: $0) } - return alter(table, define(Expression(column), nil, true, false, check, value, [ - Expression(literal: "COLLATE"), Expression(collate.description) - ])) - } - - private func alter(table: Query, _ definition: Expressible) -> Statement { - return run("ALTER TABLE \(table.tableName.unaliased.SQL) ADD COLUMN \(definition.expression.compile())") - } - - public func drop(#table: Query, ifExists: Bool = false) -> Statement { - return run(dropSQL("TABLE", ifExists, table.tableName.unaliased.SQL)) - } - - public func create( - index table: Query, - unique: Bool = false, - ifNotExists: Bool = false, - on columns: Expressible... - ) -> Statement { - let tableName = table.tableName.unaliased.SQL - let create = createSQL("INDEX", false, unique, ifNotExists, indexName(tableName, on: columns)) - let joined = Expression.join(", ", columns.map { $0.expression.ordered }) - return run("\(create) ON \(tableName) (\(joined.compile()))") - } - - public func drop(index table: Query, ifExists: Bool = false, on columns: Expressible...) -> Statement { - return run(dropSQL("INDEX", ifExists, indexName(table.tableName.unaliased.SQL, on: columns))) - } - - private func indexName(table: String, on columns: [Expressible]) -> String { - let string = join(" ", ["index", table, "on"] + columns.map { $0.expression.SQL }) - return quote(identifier: reduce(string, "") { underscored, character in - if character == "\"" { return underscored } - if "A"..."Z" ~= character || "a"..."z" ~= character { return underscored + String(character) } - return underscored + "_" - }) - } - - public func create(#view: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { - let create = createSQL("VIEW", temporary, false, ifNotExists, view.tableName.unaliased.SQL) - let expression = from.selectExpression - return run("\(create) AS \(expression.SQL)", expression.bindings) - } - - public func drop(#view: Query, ifExists: Bool = false) -> Statement { - return run(dropSQL("VIEW", ifExists, view.tableName.unaliased.SQL)) - } - -} - -public final class SchemaBuilder { - - let table: Query - var columns = [Expressible]() - - private init(_ table: Query) { - self.table = table - } - - // MARK: - Column Constraints - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression? - ) { - column(name, nil, false, unique, check, value.map { wrap("", $0) }) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: V? = nil - ) { - column(name, nil, false, unique, check, value.map { Expression(value: $0) }) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression? - ) { - column(Expression(name), nil, true, unique, check, value.map { wrap("", $0) }) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: V? = nil - ) { - column(Expression(name), nil, true, unique, check, value.map { Expression(value: $0) }) - } - - public func column( - name: Expression, - primaryKey: Bool, - check: Expression? = nil, - defaultValue value: Expression? = nil - ) { - column(name, primaryKey ? .Default : nil, false, false, check, value.map { wrap("", $0) }, nil) - } - - // MARK: - INTEGER Columns - - // MARK: PRIMARY KEY - - public enum PrimaryKey { - - case Default - - case Autoincrement - - } - - public func column( - name: Expression, - primaryKey: PrimaryKey?, - check: Expression? = nil - ) { - column(name, primaryKey, false, false, check, nil, nil) - } - - // MARK: REFERENCES - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Expression - ) { - assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression(literal: "REFERENCES"), namespace(references)] - column(name, nil, false, unique, check, nil, expressions) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Query - ) { - return column( - name, - unique: unique, - check: check, - references: Expression(references.tableName) - ) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Expression - ) { - assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression(literal: "REFERENCES"), namespace(references)] - column(Expression(name), nil, true, unique, check, nil, expressions) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Query - ) { - return column( - name, - unique: unique, - check: check, - references: Expression(references.tableName) - ) - } - - // MARK: TEXT Columns (COLLATE) - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression?, - collate: Collation - ) { - let expressions: [Expressible] = [Expression(literal: "COLLATE \(collate)")] - column(name, nil, false, unique, check, value, expressions) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: V? = nil, - collate: Collation - ) { - column(name, unique: unique, check: check, defaultValue: value.map { Expression(value: $0) }, collate: collate) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression?, - collate: Collation - ) { - column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue: V? = nil, - collate: Collation - ) { - let value = defaultValue.map { Expression(value: $0) } - column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) - } - - // MARK: - - - private func column( - name: Expression, - _ primaryKey: PrimaryKey?, - _ null: Bool, - _ unique: Bool, - _ check: Expression?, - _ defaultValue: Expression?, - _ expressions: [Expressible]? = nil - ) { - columns.append(define(name, primaryKey, null, unique, check, defaultValue, expressions)) - } - - // MARK: - Table Constraints - - public func primaryKey(column: Expression) { - primaryKey([column]) - } - - public func primaryKey(columnA: Expression, _ B: Expression) { - primaryKey([columnA, B]) - } - - public func primaryKey(columnA: Expression, _ B: Expression, _ C: Expression) { - primaryKey([columnA, B, C]) - } - - private func primaryKey(composite: [Expressible]) { - let primaryKey = Expression.join(", ", composite) - columns.append(Expression(literal: "PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) - } - - public func unique(column: Expressible...) { - let unique = Expression.join(", ", column) - columns.append(Expression(literal: "UNIQUE(\(unique.SQL))", unique.bindings)) - } - - public func check(condition: Expression) { - columns.append(Expression(literal: "CHECK (\(condition.SQL))", condition.bindings)) - } - - public enum Dependency: String { - - case NoAction = "NO ACTION" - - case Restrict = "RESTRICT" - - case SetNull = "SET NULL" - - case SetDefault = "SET DEFAULT" - - case Cascade = "CASCADE" - - } - - public func foreignKey( - column: Expression, - references: Expression, - update: Dependency? = nil, - delete: Dependency? = nil - ) { - assertForeignKeysEnabled() - var parts: [Expressible] = [Expression(literal: "FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] - parts.append(namespace(references)) - if let update = update { parts.append(Expression(literal: "ON UPDATE \(update.rawValue)")) } - if let delete = delete { parts.append(Expression(literal: "ON DELETE \(delete.rawValue)")) } - columns.append(Expression.join(" ", parts)) - } - - public func foreignKey( - column: Expression, - references: Expression, - update: Dependency? = nil, - delete: Dependency? = nil - ) { - assertForeignKeysEnabled() - foreignKey(Expression(column), references: references, update: update, delete: delete) - } - - public func foreignKey( - columns: (Expression, Expression), - references: (Expression, Expression), - update: Dependency? = nil, - delete: Dependency? = nil - ) { - let compositeA = Expression(Expression.join(", ", [columns.0, columns.1])) - let compositeB = Expression(Expression.join(", ", [references.0, references.1])) - foreignKey(compositeA, references: compositeB, update: update, delete: delete) - } - - public func foreignKey( - columns: (Expression, Expression, Expression), - references: (Expression, Expression, Expression), - update: Dependency? = nil, - delete: Dependency? = nil - ) { - let compositeA = Expression(Expression.join(", ", [columns.0, columns.1, columns.2])) - let compositeB = Expression(Expression.join(", ", [references.0, references.1, references.2])) - foreignKey(compositeA, references: compositeB, update: update, delete: delete) - } - - private func assertForeignKeysEnabled() { - assert(table.database.foreignKeys, "foreign key constraints are disabled (run `db.foreignKeys = true`)") - } - -} - -private func namespace(column: Expressible) -> Expressible { - let expression = column.expression - if !contains(expression.SQL, ".") { return expression } - let reference = reduce(expression.SQL, "") { SQL, character in - let string = String(character) - return SQL + (string == "." ? "(" : string) - } - return Expression(literal: "\(reference))", expression.bindings) -} - -private func define( - column: Expression, - primaryKey: SchemaBuilder.PrimaryKey?, - null: Bool, - unique: Bool, - check: Expression?, - defaultValue: Expression?, - expressions: [Expressible]? -) -> Expressible { - var parts: [Expressible] = [Expression(column), Expression(literal: V.declaredDatatype)] - if let primaryKey = primaryKey { - parts.append(Expression(literal: "PRIMARY KEY")) - if primaryKey == .Autoincrement { parts.append(Expression(literal: "AUTOINCREMENT")) } - } - if !null { parts.append(Expression(literal: "NOT NULL")) } - if unique { parts.append(Expression(literal: "UNIQUE")) } - if let check = check { parts.append(Expression(literal: "CHECK (\(check.SQL))", check.bindings)) } - if let value = defaultValue { parts.append(Expression(literal: "DEFAULT \(value.SQL)", value.bindings)) } - if let expressions = expressions { parts += expressions } - return Expression.join(" ", parts) -} - -private func createSQL( - type: String, - temporary: Bool, - unique: Bool, - ifNotExists: Bool, - name: String -) -> String { - var parts: [String] = ["CREATE"] - if temporary { parts.append("TEMPORARY") } - if unique { parts.append("UNIQUE") } - parts.append(type) - if ifNotExists { parts.append("IF NOT EXISTS") } - parts.append(name) - return Swift.join(" ", parts) -} - -private func dropSQL(type: String, ifExists: Bool, name: String) -> String { - var parts: [String] = ["DROP \(type)"] - if ifExists { parts.append("IF EXISTS") } - parts.append(name) - return Swift.join(" ", parts) -} diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift deleted file mode 100644 index a819a43d..00000000 --- a/SQLite/Statement.swift +++ /dev/null @@ -1,313 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -internal let SQLITE_STATIC = sqlite3_destructor_type(COpaquePointer(bitPattern: 0)) -internal let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern: -1)) - -/// A single SQL statement. -public final class Statement { - - private var handle: COpaquePointer = nil - - private let database: Database - - /// A cursor pointing to the current row. - public lazy var row: Cursor = Cursor(self) - - internal init(_ database: Database, _ SQL: String) { - self.database = database - database.try { sqlite3_prepare_v2(database.handle, SQL, -1, &self.handle, nil) } - } - - deinit { sqlite3_finalize(handle) } - - public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) - - public lazy var columnNames: [String] = (0.. Statement { - return bind(values) - } - - /// Binds a list of parameters to a statement. - /// - /// :param: values A list of parameters to bind to the statement. - /// - /// :returns: The statement object (useful for chaining). - public func bind(values: [Binding?]) -> Statement { - if values.isEmpty { return self } - reset() - assert(values.count == Int(sqlite3_bind_parameter_count(handle)), "\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed") - for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) } - return self - } - - /// Binds a dictionary of named parameters to a statement. - /// - /// :param: values A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The statement object (useful for chaining). - public func bind(values: [String: Binding?]) -> Statement { - reset() - for (name, value) in values { - let idx = sqlite3_bind_parameter_index(handle, name) - assert(idx > 0, "parameter not found: \(name)") - bind(value, atIndex: Int(idx)) - } - return self - } - - private func bind(value: Binding?, atIndex idx: Int) { - if value == nil { - try { sqlite3_bind_null(self.handle, Int32(idx)) } - } else if let value = value as? Blob { - try { sqlite3_bind_blob(self.handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) } - } else if let value = value as? Double { - try { sqlite3_bind_double(self.handle, Int32(idx), value) } - } else if let value = value as? Int64 { - try { sqlite3_bind_int64(self.handle, Int32(idx), value) } - } else if let value = value as? String { - try { sqlite3_bind_text(self.handle, Int32(idx), value, -1, SQLITE_TRANSIENT) } - } else if let value = value as? Bool { - bind(value.datatypeValue, atIndex: idx) - } else if let value = value as? Int { - bind(value.datatypeValue, atIndex: idx) - } else if let value = value { - fatalError("tried to bind unexpected value \(value)") - } - } - - // MARK: - Run - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement object (useful for chaining). - public func run(bindings: Binding?...) -> Statement { - if !bindings.isEmpty { return run(bindings) } - reset(clearBindings: false) - while step() {} - return self - } - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement object (useful for chaining). - public func run(bindings: [Binding?]) -> Statement { - return bind(bindings).run() - } - - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The statement object (useful for chaining). - public func run(bindings: [String: Binding?]) -> Statement { - return bind(bindings).run() - } - - // MARK: - Scalar - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(bindings: Binding?...) -> Binding? { - if !bindings.isEmpty { return scalar(bindings) } - reset(clearBindings: false) - step() - return row[0] - } - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(bindings: [Binding?]) -> Binding? { - return bind(bindings).scalar() - } - - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(bindings: [String: Binding?]) -> Binding? { - return bind(bindings).scalar() - } - - // MARK: - - - public func step() -> Bool { - try { sqlite3_step(self.handle) } - return status == SQLITE_ROW - } - - private func reset(clearBindings: Bool = true) { - (status, reason) = (SQLITE_OK, nil) - sqlite3_reset(handle) - if (clearBindings) { sqlite3_clear_bindings(handle) } - } - - // MARK: - Error Handling - - /// :returns: Whether or not a statement has produced an error. - public var failed: Bool { - return reason != nil - } - - /// :returns: The reason for an error. - public var reason: String? - - private var status: Int32 = SQLITE_OK - - private func try(block: () -> Int32) { - if failed { return } - database.perform { - self.status = block() - if let error = self.database.lastError { - self.reason = error - assert(self.status == SQLITE_CONSTRAINT || self.status == SQLITE_INTERRUPT, "\(error)") - } - } - } - -} - -// MARK: - SequenceType -extension Statement: SequenceType { - - public func generate() -> Statement { - reset(clearBindings: false) - return self - } - -} - -// MARK: - GeneratorType -extension Statement: GeneratorType { - - /// :returns: The next row from the result set (or nil). - public func next() -> [Binding?]? { - return step() ? Array(row) : nil - } - -} - -// MARK: - Printable -extension Statement: Printable { - - public var description: String { - return String.fromCString(sqlite3_sql(handle))! - } - -} - -/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func && (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { - if lhs.status == SQLITE_OK { lhs.run() } - return lhs.failed ? lhs : rhs() -} - -/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func || (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { - if lhs.status == SQLITE_OK { lhs.run() } - return lhs.failed ? rhs() : lhs -} - -/// Cursors provide direct access to a statement’s current row. -public struct Cursor { - - private let handle: COpaquePointer - - private let columnCount: Int - - private init(_ statement: Statement) { - handle = statement.handle - columnCount = statement.columnCount - } - - public subscript(idx: Int) -> Blob { - let bytes = sqlite3_column_blob(handle, Int32(idx)) - let length = sqlite3_column_bytes(handle, Int32(idx)) - return Blob(bytes: bytes, length: Int(length)) - } - - public subscript(idx: Int) -> Double { - return sqlite3_column_double(handle, Int32(idx)) - } - - public subscript(idx: Int) -> Int64 { - return sqlite3_column_int64(handle, Int32(idx)) - } - - public subscript(idx: Int) -> String { - return String.fromCString(UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) ?? "" - } - - public subscript(idx: Int) -> Bool { - return Bool.fromDatatypeValue(self[idx]) - } - - public subscript(idx: Int) -> Int { - return Int.fromDatatypeValue(self[idx]) - } - -} - -// MARK: - SequenceType -extension Cursor: SequenceType { - - public subscript(idx: Int) -> Binding? { - switch sqlite3_column_type(handle, Int32(idx)) { - case SQLITE_BLOB: - return self[idx] as Blob - case SQLITE_FLOAT: - return self[idx] as Double - case SQLITE_INTEGER: - return self[idx] as Int64 - case SQLITE_NULL: - return nil - case SQLITE_TEXT: - return self[idx] as String - case let type: - fatalError("unsupported column type: \(type)") - } - } - - public func generate() -> GeneratorOf { - var idx = 0 - return GeneratorOf { - idx >= self.columnCount ? Optional.None : self[idx++] - } - } - -} diff --git a/SQLiteCipher Tests/CipherTests.swift b/SQLiteCipher Tests/CipherTests.swift deleted file mode 100644 index d96d20af..00000000 --- a/SQLiteCipher Tests/CipherTests.swift +++ /dev/null @@ -1,23 +0,0 @@ -import XCTest -import SQLite - -class CipherTests: SQLiteTestCase { - - override func setUp() { - db.key("hello") - createUsersTable() - insertUser("alice") - - super.setUp() - } - - func test_key() { - XCTAssertEqual(1, users.count) - } - - func test_rekey() { - db.rekey("world") - XCTAssertEqual(1, users.count) - } - -} \ No newline at end of file diff --git a/SQLiteCipher.swift.podspec b/SQLiteCipher.swift.podspec new file mode 100644 index 00000000..33279807 --- /dev/null +++ b/SQLiteCipher.swift.podspec @@ -0,0 +1,18 @@ +require_relative 'Supporting Files/podspec.rb' + +Pod::Spec.new do |spec| + spec.name = 'SQLiteCipher.swift' + spec.version = '1.0.0.pre' + spec.summary = 'The SQLCipher flavor of SQLite.swift.' + + spec.description = <<-DESC + SQLiteCipher.swift is SQLite.swift built on top of SQLCipher. + DESC + + apply_shared_config spec, 'SQLiteCipher' + + spec.dependency 'SQLCipher' + spec.xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' + } +end diff --git a/SQLiteCipher/Cipher.swift b/Source/Cipher/Cipher.swift similarity index 80% rename from SQLiteCipher/Cipher.swift rename to Source/Cipher/Cipher.swift index 3fd2f60c..48778b68 100644 --- a/SQLiteCipher/Cipher.swift +++ b/Source/Cipher/Cipher.swift @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,14 +22,14 @@ // THE SOFTWARE. // -extension Database { +extension Connection { - public func key(key: String) { - try { sqlite3_key(self.handle, key, Int32(count(key.utf8))) } + public func key(key: String) throws { + try check(sqlite3_key(handle, key, Int32(key.utf8.count))) } - public func rekey(key: String) { - try { sqlite3_rekey(self.handle, key, Int32(count(key.utf8))) } + public func rekey(key: String) throws { + try check(sqlite3_rekey(handle, key, Int32(key.utf8.count))) } } diff --git a/Source/Core/Blob.swift b/Source/Core/Blob.swift new file mode 100644 index 00000000..1b30ffa1 --- /dev/null +++ b/Source/Core/Blob.swift @@ -0,0 +1,61 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct Blob { + + public let bytes: [UInt8] + + public init(bytes: [UInt8]) { + self.bytes = bytes + } + + public init(bytes: UnsafePointer, length: Int) { + self.init(bytes: [UInt8](UnsafeBufferPointer( + start: UnsafePointer(bytes), count: length + ))) + } + + public func toHex() -> String { + return bytes.map { + ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) + }.joinWithSeparator("") + } + +} + +extension Blob : CustomStringConvertible { + + public var description: String { + return "x'\(toHex())'" + } + +} + +extension Blob : Equatable { + +} + +public func ==(lhs: Blob, rhs: Blob) -> Bool { + return lhs.bytes == rhs.bytes +} diff --git a/Source/Core/Connection.swift b/Source/Core/Connection.swift new file mode 100644 index 00000000..1f50e984 --- /dev/null +++ b/Source/Core/Connection.swift @@ -0,0 +1,639 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Dispatch + +/// A connection to SQLite. +public final class Connection { + + /// The location of a SQLite database. + public enum Location { + + /// An in-memory database (equivalent to `.URI(":memory:")`). + /// + /// See: + case InMemory + + /// A temporary, file-backed database (equivalent to `.URI("")`). + /// + /// See: + case Temporary + + /// A database located at the given URI filename (or path). + /// + /// See: + /// + /// - Parameter filename: A URI filename + case URI(String) + } + + public var handle: COpaquePointer { return _handle } + + private var _handle: COpaquePointer = nil + + /// Initializes a new SQLite connection. + /// + /// - Parameters: + /// + /// - location: The location of the database. Creates a new database if it + /// doesn’t already exist (unless in read-only mode). + /// + /// Default: `.InMemory`. + /// + /// - readonly: Whether or not to open the database in a read-only state. + /// + /// Default: `false`. + /// + /// - Returns: A new database connection. + public init(_ location: Location = .InMemory, readonly: Bool = false) throws { + let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE + try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) + } + + /// Initializes a new connection to a database. + /// + /// - Parameters: + /// + /// - filename: The location of the database. Creates a new database if + /// it doesn’t already exist (unless in read-only mode). + /// + /// - readonly: Whether or not to open the database in a read-only state. + /// + /// Default: `false`. + /// + /// - Throws: `Result.Error` iff a connection cannot be established. + /// + /// - Returns: A new database connection. + public convenience init(_ filename: String, readonly: Bool = false) throws { + try self.init(.URI(filename), readonly: readonly) + } + + deinit { + sqlite3_close_v2(handle) + } + + // MARK: - + + /// Whether or not the database was opened in a read-only state. + public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } + + /// The last rowid inserted into the database via this connection. + public var lastInsertRowid: Int64? { + let rowid = sqlite3_last_insert_rowid(handle) + return rowid > 0 ? rowid : nil + } + + /// The last number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + public var changes: Int { + return Int(sqlite3_changes(handle)) + } + + /// The total number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + public var totalChanges: Int { + return Int(sqlite3_total_changes(handle)) + } + + // MARK: - Execute + + /// Executes a batch of SQL statements. + /// + /// - Parameter SQL: A batch of zero or more semicolon-separated SQL + /// statements. + /// + /// - Throws: `Result.Error` if query execution fails. + public func execute(SQL: String) throws { + try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) } + } + + // MARK: - Prepare + + /// Prepares a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result public func prepare(statement: String, _ bindings: Binding?...) -> Statement { + if !bindings.isEmpty { return prepare(statement, bindings) } + return Statement(self, statement) + } + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result public func prepare(statement: String, _ bindings: [Binding?]) -> Statement { + return prepare(statement).bind(bindings) + } + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement { + return prepare(statement).bind(bindings) + } + + // MARK: - Run + + /// Runs a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + public func run(statement: String, _ bindings: Binding?...) throws -> Statement { + return try run(statement, bindings) + } + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + public func run(statement: String, _ bindings: [Binding?]) throws -> Statement { + return try prepare(statement).run(bindings) + } + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + public func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { + return try prepare(statement).run(bindings) + } + + // MARK: - Scalar + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { + return scalar(statement, bindings) + } + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { + return prepare(statement).scalar(bindings) + } + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { + return prepare(statement).scalar(bindings) + } + + // MARK: - Transactions + + /// The mode in which a transaction acquires a lock. + public enum TransactionMode : String { + + /// Defers locking the database till the first read/write executes. + case Deferred = "DEFERRED" + + /// Immediately acquires a reserved lock on the database. + case Immediate = "IMMEDIATE" + + /// Immediately acquires an exclusive lock on all databases. + case Exclusive = "EXCLUSIVE" + + } + + // TODO: Consider not requiring a throw to roll back? + /// Runs a transaction with the given mode. + /// + /// - Note: Transactions cannot be nested. To nest transactions, see + /// `savepoint()`, instead. + /// + /// - Parameters: + /// + /// - mode: The mode in which a transaction acquires a lock. + /// + /// Default: `.Deferred` + /// + /// - block: A closure to run SQL statements within the transaction. + /// The transaction will be committed when the block returns. The block + /// must throw to roll the transaction back. + /// + /// - Throws: `Result.Error`, and rethrows. + public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { + try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") + } + + // TODO: Consider not requiring a throw to roll back? + // TODO: Consider removing ability to set a name? + /// Runs a transaction with the given savepoint name (if omitted, it will + /// generate a UUID). + /// + /// - SeeAlso: `transaction()`. + /// + /// - Parameters: + /// + /// - savepointName: A unique identifier for the savepoint (optional). + /// + /// - block: A closure to run SQL statements within the transaction. + /// The savepoint will be released (committed) when the block returns. + /// The block must throw to roll the savepoint back. + /// + /// - Throws: `SQLite.Result.Error`, and rethrows. + public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { + let name = name.quote("'") + let savepoint = "SAVEPOINT \(name)" + + try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") + } + + private func transaction(begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { + return try sync { + try self.run(begin) + do { + try block() + } catch { + try self.run(rollback) + throw error + } + try self.run(commit) + } + } + + /// Interrupts any long-running queries. + public func interrupt() { + sqlite3_interrupt(handle) + } + + // MARK: - Handlers + + /// The number of seconds a connection will attempt to retry a statement + /// after encountering a busy signal (lock). + public var busyTimeout: Double = 0 { + didSet { + sqlite3_busy_timeout(handle, Int32(busyTimeout * 1_000)) + } + } + + /// Sets a handler to call after encountering a busy signal (lock). + /// + /// - Parameter callback: This block is executed during a lock in which a + /// busy error would otherwise be returned. It’s passed the number of + /// times it’s been called for this lock. If it returns `true`, it will + /// try again. If it returns `false`, no further attempts will be made. + public func busyHandler(callback: ((tries: Int) -> Bool)?) { + if let callback = callback { + busyHandler = { callback(tries: Int($0)) ? 1 : 0 } + } else { + busyHandler = nil + } + } + private var busyHandler: _SQLiteBusyHandlerCallback? + + /// Sets a handler to call when a statement is executed with the compiled + /// SQL. + /// + /// - Parameter callback: This block is invoked when a statement is executed + /// with the compiled SQL as its argument. E.g., pass `print` to act as a + /// logger. + /// + /// db.trace(print) + public func trace(callback: (String -> Void)?) { + if let callback = callback { + trace = { callback(String.fromCString($0)!) } + } else { + trace = nil + } + _SQLiteTrace(handle, trace) + } + private var trace: _SQLiteTraceCallback? + + /// Registers a callback to be invoked whenever a row is inserted, updated, + /// or deleted in a rowid table. + /// + /// - Parameter callback: A callback invoked with the `Operation` (one of + /// `.Insert`, `.Update`, or `.Delete`), database name, table name, and + /// rowid. + public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { + if let callback = callback { + updateHook = { operation, db, table, rowid in + callback( + operation: Operation(rawValue: operation), + db: String.fromCString(db)!, + table: String.fromCString(table)!, + rowid: rowid + ) + } + } else { + updateHook = nil + } + _SQLiteUpdateHook(handle, updateHook) + } + private var updateHook: _SQLiteUpdateHookCallback? + + /// Registers a callback to be invoked whenever a transaction is committed. + /// + /// - Parameter callback: A callback invoked whenever a transaction is + /// committed. If this callback throws, the transaction will be rolled + /// back. + public func commitHook(callback: (() throws -> Void)?) { + if let callback = callback { + commitHook = { + do { + try callback() + return 0 + } catch { + return 1 + } + } + } else { + commitHook = nil + } + _SQLiteCommitHook(handle, commitHook) + } + private var commitHook: _SQLiteCommitHookCallback? + + /// Registers a callback to be invoked whenever a transaction rolls back. + /// + /// - Parameter callback: A callback invoked when a transaction is rolled + /// back. + public func rollbackHook(callback: _SQLiteRollbackHookCallback?) { + rollbackHook = callback + _SQLiteRollbackHook(handle, rollbackHook) + } + private var rollbackHook: _SQLiteRollbackHookCallback? + + /// Creates or redefines a custom SQL function. + /// + /// - Parameters: + /// + /// - function: The name of the function to create or redefine. + /// + /// - argumentCount: The number of arguments that the function takes. If + /// `nil`, the function may take any number of arguments. + /// + /// Default: `nil` + /// + /// - deterministic: Whether or not the function is deterministic (_i.e._ + /// the function always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - block: A block of code to run when the function is called. The block + /// is called with an array of raw SQL values mapped to the function’s + /// parameters and should return a raw SQL value (or nil). + public func createFunction(function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { + let argc = argumentCount.map { Int($0) } ?? -1 + if functions[function] == nil { self.functions[function] = [:] } + functions[function]?[argc] = { context, argc, argv in + let arguments: [Binding?] = (0.. ComparisonResult) { + collations[collation] = { lhs, rhs in + Int32(block(lhs: String.fromCString(lhs)!, rhs: String.fromCString(rhs)!).rawValue) + } + try! check(_SQLiteCreateCollation(handle, collation, collations[collation])) + } + private var collations = [String: _SQLiteCreateCollationCallback]() + + // MARK: - Error Handling + + func sync(block: () throws -> T) rethrows -> T { + var success: T? + var failure: ErrorType? + + let box: () -> Void = { + do { + success = try block() + } catch { + failure = error + } + } + + if dispatch_get_specific(Connection.queueKey) == queueContext { + box() + } else { + dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 + } + + if let failure = failure { + try { () -> Void in throw failure }() + } + + return success! + } + + func check(resultCode: Int32, statement: Statement? = nil) throws -> Int32 { + guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else { + return resultCode + } + + throw error + } + + private var queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) + + private static let queueKey = unsafeBitCast(Connection.self, UnsafePointer.self) + + private lazy var queueContext: UnsafeMutablePointer = unsafeBitCast(self, UnsafeMutablePointer.self) + +} + +extension Connection : CustomStringConvertible { + + public var description: String { + return String.fromCString(sqlite3_db_filename(handle, nil))! + } + +} + +extension Connection.Location : CustomStringConvertible { + + public var description: String { + switch self { + case .InMemory: + return ":memory:" + case .Temporary: + return "" + case .URI(let URI): + return URI + } + } + +} + +/// An SQL operation passed to update callbacks. +public enum Operation { + + /// An INSERT operation. + case Insert + + /// An UPDATE operation. + case Update + + /// A DELETE operation. + case Delete + + private init(rawValue: Int32) { + switch rawValue { + case SQLITE_INSERT: + self = .Insert + case SQLITE_UPDATE: + self = .Update + case SQLITE_DELETE: + self = .Delete + default: + fatalError("unhandled operation code: \(rawValue)") + } + } + +} + +public enum Result : ErrorType { + + private static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + + case Error(message: String, code: Int32, statement: Statement?) + + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { + guard !Result.successCodes.contains(errorCode) else { return nil } + + let message = String.fromCString(sqlite3_errmsg(connection.handle))! + self = Error(message: message, code: errorCode, statement: statement) + } + +} + +extension Result : CustomStringConvertible { + + public var description: String { + switch self { + case let .Error(message, _, statement): + guard let statement = statement else { return message } + + return "\(message) (\(statement))" + } + } + +} diff --git a/SQLite/SQLite-Bridging.h b/Source/Core/SQLite-Bridging.h similarity index 63% rename from SQLite/SQLite-Bridging.h rename to Source/Core/SQLite-Bridging.h index 6bb9f2de..31a07bc5 100644 --- a/SQLite/SQLite-Bridging.h +++ b/Source/Core/SQLite-Bridging.h @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,33 +24,38 @@ @import Foundation; +#ifndef COCOAPODS +#import "sqlite3.h" +#endif + +// CocoaPods workaround typedef struct SQLiteHandle SQLiteHandle; typedef struct SQLiteContext SQLiteContext; typedef struct SQLiteValue SQLiteValue; NS_ASSUME_NONNULL_BEGIN typedef int (^_SQLiteBusyHandlerCallback)(int times); -int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback __nullable callback); +int _SQLiteBusyHandler(SQLiteHandle * db, _SQLiteBusyHandlerCallback _Nullable callback); typedef void (^_SQLiteTraceCallback)(const char * SQL); -void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback __nullable callback); +void _SQLiteTrace(SQLiteHandle * db, _SQLiteTraceCallback _Nullable callback); typedef void (^_SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, long long rowid); -void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback __nullable callback); +void _SQLiteUpdateHook(SQLiteHandle * db, _SQLiteUpdateHookCallback _Nullable callback); typedef int (^_SQLiteCommitHookCallback)(); -void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback __nullable callback); +void _SQLiteCommitHook(SQLiteHandle * db, _SQLiteCommitHookCallback _Nullable callback); typedef void (^_SQLiteRollbackHookCallback)(); -void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback __nullable callback); +void _SQLiteRollbackHook(SQLiteHandle * db, _SQLiteRollbackHookCallback _Nullable callback); -typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue * __nonnull * __nonnull argv); -int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback __nullable callback); +typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue * _Nonnull * _Nonnull argv); +int _SQLiteCreateFunction(SQLiteHandle * db, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback _Nullable callback); typedef int (^_SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); -int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback __nullable callback); +int _SQLiteCreateCollation(SQLiteHandle * db, const char * name, _SQLiteCreateCollationCallback _Nullable callback); -typedef NSString * __nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, __nullable _SQLiteTokenizerNextCallback callback); +typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); NS_ASSUME_NONNULL_END diff --git a/SQLite/SQLite-Bridging.m b/Source/Core/SQLite-Bridging.m similarity index 77% rename from SQLite/SQLite-Bridging.m rename to Source/Core/SQLite-Bridging.m index 26b6897d..d34d88dc 100644 --- a/SQLite/SQLite-Bridging.m +++ b/Source/Core/SQLite-Bridging.m @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,17 +24,21 @@ #import "SQLite-Bridging.h" +#ifdef COCOAPODS +#import "sqlite3.h" +#endif + #import "fts3_tokenizer.h" static int __SQLiteBusyHandler(void * context, int tries) { return ((__bridge _SQLiteBusyHandlerCallback)context)(tries); } -int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback callback) { +int _SQLiteBusyHandler(SQLiteHandle * db, _SQLiteBusyHandlerCallback callback) { if (callback) { - return sqlite3_busy_handler((sqlite3 *)handle, __SQLiteBusyHandler, (__bridge void *)callback); + return sqlite3_busy_handler((sqlite3 *)db, __SQLiteBusyHandler, (__bridge void *)callback); } else { - return sqlite3_busy_handler((sqlite3 *)handle, 0, 0); + return sqlite3_busy_handler((sqlite3 *)db, 0, 0); } } @@ -42,11 +46,11 @@ static void __SQLiteTrace(void * context, const char * SQL) { ((__bridge _SQLiteTraceCallback)context)(SQL); } -void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback callback) { +void _SQLiteTrace(SQLiteHandle * db, _SQLiteTraceCallback callback) { if (callback) { - sqlite3_trace((sqlite3 *)handle, __SQLiteTrace, (__bridge void *)callback); + sqlite3_trace((sqlite3 *)db, __SQLiteTrace, (__bridge void *)callback); } else { - sqlite3_trace((sqlite3 *)handle, 0, 0); + sqlite3_trace((sqlite3 *)db, 0, 0); } } @@ -54,31 +58,31 @@ static void __SQLiteUpdateHook(void * context, int operation, const char * db, c ((__bridge _SQLiteUpdateHookCallback)context)(operation, db, table, rowid); } -void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback callback) { - sqlite3_update_hook((sqlite3 *)handle, __SQLiteUpdateHook, (__bridge void *)callback); +void _SQLiteUpdateHook(SQLiteHandle * db, _SQLiteUpdateHookCallback callback) { + sqlite3_update_hook((sqlite3 *)db, __SQLiteUpdateHook, (__bridge void *)callback); } static int __SQLiteCommitHook(void * context) { return ((__bridge _SQLiteCommitHookCallback)context)(); } -void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback callback) { - sqlite3_commit_hook((sqlite3 *)handle, __SQLiteCommitHook, (__bridge void *)callback); +void _SQLiteCommitHook(SQLiteHandle * db, _SQLiteCommitHookCallback callback) { + sqlite3_commit_hook((sqlite3 *)db, __SQLiteCommitHook, (__bridge void *)callback); } static void __SQLiteRollbackHook(void * context) { - ((__bridge _SQLiteRollbackHookCallback)context)(); + ((__bridge void (^ _Null_unspecified )())context)(); } -void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback callback) { - sqlite3_rollback_hook((sqlite3 *)handle, __SQLiteRollbackHook, (__bridge void *)callback); +void _SQLiteRollbackHook(SQLiteHandle * db, _SQLiteRollbackHookCallback _Nullable callback) { + sqlite3_rollback_hook((sqlite3 *)db, __SQLiteRollbackHook, (__bridge void *)callback); } static void __SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { ((__bridge _SQLiteCreateFunctionCallback)sqlite3_user_data(context))((SQLiteContext *)context, argc, (SQLiteValue **)argv); } -int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback) { +int _SQLiteCreateFunction(SQLiteHandle * db, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback) { if (callback) { int flags = SQLITE_UTF8; if (deterministic) { @@ -86,9 +90,9 @@ int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, in flags |= SQLITE_DETERMINISTIC; #endif } - return sqlite3_create_function_v2((sqlite3 *)handle, name, -1, flags, (__bridge void *)callback, &__SQLiteCreateFunction, 0, 0, 0); + return sqlite3_create_function_v2((sqlite3 *)db, name, -1, flags, (__bridge void *)callback, &__SQLiteCreateFunction, 0, 0, 0); } else { - return sqlite3_create_function_v2((sqlite3 *)handle, name, 0, 0, 0, 0, 0, 0, 0); + return sqlite3_create_function_v2((sqlite3 *)db, name, 0, 0, 0, 0, 0, 0, 0); } } @@ -96,11 +100,11 @@ static int __SQLiteCreateCollation(void * context, int len_lhs, const void * lhs return ((__bridge _SQLiteCreateCollationCallback)context)(lhs, rhs); } -int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback callback) { +int _SQLiteCreateCollation(SQLiteHandle * db, const char * name, _SQLiteCreateCollationCallback callback) { if (callback) { - return sqlite3_create_collation_v2((sqlite3 *)handle, name, SQLITE_UTF8, (__bridge void *)callback, &__SQLiteCreateCollation, 0); + return sqlite3_create_collation_v2((sqlite3 *)db, name, SQLITE_UTF8, (__bridge void *)callback, &__SQLiteCreateCollation, 0); } else { - return sqlite3_create_collation_v2((sqlite3 *)handle, name, 0, 0, 0, 0); + return sqlite3_create_collation_v2((sqlite3 *)db, name, 0, 0, 0, 0); } } @@ -126,7 +130,7 @@ static int __SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_ if (!tokenizer) { return SQLITE_NOMEM; } - memset(tokenizer, 0, sizeof(* tokenizer)); // FIXME: needed? + memset(tokenizer, 0, sizeof(* tokenizer)); NSString * key = [NSString stringWithUTF8String:argv[0]]; tokenizer->callback = [__SQLiteTokenizerMap objectForKey:key]; diff --git a/Source/Core/Statement.swift b/Source/Core/Statement.swift new file mode 100644 index 00000000..d735921c --- /dev/null +++ b/Source/Core/Statement.swift @@ -0,0 +1,279 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// A single SQL statement. +public final class Statement { + + private var handle: COpaquePointer = nil + + private let connection: Connection + + init(_ connection: Connection, _ SQL: String) { + self.connection = connection + try! connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) + } + + deinit { + sqlite3_finalize(handle) + } + + public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) + + public lazy var columnNames: [String] = (0.. Statement { + return bind(values) + } + + /// Binds a list of parameters to a statement. + /// + /// - Parameter values: A list of parameters to bind to the statement. + /// + /// - Returns: The statement object (useful for chaining). + public func bind(values: [Binding?]) -> Statement { + if values.isEmpty { return self } + reset() + guard values.count == Int(sqlite3_bind_parameter_count(handle)) else { + fatalError("\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed") + } + for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) } + return self + } + + /// Binds a dictionary of named parameters to a statement. + /// + /// - Parameter values: A dictionary of named parameters to bind to the + /// statement. + /// + /// - Returns: The statement object (useful for chaining). + public func bind(values: [String: Binding?]) -> Statement { + reset() + for (name, value) in values { + let idx = sqlite3_bind_parameter_index(handle, name) + guard idx > 0 else { + fatalError("parameter not found: \(name)") + } + bind(value, atIndex: Int(idx)) + } + return self + } + + private func bind(value: Binding?, atIndex idx: Int) { + if value == nil { + sqlite3_bind_null(handle, Int32(idx)) + } else if let value = value as? Blob { + sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT) + } else if let value = value as? Double { + sqlite3_bind_double(handle, Int32(idx), value) + } else if let value = value as? Int64 { + sqlite3_bind_int64(handle, Int32(idx), value) + } else if let value = value as? String { + sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT) + } else if let value = value as? Int { + self.bind(value.datatypeValue, atIndex: idx) + } else if let value = value as? Bool { + self.bind(value.datatypeValue, atIndex: idx) + } else if let value = value { + fatalError("tried to bind unexpected value \(value)") + } + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement object (useful for chaining). + public func run(bindings: Binding?...) throws -> Statement { + guard bindings.isEmpty else { + return try run(bindings) + } + + reset(clearBindings: false) + repeat {} while try step() + return self + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement object (useful for chaining). + public func run(bindings: [Binding?]) throws -> Statement { + return try bind(bindings).run() + } + + /// - Parameter bindings: A dictionary of named parameters to bind to the + /// statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement object (useful for chaining). + public func run(bindings: [String: Binding?]) throws -> Statement { + return try bind(bindings).run() + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(bindings: Binding?...) -> Binding? { + guard bindings.isEmpty else { + return scalar(bindings) + } + + reset(clearBindings: false) + try! step() + return row[0] + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(bindings: [Binding?]) -> Binding? { + return bind(bindings).scalar() + } + + + /// - Parameter bindings: A dictionary of named parameters to bind to the + /// statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(bindings: [String: Binding?]) -> Binding? { + return bind(bindings).scalar() + } + + public func step() throws -> Bool { + return try connection.sync { try self.connection.check(sqlite3_step(self.handle)) == SQLITE_ROW } + } + + private func reset(clearBindings shouldClear: Bool = true) { + sqlite3_reset(handle) + if (shouldClear) { sqlite3_clear_bindings(handle) } + } + +} + +extension Statement : SequenceType { + + public func generate() -> Statement { + reset(clearBindings: false) + return self + } + +} + +extension Statement : GeneratorType { + + public func next() -> [Binding?]? { + return try! step() ? Array(row) : nil + } + +} + +extension Statement : CustomStringConvertible { + + public var description: String { + return String.fromCString(sqlite3_sql(handle))! + } + +} + +public struct Cursor { + + private let handle: COpaquePointer + + private let columnCount: Int + + private init(_ statement: Statement) { + handle = statement.handle + columnCount = statement.columnCount + } + + public subscript(idx: Int) -> Double { + return sqlite3_column_double(handle, Int32(idx)) + } + + public subscript(idx: Int) -> Int64 { + return sqlite3_column_int64(handle, Int32(idx)) + } + + public subscript(idx: Int) -> String { + return String.fromCString(UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) ?? "" + } + + public subscript(idx: Int) -> Blob { + let bytes = sqlite3_column_blob(handle, Int32(idx)) + let length = Int(sqlite3_column_bytes(handle, Int32(idx))) + return Blob(bytes: bytes, length: length) + } + + // MARK: - + + public subscript(idx: Int) -> Bool { + return Bool.fromDatatypeValue(self[idx]) + } + + public subscript(idx: Int) -> Int { + return Int.fromDatatypeValue(self[idx]) + } + +} + +/// Cursors provide direct access to a statement’s current row. +extension Cursor : SequenceType { + + public subscript(idx: Int) -> Binding? { + switch sqlite3_column_type(handle, Int32(idx)) { + case SQLITE_BLOB: + return self[idx] as Blob + case SQLITE_FLOAT: + return self[idx] as Double + case SQLITE_INTEGER: + return self[idx] as Int64 + case SQLITE_NULL: + return nil + case SQLITE_TEXT: + return self[idx] as String + case let type: + fatalError("unsupported column type: \(type)") + } + } + + public func generate() -> AnyGenerator { + var idx = 0 + return anyGenerator { + idx >= self.columnCount ? Optional.None : self[idx++] + } + } + +} diff --git a/SQLite/Value.swift b/Source/Core/Value.swift similarity index 59% rename from SQLite/Value.swift rename to Source/Core/Value.swift index 567320bf..96e80371 100644 --- a/SQLite/Value.swift +++ b/Source/Core/Value.swift @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,20 +22,20 @@ // THE SOFTWARE. // -/// Binding is a protocol that SQLite.swift uses internally to directly map -/// SQLite types to Swift types. +/// - Warning: `Binding` is a protocol that SQLite.swift uses internally to +/// directly map SQLite types to Swift types. /// -/// Do not conform custom types to the Binding protocol. See the Value protocol, -/// instead. +/// Do not conform custom types to the Binding protocol. See the `Value` +/// protocol, instead. public protocol Binding {} -public protocol Number: Binding {} +public protocol Number : Binding {} -public protocol Value { +public protocol Value : Expressible { // extensions cannot have inheritance clauses typealias ValueType = Self - typealias Datatype: Binding + typealias Datatype : Binding static var declaredDatatype: String { get } @@ -45,107 +45,57 @@ public protocol Value { } -private let hexChars: [Character] = Array("0123456789abcdef") +extension Double : Number, Value { -public struct Blob: Equatable, Printable { + public static let declaredDatatype = "REAL" - public let data: [UInt8] - - public init(data: [UInt8]) { - self.data = data - } - - public init(bytes: UnsafePointer, length: Int) { - self.data = [UInt8](UnsafeBufferPointer( - start: UnsafePointer(bytes), - count: length - )) - } - - public func toHex() -> String { - var string: String = "" - string.reserveCapacity(data.count*2) - for byte in data { - let a = hexChars[Int(byte >> 4)] - let b = hexChars[Int(byte & 0xF)] - string.append(a) - string.append(b) - } - return string - } - - public var description: String { - return "x'\(toHex())'" - } - -} - -public func ==(lhs: Blob, rhs: Blob) -> Bool { - return lhs.data == rhs.data -} - - -extension Blob { - public var bytes: UnsafePointer { - return UnsafePointer(data) - } - - public var length: Int { - return data.count - } -} - -extension Blob: Binding, Value { - - public static var declaredDatatype = "BLOB" - - public static func fromDatatypeValue(datatypeValue: Blob) -> Blob { + public static func fromDatatypeValue(datatypeValue: Double) -> Double { return datatypeValue } - public var datatypeValue: Blob { + public var datatypeValue: Double { return self } } -extension Double: Number, Value { +extension Int64 : Number, Value { - public static var declaredDatatype = "REAL" + public static let declaredDatatype = "INTEGER" - public static func fromDatatypeValue(datatypeValue: Double) -> Double { + public static func fromDatatypeValue(datatypeValue: Int64) -> Int64 { return datatypeValue } - public var datatypeValue: Double { + public var datatypeValue: Int64 { return self } } -extension Int64: Number, Value { +extension String : Binding, Value { - public static var declaredDatatype = "INTEGER" + public static let declaredDatatype = "TEXT" - public static func fromDatatypeValue(datatypeValue: Int64) -> Int64 { + public static func fromDatatypeValue(datatypeValue: String) -> String { return datatypeValue } - public var datatypeValue: Int64 { + public var datatypeValue: String { return self } } -extension String: Binding, Value { +extension Blob : Binding, Value { - public static var declaredDatatype = "TEXT" + public static let declaredDatatype = "BLOB" - public static func fromDatatypeValue(datatypeValue: String) -> String { + public static func fromDatatypeValue(datatypeValue: Blob) -> Blob { return datatypeValue } - public var datatypeValue: String { + public var datatypeValue: Blob { return self } @@ -153,7 +103,7 @@ extension String: Binding, Value { // MARK: - -extension Bool: Binding, Value { +extension Bool : Binding, Value { public static var declaredDatatype = Int64.declaredDatatype @@ -167,7 +117,7 @@ extension Bool: Binding, Value { } -extension Int: Binding, Value { +extension Int : Number, Value { public static var declaredDatatype = Int64.declaredDatatype diff --git a/SQLite/fts3_tokenizer.h b/Source/Core/fts3_tokenizer.h similarity index 100% rename from SQLite/fts3_tokenizer.h rename to Source/Core/fts3_tokenizer.h diff --git a/Source/Extensions/FTS4.swift b/Source/Extensions/FTS4.swift new file mode 100644 index 00000000..92e7b9d9 --- /dev/null +++ b/Source/Extensions/FTS4.swift @@ -0,0 +1,156 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension Module { + + @warn_unused_result public static func FTS4(column: Expressible, _ more: Expressible...) -> Module { + return FTS4([column] + more) + } + + @warn_unused_result public static func FTS4(var columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { + if let tokenizer = tokenizer { + columns.append("=".join([Expression(literal: "tokenize"), Expression(literal: tokenizer.description)])) + } + return Module(name: "fts4", arguments: columns) + } + +} + +extension VirtualTable { + + /// Builds an expression appended with a `MATCH` query against the given + /// pattern. + /// + /// let emails = VirtualTable("emails") + /// + /// emails.filter(emails.match("Hello")) + /// // SELECT * FROM "emails" WHERE "emails" MATCH 'Hello' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: An expression appended with a `MATCH` query against the given + /// pattern. + @warn_unused_result public func match(pattern: String) -> Expression { + return "MATCH".infix(tableName(), pattern) + } + + @warn_unused_result public func match(pattern: Expression) -> Expression { + return "MATCH".infix(tableName(), pattern) + } + + @warn_unused_result public func match(pattern: Expression) -> Expression { + return "MATCH".infix(tableName(), pattern) + } + + /// Builds a copy of the query with a `WHERE … MATCH` clause. + /// + /// let emails = VirtualTable("emails") + /// + /// emails.match("Hello") + /// // SELECT * FROM "emails" WHERE "emails" MATCH 'Hello' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A query with the given `WHERE … MATCH` clause applied. + @warn_unused_result public func match(pattern: String) -> QueryType { + return filter(match(pattern)) + } + + @warn_unused_result public func match(pattern: Expression) -> QueryType { + return filter(match(pattern)) + } + + @warn_unused_result public func match(pattern: Expression) -> QueryType { + return filter(match(pattern)) + } + +} + +public struct Tokenizer { + + public static let Simple = Tokenizer("simple") + + public static let Porter = Tokenizer("porter") + + @warn_unused_result public static func Unicode61(removeDiacritics removeDiacritics: Bool? = nil, tokenchars: Set = [], separators: Set = []) -> Tokenizer { + var arguments = [String]() + + if let removeDiacritics = removeDiacritics { + arguments.append("removeDiacritics=\(removeDiacritics ? 1 : 0)".quote()) + } + + if !tokenchars.isEmpty { + let joined = "".join(tokenchars.map { String($0) }) + arguments.append("tokenchars=\(joined)".quote()) + } + + if !separators.isEmpty { + let joined = "".join(separators.map { String($0) }) + arguments.append("separators=\(joined)".quote()) + } + + return Tokenizer("unicode61", arguments) + } + + @warn_unused_result public static func Custom(name: String) -> Tokenizer { + return Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) + } + + public let name: String + + public let arguments: [String] + + private init(_ name: String, _ arguments: [String] = []) { + self.name = name + self.arguments = arguments + } + + private static let moduleName = "SQLite.swift" + +} + +extension Tokenizer : CustomStringConvertible { + + public var description: String { + return " ".join([name] + arguments) + } + +} + +extension Connection { + + public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { + try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + let string = String.fromCString(input)! + if let (token, range) = next(string) { + let view = string.utf8 + offset.memory += string.substringToIndex(range.startIndex).utf8.count + length.memory = Int32(distance(range.startIndex.samePositionIn(view), range.endIndex.samePositionIn(view))) + return token + } + return nil + }) + } + +} diff --git a/SQLite/RTree.swift b/Source/Extensions/R*Tree.swift similarity index 70% rename from SQLite/RTree.swift rename to Source/Extensions/R*Tree.swift index 9ff0f6c3..0ba81689 100644 --- a/SQLite/RTree.swift +++ b/Source/Extensions/R*Tree.swift @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,8 +22,16 @@ // THE SOFTWARE. // -public func rtree(primaryKey: Expression, columns: Expression...) -> Expression { - var definitions: [Expressible] = [primaryKey] - definitions.extend(columns.map { $0 }) - return wrap(__FUNCTION__, Expression.join(", ", definitions)) +extension Module { + + @warn_unused_result public static func RTree(primaryKey: Expression, _ pairs: (Expression, Expression)...) -> Module { + var arguments: [Expressible] = [primaryKey] + + for pair in pairs { + arguments.extend([pair.0, pair.1] as [Expressible]) + } + + return Module(name: "rtree", arguments: arguments) + } + } diff --git a/Source/Foundation.swift b/Source/Foundation.swift new file mode 100644 index 00000000..ef39d3bf --- /dev/null +++ b/Source/Foundation.swift @@ -0,0 +1,104 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension NSData : Value { + + public class var declaredDatatype: String { + return Blob.declaredDatatype + } + + public class func fromDatatypeValue(dataValue: Blob) -> NSData { + return NSData(bytes: dataValue.bytes, length: dataValue.bytes.count) + } + + public var datatypeValue: Blob { + return Blob(bytes: bytes, length: length) + } + +} + +extension NSDate : Value { + + public class var declaredDatatype: String { + return String.declaredDatatype + } + + public class func fromDatatypeValue(stringValue: String) -> NSDate { + return dateFormatter.dateFromString(stringValue)! + } + + public var datatypeValue: String { + return dateFormatter.stringFromDate(self) + } + +} + +/// A global date formatter used to serialize and deserialize `NSDate` objects. +/// If multiple date formats are used in an application’s database(s), use a +/// custom `Value` type per additional format. +public var dateFormatter: NSDateFormatter = { + let formatter = NSDateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" + formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") + formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) + return formatter +}() + +// FIXME: rdar://problem/18673897 // subscript… + +extension QueryType { + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + +} + +extension Row { + + public subscript(column: Expression) -> NSData { + return get(column) + } + public subscript(column: Expression) -> NSData? { + return get(column) + } + + public subscript(column: Expression) -> NSDate { + return get(column) + } + public subscript(column: Expression) -> NSDate? { + return get(column) + } + +} \ No newline at end of file diff --git a/Source/Helpers.swift b/Source/Helpers.swift new file mode 100644 index 00000000..eaa424fd --- /dev/null +++ b/Source/Helpers.swift @@ -0,0 +1,122 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public typealias Star = (Expression?, Expression?) -> Expression + +public func *(_: Expression?, _: Expression?) -> Expression { + return Expression(literal: "*") +} + +public protocol _OptionalType { + + typealias Wrapped + +} + +extension Optional : _OptionalType { + + public typealias Wrapped = T + +} + +// let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self) +let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self) + +extension String { + + @warn_unused_result func quote(mark: Character = "\"") -> String { + let escaped = characters.reduce("") { string, character in + string + (character == mark ? "\(mark)\(mark)" : "\(character)") + } + return "\(mark)\(escaped)\(mark)" + } + + @warn_unused_result func join(expressions: [Expressible]) -> Expressible { + var (template, bindings) = ([String](), [Binding?]()) + for expressible in expressions { + let expression = expressible.expression + template.append(expression.template) + bindings.extend(expression.bindings) + } + return Expression(join(template), bindings) + } + + @warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + let expression = Expression(" \(self) ".join([lhs, rhs]).expression) + guard wrap else { + return expression + } + return "".wrap(expression) + } + + @warn_unused_result func prefix(expressions: Expressible) -> Expressible { + return "\(self) ".wrap(expressions) as Expression + } + + @warn_unused_result func prefix(expressions: [Expressible]) -> Expressible { + return "\(self) ".wrap(expressions) as Expression + } + + @warn_unused_result func wrap(expression: Expressible) -> Expression { + return Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) + } + + @warn_unused_result func wrap(expressions: [Expressible]) -> Expression { + return wrap(", ".join(expressions)) + } + +} + +@warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = __FUNCTION__) -> Expression { + return function.infix(lhs, rhs, wrap: wrap) +} + +@warn_unused_result func wrap(expression: Expressible, function: String = __FUNCTION__) -> Expression { + return function.wrap(expression) +} + +@warn_unused_result func wrap(expressions: [Expressible], function: String = __FUNCTION__) -> Expression { + return function.wrap(", ".join(expressions)) +} + +@warn_unused_result func transcode(literal: Binding?) -> String { + if let literal = literal { + if let literal = literal as? NSData { + let buf = UnsafeBufferPointer(start: UnsafePointer(literal.bytes), count: literal.length) + let hex = "".join(buf.map { String(format: "%02x", $0) }) + return "x'\(hex)'" + } + if let literal = literal as? String { return literal.quote("'") } + return "\(literal)" + } + return "NULL" +} + +@warn_unused_result func value(v: Binding) -> A { + return A.fromDatatypeValue(v as! A.Datatype) as! A +} + +@warn_unused_result func value(v: Binding?) -> A { + return value(v!) +} diff --git a/Source/Typed/AggregateFunctions.swift b/Source/Typed/AggregateFunctions.swift new file mode 100644 index 00000000..3c905243 --- /dev/null +++ b/Source/Typed/AggregateFunctions.swift @@ -0,0 +1,251 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension ExpressionType where UnderlyingType : Value { + + /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. + /// + /// let name = Expression("name") + /// name.distinct + /// // DISTINCT "name" + /// + /// - Returns: A copy of the expression prefixed with the `DISTINCT` + /// keyword. + public var distinct: Expression { + return Expression("DISTINCT \(template)", bindings) + } + + /// Builds a copy of the expression wrapped with the `count` aggregate + /// function. + /// + /// let name = Expression("name") + /// name.count + /// // count("name") + /// name.distinct.count + /// // count(DISTINCT "name") + /// + /// - Returns: A copy of the expression wrapped with the `count` aggregate + /// function. + public var count: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Value { + + /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. + /// + /// let name = Expression("name") + /// name.distinct + /// // DISTINCT "name" + /// + /// - Returns: A copy of the expression prefixed with the `DISTINCT` + /// keyword. + public var distinct: Expression { + return Expression("DISTINCT \(template)", bindings) + } + + /// Builds a copy of the expression wrapped with the `count` aggregate + /// function. + /// + /// let name = Expression("name") + /// name.count + /// // count("name") + /// name.distinct.count + /// // count(DISTINCT "name") + /// + /// - Returns: A copy of the expression wrapped with the `count` aggregate + /// function. + public var count: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : protocol { + + /// Builds a copy of the expression wrapped with the `max` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.max + /// // max("age") + /// + /// - Returns: A copy of the expression wrapped with the `max` aggregate + /// function. + public var max: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `min` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.min + /// // min("age") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var min: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : protocol { + + /// Builds a copy of the expression wrapped with the `max` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.max + /// // max("age") + /// + /// - Returns: A copy of the expression wrapped with the `max` aggregate + /// function. + public var max: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `min` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.min + /// // min("age") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var min: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Number { + + /// Builds a copy of the expression wrapped with the `avg` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.average + /// // avg("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var average: Expression { + return "avg".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `sum` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.sum + /// // sum("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var sum: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `total` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.total + /// // total("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var total: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Value, UnderlyingType.Wrapped.Datatype : Number { + + /// Builds a copy of the expression wrapped with the `avg` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.average + /// // avg("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var average: Expression { + return "avg".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `sum` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.sum + /// // sum("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var sum: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `total` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.total + /// // total("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var total: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == Int { + + @warn_unused_result static func count(star: Star) -> Expression { + return wrap(star(nil, nil)) + } + +} + +/// Builds an expression representing `count(*)` (when called with the `*` +/// function literal). +/// +/// count(*) +/// // count(*) +/// +/// - Returns: An expression returning `count(*)` (when called with the `*` +/// function literal). +@warn_unused_result public func count(star: Star) -> Expression { + return Expression.count(star) +} diff --git a/Source/Typed/Collation.swift b/Source/Typed/Collation.swift new file mode 100644 index 00000000..5a632055 --- /dev/null +++ b/Source/Typed/Collation.swift @@ -0,0 +1,69 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// A collating function used to compare to strings. +/// +/// - SeeAlso: +public enum Collation { + + /// Compares string by raw data. + case Binary + + /// Like binary, but folds uppercase ASCII letters into their lowercase + /// equivalents. + case Nocase + + /// Like binary, but strips trailing space. + case Rtrim + + /// A custom collating sequence identified by the given string, registered + /// using `Database.create(collation:…)` + case Custom(String) + +} + +extension Collation : Expressible { + + public var expression: Expression { + return Expression(literal: description) + } + +} + +extension Collation : CustomStringConvertible { + + public var description : String { + switch self { + case Binary: + return "BINARY" + case Nocase: + return "NOCASE" + case Rtrim: + return "RTRIM" + case Custom(let collation): + return collation.quote() + } + } + +} diff --git a/Source/Typed/CoreFunctions.swift b/Source/Typed/CoreFunctions.swift new file mode 100644 index 00000000..e2867976 --- /dev/null +++ b/Source/Typed/CoreFunctions.swift @@ -0,0 +1,680 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension ExpressionType where UnderlyingType : Number { + + /// Builds a copy of the expression wrapped with the `abs` function. + /// + /// let x = Expression("x") + /// x.absoluteValue + /// // abs("x") + /// + /// - Returns: A copy of the expression wrapped with the `abs` function. + public var absoluteValue : Expression { + return "abs".wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Number { + + /// Builds a copy of the expression wrapped with the `abs` function. + /// + /// let x = Expression("x") + /// x.absoluteValue + /// // abs("x") + /// + /// - Returns: A copy of the expression wrapped with the `abs` function. + public var absoluteValue : Expression { + return "abs".wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == Double { + + /// Builds a copy of the expression wrapped with the `round` function. + /// + /// let salary = Expression("salary") + /// salary.round() + /// // round("salary") + /// salary.round(2) + /// // round("salary", 2) + /// + /// - Returns: A copy of the expression wrapped with the `round` function. + @warn_unused_result public func round(precision: Int? = nil) -> Expression { + guard let precision = precision else { + return wrap([self]) + } + return wrap([self, Int(precision)]) + } + +} + +extension ExpressionType where UnderlyingType == Double? { + + /// Builds a copy of the expression wrapped with the `round` function. + /// + /// let salary = Expression("salary") + /// salary.round() + /// // round("salary") + /// salary.round(2) + /// // round("salary", 2) + /// + /// - Returns: A copy of the expression wrapped with the `round` function. + @warn_unused_result public func round(precision: Int? = nil) -> Expression { + guard let precision = precision else { + return wrap(self) + } + return wrap([self, Int(precision)]) + } + +} + +extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype == Int64 { + + /// Builds an expression representing the `random` function. + /// + /// Expression.random() + /// // random() + /// + /// - Returns: An expression calling the `random` function. + @warn_unused_result public static func random() -> Expression { + return "random".wrap([]) + } + +} + +extension ExpressionType where UnderlyingType == NSData { + + /// Builds an expression representing the `randomblob` function. + /// + /// Expression.random(16) + /// // randomblob(16) + /// + /// - Parameter length: Length in bytes. + /// + /// - Returns: An expression calling the `randomblob` function. + @warn_unused_result public static func random(length: Int) -> Expression { + return "randomblob".wrap([]) + } + + /// Builds an expression representing the `zeroblob` function. + /// + /// Expression.allZeros(16) + /// // zeroblob(16) + /// + /// - Parameter length: Length in bytes. + /// + /// - Returns: An expression calling the `zeroblob` function. + @warn_unused_result public static func allZeros(length: Int) -> Expression { + return "zeroblob".wrap([]) + } + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let data = Expression("data") + /// data.length + /// // length("data") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == NSData? { + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let data = Expression("data") + /// data.length + /// // length("data") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == String { + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let name = Expression("name") + /// name.length + /// // length("name") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `lower` function. + /// + /// let name = Expression("name") + /// name.lowercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `lower` function. + public var lowercaseString: Expression { + return "lower".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `upper` function. + /// + /// let name = Expression("name") + /// name.uppercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `upper` function. + public var uppercaseString: Expression { + return "upper".wrap(self) + } + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// email.like("%@example.com") + /// // "email" LIKE '%@example.com' + /// email.like("99\\%@%", escape: "\\") + /// // "email" LIKE '99\%@%' ESCAPE '\' + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + @warn_unused_result public func like(pattern: String, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) + } + + /// Builds a copy of the expression appended with a `GLOB` query against the + /// given pattern. + /// + /// let path = Expression("path") + /// path.glob("*.png") + /// // "path" GLOB '*.png' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `GLOB` query against + /// the given pattern. + @warn_unused_result public func glob(pattern: String) -> Expression { + return "GLOB".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `MATCH` query against + /// the given pattern. + /// + /// let title = Expression("title") + /// title.match("swift AND programming") + /// // "title" MATCH 'swift AND programming' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `MATCH` query + /// against the given pattern. + @warn_unused_result public func match(pattern: String) -> Expression { + return "MATCH".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `REGEXP` query against + /// the given pattern. + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `REGEXP` query + /// against the given pattern. + @warn_unused_result public func regexp(pattern: String) -> Expression { + return "REGEXP".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `COLLATE` clause with + /// the given sequence. + /// + /// let name = Expression("name") + /// name.collate(.Nocase) + /// // "name" COLLATE NOCASE + /// + /// - Parameter collation: A collating sequence. + /// + /// - Returns: A copy of the expression appended with a `COLLATE` clause + /// with the given sequence. + @warn_unused_result public func collate(collation: Collation) -> Expression { + return "COLLATE".infix(self, collation) + } + + /// Builds a copy of the expression wrapped with the `ltrim` function. + /// + /// let name = Expression("name") + /// name.ltrim() + /// // ltrim("name") + /// name.ltrim([" ", "\t"]) + /// // ltrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `ltrim` function. + @warn_unused_result public func ltrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `rtrim` function. + /// + /// let name = Expression("name") + /// name.rtrim() + /// // rtrim("name") + /// name.rtrim([" ", "\t"]) + /// // rtrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `rtrim` function. + @warn_unused_result public func rtrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `trim` function. + /// + /// let name = Expression("name") + /// name.trim() + /// // trim("name") + /// name.trim([" ", "\t"]) + /// // trim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `trim` function. + @warn_unused_result public func trim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap([self]) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `replace` function. + /// + /// let email = Expression("email") + /// email.replace("@mac.com", with: "@icloud.com") + /// // replace("email", '@mac.com', '@icloud.com') + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - replacement: The replacement string. + /// + /// - Returns: A copy of the expression wrapped with the `replace` function. + @warn_unused_result public func replace(pattern: String, with replacement: String) -> Expression { + return "replace".wrap([self, pattern, replacement]) + } + + @warn_unused_result public func substring(location: Int, length: Int? = nil) -> Expression { + guard let length = length else { + return "substr".wrap([self, location]) + } + return "substr".wrap([self, location, length]) + } + + public subscript(range: Range) -> Expression { + return substring(range.startIndex, length: range.endIndex - range.startIndex) + } + +} + +extension ExpressionType where UnderlyingType == String? { + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let name = Expression("name") + /// name.length + /// // length("name") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `lower` function. + /// + /// let name = Expression("name") + /// name.lowercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `lower` function. + public var lowercaseString: Expression { + return "lower".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `upper` function. + /// + /// let name = Expression("name") + /// name.uppercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `upper` function. + public var uppercaseString: Expression { + return "upper".wrap(self) + } + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// email.like("%@example.com") + /// // "email" LIKE '%@example.com' + /// email.like("99\\%@%", escape: "\\") + /// // "email" LIKE '99\%@%' ESCAPE '\' + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + @warn_unused_result public func like(pattern: String, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) + } + + /// Builds a copy of the expression appended with a `GLOB` query against the + /// given pattern. + /// + /// let path = Expression("path") + /// path.glob("*.png") + /// // "path" GLOB '*.png' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `GLOB` query against + /// the given pattern. + @warn_unused_result public func glob(pattern: String) -> Expression { + return "GLOB".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `MATCH` query against + /// the given pattern. + /// + /// let title = Expression("title") + /// title.match("swift AND programming") + /// // "title" MATCH 'swift AND programming' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `MATCH` query + /// against the given pattern. + @warn_unused_result public func match(pattern: String) -> Expression { + return "MATCH".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `REGEXP` query against + /// the given pattern. + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `REGEXP` query + /// against the given pattern. + @warn_unused_result public func regexp(pattern: String) -> Expression { + return "REGEXP".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `COLLATE` clause with + /// the given sequence. + /// + /// let name = Expression("name") + /// name.collate(.Nocase) + /// // "name" COLLATE NOCASE + /// + /// - Parameter collation: A collating sequence. + /// + /// - Returns: A copy of the expression appended with a `COLLATE` clause + /// with the given sequence. + @warn_unused_result public func collate(collation: Collation) -> Expression { + return "COLLATE".infix(self, collation) + } + + /// Builds a copy of the expression wrapped with the `ltrim` function. + /// + /// let name = Expression("name") + /// name.ltrim() + /// // ltrim("name") + /// name.ltrim([" ", "\t"]) + /// // ltrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `ltrim` function. + @warn_unused_result public func ltrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `rtrim` function. + /// + /// let name = Expression("name") + /// name.rtrim() + /// // rtrim("name") + /// name.rtrim([" ", "\t"]) + /// // rtrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `rtrim` function. + @warn_unused_result public func rtrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `trim` function. + /// + /// let name = Expression("name") + /// name.trim() + /// // trim("name") + /// name.trim([" ", "\t"]) + /// // trim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `trim` function. + @warn_unused_result public func trim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `replace` function. + /// + /// let email = Expression("email") + /// email.replace("@mac.com", with: "@icloud.com") + /// // replace("email", '@mac.com', '@icloud.com') + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - replacement: The replacement string. + /// + /// - Returns: A copy of the expression wrapped with the `replace` function. + @warn_unused_result public func replace(pattern: String, with replacement: String) -> Expression { + return "replace".wrap([self, pattern, replacement]) + } + + /// Builds a copy of the expression wrapped with the `substr` function. + /// + /// let title = Expression("title") + /// title.substr(-100) + /// // substr("title", -100) + /// title.substr(0, length: 100) + /// // substr("title", 0, 100) + /// + /// - Parameters: + /// + /// - location: The substring’s start index. + /// + /// - length: An optional substring length. + /// + /// - Returns: A copy of the expression wrapped with the `substr` function. + @warn_unused_result public func substring(location: Int, length: Int? = nil) -> Expression { + guard let length = length else { + return "substr".wrap([self, location]) + } + return "substr".wrap([self, location, length]) + } + + /// Builds a copy of the expression wrapped with the `substr` function. + /// + /// let title = Expression("title") + /// title[0..<100] + /// // substr("title", 0, 100) + /// + /// - Parameter range: The character index range of the substring. + /// + /// - Returns: A copy of the expression wrapped with the `substr` function. + public subscript(range: Range) -> Expression { + return substring(range.startIndex, length: range.endIndex - range.startIndex) + } + +} + +extension CollectionType where Generator.Element : Value, Index.Distance == Int { + + /// Builds a copy of the expression prepended with an `IN` check against the + /// collection. + /// + /// let name = Expression("name") + /// ["alice", "betty"].contains(name) + /// // "name" IN ('alice', 'betty') + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression prepended with an `IN` check against + /// the collection. + @warn_unused_result public func contains(expression: Expression) -> Expression { + let templates = ", ".join([String](count: count, repeatedValue: "?")) + return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + } + + /// Builds a copy of the expression prepended with an `IN` check against the + /// collection. + /// + /// let name = Expression("name") + /// ["alice", "betty"].contains(name) + /// // "name" IN ('alice', 'betty') + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression prepended with an `IN` check against + /// the collection. + @warn_unused_result public func contains(expression: Expression) -> Expression { + let templates = ", ".join([String](count: count, repeatedValue: "?")) + return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + } + +} + +/// Builds a copy of the given expressions wrapped with the `ifnull` function. +/// +/// let name = Expression("name") +/// name ?? "An Anonymous Coward" +/// // ifnull("name", 'An Anonymous Coward') +/// +/// - Parameters: +/// +/// - optional: An optional expression. +/// +/// - defaultValue: A fallback value for when the optional expression is +/// `nil`. +/// +/// - Returns: A copy of the given expressions wrapped with the `ifnull` +/// function. +public func ??(optional: Expression, defaultValue: V) -> Expression { + return "ifnull".wrap([optional, defaultValue]) +} + +/// Builds a copy of the given expressions wrapped with the `ifnull` function. +/// +/// let nick = Expression("nick") +/// let name = Expression("name") +/// nick ?? name +/// // ifnull("nick", "name") +/// +/// - Parameters: +/// +/// - optional: An optional expression. +/// +/// - defaultValue: A fallback expression for when the optional expression is +/// `nil`. +/// +/// - Returns: A copy of the given expressions wrapped with the `ifnull` +/// function. +public func ??(optional: Expression, defaultValue: Expression) -> Expression { + return "ifnull".wrap([optional, defaultValue]) +} + +/// Builds a copy of the given expressions wrapped with the `ifnull` function. +/// +/// let nick = Expression("nick") +/// let name = Expression("name") +/// nick ?? name +/// // ifnull("nick", "name") +/// +/// - Parameters: +/// +/// - optional: An optional expression. +/// +/// - defaultValue: A fallback expression for when the optional expression is +/// `nil`. +/// +/// - Returns: A copy of the given expressions wrapped with the `ifnull` +/// function. +public func ??(optional: Expression, defaultValue: Expression) -> Expression { + return "ifnull".wrap([optional, defaultValue]) +} diff --git a/Source/Typed/CustomFunctions.swift b/Source/Typed/CustomFunctions.swift new file mode 100644 index 00000000..068d0340 --- /dev/null +++ b/Source/Typed/CustomFunctions.swift @@ -0,0 +1,136 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public extension Connection { + + /// Creates or redefines a custom SQL function. + /// + /// - Parameters: + /// + /// - function: The name of the function to create or redefine. + /// + /// - deterministic: Whether or not the function is deterministic (_i.e._ + /// the function always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - block: A block of code to run when the function is called. + /// The assigned types must be explicit. + /// + /// - Returns: A closure returning an SQL expression to call the function. + public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z) throws -> (() -> Expression) { + let fn = try createFunction(function, 0, deterministic) { _ in block() } + return { fn([]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z?) throws -> (() -> Expression) { + let fn = try createFunction(function, 0, deterministic) { _ in block() } + return { fn([]) } + } + + // MARK: - + + public func createFunction(function: String, deterministic: Bool = false, _ block: A -> Z) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } + return { arg in fn([arg]) } + } + + public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } + return { arg in fn([arg]) } + } + + public func createFunction(function function: String, deterministic: Bool = false, _ block: A -> Z?) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } + return { arg in fn([arg]) } + } + + public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z?) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } + return { arg in fn([arg]) } + } + + // MARK: - + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + // MARK: - + + private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z) throws -> ([Expressible] -> Expression) { + createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in + block(arguments).datatypeValue + } + return { arguments in + function.quote().wrap(", ".join(arguments)) + } + } + + private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z?) throws -> ([Expressible] -> Expression) { + createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in + block(arguments)?.datatypeValue + } + return { arguments in + function.quote().wrap(", ".join(arguments)) + } + } + +} diff --git a/Source/Typed/Expression.swift b/Source/Typed/Expression.swift new file mode 100644 index 00000000..c69d88ac --- /dev/null +++ b/Source/Typed/Expression.swift @@ -0,0 +1,139 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol ExpressionType : Expressible { // extensions cannot have inheritance clauses + + typealias UnderlyingType = Void + + var template: String { get } + var bindings: [Binding?] { get } + + init(_ template: String, _ bindings: [Binding?]) + +} + +extension ExpressionType { + + public init(literal: String) { + self.init(literal, []) + } + + public init(_ identifier: String) { + self.init(literal: identifier.quote()) + } + + public init(_ expression: U) { + self.init(expression.template, expression.bindings) + } + +} + +/// An `Expression` represents a raw SQL fragment and any associated bindings. +public struct Expression : ExpressionType { + + public typealias UnderlyingType = Datatype + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public protocol Expressible { + + var expression: Expression { get } + +} + +extension Expressible { + + // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE + // FIXME: use @testable and make internal + public func asSQL() -> String { + let expressed = expression + var idx = 0 + return expressed.template.characters.reduce("") { template, character in + return template + (character == "?" ? transcode(expressed.bindings[idx++]) : String(character)) + } + } + +} + +extension ExpressionType { + + public var expression: Expression { + return Expression(template, bindings) + } + + public var asc: Expressible { + return " ".join([self, Expression(literal: "ASC")]) + } + + public var desc: Expressible { + return " ".join([self, Expression(literal: "DESC")]) + } + +} + +extension ExpressionType where UnderlyingType : Value { + + public init(value: UnderlyingType) { + self.init("?", [value.datatypeValue]) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Value { + + public static var null: Self { + return self.init(value: nil) + } + + public init(value: UnderlyingType.Wrapped?) { + self.init("?", [value?.datatypeValue]) + } + +} + +extension Value { + + public var expression: Expression { + return Expression(value: self).expression + } + +} + +public let rowid = Expression("ROWID") + +public func cast(expression: Expression) -> Expression { + return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) +} + +public func cast(expression: Expression) -> Expression { + return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) +} diff --git a/Source/Typed/Operators.swift b/Source/Typed/Operators.swift new file mode 100644 index 00000000..816560a5 --- /dev/null +++ b/Source/Typed/Operators.swift @@ -0,0 +1,541 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// TODO: use `@warn_unused_result` by the time operator functions support it + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: String) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: String) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: String, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: String, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} + +// MARK: - + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public prefix func -(rhs: Expression) -> Expression { + return wrap(rhs) +} +public prefix func -(rhs: Expression) -> Expression { + return wrap(rhs) +} + +// MARK: - + +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: V) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: V) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: V, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: V, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} + +public prefix func ~(rhs: Expression) -> Expression { + return wrap(rhs) +} +public prefix func ~(rhs: Expression) -> Expression { + return wrap(rhs) +} + +// MARK: - + +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: V) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: V?) -> Expression { + guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + return "=".infix(lhs, rhs) +} +public func ==(lhs: V, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: V?, rhs: Expression) -> Expression { + guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + return "=".infix(lhs, rhs) +} + +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: V?) -> Expression { + guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + return infix(lhs, rhs) +} +public func !=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: V?, rhs: Expression) -> Expression { + guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + return infix(lhs, rhs) +} + +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func ~=, V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +} +public func ~=, V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +} + +// MARK: - + +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Bool) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Bool) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Bool, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Bool, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} + +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Bool) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Bool) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Bool, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Bool, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} + +public prefix func !(rhs: Expression) -> Expression { + return "NOT ".wrap(rhs) +} +public prefix func !(rhs: Expression) -> Expression { + return "NOT ".wrap(rhs) +} diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift new file mode 100644 index 00000000..3a3e3e78 --- /dev/null +++ b/Source/Typed/Query.swift @@ -0,0 +1,1121 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol QueryType : Expressible { + + var clauses: QueryClauses { get set } + + init(_ name: String, database: String?) + +} + +public protocol SchemaType : QueryType { + + static var identifier: String { get } + +} + +extension SchemaType { + + /// Builds a copy of the query with the `SELECT` clause applied. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let email = Expression("email") + /// + /// users.select(id, email) + /// // SELECT "id", "email" FROM "users" + /// + /// - Parameter all: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT` clause applied. + public func select(column1: Expressible, _ column2: Expressible, _ more: Expressible...) -> Self { + return select(false, [column1, column2] + more) + } + + /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.select(distinct: email) + /// // SELECT DISTINCT "email" FROM "users" + /// + /// - Parameter columns: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT DISTINCT` clause applied. + public func select(distinct column1: Expressible, _ column2: Expressible, _ more: Expressible...) -> Self { + return select(true, [column1, column2] + more) + } + + /// Builds a copy of the query with the `SELECT` clause applied. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let email = Expression("email") + /// + /// users.select([id, email]) + /// // SELECT "id", "email" FROM "users" + /// + /// - Parameter all: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT` clause applied. + public func select(all: [Expressible]) -> Self { + return select(false, all) + } + + /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.select(distinct: [email]) + /// // SELECT DISTINCT "email" FROM "users" + /// + /// - Parameter columns: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT DISTINCT` clause applied. + public func select(distinct columns: [Expressible]) -> Self { + return select(true, columns) + } + + /// Builds a copy of the query with the `SELECT *` clause applied. + /// + /// let users = Table("users") + /// + /// users.select(*) + /// // SELECT * FROM "users" + /// + /// - Parameter star: A star literal. + /// + /// - Returns: A query with the given `SELECT *` clause applied. + public func select(star: Star) -> Self { + return select([star(nil, nil)]) + } + + /// Builds a copy of the query with the `SELECT DISTINCT *` clause applied. + /// + /// let users = Table("users") + /// + /// users.select(distinct: *) + /// // SELECT DISTINCT * FROM "users" + /// + /// - Parameter star: A star literal. + /// + /// - Returns: A query with the given `SELECT DISTINCT *` clause applied. + public func select(distinct star: Star) -> Self { + return select(distinct: [star(nil, nil)]) + } + + /// Builds a scalar copy of the query with the `SELECT` clause applied. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// + /// users.select(id) + /// // SELECT "id" FROM "users" + /// + /// - Parameter all: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT` clause applied. + public func select(column: Expression) -> ScalarQuery { + return select(false, [column]) + } + public func select(column: Expression) -> ScalarQuery { + return select(false, [column]) + } + + /// Builds a scalar copy of the query with the `SELECT DISTINCT` clause + /// applied. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.select(distinct: email) + /// // SELECT DISTINCT "email" FROM "users" + /// + /// - Parameter column: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT DISTINCT` clause applied. + public func select(distinct column: Expression) -> ScalarQuery { + return select(true, [column]) + } + public func select(distinct column: Expression) -> ScalarQuery { + return select(true, [column]) + } + + public var count: ScalarQuery { + return select(Expression.count(*)) + } + +} + +extension QueryType { + + private func select(distinct: Bool, _ columns: [Expressible]) -> Q { + var query = Q.init(clauses.from.name, database: clauses.from.database) + query.clauses = clauses + query.clauses.select = (distinct, columns) + return query + } + + // MARK: JOIN + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" INNER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(table: QueryType, on condition: Expression) -> Self { + return join(table, on: Expression(condition)) + } + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" INNER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(table: QueryType, on condition: Expression) -> Self { + return join(.Inner, table, on: condition) + } + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" LEFT OUTER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - type: The `JOIN` operator. + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(type: JoinType, _ table: QueryType, on condition: Expression) -> Self { + return join(type, table, on: Expression(condition)) + } + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" LEFT OUTER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - type: The `JOIN` operator. + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(type: JoinType, _ table: QueryType, on condition: Expression) -> Self { + var query = self + query.clauses.join.append((type: type, query: table, condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) + return query + } + + // MARK: WHERE + + /// Adds a condition to the query’s `WHERE` clause. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// + /// users.filter(id == 1) + /// // SELECT * FROM "users" WHERE ("id" = 1) + /// + /// - Parameter condition: A boolean expression to filter on. + /// + /// - Returns: A query with the given `WHERE` clause applied. + public func filter(predicate: Expression) -> Self { + return filter(Expression(predicate)) + } + + /// Adds a condition to the query’s `WHERE` clause. + /// + /// let users = Table("users") + /// let age = Expression("age") + /// + /// users.filter(age >= 35) + /// // SELECT * FROM "users" WHERE ("age" >= 35) + /// + /// - Parameter condition: A boolean expression to filter on. + /// + /// - Returns: A query with the given `WHERE` clause applied. + public func filter(predicate: Expression) -> Self { + var query = self + query.clauses.filters = query.clauses.filters.map { $0 && predicate } ?? predicate + return query + } + + // MARK: GROUP BY + + /// Sets a `GROUP BY` clause on the query. + /// + /// - Parameter by: A list of columns to group by. + /// + /// - Returns: A query with the given `GROUP BY` clause applied. + public func group(by: Expressible...) -> Self { + return group(by) + } + + /// Sets a `GROUP BY` clause on the query. + /// + /// - Parameter by: A list of columns to group by. + /// + /// - Returns: A query with the given `GROUP BY` clause applied. + public func group(by: [Expressible]) -> Self { + return group(by, nil) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A column to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: Expressible, having: Expression) -> Self { + return group([by], having: having) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A column to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: Expressible, having: Expression) -> Self { + return group([by], having: having) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A list of columns to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: [Expressible], having: Expression) -> Self { + return group(by, Expression(having)) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A list of columns to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: [Expressible], having: Expression) -> Self { + return group(by, having) + } + + private func group(by: [Expressible], _ having: Expression?) -> Self { + var query = self + query.clauses.group = (by, having) + return query + } + + // MARK: ORDER BY + + /// Sets an `ORDER BY` clause on the query. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// let email = Expression("name") + /// + /// users.order(email.desc, name.asc) + /// // SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC + /// + /// - Parameter by: An ordered list of columns and directions to sort by. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func order(by: Expressible...) -> Self { + var query = self + query.clauses.order = by + return query + } + + // MARK: LIMIT/OFFSET + + /// Sets the LIMIT clause (and resets any OFFSET clause) on the query. + /// + /// let users = Table("users") + /// + /// users.limit(20) + /// // SELECT * FROM "users" LIMIT 20 + /// + /// - Parameter length: The maximum number of rows to return (or `nil` to + /// return unlimited rows). + /// + /// - Returns: A query with the given LIMIT clause applied. + public func limit(length: Int?) -> Self { + return limit(length, nil) + } + + /// Sets LIMIT and OFFSET clauses on the query. + /// + /// let users = Table("users") + /// + /// users.limit(20, offset: 20) + /// // SELECT * FROM "users" LIMIT 20 OFFSET 20 + /// + /// - Parameters: + /// + /// - length: The maximum number of rows to return. + /// + /// - offset: The number of rows to skip. + /// + /// - Returns: A query with the given LIMIT and OFFSET clauses applied. + public func limit(length: Int, offset: Int) -> Self { + return limit(length, offset) + } + + // prevents limit(nil, offset: 5) + private func limit(length: Int?, _ offset: Int?) -> Self { + var query = self + query.clauses.limit = length.map { ($0, offset) } + return query + } + + // MARK: - Clauses + // + // MARK: SELECT + + // MARK: - + + private var selectClause: Expressible { + return " ".join([ + Expression(literal: clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), + ", ".join(clauses.select.columns), + Expression(literal: "FROM"), + tableName(alias: true) + ]) + } + + private var joinClause: Expressible? { + guard !clauses.join.isEmpty else { + return nil + } + + return " ".join(clauses.join.map { type, query, condition in + " ".join([ + Expression(literal: "\(type.rawValue) JOIN"), + query.tableName(alias: true), + Expression(literal: "ON"), + condition + ]) + }) + } + + private var whereClause: Expressible? { + guard let filters = clauses.filters else { + return nil + } + + return " ".join([ + Expression(literal: "WHERE"), + filters + ]) + } + + private var groupByClause: Expressible? { + guard let group = clauses.group else { + return nil + } + + let groupByClause = " ".join([ + Expression(literal: "GROUP BY"), + ", ".join(group.by) + ]) + + guard let having = group.having else { + return groupByClause + } + + return " ".join([ + groupByClause, + " ".join([ + Expression(literal: "HAVING"), + having + ]) + ]) + } + + private var orderClause: Expressible? { + guard !clauses.order.isEmpty else { + return nil + } + + return " ".join([ + Expression(literal: "ORDER BY"), + ", ".join(clauses.order) + ]) + } + + private var limitOffsetClause: Expressible? { + guard let limit = clauses.limit else { + return nil + } + + let limitClause = Expression(literal: "LIMIT \(limit.length)") + + guard let offset = limit.offset else { + return limitClause + } + + return " ".join([ + limitClause, + Expression(literal: "OFFSET \(offset)") + ]) + } + + // MARK: - + + public func alias(aliasName: String) -> Self { + var query = self + query.clauses.from = (clauses.from.name, aliasName, clauses.from.database) + return query + } + + // MARK: - Operations + // + // MARK: INSERT + + public func insert(value: Setter, _ more: Setter...) -> Insert { + return insert([value] + more) + } + + public func insert(values: [Setter]) -> Insert { + return insert(nil, values) + } + + public func insert(or onConflict: OnConflict, _ values: Setter...) -> Insert { + return insert(or: onConflict, values) + } + + public func insert(or onConflict: OnConflict, _ values: [Setter]) -> Insert { + return insert(onConflict, values) + } + + private func insert(or: OnConflict?, _ values: [Setter]) -> Insert { + let insert = values.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in + (insert.columns + [setter.column], insert.values + [setter.value]) + } + + let clauses: [Expressible?] = [ + Expression(literal: "INSERT"), + or.map { Expression(literal: "OR \($0.rawValue)") }, + Expression(literal: "INTO"), + tableName(), + "".wrap(insert.columns) as Expression, + Expression(literal: "VALUES"), + "".wrap(insert.values) as Expression, + whereClause + ] + + return Insert(" ".join(clauses.flatMap { $0 }).expression) + } + + /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. + public func insert() -> Insert { + return Insert(" ".join([ + Expression(literal: "INSERT INTO"), + tableName(), + Expression(literal: "DEFAULT VALUES") + ]).expression) + } + + /// Runs an `INSERT` statement against the query with the results of another + /// query. + /// + /// - Parameter query: A query to `SELECT` results from. + /// + /// - Returns: The number of updated rows and statement. + public func insert(query: QueryType) -> Update { + return Update(" ".join([ + Expression(literal: "INSERT INTO"), + tableName(), + query.expression + ]).expression) + } + + // MARK: UPDATE + + public func update(values: Setter...) -> Update { + return update(values) + } + + public func update(values: [Setter]) -> Update { + let clauses: [Expressible?] = [ + Expression(literal: "UPDATE"), + tableName(), + Expression(literal: "SET"), + ", ".join(values.map { " = ".join([$0.column, $0.value]) }), + whereClause + ] + + return Update(" ".join(clauses.flatMap { $0 }).expression) + } + + // MARK: DELETE + + public func delete() -> Delete { + let clauses: [Expressible?] = [ + Expression(literal: "DELETE FROM"), + tableName(), + whereClause + ] + + return Delete(" ".join(clauses.flatMap { $0 }).expression) + } + + // MARK: EXISTS + + public var exists: Select { + return Select(" ".join([ + Expression(literal: "SELECT EXISTS"), + "".wrap(expression) as Expression + ]).expression) + } + + // MARK: - + + /// Prefixes a column expression with the query’s table name or alias. + /// + /// - Parameter column: A column expression. + /// + /// - Returns: A column expression namespaced with the query’s table name or + /// alias. + public func namespace(column: Expression) -> Expression { + return Expression(".".join([tableName(), column]).expression) + } + + // FIXME: rdar://problem/18673897 // subscript… + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + /// Prefixes a star with the query’s table name or alias. + /// + /// - Parameter star: A literal `*`. + /// + /// - Returns: A `*` expression namespaced with the query’s table name or + /// alias. + public subscript(star: Star) -> Expression { + return namespace(star(nil, nil)) + } + + // MARK: - + + // TODO: alias support + func tableName(alias aliased: Bool = false) -> Expressible { + guard let alias = clauses.from.alias where aliased else { + return database(namespace: clauses.from.alias ?? clauses.from.name) + } + + return " ".join([ + database(namespace: clauses.from.name), + Expression(literal: "AS"), + Expression(alias) + ]) + } + + func database(namespace name: String) -> Expressible { + let name = Expression(name) + + guard let database = clauses.from.database else { + return name + } + + return ".".join([Expression(database), name]) + } + + public var expression: Expression { + let clauses: [Expressible?] = [ + selectClause, + joinClause, + whereClause, + groupByClause, + orderClause, + limitOffsetClause + ] + + return " ".join(clauses.flatMap { $0 }).expression + } + +} + +// TODO: decide: simplify the below with a boxed type instead + +/// Queries a collection of chainable helper functions and expressions to build +/// executable SQL statements. +public struct Table : SchemaType { + + public static let identifier = "TABLE" + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, alias: nil, database: database) + } + +} + +public struct View : SchemaType { + + public static let identifier = "VIEW" + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, alias: nil, database: database) + } + +} + +public struct VirtualTable : SchemaType { + + public static let identifier = "VIRTUAL TABLE" + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, alias: nil, database: database) + } + +} + +// TODO: make `ScalarQuery` work in `QueryType.select()`, `.filter()`, etc. + +public struct ScalarQuery : QueryType { + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, alias: nil, database: database) + } + +} + +// TODO: decide: simplify the below with a boxed type instead + +public struct Select : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public struct Insert : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public struct Update : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public struct Delete : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +extension Connection { + + public func prepare(query: QueryType) -> AnySequence { + let expression = query.expression + let statement = prepare(expression.template, expression.bindings) + + let columnNames: [String: Int] = { + var (columnNames, idx) = ([String: Int](), 0) + column: for each in query.clauses.select.columns ?? [Expression(literal: "*")] { + var names = each.expression.template.characters.split { $0 == "." }.map(String.init) + let column = names.removeLast() + let namespace = ".".join(names) + + func expandGlob(namespace: Bool) -> QueryType -> Void { + return { query in + var q = query.dynamicType.init(query.clauses.from.name, database: query.clauses.from.database) + q.clauses.select = query.clauses.select + let e = q.expression + var names = self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } + if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } + for name in names { columnNames[name] = idx++ } + } + } + + if column == "*" { + var select = query + select.clauses.select = (false, [Expression(literal: "*") as Expressible]) + let queries = [select] + query.clauses.join.map { $0.query } + if !namespace.isEmpty { + for q in queries { + if q.tableName().expression.template == namespace { + expandGlob(true)(q) + continue column + } + } + fatalError("no such table: \(namespace)") + } + for q in queries { + expandGlob(query.clauses.join.count > 0)(q) + } + continue + } + + columnNames[each.expression.template] = idx++ + } + return columnNames + }() + + return AnySequence { + anyGenerator { statement.next().map { Row(columnNames, $0) } } + } + } + + public func scalar(query: ScalarQuery) -> V { + let expression = query.expression + return value(scalar(expression.template, expression.bindings)) + } + + public func scalar(query: ScalarQuery) -> V.ValueType? { + let expression = query.expression + guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + return V.fromDatatypeValue(value) + } + + public func scalar(query: Select) -> V { + let expression = query.expression + return value(scalar(expression.template, expression.bindings)) + } + + public func scalar(query: Select) -> V.ValueType? { + let expression = query.expression + guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + return V.fromDatatypeValue(value) + } + + public func pluck(query: QueryType) -> Row? { + return prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + } + + /// Runs an `Insert` query. + /// + /// - SeeAlso: `QueryType.insert(value:_:)` + /// - SeeAlso: `QueryType.insert(values:)` + /// - SeeAlso: `QueryType.insert(or:_:)` + /// - SeeAlso: `QueryType.insert()` + /// + /// - Parameter query: An insert query. + /// + /// - Returns: The insert’s rowid. + public func run(query: Insert) throws -> Int64 { + let expression = query.expression + return try sync { + try self.run(expression.template, expression.bindings) + return self.lastInsertRowid! + } + } + + /// Runs an `Update` query. + /// + /// - SeeAlso: `QueryType.insert(query:)` + /// - SeeAlso: `QueryType.update(values:)` + /// + /// - Parameter query: An update query. + /// + /// - Returns: The number of updated rows. + public func run(query: Update) throws -> Int { + let expression = query.expression + return try sync { + try self.run(expression.template, expression.bindings) + return self.changes + } + } + + /// Runs a `Delete` query. + /// + /// - SeeAlso: `QueryType.delete()` + /// + /// - Parameter query: A delete query. + /// + /// - Returns: The number of deleted rows. + public func run(query: Delete) throws -> Int { + let expression = query.expression + return try sync { + try self.run(expression.template, expression.bindings) + return self.changes + } + } + +} + +public struct Row { + + private let columnNames: [String: Int] + + private let values: [Binding?] + + private init(_ columnNames: [String: Int], _ values: [Binding?]) { + self.columnNames = columnNames + self.values = values + } + + /// Returns a row’s value for the given column. + /// + /// - Parameter column: An expression representing a column selected in a Query. + /// + /// - Returns: The value for the given column. + public func get(column: Expression) -> V { + return get(Expression(column))! + } + public func get(column: Expression) -> V? { + func valueAtIndex(idx: Int) -> V? { + if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } + return nil + } + + if let idx = columnNames[column.template] { return valueAtIndex(idx) } + + let similar = Array(columnNames.keys).filter { $0.hasSuffix(".\(column.template)") } + if similar.count == 1 { return valueAtIndex(columnNames[similar[0]]!) } + + if similar.count > 1 { + fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + } + fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sort())") + } + + // FIXME: rdar://problem/18673897 // subscript… + + public subscript(column: Expression) -> Blob { + return get(column) + } + public subscript(column: Expression) -> Blob? { + return get(column) + } + + public subscript(column: Expression) -> Bool { + return get(column) + } + public subscript(column: Expression) -> Bool? { + return get(column) + } + + public subscript(column: Expression) -> Double { + return get(column) + } + public subscript(column: Expression) -> Double? { + return get(column) + } + + public subscript(column: Expression) -> Int { + return get(column) + } + public subscript(column: Expression) -> Int? { + return get(column) + } + + public subscript(column: Expression) -> Int64 { + return get(column) + } + public subscript(column: Expression) -> Int64? { + return get(column) + } + + public subscript(column: Expression) -> String { + return get(column) + } + public subscript(column: Expression) -> String? { + return get(column) + } + +} + +/// Determines the join operator for a query’s `JOIN` clause. +public enum JoinType : String { + + /// A `CROSS` join. + case Cross = "CROSS" + + /// An `INNER` join. + case Inner = "INNER" + + /// A `LEFT OUTER` join. + case LeftOuter = "LEFT OUTER" + +} + +/// ON CONFLICT resolutions. +public enum OnConflict: String { + + case Replace = "REPLACE" + + case Rollback = "ROLLBACK" + + case Abort = "ABORT" + + case Fail = "FAIL" + + case Ignore = "IGNORE" + +} + +// MARK: - Private + +public struct QueryClauses { + + var select = (distinct: false, columns: [Expression(literal: "*") as Expressible]) + + var from: (name: String, alias: String?, database: String?) + + var join = [(type: JoinType, query: QueryType, condition: Expressible)]() + + var filters: Expression? + + var group: (by: [Expressible], having: Expression?)? + + var order = [Expressible]() + + var limit: (length: Int, offset: Int?)? + + private init(_ name: String, alias: String?, database: String?) { + self.from = (name, alias, database) + } + +} diff --git a/Source/Typed/Schema.swift b/Source/Typed/Schema.swift new file mode 100644 index 00000000..d588685c --- /dev/null +++ b/Source/Typed/Schema.swift @@ -0,0 +1,513 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension SchemaType { + + // MARK: - DROP TABLE / VIEW / VIRTUAL TABLE + + public func drop(ifExists ifExists: Bool = false) -> String { + return drop(Self.identifier, tableName(), ifExists) + } + +} + +extension Table { + + // MARK: - CREATE TABLE + + public func create(temporary temporary: Bool = false, ifNotExists: Bool = false, @noescape block: TableBuilder -> Void) -> String { + let builder = TableBuilder() + + block(builder) + + let clauses: [Expressible?] = [ + create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + "".wrap(builder.definitions) as Expression + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + public func create(query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + Expression(literal: "AS"), + query + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + // MARK: - ALTER TABLE … ADD COLUMN + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V? = nil) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + } + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + } + + private func addColumn(expression: Expressible) -> String { + return " ".join([ + Expression(literal: "ALTER TABLE"), + tableName(), + Expression(literal: "ADD COLUMN"), + expression + ]).asSQL() + } + + // MARK: - ALTER TABLE … RENAME TO + + public func rename(to: Table) -> String { + return rename(to: to) + } + + // MARK: - CREATE INDEX + + public func createIndex(columns: Expressible...) -> String { + return createIndex(columns) + } + + public func createIndex(columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create("INDEX", indexName(columns), unique ? .Unique : nil, ifNotExists), + Expression(literal: "ON"), + tableName(), + "".wrap(columns) as Expression + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + // MARK: - DROP INDEX + + public func dropIndex(columns: Expressible...) -> String { + return dropIndex(columns) + } + + public func dropIndex(columns: [Expressible], ifExists: Bool = false) -> String { + return drop("INDEX", indexName(columns), ifExists) + } + + private func indexName(columns: [Expressible]) -> Expressible { + let string = " ".join(["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).lowercaseString + + let index = string.characters.reduce("") { underscored, character in + guard character != "\"" else { + return underscored + } + guard "a"..."z" ~= character || "0"..."9" ~= character else { + return underscored + "_" + } + return underscored + String(character) + } + + return database(namespace: index) + } + +} + +extension View { + + // MARK: - CREATE VIEW + + public func create(query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create(View.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + Expression(literal: "AS"), + query + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + +} + +extension VirtualTable { + + // MARK: - CREATE VIRTUAL TABLE + + public func create(using: Module, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create(VirtualTable.identifier, tableName(), nil, ifNotExists), + Expression(literal: "USING"), + using + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + // MARK: - ALTER TABLE … RENAME TO + + public func rename(to: VirtualTable) -> String { + return rename(to: to) + } + +} + +public final class TableBuilder { + + private var definitions = [Expressible]() + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, primaryKey: Bool, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey ? .Default : nil, false, false, check, defaultValue, nil, nil) + } + + public func column(name: Expression, primaryKey: Bool, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey ? .Default : nil, false, false, check, defaultValue, nil, nil) + } + + public func column(name: Expression, primaryKey: PrimaryKey, check: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) + } + + public func column(name: Expression, primaryKey: PrimaryKey, check: Expression) { + column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + private func column(name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { + definitions.append(definition(name, datatype, primaryKey, null, unique, check, defaultValue, references, collate)) + } + + // MARK: - + + public func primaryKey(column: Expression) { + primaryKey([column]) + } + + public func primaryKey(compositeA: Expression, _ b: Expression) { + primaryKey([compositeA, b]) + } + + public func primaryKey(compositeA: Expression, _ b: Expression, _ c: Expression) { + primaryKey([compositeA, b, c]) + } + + private func primaryKey(composite: [Expressible]) { + definitions.append("PRIMARY KEY".prefix(composite)) + } + + public func unique(columns: Expressible...) { + unique(columns) + } + + public func unique(columns: [Expressible]) { + definitions.append("UNIQUE".prefix(columns)) + } + + public func check(condition: Expression) { + check(Expression(condition)) + } + + public func check(condition: Expression) { + definitions.append("CHECK".prefix(condition)) + } + + public enum Dependency: String { + + case NoAction = "NO ACTION" + + case Restrict = "RESTRICT" + + case SetNull = "SET NULL" + + case SetDefault = "SET DEFAULT" + + case Cascade = "CASCADE" + + } + + public func foreignKey(column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + foreignKey(column, (table, other), update, delete) + } + + public func foreignKey(column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + foreignKey(column, (table, other), update, delete) + } + + public func foreignKey(composite: (Expression, Expression), references table: QueryType, _ other: (Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + let composite = ", ".join([composite.0, composite.1]) + let references = (table, ", ".join([other.0, other.1])) + + foreignKey(composite, references, update, delete) + } + + public func foreignKey(composite: (Expression, Expression, Expression), references table: QueryType, _ other: (Expression, Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + let composite = ", ".join([composite.0, composite.1, composite.2]) + let references = (table, ", ".join([other.0, other.1, other.2])) + + foreignKey(composite, references, update, delete) + } + + private func foreignKey(column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) { + let clauses: [Expressible?] = [ + "FOREIGN KEY".prefix(column), + reference(references), + update.map { Expression(literal: "ON UPDATE \($0.rawValue)") }, + delete.map { Expression(literal: "ON DELETE \($0.rawValue)") } + ] + + definitions.append(" ".join(clauses.flatMap { $0 })) + } + +} + +public enum PrimaryKey { + + case Default + + case Autoincrement + +} + +public struct Module { + + private let name: String + + private let arguments: [Expressible] + + public init(_ name: String, _ arguments: [Expressible]) { + self.init(name: name.quote(), arguments: arguments) + } + + init(name: String, arguments: [Expressible]) { + self.name = name + self.arguments = arguments + } + +} + +extension Module : Expressible { + + public var expression: Expression { + return name.wrap(arguments) + } + +} + +// MARK: - Private + +private extension QueryType { + + func create(identifier: String, _ name: Expressible, _ modifier: Modifier?, _ ifNotExists: Bool) -> Expressible { + let clauses: [Expressible?] = [ + Expression(literal: "CREATE"), + modifier.map { Expression(literal: $0.rawValue) }, + Expression(literal: identifier), + ifNotExists ? Expression(literal: "IF NOT EXISTS") : nil, + name + ] + + return " ".join(clauses.flatMap { $0 }) + } + + func rename(to to: Self) -> String { + return " ".join([ + Expression(literal: "ALTER TABLE"), + tableName(), + Expression(literal: "RENAME TO"), + Expression(to.clauses.from.name) + ]).asSQL() + } + + func drop(identifier: String, _ name: Expressible, _ ifExists: Bool) -> String { + let clauses: [Expressible?] = [ + Expression(literal: "DROP \(identifier)"), + ifExists ? Expression(literal: "IF EXISTS") : nil, + name + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + +} + +private func definition(column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { + let clauses: [Expressible?] = [ + column, + Expression(literal: datatype), + primaryKey.map { Expression(literal: $0 == .Autoincrement ? "PRIMARY KEY AUTOINCREMENT" : "PRIMARY KEY") }, + null ? nil : Expression(literal: "NOT NULL"), + unique ? Expression(literal: "UNIQUE") : nil, + check.map { " ".join([Expression(literal: "CHECK"), $0]) }, + defaultValue.map { "DEFAULT".prefix($0) }, + references.map(reference), + collate.map { " ".join([Expression(literal: "COLLATE"), $0]) } + ] + + return " ".join(clauses.flatMap { $0 }) +} + +private func reference(primary: (QueryType, Expressible)) -> Expressible { + return " ".join([ + Expression(literal: "REFERENCES"), + primary.0.tableName(), + "".wrap(primary.1) as Expression + ]) +} + +private enum Modifier : String { + + case Unique = "UNIQUE" + + case Temporary = "TEMPORARY" + +} diff --git a/Source/Typed/Setter.swift b/Source/Typed/Setter.swift new file mode 100644 index 00000000..d8bf775a --- /dev/null +++ b/Source/Typed/Setter.swift @@ -0,0 +1,275 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +infix operator <- { + associativity left + precedence 135 + assignment +} + +public struct Setter { + + let column: Expressible + let value: Expressible + + private init(column: Expression, value: Expression) { + self.column = column + self.value = value + } + + private init(column: Expression, value: V) { + self.column = column + self.value = value + } + + private init(column: Expression, value: Expression) { + self.column = column + self.value = value + } + + private init(column: Expression, value: Expression) { + self.column = column + self.value = value + } + + private init(column: Expression, value: V?) { + self.column = column + self.value = Expression(value: value) + } + +} + +extension Setter : Expressible { + + public var expression: Expression { + return "=".infix(column, value, wrap: false) + } + +} + +public func <-(column: Expression, value: Expression) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: V) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: Expression) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: Expression) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: V?) -> Setter { + return Setter(column: column, value: value) +} + +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: String) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: String) -> Setter { + return column <- column + value +} + +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: V) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: V) -> Setter { + return column <- column + value +} + +public func -=(column: Expression, value: Expression) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: V) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: Expression) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: Expression) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: V) -> Setter { + return column <- column - value +} + +public func *=(column: Expression, value: Expression) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: V) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: Expression) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: Expression) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: V) -> Setter { + return column <- column * value +} + +public func /=(column: Expression, value: Expression) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: V) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: Expression) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: Expression) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: V) -> Setter { + return column <- column / value +} + +public func %=(column: Expression, value: Expression) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: V) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: Expression) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: Expression) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: V) -> Setter { + return column <- column % value +} + +public func <<=(column: Expression, value: Expression) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: V) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: Expression) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: Expression) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: V) -> Setter { + return column <- column << value +} + +public func >>=(column: Expression, value: Expression) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: V) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: Expression) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: Expression) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: V) -> Setter { + return column <- column >> value +} + +public func &=(column: Expression, value: Expression) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: V) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: Expression) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: Expression) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: V) -> Setter { + return column <- column & value +} + +public func |=(column: Expression, value: Expression) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: V) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: Expression) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: Expression) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: V) -> Setter { + return column <- column | value +} + +public func ^=(column: Expression, value: Expression) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: V) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: Expression) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: Expression) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: V) -> Setter { + return column <- column ^ value +} + +public postfix func ++(column: Expression) -> Setter { + return Expression(column) += 1 +} +public postfix func ++(column: Expression) -> Setter { + return Expression(column) += 1 +} + +public postfix func --(column: Expression) -> Setter { + return Expression(column) -= 1 +} +public postfix func --(column: Expression) -> Setter { + return Expression(column) -= 1 +} diff --git a/Supporting Files/Base.xcconfig b/Supporting Files/Base.xcconfig new file mode 100644 index 00000000..bc31c8a3 --- /dev/null +++ b/Supporting Files/Base.xcconfig @@ -0,0 +1,5 @@ +INFOPLIST_FILE = Supporting Files/$(PRODUCT_NAME)/Info.plist + +PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.$(PRODUCT_NAME:c99extidentifier) + +APPLICATION_EXTENSION_API_ONLY = YES diff --git a/SQLite/Info.plist b/Supporting Files/SQLite/Info.plist similarity index 79% rename from SQLite/Info.plist rename to Supporting Files/SQLite/Info.plist index 9e2c8628..d3de8eef 100644 --- a/SQLite/Info.plist +++ b/Supporting Files/SQLite/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.stephencelis.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,13 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.1 + 1.0 CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2014–2015 Stephen Celis. NSPrincipalClass diff --git a/SQLite/SQLite.h b/Supporting Files/SQLite/SQLite.h similarity index 55% rename from SQLite/SQLite.h rename to Supporting Files/SQLite/SQLite.h index 418bb477..f6fa54c8 100644 --- a/SQLite/SQLite.h +++ b/Supporting Files/SQLite/SQLite.h @@ -1,9 +1,9 @@ @import Foundation; //! Project version number for SQLite. -FOUNDATION_EXPORT double SQLite_VersionNumber; +FOUNDATION_EXPORT double SQLiteVersionNumber; //! Project version string for SQLite. -FOUNDATION_EXPORT const unsigned char SQLite_VersionString[]; +FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; #import diff --git a/Supporting Files/SQLite/SQLite.xcconfig b/Supporting Files/SQLite/SQLite.xcconfig new file mode 100644 index 00000000..05f740a1 --- /dev/null +++ b/Supporting Files/SQLite/SQLite.xcconfig @@ -0,0 +1,5 @@ +#include "../Base.xcconfig" + +PRODUCT_NAME = SQLite + +OTHER_LDFLAGS = -lsqlite3 diff --git a/SQLite/module.modulemap b/Supporting Files/SQLite/module.modulemap similarity index 100% rename from SQLite/module.modulemap rename to Supporting Files/SQLite/module.modulemap diff --git a/Supporting Files/SQLiteCipher/Info.plist b/Supporting Files/SQLiteCipher/Info.plist new file mode 100644 index 00000000..d3de8eef --- /dev/null +++ b/Supporting Files/SQLiteCipher/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Supporting Files/SQLiteCipher/SQLiteCipher.h b/Supporting Files/SQLiteCipher/SQLiteCipher.h new file mode 100644 index 00000000..f34ba14a --- /dev/null +++ b/Supporting Files/SQLiteCipher/SQLiteCipher.h @@ -0,0 +1,9 @@ +@import Foundation; + +//! Project version number for SQLite. +FOUNDATION_EXPORT double SQLiteVersionNumber; + +//! Project version string for SQLite. +FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; + +#import diff --git a/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig b/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig new file mode 100644 index 00000000..4af62038 --- /dev/null +++ b/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig @@ -0,0 +1,5 @@ +#include "../Base.xcconfig" + +PRODUCT_NAME = SQLiteCipher + +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) SQLITE_HAS_CODEC=1 diff --git a/Supporting Files/SQLiteCipher/module.modulemap b/Supporting Files/SQLiteCipher/module.modulemap new file mode 100644 index 00000000..0169f9bf --- /dev/null +++ b/Supporting Files/SQLiteCipher/module.modulemap @@ -0,0 +1,12 @@ +framework module SQLiteCipher { + umbrella header "SQLiteCipher.h" + + // Load the SDK header alongside SQLite.swift. Alternate headers: + // + // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + + export * + module * { export * } +} diff --git a/Supporting Files/Test.xcconfig b/Supporting Files/Test.xcconfig new file mode 100644 index 00000000..40e5c38b --- /dev/null +++ b/Supporting Files/Test.xcconfig @@ -0,0 +1 @@ +INFOPLIST_FILE = Tests/Info.plist diff --git a/Supporting Files/podspec.rb b/Supporting Files/podspec.rb new file mode 100644 index 00000000..33edda9e --- /dev/null +++ b/Supporting Files/podspec.rb @@ -0,0 +1,25 @@ +$podspec_version = '1.0.0.pre' +$podspec_source_git = 'https://github.com/stephencelis/SQLite.swift.git' + +def apply_shared_config spec, name + spec.version = $podspec_version + spec.source = { git: $podspec_source_git, tag: $podspec_version } + + spec.homepage = 'https://github.com/stephencelis/SQLite.swift' + spec.license = { type: 'MIT', file: 'LICENSE.txt' } + + spec.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } + spec.social_media_url = 'https://twitter.com/stephencelis' + + # TODO: separate SQLiteCipher.swift and share `SQLite` module name + # spec.module_name = 'SQLite' + # spec.module_map = 'Supporting Files/module.modulemap' + spec.module_name = name + spec.module_map = "Supporting Files/#{name}/module.modulemap" + + spec.source_files = [ + "Supporting Files/#{name}/#{name}.h", 'Source/**/*.{swift,c,h,m}' + ] + + spec.private_header_files = 'Source/Core/fts3_tokenizer.h' +end diff --git a/Tests/AggregateFunctionsTests.swift b/Tests/AggregateFunctionsTests.swift new file mode 100644 index 00000000..52d635a0 --- /dev/null +++ b/Tests/AggregateFunctionsTests.swift @@ -0,0 +1,64 @@ +import XCTest +import SQLite + +class AggregateFunctionsTests : XCTestCase { + + func test_distinct_prependsExpressionsWithDistinctKeyword() { + AssertSQL("DISTINCT \"int\"", int.distinct) + AssertSQL("DISTINCT \"intOptional\"", intOptional.distinct) + AssertSQL("DISTINCT \"double\"", double.distinct) + AssertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct) + AssertSQL("DISTINCT \"string\"", string.distinct) + AssertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct) + } + + func test_count_wrapsOptionalExpressionsWithCountFunction() { + AssertSQL("count(\"intOptional\")", intOptional.count) + AssertSQL("count(\"doubleOptional\")", doubleOptional.count) + AssertSQL("count(\"stringOptional\")", stringOptional.count) + } + + func test_max_wrapsComparableExpressionsWithMaxFunction() { + AssertSQL("max(\"int\")", int.max) + AssertSQL("max(\"intOptional\")", intOptional.max) + AssertSQL("max(\"double\")", double.max) + AssertSQL("max(\"doubleOptional\")", doubleOptional.max) + AssertSQL("max(\"string\")", string.max) + AssertSQL("max(\"stringOptional\")", stringOptional.max) + } + + func test_min_wrapsComparableExpressionsWithMinFunction() { + AssertSQL("min(\"int\")", int.min) + AssertSQL("min(\"intOptional\")", intOptional.min) + AssertSQL("min(\"double\")", double.min) + AssertSQL("min(\"doubleOptional\")", doubleOptional.min) + AssertSQL("min(\"string\")", string.min) + AssertSQL("min(\"stringOptional\")", stringOptional.min) + } + + func test_average_wrapsNumericExpressionsWithAvgFunction() { + AssertSQL("avg(\"int\")", int.average) + AssertSQL("avg(\"intOptional\")", intOptional.average) + AssertSQL("avg(\"double\")", double.average) + AssertSQL("avg(\"doubleOptional\")", doubleOptional.average) + } + + func test_sum_wrapsNumericExpressionsWithSumFunction() { + AssertSQL("sum(\"int\")", int.sum) + AssertSQL("sum(\"intOptional\")", intOptional.sum) + AssertSQL("sum(\"double\")", double.sum) + AssertSQL("sum(\"doubleOptional\")", doubleOptional.sum) + } + + func test_total_wrapsNumericExpressionsWithTotalFunction() { + AssertSQL("total(\"int\")", int.total) + AssertSQL("total(\"intOptional\")", intOptional.total) + AssertSQL("total(\"double\")", double.total) + AssertSQL("total(\"doubleOptional\")", doubleOptional.total) + } + + func test_count_withStar_wrapsStarWithCountFunction() { + AssertSQL("count(*)", count(*)) + } + +} diff --git a/Tests/BlobTests.swift b/Tests/BlobTests.swift new file mode 100644 index 00000000..ba0a7c54 --- /dev/null +++ b/Tests/BlobTests.swift @@ -0,0 +1,12 @@ +import XCTest +import SQLite + +class BlobTests : XCTestCase { + + func test_toHex() { + let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255]) + + XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") + } + +} diff --git a/Tests/CipherTests.swift b/Tests/CipherTests.swift new file mode 100644 index 00000000..78e3ac1b --- /dev/null +++ b/Tests/CipherTests.swift @@ -0,0 +1,26 @@ +import XCTest +import SQLiteCipher + +class CipherTests: XCTestCase { + + let db = try! Connection() + + override func setUp() { + try! db.key("hello") + + try! db.run("CREATE TABLE foo (bar TEXT)") + try! db.run("INSERT INTO foo (bar) VALUES ('world')") + + super.setUp() + } + + func test_key() { + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as! Int64) + } + + func test_rekey() { + try! db.rekey("goodbye") + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as! Int64) + } + +} diff --git a/Tests/ConnectionTests.swift b/Tests/ConnectionTests.swift new file mode 100644 index 00000000..1b614d2b --- /dev/null +++ b/Tests/ConnectionTests.swift @@ -0,0 +1,317 @@ +import XCTest +import SQLite + +class ConnectionTests : SQLiteTestCase { + + override func setUp() { + super.setUp() + + CreateUsersTable() + } + + func test_init_withInMemory_returnsInMemoryConnection() { + let db = try! Connection(.InMemory) + XCTAssertEqual("", db.description) + } + + func test_init_returnsInMemoryByDefault() { + let db = try! Connection() + XCTAssertEqual("", db.description) + } + + func test_init_withTemporary_returnsTemporaryConnection() { + let db = try! Connection(.Temporary) + XCTAssertEqual("", db.description) + } + + func test_init_withURI_returnsURIConnection() { + let db = try! Connection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + } + + func test_init_withString_returnsURIConnection() { + let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + } + + func test_readonly_returnsFalseOnReadWriteConnections() { + XCTAssertFalse(db.readonly) + } + + func test_readonly_returnsTrueOnReadOnlyConnections() { + let db = try! Connection(readonly: true) + XCTAssertTrue(db.readonly) + } + + func test_lastInsertRowid_returnsNilOnNewConnections() { + XCTAssert(db.lastInsertRowid == nil) + } + + func test_lastInsertRowid_returnsLastIdAfterInserts() { + try! InsertUser("alice") + XCTAssertEqual(1, db.lastInsertRowid!) + } + + func test_changes_returnsZeroOnNewConnections() { + XCTAssertEqual(0, db.changes) + } + + func test_changes_returnsNumberOfChanges() { + try! InsertUser("alice") + XCTAssertEqual(1, db.changes) + try! InsertUser("betsy") + XCTAssertEqual(1, db.changes) + } + + func test_totalChanges_returnsTotalNumberOfChanges() { + XCTAssertEqual(0, db.totalChanges) + try! InsertUser("alice") + XCTAssertEqual(1, db.totalChanges) + try! InsertUser("betsy") + XCTAssertEqual(2, db.totalChanges) + } + + func test_prepare_preparesAndReturnsStatements() { + _ = db.prepare("SELECT * FROM users WHERE admin = 0") + _ = db.prepare("SELECT * FROM users WHERE admin = ?", 0) + _ = db.prepare("SELECT * FROM users WHERE admin = ?", [0]) + _ = db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + } + + func test_run_preparesRunsAndReturnsStatements() { + try! db.run("SELECT * FROM users WHERE admin = 0") + try! db.run("SELECT * FROM users WHERE admin = ?", 0) + try! db.run("SELECT * FROM users WHERE admin = ?", [0]) + try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + AssertSQL("SELECT * FROM users WHERE admin = 0", 4) + } + + func test_scalar_preparesRunsAndReturnsScalarValues() { + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) + } + + func test_transaction_executesBeginDeferred() { + try! db.transaction(.Deferred) {} + + AssertSQL("BEGIN DEFERRED TRANSACTION") + } + + func test_transaction_executesBeginImmediate() { + try! db.transaction(.Immediate) {} + + AssertSQL("BEGIN IMMEDIATE TRANSACTION") + } + + func test_transaction_executesBeginExclusive() { + try! db.transaction(.Exclusive) {} + + AssertSQL("BEGIN EXCLUSIVE TRANSACTION") + } + + func test_transaction_beginsAndCommitsTransactions() { + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + + try! db.transaction { + try stmt.run() + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + AssertSQL("COMMIT TRANSACTION") + AssertSQL("ROLLBACK TRANSACTION", 0) + } + + func test_transaction_beginsAndRollsTransactionsBack() { + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + + do { + try db.transaction { + try stmt.run() + try stmt.run() + } + } catch { + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + AssertSQL("ROLLBACK TRANSACTION") + AssertSQL("COMMIT TRANSACTION", 0) + } + + func test_savepoint_beginsAndCommitsSavepoints() { + let db = self.db + + try! db.savepoint("1") { + try db.savepoint("2") { + try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") + } + } + + AssertSQL("SAVEPOINT '1'") + AssertSQL("SAVEPOINT '2'") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + AssertSQL("RELEASE SAVEPOINT '2'") + AssertSQL("RELEASE SAVEPOINT '1'") + AssertSQL("ROLLBACK TO SAVEPOINT '2'", 0) + AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) + } + + func test_savepoint_beginsAndRollsSavepointsBack() { + let db = self.db + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + + do { + try db.savepoint("1") { + try db.savepoint("2") { + try stmt.run() + try stmt.run() + try stmt.run() + } + try db.savepoint("2") { + try stmt.run() + try stmt.run() + try stmt.run() + } + } + } catch { + } + + AssertSQL("SAVEPOINT '1'") + AssertSQL("SAVEPOINT '2'") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + AssertSQL("ROLLBACK TO SAVEPOINT '2'") + AssertSQL("ROLLBACK TO SAVEPOINT '1'") + AssertSQL("RELEASE SAVEPOINT '2'", 0) + AssertSQL("RELEASE SAVEPOINT '1'", 0) + } + + func test_updateHook_setsUpdateHook_withInsert() { + async { done in + db.updateHook { operation, db, table, rowid in + XCTAssertEqual(Operation.Insert, operation) + XCTAssertEqual("main", db) + XCTAssertEqual("users", table) + XCTAssertEqual(1, rowid) + done() + } + try! InsertUser("alice") + } + } + + func test_updateHook_setsUpdateHook_withUpdate() { + try! InsertUser("alice") + async { done in + db.updateHook { operation, db, table, rowid in + XCTAssertEqual(Operation.Update, operation) + XCTAssertEqual("main", db) + XCTAssertEqual("users", table) + XCTAssertEqual(1, rowid) + done() + } + try! db.run("UPDATE users SET email = 'alice@example.com'") + } + } + + func test_updateHook_setsUpdateHook_withDelete() { + try! InsertUser("alice") + async { done in + db.updateHook { operation, db, table, rowid in + XCTAssertEqual(Operation.Delete, operation) + XCTAssertEqual("main", db) + XCTAssertEqual("users", table) + XCTAssertEqual(1, rowid) + done() + } + try! db.run("DELETE FROM users WHERE id = 1") + } + } + + func test_commitHook_setsCommitHook() { + async { done in + db.commitHook { + done() + } + try! db.transaction { + try InsertUser("alice") + } + XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) + } + } + + func test_rollbackHook_setsRollbackHook() { + async { done in + db.rollbackHook(done) + do { + try db.transaction { + try self.InsertUser("alice") + try self.InsertUser("alice") // throw + } + } catch { + } + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + } + } + + func test_commitHook_withRollback_rollsBack() { + async { done in + db.commitHook { + throw NSError(domain: "com.stephencelis.SQLiteTests", code: 1, userInfo: nil) + } + db.rollbackHook(done) + do { + try db.transaction { + try self.InsertUser("alice") + } + } catch { + } + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + } + } + + func test_createFunction_withArrayArguments() { + db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } + + XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as? String) + XCTAssert(db.scalar("SELECT hello(NULL)") == nil) + } + + func test_createFunction_createsQuotableFunction() { + db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } + + XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) + } + + func test_createCollation_createsCollation() { + db.createCollation("NODIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + } + + func test_createCollation_createsQuotableCollation() { + db.createCollation("NO DIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + } + + func test_interrupt_interruptsLongRunningQuery() { + try! InsertUsers("abcdefghijklmnopqrstuvwxyz".characters.map { String($0) }) + db.createFunction("sleep") { args in + usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) + return nil + } + + let stmt = db.prepare("SELECT *, sleep(?) FROM users", 0.1) + try! stmt.run() + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) + AssertThrows(try stmt.run()) + } + +} diff --git a/Tests/CoreFunctionsTests.swift b/Tests/CoreFunctionsTests.swift new file mode 100644 index 00000000..8f7460d5 --- /dev/null +++ b/Tests/CoreFunctionsTests.swift @@ -0,0 +1,136 @@ +import XCTest +import SQLite + +class CoreFunctionsTests : XCTestCase { + + func test_round_wrapsDoubleExpressionsWithRoundFunction() { + AssertSQL("round(\"double\")", double.round()) + AssertSQL("round(\"doubleOptional\")", doubleOptional.round()) + + AssertSQL("round(\"double\", 1)", double.round(1)) + AssertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2)) + } + + func test_random_generatesExpressionWithRandomFunction() { + AssertSQL("random()", Expression.random()) + AssertSQL("random()", Expression.random()) + } + + func test_length_wrapsStringExpressionWithLengthFunction() { + AssertSQL("length(\"string\")", string.length) + AssertSQL("length(\"stringOptional\")", stringOptional.length) + } + + func test_lowercaseString_wrapsStringExpressionWithLowerFunction() { + AssertSQL("lower(\"string\")", string.lowercaseString) + AssertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString) + } + + func test_uppercaseString_wrapsStringExpressionWithUpperFunction() { + AssertSQL("upper(\"string\")", string.uppercaseString) + AssertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString) + } + + func test_like_buildsExpressionWithLikeOperator() { + AssertSQL("(\"string\" LIKE 'a%')", string.like("a%")) + AssertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%")) + + AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) + AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) + } + + func test_glob_buildsExpressionWithGlobOperator() { + AssertSQL("(\"string\" GLOB 'a*')", string.glob("a*")) + AssertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*")) + } + + func test_match_buildsExpressionWithMatchOperator() { + AssertSQL("(\"string\" MATCH 'a*')", string.match("a*")) + AssertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*")) + } + + func test_regexp_buildsExpressionWithRegexpOperator() { + AssertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$")) + AssertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$")) + } + + func test_collate_buildsExpressionWithCollateOperator() { + AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.Binary)) + AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.Nocase)) + AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.Rtrim)) + AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.Custom("CUSTOM"))) + + AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.Binary)) + AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.Nocase)) + AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.Rtrim)) + AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.Custom("CUSTOM"))) + } + + func test_ltrim_wrapsStringWithLtrimFunction() { + AssertSQL("ltrim(\"string\")", string.ltrim()) + AssertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim()) + + AssertSQL("ltrim(\"string\", ' ')", string.ltrim([" "])) + AssertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "])) + } + + func test_ltrim_wrapsStringWithRtrimFunction() { + AssertSQL("rtrim(\"string\")", string.rtrim()) + AssertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim()) + + AssertSQL("rtrim(\"string\", ' ')", string.rtrim([" "])) + AssertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "])) + } + + func test_ltrim_wrapsStringWithTrimFunction() { + AssertSQL("trim(\"string\")", string.trim()) + AssertSQL("trim(\"stringOptional\")", stringOptional.trim()) + + AssertSQL("trim(\"string\", ' ')", string.trim([" "])) + AssertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "])) + } + + func test_replace_wrapsStringWithReplaceFunction() { + AssertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net")) + AssertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com")) + } + + func test_substring_wrapsStringWithSubstrFunction() { + AssertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2)) + AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1)) + } + + func test_subscriptWithRange_wrapsStringWithSubstrFunction() { + AssertSQL("substr(\"string\", 1, 2)", string[1..<3]) + AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3]) + } + + func test_nilCoalescingOperator_wrapsOptionalsWithIfnullFunction() { + AssertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1) + // AssertSQL("ifnull(\"doubleOptional\", 1.0)", doubleOptional ?? 1) // rdar://problem/21677256 + XCTAssertEqual("ifnull(\"doubleOptional\", 1.0)", (doubleOptional ?? 1).asSQL()) + AssertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal") + + AssertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int) + AssertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double) + AssertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string) + + AssertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional) + AssertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional) + AssertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional) + } + + func test_absoluteValue_wrapsNumberWithAbsFucntion() { + AssertSQL("abs(\"int\")", int.absoluteValue) + AssertSQL("abs(\"intOptional\")", intOptional.absoluteValue) + + AssertSQL("abs(\"double\")", double.absoluteValue) + AssertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue) + } + + func test_contains_buildsExpressionWithInOperator() { + AssertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) + AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) + } + +} diff --git a/Tests/CustomFunctionsTests.swift b/Tests/CustomFunctionsTests.swift new file mode 100644 index 00000000..67150ccf --- /dev/null +++ b/Tests/CustomFunctionsTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class CustomFunctionsTests : XCTestCase { + +} diff --git a/Tests/ExpressionTests.swift b/Tests/ExpressionTests.swift new file mode 100644 index 00000000..036e10ce --- /dev/null +++ b/Tests/ExpressionTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class ExpressionTests : XCTestCase { + +} diff --git a/Tests/FTS4Tests.swift b/Tests/FTS4Tests.swift new file mode 100644 index 00000000..a94d9073 --- /dev/null +++ b/Tests/FTS4Tests.swift @@ -0,0 +1,77 @@ +import XCTest +import SQLite + +class FTS4Tests : XCTestCase { + + func test_create_onVirtualTable_withFTS4_compilesCreateVirtualTableExpression() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4()", + virtualTable.create(.FTS4()) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\")", + virtualTable.create(.FTS4(string)) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=simple)", + virtualTable.create(.FTS4(tokenize: .Simple)) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", tokenize=porter)", + virtualTable.create(.FTS4([string], tokenize: .Porter)) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=0\")", + virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false))) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"]))) + ) + } + + func test_match_onVirtualTableAsExpression_compilesMatchExpression() { + AssertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression) + AssertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) + AssertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) + } + + func test_match_onVirtualTableAsQueryType_compilesMatchExpression() { + AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", virtualTable.match("string") as QueryType) + AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as QueryType) + AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as QueryType) + } + +} + +class FTS4IntegrationTests : SQLiteTestCase { + + func test_registerTokenizer_registersTokenizer() { + let emails = VirtualTable("emails") + let subject = Expression("subject") + let body = Expression("body") + + let locale = CFLocaleCopyCurrent() + let tokenizerName = "tokenizer" + let tokenizer = CFStringTokenizerCreate(nil, "", CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + try! db.registerTokenizer(tokenizerName) { string in + CFStringTokenizerSetString(tokenizer, string, CFRangeMake(0, CFStringGetLength(string))) + if CFStringTokenizerAdvanceToNextToken(tokenizer) == .None { + return nil + } + let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) + let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string, range) + let token = CFStringCreateMutableCopy(nil, range.length, input) + CFStringLowercase(token, locale) + CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false) + return (token as String, string.rangeOfString(input as String)!) + } + + try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) + AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") + + try! db.run(emails.insert(subject <- "Aún más cáfe!")) + XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) + } + +} diff --git a/SQLite Tests/Info.plist b/Tests/Info.plist similarity index 90% rename from SQLite Tests/Info.plist rename to Tests/Info.plist index 00b831da..ba72822e 100644 --- a/SQLite Tests/Info.plist +++ b/Tests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.stephencelis.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Tests/OperatorsTests.swift b/Tests/OperatorsTests.swift new file mode 100644 index 00000000..86aa34ed --- /dev/null +++ b/Tests/OperatorsTests.swift @@ -0,0 +1,286 @@ +import XCTest +import SQLite + +class OperatorsTests : XCTestCase { + + func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { + AssertSQL("(\"string\" || \"string\")", string + string) + AssertSQL("(\"string\" || \"stringOptional\")", string + stringOptional) + AssertSQL("(\"stringOptional\" || \"string\")", stringOptional + string) + AssertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional) + AssertSQL("(\"string\" || 'literal')", string + "literal") + AssertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal") + AssertSQL("('literal' || \"string\")", "literal" + string) + AssertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional) + } + + func test_numberExpression_plusNumberExpression_buildsAdditiveNumberExpression() { + AssertSQL("(\"int\" + \"int\")", int + int) + AssertSQL("(\"int\" + \"intOptional\")", int + intOptional) + AssertSQL("(\"intOptional\" + \"int\")", intOptional + int) + AssertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional) + AssertSQL("(\"int\" + 1)", int + 1) + AssertSQL("(\"intOptional\" + 1)", intOptional + 1) + AssertSQL("(1 + \"int\")", 1 + int) + AssertSQL("(1 + \"intOptional\")", 1 + intOptional) + + AssertSQL("(\"double\" + \"double\")", double + double) + AssertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional) + AssertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double) + AssertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional) + AssertSQL("(\"double\" + 1.0)", double + 1) + AssertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1) + AssertSQL("(1.0 + \"double\")", 1 + double) + AssertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional) + } + + func test_numberExpression_minusNumberExpression_buildsSubtractiveNumberExpression() { + AssertSQL("(\"int\" - \"int\")", int - int) + AssertSQL("(\"int\" - \"intOptional\")", int - intOptional) + AssertSQL("(\"intOptional\" - \"int\")", intOptional - int) + AssertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional) + AssertSQL("(\"int\" - 1)", int - 1) + AssertSQL("(\"intOptional\" - 1)", intOptional - 1) + AssertSQL("(1 - \"int\")", 1 - int) + AssertSQL("(1 - \"intOptional\")", 1 - intOptional) + + AssertSQL("(\"double\" - \"double\")", double - double) + AssertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional) + AssertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double) + AssertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional) + AssertSQL("(\"double\" - 1.0)", double - 1) + AssertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1) + AssertSQL("(1.0 - \"double\")", 1 - double) + AssertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional) + } + + func test_numberExpression_timesNumberExpression_buildsMultiplicativeNumberExpression() { + AssertSQL("(\"int\" * \"int\")", int * int) + AssertSQL("(\"int\" * \"intOptional\")", int * intOptional) + AssertSQL("(\"intOptional\" * \"int\")", intOptional * int) + AssertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional) + AssertSQL("(\"int\" * 1)", int * 1) + AssertSQL("(\"intOptional\" * 1)", intOptional * 1) + AssertSQL("(1 * \"int\")", 1 * int) + AssertSQL("(1 * \"intOptional\")", 1 * intOptional) + + AssertSQL("(\"double\" * \"double\")", double * double) + AssertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional) + AssertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double) + AssertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional) + AssertSQL("(\"double\" * 1.0)", double * 1) + AssertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1) + AssertSQL("(1.0 * \"double\")", 1 * double) + AssertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional) + } + + func test_numberExpression_dividedByNumberExpression_buildsDivisiveNumberExpression() { + AssertSQL("(\"int\" / \"int\")", int / int) + AssertSQL("(\"int\" / \"intOptional\")", int / intOptional) + AssertSQL("(\"intOptional\" / \"int\")", intOptional / int) + AssertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional) + AssertSQL("(\"int\" / 1)", int / 1) + AssertSQL("(\"intOptional\" / 1)", intOptional / 1) + AssertSQL("(1 / \"int\")", 1 / int) + AssertSQL("(1 / \"intOptional\")", 1 / intOptional) + + AssertSQL("(\"double\" / \"double\")", double / double) + AssertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional) + AssertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double) + AssertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional) + AssertSQL("(\"double\" / 1.0)", double / 1) + AssertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1) + AssertSQL("(1.0 / \"double\")", 1 / double) + AssertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional) + } + + func test_numberExpression_prefixedWithMinus_buildsInvertedNumberExpression() { + AssertSQL("-(\"int\")", -int) + AssertSQL("-(\"intOptional\")", -intOptional) + + AssertSQL("-(\"double\")", -double) + AssertSQL("-(\"doubleOptional\")", -doubleOptional) + } + + func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { + AssertSQL("(\"int\" % \"int\")", int % int) + AssertSQL("(\"int\" % \"intOptional\")", int % intOptional) + AssertSQL("(\"intOptional\" % \"int\")", intOptional % int) + AssertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional) + AssertSQL("(\"int\" % 1)", int % 1) + AssertSQL("(\"intOptional\" % 1)", intOptional % 1) + AssertSQL("(1 % \"int\")", 1 % int) + AssertSQL("(1 % \"intOptional\")", 1 % intOptional) + } + + func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { + AssertSQL("(\"int\" << \"int\")", int << int) + AssertSQL("(\"int\" << \"intOptional\")", int << intOptional) + AssertSQL("(\"intOptional\" << \"int\")", intOptional << int) + AssertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional) + AssertSQL("(\"int\" << 1)", int << 1) + AssertSQL("(\"intOptional\" << 1)", intOptional << 1) + AssertSQL("(1 << \"int\")", 1 << int) + AssertSQL("(1 << \"intOptional\")", 1 << intOptional) + } + + func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { + AssertSQL("(\"int\" >> \"int\")", int >> int) + AssertSQL("(\"int\" >> \"intOptional\")", int >> intOptional) + AssertSQL("(\"intOptional\" >> \"int\")", intOptional >> int) + AssertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional) + AssertSQL("(\"int\" >> 1)", int >> 1) + AssertSQL("(\"intOptional\" >> 1)", intOptional >> 1) + AssertSQL("(1 >> \"int\")", 1 >> int) + AssertSQL("(1 >> \"intOptional\")", 1 >> intOptional) + } + + func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { + AssertSQL("(\"int\" & \"int\")", int & int) + AssertSQL("(\"int\" & \"intOptional\")", int & intOptional) + AssertSQL("(\"intOptional\" & \"int\")", intOptional & int) + AssertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional) + AssertSQL("(\"int\" & 1)", int & 1) + AssertSQL("(\"intOptional\" & 1)", intOptional & 1) + AssertSQL("(1 & \"int\")", 1 & int) + AssertSQL("(1 & \"intOptional\")", 1 & intOptional) + } + + func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { + AssertSQL("(\"int\" | \"int\")", int | int) + AssertSQL("(\"int\" | \"intOptional\")", int | intOptional) + AssertSQL("(\"intOptional\" | \"int\")", intOptional | int) + AssertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional) + AssertSQL("(\"int\" | 1)", int | 1) + AssertSQL("(\"intOptional\" | 1)", intOptional | 1) + AssertSQL("(1 | \"int\")", 1 | int) + AssertSQL("(1 | \"intOptional\")", 1 | intOptional) + } + + func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { + AssertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int) + AssertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional) + AssertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int) + AssertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional) + AssertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1) + AssertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1) + AssertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int) + AssertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional) + } + + func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { + AssertSQL("~(\"int\")", ~int) + AssertSQL("~(\"intOptional\")", ~intOptional) + } + + func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" = \"bool\")", bool == bool) + AssertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional) + AssertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool) + AssertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional) + AssertSQL("(\"bool\" = 1)", bool == true) + AssertSQL("(\"boolOptional\" = 1)", boolOptional == true) + AssertSQL("(1 = \"bool\")", true == bool) + AssertSQL("(1 = \"boolOptional\")", true == boolOptional) + + AssertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil) + AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) + } + + func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" != \"bool\")", bool != bool) + AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) + AssertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool) + AssertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional) + AssertSQL("(\"bool\" != 1)", bool != true) + AssertSQL("(\"boolOptional\" != 1)", boolOptional != true) + AssertSQL("(1 != \"bool\")", true != bool) + AssertSQL("(1 != \"boolOptional\")", true != boolOptional) + + AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil) + AssertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional) + } + + func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" > \"bool\")", bool > bool) + AssertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional) + AssertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool) + AssertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional) + AssertSQL("(\"bool\" > 1)", bool > true) + AssertSQL("(\"boolOptional\" > 1)", boolOptional > true) + AssertSQL("(1 > \"bool\")", true > bool) + AssertSQL("(1 > \"boolOptional\")", true > boolOptional) + } + + func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" >= \"bool\")", bool >= bool) + AssertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional) + AssertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool) + AssertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional) + AssertSQL("(\"bool\" >= 1)", bool >= true) + AssertSQL("(\"boolOptional\" >= 1)", boolOptional >= true) + AssertSQL("(1 >= \"bool\")", true >= bool) + AssertSQL("(1 >= \"boolOptional\")", true >= boolOptional) + } + + func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" < \"bool\")", bool < bool) + AssertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional) + AssertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool) + AssertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional) + AssertSQL("(\"bool\" < 1)", bool < true) + AssertSQL("(\"boolOptional\" < 1)", boolOptional < true) + AssertSQL("(1 < \"bool\")", true < bool) + AssertSQL("(1 < \"boolOptional\")", true < boolOptional) + } + + func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" <= \"bool\")", bool <= bool) + AssertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional) + AssertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool) + AssertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional) + AssertSQL("(\"bool\" <= 1)", bool <= true) + AssertSQL("(\"boolOptional\" <= 1)", boolOptional <= true) + AssertSQL("(1 <= \"bool\")", true <= bool) + AssertSQL("(1 <= \"boolOptional\")", true <= boolOptional) + } + + func test_patternMatchingOperator_withComparableInterval_buildsBetweenBooleanExpression() { + AssertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) + AssertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) + } + + func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" AND \"bool\")", bool && bool) + AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) + AssertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool) + AssertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional) + AssertSQL("(\"bool\" AND 1)", bool && true) + AssertSQL("(\"boolOptional\" AND 1)", boolOptional && true) + AssertSQL("(1 AND \"bool\")", true && bool) + AssertSQL("(1 AND \"boolOptional\")", true && boolOptional) + } + + func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" OR \"bool\")", bool || bool) + AssertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional) + AssertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool) + AssertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional) + AssertSQL("(\"bool\" OR 1)", bool || true) + AssertSQL("(\"boolOptional\" OR 1)", boolOptional || true) + AssertSQL("(1 OR \"bool\")", true || bool) + AssertSQL("(1 OR \"boolOptional\")", true || boolOptional) + } + + func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { + AssertSQL("NOT (\"bool\")", !bool) + AssertSQL("NOT (\"boolOptional\")", !boolOptional) + } + + func test_precedencePreserved() { + let n = Expression(value: 1) + AssertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) + AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) + } + +} \ No newline at end of file diff --git a/Tests/QueryTests.swift b/Tests/QueryTests.swift new file mode 100644 index 00000000..c6f8b016 --- /dev/null +++ b/Tests/QueryTests.swift @@ -0,0 +1,324 @@ +import XCTest +import SQLite + +class QueryTests : XCTestCase { + + let users = Table("users") + let id = Expression("id") + let email = Expression("email") + let age = Expression("age") + let admin = Expression("admin") + + let posts = Table("posts") + let userId = Expression("user_id") + let categoryId = Expression("category_id") + let published = Expression("published") + + let categories = Table("categories") + let tag = Expression("tag") + + func test_select_withExpression_compilesSelectClause() { + AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) + } + + func test_select_withVariadicExpressions_compilesSelectClause() { + AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) + } + + func test_select_withExpressions_compilesSelectClause() { + AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select([email, count(*)])) + } + + func test_selectDistinct_withExpression_compilesSelectClause() { + AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) + } + + func test_selectDistinct_withExpressions_compilesSelectClause() { + AssertSQL("SELECT DISTINCT \"age\", \"admin\" FROM \"users\"", users.select(distinct: [age, admin])) + } + + func test_selectDistinct_withStar_compilesSelectClause() { + AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) + } + + func test_join_compilesJoinClause() { + AssertSQL( + "SELECT * FROM \"users\" INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", + users.join(posts, on: posts[userId] == users[id]) + ) + } + + func test_join_withExplicitType_compilesJoinClauseWithType() { + AssertSQL( + "SELECT * FROM \"users\" LEFT OUTER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", + users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + ) + + AssertSQL( + "SELECT * FROM \"users\" CROSS JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", + users.join(.Cross, posts, on: posts[userId] == users[id]) + ) + } + + func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { + AssertSQL( + "SELECT * FROM \"users\" INNER JOIN \"posts\" ON ((\"posts\".\"user_id\" = \"users\".\"id\") AND \"published\")", + users.join(posts.filter(published), on: posts[userId] == users[id]) + ) + } + + func test_join_whenChained_compilesAggregateJoinClause() { + AssertSQL( + "SELECT * FROM \"users\" " + + "INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\") " + + "INNER JOIN \"categories\" ON (\"categories\".\"id\" = \"posts\".\"category_id\")", + users.join(posts, on: posts[userId] == users[id]).join(categories, on: categories[id] == posts[categoryId]) + ) + } + + func test_filter_compilesWhereClause() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) + } + + func test_filter_whenChained_compilesAggregateWhereClause() { + AssertSQL( + "SELECT * FROM \"users\" WHERE ((\"age\" >= 35) AND \"admin\")", + users.filter(age >= 35).filter(admin) + ) + } + + func test_group_withSingleExpressionName_compilesGroupClause() { + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", + users.group(age)) + } + + func test_group_withVariadicExpressionNames_compilesGroupClause() { + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) + } + + func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING \"admin\"", users.group(age, having: admin)) + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) + } + + func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { + AssertSQL( + "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING \"admin\"", + users.group([age, admin], having: admin) + ) + AssertSQL( + "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", + users.group([age, admin], having: age >= 30) + ) + } + + func test_order_withSingleExpressionName_compilesOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) + } + + func test_order_withVariadicExpressionNames_compilesOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) + } + + func test_order_withExpressionAndSortDirection_compilesOrderClause() { +// AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc)) + } + + func test_order_whenChained_resetsOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) + } + + func test_reverse_withoutOrder_ordersByRowIdDescending() { +// AssertSQL("SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC", users.reverse()) + } + + func test_reverse_withOrder_reversesOrder() { +// AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age, email.desc).reverse()) + } + + func test_limit_compilesLimitClause() { + AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) + } + + func test_limit_withOffset_compilesOffsetClause() { + AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) + } + + func test_limit_whenChained_overridesLimit() { + let query = users.limit(5) + + AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) + AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + } + + func test_limit_whenChained_withOffset_overridesOffset() { + let query = users.limit(5, offset: 5) + + AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20)) + AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + } + + func test_alias_aliasesTable() { + let managerId = Expression("manager_id") + + let managers = users.alias("managers") + + AssertSQL( + "SELECT * FROM \"users\" " + + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")", + users.join(managers, on: managers[id] == users[managerId]) + ) + } + + func test_insert_compilesInsertExpression() { + AssertSQL( + "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", + users.insert(email <- "alice@example.com", age <- 30) + ) + } + + func test_insert_withOnConflict_compilesInsertOrOnConflictExpression() { + AssertSQL( + "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", + users.insert(or: .Replace, email <- "alice@example.com", age <- 30) + ) + } + + func test_insert_compilesInsertExpressionWithDefaultValues() { + AssertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert()) + } + + func test_insert_withQuery_compilesInsertExpressionWithSelectStatement() { + let emails = Table("emails") + + AssertSQL( + "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE \"admin\"", + emails.insert(users.select(email).filter(admin)) + ) + } + + func test_update_compilesUpdateExpression() { + AssertSQL( + "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", + users.filter(id == 1).update(age <- 30, admin <- true) + ) + } + + func test_delete_compilesDeleteExpression() { + AssertSQL( + "DELETE FROM \"users\" WHERE (\"id\" = 1)", + users.filter(id == 1).delete() + ) + } + + func test_delete_compilesExistsExpression() { + AssertSQL( + "SELECT EXISTS (SELECT * FROM \"users\")", + users.exists + ) + } + + func test_count_returnsCountExpression() { + AssertSQL("SELECT count(*) FROM \"users\"", users.count) + } + + func test_scalar_returnsScalarExpression() { + AssertSQL("SELECT \"int\" FROM \"table\"", table.select(int) as ScalarQuery) + AssertSQL("SELECT \"intOptional\" FROM \"table\"", table.select(intOptional) as ScalarQuery) + AssertSQL("SELECT DISTINCT \"int\" FROM \"table\"", table.select(distinct: int) as ScalarQuery) + AssertSQL("SELECT DISTINCT \"intOptional\" FROM \"table\"", table.select(distinct: intOptional) as ScalarQuery) + } + + func test_subscript_withExpression_returnsNamespacedExpression() { + let query = Table("query") + + AssertSQL("\"query\".\"blob\"", query[data]) + AssertSQL("\"query\".\"blobOptional\"", query[dataOptional]) + + AssertSQL("\"query\".\"bool\"", query[bool]) + AssertSQL("\"query\".\"boolOptional\"", query[boolOptional]) + + AssertSQL("\"query\".\"date\"", query[date]) + AssertSQL("\"query\".\"dateOptional\"", query[dateOptional]) + + AssertSQL("\"query\".\"double\"", query[double]) + AssertSQL("\"query\".\"doubleOptional\"", query[doubleOptional]) + + AssertSQL("\"query\".\"int\"", query[int]) + AssertSQL("\"query\".\"intOptional\"", query[intOptional]) + + AssertSQL("\"query\".\"int64\"", query[int64]) + AssertSQL("\"query\".\"int64Optional\"", query[int64Optional]) + + AssertSQL("\"query\".\"string\"", query[string]) + AssertSQL("\"query\".\"stringOptional\"", query[stringOptional]) + + AssertSQL("\"query\".*", query[*]) + } + + func test_tableNamespacedByDatabase() { + let table = Table("table", database: "attached") + + AssertSQL("SELECT * FROM \"attached\".\"table\"", table) + } + +} + +class QueryIntegrationTests : SQLiteTestCase { + + let id = Expression("id") + let email = Expression("email") + + override func setUp() { + super.setUp() + + CreateUsersTable() + } + + // MARK: - + + func test_select() { + for _ in db.prepare(users) { + // FIXME + } + + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + user[users[managerId]] + } + } + + func test_scalar() { + XCTAssertEqual(0, db.scalar(users.count)) + XCTAssertEqual(false, db.scalar(users.exists)) + + try! InsertUsers("alice") + XCTAssertEqual(1, db.scalar(users.select(id.average))) + } + + func test_pluck() { + let rowid = try! db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(rowid, db.pluck(users)![id]) + } + + func test_insert() { + let id = try! db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(1, id) + } + + func test_update() { + let changes = try! db.run(users.update(email <- "alice@example.com")) + XCTAssertEqual(0, changes) + } + + func test_delete() { + let changes = try! db.run(users.delete()) + XCTAssertEqual(0, changes) + } + +} diff --git a/Tests/R*TreeTests.swift b/Tests/R*TreeTests.swift new file mode 100644 index 00000000..7147533e --- /dev/null +++ b/Tests/R*TreeTests.swift @@ -0,0 +1,17 @@ +import XCTest +import SQLite + +class RTreeTests : XCTestCase { + + func test_create_onVirtualTable_withRTree_createVirtualTableExpression() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING rtree(\"int64\", \"double\", \"double\")", + virtualTable.create(.RTree(int64, (double, double))) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING rtree(\"int64\", \"double\", \"double\", \"double\", \"double\")", + virtualTable.create(.RTree(int64, (double, double), (double, double))) + ) + } + +} \ No newline at end of file diff --git a/Tests/SchemaTests.swift b/Tests/SchemaTests.swift new file mode 100644 index 00000000..a269aee6 --- /dev/null +++ b/Tests/SchemaTests.swift @@ -0,0 +1,767 @@ +import XCTest +import SQLite + +class SchemaTests : XCTestCase { + + func test_drop_compilesDropTableExpression() { + XCTAssertEqual("DROP TABLE \"table\"", table.drop()) + XCTAssertEqual("DROP TABLE IF EXISTS \"table\"", table.drop(ifExists: true)) + } + + func test_drop_compilesDropVirtualTableExpression() { + XCTAssertEqual("DROP VIRTUAL TABLE \"virtual_table\"", virtualTable.drop()) + XCTAssertEqual("DROP VIRTUAL TABLE IF EXISTS \"virtual_table\"", virtualTable.drop(ifExists: true)) + } + + func test_drop_compilesDropViewExpression() { + XCTAssertEqual("DROP VIEW \"view\"", _view.drop()) + XCTAssertEqual("DROP VIEW IF EXISTS \"view\"", _view.drop(ifExists: true)) + } + + func test_create_withBuilder_compilesCreateTableExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (" + + "\"blob\" BLOB NOT NULL, " + + "\"blobOptional\" BLOB, " + + "\"double\" REAL NOT NULL, " + + "\"doubleOptional\" REAL, " + + "\"int64\" INTEGER NOT NULL, " + + "\"int64Optional\" INTEGER, " + + "\"string\" TEXT NOT NULL, " + + "\"stringOptional\" TEXT" + + ")", + table.create { t in + t.column(data) + t.column(dataOptional) + t.column(double) + t.column(doubleOptional) + t.column(int64) + t.column(int64Optional) + t.column(string) + t.column(stringOptional) + } + ) + XCTAssertEqual( + "CREATE TEMPORARY TABLE \"table\" (\"int64\" INTEGER NOT NULL)", + table.create(temporary: true) { $0.column(int64) } + ) + XCTAssertEqual( + "CREATE TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)", + table.create(ifNotExists: true) { $0.column(int64) } + ) + XCTAssertEqual( + "CREATE TEMPORARY TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)", + table.create(temporary: true, ifNotExists: true) { $0.column(int64) } + ) + } + + func test_create_withQuery_compilesCreateTableExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" AS SELECT \"int64\" FROM \"view\"", + table.create(_view.select(int64)) + ) + } + + // thoroughness test for ambiguity + func test_column_compilesColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL)", + table.create { t in t.column(int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE)", + table.create { t in t.column(int64, unique: true) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL DEFAULT (\"int64\"))", + table.create { t in t.column(int64, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL DEFAULT (0))", + table.create { t in t.column(int64, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE DEFAULT (\"int64\"))", + table.create { t in t.column(int64, unique: true, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE DEFAULT (0))", + table.create { t in t.column(int64, unique: true, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, unique: true, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, check: int64Optional > 0, defaultValue: 0) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL DEFAULT (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0, defaultValue: int64) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER)", + table.create { t in t.column(int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE)", + table.create { t in t.column(int64Optional, unique: true) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0))", + table.create { t in t.column(int64Optional, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64Optional, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (0))", + table.create { t in t.column(int64Optional, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, unique: true, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (0))", + table.create { t in t.column(int64Optional, unique: true, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: 0) } + ) + } + + func test_column_withIntegerExpression_compilesPrimaryKeyAutoincrementColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + table.create { t in t.column(int64, primaryKey: .Autoincrement) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, primaryKey: .Autoincrement, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, primaryKey: .Autoincrement, check: int64Optional > 0) } + ) + } + + func test_column_withIntegerExpression_compilesReferentialColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, unique: true, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, check: int64Optional > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) } + ) + } + + func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL COLLATE RTRIM)", + table.create { t in t.column(string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL COLLATE RTRIM)", + table.create { t in t.column(stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + } + + func test_primaryKey_compilesPrimaryKeyExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\"))", + table.create { t in t.primaryKey(int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\"))", + table.create { t in t.primaryKey(int64, string) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\"))", + table.create { t in t.primaryKey(int64, string, double) } + ) + } + + func test_unique_compilesUniqueExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (UNIQUE (\"int64\"))", + table.create { t in t.unique(int64) } + ) + } + + func test_check_compilesCheckExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (CHECK ((\"int64\" > 0)))", + table.create { t in t.check(int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (CHECK ((\"int64Optional\" > 0)))", + table.create { t in t.check(int64Optional > 0) } + ) + } + + func test_foreignKey_compilesForeignKeyExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\"))", + table.create { t in t.foreignKey(string, references: table, string) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"stringOptional\") REFERENCES \"table\" (\"string\"))", + table.create { t in t.foreignKey(stringOptional, references: table, string) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\") ON UPDATE CASCADE ON DELETE SET NULL)", + table.create { t in t.foreignKey(string, references: table, string, update: .Cascade, delete: .SetNull) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\", \"string\") REFERENCES \"table\" (\"string\", \"string\"))", + table.create { t in t.foreignKey((string, string), references: table, (string, string)) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\", \"string\", \"string\") REFERENCES \"table\" (\"string\", \"string\", \"string\"))", + table.create { t in t.foreignKey((string, string, string), references: table, (string, string, string)) } + ) + } + + func test_addColumn_compilesAlterTableExpression() { + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL DEFAULT (1)", + table.addColumn(int64, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (1)", + table.addColumn(int64, check: int64 > 0, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (1)", + table.addColumn(int64, check: int64Optional > 0, defaultValue: 1) + ) + + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER", + table.addColumn(int64Optional) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0)", + table.addColumn(int64Optional, check: int64 > 0) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0)", + table.addColumn(int64Optional, check: int64Optional > 0) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER DEFAULT (1)", + table.addColumn(int64Optional, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (1)", + table.addColumn(int64Optional, check: int64 > 0, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (1)", + table.addColumn(int64Optional, check: int64Optional > 0, defaultValue: 1) + ) + } + + func test_addColumn_withIntegerExpression_compilesReferentialAlterTableExpression() { + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, unique: true, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, check: int64Optional > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, unique: true, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, unique: true, check: int64Optional > 0, references: table, int64) + ) + + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, unique: true, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, check: int64Optional > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, unique: true, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) + ) + } + + func test_addColumn_withStringExpression_compilesCollatedAlterTableExpression() { + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM", + table.addColumn(string, defaultValue: "string", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(string, check: string != "", defaultValue: "string", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(string, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) + ) + + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT COLLATE RTRIM", + table.addColumn(stringOptional, collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') COLLATE RTRIM", + table.addColumn(stringOptional, check: string != "", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') COLLATE RTRIM", + table.addColumn(stringOptional, check: stringOptional != "", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(stringOptional, check: string != "", defaultValue: "string", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) + ) + } + + func test_rename_compilesAlterTableRenameToExpression() { + XCTAssertEqual("ALTER TABLE \"old\" RENAME TO \"table\"", Table("old").rename(table)) + } + + func test_createIndex_compilesCreateIndexExpression() { + XCTAssertEqual("CREATE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", table.createIndex(int64)) + + XCTAssertEqual( + "CREATE UNIQUE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", + table.createIndex([int64], unique: true) + ) + XCTAssertEqual( + "CREATE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", + table.createIndex([int64], ifNotExists: true) + ) + XCTAssertEqual( + "CREATE UNIQUE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", + table.createIndex([int64], unique: true, ifNotExists: true) + ) + } + + func test_dropIndex_compilesCreateIndexExpression() { + XCTAssertEqual("DROP INDEX \"index_table_on_int64\"", table.dropIndex(int64)) + XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex([int64], ifExists: true)) + } + + func test_create_onView_compilesCreateViewExpression() { + XCTAssertEqual( + "CREATE VIEW \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64)) + ) + XCTAssertEqual( + "CREATE TEMPORARY VIEW \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64), temporary: true) + ) + XCTAssertEqual( + "CREATE VIEW IF NOT EXISTS \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64), ifNotExists: true) + ) + XCTAssertEqual( + "CREATE TEMPORARY VIEW IF NOT EXISTS \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64), temporary: true, ifNotExists: true) + ) + } + + func test_create_onVirtualTable_compilesCreateVirtualTableExpression() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING \"custom\"('foo', 'bar')", + virtualTable.create(Module("custom", ["foo", "bar"])) + ) + } + + func test_rename_onVirtualTable_compilesAlterTableRenameToExpression() { + XCTAssertEqual( + "ALTER TABLE \"old\" RENAME TO \"virtual_table\"", + VirtualTable("old").rename(virtualTable) + ) + } + +} diff --git a/Tests/SetterTests.swift b/Tests/SetterTests.swift new file mode 100644 index 00000000..d4f189d7 --- /dev/null +++ b/Tests/SetterTests.swift @@ -0,0 +1,137 @@ +import XCTest +import SQLite + +class SetterTests : XCTestCase { + + func test_setterAssignmentOperator_buildsSetter() { + AssertSQL("\"int\" = \"int\"", int <- int) + AssertSQL("\"int\" = 1", int <- 1) + AssertSQL("\"intOptional\" = \"int\"", intOptional <- int) + AssertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional) + AssertSQL("\"intOptional\" = 1", intOptional <- 1) + AssertSQL("\"intOptional\" = NULL", intOptional <- nil) + } + + func test_plusEquals_withStringExpression_buildsSetter() { + AssertSQL("\"string\" = (\"string\" || \"string\")", string += string) + AssertSQL("\"string\" = (\"string\" || 'literal')", string += "literal") + AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string) + AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional) + AssertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal") + } + + func test_plusEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" + \"int\")", int += int) + AssertSQL("\"int\" = (\"int\" + 1)", int += 1) + AssertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int) + AssertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1) + + AssertSQL("\"double\" = (\"double\" + \"double\")", double += double) + AssertSQL("\"double\" = (\"double\" + 1.0)", double += 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1) + } + + func test_minusEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" - \"int\")", int -= int) + AssertSQL("\"int\" = (\"int\" - 1)", int -= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int) + AssertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1) + + AssertSQL("\"double\" = (\"double\" - \"double\")", double -= double) + AssertSQL("\"double\" = (\"double\" - 1.0)", double -= 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1) + } + + func test_timesEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" * \"int\")", int *= int) + AssertSQL("\"int\" = (\"int\" * 1)", int *= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int) + AssertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1) + + AssertSQL("\"double\" = (\"double\" * \"double\")", double *= double) + AssertSQL("\"double\" = (\"double\" * 1.0)", double *= 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1) + } + + func test_dividedByEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" / \"int\")", int /= int) + AssertSQL("\"int\" = (\"int\" / 1)", int /= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int) + AssertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1) + + AssertSQL("\"double\" = (\"double\" / \"double\")", double /= double) + AssertSQL("\"double\" = (\"double\" / 1.0)", double /= 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1) + } + + func test_moduloEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" % \"int\")", int %= int) + AssertSQL("\"int\" = (\"int\" % 1)", int %= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int) + AssertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1) + } + + func test_leftShiftEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" << \"int\")", int <<= int) + AssertSQL("\"int\" = (\"int\" << 1)", int <<= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int) + AssertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1) + } + + func test_rightShiftEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int) + AssertSQL("\"int\" = (\"int\" >> 1)", int >>= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int) + AssertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1) + } + + func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" & \"int\")", int &= int) + AssertSQL("\"int\" = (\"int\" & 1)", int &= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int) + AssertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1) + } + + func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" | \"int\")", int |= int) + AssertSQL("\"int\" = (\"int\" | 1)", int |= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int) + AssertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1) + } + + func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int) + AssertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1) + AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int) + AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional) + AssertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1) + } + + func test_postfixPlus_withIntegerValue_buildsSetter() { + AssertSQL("\"int\" = (\"int\" + 1)", int++) + AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++) + } + + func test_postfixMinus_withIntegerValue_buildsSetter() { + AssertSQL("\"int\" = (\"int\" - 1)", int--) + AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) + } + +} diff --git a/Tests/StatementTests.swift b/Tests/StatementTests.swift new file mode 100644 index 00000000..44a7ae17 --- /dev/null +++ b/Tests/StatementTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class StatementTests : XCTestCase { + +} diff --git a/Tests/TestHelpers.swift b/Tests/TestHelpers.swift new file mode 100644 index 00000000..e41af0f8 --- /dev/null +++ b/Tests/TestHelpers.swift @@ -0,0 +1,115 @@ +import XCTest + +import SQLite + +class SQLiteTestCase : XCTestCase { + + var trace = [String: Int]() + + let db = try! Connection() + + let users = Table("users") + + override func setUp() { + super.setUp() + + db.trace { SQL in + print(SQL) + self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 + } + } + + func CreateUsersTable() { + try! db.execute( + "CREATE TABLE \"users\" (" + + "id INTEGER PRIMARY KEY, " + + "email TEXT NOT NULL UNIQUE, " + + "age INTEGER, " + + "salary REAL, " + + "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users(id)" + + ")" + ) + } + + func InsertUsers(names: String...) throws { + try InsertUsers(names) + } + + func InsertUsers(names: [String]) throws { + for name in names { try InsertUser(name) } + } + + func InsertUser(name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { + return try db.run( + "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + "\(name)@example.com", age?.datatypeValue, admin.datatypeValue + ) + } + + func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + XCTAssertEqual( + executions, trace[SQL] ?? 0, + message ?? SQL, + file: file, line: line + ) + } + + func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + try! statement.run() + AssertSQL(SQL, 1, message, file: file, line: line) + if let count = trace[SQL] { trace[SQL] = count - 1 } + } + +// func AssertSQL(SQL: String, _ query: Query, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { +// for _ in query {} +// AssertSQL(SQL, 1, message, file: file, line: line) +// if let count = trace[SQL] { trace[SQL] = count - 1 } +// } + + func async(expect description: String = "async", timeout: Double = 5, @noescape block: (() -> Void) -> Void) { + let expectation = expectationWithDescription(description) + block(expectation.fulfill) + waitForExpectationsWithTimeout(timeout, handler: nil) + } + +} + +let bool = Expression("bool") +let boolOptional = Expression("boolOptional") + +let data = Expression("blob") +let dataOptional = Expression("blobOptional") + +let date = Expression("date") +let dateOptional = Expression("dateOptional") + +let double = Expression("double") +let doubleOptional = Expression("doubleOptional") + +let int = Expression("int") +let intOptional = Expression("intOptional") + +let int64 = Expression("int64") +let int64Optional = Expression("int64Optional") + +let string = Expression("string") +let stringOptional = Expression("stringOptional") + +func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () -> Expressible, file: String = __FILE__, line: UInt = __LINE__) { + XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) +} + +func AssertThrows(@autoclosure expression: () throws -> T, file: String = __FILE__, line: UInt = __LINE__) { + do { + try expression() + XCTFail("expression expected to throw", file: file, line: line) + } catch { + XCTAssert(true, file: file, line: line) + } +} + +let table = Table("table") +let virtualTable = VirtualTable("virtual_table") +let _view = View("view") // avoid Mac XCTestCase collision diff --git a/Tests/ValueTests.swift b/Tests/ValueTests.swift new file mode 100644 index 00000000..bda2b4b3 --- /dev/null +++ b/Tests/ValueTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class ValueTests : XCTestCase { + +} diff --git a/Vendor/sqlcipher b/Vendor/sqlcipher index fafb0d05..c01b94fb 160000 --- a/Vendor/sqlcipher +++ b/Vendor/sqlcipher @@ -1 +1 @@ -Subproject commit fafb0d05bdae394037ac495fe7261573f5f675eb +Subproject commit c01b94fb6a6b54e00b9839e7314893f72c9dab22 From 8514451928faf200f151a0ea0cdb7e3b0fe04d89 Mon Sep 17 00:00:00 2001 From: Todd Krabach Date: Fri, 21 Aug 2015 20:43:18 -0700 Subject: [PATCH 0300/1046] fix podspec var name --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 39e910cb..f2f43fbe 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ require_relative 'Supporting Files/podspec.rb' -Pod::Spec.new do |s| +Pod::Spec.new do |spec| spec.name = 'SQLite.swift' spec.summary = 'A type-safe, Swift-language layer over SQLite3.' From dc1a88a5596108c25b60e37b88f13a3226c23884 Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Tue, 25 Aug 2015 11:13:05 +0100 Subject: [PATCH 0301/1046] fixed issues as best I could with current knowledge re: Xcode 7 beta 6 language changes Converted to Swift 2.0 for XCode 7.0 Beta 6. Compiles but does not pass tests. --- Source/Extensions/FTS4.swift | 8 ++++---- Source/Extensions/R*Tree.swift | 2 +- Source/Helpers.swift | 10 +++++----- Source/Typed/AggregateFunctions.swift | 6 +++--- Source/Typed/CoreFunctions.swift | 6 +++--- Source/Typed/Expression.swift | 4 ++-- Source/Typed/Query.swift | 2 +- Source/Typed/Schema.swift | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Source/Extensions/FTS4.swift b/Source/Extensions/FTS4.swift index 92e7b9d9..13da8b97 100644 --- a/Source/Extensions/FTS4.swift +++ b/Source/Extensions/FTS4.swift @@ -101,12 +101,12 @@ public struct Tokenizer { } if !tokenchars.isEmpty { - let joined = "".join(tokenchars.map { String($0) }) + let joined = tokenchars.map { String($0) }.joinWithSeparator("") arguments.append("tokenchars=\(joined)".quote()) } if !separators.isEmpty { - let joined = "".join(separators.map { String($0) }) + let joined = separators.map { String($0) }.joinWithSeparator("") arguments.append("separators=\(joined)".quote()) } @@ -133,7 +133,7 @@ public struct Tokenizer { extension Tokenizer : CustomStringConvertible { public var description: String { - return " ".join([name] + arguments) + return ([name] + arguments).joinWithSeparator(" ") } } @@ -146,7 +146,7 @@ extension Connection { if let (token, range) = next(string) { let view = string.utf8 offset.memory += string.substringToIndex(range.startIndex).utf8.count - length.memory = Int32(distance(range.startIndex.samePositionIn(view), range.endIndex.samePositionIn(view))) + length.memory = Int32(range.startIndex.samePositionIn(view).distanceTo(range.endIndex.samePositionIn(view))) return token } return nil diff --git a/Source/Extensions/R*Tree.swift b/Source/Extensions/R*Tree.swift index 0ba81689..a5571ea6 100644 --- a/Source/Extensions/R*Tree.swift +++ b/Source/Extensions/R*Tree.swift @@ -28,7 +28,7 @@ extension Module { var arguments: [Expressible] = [primaryKey] for pair in pairs { - arguments.extend([pair.0, pair.1] as [Expressible]) + arguments.appendContentsOf([pair.0, pair.1] as [Expressible]) } return Module(name: "rtree", arguments: arguments) diff --git a/Source/Helpers.swift b/Source/Helpers.swift index eaa424fd..c8e9f361 100644 --- a/Source/Helpers.swift +++ b/Source/Helpers.swift @@ -30,13 +30,13 @@ public func *(_: Expression?, _: Expression?) -> Expression(join(template), bindings) + return Expression(template.joinWithSeparator(self), bindings) } @warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { @@ -104,7 +104,7 @@ extension String { if let literal = literal { if let literal = literal as? NSData { let buf = UnsafeBufferPointer(start: UnsafePointer(literal.bytes), count: literal.length) - let hex = "".join(buf.map { String(format: "%02x", $0) }) + let hex = buf.map { String(format: "%02x", $0) }.joinWithSeparator("") return "x'\(hex)'" } if let literal = literal as? String { return literal.quote("'") } diff --git a/Source/Typed/AggregateFunctions.swift b/Source/Typed/AggregateFunctions.swift index 3c905243..aa482e41 100644 --- a/Source/Typed/AggregateFunctions.swift +++ b/Source/Typed/AggregateFunctions.swift @@ -53,7 +53,7 @@ extension ExpressionType where UnderlyingType : Value { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Value { +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. /// @@ -114,7 +114,7 @@ extension ExpressionType where UnderlyingType : protocol { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : protocol { +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : protocol { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. @@ -187,7 +187,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Value, UnderlyingType.Wrapped.Datatype : Number { +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Number { /// Builds a copy of the expression wrapped with the `avg` aggregate /// function. diff --git a/Source/Typed/CoreFunctions.swift b/Source/Typed/CoreFunctions.swift index e2867976..b929e40a 100644 --- a/Source/Typed/CoreFunctions.swift +++ b/Source/Typed/CoreFunctions.swift @@ -37,7 +37,7 @@ extension ExpressionType where UnderlyingType : Number { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Number { +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Number { /// Builds a copy of the expression wrapped with the `abs` function. /// @@ -598,7 +598,7 @@ extension CollectionType where Generator.Element : Value, Index.Distance == Int /// - Returns: A copy of the expression prepended with an `IN` check against /// the collection. @warn_unused_result public func contains(expression: Expression) -> Expression { - let templates = ", ".join([String](count: count, repeatedValue: "?")) + let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } @@ -614,7 +614,7 @@ extension CollectionType where Generator.Element : Value, Index.Distance == Int /// - Returns: A copy of the expression prepended with an `IN` check against /// the collection. @warn_unused_result public func contains(expression: Expression) -> Expression { - let templates = ", ".join([String](count: count, repeatedValue: "?")) + let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } diff --git a/Source/Typed/Expression.swift b/Source/Typed/Expression.swift index c69d88ac..551831d7 100644 --- a/Source/Typed/Expression.swift +++ b/Source/Typed/Expression.swift @@ -108,13 +108,13 @@ extension ExpressionType where UnderlyingType : Value { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wrapped : Value { +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { public static var null: Self { return self.init(value: nil) } - public init(value: UnderlyingType.Wrapped?) { + public init(value: UnderlyingType.WrappedType?) { self.init("?", [value?.datatypeValue]) } diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift index 3a3e3e78..a4047a0d 100644 --- a/Source/Typed/Query.swift +++ b/Source/Typed/Query.swift @@ -866,7 +866,7 @@ extension Connection { column: for each in query.clauses.select.columns ?? [Expression(literal: "*")] { var names = each.expression.template.characters.split { $0 == "." }.map(String.init) let column = names.removeLast() - let namespace = ".".join(names) + let namespace = names.joinWithSeparator(".") func expandGlob(namespace: Bool) -> QueryType -> Void { return { query in diff --git a/Source/Typed/Schema.swift b/Source/Typed/Schema.swift index d588685c..a768b740 100644 --- a/Source/Typed/Schema.swift +++ b/Source/Typed/Schema.swift @@ -152,7 +152,7 @@ extension Table { } private func indexName(columns: [Expressible]) -> Expressible { - let string = " ".join(["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).lowercaseString + let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joinWithSeparator(" ").lowercaseString let index = string.characters.reduce("") { underscored, character in guard character != "\"" else { From 05e5e4111dcbd28b8e5160ef3cf093e462a408f7 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 26 Aug 2015 08:14:45 -0400 Subject: [PATCH 0302/1046] Xcode 7 beta 6: Use C Function Pointer API Signed-off-by: Stephen Celis --- Source/Core/Connection.swift | 143 ++++++++++++++++++++++------------ Source/Core/SQLite-Bridging.h | 26 +------ Source/Core/SQLite-Bridging.m | 78 ------------------- 3 files changed, 95 insertions(+), 152 deletions(-) diff --git a/Source/Core/Connection.swift b/Source/Core/Connection.swift index 1f50e984..c9d19dbe 100644 --- a/Source/Core/Connection.swift +++ b/Source/Core/Connection.swift @@ -357,31 +357,43 @@ public final class Connection { /// times it’s been called for this lock. If it returns `true`, it will /// try again. If it returns `false`, no further attempts will be made. public func busyHandler(callback: ((tries: Int) -> Bool)?) { - if let callback = callback { - busyHandler = { callback(tries: Int($0)) ? 1 : 0 } - } else { + guard let callback = callback else { + sqlite3_busy_handler(handle, nil, nil) busyHandler = nil + return } + + let box: BusyHandler = { callback(tries: Int($0)) ? 1 : 0 } + sqlite3_busy_handler(handle, { callback, tries in + unsafeBitCast(callback, BusyHandler.self)(tries) + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + busyHandler = box } - private var busyHandler: _SQLiteBusyHandlerCallback? + private typealias BusyHandler = @convention(block) Int32 -> Int32 + private var busyHandler: BusyHandler? /// Sets a handler to call when a statement is executed with the compiled /// SQL. /// /// - Parameter callback: This block is invoked when a statement is executed - /// with the compiled SQL as its argument. E.g., pass `print` to act as a - /// logger. + /// with the compiled SQL as its argument. /// - /// db.trace(print) + /// db.trace { SQL in print(SQL) } public func trace(callback: (String -> Void)?) { - if let callback = callback { - trace = { callback(String.fromCString($0)!) } - } else { + guard let callback = callback else { + sqlite3_trace(handle, nil, nil) trace = nil + return } - _SQLiteTrace(handle, trace) + + let box: Trace = { callback(String.fromCString($0)!) } + sqlite3_trace(handle, { callback, SQL in + unsafeBitCast(callback, Trace.self)(SQL) + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + trace = box } - private var trace: _SQLiteTraceCallback? + private typealias Trace = @convention(block) UnsafePointer -> Void + private var trace: Trace? /// Registers a callback to be invoked whenever a row is inserted, updated, /// or deleted in a rowid table. @@ -390,21 +402,27 @@ public final class Connection { /// `.Insert`, `.Update`, or `.Delete`), database name, table name, and /// rowid. public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { - if let callback = callback { - updateHook = { operation, db, table, rowid in - callback( - operation: Operation(rawValue: operation), - db: String.fromCString(db)!, - table: String.fromCString(table)!, - rowid: rowid - ) - } - } else { + guard let callback = callback else { + sqlite3_update_hook(handle, nil, nil) updateHook = nil + return } - _SQLiteUpdateHook(handle, updateHook) + + let box: UpdateHook = { + callback( + operation: Operation(rawValue: $0), + db: String.fromCString($1)!, + table: String.fromCString($2)!, + rowid: $3 + ) + } + sqlite3_update_hook(handle, { callback, operation, db, table, rowid in + unsafeBitCast(callback, UpdateHook.self)(operation, db, table, rowid) + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + updateHook = box } - private var updateHook: _SQLiteUpdateHookCallback? + private typealias UpdateHook = @convention(block) (Int32, UnsafePointer, UnsafePointer, Int64) -> Void + private var updateHook: UpdateHook? /// Registers a callback to be invoked whenever a transaction is committed. /// @@ -412,31 +430,47 @@ public final class Connection { /// committed. If this callback throws, the transaction will be rolled /// back. public func commitHook(callback: (() throws -> Void)?) { - if let callback = callback { - commitHook = { - do { - try callback() - return 0 - } catch { - return 1 - } - } - } else { + guard let callback = callback else { + sqlite3_commit_hook(handle, nil, nil) commitHook = nil + return } - _SQLiteCommitHook(handle, commitHook) + + let box: CommitHook = { + do { + try callback() + } catch { + return 1 + } + return 0 + } + sqlite3_commit_hook(handle, { callback in + unsafeBitCast(callback, CommitHook.self)() + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + commitHook = box } - private var commitHook: _SQLiteCommitHookCallback? + private typealias CommitHook = @convention(block) () -> Int32 + private var commitHook: CommitHook? /// Registers a callback to be invoked whenever a transaction rolls back. /// /// - Parameter callback: A callback invoked when a transaction is rolled /// back. - public func rollbackHook(callback: _SQLiteRollbackHookCallback?) { - rollbackHook = callback - _SQLiteRollbackHook(handle, rollbackHook) + public func rollbackHook(callback: (() -> Void)?) { + guard let callback = callback else { + sqlite3_rollback_hook(handle, nil, nil) + rollbackHook = nil + return + } + + let box: RollbackHook = { callback() } + sqlite3_rollback_hook(handle, { callback in + unsafeBitCast(callback, RollbackHook.self)() + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + rollbackHook = box } - private var rollbackHook: _SQLiteRollbackHookCallback? + private typealias RollbackHook = @convention(block) () -> Void + private var rollbackHook: RollbackHook? /// Creates or redefines a custom SQL function. /// @@ -459,8 +493,7 @@ public final class Connection { /// parameters and should return a raw SQL value (or nil). public func createFunction(function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 - if functions[function] == nil { self.functions[function] = [:] } - functions[function]?[argc] = { context, argc, argv in + let box: Function = { context, argc, argv in let arguments: [Binding?] = (0...self), { context, argc, value in + unsafeBitCast(sqlite3_user_data(context), Function.self)(context, argc, value) + }, nil, nil, nil) + if functions[function] == nil { self.functions[function] = [:] } + functions[function]?[argc] = box } - private var functions = [String: [Int: _SQLiteCreateFunctionCallback]]() - + private typealias Function = @convention(block) (COpaquePointer, Int32, UnsafeMutablePointer) -> Void + private var functions = [String: [Int: Function]]() /// The return type of a collation comparison function. public typealias ComparisonResult = NSComparisonResult @@ -510,12 +551,16 @@ public final class Connection { /// - block: A collation function that takes two strings and returns the /// comparison result. public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) { - collations[collation] = { lhs, rhs in - Int32(block(lhs: String.fromCString(lhs)!, rhs: String.fromCString(rhs)!).rawValue) + let box: Collation = { lhs, rhs in + Int32(block(lhs: String.fromCString(UnsafePointer(lhs))!, rhs: String.fromCString(UnsafePointer(rhs))!).rawValue) } - try! check(_SQLiteCreateCollation(handle, collation, collations[collation])) + try! check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in + unsafeBitCast(callback, Collation.self)(lhs, rhs) + }, nil)) + collations[collation] = box } - private var collations = [String: _SQLiteCreateCollationCallback]() + private typealias Collation = @convention(block) (UnsafePointer, UnsafePointer) -> Int32 + private var collations = [String: Collation]() // MARK: - Error Handling diff --git a/Source/Core/SQLite-Bridging.h b/Source/Core/SQLite-Bridging.h index 31a07bc5..d15e8d56 100644 --- a/Source/Core/SQLite-Bridging.h +++ b/Source/Core/SQLite-Bridging.h @@ -28,33 +28,9 @@ #import "sqlite3.h" #endif -// CocoaPods workaround -typedef struct SQLiteHandle SQLiteHandle; -typedef struct SQLiteContext SQLiteContext; -typedef struct SQLiteValue SQLiteValue; +typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround NS_ASSUME_NONNULL_BEGIN -typedef int (^_SQLiteBusyHandlerCallback)(int times); -int _SQLiteBusyHandler(SQLiteHandle * db, _SQLiteBusyHandlerCallback _Nullable callback); - -typedef void (^_SQLiteTraceCallback)(const char * SQL); -void _SQLiteTrace(SQLiteHandle * db, _SQLiteTraceCallback _Nullable callback); - -typedef void (^_SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, long long rowid); -void _SQLiteUpdateHook(SQLiteHandle * db, _SQLiteUpdateHookCallback _Nullable callback); - -typedef int (^_SQLiteCommitHookCallback)(); -void _SQLiteCommitHook(SQLiteHandle * db, _SQLiteCommitHookCallback _Nullable callback); - -typedef void (^_SQLiteRollbackHookCallback)(); -void _SQLiteRollbackHook(SQLiteHandle * db, _SQLiteRollbackHookCallback _Nullable callback); - -typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue * _Nonnull * _Nonnull argv); -int _SQLiteCreateFunction(SQLiteHandle * db, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback _Nullable callback); - -typedef int (^_SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); -int _SQLiteCreateCollation(SQLiteHandle * db, const char * name, _SQLiteCreateCollationCallback _Nullable callback); - typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); NS_ASSUME_NONNULL_END diff --git a/Source/Core/SQLite-Bridging.m b/Source/Core/SQLite-Bridging.m index d34d88dc..ac5e54db 100644 --- a/Source/Core/SQLite-Bridging.m +++ b/Source/Core/SQLite-Bridging.m @@ -30,84 +30,6 @@ #import "fts3_tokenizer.h" -static int __SQLiteBusyHandler(void * context, int tries) { - return ((__bridge _SQLiteBusyHandlerCallback)context)(tries); -} - -int _SQLiteBusyHandler(SQLiteHandle * db, _SQLiteBusyHandlerCallback callback) { - if (callback) { - return sqlite3_busy_handler((sqlite3 *)db, __SQLiteBusyHandler, (__bridge void *)callback); - } else { - return sqlite3_busy_handler((sqlite3 *)db, 0, 0); - } -} - -static void __SQLiteTrace(void * context, const char * SQL) { - ((__bridge _SQLiteTraceCallback)context)(SQL); -} - -void _SQLiteTrace(SQLiteHandle * db, _SQLiteTraceCallback callback) { - if (callback) { - sqlite3_trace((sqlite3 *)db, __SQLiteTrace, (__bridge void *)callback); - } else { - sqlite3_trace((sqlite3 *)db, 0, 0); - } -} - -static void __SQLiteUpdateHook(void * context, int operation, const char * db, const char * table, long long rowid) { - ((__bridge _SQLiteUpdateHookCallback)context)(operation, db, table, rowid); -} - -void _SQLiteUpdateHook(SQLiteHandle * db, _SQLiteUpdateHookCallback callback) { - sqlite3_update_hook((sqlite3 *)db, __SQLiteUpdateHook, (__bridge void *)callback); -} - -static int __SQLiteCommitHook(void * context) { - return ((__bridge _SQLiteCommitHookCallback)context)(); -} - -void _SQLiteCommitHook(SQLiteHandle * db, _SQLiteCommitHookCallback callback) { - sqlite3_commit_hook((sqlite3 *)db, __SQLiteCommitHook, (__bridge void *)callback); -} - -static void __SQLiteRollbackHook(void * context) { - ((__bridge void (^ _Null_unspecified )())context)(); -} - -void _SQLiteRollbackHook(SQLiteHandle * db, _SQLiteRollbackHookCallback _Nullable callback) { - sqlite3_rollback_hook((sqlite3 *)db, __SQLiteRollbackHook, (__bridge void *)callback); -} - -static void __SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { - ((__bridge _SQLiteCreateFunctionCallback)sqlite3_user_data(context))((SQLiteContext *)context, argc, (SQLiteValue **)argv); -} - -int _SQLiteCreateFunction(SQLiteHandle * db, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback) { - if (callback) { - int flags = SQLITE_UTF8; - if (deterministic) { -#ifdef SQLITE_DETERMINISTIC - flags |= SQLITE_DETERMINISTIC; -#endif - } - return sqlite3_create_function_v2((sqlite3 *)db, name, -1, flags, (__bridge void *)callback, &__SQLiteCreateFunction, 0, 0, 0); - } else { - return sqlite3_create_function_v2((sqlite3 *)db, name, 0, 0, 0, 0, 0, 0, 0); - } -} - -static int __SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { - return ((__bridge _SQLiteCreateCollationCallback)context)(lhs, rhs); -} - -int _SQLiteCreateCollation(SQLiteHandle * db, const char * name, _SQLiteCreateCollationCallback callback) { - if (callback) { - return sqlite3_create_collation_v2((sqlite3 *)db, name, SQLITE_UTF8, (__bridge void *)callback, &__SQLiteCreateCollation, 0); - } else { - return sqlite3_create_collation_v2((sqlite3 *)db, name, 0, 0, 0, 0); - } -} - #pragma mark - FTS typedef struct __SQLiteTokenizer { From e59e9a8e1bc22843f1b6d6766791665d506d2a17 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 13 Sep 2015 16:31:53 -0400 Subject: [PATCH 0303/1046] Use Swift 2-isms where applicable We should use guard-let wherever we have an early exit and can manage. Let the compiler enforce our requirements. Signed-off-by: Stephen Celis --- Source/Extensions/FTS4.swift | 14 +++++++------- Source/Helpers.swift | 18 +++++++++--------- Source/Typed/Query.swift | 23 ++++++++++++++--------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Source/Extensions/FTS4.swift b/Source/Extensions/FTS4.swift index 13da8b97..a755f96b 100644 --- a/Source/Extensions/FTS4.swift +++ b/Source/Extensions/FTS4.swift @@ -143,13 +143,13 @@ extension Connection { public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in let string = String.fromCString(input)! - if let (token, range) = next(string) { - let view = string.utf8 - offset.memory += string.substringToIndex(range.startIndex).utf8.count - length.memory = Int32(range.startIndex.samePositionIn(view).distanceTo(range.endIndex.samePositionIn(view))) - return token - } - return nil + + guard let (token, range) = next(string) else { return nil } + + let view = string.utf8 + offset.memory += string.substringToIndex(range.startIndex).utf8.count + length.memory = Int32(range.startIndex.samePositionIn(view).distanceTo(range.endIndex.samePositionIn(view))) + return token }) } diff --git a/Source/Helpers.swift b/Source/Helpers.swift index c8e9f361..c1775e68 100644 --- a/Source/Helpers.swift +++ b/Source/Helpers.swift @@ -101,16 +101,16 @@ extension String { } @warn_unused_result func transcode(literal: Binding?) -> String { - if let literal = literal { - if let literal = literal as? NSData { - let buf = UnsafeBufferPointer(start: UnsafePointer(literal.bytes), count: literal.length) - let hex = buf.map { String(format: "%02x", $0) }.joinWithSeparator("") - return "x'\(hex)'" - } - if let literal = literal as? String { return literal.quote("'") } - return "\(literal)" + guard let literal = literal else { return "NULL" } + + switch literal { + case let blob as Blob: + return blob.description + case let string as String: + return string.quote("'") + case let binding: + return "\(binding)" } - return "NULL" } @warn_unused_result func value(v: Binding) -> A { diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift index a4047a0d..544a8633 100644 --- a/Source/Typed/Query.swift +++ b/Source/Typed/Query.swift @@ -1006,19 +1006,24 @@ public struct Row { } public func get(column: Expression) -> V? { func valueAtIndex(idx: Int) -> V? { - if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } - return nil + guard let value = values[idx] as? V.Datatype else { return nil } + return (V.fromDatatypeValue(value) as? V)! } - if let idx = columnNames[column.template] { return valueAtIndex(idx) } - - let similar = Array(columnNames.keys).filter { $0.hasSuffix(".\(column.template)") } - if similar.count == 1 { return valueAtIndex(columnNames[similar[0]]!) } + guard let idx = columnNames[column.template] else { + let similar = Array(columnNames.keys).filter { $0.hasSuffix(".\(column.template)") } - if similar.count > 1 { - fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + switch similar.count { + case 0: + fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sort())") + case 1: + return valueAtIndex(columnNames[similar[0]]!) + default: + fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + } } - fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sort())") + + return valueAtIndex(idx) } // FIXME: rdar://problem/18673897 // subscript… From 723e19b6ce67a23b3acff0ebdc95d6825c8a4643 Mon Sep 17 00:00:00 2001 From: Stefan Arentz Date: Sat, 12 Sep 2015 07:45:43 -0400 Subject: [PATCH 0304/1046] Changed deployment targets to 8.0 for all targets --- SQLite.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index cc0edaaf..f0cc7e2e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1035,7 +1035,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -1077,7 +1077,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; From 8bd27bcd5d0e6461620e51a651156d57c1c2c6cb Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 20 Sep 2015 21:43:12 -0400 Subject: [PATCH 0305/1046] Mac OS X 10.9 compatibility Let's update the deploy targets and ensure that newer symbols aren't referenced. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 8 ++++---- Source/Core/Connection.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index f0cc7e2e..74ff688f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1153,7 +1153,7 @@ FRAMEWORK_VERSION = A; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1173,7 +1173,7 @@ FRAMEWORK_VERSION = A; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; SKIP_INSTALL = YES; }; @@ -1271,7 +1271,7 @@ FRAMEWORK_VERSION = A; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; SKIP_INSTALL = YES; }; @@ -1289,7 +1289,7 @@ FRAMEWORK_VERSION = A; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.9; SDKROOT = macosx; SKIP_INSTALL = YES; }; diff --git a/Source/Core/Connection.swift b/Source/Core/Connection.swift index c9d19dbe..fdd19083 100644 --- a/Source/Core/Connection.swift +++ b/Source/Core/Connection.swift @@ -91,7 +91,7 @@ public final class Connection { } deinit { - sqlite3_close_v2(handle) + sqlite3_close(handle) } // MARK: - From 2338aaf3902412bd8fa321b7bb495d7420ea78d3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 12 Oct 2015 20:40:19 -0400 Subject: [PATCH 0306/1046] Update Installation Instructions We need a new screen shot, and with it we can bring a simpler set of instructions to the table. Signed-off-by: Stephen Celis --- Documentation/Index.md | 8 ++++---- Documentation/Resources/installation@2x.png | Bin 270696 -> 312530 bytes README.md | 12 +++++------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4b1b6c67..baa09de5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -99,13 +99,13 @@ To install SQLite.swift as an Xcode sub-project: 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.) - ![Installation](Resources/installation@2x.png) + ![Installation Screen Shot](Documentation/Resources/installation@2x.png) - 2. In your target’s **Build Phases**, add **SQLite** to the **Target Dependencies** build phase. + 2. In your target’s **General** tab, click the **+** button under **Linked Frameworks and Libraries**. - 3. Add **SQLite.framework** to the **Link Binary With Libraries** build phase. + 3. Select the appropriate **SQLite.framework** for your platform. - 4. Add **SQLite.framework** to a **Copy Files** build phase with a **Frameworks** destination. (Add a new build phase if need be.) + 4. **Add**. You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift. diff --git a/Documentation/Resources/installation@2x.png b/Documentation/Resources/installation@2x.png index 29cb14a8b590f4817cd3735ec502dc126c45a7c5..6b31f459348e97208dbc8b564884a0d065c803d2 100644 GIT binary patch literal 312530 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 z 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 z°_t)IOu$Rx=D`WA|2G?@;)H_S6Byo1_=rTpq$GRyBN1fyLqJzn~7D(#?7dmeRU zNOq_cHL=kSti7HPKX5AXL25B`L{2da<)m%%%2_@JcIx+uQ7bd~1j&Dg@Hz2^J>8Wj z^Y6w%um8?%hZ_T8F3n%C=S+&UvqtZ-Li5x1A{i|$}@c) zlYy%*&K*qWNQz9G{)^HYS7UPoUNzR_p2x}&l0<0uo)@R@V+|{KC*o3J4^x|9a}r>? zuZKon<4_(7HHFpA8fNG=qyDbOyP-sgLerF|@ziRn7lj#DQPh<7BQ51JcX`8=93U|^ z4fM_A8@rn?IJ|ijNXjSXSk$)NP7MqP>9W@j=RVkToL^~qdhyh+hxF9;$5S6j7l8tO zF2@l}pi2$N&Nk&QTdBIQpvj;~S97^a@T_R}chXrzX9?Ju@&@57hWRTF;t^$=n?s)QgEGriSztI=$2kIv@cG z^P~oV)PNGAWLbXa^+XKG;zgdzOiJ%fn?+O-$jfp$eR>UlV4RBO_(@weJUoUZWy+}Y z&-|&0eNka&w7@aOt)|xsb6m{-YrrY(>L_PDjx(x^*?yTxr11;fff6iJ%z#f`!xX)8tX+4ba@7*v4MFTweB( zL&v~K9u3|@RPcDKqR=6JMru_HdcRxN_E=I~q|8E0$T5tv$&$O?bQMoMRZ1=TJ-1il z)aUWGw0~2P?EUmUqKD45-}KEGBf%8;Z@(mHytSMSoL$DJ2dMytww~W>&n`p zz(F%-q`PD3Xz^-~={k1R-{AcG{H~BhIvC3apc(BZIWI3B??rysqol2#j>7|hu?R_@ zQ_X{_Mlwv;Ak!>dFO?yPug~cS+ZH4GAi?;c_+&H&qgBnC8=zQ^KxHw>kfRK_p_U1w z8#3RZ)3(=S{Iayw&~zbG&VBpcSZX0VZppgemea<%RdzDrz2Hqw{=iS^YDk1Jq`D94 zo9Ak?)XO^Al9$ltEm#It=pK3HzL>`;MdjnNnZ!f@gsUA}=nO5@Qy!v!3T@w|GbK|+ zGdEA3POIP3yQB9C=(^id|{!hwf|+SZHr8qb`2P7~0au>L`H9 zHC=}{LVl>0D8)39O2xKX4`poNp+P_z?w)MXm$=W4n1xC*g9Nx17{qT{cC0zDlWNGKs6(Wr4kMrIpvTj= z1sejXyzG7FoHseGh^*JJNfm!(A+YWFmmZQ1$RaZ;6E`^i>O>LJ4~Fe7sUni29sqcqy42K_m zD-RmU5bpRyrg~}gjG2!{fvxFcC|A{mQf_p)Y6^~mv`Mv9i>P}&#B?R0bUR_d!pKf; z@?=8EO)sqV%nLuDjVlK9j}j1w%6wOv?M;x?zrt=fXoq^NnCKf?>W z2zoy5i8)!P710)S(5AG)WI9$}kAeF=$l$gsEO6DqP@|^LWhJ=oBjyb>t9sILHpwsQ zj0pH0e+diL$x8|mks+coje73mUrTs-p8p{WJyZ?=Pr-%KyyBM%&zlQgU7984oSlz} zc4N<4w+U_a=HrWUx>{Fa`MX#60b)Ke^=Thd*z4qd3VH13>9(=Z|H=wWP zmDW6A90iJMFJb;@@NXt=T5E8bAcc zwHo~CqY#Nq7YflNX9{U&~r0a1!|F|bT*Tzu@2Z46t*zY$cpld{DdZDw>T!jwQD@=A} zdZT9Y!^gs;=1KElsxU=@b%?M=$u~*WD_AB*3`I86Vgqw%!JD%{C+fIY(Wj=o0h)1% zJ{GOAOd@jGiw>`8P*D4Ro8h1xq@$F6A}-otorlz{q$YOk6+MK^pLp}=>%*wZA^~{^ zrZdvTadPX;c`vx%z*$*o&iD+zp?w6&>7I4d1=c3C zy}V&YlaeO0W=4CwD{7)@ZG|2S%VUO?%plUMt}VBAex}OuYx&dPLNOyY)YQ zT5G!ANU6Z~`==hzuROZQTf476&|~$D3V2&(7(uN*s~vM{3?Lki^GqRw!+)hXE<*BV z3VeZXY_z5up<8qk&kZltlIxODIi`VIoHJv!T=n%IR832+P!vCFCy#y^H(Va>mdeIQz52?IkAj<{iYUr*O6uTan4|5TvjIRSd)(2G(6LPL zj3pwpO9P(q#R7k}a0&&b0jd06=g@@j0erV<8 z<>B(U$-6XRD8z-R21uLIeeZF#D^|6h5yGwYhOoSyq;8}!%1HbG`YBeT`(4E~2&IO9 zkeUiMS!iNFo?8a3MI_rM2=4k|0X&&~<_fG6zyT3?4d6AZdZEdSd)f*ZKf0feFUhO> zbcEpZI7|`)fi<4y0X?W(V%@AMR6w){(2T6qQSnFQRKDH$c?m6Px7%HUljh5S-ro)M z&%oKaQ{NirL`yQ&pPdMMHvLHO-t_^f z3_;5(pyvU8J~mFaH?sehd=Ea3jf@wJI)iig6UsE47;)uMaRvRfKz) zJqH;2u?5hu0e~-GHgmcu)Rx3pFp@s_NUGu1aDjYsg%yb*nGqlW zhJ}VA5%4Wn>UDf922uI8-0!3w0(jZkF)0hGv?CR@wY_ZD5ZL=Z>VuS<5Ekh=zYy`% z+EN>7{g9ls3E8x(O(Z3RUn*b1A7*Xbd(yS9x@G$iBGOMp@~!skS1R5!PnuuQ%v_6p z0N6iy-W!2jM#nWx`1AOkp>&nv7iMO6DKmVJLQ0IU5p_i(rqd+D@>cnKBtLgFgz}|D z`VCEI0Ur(!ySUy~{8h>NahpNMGx!q`2Wp90Id}L$U>$ugFOoW7?Bxh)CL+#6i2Fe9 zu@Ntc4QfO)9XnI1Hpf!@O~nV=@BQ$a7w4+r3lyTp!cqrIf9Hv!Zzr^fdra7sEcv!~ z<$Qs4wDPkHagowxaMPnXAB}$CTLxXq(#ju``k-E=t;{>I+lNDnAT#rP!uiR9pU3Nc zizPoYP87+ z3(W9QH^ixzYSPY+WBL5UswphO68@Hr9|Q&0SG!4lxw~}+<0=#++`j6`*--4d2;h4;yPKh$($7x8{7abi@xg}bwYNz$D zba;KSyLqq>zcj}s-*gDefxQH%mf|p)Z~eo>)^{1h(R-*OIE+wYrYY}6Uw2=1v(LeQ z1*Q9bZJp#rpL!TWgWLhwHj|6RzITyOq^wOgf9^$#iSotudv98J)C7$qR(zahZfbp# z8{O;CLhWb@tCNmDX-YB`slab|!Sd{=tkT>T(!a&T&>!{kVZb5bhmR++nWH$goC291 z2yxNs%RZ1=cURV=Dmm~TE?*iR*7_tV{6K5&ACQvgl+j>&$S=%xh>qpPnWGbjWUTg| zxzXOL5}}*?fzX_X4Q&5%AybW0GBL=?h}M2n2+5;C>y*bs zr4wvJNv|`&ql(~|P9f^tx;k8XUz0;7fBZ&!12hQWS|~kiyD82bM;)TlkcuX;K*3(3 zYpPAc#KAcXg(+oWH@eHSD-t@m-&`DOFG0AOt32i5vZ8kB4NqeQI&%XL9Zk2w2h5mD zMTl2*c?yIWpf5;Me>UcPLAfpRr&IlU%@D|Kaa77_D%O-S;>Y*TVBMv1m9;|5TW{?C ze-YQYw}@AuhVkhF_nJDF7&Eszi**c1Uo23)D71P5cas!PvDueEIHxstZ^P!2n}DYU zBYTz-Wil&RQ#M$HE`d?aiW6&W;PM{OwfNh7hghP9234aWC5(T(ohOU(A@#-3*_mbh zl699zv%Dl=?MY{L&66~U6Vc?!VB<8DNhpve=TU|6C8auG=etfRb8Wx#-iLIAH=XrJ z?JYlpf}rkZLp4WT#!$ImM8T{S`+sKR&F^(LSuc?%B6zvEpgWqvlrF^IQ7&>H<}`JW zYIjI%<69N&kDkGd*v(#vhIxtmzg^7|v^3kg?&~%3y zp34tJj;7FG+BA}C#!Eic!tJ0A5K0Gr%QnjM89lGH=??Yy83^;?L$f6eLgw8=p^=2p zRtY}k=t|v@*9xnKgs;swkDXR1?LynbI&-`2tibKNH}*C{^yU8VSpF3E%->e{c(HxNh|eD|uzLDO528&PRwNUu^1OtTW36p%oj(J!(e-FZ zg;LL1l4zWScZ|Csa1e10Wum8Yr_xc;73d-w!d-$HIi{C-+N-q;_Hw}Bzcdk5h%Z++ zjCSlivS0sQfFZK2<756@ajA02fT=^*=A{v#?6$f7B1A5W%LTQssgCtRK(HxJML;@U z_1zRkLR2T|cKde+{cE1Qex0tFUHwXb)4SV8=zGC`qtY8|LZ(aM zRDLX^4T2@^w(9HFZE4Y-N*4_#p>C~&OF-FFfvCZil2sY{H`kJ0bV*T=Vjo^kn4l)I z5-j~8AzUtOK7%B$Z*GRw&=`G4blmpQZuMOzhR2AgML*SOSda8Gy&V9{(g&n!u2_a!kS2|>pof0g3@>X4|WnLR>YclA&892q#Hh5LBxCzUB zae0>=P2Yi5uH!{F_tBd&yJQ5 z8j-=De`EBo4}3o^Wovr3JUtE+LQ9sM+ceX#t&Yd&K4}jCZbVYIEcxaT=lhVZ|J(~O zoa^hV+_tzI(J+i!%?}l{q*#4_s;@kBu(?FAq)+H^KPYrLV^mF6S*AkOAey#`PFf<0 z<&^M}W{`v%c#F%W8z|cEsOllE%Ae$8EO?r6#{^GBOY7o(8xK|>=6!B_f~--8lsK}} zacAS&Efnz-4}#h?DM1lSAQiYa?|?}^JVowBWBtBgzAy1EWiOzN;~0-?gT@Ecp*=n`74H-BXlkL zpW*c*3<_3C0F+lIerkUtRSDgn=vJoBC@w#7vBWyErQ`eKu~xtFE%H1jujHZ2k2gNK z@gssm3W6%%937UsHb5GCQ)#?(A%bGyi;1d1!=t3=6){9T&Y{sBH(Q8DwDN?I3;$I` zdNI&3{feau`?J)S90KeK5`-9%W-2g|%>a)hed{T{M$>WHmfutTlO6>`44S8?y)Boo| zKtG)-1>BYFte}CoX|Uh@nlwO8{ZJB)Bh|iI^Jzy6VR)|KXjtX#kDA26-1{bXQ^LmE zY8C0x_gj4*-b%1G-|Zvu%yAK@0kqhDFn(Xlgk*jHO;yWQB|b<*jQ2~Vp*b|Ku{*#w zCK7vsK-76gkm%jnxY=MQ7R^xL&q8*7>aLv0ae%+EhASt#Qm)oR!J)-n&grb_l6M$# znZ|fa&UsZUm*uD$UN^$ZNo&}(@{z9 zMk(_ZJCvzYpz%3bp(EKBmb;$f9wX(6MDX?d)3)N^*53#D@wb83?)$mXIw_?m3B35p zx58hjrH4vnIQ!1d{)+>4!%^{48Q4~%r<-bN;W%pAJaR9gIaO8Y+es!pED?*BkLM+x5jR8kY9zrbUsM z>}He>zn96W7~;)UDmt95i5C-@nR+^mF}ce0IxNbodJ|SGXa@s4$Q?`wWwYfjen?w} z`XyY&PEB&bl=AtSl=JuYR^pu;;*>r(x3l9>Iu@+S#;MW@j}QEUCU?zwN!mXk*iixS z%|vUX3KvHtXnES=q|^45 z%bt}?j6%J`wBFmwoGT9_v3BE^`m6Punc#?ky#M&U-OwbS%RQSndOkRLQ*=7mx79#kH719$cMOx!|fT(1h!kEM;@M`p3r zs84(e`|1J=r|q0MUaM3SJ0>d{~3nE%*N|8 zP=uDe27qb`Pr#&b*`+B|UEEhV(92~16)XUB5CJsH*hQUOOPpPGz3a~C`(<-`-d>t` zT#nVq;M8+2rlhIXGHykMj9waIK|?~A%{35}XQ?491^Md;@Wx~mp{tUfOPu#vJHMw+X}?S0(p+P^O58eQ2 zP%g|{m8xLt+xVa?wVRlXiM=sX?P76RJrU*`l&?mhZ(mVCLgM|Az1cq1!98xh<0hVg z@PPc5x&s@b$%&ui_NyhMzqpRITvL#Gv<8+jbY%3w?v+=fnKaybS4$^t4a=rbX$-cP z7J@Es%HyMEffNcW7&y{NruoafPYgiwg6Oa`d))pYtQcgY{L6_xi>WW5K^W=C;PoAG z51w2^tk@^vJfN3oljEkZi1mQ0a^73f|3Y}zZI+=P!-@Xu552%fVJNHVOwxI!6V-2y zVS<#aSc(Nrufj`s<#DdRM4fSjw=3grc45t{(e4msplPELB>3O8xt=5hH>vX|jz4_S zbB(>6Z!!?8isYImkkvwwnL$-BT&uwd!^&q9T}jDW@VkqqMJkuH!>9SI;r$U7es4v{ z*yEj#dxwp^)}Iz_@wx7=hT*A8t{r;m3XVy79C{U<7IRu(%S zG(S%+%WwGoSYq>Csh)%FU*JV5dr?&Y24e?ZFgX5KK-g`C9+!r?0=08JJXtfuVn}2r z*vnG^ETHN-a|;^)af59@O8pn3%|6u#gaAoC%I1f}!muq}Du&{W!fRq-; zMOSmzO}MVGIc@xMIE`!}AtX$aQ8mW5t) zDt^@<{Zb&3Dwj>(s3>QO7KUGg@*qPK_^@syveyQ`LLk)bd_^(E9vpSNj!Tx!2WeH_D}SqV@-SQ<@RXjWBwqzuC6`&94y#CVwp_`C7hzL^=e|96 zzoKuTS@{e}VXGSeG3oAH`q4VLKqp0DrJrl)+N{xQ0O>~?zg)2`Uum3@fPehe{}VE9 z1R`E4w?TK7?^`LT)axx(G&MD$wq#l%)Qj+t_|l;onoy!IjFVJh(M$v24y_sm2mu)HJ#w zD+|q1vCCXVkWioy?O zJXR#+f_ut`q8_Cg*-FVswrU;4RS@JI7YQqhZtpDwM;SkVRxQ5QiQzne-Hn=hp|y>p z6`$S7>Bie0rf*owDjH{_)f*%;e;i;gzUX`{_5jsJ|Kx{^4CE>^5cll`4pN7Usv_Bo|n6!BDy)!f1O{v{95$4 zH)izaFnygI9ia_7C!jsp^YTa{vK-8QDFsFS2S~Ptb}=N*@Ube!xg=EFV%VlAbTb@k z09Y_jk}m4inhgzy;z$(bt&g|ak@T<#paNB>G|!{$@ZB8XUBp*1G@dIZMa6~#G8R~@ zHer{Fa_@i7?eN8|+X|)M8T8j)zGQ)}<^T~gaOD-W%NG^Rb2r}4)6?7cs8D4CV|ED} zl{4pKX|+-U@s~%a*Jg-;wmWy+{XBGPz+vev=B|!dyVYH$bWABC>8tV@6slsJA(Y?^6mFy9Qy}XPQa{rBlHj1AFS|8D7AXt6I6I-S;hwycxT>o z99QRT64k;Jwi8O89=W{p>RQZuKI2TjMc1KY(~=R-jr=s#^W(qom_OIg(V?p2VuXOe zVAaD}S}i0~v%-H^Xi}-}5k0(BIT8uF`j|I}32+Me>Q|}9jf%hFg7Gn6u(9BIE(>8{ zGUC-1oJ^=W)EX+o{(_+k`q13S=~I<_|3;w?4Nw2TRUBBd5hl)h*Y-6~SYoW$P+)ss zPU^709_Y54?#>7DUC*_uS`}c`0Y_H2 za(y-O`BdB!eX!&=&B%7HO?bfdO+skr)X!>#m7W1H zh{~4Qv;)Q@Ns+)mhCqdD{TGnb7^&n2@AGbnK~*aW@18uDFfEB%=P~dJPjtHM?L7tQ+@l^ z8VHrUTg4355KjSu;dt}<1xHpx0+ZRzP)Wl?c0#nJyw8U0e7ItG+H_`&NX2|KRrZV{ zqpeE&dOd6(7vH5SmpMZgk2Y8F0uJhbhp!xxE4K5a4!=hkkW(K(Mc~`FL$if%fwb<( zytv^IO`w^zr=W*I9V-RnJhdvtBV4LY@QTiTIR)|Jxmuz%M{o9?WE6#m5xkC1j7w~A z0L8$>78lzsO)mUaX92JS12l2qinN(lPbPZ9+cJj4n4EB=v!Q2kQnL{Yra3HAl`ABl z<)uYU)^3Flp3mbV`LFl)4Wej!pW4ANxhuQB69|txAu%x#Dr zt=ycS$DTmNqg453EtWJ1QC<33(VsvBJX$#jeWvL6Q!N(a+g=eO6dcniMT7BV^l@jW?&fD!qkG=3UeUhJ{sxdF`rXYQ*Rd zT?u}@ExE&Mi$n_n;wZD|P#rcQ`TFjzNn9_$V4$S@W@$|= znf^7DFC0B(cpwqrz|&vz!$KT>BI?~QVU0hGu{!W+UxLJ#qDe#YRW0M4XX*oppX97w zurAFL;l%j&F9@g`RcG^nKD)~Ut9LeI*z-ATZpXowl-(YNxX$qWXiA@qJ_h4 z89Tlmj^+um)ztztdLiJl`hX!#d717 zcs-Vy0BzghZTY!~w|s_ZK8OX)sNK=k`5g6(6F3p-U)F_uwLmkl{?Ae4@4tv)HQ#oO zb!MQQLg?uRup|1RawRDC4u#*TT3{STO{zj`IxXM{)luiqk_M!0z5y6w#m{-@rAWRL z55hQLg-lBcvXDfhB9IZuLX0)VQEe6E06O1ULP_Fh8*nHos6x>JDXMgfOf8UaVhfWK z5s=Hhj?SW_^~=If^EgjQQF}_*A>j1a$_feZYR zgV?V>PQaXNNb_<%_Y3x9sYCPcFchgDHG2B{rLTn#!hOOR8B}>71PwjjCuB$tPz*Z` zpg^nX(EK&Fkb2`?ZjQV_HaxNR*-SWmA2S$hinDuw_;fQP{8u4;#dPi6RSPBo53|!y zX-SH(ARVo{&*r95s9#OzyzKdf-}}O1CrI~xC5#V)&37pqH>JP}$QCS-ayMJf0&$Kd z(X17Zr_Z{lh_MQW9745tgvAEe^q4OO{A|7SJiD577Lrat^|#*$ya!d%4e$gG@z1?% z+o2|phrv3-B_|L=FbisUdxMGm@BM4I{p{BNJvBdYkgmOLUo}+Ak@===Z_4tuPuR7_KJQJ905YO#55YX&ArltAp=>i}7y^x@MRV2IKWH;@ z*@A(zomD3r;b=nr!j=B_+b-8bRZrDQ!(W=qo?8FIHLE@)Oj)@tg^rq7RsYCSI0Z8> zk-CXLlN2&y#)S9~Ec+5a<)oEM< zYD~x3sNH-8CFcedZNB}#X!DEFN#Gbul9$ivuu>hhc%+zaCQlO@6tYddy^7@rIBMwG z(qJ=yy&vi@dgy39oJVQ|`3%>?>EqPV_vrzsDg+98kl$86CUj4WDhhTA)I+B0t6VDa zY&-eRO)C(!>9Z7p`ndS`;Om4}opukE987QanKy>6QyW5pg55nv4E?_|{`d5}S=WH! zrx20}`e;d3FyZp{ac{EXRr! z^fP;nB?)_;Y`I%qP&h(GDcXj>X-n$?Y1X@SSh>b}bd}F!GZx~h`Z}^Y8j0@#ENm`6D9u(#x_im5!{ z-P~IGba&S-TVvhE^KsY{J51GT%dwW9Y`6}09M{F1ZGuD5nLcy|tyuVv|kE#ZNNp zQ;+COZjrF^x<8dDi4WKM3cT(bmvZfZ@?Nd2ophq)6bz645op|hHAh42i_(52i~n0>nClPnaoLM zr`HjruJ=Q7&W4Z8Ro%-zU(ngtTD-1jlXAQd5o_Ao%#@D}Gc2#{>iuZ=h9&AEt$#C9xX?GN>Q`Ob~! ze$=C5*}=4+VIsARrPUbD2hf)Dr?dZKbM6y-+NXvwH*BS4i2;+%we1JmS1*rI?V&cU z;0Txa{)Rp@hN?#KbJ`i)P+Co<)A=9P#YTODYt z57!#CK0zr|?2Fs@%x7?nyJ)@mKJXPP#BV4keC84>n{&Id_vjVdE@RBJGR#CFd(jYY zjPOyxL~D)3bPZO}>S@aSyB@o&uTBHl$`9K`_(ZSA1i(y83awzXnJ7CvUdb}~chzW< z!RuA4uNd0Y^lxq}!hs&;Q&O<;@pY}l72=eWV~IdDHg$@1oI|)`zQZwr7^HrCuM6DB zon$4iejPyIf<$b?zJJ4aNP$VcHM()}p#1mf^7Zcy)O0XkH+w^J_)%f+Fy$4#BB8`Z z46o*n04SV)$Fo^D0#AK`5=$&HOC&N&=wJdt5BJ;t+lW@rQ}0u6f=C7#C09~Fzdu3) zY<(#>s)-zcs(sYF-Za@~paL&r#T9X2~d~I7$Inal#C_@ZYhKK$>6Y`&R0{DLWr7-b(Y(`x=zZ;b=9x*$Em(taOy{-l$|&spMDPfH>tnpN;HLyaV+YT3d|>N1r-J$pWIMt1 zcpqoekt)(*&=`S-TWntgZz_4eGk_QTa2xr4uD}J+A>sIL;d5M(Q^M}mM!=?pj5wT% zDJyDW`cTz{rh78N5NG2!&C6%Ri6tFtRfQ=b9Z3i&Nlmvcof)!JSZA;<>7|K(JBzfq zFp1!Pu$+f=zf<(g@mDnTrSpoR?p?}M&f6zw<9pBDOLS&{3@hkxLQzLV{R{fi#$R6# zUA*<>Wv%dxfBP)_7B#zKJxduYSQHTmYh7w*g^ul>mrDE5X@K3h~L zz!fk36=y|#s-@n1-jB5V?kz-w8IdQ9C4cd^gJb^kP))CFi5iz=&AV1$auX%yeh%6c_3L7U$DdVHV8}A1mndB3Ir4JB zCXAf#(^Dp7voKe$jKhRhDtLV9`SJmmv4KK}^O@v&4YQ+=gBw@-*1NzN#loJZG}N4I zVV3&E>byjW&ixOws}FcH4XeCb1&y_$LZ)dGH%kFRB9U<{^(yWJB0T+*VAr8@b1c^? ztw?fT-0J&sUzj;}S}k5^LM0TIM_8+eMJ1A$Xy=q?7|W9yW|Lnm_15>r{du+I{R6Dx zePPO*`Z%xc-RBgWqaBYc4$@7CP9d3oq1+pRwkJ1T4~!@M{VfryzXIOgNM2pN$#iC1 zi_Ip6@uI;-%SGm7vPC7#R3>%L+2fM|XxqKp2_01wjt8Qpp%taNRA=$DBjYRdmnQX} zF-;W*T;)w(yR!X%OubWloYD3_Tm+5nG`4LtwlQ%U+je8yZfs7>#_MX(P&Ehz=bJ?>EM81NUg!b40^I{x|Kv7bcs6R@ z`ipz-$0lyP6PZc&2r1t$vOs;$6PdtKwdRf2t5F+8`tZWv@%}E!EQ)HG(?ZmD>Lj{) zR16GFD-%W8y}fwnwgWqU#J}~ zoGIc{<=uf z(QW{mz-7SD)jr+6|7{zDs3Z3wCE^*cH|8l9Sq8z4}3tNq-1(@U`n*DdC{E zf+3vU^!BuOQ;BNsS*r!m>bhcrM`u6HR+3{F6V<%~u}=6M?#t8eyT4WJaytLnrPJkU z#M$AcK8Vx|RhBeFPgL42ZPAI2ilnab!$hV_CF-TT;JYCps&Pp2T3jdn4V^k>{Y7<& zq}}SS9=c>NA+*zPyM+p=^Vdl=v<3TT#k{t^IqnbK;8ag=qvd+}j5%l4#Q1nv$hS_% zC&lNvGTZ^IWw7w7pG&L&K?FrI47`juB>U7!ndJ2mA}fuCrA8kcRpUZNn$eeD^>A>? zL}?+KMhKdJQU-rl9S*J(e#X9&?w4K)$XYrA9q+d_TxhcQ!Xrdv&gr|(J`{3)|5uIn3Nih zac7fnOSkeHT`1Y-cANGpr?P(lwZ=f#^`orlg;_QVwxXtr-C$J(CC`yd6Hg@mb2wkN zgOq1;zQOW3*Gb-lO{7zKd!90_35_NLRde%Oxw-~akRURC#?#eJV_#yBIn(VBc@QZl zAc6R2^1d^qgbOKnh6>&9i4iah2S$a&s|5-p{vo-_2^BuHB!uVsFA`Q88vpX#WkPwx zXkWHh!$W5~lSWcH;J2~`PM`3IVVG*gGA_6Cs4;DaEnQ;H9xQDK8oAP-8J5sYM1)?o zsfQM>g1kQe_W>5O7ONN8^f6T@`#=n~j_Xwi=Jk)Yb&rGVhfXHX-oq*4jpwrnO#~-@ zn@~KB_vyi@Jh2GxgYv{G|NlXkn!2u@#yCHuQ~LY*?#@;gezUuNH*%ZMBQ)pqM}?@& zfE<8jsUk~wOd=7huB%TxJZIS?;vsEWc{};0AKJtr6(&%m)FL7MTwum&e@9QOz2Af~ zFsi}ZFCxD;_|zHYNbV?Gsohf9=)$t7%>bWXYk2lG(FA*EGZeonUF|xaIuw`!-T77` z@)e&kRXX>Kr=qL4fI6Y*l6hdhcx^JU%!YoD6%B~qYZ?KOxe_p2!<*&aI8f(T9u8m{ zRmB-0auUxoX4FGV4=^zd*3+n(tg!1H@rDef_K+er}b^^ zxzlX7?}xY&2R6oW6OsE?5|&m7US{0hI=cS;^5YXS8X9;Y$afW&*It+qKt@J3iR!fV z3Lak&V!{~~JA_upz_#SD`J-_>)4{&BM=*jdtBB;rH-yR~0=fGJH`AL+3LE9c+$_21Ll@SCsDxPavX*SmG?BumIS{|qG`Pl7XUG$zX! zAT76+h-L2>!E|htVMerqDI9bKd158Aa&7|^y}y=8#0-{2D`BxCL}H~zY$8NnEf;@8 zoSs&43j?&j!j?qj>;(9xQ%{Nds1r71Nx3%CRgCibriGA-u(@l8>=^94SNuPaI1ZFh z^;YAZPYSI_=CX@vdJ2I-3aIyMVq#L}gpEZza{^nETq$qg%*d0mg;Yo>$wG4I8iV{& zW&=wOOB?WUMXOO;6qU{4-R6|eHW$%|7$;$lV>NOyC2F2M`eoS!EIkt)7Q&*l2uymXT(j?;(2{wlRua%Gx&Z?Ru`f1mkk+9ff-V98JR?CNnHb+I-QT5ieelCH*%KdCBih;B+dZ-8= z=VqG^g*IG7z)X90&5B-<)@V^uLSn(Dq`Dkf22L}=P&(PqI0yef7~(fXPZiy{7fGZ8 z9tLS;=><o8)-0G%cfiLBh(Q`S7lds% zNM50Gw5W;qa5$jLcPwDWe#CDj@?l0t`ZbAr70`~4!(5_}o4=~Q-DnXyJD;dFkSJ*W zHowj8E0F&0&h{C+E4SIUSqt5qg~aLj^=`UqL#KN49V-5L)l&($Z+Eub$nSbNne!H` zhJ%5OT`ErzvzZwW3#AxhBB7&mK1U4zNs&k4@-DOG$Q;-x-aTR}w;xQQjnxI2AUfYp3| zLxmI6n%X+rG0rFCIZk{k-7ukFv7m;-Sa#di1d0YE`Zz>^<@Ka@xSr+J7iU=7AjsHK zLB;{irnq6)ah>2f4!0T$;jFcvZ#G@z#xpCDQf@=7ptJt2~XkBm`Cko{Dd;2y*g^K?m@KiJT>!G1P+~$lRDem zu}{RzGB->jsd9G#+BbF%&ZA$IHK$#oHAZW(V?0n`!iC`OJx$A}$)% zT8RO9RP}e{`g1ha$j0_6_2(MzsgxDcy znZS^HlSaqM9UESr-}R;;7k^5Vb+pq0C-(zKf*#yBWo55YgH@$k%5>s&J-l6s;*!w4 zo*(Pr)MwXi*wEGq>4LWI{a7MasKy!+j{;#Xw9N$B41yL?ml zRwCN|I3IwQ+kJZN%gU^)O*B~jI+X7HJUO6R$5IOL`knUtxjePP0Zj5{D&6)k( zB#qoQZsh>srv&6|uaDI99U-u;`zm*r`{~SYMP3gBCA&4b=EuhkRi`awkA%?^EmfTY zLE%;3pB$g1d`uL?lD#}P=cBojbah1%LMO(&a!tw*dzqxc?{ERt8j z{Vs_zv1mL?^Kq*?Az>yNX44?P*OsrbWS2S(Bt`jVaNXx1@atCp>$J}tI7uOkho|gIjR)y;)-%LEmnw_x1@^GqFDjhAirOp z6}CNWe)5-&kU@&OT;!(j3&~urn_E#FDZ;*46?3q*MjIz!Z=wFIk^DFgDR|{+xu6|t zL4RxLf>viyY>CbWr)h>y%0N|C+=Sm_vp*MXH&$OTybyHNB1uq0K z+SWEk=a63k+D&+8up*s(nrUfKffy=!JBX|&+j4`s5K6z*hr#rojJ&WD7qQXgmuadCjr?p5BLt2@p9G+XzNDfNAJ+uilm%=R@? z?vI1%@fuQSvYcAjZv}*o=hLhKX-OjJYuuE6m5Ovm(#$oEqlK%t8DGNt6iH$d>ClMv zH<|vWE)Wm%*Fg50^9Fg!iM0XVwg+7ZMr9LvZ;z8QtP@LS+L?HJUB+<*OdPbTbh~`g-+SzzyRQ>=IP9-WKof*{4VsSmE(7d% z(f*L?!$R0RVJgz@6rhF`o2IRr;SKs#XoW_j>0IfgVJ*6*vSgxQNer{Sv|d_LNqr#h z#LGd6&FQQb;9M`lPF|Qo00XHuEDTp;VQ(lj)0l28I>v1vjo!H&=?yb49x|%qU_D)P z$sZMMKSZHVI7o4CbX#G0Upw!qEJ$j|9t2RvG0GvHA!%l{yZF> zI&n<4TEkCzJ`dXPOGrfLrzZO>pnNMtM_f*=f7 z%R?y8)s@h+L^o~f2wfmZmOO%)wi7DY)yp0#HPO^!aZriJ3zFieQL?LFPPr#Gp+B+T zoZx4_SZzLWOfpTPKiSUOyEtF6-wu?+Kc;-8VGJQJLrkdGC_RRsosG*Ht;f-$Mh0!P z0@)*;RThu=)==5g?WXs8tV=#oMzRwuvDPM=1qoZR7<%F`S z@shsmYH!ldFr|`No{1|=X6QiKir68{Fk{oSTr-Wb88md%3_g!i32795o>q`xfXStv z?EIzc$=nQv$efgIb;~RxMZ?Y@<^RngPN%HGF=%8NcScUUxmi2Jq*&pn#Rwh2b$Pbi z_9cFeNGz%BLFZ3i8Y}+sKY$(}H64DcQ=qCd$sJeb{cE!WxsvP^4JAV=M^u>|Ke5Bp zar-zt2Ada9?AVTkEUYEtb(WVOrhLO9c6?YlNOefaq?1GWIWc`Aj5l<#7mTvBXT*p5 z1b1#-Wy4S<)m3?gP4Q0xJpkXySx)_K+te4yCaYf6i1_L-nOc_9Z4=u00jYY=1UFX!R(5_EG^?3op>v5kXu3aE4ZXYTdFns61D(M^c(^RwAc2#8@)?B9J4a zpDuz^uOm55Aq7kRlpp{qLyE?=2FEcBf0X_XA-ftQx|1~hd%SdX*=6vN%o+nl*+Ze= z;5bw5k@X5es16Zh=05qWv2;xi>IszbenR*wObca6=_&g(^o#v&UX2purVMXR+OscW zlc0MJLoS!&srZcJm{IHN$t%|5Rnfo$<8b+l&x_}M+*f@cfhn6G)RDF^UYi3|oqTk- zxZ_z@r@Y#%u)`A^*Kf6R-=HMKg2d$@+<<)k${UspN-rJm_g3cjLSHqho4P+#+aA_i zs8z@-LcTLFxnGrr-=-#3jf&9o*a0x$sCb%h1{RXSDD5RAYObj^+G>b#hH7g;|ewL|4`+A0u`8Ims_P9oVb*U-pPf zq?M6VuE81lOp>DAk6Vvdevam&g^MPd#?W+*wVlj-g||XbKzK3V^VdW3L(NiizqrL; z?JtD0VYWRi_BcO179~w|IXwZtDCgRIZs3ACdGEa)fUJkQCh_KW8Q0}3XkQM7wFkh{ zx;?zrlWQ4$J7KtPK6w(K^X{XS)J=uynu(w41Q-)#gdSv2+}pQV@@aOjC`GL z4$JP<7dB~KD%=Y-CXWH+38ndn^t)>xg!WXj{*U?m#JzD|o_FKq2j1T9u01YO4v&Gh z0T&LJ5W#022N#QrER~!g6ugtxot-hx$LS!fXiHnZKBp{qmryT3#oEv$AZAj8gEQdx z&SNweNM2g~IB@tm2z1IFsVN`lyU$}(SG9l9HZJjG2d0CvbJ{7&emIkPSK4nDfRrgV{@} z##xjfgBOs)`!lvvX;uf}?UJ(zcCQDdH|LoRJis=O_pR*y2%ntXb4h8^o9w`e=$D^2 z;5KQL2SO0d;XaW*wxS}S!gjw)g+9W=+Xk-=8??Ww7_lP*MT`xa>|UbI`i#RBE3K!? zmt@*)#)rl>8XSV>e}035IhiMI)^5EIpa=P6RcO?!QlSat5K>i~4_+BiQWnX87e<^) zZ|U%w-|{vh;GO!OOOkn)7}8bR2RYyWX-uzuoJkF(Dq_zPjL-J6SxQ09ge$P-+*tw7 zi%aLm!^k~{Ml#s`%8b#$z|p~?EI$%EvhLBQ#WTDM8dJ!bj;qq5eV@ANsnWquK-@m1 ztXrGp%%;P~uA*F+fl3-0KB*LKzrOGB%FDo2Zj?@Ge@x|*b_)Ep8wbo~VcYvt)S1Ft zP&K?1O=Ucp08o{LDaG|==|bz9>?b9cY@h4@x{OKUB9iwxd|!?hfEsDz29mng$%QF5 z_)qEm3*wVf<4|+-JG}|O=-u}X)E zpH&gi$_bD=*CNfetbGIKh;>Egrf8On6OTsp*!1Xq;HUj59keO z^YfIEmyb^=l+jNU9_Y{Lv^}?fHWIW|HBK5*o~#rKKId(*V8P=BzlAaYRNHi) zYGF)tb+zh~E7Xc-5C*EnOqtq-hE3OJFx;#`m6NhB^oGrwu|iao>W2wjebv!q{F9Jw zqz8^^kRK}$I6%`RSt5KG6JU1J>At9F%9Pso$m%ZS78uW`H+?)8PgSTUEe7ZP3>1hC z`Aqxm2v!u%zf?x{_gg!DdB;M17#Ey}d*7y*y$qS^aDLZsC#>2Atf&3s*h+;dflM9E z_WnLblCAp%)m7`^F|@E*LxEVu*apoS2)h5`+D$QuIkJV^*$9t)zm)VdkMUh_tMt(P z+`VCG-Z({n);XQLw@@x7`LSC(!10PidCx?(t<;wCNK3zHfK;k5hZ3Dv`SuK-knUS} z_t=-cZtTOHp^!_g+kU+@Z+Qgh?Xj7Vn2(K93PKk00q0(@v2zP6sCXu}vTscpRyz9F z{=k!hMq+njF90>xGU*!%IlsK$dJXq-(&i63dp{|Ns)vMJRgK~w z1sTj;Pvd`Ju>w0sgz6r8Y@{XFnoeyC!e`wwySiao={Ot#!ZQzN^jTKaM(JbRkMpxw zA1S>Wd9v?b^Y$K^le(ZR?CP&Y2e9xPFDkrBnJ#sr)u>-;GYZ$p3A302;oyn=2{}mj zcAqys4kw%Q_$b5$7>`l`>^4i~%B7=$`{ix8^@7~4kJZLwX-`H#>c%|g3IXLB(|Q$G z1?-5qjko$B`&QOrp5z}Tj1;Wv5|QfoUsYqp{5P^}=P>>!TK+kd{J3m6laJIph4)TF z@YKpOA050*04ipgGAjt)m&O$t()muLvN4Tn$r~DSU6J+l>Y769gcOZqA)JYctL0YA zI`3xy$r-#tADacPx*at2%O8Al+~{huigYawLiL+GwhIp4yY>CV@ysJf2+@-Bj_vNBu1JeF^fTEQI-u&1Gmnv$j1ax3XILrmIIhKzca<>NCxR7TTN zO%oPTR;*0S^kEnyvL9zY@qAZ-AcTkSWpNY^sC?9PoC^;*89SCZ<7B_ji4C&DUn|m;eQ?4q-)=^;d@K+ngw0>;$SS zJ-2K~P4+G9xkQ(H5-%MjHGbOAM>V_M7E|Be1!-1WnA~P=)wl5cik#Us4|W8rl`>Wr zEsco8`uhn71V=N~p&l98u)7n$ z1uE+T8b)$6Ds@^!HWSbA%95arIEwH%xA{<=-4YH?GGRX4#bTv_V&gbe==XFn^S|hi zO=|XsW=%PkpMBXS?sd8Qbh}nteMip?mSc_7E#o|r_pj6_}F~wwYWGg3!H{C z&aq4NZ1@M4`~)8BWpZ6v|NH+YyB}u+W5shvB$fOqK)F*p+yS)?8}!UzWw0w@7Bn(h zGf5j@I#7w;$B2!$a6a&b)aW#tMmcZg(!yzTxhfFH7n>&GY9NhAaUmJAa_<)jgEh%Q zFcr*8$vHMA;Gts7){<-5<)_~zjZ{bd3I_RoSZ9g0&v*KixMByKO67Lt3^)4|-&sYM z%9RS|uUA1*53u12C#)hvOwzs7U{SL`$c-?QmAt2Cuq5S`J>SE?LHvFxb$qHwaAoZj~C9>f2 zup-1hV(4%)doZ&mabnrlXBnid^7(*yLq#3M@HyAw(+&pn0#DmaRPRh#mRZ5rCl4yW zStY~#E!6@SECMI;={x5)E!~WzmiZd-Fh{%?T%uR2RRi%Egomu;Rkl}eLrHyv#?Nad z$pPYN#L=>vp0Qc;%4bXp8XlZDrj`_c_)tQE8l5;@xVkI#1@jXED2cJb-^sr6P@ zURd*qFrr<2x1jyOm#0_)VqkGO9UC&GGBGnN$rhzROZWB7S2GycC)0`MDdbK4+WzZo z@bCHEaVhJyhmq$tUak!+wAtto5D=&&7rzrsfbO~B8L18F6OTe13CE_{O7Gep2;1Y* zR65pBJ%8x~a3cBbMsN^QMu@0qiSWb?eGmKztWD_-z?dmCOCu#@dt&gim3sMIzde{( zuSRtW=%z?K4@X5o03J{Vny__T9pM)*ZKlpVQ^sZ!Bd}CMf|L`W+VTgT)cWYqdF_7e zK8kKQQvDcOy`7;9$X^q?>|oy?wTYmC_B{3+I?Ao^!S%=$(<}(aP&#S z!K-L72Gw`t%LAR9<(kkL2uVrf4FY zd!t_SFIN?7XDRBTx5EJ4IVq;ljARFq9ygSmE*$bpccT_Ze_d4gq?8Fry3iqNGD^lk z%`Ow&C|XgYn-rHA9?dAT?eZ(eEm6-TS^)*m-P+<2^;$?#NW0T*ekgIAR1tMer_{6_ zQ_bddYyxV6*N|XHt!e_Nr9fq99vVbuDISh#X6J?E7pC)13n_^Fv$Q3)egrP*opT>J zlj}I*B=53GzyDAtwFI=*SyY5ly`{QsW=k(zFE;L(3apT@-+$$f@ILA|-&&BZNP9SP z7BdSH6T=#@zLs=Ktu$M^?!1u0CNt}DbfJIrMhr)l+`oOGZ?Ai;L!DmiJuKxqww?K9 zZMFCAC4EAi?u^3c6}%cPzuwm_wUJT3stx?zwm+nv3=76=(@4A9%nx( zY(K{vEoE{$<8r%$tgbsi=CtRE#B(CT98uhMYLENjR>FimxP;EMNtd*hjd3xI;v6Fk zAv=gd;pg2@LFg!P^IDvW`PD*r;%9A+V#-D zhmR*D?v<82#V%&neUmO{Wnuo6uSXjGhUk)Mj0LIYm5h2)rd#SOqJ zw$&W_W6v5clYCb{?!CtS`oGO=@3#hXm9?yKWC=&0POXkJpik8DGqm zNtkYxrkWcx9A*ghurl&|?Yi!txiBxp$oqMt=nX$7KEBYnGV2KZu&3!V0)#0HYtHuGsgScGKio~x&j_5Jc^}w zHA&I>c?kv!JVYTdAfQheytRb0fq*^P0OYdy^wu~E!erqxVZzc0Sb`rF;T}N*iizSl z)lNg#rqBQ?jU`Am6EYK(eo&OtU{r9j^PVXNrJ{8Q^|FN{UE9Cm&=Y^g25gdIP}pz% zqJhB;sh^HX80#}}wx+1T#ayxZ`EqDl6hGAn2fOxhz*_P<`ooDJ&+FoHcpFL*leKV2 zZiHUkouKUks6xK+eQnsg^dKK2-8#jgqxYbJ^rnH95)M#Em-hUlGmNW5Xk^{hJ+*9S zb7_M&Zg2j^s*!OQ z%Fbon$D(=MjoFOPK^y_HpgRFzNx^fF|C@xfVbTMyI?AM}6Fj zKkrOuT+h!Y+Kg$`kQcF_8MR6PMVm?=;NLbJ1O>xn=_ml&I$8?wq zluSb%x(Z3FeCqa79L8AiZwE)kY|y6nU>^9{?&qWpF$f&CW*%UCcE6hVe7KSaGhB2w zcgG@$Io+l)96ZH-=PfM?`T})$_=To9h=c$hQ@wg%Pw3S{CN{_=mTTpZyU9%E=<2LJ zz2#B5>4@yM$UtT;yYrQ~{-Jspz>5{c?|gB6#J}j1p>cn;&W%pC1W*DerGGp9CJ|=6@fWEX=azB2D&)6@V0Ar*%ELoMrc>cVaNoVn}>oa_gGKJnRFVlZ_EC3J8Jn zAG~>7xx> zDZS{&E9{&`Z~42(KUn2ec$*|qSvvT#B%0OQ+;$K4_bZbS;WPY|(TDr5Vf!Xi8mPT` zzd}O|I7gz&%>Z5qzPWBgt1OX%!a^ITk|3;76>+P98d85hJ&abx2Xf;?M)9i1+B`aL zGxMkSx*95=g>k8p_xSnn9lR9bt2f`or6`G5>bIJDkZ>n;a9uRQ-~qgUIZdv5n`39q zS|Pb+;@;owcwm8<#}|{i+)Wd#Ns3GIv4-OUQdKtz;!7mqRce_I{APlgzHgTWlLF)k z!0XSZN=N{+SHx!1+9|P#BAb?0ax>4BkQ&KtzkW@8v@qA-Cgj?629EG!&x}R7i_e%( zp2`7uSScE2`#8NDn^>;^5)dP%t2a|s8{Xv}DC{R?`mK=}A9K+zUDMQm$03N5+H)qi z?6^(iU)@=(uUKiTAi00HmY!+t{6R+kyI#LO2`f4wPOuA%z5>JX5 zDNDo8XDIAbQA+9VX0I(P1j}D5uior-*oFYubN1Qske>Slp7I6S1o~}Bu?-*#GBS4-ENML7$*;01~R58I>_r4j~dPhM<0dW1T&$+@~bkPrHz7Dpq&V652sGq~=;u zC1BnOPUWrLPHA?N|95}>wTgvM-X~iK`5g(6u3kQNKT)YY5QTTEod0Yc%y1g#%8$L(B-kFt03OMMB3d$r5+!)i z-kH|NV+d&qF`-tayvr^1YpBi#r!_Fcy!VjlYh08zkK z3erV82Hg!dTYOn!QUqMUzb2+V6if7=@owtdqd2uM!j|88f;)QsZTWnXUr_#lXw=QR zJ$D53$^^GvpVJcNA9u8A8wHXistiq252O)B?MOxU@i3EMm-hNgY&sZtMMFtHl17S@!f|W+_&z3S_sSnd9*%Jp3XygyFvjyk`8{|Y zgQv{WsUv!%!{_PQX>Ik_gtny^oeL9cUNJds^;=-@ zSH6`dRTzITVIw_P1FXNl726X3R46qpu^m5U@Ncp5>!gHy?)MJZ+FWF2D@0)z^p7>Pd(BafU(l3@LF<2`^oiw@IlwE> zV*V;BJp5#)NKV|=ww_Nt33!r0;*=m{Lqco5O)2>}CG0Asr^qLWCv`(-&StL$6nYjdYc!2-}C!H_b{E&D$=e!Im%q z=kI2$u?aD9*^+P?;m>zcPZ55=a+Kocymv3onI1q)2$~9w2jsUG4tM8g^OA}ni~-!2Oq>V3Mz-2v8Z8%9;Ws z48xtT)?U7sip*CgCx3@QzfSy^ecH5bITLAz<2kK7O__}t&D+Ocoqv~9SyQad!PQsu zY?{x@on)BXVX4l!RhG$C`pk53*c!F2i=F?C^(QbyWm2#9Fns#)h5V{N4AKwhYanK< z>4Nr_A@E{T?2sm$&Us1Tr)NP-VYZ|mqx|ZlFYm3@JDak?YiLym5 zgVG8~7=jv_e^_hLk^uz7ku}($358i1S8;C%s%hZOkWEnq@L!KT9F!Z`xLnTzsgq7o zX3X&L$%-i4^6sIwTPle+UTudpjE7<-RzBW>w31D&RDqao^Q3lA>zjk~ z9}j>ef45xMRm+5Hq_i+fv}nkj`vX(qN$I6c%;^>4?JfJg4p*7?8=}$T69~vu_?vhv(5#*Xh6Hi8g{Y`H)A|3#6BX zt&HJD<>#TtH3&lYB!Uj#fF?t)DU5+t@lICE8C z7~i*Ls@m(xXggz!Zse?9jF&A=hQoVrak)f?d$s`No$nJ%xac*U$q1gD2Z=q30leIO zi*HBzEB#@S1Wr!~D?sSpK=m&mGUMfc$c!r_&8UWW+Rf6-U&4h0N{>xHDQ+lzz!@fJ zlYtkL9vfkv^o`2Hv6e6h{$q@!#kAbr^hotzq-0yR*yr|La-#8mi|VHA^52xX#Qnw; z|0y(bwrCL!dm|W5e&3h9_tM6Z<5yn~yT*TFyTNdm=C1Ct_LJfEXl9^KnbQv|ttVGRAULgbHhM11G;NY)pW{mNnX>dUz(Cm5Od+`} zlOCgF@woq6K+1Q%g0*JJHKEqk59y7w3#)OFCGLH`j=WxVEvCZ5tKQv8!ut&*UUC_U zN2ejA=6LpPLwDZ<$+@N#ZSAR~wQsts^}99NQdHE`UITU$2y1uu%Z`RiZ~Ze5oY~v) zcvy(kF2cpI^((YI?e}Yx77BwgOS-&^B}2c4w3DPU;ue9|^>);;k|LwVmNp{oc;8o~ zWm$LE51d$PiR-g4RxhOx*)NlPsa<>}{+)Hj=9|Bol14WSs@61)nQkPD8g<-_)=%9I z=nuxniq?Xb0W=oatFHS}c_34P`sSzTgccu++V_MrbiA3*nI?Gk2t>is@8|0Nc;FCt zd`|i&G%vB(!UyqEj%N{+oU;NK7b8@^9RW>A1N(`>7zF~#pPPlXZQP5+DRq-6C#(J) zMlKBA7kkPW^Yy@C25Ar|7Ss~nwQrsXUFOJ5$P;8%9EOT!7(ouQqY?j&SW_F41J#W> z5IRhSMf-^11n`4wJ%=U~PIfdm!?w#P#@}`=4O?Yoio6AMDYL`ITKFTi`1u(z0Z=|k zXBQ<0G(2rUW>-$ogmYLf&K4D}W^L}uuv@-A1d)R5DvfPQl&%jsqwmDqFG@&-NvXA6 zefR7p=(B*P=Qut^Xxy)qnmo7HJxLe38t%PhCX%E^?!1>vuZMDckB&mdsi!qP_?b-i z4N=3>8N)kxJ};pz>pGqsQgx`s4cw)gV`>eXE75!}c8#V6^3&RchSHvgQ^6PFVNeO) z_jmmEB&-ukRtXFpvIDztvH(aSqQ4oSIKs8oBLB=+d{4qkQ*=Y^_(|~=hj2h)-7A}K z92=)hXd6w-j!`5L4$L*H2rX<4s!u&xcDwCm9n&KGB3G>KF-vQmbVNwKQJd1dUh=*V zYc=mUKb$cu-B*SwBP-{pc;pcBTg2V_4tUyii%Paf^K29RR)oUGfAVA0klm zQ3!X>s_)58*!K-xNo6YX&>!ENa(=l~ZU{qiqoVa2ZGJoM_sU7^3e2@^g1Y|Nm&=yY zi=NP8tKuC#E&0I3$Wc&YoIF3L zDn^Ovn^)duz-qvj2Fk$BxHWKbdBrLe0Yq^{%!_k-fwY6HgxF{p#Qsx%l;fZqQtWB` zTm#FASft6zh+6zs%LUOHBf5W1GR_rf(i~r5x*R3B)`kRq#VUxSyhux@PD3z6#rBuo z3w~#8i&9r9QMPWFPS@{&quW?Wt%D;0d}QztdLm#kfmQNmaKja9~7V-TGIWOf5lGY>hoZz3&699xIjd*g> z=c;cQMis+FVu#Dt5@?yHCjaaOJk=Fen`*ChJXBs~dGJn}M{?V$zV@TeYWq*KX}9x} zw#{CFoMXiu7Ls%w>n}f4r>H3crAYj_cY<45%$^P&QVU-(b~EPFA9BtGiXSceH{s6d zxzfRDGs3vVv5%R3jCmbJXBlcp$KK`P_TSEgN{i85)qMtx?v9b2O8hE*34{l2!0}il~V=rkR3(HFYens48ltS!P9gW~t`X{~t~7z+lPN zG>x`x+t##g+qP}nw%yaVZQHhOPg`F<=e;+7LDjCcaz`Q}h!;_CuEhgi-{K=r6dDWTaa)a}y6L0##f^4d_#2*&w*%zj%+2dr%R2Te|^3 zSD7!LC~2o={+D|B^^q)ih!!X1`#th6_3SUw^$wcih=R?DBjeJST9yCxt7>8*NC)-L;tCnal z3wSv#S~PLMMbfJ@3bK}>RmytEkNIAvdDa0*srFdWFrS#+xb8kIBW8k=XU&nDhlna% zyp40+=a`&uyHiJ%C#LQ;HTv3!&n)RZ6nW=1Et_}FS#5yF?3;a* z$4vbA1*aX#h9``(3DL^t)`3OweMetMlX$dX^vfAP_Pp$ef&CP6pI6%uOkQou9nfqVY~iTn&k ztU;-e{^sJ&hcaQHm2^Ccnoo__9^G&@LE1e{n^E5`PkZ3S?*HMJz8ImhV+biHb&Dsj zsR4o^a}LLmoOh}+O4q3j&BzuTGCOUQRgIzZc^@Bn$F|qNjWS;~weQpP+3VFx=b2=w zO}&5PbWemFCpJfaZ`=*Z9ga#Ykv?_fDqp@2hmX7PqWJjd%e zb2FGMs|7f#Lu2E2J2Szm3**JA9B7aD`%V__GE`0D0kG!~{Y4vni+zn*a#I9l9u+p|x?MSNHp?*hp zP@B2Wzz3D;M@qj}ZJDH)_4lTp)x1hCUf3*=V-_SNw%LEm>m}1*Ehj(Wm1jR2%#$aQ z3hjiN!rM!a8t0CE7*<*mCqQc8t;6o_svuyAJTLIJt#+v^7@E%%ny?OCylKXDEa-T+ z%)Z%hz1egsTS@x0i25Woth7DZJ)Dr{-VE~G4aLTO+;#HQ#J;UYeca)5jp1``okXF0 zv~Y%ndi@V0*%$V`GSJ2S?O2A_Djl49s=C)pIYOvSx#}y3&pal7D z3Hh=|_2N^?#)6ZA@inxYCoDiOi(ms=7V&4GzX>r$mMSP$%u9zCmM2@=r>JJu;4OMf< zu8Ytv9b`a)Y$BMQd~=3}RH;n4TZpp9@;PPeXla|`SIGopyoFE;Gn<%-H64dEg|CIU zwK16IwE=`~IyYOlq~DplOKou{kDC4}$+~6t(P3*yBtBX(QDVoCHzAR->Cx&sE9|$p z24i88tcy<~P>o%OstY%Ho@$Tru&5Uw%V`Uw1v6BskA-)bZk{QRbv`HMkcQwC3?6Dp zG+T(Ct5s7sPGCw{S@GYKhH&>JDUX3}T^J?%+Q=-+2Z0UjD31py0jd-#-HGN)F zN1bNj(*8)(d3<7bS$3>FG*6?%v_@sA9c$fv;o?+VY`TRsK0ZA+GbQ6xzs7yqgViQ3 z3BEt~FMdYRM61g>hR-c4vV*8jJ^SG?iC;qFxa5al#w2CrW ziRZ_mr#djJ0GyzcrQpr_5erLLyfb4OkDgW-cH8I1hU!ZR4mb8{oRuB|gx8w7GGAw* z@nY+sK91b*`RMD2xWC#N`In1zTu@DU`S~BRpm-9(C|BzU#nN;n1#)j?T~)&nG%a3O zcp^W~;1-p`U@{n;M37lRCU{O@}h-Sc1gpnNQ1g=0R{Hl&;6NXec9m1DK-dnbkUn9`mSNEd#J8Eib}G-w(t3)p zJ|1-NKMJS0KD{kC;SHC0%KjmWbxg~Q;U_}#uLzea z22XO9;Rps(v@|{niIMlx-98pN!oy&8lbD1WUqtK|>Fg8S{StU6P({#*9xs%l8K$Fo zowy5cgx+xFWyAkioc>^8^PckMVlbQ`zl zO_Wx{hoJ=|q#gg%o`KBMe;`Or^z%|3peSy@DJ<&y zdSDZ`vvqjZ4PB~!UGNfpfiwMi`tF~Wd>gxH(=3aU8(bakPv>4airB>}PU7N=i6cq$ z%KE6*=_7CZnQ65s-Uu~$e#t`k<4{Exc|*{)>;1?-8nv;;Q7$MCN6A+mADj^G25-V! zVb6R7)2%~ohyw!m&y>$NRjO+)yJLjU4fFc=Q62quGLsN*wU+|DZ*sJUtnNvux7e(z zO*K^Z&7Mju8;Qx;$yAP7=E%CB~M#*LPgE~~2}a$la6kpi+&J$1zy=^c3sa)y%Bt*Dln~>}2y6Ti22F^hPzmAwAoNuKE@}V?> zYpv7>{xm~Jr|;J|{p}Jeo0^%G!9#q^&Wdafl8Z&uoPLk6tPRYjJ4b+KY7k@~XqLq) zg|EMTAHEM2K+mIJx-c*j;o<4RmX3L>4&#vz+2I7ytC=6f|H3R|9%&7di$=2^Ymrl~ zlvs~qTVm139q-^ZOp@6oYDyY}%Wa2@ma|V70X%{wuR#CYr;G9Bt8DXa8)GYEIZ3~1 z+4qK)oQTY3_n%(Z`5M#U!GqWTUo;WDHcx{z;$RKHHesdv*Plf-ql!fl>%7F#G9j3R z)(N2xhPoP5gm& zSiKDlx}=35FS=AiG-11mxX)}~CYD4Wyx(e8DDV)=6dy;yHMFv8L?I2<^zE5(1-(K^ zqzv{kIa25VM_1N)S9R5vt*he4iYrsjV1_g{F0wCKm~zLMioZD6V<#otQH9BomHF{c zR6U~Pav!u3@jUk(y+z?bj&{eBFPdfb}7_W zzq{VT>|v>})wMJdZzlNjU|aZP3>-#)q ze(2%FXf(07euaq%EzbfIj=E#_OKeN}t@-X)EH#AQ!NlrI2jJa!j|<0!agnBI6K;{s zrLO8K-CcLW6IIo+Y_1q3FGDZ%VAloC29F`$zZJ^?If*cH7T~5t3@7-3l?=mL{IR!Yb zuNS{7O<0GEPkad`Y`3V%^h(6=mP2omgl9)tvTSzlF%3Z8kC1MhaB-i()OyamSpVHG z{U#)426ugmmq1t$8gSk<|Ar`{Zlxf8)t0I)8b5a|z9(lVYk##E5`4M*%84*hvtaZJ zp&{*~s78dQ3xFT`rmuIZIkW~|kgQb5hl~kuc;LsT4m2%q4|qOtZhJU47ntZ?Lb#n6 z5C40$(Dq~&^zv$#{>AM5bn&q|?Q6|~T6MVKBO4(kRx}BxvW?rDV;q@>j3AUhan+nq zH(x?gaww1--CSiRdcx^D-`9jy0lBMi!bJ8|%Hp@FFy;+Ikq?E}>{yabN3DqXx# zig5GO%=!~PMRe9cQr_4vRprO~Cp^T6aS?cUFl8NXB!uvHEqpGjVjBw4Ps8CSlz9fH ztu#WcM5@wY@J*q#pzcxMUSL^J*X=&p=B~#<*~lJ@D@x~{k0n`z`lFT2941hvoh$Vr z8#4?#)Zqt7-=P5Ca^s>Me?iqX8=rIbAK;h^FnnCPD&bb#if*IaFRh%-z}7dzr?gIH zvG}g|2WATlLhWVc^v_YcQL%Lcei>atm%$GX z8#V2W{mG{Ze2QWfz;7N@}n)@>l+2T%x4h92jyg(hS~~!S|xEqcl4g zMh>2>uF!i!dIHq5(Wkqvzn$Le63eq;{lGpV<8n*WoxoelGN!Lrqq$`9C&bw_D`&@iVj;O&qe?kcg3&xeDa8KQEn}$m7lX zch}gU7Lb>SPycuiwWJ5>czu*|I(1Wy-ixly+nqp5l!#!VKzeg!(D%M@gQ;~GoilvS z8yuaV88@5&ZR^`9!#=g|SR(RdGRhwsJdq*ix8(Q@$zP{21Rz{#*m~*Ok|AAV6+Lvs zK2~cD#emOJGGkd4uGi-+ho7KO&6);9zATG;THo@S0-NM6;qfnSRXe7E@UbqIvc+20 z{!T9w!cAd9p1K{@Qi#fx`LZ!t58D2-Dv63*(;W5AWy*WkZqoAG_1~#pT0?NRB}2F% zU5vffcs?ZkW9pIt^wj{VyX55YCTsC|4AIsr3cu@#oo-owL;K`nIAWIRh zU(Arn|Ckn(4tHl|&?-!J3Eg-WPNrP+LLgrm%(qdn6M0lY|5}?-aC3q8_KH8!s-bf) zQl}USk>yxP6~D}VbHKZJpd|Pxa|4bZWwT*g_Wo}4k23}*4vPMNp$Uq`a4J9-mIeqe z7;c~uSy07VId1&B397QJlAr_Qjqs3Hton9x?=^r))!z2musQfcwG^o8ld(Aq57zeyACCue3=jfRB}fZ zX|-q>6Y#$vj6K2}*|%4(RxO-Y*U{0@(9lp&FhCH{s>H|&a8YL;31A1yQ+aDCVpdYwK;_@y`EC-CLPE%2QZ$V!SxoQPOYtq96etl(;?2VuDL;y ziH2hN7Q-F{$=K0hTG+K3lK7iy)!cD#cd}ggPQgHGl%ouK7M%|3w_B)X_~gCFg8Vr? z)dC|!CeeTLF9nx|E9;MwH)RG7Of1r@_|$$4q2ut?u8i$I;2Y>6Vv+L?*6A8Xo)HFf zLD&~0dWC6j=lSVOD)lCWFWbVG>|C%sB)x$T&8=wTQoa&Oq`j(=9BB$nNUqk7l9Nxp z=Zqp2^}oV#_}L4LZMi(@f@e{0yfQ%T9AZdIZ5V9E$JAE?U`H6b(c?dE_|KLEWWbsNfM{O>8wAv~Xg zm0MgD$%*mq{tv*SiQjf1OmV-p(@i=slls@tq~&l`PW4umQ>bCVICiEO2Qwg1C(TpE z5EBJP#@3fKnQ*!#hz(N+$A5;GBMG!Vs%VG1i)3HJ_$U9aj5C9Px>idmXE`E&hMjDw#xUe9qKg6;*$$C>MDXC?ZmB*^k0*egZKp9s* z5IXgY9r8);tPP9f={qNN?t&#G#!wIv00zjA0D>6I{iH!c00So&xPs#7D&~K5Ocgh& zl89(Ygsb#w;W+l|8Z)Vn6+F^#a5o$v85W56${a=>A|eHuFP&cRTnKnTi-ebgd*c%r zuAAa^?QYu#KIxFr2&VU0tPWWNA!HU;v(?$x1T>F~`?jwG1P`r&3MJ~af}u~sMp6yx zZ7ZSy=z6(P|x>@i8Skp=9kZ!MDFAyrH%MOf4#WcMAEQa9T5+a#$t0g>_McjC|J>qHns5J znDyfq$;Y2RcyppIA=q6&j$n|{?vXc~^73xpQ>^iWs7=-?p^IYz+a5P7@TXvhtCkJ7 z)fGmKk)f4@t+bDGSDG_9Ur<88syIPCUryWeu&5ht&2j)QB^!0prMccTG>9tw5W$R^ z=%&S#_yFTf`gY0*RP@N-94t2cXj)4BjC{kOG#{!76F=V#{o*Q{(u0scI1^L=``qOQ zm!qO6lnVT6!8OY5MgPoEf{9S%tdFr&T#l6a%2uRzGf{;XT=o|hirnGKXsph254?DU zU)qmPC)C?=7l6}MH+l9h#z+jf%*giVi$4WolqH|#t5=FF6h9cJVAfTXM737J%`@y( zfx@hSrHOd}W;Ww+b7O=m!Vk(z7G_i@jeycguFaW~PN}crJ6rYW!3T;hgjD^muRaa_ z?~IwV#D@^T2b+i1KRRtkI=*A5z5o~-~5<`T-I*gwg~NvrPNux&OsEriW+3wyXQxALhTX z1NI=%sJ=2W8M?_a5Q85@LHCf6P*ld2-x<;6?XyQix!%(=Ib|8=I;`DXcI{HZmK#$6 zp|s0Joa7i1+lps4ptX0S^Ai{Y4|UGT3>$WI*ya7+?Wmn$OLOf!jcEP;T9INxZUIXT zdrND`h~s`AQ>&Hhs;Lf>lL^QjO@{Dkz|Z>%yc}&qS?i_7$OJ3G)h3f>`03L0!sNFQ zQN}ElF6F3&Q-=ZNlAIi|M%LP%Y~Gmm;@3X9swX_=Iv1hZXbqc%2Oh5o6{DNW3JSrm zg$^sJA!7Te2^R!~LkarE79wGG~Q%1sZ*r-e=n$(P`156Zk zTxg`{pkj05X~#+C0(nHuhlv)?pjqI6Hv8r-0(zfg%7AiW?AJcdxHB$J;*5!|MkT2& z64ZbItAYaI-HPhd6)-0+8VKO&yL*-$&*hx{Je=L=(j1RddoKCk!_)kEQ^=41d|x;q zdUhINJ?#8mGylV(dN1sv ztOWLn%L%eE&uz?4x*myR-7`R|T9hj20aNvx@myU&3pVL(*w7td9JoR#_;j)OcEb92 z4Z6*}g*@BPhdo^S3~>n;27OcRVvGcdcC7=ps^9*Z##@1Rs}L|I4j$tF?-WF@1d?Uc z7Tq$xo!3;KMVgMLE+P9#>Z%41cU3WapQ5?W#y;G1SByrhk&=}wQ(;PYvKZ#fRC4t) z055OUWCK&C*xx_W&g5#v5q|fQUig8 z?XVBva7i#Z%M8TKtPD=0dZ#+OY@N1TdY!w*F%l%Hmg3_z0&S@P4hG1So{y=X<`viY zU;%PWF|7CqYq!;*NhtkMW}*rNg3SpGB&)no4Ai6;X_Pq~7xd5<{DITyj(yq`w}Dr7w;~fx$CHW7aO8 zqdmHV z7Jzq4r8nl@$jVlb8A*eM5kzT%5k?(oVA3*}DIhY9W6~a^AV$oo6Bfl|x0D`98xP!m#Oo9QIU;bvjV-)%b@2`XQXNkx$Ob2l zG?P|&LNLL-O~KCc0ZW`z;w>&%x-R~6jX(W8&$V0e4Q-Q$-jdL4ax8RQ4Q4oNl zDjUNj9em7_&y*LB&?as@H%0Ph8`VyBw4ee~D&~%3qGOW;~8A3B%tJadvIlW?$gChlFK^xZ$64#8`R z@KhWfIK=eJ-{CI`xK)>h$$?aag*|${$_Ze_xV{?2;C*k1z^jF7!dGf1-{puQeV*0u z=c_miE$PaW$EeIN`1majm`B-Ep*4mX zG(&yt-wb6ct^O5vcN9GGvam8avaye@tA+yp|ATlefqPKBdZl8q)Va1hIsVN{$y%*h z-f1at(rS0gle6ZG;HPit6!W@yl@ivW94OA*3ajejk5L?a>G7tt2AKIn9ddO+<> zIHNE0Z+GyMj#z+8lw^&3fR5l4e1Q+XpgDe_3HD)=^0j?vm~sr3jVtYar~&^&WqA)0 z1{2M5x9YJNUx5*k%LN?6?mr4>8@AJocw$C@O`+Dg{jH)HbonIU9kLAO=-9$M30nq) zSEhV=7)e>6i21xp{$Xe8W2uL@ED(urn_Q)bLNL+ZZBYm}j1pn|pZ(;|&RbGF%romj zVl!sj;ktf_T$1PDC>~Pj6Czr&tjZ>CK1jM;P(5oSIDbU$4D)5mQo3xX&TjPSb~1hx z_cyxIc#PPr9!PYfw{n3*JTfv0Q(d#Wu?C@r#Xb4Ir=`-j@@*8l8yT^>ZiHvC&x@oM z@PF3##`9V@;iz9d8?+iSW%K`XY@Dfv#0u(tI2RZ}32nWdapBeDf zh9!aUR4FZ=?{nRd=~gTcnqk+5>Eu^gFshMyheh2uuI)1+?~gL=JxZJqJgUmz7`S|W zjmejqYtYQt=D`O+Z#bPR#|$~@D~j2RdE=%L4eo7Ux9g&?x*7cMz-u#s81*2Tk%tSx zOsC6`{SHW;^}KO(HSiAdT}?zbR&&>%uNX6;KbNrDVR{P&F3WhbZcWNAo{0W$PEdE| zvtRlenUoIy`{(Y*AkL+e;cmT|`0vJVz}s4V)7-fZ_2=}i(&vwLQ8ie978N~oD*fIm z`w+*p8eAALLv#k0JZ}{bOg(5+Sv^w(IA{c6PQ@)|B2$eQCa8G-bR^1^mcKKHvCC;V zKoL;%wI;Kl`c809_+dFRyf~0JLExRDMhcjT1oRx^b@yWiY0g!*IGlZ+8IboA8oQ__ zD~qDo(m-a7ZVWFE5s%n&I?~%T{USR{;}87?5uG{GzN5wbx_iL|&j7I@YQ}liW#lwW@d575~LNu+Dvh9Ib~HJk}ao z@sPh@Ap?71(9RCDaWv6xnw7*UCc|$$r}AQ=GgQ-aTs|6$1_v_t0xeTC6y&wJgvjaE zO|{64##^UKe3Lz1SqYK%`0u0(ye`LSwsj4Mn%lhNyl&?YtlPMSeWZBYk8hDfcIGi< zE*lQ-vRs!9+s_|z`dx_mh4Ll;H|l^U7L|dybiZM>&^q!CdI6Wm?z?srt3)6e$pGpb z7c7OyIQH1dtc%sN(ZJ!=8r28%-NBx<;T@?FK(r$A0k+c>iEqNtx=z?wOYn{1RSBse^=@eG#4^cfPp^Ql-0~IUdn4_?*SR$Q z$OlErE6tnPo7mUWfQjuirs`L0M%8R#kYGXXwAoA$zD$dh4#mYqZY`^W^OJ)A1pjUa zjRah}ME6~H0Qmp@w2-*8s<1bZZH2|j`M5|wM#Q@vQJO1pz68KEUGQ)#?Z2LVvWV;C z&zCTV2#F@*?3L)BFBk$AMI-FAX^gd;rR{qaVd%8_sm^Hs=nwegUIG5y$uF{_K8opk zvNy+B`^vfJPCERl4MXmVn#=W=A-m*5lU}SpCmD6yTrSO7U^hd25id}3>vm+qNB~ka zyiB&=n|qGbVAx_;opw&gnuQvcx$ zsPtS&*rYKdWu*R#vb=KR%T>i!otG9$NfmaN22Oady43yJ&fN*2K$ws}p;WY>ie!?7 ztwRt@*H7XP>%gZ4i_9{S(@Fw+2FA^_BL`O5HRpz?IRkpjhbj-!cH5n$>Mt@ zbE2BlKd1FvzrC`Zoj1`8&Om}=STRJDD>ZR?JwgN8k6*VZdv+YR9gowkfKNG2>a+w1 zSOF9PBmluCk!9E}{Z4mqYLr{SV(@M{S4zU*JRq_%3I@&sD_)ShdawD`-T(vW;BJ+0 zdzlgOKKz?JR=Z^Ml`G7~7l?N%n4M(}_PFyBuP$u&En*Lgw?i+ZOP@e-XRNAg<=omd z7;47DE=85A&u~3n^8#-K{T6S7H?~SHHyoQYh*C13yk*&rUx5kg)`t{m1thZrBg>O` zg~%Her7|SVX)FIa&0i}e_DZ8UIq-fR3vV)G%iZwS_jd%Sl(Y;>fAm@0%F3~$bNvP{ z5#5=H3X{DGr@eN-M6SRu_gJiOU4|)-ezwR+cTAUI;)mqjZe*+B`6D^wr?LflTmu^1$jTQA zN_`Y+v8XCtE{vhGHI&jd*VH}##?Z0*>b$ZO`K+1u;Ni!2>vVmKN6Z{f0hdIfPorpJ zE^9u--0tE$6MyDz);8WbzRC=VZ83CujfrSB-J@g5zH}xt3b5+&xixq(=zIUPb`763 z#N|Kb>@h27mdpSsZ5JOGJ1O*Zw177`G9PNCdw?T!WUF#{LY0YS`aD9tXW5s2k;jVM z;)Tvad1)g?;5NTFgS@nrvgTI07ci5_4{ISaDJ{zW$aD1_rAm)SS6-Xje29K>rN@E^ z<2RG``n$FU#ZnOl>+b^cPx#j~rC2VFS7e6D2bB5&SDHcP5E1emggCRUbGK)#R}y@k z;qb8}YQ`LQMY+`>LiR#72)G3hu=vg=n&z#si}^#GLj2dc2zRoiZY-F6xbCoDYRu)=JGE* z|5uA*OvBjO@%_1fk4H)RYhYeD9^PVY+3WMDl6lWL+7HpZTOYFdkTq{y@zjsCQf>R7 zWg}4g2?3XV2%|cL|2aO%2Ze5RlCST6mwOvB=DJ+P0BZnzy&9O>U@#q^P+LszrwN<) z;gIrfzeqV`&e;;*oqMI6>g~Z~3JL}`qG!COVAZMF%W87iQOTJ|4&eEE@T+e4l~E6R z6%@kWKP;)f3XJ|oSG&b}oo&}U31ei3b&u**f4PU5Fdi|yd*ble9@KNss;+fylGdsI z_~XyI8)mEAtKUY*&Ki6L2L5_E%5^#?ue!RQ2p4;6JKpF@3}TCom=-j*l`KWyht>Q9 zQ(v`xDCG=m_h-_LzCSBsxy>H@8H32`?+|<>K?0i&A_SH3%-kBdG;W1-B4X z4OOb_LID;o=6(tP!mssfR)QY(cLN@6_uBw$nrz=^o|W@=;E!boiV}5~l3_{@R;M9^hLaJD@$G1XTmZDY#$NIT? z^H58>#U~f=okNU)iLHj(!jnL!-w}*c+$$abo`1RRX`!(NO}7Y{OQ2CSu1eXI&RjFi zY~UfkE)i-+=xzg1;mmB*BY{_7tQsE z!Z^y@u+Xo&**5TMpq=ZTz6#4!G9;T3b8_W~1B-!h-a@7oegrNJPJUh*pu_V)BiG@b zd{8=nN6Y80wtBK+Qi9{x2<X5FNtophx>HzkeYqtZpwZND)Uw^NEW)n8_Fz!|) zQmdGMc#w0b!9c}*4U}7)=nnWV*Z(QAWNF>VXK@hX;yO?(Nd!b zJ|&Y=gU3X}t09I25_}!BwGHdlmkl?;Nv?kAkTu@`0O_7qkO)+!O4&CsfC>TX@Ppc5 zendqY_1=3$O-_k>s5Hn(p>71^3Q|jC@L&BdP7P3J3137g_q0`qy2=4L624TxnI0@$ z+3cV#ALuvjy}i=-zWo=I^K)Y-X4JnK5+^!Jx?AiMp>z%iHnIy0ev78c@&0lfr-2Cc zbjhok>bvq?4T`|6%zmDYtl%{eWTJ5%Y=F_Yo}H7!G{pwSVLz<2;oS>~=k^ULffUr4sTsV4w1hKVe*(h2o}4=ItFM$B#0d;KyocL5V< zXAQAw86Z)(DuyRz(x!gWr@QNoEOQC2Wf>@?E;!uLDWL$pUjCDJHbIHg%FFsNbMBE6 z^lUguUu;yPmCAtx10Ay2dmhED2nFTcvXcyY+~wZehhK~3z5J{qn(i$!ItFMaGPy4g z-Oq=Z4ylBiT^3G)sj*no)kVxzW)jgs-@c&ll%19{8@whSAtal#mxGZcdpp9;gn$^4 zSkkvd=EjQ`o~#x8dLWPF4V}(DllO>cTV$M1kJH7V+eV5fIqdlK%Q0fD#+{%}+@k=BF7(j?nEj_T>k_HQy zzqdVcKbyZk=2pDclT%Yy8_ibg>gm#C#KQ3;5=#Jr>Rv=S&;utx=VsTtoB_e^5(vLl ze4&JTuWvU^Um6OZP0PCViIZ8U?amxGldi+xa8BzTdsZ^lmF`l}>JvS@#;}0{%VeEB zC({}BsmzE7P=h<84G#PAEKefBCP9n_F>KtzYE%hM+8@({M;OPc;4ZS7h5)-b#|-p< zkQ$lLT}8j?;WBv>V#U(5$lBUBWs7 zgqD~`#4IbafOoR1CxwI8tWRNu->>dk?|k$^|C?d66eV=s|0{Rx{ob-B_5jK&byDRn zk)e{hkfUl2{8jzjz7*UXjzZscKZN~0n&G)es7T@F4Fa)-hx?Sg@UK(nTHWBjOy#rj70QvNRM(KNY3UDs75 zjw4LTT%c-N9>-c&LC0My0%Rb`_6Fn59k(KUqj_iH(jO=9w@cFj9|Y+vR()t*tX|TL zDR7`EM`aucTT6mjQ>CVrF;Z>^tYTgQ3$;)egj3B6LTxr?-EjpS0*DYv1at5(?L8A{ z+(i%;W0JqSN#M6%INuNdhWiqSux_Q;3u6PccqDHWIT2XMu>9ti1p_O0%2dKy&90{H z1++N2y!ZN>g=f0`bk`w&_RAJR=o;99RW8cwv`n^NbVjcQgi9vi2LEpyXx;Zfm>fdm4GwbrkIRsC*u zD_(1N!ZX~WokUeSbtgjF2EBpAK_^(6#G7}g^Z`cZL-T%v@qUkDU#Fh^ooHS{ipLb= z%K^@+%kSjrzv%@Gx?UADnaZ6QLR(#qCB^+9WNoixf0V^jHqN0BA>P@*7E-gZ`Zlur zO!p<1D_a-|hlvEP-|}0=H?8DHvnSTl(8he+Y?|VYS8}|UFXr-MelwCJNwi~uyumi0 zh{WTU&1SLvVMe2AuZu68>kw1=-QP>l9Sbamtyd%gx9G+PT-hDq6UN~~jMv#t2$v4{ zZdCP*sQj{x5589efjQPkZ}}w{ii2vlXjXKAnPX`{yW3#jXdW*GFAyu6>+oi$0r}H; z_xtRzBHIui%{S*WOHuFAED0Uhts+X0zRz<0tqsHjqg98Ww0yx8#%Uu{wfb)W6^+od z3M4VqP_=HTV7-fEP-f|i^d>0PYL;0#ojiUF)gOnx#fcMp0qL3xSo+9cEaA>17J|I3 z*cU$&-a@}ynfw|2T)k*|ZzbRW7Fm=IHqg^TeAtxpv@x}nm;&}1o?2N^6T5UN;R^)g zQ_J*$2co*(2d&j#?noo<$DrK^nUe#mhYKE7gHRhQqBaf--GYKQU|NP{DG>-#3uQpz z%%RVvI_*_|wWLL^*KNQ*+a3~;s~wxoEL@*=wCHx|N!)P1tM(jDU<5$nR#$rEs|gev ziI56N1#>knn!?B!2ok@k5o1sM+hJ@AG{8t27q;HWtE@8rUf^P%DEX_`%j|S=uiyOqVr$Nr^%Dq zx%6Fm2sJ7nQZIAai=P+-qRXsCXqO)| z$d=?%J2@>@VfTC7FUry%I5;-a-!0Z_@g3V%>_0jeZGN-O1qQbFGdU82)ep`Yl3;Y1e~!#wnV<`QD8JX0g8E1L4UhC6HE@)4%>N3DYCPDi+M=8SVpp4_#;N9l z#6Oz7R#~JB64lBR$Bw;8G#~*eAsRO}@iUN8Lm)r+QdG?T{yLujxV4By+eR1n~S_r%BkCog?{(TuF^GwzZ>5W}BR;M+7wK7B>rJIXC z|7;-JBjZ_2X=&Nm+}0aR@2(L!w0{;&GNm<|Oy@frZnf(t8um7EB9Tuq&0MW4Zrs=XV^3vJCJ3xW6- z&!2a~KLGgJ8;{~Eh=fEU7C*i(>3g(XY$lhXs-&yXUq=IF{l_X& zxuo^kTXj5o3)m^y+y}}*?FN*<{4I6Hi?n`sVe^>-5CG6i1{v{$v#mKc`$>nu@N#Gl zZb!~7JD^Aj?j?ua;fYWZcq^{M=QddyK^BL?<ljZZ8|}S z(SYw@4_qQaVd6BEzqziw*yg@~3~|CClQ@hvV%#!CQ#8>RtXf<<3azC~_z4UXLJ}R3 zfB;$=4oy(MH2nxJC-Tp6s+#hUu-Ib;+gZ@lHp4%)ty?JgK~0?BD@q+30Qwb}S+Va5 zLu#h~=(wH+j8>69*z2WQ4GLX*!~%yI`m@uhA246R;jI5Lam$p^68DIXi(6X5`8mH9 z*g$#>BL?-NLTbib4B^vTCKm9dBj*q(7Y+Mj?TpI7mg=|eF#4F0bQ4ipI`*~{VzM-o znbU-aRqEuCGg%s=#M|*pAKedk7eNPIOOv~A-)53uRhvu(KuT5D;~w-(&7uK5UUCBq zCQ81AQ^AsK52v^N@V(UJN=1Z;Rs~Bnp+$=_{LxK>^(2vl!>7#URZ88de1>u>j(>k6 zH(h)el`jy!8XL&z6>A|*!GbV}shkOcMlYfWblY+m-3ovLDCNU4-xdWdB%Hqy{&X7S%eK5Gd53hY^GO{%PG-y;8!T;7qHNs zTKBBuAl4+6FF%dIiPLLEfFqTjhP%)KKPqV!^z4F?AW-^w_;DOyq?Q1TK18QyBg~+s z{feY;(W>)~5U8-;9hL)}d458*YJ&)()w|jr1(;D4lvF$a940cx=N0wZuL9hF4TjB*#%l?(_sQ`&C zYyP-S(qCw~{b6R7?T`>^%V32UdGy26$=kXqDymG7Ildjt`iIk=l$y@Mm21vde`rjl zm`jJ0T(H&FLXaImY(=kLJB^vU=W+?UZQsnZ8dA#ua{gYN?yi6R&5X5vP^|KCPIHHWVsn0gS z;;Bez|SZn%!NV3_$Avqj(FGbf-96#BoJ++jt)-NQjO z2ZA7q2#2IpVNB>ASncmsD$PASZ6$2dx!)VFOG9}iAuP2@d8yM^tYSeUOa=bzFFmep z!!^Y6P)}y0lEuWVjg?RxZUO8#doqa(dZzbHoRts|_ZBAmc&0eL`vRnv=nEl!5=(92 z>MEl3!|}=AhL1v0GWrsEn?0-A`Ie^2eVuz0eCJDR`LOmCG-f|%xwmQ^x%MSP1FL)O z`Ur6q(-TMNn)-}s_|8Q9G>wbY=9b)~syJ!R&_3k28+d+FsA&fKf+mn0)+6>6F*kC6 zuq~L|-HtEw)f?SKmON?;c)jT81Or0QaJl$_%b7XeSP_|MaOrd~GEu*2mx6!J+x+`o zKM!l=2C?08s@^SzP{URt2GyfRW9E3DuGzC|p16Z3w0!{E!=U`ISm3vN*c2dxjk_*8 z^>e4X_=8&qQOdb}0QXlfM6&!t#%ZJGO7+xB7`5`pUE_1BBlsYwBixK+6LoSu=RE?0 zmHPV59rJf~Z%!Id1g;qsSx^zU2_GkJRh#Y=R@QR${IbI!hc$`+3Hf_Xdnr0?>#QG| zMCTSmsN(2PoT7o5ueGY7(doLr-!G|EtIP`zPvDe|n?%?n_m*)F^qbUMn(OA7x}y7rBcDiK^Y&D@7fqHEF)nR!T#Pfx8;ch{Ru0T0JRVq3wqk&*c<8n@B!{h@S*G)i zfB7DKo@S!!%n3SwNc?S|)CN6n0oniizV2u%!pYa!D)c6dnw2F-%x5-WX-FuwyrSU!$BZ6*31PTaYWwD%SV9#|*!v2t+ z?=EFlj-7prN<0+{vc%YDIuENJ$r)NZ0q__8+%4@{cLU&tuV%>nw?7bAq#%$6gFLtURBBQ8@-jEOa7}P zK9}ZY6kN&F-B#l%v)oBv8WcSCuvAC*Y3ZsPrI26MEHU21Jh$^rakc8X;_mtVi=jjR z?-9_~Sg~!Loc#;=Ebw;4u|RI#QarmF3U&1VsCuU`NxPs~v~5n?wrx+_wr$(C?P=S# zt!dk~d)j^K`}f{wpOY8$)Wv&ISWm9Z$czYi8+qQSD`8Z{OwL*RVe1C-H>$KoE)gWxtQ(|B>^v@pFrb^tFxHHX++D z`rd|>@7L)OL-p6Yx(^LaFx*PKI(YA!cWKbR)0qTu94t@a&!`ZfW8=|)fF}Jo0ZgF8 z{Q|}R1rH>LYw0L0Fv&ejz@sD5*<=R5gyE9c7D+e&4f3ieiQb5@`5+ z()_(eSJ;K5fRwXl>mnr_VwZ#IQGZr!NcWSn-FWybV-w-bTYLw$I)9}ssD)Nfi&!_D zEDd-L72PLg+B%(Hgjl@RHJT&iW3wbCc2*&fp@zCXw2Tgi6tdWDK;dDL@0Bbla2}?= zv3kaA5%ma?FG=2Z>c>w>4If;_=wG8QzhWH0xqAGdcON z0db{w3Xm#PaR&D8M^!NeiqKcY3ard0f+5KAP;+7qz!G8Gi@8%BUMSc%`nCDv#>oeVk6(ttJ_z!(p>Mm8y6Mj`t&0jQag7HW zA&=H5h?#hh4bS?^pk}k`R3E46hl`S3XAKr^3Kv4`5@@P% zP@v*%p#O@3BH&jR{RTK|ch13@Gy|3v`V9Bf@#Dw|ch1*}3V}vZ zbDCDO2{|p5qmcSQX0pg5uyK}PVVaDz-?iPO?|L1+I1n?xF-4Wf$8NGcA@X-iLaRdG z-1Jg8HYjgOp3&yDe!&PqhyJm|M&lw<1#iDNRrfSz07D&_`Dryd#=F1W0z38a6Nm>{ z$91_u!UK?edLT$kkNiu;pw;@cA$|i}EK(X=)0I&IZi-&RugywYgeUV}y2s+>KIw#z zc|KM=BUAxg*323^u?+l*N=IGRT|Cw_)h;GdCMmzw1gM%{dH|oV{MM6H&wr*Q(40iN zEX_rw)8i+q^O_TI~}zM+EA=25r;--ZVhFD(zq5_q0ss#*HJ1m16Fe0$MBmzqJfon)GPANq7BNS9c6p-UdxY z#<>-phaUsfS+r5H3$^w@dJ@# zCfYuedxP$eqq`|ASt6g_?c)ByiUi|crn z5z)wlHW&Nrc<13CGv7Ly4gQOXRQF^DFH=p~OU1q7(9R@>k{&5(Klg)AmyV0+2bR%C zQ^hRX-t6ZOw}bLgeOr`njl~yF#R=m4b*F*vPh1G&y;Ml)AT`~E@au9`!b@Cg5r zzG>}C&HKy|tZ7>5OKzVW>N}DsJ9HFLIoa|KPw2B9nDyUZ&ac{-1E|s*lL43gl2cHF5yQxG316~30}L_w64RYsIqNdquVWW+kgB^mg5$;Y{y*ApF#Tv(-H zMb#qfos2{Y`jVoqDIUKRV6ox)j>l%?0JCNV6x2qd~a5Ti{?a^)@2$J89FJ%DYeZn zDL&$(gM@)7SpZH_14cU)e}u($Z1E{g!5np#d4ua>3YXg2YWlq<|MMww$b1yJFL{tv zFS1+|&)?~CuGFJ<`&=tm#*!)HgA68g7wj3&3G@F}PXE2Bf&HFVhXN*&Ay^G+ZQqOB zH$TYP)!0y46BNtAu$HheCJfqoWgiCpm^a?;$0Ctg43Yp}(}xqM2wF7f0)zTRjGl-D z6UGoGKl3)-H+|ByMK?BWFB6-WhF_&ho%g2S0-*aoGc{HrF_Y1ZG1w!o`K@pRzIWA8 z^~<01)b$MkVu&6WDm8{FpW>P9%#q#eR5gNJwfw+B-7%9knIC)8R7np~Hnsac$RxE{ z`J7KbKgALO;u<%%`yVNRb}GY_fWnQx5&7HsU_kEM%-NSlAGgw*BcS!)#rq@6%R#{@ zXZp{h(`%qVV7~g)qBp}p2Kk?Up{Q(3G9rG?@&}(pOh8QdJb|{py|y9VdC_BHW_+ne zjgK?8W^VA^ai5w_CWGJRk-?s$km@S;*HA?5-XVVXl)fv<(7}2%)oiU?ltN3rog{%2 zd&{m6db9wtmCUBT1rB(Be0b&VmWcruQMhDJvybUN{=+m8z?~hcq)pN#tF-|} zc(f1ABI-H;d#Wk4sQpG)i8E_Ib=uiI&ih(~_2~qMLSNwYR9?^j;N1m87@MhwrvysV z9P)R6lFZ>1II%hH!Hm0Ew+uC0=N(_; zW{WQnofl~3j%Zz&iqxU53Lu3YUa?k}qD@E;s%u`~GBH=AuQtq+luOmv%DyU=v_GuN zmH?Ogh9Hptjuo(y0zdfXYa;JFi|*GPhEAQr<*E`v_v&k6w}m3?qz{|L-#_%aow%G1 zdspjCpK}7DG1zRA-roS%6LKqC-` zR$Lhh3c}w%ItjU zcdV4g#`Iaic}+UsOP>*>>R6LvPs_cJUHDXA!^~lBjEj@>xpo!CJR1iou#TFuCF6=QV5qU!G+Y_xY{VKN>=Jj#K(- z?S3Xnr4>LSaN)B0gGuzSi>QW&`d04^XQOOH-Ug{=+w^KDzaM8LI2+}^QoKVAfJY;! z*SYjkq_B8JuT)J|-Vmh5Xxun;>-&hQxax-eesY;(G*kHO(@8;!iq0Ya1xy>`og;Mo z<=kA`_rt@YP2Jrtu zkpA~ek*Tm2ns_gwU`uiTJ*v?AYs`XV{ar2<1!jx}`mk013yq*8Pz`QQb(k~HkSdpf zKJr=$A|?XgB8o1?5JK!JLTX{O7*RvLE_%}Ud(O@1`*t07T@3c!#c>-mP1Ti7qh6~= z3jbpwyLftOkF#}*@Qs*zr5-uv2Im6CCfWEg$fGw!Q?K=wN_wQ;wW=WyZ`a0ALqqb= zfbB%n<-Zp9 z^m1_NhsF2J3_`@C^xIjwiB5&y!@oO1$}S2R5HX{4Rg*5Zo%!?}HCSeGlKi{A2trtAr$K6o+B#jHhoi<={Dd1XVZ`U32*is`ZDn77Oztqda(bM1WG{ zdlDZ+?cXHOIvfODCX&`9BI&X#-Mq?kg(fgC#J13d;$jrOpA8@YD7>~u7nwwufdws(QA(2W>0(LjG2*(`Z0B(=bn|S}9b9Tr8{A+j&sSwvC73(=d-DawY z7%HQhT#A-780uc=%p9CUP2>pYs|K8fR;y!moB)RFDy6*JRq(ah<@#*a5Hb%o`_r%B zOp%l~PhGZ*ln{z51Y1wJo(;3Sf?pJvLAXo|8iI(YDhAGdT;fbCF^U$c)Uu|I5Q08V3fh9ebp9v9p1{CJem&b0kW#7$CXALglvo_ z#OOGMG*!qpOV8@oKrsIq!a(?izec@PVZbhv1_9We2sqDwIGBI@98>KkQJz$^T}(Bn z3+0kMWC!x}m0#V?hsF-_vv*+B^2p1;IL5b-ILB9^la!;$-E} z=iF_XmdO*meQ9?SRIym(oLtLUyLF8(wOILeKGU_j7NZYWn|Na6OT{MxYz|D|UnjRq z>r{4ID6i>P-L3u32l@2Wyf_2=hYDTM8|Psw=h|3ZTnr3zw>qlAdktA`Bc1Xog5 zAnu5uM?a)#M;4m^0I}FL;3@iKaLh&f=?o@@-q(Y>{UKX+oP2%~663qQfoyi$1N$$c zLh*R~h6Vr>DSbkN{~0L^cFwL3g@D6h_xZZ}0~RFjzpW5U8iKXxE8oj*I*CgAe`aj| zQ~J$7{G8Abl9(<_sZKHJt5&Ie#-t#b74c()V~IJC8F1e1WBp}-F&PHV0vAQ!FRO<{ z7RDU{N{du%%y<8bAa_!$qLs%*NdkqHGJ#B}fxdhyb^XM_27*V05TOz!R`zf;ce{^AoQ=P*Z1+%%UbsnB=-S8}+^2doe;%|*Bw`1S~G{f+4EpC z1^u`eLa7d#p>Fj>i<~>#7Wi!#o8V&@&p^QJsk*nz42F}eU;lukVE6Yncy*~AEkLM> z>S?*nxBv(nm8}l5#X`og4_`nVS_0g;;Uap5io(bY{M!Z%>f<$`6jAH#DI;#PZ|YN7 zV8Y)Elnkqb%Q)65&}~Yd;%uyQ>I!1W+jUk(qQ1yFV0*ly?N=9jI39-tV^Z$6HRrjF zy=iCp!qwaI9AxeiwEIrVFWK0mK0vU3aa@IsQ@A8M@KAqa`V>P13*H6w|>s3<9n`%U{kq7WDu7zA8SM$d<%hzO|gAtA*Sr3z9}iCK*W z$wTT_fSy(!XX2P+j&MOa&ayIUe!=|=N5k9yr7r=J9M}GwkLa(#u^0$jOhzm%s1Ar& zHTqF<+zoChF#+@^+soc~8x>LqtF9C`vA@npJAV^hWo#79XcT z5}>1mocpdnJgjKd{Fjb3fov8b{Y|^+F23Y!vS{c(@+tk6c=xc7E^8p_QFy(BEeYr=oV05mYj_8+7Y#JH7B3tg zv{0JPtq`UGfvwRhObS-*=3c=`(LgA=3tCpCtFYxP!P2*eN*@$ZwYZw@Tb_3)37XKc z0#fGv)cm!wnC`#z<^yD$N1IL+fyky8bY$;nUV8rf`4I|UoDjUXPp&$y;`3sEJONu{ zt6x0tyl%7h!OEcdPf3D8o2A{31>zyq?r>PJ{UgF-T6I$!Qtj^I6D&NFrDfwNsHA_+ z<{q#D0s+HT!VWU5A=f{h%;k_new&j&Eq(tGnk;=SB$>m`L74PSO-%0mX{f1ZbGVe% z)JDtXWn^%fjD}yXHxco9$9K7XUhfh~#K~o|P|?xN*t5}KLUrgVtOz^w*fxTm_kt07 zKOd&>G4wt5Lv_2{3=94*+Y&Ifk$cTvy;s&tRno*l+jbhSiHlLAPGwd)F`r38yH;n7 z)_@3|7#J|RyU0)I8*dy8(|{Y(H@EDHZOwQaW=_IfR$J!KED>Vguq&g=D)_p-n5RLu z3Kf-Oz>%lJt;py3eR?@Sb2D(zyGfz}w%ajJcbBV!u*$n|5-vKw!0g`j+f=@1+JdG; zxY6v4q8t+>B^Vvfs}_l^C!J8_&(6U0Q7E+c3a>Nr%K{_txp_4DwLbjL&4xW=FD#X)# z;~CU7wqD%oxL)4q@P$k`5Rd*&HO%||?`fc5IU+Uen9T#n}^`n_a%=fukYy)D13? zgPU7wjb1!1RcVnuA)@Yo@>m(S07#{4Z^%uRf*i0u^}G&_^WTn6v)DMGYm!pM!@+pk zlzUo9lJDvLrhEGgon<8>izS>uLK#XPMmbTwUi$r6RbD9s z0$dgVOFs-zMXee&r=Ekyl&Rw+TNz=65a0LnvbJ5nkN!Z={|?Ts+emQV`0fG9-Nfs# zM(6*x`^o}7SehLRYKfjiIsnxjh75| zm5+^wQTNtIi^G7Yb_-vmG-_xZtHc5-LXWH7T#6{2*Vd-)A`j`~6Q7XZ>Fec4?DP0R zBzj&H>=#R{h3~lz3GI8FbTR!p0^n87;b-00PP`5u6281Y|6qrDom*!-Uq*^hh=;bf zVpeikA5*V4cbr>@d1F4EExXk19UySFymSapda}Ebnk=gGem?q0T(!HaF&>uwS>9rm zGm@z#mO5!SIjr1!wb_)2{t)e4Qs%EoUnkP(lgwvn46rrpW=d$-lnr}Lp#IJmTPL9? zb#x*QD1Ek1w|c#YP?*zLNcn|B))hNL@Xl;1gaq_8qrd}{WswfU>u53UQGaAt#W0_e;G4n(9qDnzrPXro)k<>NRv+bfdMc^EEyYbd^2zsoBI=kh4a7R zP*8}t4#B|UT7t5XR$&4E#ow#}N0woehBc7hfXgx!=nx`q+oq|?1K18Je@+?Gbp)?# zlIVf`D(JG=ea`Vp;(VSE!>0)JCRz~DE<@z@9Sb8x;G3wZs8ZSNR-=)q@J+E_V3w>F zv0e+~ZOj^SqaSnR#y=)W@7SRI_U@<*SU3(xHT{9RsHtiju0lJwwK2%`cIi76OKRqv zwwePIKVs9^djus%J#8f+jzHX5rEh$_Rz6DOZ~rP-&7D*%JLG(QFIc`}=A+q1AUAZY zR#Cj2T;qR(b+7dum5HFOJYEOAO_4)@G~Trx+H5X(jl#^CT-Y3bP!(TsK#U_6W1OHX zQ|xN_n)iDrLh7NA`d7D>YJR$(D@!J+&V|8!$(2wuX;DzC$&^rbivDCdvtUNlaIh8` zfBo>pe7)^155irOhNX)bGOM(I@D?y?>mfLnx7*^WZ>&02w8Xu$ zpLRQVFuKwyR^?&bPzDCsX5q8mWeZo4IjpVe*M&ClP7R>@uzU2foOltBM-$RL#o<_6 zyU}M?w|#A{hBkj?lh(wQfXFk9o@Q6`8_)crBV)eIlV{J+*%y=^_|L3SOeW_7*4JxT)%oKYSO)AD;IN& zz%-MM`YeeG3tAi-G_4C4Bw!#ZQ2v6nUdq$A&}fWUETN$xO<61;F<*z7#FDNWL4|o- zlrLNKySL;!3N&&61uc-UJELi5U|^V=o8!QQ`xO~-zZf*;)ze#9S;-TdlY{H=FdT^j z$f&Xafe8`NR~n%q%UOi-ZeLpOTW23TNd=HQXtqAQ(>Jp~?_`WVxRio^4(vT{gUen~ zB%odzInuy`P0h8QiAS-ZGt|eb=1=x^ZwH^ec@C>_Y)gwP>VMI0$=3#=KHrr!{I&cSc_b){t!NooXY1<&<7bi z+quLKPnzAYR7UbBK)c-}XVUM4&!w%i`zcW%+ zs@bm|SsH0`hPhE9z-fB(^BX8AfZ?bqsrnPsya2SBM~4)F_AMj?(-58JZjSGv`aZX7 zqXLY?_ZnK}ElTJu_&?zQ^Uqqp0Su&9hPr2;05!?IYTdL43yCsS3fzak%ZwK(+8Rt$ z2U!J(1!W*ctR}fRCLjFSk#tLj(~wy)Bz)c?L}V9WFwUUg>*F9}qRB;0#3lv-phS?0wdHAnnDX$5sQi(Y`i zwK5f!QV5Vq*0s^<{)a!L-rZ7JzrBgv1o1kHO{nFRfUVA(NsEv=wBKC&Vb0y8b*{On z9!68V$ z=S8mWPA!d5a)cmX$n8n+rK@6sqIrM#>~4G-A}ev9n?S7Vs#C>|gyKE^Noik6-NcCA z&r4B9r}J#%niomb9oSG9TS12La6gw{|A_11t!j7$_>_>Zvu>*5ID#__d^T&We6L;w ze!H%8$#lFwUjv13sRe2x%a@UkQ>u4P7SIT(UsiSETx&*3#CK>xrGIc3FMCY<3&{%c zNz-`1jWo7VZG7pQW$_a!Im*pGZ$fC&i+w;%yo z03=Y5Jb4=q8n!o6ugscFIqz^LYv_6#Lz$8>5D03T<+@av0=}=B)XsESByW08figz< zmd31w8X>%B*=lNXQieQPgZAX!;|@+Ti55M&pw7tHI5|1_=ljDxR!mt@(eZdPwY$6f z&mThi^yov*+QQ%(2YHla^4W~x^^cI(Xz2~Ii`uAFO7?0mJQJ*4f3?$#-L4`Y8bOkzZ3Yp;a$;*3ly9*S}p&reeeE^z`36L$uoo6qG>LQ zmAM}y-@Kvd$R~o2QUCmF^DzO{k~;7An%DripR?7G3FgzLPF2l{^z-VtYtAA+-VGhD z+)f#DREZ;yIQJ`f2ENA^*q^ET@#@=mu);d4K^3nLCou@my5u-gaE;uL4Ue~F0t{RF z)=$fr>&2uqn(G@f{aorX?s$LO-`%c+r9JKDm$S*4Vjsnm?ffXCa8HMvR)6*m@?`yU ztM_f_FW7ws|I+WfnSQ-4R~X5xIBsoGZ1CJRB%-Y{U?}zQD4#6bxN7idkp{Oa*&jp~ z^R{waNKXL~5R2%0^=Jn;&jt{xTngysNp53&C-3ASlbEnI4qEd+cWWGr1wbBf*-v^2 zB{w(^ysIc>Jgj8LYv1C^4vf@ff6r34{qSa9?z%6n1wzNgy9k$Uba@_DRKtWn4&J3i z9)^cSR>^85_jnIvDFeOAWN`2RET^{F-p;2l+BxL)7hj_Ohz_!)V&}{xN4>@xAwvC6 z)F9oA(m1@19X=E*Ns76|FbQN!ubAhd2so8rGzh_CEJYYrp1CcT7Q|#LLGQr@Se?#W& z8*y6=JpB~n_`Q?lIyNP#ZH@+)(_4_YqWYjsovybRIVdgSg9ap{r)`6ybZe1yB115w zEfUnKJ6^Srp{zHv{0vy4atQKog96^#%Vg>3bpNKLr*?KR@itTZPNrJ$3`R|!v5rKR zMo9Z|DX%v3oijfV^K<_QHwUHl#Qym(pBKPb2IbBapK84*?}QGq2VHR_0cD0VI}}(ZZ}$*LVb}t4ukMLu5(XkFK(of;Z7tRYG7bGJIbA zXj0jCeMFT6K5wJn=mrhI*^r#VNkbItI&g;y3^3C~{lw1@1$xBA&_l5ql87=|0XXII zL>f5+brB&~Q>zGD-7sRFeXcIAZg@cMKp-F>!oGP#n4_nUzCJ#+BNUh{_Dd}k_8N^) zcGYsZOqtUgRrV6{-*z9vFZ}!hr`%J`%Ks24)w;PAnhihIf}+7I}Pw_NqU__|SokcM)l9lCeCWuyAF5UuN!U z$MUrj!)#WpDI@4*1iFp&7pWX#lvU3L65F3&vsI}8&G0{5O~%VL-H9w>WKC6ceJhZE zXe*pwLyg}8P|}e`FnitpFllwtS4sT z-fFmQIGEZl%(TVulB&i8pNy^|Uzj;&H5QB6=j&m7d~-DI5vi4w6dVL7uC#NbyRvs(dMp(*7`ZjuU?*H(p^E+%$hiGC2bBHPMHxuMWjAsbbUw%aTX@g(Rh zzwuGLRuGE6kSJ1uyoC)`Rj>X1MbYw*za*s|TN2xEskX!q6ZtT@2c#pegEc*h?;!YNW^D+H5#2{*Z zN%e?eqxD854PjAl&8vLz2{=*aAV;K%b}O}f^R6Md^Y?tKWxK20PZ*y0-j}kE<0dx2 zh>5h9yiVi>Le@hsi*V)?QIEF%^++<;&@Yl)uojjaK9^Z;DFcqi!R;PcPP%uI7OfFy zfVI(YwttqK52yGf+k*4HqcK3I6J-c=Ho9InjrCM^nB;@f|HJykGOwCiT*7|49N}GWQjF)TLd{I6G6ceC@4#n z%o%A7txR@~*qmv-+3aLSj*(KKSmm7dyykFe!chD;K|M_v zOD6R|Gpc8BuGs_Z`^DEmO4;%1Pmw6HH+Xj+T-hUq-C!Iu8q%=IX<)TMHHr8$LEd)a zu2PYnJn{+*8x^%&Di%}Y*fQDyNZCJ7`t{M5nWa)WGE7NbXB&Tr1~%(&23d= zW(KmYlN0%<9pL#6DF9vHmM|o+c8yG|zsTokz`zBjK{IC2BSs}ko<)Ev`m5{BoyY_{S1J4QS8kR{y@n`IbH#E7W@Ow%Qw03Qi#;P4(`j#^b&dE~@Zjlf&CY!wXv z7owwO$&*#6L%EFc+nZFjdWjQ&a%VqWL)HhUbH7vEL*uyQ-hbS`t5F4WX0<^`_oDF(Is&DF%k`FtJMJp#Gioa)>0 zemm0EArPEcS4}>V6DCYmtg9v0DtVvI@=57-CM7Z3qWuMp)G^|rWlgKFJKAC!Xdo8H zUTck2I#X-ob}hM|ZRjacaB5H~Ua5%XUdSnsR9902ANF%1QqxRH*H$nB8O#{gxZH>v ztvu+?;YlS&2`71ig-1++#T!=34p5?p6wLJ|0k~xSngH2ohBm7uuTn{0fc+DJTR_as zK1fA%UP`>dOzj-G#qLvBQCU3+#_6^ZpjsY;K9s&WVMaaPsizVJ3M@|g2#<(}zNFB; zF|0UA)?Yk3Q%HswVTVEM$b?`LRnMBBjM;2;*i!|NSDVlw(zpJb^;9fDU3dk}p`vCL z@K=0tY^fwPR1}SKFI-$4hORA3(wTwc2fwEjQU3<#h@u5ns&rMw%8@W#s$i)yCrFBq z9_b@2j5@PQQr1yS4*8jKUwVin`0}?qx|tcvD8JRl8d!%Wb|9_Ic(J)^e)TxGU}{=0 zVuZ=!aDSdiOjAc+%?}K2MB6#Di=M_FqJToP&`tp#7l147)%%3J6eEV1!D-VjRMVwi zT2KSRFZ+aT0AvQPwOR0>7Z;_5s(plkPJQalD>kSiK8GgCk9wx@y$xFytt(}{kb+iu zW9fiPt&YF9M!&)v_0UwY47@kAwaOt;Q4T7qsGYhED~JQ3O^*hwQ#DxzKg7W*Z0Y7# zxXPkz>Ch^8a(G*X`e0y}s;crGX9%QgwH%D@xRdh2URT$x?(r`VAF|c$k_f7ZI@*rV z@B>+1AjhJmGeRz&w`fkg_maKei3R4_aDS;M-`xWPA{7$2&=uukIAL#hAtWAb=pKT({81!7y3EjnfFGx%sfQ2~xp=2}EKtKr=j zNz<4(iJK7{_}UkI35_j`L+r3XBpefhhM5=yU8PV7I2gF7Pq#+XP8+<#2F&9}pF$C= zC#a(eM!R-D!Oq_bmmAFb0t5%++iA8c7}Wu_i)rS*3_)u0b~1Y_HPr6+Rw_@CT|H=; z^3oC)adQ?9W`8}zuHv78Oh9Kz$ol;l7e{b*c}Zix`SRQAHK04k4EgZ41t%&{)PVbT za#(Li+o_&;!PaO%`3La0DN!4b1R+pWBb#lo&a_^>i;MqI1U&Lvr? zNUs519Q2&B)l^g^KdI7WZs`s65t@$zy-sCHsF-tb6U^?l#^@E;jJL8lHNB4H;zWxgR9sKnkKw(b#XsJC z^zsGiv&0h%U(%=kZDY0G@8dBH`kuTysyF8Txo`I`PAA72PG{MYE-@3x1l7I`(N_nZ zT(|aAq~Lh?=T7#wj^ymYcReka2AUz{|Kg8#Yq=6Evs3lcPkVj*n+0n0b)4U3%64Z! zHEtV>Y1r*MZFEbQZ}-6kre8WaQVb>E1$KSvuNqfM*(Tf={9a}wNS)6- zs*WTuI3 zhJsw{?+d$J7Xh;pz9o!NJ^}ZfqtV*iw&D#H7LTRrQyy@SNyGC>3oDR_5#sP{zSoIU z3X@a|uwKRKVV|A*FPVAAq0w~*b@VC3vxg;Pb^oo@<0vwx(8(BGSpPmC&I$Wo7H0Rc ze>eUZbxb#7do))T`71%Zx#X2dwAkX6Z#Q zMY~w(V2CiYUrkwVkxfy{wo4W(4e(`A4YP;(c*qQGTfsNYSvResTdZWb#4POc@59fQ z%6?Ztvfo<~4J>(bcgHJ**i*N~dbX{K6X3-LJ!F{MoA~704a3OiEAZi>Pd_A>EZ^wA%T|UhY#t`MqtES@H*|A+Yqp#bNSz+; zV3{LSX=M5uUBCB?uzOLMMl#6Y;8BT*`~m|+$OrDUb!v43@N}ai7cM6)fF(PAo8Xf@ zW$lG*SkT5pDgyim6Bl9ZOCf2lS9(W8M0u&{p&&(?n$>$66UPrwDlu)}d7xeDsC|Wycvg?b@;(*A> z7=_aEHm4q0@_?m7RsXZ50za=|;3)2>O~jGGS-rpe52EEdQr5qQ%{zGD|6%}z@g6id zI2g9ejbRTqOAkoH!cfYcGN5Wa(1iiK$xBCbAP2Y^5epaO?#%~xtf{G~PN&1^bQTYR zPdLyM1`H&Fu1P_yM!zF*REHhXVdO-X5WMUHtTAEM|1GU62f&`6$bw@&Cn;jeBpmh zP4>&Sy1#2>ryhNRulYe4E}xl zI&USzxs$16$%9-yoYsA5nE)hzwUFT+3 zh05ycbT@oHYV%`sRw@dt-3&wIJ#*%t|j+-ZKAn> z5hhIF*;>2vj|?dAI7jYOG~KT)hcIoTdf3Y!fvq_T93PV*R^8%!nkn?Bp6MLS^kz;5 zJkjD~VX>(0{|!B)s~D}2nOgmRS*B}RJE50@zlbSw0>=LxgN1Ru=GClV>z*oaqH{W% zyD3hcA+T)9Dj+QObaQseYW_0|is_$5tSX6jl_$H-MpgKt6UlchqAF?g!bn2%LaTk& z-zZpot=;9LuXfFWQ9~e;ot0&Ox+;n3ET{AYx6}=~8IrH@QKNI)I^vk-C+_zKTfZ5^s;2NVx&C@65mXibBADcJZ|yo>NL)Z9(aJcUGTVGlt9pk zt&}{P3jJ@4xYtS%FhJj&pRaFWauhhbJ(|N!ZZU`yR8Qeg$VEoX{0j!~ggyH5xg>cl zVg8)?`-=@b4!#B#-I~DZy$77TDvhtIYkIj7ux#wJ&pKw0SFO206Odj}n;>SzbZ#l) z?XyLf=@<);+sEIzCd98T`s4kb9&ggNcw3a}diip>E(aVlc~Vz6M`X-|5!e`L5~auW zw^jRd!Swfr#OpDi*VD6w76$&Vl8hS6mjQ;wE)D-qXOrcy?4>(Kuh*-8`k17rM(;^9 zQ#+rxfktW@8)O*xx;eDY#2O!w`9E(dk8o988bJv5KA%=~ocacZ{NlqjbG2O3GaF%W zd2I%6{!AmZ=(ZmxGx_o9jm(Jpb(l8_e-k|Q=Pn3Du%-Wfz3!5ovFvE5uB1wLgzYzLr@jv+W=}sbwf!qWm%TiZrz90;Ym^{t24SUUNsx%yeXM1Il3kSsdw zVeiF2{d1z85khW#pRyWnS@DD-@cC9T>u0A`b6v!;<6V4^Ux!g@#b`L@XwN^tBS~y` zcNb~QE&2wO1G4~-r)*tvlETTU-(kt~TZ@lRd_VB#`0}BZ4p%L<`OZF`Me+KO#V0<*)GVDlb>pY21NG<(AMhLBW7yHrS8%* z2E4d_6sG0f+w)jVdL#0v@AWwIU|oo2ey%meyC*v_ioDGTsC}l@4#33Q%il5qkZU`z z!}Pxl%>Jhp!Yh%%>X7`K%U-=_pU9I+6MNy1A=j^$G0(huseYXClL~2fuQ3f&!qiCm z=lk$9f4myJE!!&JhZXIdN4kDECw!#0Y?mHIcofszIKIZSVj)?9a$Y|?c01x>tSPtV z&2&_->N>TmfLhdE> z!@kNJIJ#TK6(q(!q4h9h9Mx+8Lzs=s?07g^3fHW_=Iw-YCaVwQ|IA1h0yge!Ip9Dgn2SYV3(F@MV=305PRz|ap zk6U%UU9q=J;QWS1Gi|*2%IiQl{P)jSyE}YHkJ0^^OTl)aoX#gQO^9j|^#dkOhr!?$9M$UHM%wuxkKqRL zqk*+T)lgVS4g@xbR*dj}Y2l&=EZU$n4+|P6QAr7vg8I)mo%WHOK7K#6==IacsjETe zqN8n2NArusUNkRVbT1|hq5qdigS-LSsue2OzkVHLXSB1B*HU>u@E9NXCMOxpD+Fi- zQMP+7HfT674jwy&3KxWg$&ec(>|yce&$a7$q6#FC{IQ^3SdeXrA-x+_fze)hE3(&v zZ+nViQTq8Zoi}&Z_cB~kR_@z&SX)UC#o`Vb5$iZ98E%+JsX|e!oM+n$7KS@y`qp^C zli!~2dr^i@=X}n#3_jMW6d3*|o5l9imvX1Eqc84snNiTFz_p^tI%oh9Wk3dvsdR7X^Y9^uB+*N?5`y%Xuh|cWWG!e^JPOZP^ZO6edc0C zvc}GGwrZaGZ>-hi-iv88G&ZU@bv?c`dv+oj_Zw_kv0tJbfD1FJULNLHy`a}|4`{mw z|L&y>&RsMzo%dww2S-NxV&0g8+Lr7e-sel%+&^`gj<`_ms zb;2BnO&^xQKU5}v{_A)OVj9Fb3|8FYlwIy%Dw*mgYBI3S=k?rjf_T!&Nxm4%hCZ!@ zfUlJ1d7peV(aqO}@UR?h$=(Ez7dkC8Zz=E#AzkM=P+!xoNE_G12Tnr<;$~MT0-^}S(nv{$VfFEUl>dL4M9{2wu>U^ zA~SiPLoxI+W);MnXVH4X5erM0_nxq9XV+aeZm9vTjd1SZ$VjXI*2j!0p0W1(sq9Fm&g9fPwK0*?{W-` z)ib|J$Omo3N0g)9RWQ0alN>$LAq&D-ERWt6(>l~Ht%V)Z=|~u`hS1J1@O0$s>q|mR zTwCU{S)wT5$I}Kqxs8S`RO%8OI#^f$q|w7)H9(1o`OEFW5HaVz-H4yO37%#AQ?qV5 z@B%+A7oNC~lL&_7IQW>I@9N^xK|x`9rpGkOP6UE#=G6M@P7;^zwQEKfz;^Ju+8ZFL z;AolFw7m~}*8lk!E0|O3&L(|S@yp$*7OQO&P$q^0U$s+GE+r+{n(Q)dte2ogCx;|N z4A5?q28yVZH|MC9Ulj~5Hwe08zoz_u*!t$^TDot`IJx1yv8|gE+qP}nxv_2AwrxAP zv2ELS=lg!IU%&3r`;R?pj5E%uUAtD*S$poe=EPA%<&f-SFZw&8H$Rv97MVkYC!|t;Z4;{CQC_&R3L${Sn+3CcM@@3 zYz|M;8R;J1izP(JUflF(iNbBJR4NVoO5|s%RA&6$mxtKJu>8oTIb*DAw#IJVFMZ*Ol%Z0zO~xo0MVQPKM;{Q zB!ewd<<;^sFAIvR7rA=$@bAA%LPHiWoUxz?2h6+q1Fv>hz2hc3QR}DGkRLH`l*}MF z*Vi}nT*`EAb?ziqX#oM^;tVXmD977`n)x)p1Ub;Mb0)daWjLzM7KqYiY8qXeXctkBr=V!_pB= z%GV5%uLYkvx-ltXrklE^d!m|g6VIO?D;Rs z_opcY2z$zMkx~`nz)> zm)UBYFdnObG&^=oO_!ML&KW=O#$g^d4!ZHZ4`mPg4j&W#Je%&lT&_8j~)b{CPGf?gF9bQOmJzaxx5PA zH+e}E-~Q|v$~>g>sKeI{opca#kFrF4<0WL=D&dliQove+iP4qKqx1xi;vB?38vd$ zcqg;jN@W?~SaT1whKX!`nu0yoL`4aYI%>=`)j)y0!=YDm%nFl$fI8Ox&KsTuW1|b| zBCXGad32joqkOokUpd6APNb^Lfv+(Fr|JAt!e9ml5qJl9V5Qz97V%Q0X2r5r6&o8H z*8LU($dcRM+)!HCpu)6#xTc$MHEsjXe5nfO`)sS%)zh?h>?D+HJZHYLTOr3$t&%Mi zxLlyF4qRW8nI5$<56k4QO490^S1B8StQVNa4<^>-Ce&_~3W}YOU~avrJmDTWe+W&k zZ|`PK?62)*KAAyyY&N@s&RA`LFT$!IbSIbqeF{XIc7-E+^Q(X_BbA^P7JGIcQ(b4jK58<9K>=A>jwqa>@{R; z8C6xyMJKVxJ+75qmpdh8`OB2{859?G4B}CRop@yZj zk;$_sR(@eng-6?n4<23@0x?{~O%Xv$gKCUdo4tyP}FG)?Jt1&Kd);>>%X0f*(`N zZ3UGq_`u2lqeRukRuoOk->vh_0&9^=PEWU(H82k+O8x<+h`g;rTU&LS5Li1Nph0O4TRA8oQw}`hU z8`9eonsH-6!v4=IjgTI)n5fF7hnwV0Ow`4aMVEu^?1rGV&bI4JzL~>m*Onc-!yD(j zv>L*^rr*DH1t^ld9`|>~)KN>%S7wq2AtYsc0mCv`9aV!lFk?uKHw+cp7DpI&586Hldu% z`N!fs5K`Jq`QB>deN5n0d-o?J6-<;HPzcgxArZqLeXGq#1zPi<6&?ICwZ2|@z1#XE zJ^$JM5~PxqLcd$`_+&tFkV$#I#metZ)f!*@E~H~mOv=Ia$9le1K#B~@%Nk|o)rYqD)VE*eC+9|@X6A2v{emkNF5vSc z=t*w1TZf;+0rjFS7dWRSWEW3u*9V_k?HlKg&iux?Q6#-3V(pKQ7J_eg_||KZhn<1n zoC+0?aQm1E^l7Fr{_b-l!-)%XzPuBY1SK{7lUVY)Y|A$PHihvg`2qLC8Vv_&_V-Kl zzBPxDU3G+;9_g3pfkwOnNIb`I-}p{Qv-W!85?PcU-pzKeKTY?$owat>H*vcZ+Lw|*mM->lD2z(G(Vi8t$z@txSE=lIJW|> zOnrnp<90SXYWb>_+sZ?Mm+Ehk%J;YL9HJ9FIS|^#pBW>yx&{hpDuL{ZK&<;9p?_V@;FgNjgCZrJ6efO?cf|czusJN zFDHt%I@P*Zk#%$X#Jz7`w%WXJk%h}lr#hj>pzCHQ=2YP_jYGb(dop?U?g}|b=5^ij zJoUe7`qh;3pX^j(d4D`CA+aq?Vhm9%RB|R(b;0!-<>AHcaOIUBJ=Hf^jobzOcGLca z+HAE{#p#1msan&9?lbL`c`&U_C)C_U2V}bB{hIHC{(W zXa9AosD2yLggc{Rs4CBqTCubaPLz<~lB&U@X?Gr#+aQ2^AI7@x^&u-=0d(D++hleLLHoF4|OO z57ah0s6R@KN+p{gtG7hN0B5UQ5uFw*}>|SzqJ+m6tf%;UK0UF z2)vHzbkcV?@<40DLLCNA5Mh$YF~sEJ+#rY5@e@!8%!E3{3wZ3 z@RY0rN{56PJd(b&|A?Ig^944}Z=pujWxMO@qy575N16Bada77T&Jv}Wmb-f_3iAV; z@npT+p_Hi-(hK$qitGEsCFk86U!|fTWjkrXHqL@iz2Q) z{jGVguwbzSJ#dm3M4Gy4ZO~d>+Hsx>?oh3<3ui=ikF#IXJ@xqtOI~2Oz|1X zEhbfKzqviMNu@IzsiXi(XqKPO=V&G}A7+=MKTETpqU2;`n)Q>LaVO_fC55e4QmJ&h zY9Bk2(^lrfdJ=Hli&bgPmWa~WOxHT29}I)NGAqtvZdUINpo;CwwZF_^EODq3-rt)7 zZQlR*o`$_dVq+euP1Cy3(VAk#qj{}IU3Oo5m`{FUcdA%rZj`bJG#z}tU%Zx=L1Pb( zDXFkfsi#VOWz2WORNLY)g+2agIFvM#NVe)s&=RC-1ivoJOK(@9VpkNEnS6T@n@GZA zbNIPOR=m<~{yEfdmguISyEOA*20fsv`Ii>3^MPmkM`(6^Vyx4XV+l8vjRI}%Mlpe3 z{9`F9V+@eHo^p zpSpLY=L$J>fbnO&pjz_nsd-`e)@tsWzpe0DRt+V4v&Q@hyaX@9Qmyz_ zlm1vY+Lc*}`%e~Ao6NPlkFxxGn)Mil$G5aSGWVl!(HBiofI<$!Z@*Kv4_rW52IDS^+F`58J2f%zI_8KsTs>--#} z_ZKMW?X~(x1p(y414X_zV0f=qwW0{`rVo(>F*G~-?&`3vzMkjn{q9@5m%_hP#%5HJ zf>**6NvP&?ZWuxIoi|LIAl<>J=oL$#Ub=E#rFBAq+K&U2(89Z)zCEJdPx zX+x{qZKJX?T=y(Fj;AiUrKp%ofJYSG{Ij`J`fQ5=XbDt2Irv3DbL%#p(>Ba z^$w}-T`m?fa9mE$yNx210(Hy+Jg+(D-FQ|l(9sAL@B2OC!vt8HMvv_>1M}p9?XTW8 zRaLf0|6wz5B#yG+qxu@5Ct*_#T3@+37iE^XBqt9! z(!QA=UvBDiKbjPxD8>@+nd>@O6B}0p5fyHkdy>Fmk*(ikq5@XmA37#<+D$2urWc+H zf>C^-E~gcKNXzQsk63H1rTL%Rt~p%6En??mDlJ#rw@h^AyF7Z^OOL<97x6S5#v52z zmY;szw#%e;QfEM)%;Oqm@}zyO7qBe#p=xrvdp!p^8wg!qViqfm6Psn-v#bErvRKb= z#+IBnr-7{w4^g&OFYxY-_8T+750-Ei6E1q!!7q7B$s#b$N*;uBH=k_DMzd1xg;puBdS$CL4iYGmD%-I$;jD9g zh)tXOb0N~rKs_=7lSX5(OO2}Y?0ys29f&jB`6B2J8_&gja)ZV5kq?Y-we{NI>9qLy z_t{q(Er1~bnLaWEWpgg308G)?M3Gj;CywGd1d*LMCa)&o-{+GGrD8Rxkh=TW*6hs z552ldt+wQcV~njri{XKSmJ}yF0s`n(5Mvky6XA}?BPm{Xtns(Ex2Y_yx7UZW<_rQZ zF3*?SgL#m7>?m*18bV?)GAJZ?XE3p!^Cf6(Jo95o>WdlDcY{Cf_p=;|^#`;X|HRYV zfOW<;3eP&EeoLWZ{<9+j;>SVDJ}3x+5uEiLU*BC_BU-28x1QzCE#$EOELQdiFT ziG!*Und*M41k}T`Ble6W2^8NuaE<6f(9z-f_PDdNLo2YB4Zj`Xe(uFcEEy{p63!<+^8|kyCvdXS%3` z9MRD@?o8njiXTDs$NkPcuv7Ane!SFIZ!8{k^Mh$(D}@yN;9UPTjXuL#&;9Qb#UD#m zf^>tiG0>yGrXD$Y46kQpbWU{@MCcv1O}0Ge)7cH2ocdVxjYodhTKTZT4|~WTe-?xfvDKJb&8>~{P)LHJYPj4Hio0TLW#IPj7|(@^qu3< z`OZXCD@kn6m#a-43urj%uT`EtO)P6;;xfr<#*zRzIHH1)0CF6rWHtk7>=~mn$)fWZsV@D9tS#RQB1376t;3V)VkB{@{?s<71`3%NU z7v=kHf2B3dOLC#nV%9=U^HZn24Dcl2t7EgCrWLBA@qU`9&@YkM_M|iy-5W10ViWo< za3KUZUf>KI*!h+m^)J981xt|O3vs?8^W?mZiwXF(Fb`cJJ>54H=an)n3=Q2#WHFh@ z>&JBdfFzMgc~Rc_lY2Ckou>3C2y_(G%UF(Y*@tBliLuPKyrolme{&;83XlGExc`kL zyqYcug}3+={?=OIOs9@eNH+ae<<3rH($kG88M;$OUUxokIEfxyJ-CX$-sPY{2vKMo zt(4dLaA9t42VkMxSbgoQ-jDAFMY>v>#)Mehb>uW+%w8<0A?YVNeZwXR-=x)YWxt79 z2t8X#Noh6ymV|HRR*CbW5_RMqKuIdlL->F2gv%1Ex5tZ2gFTCk;>|7Gu3phN##Bl6 zPVMpqWkva%N2NsbM&bO0(+9U_K-AtiYij=v)U!wIcNJ^6A>!`<*iV|+5J5s0TvFXy zY?GO@8$w6>&>7PT`SPp81Vz(%0g>p*k&)1_u)N@ERbAcf2n>cz&nK-Eh(&OF>@EFq zTt6XiS?I3{ir+IyNqyos*m?Q753%b2Er{wTj?*#omh>MbVhs%em+}%Zz_uz+Od&(& zJWIk-iyO}f+EYGnJN?1=5CXg|K(>$PYAsi*jR1L}e!f+jr*9j{4Gq3dTqr*_Qd3m~ z{6DV@zoC{htIEpSygpuI2n0H)$yn-$gyG}l!%ju6T(BqPBzasRST zxZeYs0qCjoWgUKqk-%jMM2Z1H!DE5e>_A0?iDeDnI+6ME3AyjDT$eMHUyjTlcG-jM*|rU~Qx=B9nu&7wdK#0vpnjdCE7v7cLPKj>)F?P^>U-NI9XX4v{%* z5$Jti-nkF60W&fSQJg&XmQA~&E*ONhY4Tz%kySPzK!T0gTBfamM91nkWX|2Qipap& zS%s1c)oj4D;wGR?LwHJ~*616i5l?Mj(@{{?RL@D|*)aHk8^#}CYYExw*8YbPgWNem<;0ncy<_iGPs$tmm}3ytu0B?f;Yc)p0&C38P`s0(Mng zq>4}wumUHL#9jM_O6<5Q{AdrWbz0j})K93479(=K82yD#!x5^sMk-`T(eBRj7mP`& zC|9q;6JA~2-op3!2sM(V>AY^Yx10+xr0{vV8GDDPU|8U$*jNV9#9j#hVK@2sTW z9eYNGN*xMnp&Il84e8agf#mwo{0uLN7W%z9YDg%0%ERC(&^$?%la0-J*6z0R`!n8EEb<)mWiw@b*8n%N+p^43>ap{yXu0ah&<7xxe9*YBp2RZ%8)@> z1;kLWVm&`D*Kh&!cuG>zph5i^99IT)42wZ*&GM)=2*#c`8v$$uYN-#G1H*)DI!*im{8(a zxVYb*1l8Y3O?yMZ{&BCr3XL`_H0*cKd*DuCj!$ErfY<-$`^!cS7BGddJpP zPrLQNL-y7HNyNc`2>hR4{BAB-jS-uoXC35=`$*}LXAwqKu-F0Jcat1*MN$9&6<3Rm z&SE4MA26^T9$r{io>m3=u6XoWWRGZ!Fp@E$Xolf0Vffs`ZPqc~6j;(ERbFTp;wem6 zv}IU_^sG4ZeUMH|NU75F!@`P=Nfa)hb(F=wQkrWAf(q-1Qn@0t9Lxd#M%{GT&#zf) zIqRws!`rGk^t)dgfBw+U(t?2WeZcE2#m=>JswbsaQ!Ld#^HxLVg_MxDhWIP`|NB*+ zY4noLc)5vQMwkv6{aDE&$)s;T@|~*Bje0UN1_s{Jdj;}+Ztid8hq0X=L3Vx(A>~B< zz}EBe_VaIWv^J`exh|3xv@4W`)X2+)7k#EFl1^F=I>xTRBgsdq`sTO=Icd1jvF`h3(SyX*C|{6RGIpV3Xn z{SpB^`Dd>pm(KvIi<4eG>i$2Y_L&AV#o78BL;UA0lvwS~CSCVII!>*X#1DT|Tms>I zW9*NLI1f*&BA$74O6jFb;;+a7cpgND5N_a4=uuVY{l<`t*Z#oL%pz(mV> zE{b(cBAgs6!R0@Hf}l$218bm`Q)|n?-RImkfhIL`Rh=gznGQrb)~{AJgMhMM!@wD0 zWBRrba{m5l(Mz@9v&uA2nw~wOzdQq(IAAx(xv*c(Df91T{MV5G_sIXu^XoL`U`UDZ z2i>1znk4qo8h=m(wX=74d-GXa`^cYEE7Gr{P?mrIq=@8Y{yZvOi+abkBdX}<=y@xUp&V|O!DtS;Q#9Q z`**Al+Xg3C;}^X#nPnvW`*#!*5({y8vr}UPZ}5gJIfT> zI0T^Ne&0ZL_Gmq+{ARZV0}=9HQoKVko6Q}EbvsGInyo-`$*80qGhn!GZol9b0W*I_ z(to8iGr00*mTC55p3$(gvl9^H78DGa(Lc{El&ho;WEF!|8NjAFup@cw*@0m@b5BW9 z^!$Q4MO#jOgeZSnaP(1=ZSD*w{R#TdBk@1m4tLPrxxBsPYy5*))I!#%5+ZzX|DGMV zN0)TI-0N!{8@3DV)K7G;v4k5)6Gs$f2uVg3Azewi01{m+K6{k_SM~L%?z$kvg5T5z z%zu}8KN-8s%>&4xr(sdk2g^i+c;_np59$a)NDYksE?#i%I#RjTjHB$RBPIE7g}{g=9@1v1Hqp!q{1~+x0wGcC!d-iI>B1AQtKpcE6a8x; z$Yd=XOU8Hcw8b+;#It3my$aDL(X5O@WzVuJTF}SZ{6A>&fA;~9VDlc>i*EqZbERA@dHBidnvoeRJ zx1#V1Je3PYJ%*bW_?gk$QS(VYKR?OE znX+Bhmub!gk+AwR_MX!#f{{~IXDsY(33Eq`vF{CqKUNn1=bu(hl?Y@jRuTN0$ zZ@s3f*^~(3JDB6^qHor{eBwv_Vadz1Zkc=ID10T)1op+USCbryX=RiK%$!@T;Kzd_ zQnu@1YH0nWa;`ht{|tuj;~hReP=hxYekR?A&YE7k)|Bvrnib+?fPQU>QmHArR1x9E zhUfMa;bJBx!*}T?Fid|7Wo`t3NGxw%)tc~u*epGwp&NRtzs?Ec%LUOrZSb~tyAd0H zog=mhc+xPd9IV`%NFHBZ)vyCKaA+QOMi&?}bA#`UYJ-JWYUS_i#OK9UMU_lb7H zn_A!2a=Z*O4E+V=%qe*-+H-81%TOkHU}1-f+ue+?T5}tcL55j5(k6lLXnFNuzb^SvcCw& zG!caSeK8UGH3FgACvsQFvGsML==g`{>vFEk!=|A7VCt-U%G3VCitzG9 zh^A)q?TRA(kEgE!_uK6VycfJ(S_byYJf;1U$k)?GW7#Pr1O}bmPV(I9%CqqXTlY@< zllNGYC#CTH*LnUDJH-mG_f2Bxm-ABGb^3}-zjr`?*YiiAVB0{46bz4!J?m7{K$k+7 z%h`yaq3M4x`v22c;$%&EHQTY^xZluWUiTQ%E1d$UF=dkLC4R&a12tNs=xJ|^whbjd z$RNg~_+5zrq6)VGnMJlOsE`9VvVY{V%jV9cSSMRN7G#DCSQccz#;5rO9$LIJm;yyC zy7h<`p$b`$L)!UyOE`#ufsxJXACmLo6_pOVs8;T;Vu2T?z`6&#{7ZrbUYq9igJ-4qUw!Lu{8_$fs;EQnvJP&8FgBJ| z+VSDYvuqV!kvmo7r5L4`@9&c^7Bng@+B;!@v+T6?fu#1lEZo+$!*4xCrEutZmBkLz zohrh;cnmn**5FOg&o$04guQ@87wMpMoQhHAiA=r2Dq^{jkzeE0B3_?U!|u>AjsKNX z|0lEl?i-w3Sb;a?TgoB8sT;aeh{cei(j|)!wMK-LDTDyA#P!AS$Bwnzn{K*XHt9bc zv+zqJ8_q9S4*B77xge!W8K;;?`P zv~7LVPD$ekZ*;IMW{@|0@HO5Qk6mX+$w*(|5teaNN#j2JJm7V-T`M)W{*i#Q;`HN_ z==7<;(Xl*ytr>K+ytEx*d-(o>k4x{;k}K{&{HNU0QL^=Fm)*$d#TlARM6tMGVYRk% zU4bzF2m{_njpxT`;+Zq$%M{XhX=Jt~*+TZGxJt)ZM_{*A@%8X~GQ`Z&S}V!f zY>J@S*JW{B2&Bi$S+>OCm(9p14$r$Y`9}mt%U&A9H4@KemqGtg{;@|Mx2DX&?E{1D zeD)Ky%|x!>&0}h~Lx$G|9t4F= z#f{*{B9C@ftDuM+%>WzA<*Nh}LLs8$4i@H&+%94((ups>qV}Gsi70M8|IH6C{$$xh z*1D&8JY%QAfEmj*Z-omntA|v`Cq=nDX`S1d9(3bQfb@G!D;`4e?f6<V9;3gd`4PoWXc8*p33~2UxXc-SLkf z^eq4HyHy!prl_}*_Gr@&3Mrf%j1N6hxG|Ta-vzGty++fTG~ZRms>SM=Yu1o(-0T>% zr5TsiFpuP(Me+o~|C{B7*Z`x#K&Ps00S&Uj(0H7BPi>);Jd5>BuPU>>`n`bOfRtDF zlhc2Z7z*=Ml7%;x#oH)FC2SUFwI$Fk&nazrA=qYm6(g3_?B@iP(gjCsf5r%#*c-|( zP>2RJ29wK-4STpW+?CJJ-osOW{w%-`4n1;PGzfhCzRh_ z4*Uo@YYw>|!6Gw|!jU3L+9_WCpDKPzhlX1X_#Al8v){gu&cE9t_KsvaB^ismJr5JX zz417+wN$M^GIB~%Qc}qAn#=9$Na)ix%G9~5ykGwSpbfWE9`Si+(qv;nslvZCjO(1Y;_hVjag6Dlwxlc$4# ztG8*aI+!q>eb^M3#TA*wk>dQ}=1=Qz-s(Kca^G@)+K9KWlFeVBKdGafFKlvKt%7_n zy_%T3ER29qqCu}ZM@6)d4C_0~VED9bwwn|7eTRtpvG#{4()Q*m;2pfvQ*I}4%Q}W- zR}9IGus}W)mv=P@akp;d+1Eh5B{%L4(wcApHj9&3H93cKXDWZc61%SulwLF1y5 zcAUgVqUHO(70k$Lv}^ZptPB!y3gg~=&_Z#kmDdJDC#%`(SS2M^Zoo|VW{7qJTP*qR zdDnxCl4BoVg?tR;*^T|(a3h2FhUDcrqrQ7#>fMWtNFu*&sB&682? ztKqMG&~W&e`CEx8HFoT`vgu2Eb{fueQRXhb?Ucs(_paPPXxKnz{Dg~*%Bwj5`k194 zA1K$(sd3*50#3k|C;kR>F#gS8Y4GB`U^bgYuEx?@%KA+|Ty6Su8ek?SRw#mIT1J^pQym+Lv!?%KFP zT*M!IzL=q~!%#+M$t8^y9i8iyrA5KjJp3!7SRaetTL>j2mF~e_1|drvqFQNB&+ngb zX9(Q9s;igvCtO1fH68`zC40vh17})|d5jmAD^dWK^?2RXH+b$0#JMm?PdFeTi34 z^Fu`&*uK)AzUGbU`7`7x6zjyK_(7T}RdebS^}6l!FrWsEXo20B+)sj!JE!g_qNTY# zw(A9g?Nq8x>CM;07N3~qOVam>i!f7gH#1H1ND zJXIR&uLEO{%;hsmyWDeDc3Y!4Zs2528-z`!98y^fdOkODBrlLM!xHF_Umcv~XF&ZN zAC(UrxbQCp6rwqdwl*FF1Q58#){OYNn(H2{jxQk;XET}p?RTXJT~(46WhYx@Foa;n z8LM6nw<7-OC(ROgnQv$E?1)X6Ol|2{?~;QMjv2ArcW{>+c$H?ss?ZWAVGU z7(#t8`5Ui zy%n5grfpjPR1J=^aBH;f!&J$;T15u0RMS1up4 zwF9S|H3<8s|fQWYL2nTQ0wypz(RC5vvNRc~nbGLWSxWlUUGvA^?Q zjLfj_k5~}{Mb03C3RlR#<^v=XO_p;msC_%lF_uch@OVTgs0oQ`p*27F`_tXSmzl^^ z3^`k}KMSA;nA4}6Z6Dymp?;-#zRHr%hZyy;7W{7SGCYl2gL1!|u;IZx++F|<73UHa zpQMo|VAj9m>QfJTYrn%$YGeF(R^h=O0)_N=+?;mns@l6(?B+h;{QvAIL>D27k^ z8-gsjGY0yB+DFbcH%(|$b*!$LclPKpd*9!77y#?X%lcdpS<+LF%uq8U1D&(e*lxWB zEX#9ew+n1_)9UxT%JJmQXl?c)(=|F!ZSFmuXBTrkBA6Ir*CPPlr|Iz0y)5Rt$64_y?3cC+ z!0UV*>=*w~alnB)1Ozun72RJBT|I?8UWM+xy4OlF#n|5+XMOxAT%abVBO-;rK(tg`970JRNU<1BMvJgk=*O== z|GeO|8!wV>O(%sj7YZ#%NLDFn1wm{7h)5`m-u2$fBxb%*MQb=?F?YyT{!UUfmil&w zF~I31DJyxRU0`O_H?S9*8&{;|wZ8~`gq9#E6pL{1D5 zeRgb-FoV>upEU6o6JWg}!8*A-Jh4ZxpBzEB0p?WNSQp)K-kb<@qvvao9+5#IHzL?cZN*ZF_Z_DyScz>iyjolGr*uwR*jt*qv+K zQ+dMIh3KrYEr0qpZ^rASnX`dCxt|?pt9Gc=>;iZ!$6bMSSFSvZw1Lg7Rk25=ydB=_ z^+KGg*BI}x=vcL7$$Tyf=FEwg%NQ)3zgrue7aHzjd6HkwxlXRxL>(hSb;1Jy)r;C& z+phnl@Rro{AH`dD63x0li1?c{&Z+OyN*rdeC8^__Ye$%HbwQO7GPqCkikG?mc~}`2 zsbLJEIPnRPuGFYGRJA@>4wj@s2^T~9rCj6u(04ZSrAAj?{0UMjS8ARpi#$VwIY>rW zu@sV(D2#4){)XgiM8_3Gv%=gS?%B5fHF2+VpGdgBPgI+MTz@?|Lt>-peH45JDNBlH zU$}N(BEErN>xT1#6z3(gNvjc$v(=-eOYf~igU&5*w-qAG}_(7L)f4aSx_KaN%YsiGl}xB zS46HqrL4MOgR-~UVtXEXp&SUa#1hUHz9l*nTl>+yHx_PA>!w!3c`@F0QLG3-m?!=t zUV5;|xcm8X{}TI7NW;_0aOn*+-_ z)dbkT5lr9wsC2=)4j77pJtGGV(u0NYcQGL>N}sLwAz==?4^~RcKOShq*>?u5rciuX zpuNQxZnf2$C%vUT#k*1Q*G$ZkB#0+Zpv0{o6ITichpCn#OHm`5V7T&MP|dsBiKLO% zB|5>HYP*20uU%s&=Gta$+1cCO8&0*feh(??>P|5yvRw(ANDHhPUVwMLY_h{Ci;Ujw!LKa>b?I>N^8lcj6XSu%Fem`5*pv~ zrLT^PMi#j4FO*taWuCIaXS;crx|}*8LG*s;y5LX7pFlto2?xZbOi=cHg?@jN{;v0D zyokqjL^o;eXwy)BdZ;@3 zg%$)T5ofP-8aiUzZb-8|KiFk2NKnjafGSMyFs$k|V|uvfYj3Q6mQFeaHoF@FRbc;b6~#%n7H1KZAKU9kQw2b-YB9GC$uogu1T?VD(5zCY5fv|6P} z1R;qtw-GLp(L0*4Uk>?%Gohe&8!Xb4d0T9us4x>5e>#geA!pW3Zxz+zuVOghxug7! z>MY}Vr?$1Y_@=P3B>8XbO)q0m@q52n^_WeyeOnqs3>nyZ8UZ6bKLIQP&&rFP3;P^7 z$~GTBOyLkQJbK{j^SWv2lDcQJ4@L_nj2~4d-nrMIGj$WMqMCKtmIVH)ktPf=g$(FL z0>W|7zO@3-tQ(7J+*pf&ie;1R3`rU<6P8-^;6p-#V-fZ#v;Ic9sL_f3Y;6sV(ZCri z5Vz;Wypw3Jak1XiZ3F@8X#;;HapP5caI8wO?EJnq zrMu}jiWct945?WwzA6)GCa$tkU+~vyUN7_lT{`9Ap}9IKcAh#AdQ5$^a5;XnshkvC zZq#YwLT{q_4RJ@#ucFT^2Tp1*v&gP z`x{tXoLKbuaMEP&*00^!9xMwaH39>5gn)s2Q5J>xsPJpp5J&8U?8lsSTVfvEFm9hZ7ef75zWwe zAG!2bw^2J(Cox4RE5nCatNMigO(%%I)FM#6xX9+^C6pKgu54lZE3*x5jz*|@oI8Lm zI3|7Y2`UBzt7!kzOvTxZACid z@tomv(OF*n7f%>WR}a$KuXd~x?XX(t`^|d&z4E(te_l=9s+R;Rb;QG_BPu}X*r-9M zZ`8`jDRVj3lnxFKQ0NN;*}+fOayw|blI4*Vwo#CeCz;sM zxpZ{4@5V+mTLlwoh(q3jVyuOOgj5=1{|1ir0mS7M?p&)l1N~+wDMM@)v-*jdWJxEf z$>R_wNFDATpP2Usjns6@j8ZSDK?-7zjOD1#Fwpg>@sP}@Scd(keWZAB@fBlDroU4h z$4V(>4F9fG^N4({$S zxVyV<{(qgl_FCsT`#k$*&sERGS6_EeO?7p>Rc{edOw5g~uczrNpoZ;3Wl!@d~>s>jH8D!}F6XnfNpVuO-aWA|QESdAbfdn{n#!c3|$I0W|NlI>j>S+jz7 zhWcC6k{T0`BwQvCz2BVu0>?Q@k%)nz=(VaTEE>dI72T-9(0ZMxv9XW}B$MQ>i_@Yg zS??BsZJ_>=JTcQuh*TpWSK=_yTG=0x!syv?8!?{A+1SzgphNHq zuFaVdloEV}?)DS_S5v3f{M>Z849QY{vtb*$c<8bQvRbQ}zb?#fw=BB(31k2~yUGWv ztwpRU$Lz4Ij6q_lmEjVU-&R&0&$gQQEFX51W)>wAEh^P{5bskuPDZNbI||CTeXOz; zUqJPjuxTD^4|aW$r~F9jRhZT6HQ+FaIw)8~6e{zu043T)$zLiasqR=%C1$mWqP z$-``gsYB~RIa-m9x}8SOmZoXF?KWh;R&;{a=wIafUUy)bpVsPX{aI-pU?58$6Ky5K zOotlZd=N5=vYK1v<@U(kS+l0ASbw{gYQ1g;cen)Pje zQr3Z^dH$M7T@75R5zr=c`1H1(S8TpYXK~eQ{;XY1h*s_GVsf|=1(oms#y=M2{b69v zt{2$^kFGsUU6kB7O8Y?`fC@b@QgH%yZEKn2h#C%|X{D;r2JA5*#M8vm2R?`Q<9~Z#-XW)TyfAiuvr6!nSkP-%|`f!tY#8RM(o702MFD*JgB&)ElaPGc*|PC-`Pt z+os?nHt%8oiMZ<_0P!=u{A*Wu&N5Ou&spE=t(5Mu)ib?}fix5JM~ zM#`FK9(F*^t&=V;=9pNp73oUp^NmmXqTo30CjoRmL0hBu-xEhUginX(!hRSH4`a{c zu1X7L^YCTXOWuRO)$CZ>ff%J@i?Ui))@t%E%c*|580@+auUT)v9~=X>E0b0}9QE~v zvGg2@l=$s0Z`rYAONIz(9!`Eppr<>KzLX`A@x}7FAQnNBUiVHPg`6SeWgBouxORkAc62GRKuYaTTm@YohHqMQjL2!y=n{aclS*7INg7%QUv9aS7F!!=b`B$Z zOz8=yd{1RY*+oS;NM0@YQOR);ngoe-h?w#o5v7qZT1HLFHI3#rbItU>O%>mNdJ5+- zX(y>0nenAMqK61)TY5B4sz5-v9Ca$;NMY(BIx=Q{{k%GqR7&~HjRf-Y>L-czHKSgV zw}dD~LFCrw(i)NvY=#q1q{76m63qW{IPef|m+SN4!p~k$2(Ght2fe8$$_62YY5JO7+Ys=v` z_VD=jy3^3#2jwF_aq(at{fj(gbrTko zx191%L#eoeMP&3#Q7Th&8#O-&f~PX20~Q)4G_Wg~7+zo2+Vf6BHK@afYcJMC7^AW+ zRq}8h>RVsu+<6gbEKBO=k$XIH++s?<_|oXrNVScA({3c{vPUXTVy@<20~nO?`P}WM8U)cj0s1UK^tL1$WIjU4Hddu_w|N4ezf&(9O8ly`$>-RjqD;^PPhZu3P(rlk5x7tiZpFK+J z^1CuJ)5!kraD~x-oVIZ=&^}qbT#bQfeTm}j+91K72Zgom&0u#|UZyRLElUZ^s zkD;(EfHqwl7S=2t@>i>}L+r}R9jicm>WQNpfzh(iQbma;A>&nlRq$R?%B~e?BWT!4 zGJU8n{q}GZ`05-z2XzeKXq0$5dT!XI*!(v?hGYkpS$^H-Z> z(_tr&Ke>CW?qLmb>eQ=U`oNcZolI5Mn3K|eiCXs*WoaM16xwKZJk^&4N2%v^u}g)! zDP%{esTvjujb0R{iAow&v!5Uj%XnZBq(POo%9tt6E8^jmsrQ{YDMI6NFS63|8`;|( zEOFR=bOSCRDXF?G{pkA5h7d6cF0XJlSH5042Buhwbaw)+`|jKkpFTrC$dbo;l#^i% z^%j6ru%}gD?TIO%O-okY>Su3%yJ+UVm0<@#icHv!O6m zzy6N1GC*502RmCR|I+yZJ5XsD&A}X&;`m+-{p**23GL!C(zSf{p zo1TYl_rWQ*iQxxRR}>~~3sfXK*6Y`wUE{ad1lsN^2ikqwRVHt@y+dthlfAp_^;Ki` z7qI2Khc(4D5WY9-$?<3a0`SEGa4u6iT$UCOjD)w+@ocl`mt@ij-9d}f&hO8}U(9a{ z67nlD9^YKKQ-D|dAgQAh!|XXg+ryr*-^iOuMbt6+ULe~? zRv&+}6H#^iEG%u&LRw|pMql3r!X?NZ$emTvIsENj>x?VcJ$iHMG!Vp5VGdux0D4*2 zBW*D(@_?02S**>cj7-XS^z1qO1EU}~94)E6{$Vy` z0@pp&$^JG-yR%de2!L^G#U-SYm82!5gnvnUp8tv+-F1gx)59uJJ$-^tZl({uvXAe| z?ek8_YS+>730OeZ{1(T46^YB@s2YxlsF*15biKbGXQ;Xs@UTO0O)Bz5O&QnnlNpY> zBCD-N$@{XnVL^;rc$z@d_a%Fl%I}MA(GG&`HRr+;Q3EY_S)e!h9!iwsp9qTaZN}Rb z=ol2Bl#7EtC;G=P=ASr*Y1|(CAv1yv3>u51(d&D^gJmlky89rwj?esZ4koe|E@cQ;1^pRx=vikX4e9kmqqw#K^Zrt_QHjmIzG zKO8rou)qC#q{7TADx#%*;n-fw#Le1vuOZFG`jr7T<3ur@HN_B-!r3YnI;HQ1o?$ z*o@87$`zsQ5tsb$T*0S#h+VE;-`j8(kM@s5ed#02F*MYuIufoAkOKnWNpsFb>C4K^ zU9s&Rqfl~1MiTIOTnuqxdYl#o?VVg}2f4%feQHKK?V$}=lJo`xloM7RbE4K2{zEi8 z+!JbufWNQnwARiX{>xn$2QLME|1OKjAs*R%rd6xlRTqp6Hhw6> zsmAI^APo@zJ8T3k=-Fy5NfROVZaU-i{aG|zt~JFP#q?Yf;xLC164o8nNU1u z=&zUZ+&FJdAtw)Mf5pm>Qa${x+3< zd7o|k(GJDMGN=Sj##PEf!k4EGb_J}}nC*GH6}()ahWlUi1g)kRv?bo(lq6S|w-s@P zo;!nTe&E9wV!zh#bTx*zT?b4BN=+F&z9!cMW!p-v%7_yQbH8mIZaag0c3aM`6pChL zn5vac9E@WG^GgjQmF#0v)3?(~HQckap{O6>b#cn-j;uy(rqg;WiO8@@P|z94(cIM| zBr4W6AyqVPtqa58`T8IklHCiHCxmDqlgVH@PT#tPguHIU?Y|LC6PgK|2#M6E8yu1` z6$_4}vCZp;HIK^VnzQ$+H+fweseQ``{p9gv!={P^RplG$t(xZVSnUMb4t2Vy; zllPI?*UA%Uhge<5hW@SJY%iO{vx=_3m^J#RpIz@s_`uP{UK7Y_@1r|L5|hUUihJvt z6qobcY;kQ!ltWkc&Q@G?>HF*TO_HG3^4i>TA+(4{emSE3#VVk5wD~?bov^R@z;;jH zF@HCpt8)c)Sopx!3H7HgGG3>>cJfX76pIQm;}?{-U3H4?KP+eqrO9_=kFe!?Q_a@3 z4^4c}y=Uu{ni;*wN2_xay{CRyE9n|Pelt)H(KDz}p|e2QRzxo8OpI}Y5YLXYucOt(a(RRfBD4MF>KW4N?vE)l|zf@Mv63T|T zuN)rocyhQQ=LMX-!o@$iMID=>7f;i=d3e1|#o6^Im^TNC_NLwhVr0EooK;3q^(@}( z3PxC-A9{CeF1JLeB*fO2tY{41(s<~g{Awh~p>#M$bKSmxE@LD8RWVn&kA{Pxo)9>M zMMUH?8ND6v+?! zGLmgvt(=fWU0|S-d(vhRp^%%EL39KNjaJn-YckCngvZ7eKWziX$y8_L;z44y@9da`GY7@;B&6Tbt6jr(G80`SR?~j%b z4+F)JB*~+-Bp~DH|Dlo%S9~yzJvLX`xp7Cusktl%3=vvb$(lhM6E0}bAKK|HV%NLN z-5&!Tg;|$*`!=jZn-qE1s%J>?+&ckk-B`8MjyxjJs}VCi3>>)&c6%e=Vn-=Pv{SUs z5V)~F5$8U{k9){se}AhtAS%6jC9hTtL^kMu3rv!VdUTVs*jojFg0q~ zv?hlhiOP+*%R9j{a}O!O#0X(m{j7b9ui+lE`NNCeZ~?HmVu}WMv+HivrtxOd8M9=Z zGsA{M10d~7&zz<^9Lw5#z}KPSW!+@4Klh2YYEhMBYrlGWV$lX2Gu7`8-qM*Yw~JS5 zvCe>ORBXFP(t5cI*HaEUqH5GQjbBmj?pfY+$pf38TsNr@m3SKGDDwEOQTb0J8^g_x z&yqWeSZEL{&Wi%Lm+PUNR7FP1Tq<5e%TstIMe>n*nfSRM5K$ zMK+PJERIWlX0Sm`O$T**??Jr;ei<31{Rt~z*_GXoEHPT*2Bxawk1&z8-;slajY+~= znB+1O0f5&PT$U0ZntJjG6D;?kh`)G3UZEpMF&6m%;W3sARsV4!Om^6Ku9e!Chz}lo zG#HX5_FG$mUkrflh;CsceX^gL<1rvhHJ}hTlM`9!lX=vqHldj9r=Z0__V~7SCvv8$ zveL57oSZ5#!2_fV9V8MWPOqcGR)~qV3i}+8y(kV`e`UqePxm^u90$8hbGXSk+kB4y zRpE&E9~03RkvI-)l*sJQ&J2mqJK~Aa6WA;zW>VHHxbt56I=>5q;D9seq4r^_-PyVM zPmom#foGZZb{KexvOjvg#Zh0yA~gixS9A4Xug!i2UIk#{>$VnkHkI-9=N{t>Go!-7 z;?=85S917o8;S(v(Phe?`j5NgBOXnHvjDm^^*K3eT&H1*)cY9#?v{kalia7B_7>mi zqO?huq!nU4-muVcuL59`4?*qrS{EuoZ(LSmaGb}{PZ@p=1hc7jyioU1skXS%&}O@0 z_)Q=`uS?kC)Y^gn!_nLq!yA4*QoWm_@@sARg+&Ao(^VS-*XPyux8~y!+io}h!W1M2 zdR;e{ASXP0+*Qis2}eMz0d}G*rwYdR-o2oJW{Nesf!^ZglDBNwV|r6IT(@Ql{G>$1 zL?Izn`;Isn-A3<3D+|S6r4qXszhUz*KlwLj_bT`k6ztriAUme-vD1|N&h9h`9l}kE zGxRzB5iusu?wLJ2@%JTVg^!=Rp}u}KNnmW(QV@S=wzzK$4Cusca{X*~zQS1kI*EHv zMLT<2^sjmpsT`Sgm|39xGCj*ViG;?w-aYW*N=v=fbNC zi{)yDs9w$avo`L2S!p#ElZ8c_5`sC``2D+yk&?g?a2q_y{oD2F0WIQp-OmOEa}(M* znk>GY`7txJ9m7@eIo-oZlzhNmC6`k7@8!3pO8SnBwWTipINBRg7wT@78ALhS-24&* zyBcboHvT3hOm0ddkkg5adh>9uBk0XCB8Qa4MaWY9GS*6&sI0Ecw+_|~2Ig}F& zd;VG}eXVIAJM4UFBEtZ?Z@ny#ng_=QCG26A)CE%DOPkK?muOW~R77GlG5lfwk0|3D z+Kz@mjHKtq=Zm`f^3z|WuJJ#XD%!wEDLgd&0r@jtH0J}~Ljn?`4B^{fmRyPvTJ3o~ zO^)vZn<>^Mie>x$ah`s7UP-k&9el6)1eIM{8kSV3f+(~YT|*3vg~&51s&A6WCD*!v z+sg2mA`Eu6O9})Q3LLBrp_tKsw5!pWwhOi}@h$}w1_hOdA%5@y*l-EkJ`MWZrBGj} zJLxohH#Db?aAAPr#|U%NUuTq?dC~hw#SHAQEJoqvwrmpv7s>SCJ``UzAE&B2JBO4Kbp%K z<5VXBV*nw}1u%pgAEDph&FN6B_zkphg-hx&d83$QI_-e!fhvI-*@7L!+)B)R&8Ql>JpY@lVkY|8gG=3X){yC}7aZf{hwtNdkjq#eit|u3p zJ)HJr!RnO7&Wv7mK|H45H#~|Zv-)N+WDONm6?pP3Tl#bmP7p&Lj+KkcCf|3fY0bf! z4#RTPCWZ%x6KObCX>NQ;&LHBNQN&gm19_1^(ARB!a{zcj>ggh+V$<@bfwr9HRm!q~ z*6eMVlrQ;D<%`yfn%`0Uil7~dqJEMd`v)A^eN{5jq^=yBM4zZ)Xq5VozCj#i@kCsw zv!R79Y;Wd4W5!W$Txy6gWD*bvXsywU(2Gfk*3pD)&@A=ZKo8iKD@%gRi)CA!)BS-1 zNF5~XcEw*s5}dxvGt(`SPJ(AwF6)Zv!ej` zO-k+JU~qh2cwLkNWMsrzRm10TfHpn3WQfk)bOk;Z`x-CTF0^r|^0|t~ElZK$FlPDzQ{|HxUmtElf;5{&atj1UI48 z=J9xiR$S5e=^p1NcyZXOuX4qWEH{}g(LDZYwAGl?t2V?5`TA^)b2dkw>HH>g{hi!Q zOwR6hvY$Wx7RLE>Z8h+=t_)-GO;Ui@WO5RdVoa#PK;$ul)vIjTExkiH5akr>hxF{? z?7Xs0MG9MsHp-3`C|Z+(oX1{#Z*1|r(NMLpI?^+%kdcqFvEt?RAOVj0UWRb7&dE7P zGjxvZO#StPibAmBaZeAKI_)EgFsgLkYWZp=6p1o^@896t4?nrW4W9@OjkH@|-G7SY zZSb++KgC0a;wyhJL1gzauAv2K*fuM7Q!BIlc?zf;RP_lA)4)L=>P+EF->1Dlq&}Vx zT^JsjnF06o%=GjG*tP31i!*((4@NfsAjG%K-IO^FwaAMwC&RP~+mN2G3DAcpL6}!0 zte=pUr~I_HF>PDL{|{L1?Weo-;la=KKpk5r*je z+1oqdJN5Eo9OtDmqEd(izHUyL-M=GigYI+QV#*#T^Ccw&v|5fCE+2Oi-!oS9o(2mJ zS>9)+=u0t3-fzQ1K+51@oQK2A$JJ4Z-9x+|Fc2P1Zku%eaEzs!Xhd<0{uA$j8D*Zx%L}hue7fFwBUeR%G zs;ZhOJfn$LtD?tv2sdXBU%c|I>`aE#=Br;y9g9PF85n2 zguQ#}o8_{UhkCqwA6V3PYX$z&*Zt$k?7RI%4+Ky-7ee$!*mP9;kG&n^nIFxJL zTQ_^G)&*~V`0P--d%cZc2ph5!UMaYv@Q5We>y@pV#LV{;)VnuPiw73>iF;YfmQXl% z(xSN?RniKz8+FhvHSK>p3`q2#VR(!`bo_*Z%Tlgg6hbqrw%=&UtzB+#_Ygo>&mO!{ zxDZ~-J9V=QkAc6^az#&@T zipPOZN3KqLJ>5XafTKO~^=!esKN^0f!*qq}6ZQVqd^+n25jID@;D8?5@dRT;g<<*E z#k)I}cn+q$xumcXPBDzGRV`p2QN>?rOHJCn)MCRZ%u&)}+@IHgj@CatnuY!LK4>fZ z^~Bs@d)_aWCv{P6&WfD3I$!%imiIK8mV#Qg<<2=u{lQgYGHuFb8j zh%S5_VTSWgN=d`>Cw>8VhQMfwB+1O@Zgrv$#ZIL&ZKS0@Ji)aFl|26 zT8pmikHx`a_m-+%%x5QdJHHKVVM;{2$FrBDmOs3{oCob=S)jFv118|b#==Kul-TTk zL3e}O)cQ;C{eY{=2uamQ)2`q-4tUMs6))1(g6$(s17k!Rg#-Ka0_xeT;js|XClGR^ zhzRgv7B(suSLDH9)V_!<`J3^Hj1BYGEJ-S|KQ>pD^e)K{DNbh=%8tPzjmYS_m<m23n#0l5d?eYN!l+SY4~1=9&~9UA=#`3*8B+_~?ESMvTAj_@7j4S@6Cz6(>FADLI(RWh3v3J^lkS>E9SX8+-i;qDdF3kY=Bm%_st z?0nfgVumYME>$R1lE{~0U`+ZwJUk*fGAuD7F)~8F%QA%K2!y3&%~F&brc1C##|=z% z8Pzpkf=cN8b$V8LgE$5IY}5A9Pb)1fEUlFGlH}{>0FPlO5uI-wlky4WPUu!W#K~xY zu4ZMzFl5!p)6fXL+hW+OjvDp2Ce{kbdjT;L{_^Z+HVglpPcz`sQzGut78?nuo%@Vmrj`+pnTM5k;JTk zyu2p=3Lz{cW%Wa~z6tKbaO{p0cu)ET2`YS^`SQb?hg5M$)lVR5fnJ=*PxJ zsbi@_<_#|Sq5S+-A2H9aj(k^Ewysz(sOiCsb!y`=N?Dea(okdlbZYPFSOX@)<;q2K z!F^lR;gpkeq2EIM7yVTEoSkF(T)u~{45Wy#q)(^}?RtPFb*xdrb}5Ywj1P2Y%xByJ zPRD7 z2oxoyO(LdHpos}9&=mJ&S?l8Ouq>-5c`8AiOml4K%~C0Dx8 z;D3GPr!&9p$|iIs$J}~3K*2)VRvgQor9zE6KO-puh?ITYh7IFG5JN-uiik!~-Jamw zT6rqUEv+f?+uEQgC4@X=kL{I55bS#udTmiIIlsM~y|KGnC!(F!T=->+RwEt%s+Ju( z*jlXCF9$SYh6vx>e20t^XN2hfyHK^@^;eMesPMw=pz`UyU;odL~jY}GR*m<*7yJ#p|uzJa%D5`&zS;WDOomT0Fa>gWke_R=5 z8le#loRvOGe|GCX(d156NJ;F;HYM-5VoxYv&o2t@Q2;hU>$zeQjJF+I$hD;bVphF5 zB$bwr@@HpS{fA47;`o$U;WI~0z!Gu8HZjf5jbEVaa-GJnpwPsKHAy7 zQiie3ojh6V$qto-F)4Ya;_7pCUlKzH6#%;Zm$ZL__pk!P7z=rF5&OOR3K8H9B zY?Nk%W{Qjw0rY?KoMeEZb~;+)$?g3|%!JdvBBFks*m?4yWBJ{mKQ`&3B_2gwOam=c zLg*V3T}md4l5vy-fGc(`!@OVuH#Z%Lp?9>fNY_qyduxRrD>)DiBy0n|}YbVWPNKFZ+NmT!%l z!-7`CYgR0_EvllNpWH7b`Mdu7Qy|Fa$;DOhQ?XHJ>R5-lP8~L|nz}XKdxCOI83RKi zL84q4?Y&+d$+9D7wPX<;pCfIAJVLqGYm0WIvu{zEJ0?P@K~qF^_qqv5jjiR-KH~>= z8OjbBlO+GF4?m2vMvzS?EFG7)F4ENYzx7;Ic&?_0l^{}4OOziUd4t>)jTSh_pTBsO z=8IVJ{rRhFeqlDBIl*`vezVuL!U@5~zT)A#bQ0v@#mSHkLV_IOKPty5`Ob^HTg*g8 zJ2pr)w_5NC^4EZDuqo_3?Z;|1Oxs?$`34F*eMP|e$?Ein8hx7=N;DB4M!MK$*)HJN z7j7EWT1|>V^ic;jM9qRDmxYWW7kQf-Ob_8=c_*OT|e|O z%nV+2@PIsKT=AS)vj#(Z0Z029M+U6?&_Mt$5|*7qaM7T;sqMf)8}*Y8x7r_A4dRnJ zjua&?#jCg;iIBNrLst&yno$-3H|3wCXA}g@Iti{GTffg z$2;jmIrNB^Ekxd$rloM7{CW%*B*YqegRSkth)oYDq~ z3uzZC{t@)QJ|-SjsiA+7%vG7$HLg))V1(+5 zl3He}FzG18!b*#Oh$UEobU)Wpdogu#sZISaasQ5P!|fvwkte=dfAZbrknrOz5th{9 z*z*AlHxIeg&c^*+4y-7gKQ!y5znL+fZ$6}kg9c;#cM%a-`#gyo5$v)-OK}%&jMbt{ zrD)sr`{BH049f+x%|{?iBwFwK0;M))yy{L^eKl_O_Wv#b0+(4PDr}Do5p!+QceCDw zi95e{xyque`&en^=}`HWYDdwatNWBU2v5s!#!p_@ihrP!{SX%o$ zO1s{ipAi!ymgcK+iAS-U6w%E{kxaT+UbZ0GxvSxzZdbVJINj*!@4_36q!SB|?r4q` z|C5{GUzu{wRM!csahu6VaNO>(b&;ZFPYyk9JanqJh-;mzBi?~EI!bd19ENjNz15XNIsz+6t=k%8 zOe1r(CVF0r65ob3ytU%ja@~clnhLMYh-+cfkp z=A2RSLNtY;0ROLvZ&3X{r)_;HKFJ2Ab6mh8kp-(|sC*Brtw>F_HFX?p2+Xf5OIkma zk(|r3HS;oeAuLmOQ{LvE!kydAq1NrY%?q68oZe-vQ1UDTU0hszxIvg07%9@CCoS&x z3>k}0@kM*ser5qbrB`g#_!9FmC=dSx_iZmVGLY{g8Qo7cYq>BoxiYxb`TQfi zMKatsoEpDtxciK9!kJb;Lhkh9o6!q5b{&T7xmZZim6@*f*k6L!8?+Hp3Up(io_8}O zeD@f?R|!EuQ$9&u7F9(_Z38`@ic~LY>36Jg)N06iU3s&-p}2UIMRzjiU( z)#p@#>4LWuj|C+Hrm^^2<9~!v!JqK5L+>cVx?F;Cdimz zwuQg!M?IEK0@XwI*Z@A2CsFw(uOglQS>>=m7qV=>BPvWcLCW-+fhcWFw5ifO+(dlf zBZwL^S}=$qv5xox8{;jeYbZeiPMt1NqIrV89?dMxXV0g3G=An@70X|~GfPO1Y?t!l z!S<@}8273f_FYyeE;;NvS-|B1tz>^oI)BvZF0Jg~F?Ro!4*y@L9FdOe1=MJ@_6t~T zkmX+Bx5?sHvQbAafKyM3`7Ti<{H0K(q zCGs)gnFFjywgL6(3N&*>2H242Yat*qC28Qfi5?KV)>R1Az zMl`;wzyBkm^ay0oCKHF-A!ja#D*ghhcRWOW4~Q6 z9%ju#uwfP+=H3m0o}(oP<&(~4y`gCoOzB~oYDt+gMa+g-t&XlVKm2;FIYAFDA9Uvw zQzU*1|LZL>gD74;=Ut|JG8tXl-Hf?*I++073wH+y)D=l-OaZD6eBeE~&aiKtel#G6 z93xTZ-5XPaw(oeM={Q)_{%r3S#a08UL`I+blEvmv!Jv6)XL7kGsU-C1(K7b5R~sXc zpnZO>RYk=Cz~g>>Uck=sY^xup34AdEK8__LYsn~tFOO0}Bjw|(J*^X-iGKm2ePgj3 z)Q*xu?3n@Hz|9{$fQE0Q&H1~ajqYHFk9xR8z@I1kDzR|f8qEqxiT ztOW$Mo6V_{YSJ6(I8o(OGx{2KeEPD`eu4Xpp)5#APp7c!Qq@8bA%w=xAl%WBS8cf7 zU{6s{62EP0I{PNKUSf1*wm=#FWb>j|BERLY|Hr-I5G-R19k(XvnrdMwf4)!JdWZ94 zZ8;8wEL2)T<+@nlVjx!JBNv`3d`;fwaeqNz5l~NoRiA@F`nYg!0>+?br=N?{7H~an z1RLH~Hr4P}$6=PS)@jD@^KEx_i%Xs<7uOg}_;NZ@P|2(Q`2*j6d&2mx=EN5C(CNf^ z=SrFib#DN$CHesQpwc)j-EQYp^!0MiS6fw9okV_EkU61#4mJ?ho5cQ~2IB|6j*?Bf z`ql=wy~3=Il`8TOf^#rRqJ3mdv2RnZ0`7S~UYcz!0gRo|S3yNEM&N?nLg9k+kknYZ z@)AY4h)tteZw8m%(Q&!5*Dc(3*Ik(FFC_dDI~2nWArs5 zLNEyh(4AcOBs|FURBlSbKLDY%)}Cj)x^7nouFKhx?6fueaL0L4t4h z1JQy;kF%v)c3zqter+9FnwY?Q;Co^PeDV2vyVX=68hmSmmGQJJD>Y$pH~4vaXb$8C zYCg(EjoHBz*(-3h-@3+0YQ&HGl1U)oCjN2|n^7qnGG^=+@#qE~m6#}5Mn)G}fblLo zcPpEV-+N1IarXs|1lZFXbzCk@lWW@~44a$tjS}=Jkw6Acco@FijcE%@3Qk*FGwU`2 zIuWt`mYQhGm8rf)4Y*NA!g&`$Mks`E;qS33v;F(Kcq7T8x*)BeX3>{B#x}7)t_>>p zlr&QL%3z>yA_=Kf;ud!;D)NthHsr!b+7Lkm~^Li^fkrYeu@&ckQj>1zmC zrOpns)A1cH60NUaoXj1W{I-%U>-YJKXS~G^P&H%2NQ;7%jkeJ({_EC#(w+Vm^uiUz z&3z#SQlEpf*|R|Oi3zWSd=)=l4- zV*$KMFoT(Z;qF3|c50S)m7n{>*nuv(wM}QCpv*}Qt4Y9}n`h6;DED!tUJW!Qfpp5O zf~vK8>2GewKR=aS?^^O-dmK_t`8~R7%AUhejEwugkEhQwBnPmWVQA(z$kRKDUHKXV zL5~4^)RH@jroMVL;tMQfOoQt!(2x)=Zd4NjSxy$XNt!$Bn9VRpRym0Lit?M`_147a zKXz~;*${e}uqRCGgNJ@$fk|zhXq371H-DoOl;Z&sjB;NT(O8r+b^RJzKi!oWI%LxT zUx?USe~W4b|Nl{$FuPwW(W0C6*683*$7^b{I1X9*^WX0^5+WfwUqp*5y%14zgMJ4r z29Mpto8z3Fk30W4oW9xCezn0y!M|8?|K)nicM|`Bc ze1%`iK|KcZsR+y&I*FvUM!wA`|7a$DAT;pPewnM*3YGIfqAw>bqB1m=KOoENbm)tM zDr4;&CS5ImF7wUQQ?EK=B6sehXW_-CQd9V1gWKiXt5`xgFA-H&w`I}TCxBZq;4djcX!qBGd_7HlgB2sCkjd`}I|i)GpJOc3no+oNQdbo7v7F1F z1@3%TT+Bz_W(1NsIlL|1G^Ih;Q@k;(Il!sFb;?lQS5Kv*Mxpt(7t&X%&|)K7nYW7XT5Watt# zwA8Q_4{vtzaP2za*vpc*sCjO@9;^oO>{_L=6=9IaDrjkC?;&WMaKuqb6861!@QPd1 z92EW41j*+<*XgNKEw^A|3XW4n>*^J^2moXfoebvgB=dI0g6L9~%L@!-83wHQq(t|f zPFYO5g5kvwE-ofZ7KPIZUpI4f*_}-y$}|de?KD1VG{yd-wm-_bC<6UnuiK2GddCV~ zKdykkz5dQAQ&94iZbnY5``8lD@A`5|xr2RYa^UCte3fLm0Aq<)&c5GEQPrzD4d&|P zi9kO3-5eUy`Wi31#9l(y+J9U&8}W7`T&Ddq;fwziz#UjfCR{>GjuSS8d}VdHiXJwN zy7Cu{ifyzzSv%8i)D*_J;c(M_*#zkC_a^Twj*i^GRS{?8$ZK#U-f^i!s2{??XlWH( zqjgVKbS&3-^uK;#KEhV0&)C+;7?QLr2E|5XwOLYUFu6_PO@(n#exsf^19&+RHji=U z4Qx=yQQRRo5BoqsunFaHH*#6^I$klyxwzYaiCw4T+JqYn_E4cmwO*q)qz69 zIYP-iecGOGUi0o^utZgFhZ-AXqx(^-Tk&s9Lua+9c6`ygto%7PdwQ=lcO0 zVq)Jf8{Vr%@jxD4ojC@3%^NKgj?Yy(>}1FvJevfFnAjDW-57_KFR(%L`^xBc3GGx)?*oPK;{bDaELEHx@=d3TAOE{lcg| zs6%RB;YQE4z|Uy@0I=ZPQpJU`H64J9tpq2m>`(t;#s}Ce{ZqAT`O)0>9{8|5l0%+t9Skk%h|3;AJJUCEgEpHVi|x zLX?xX_>#^Y&by2=wpQ&TD-#Dd^ZZkZudI46-VD04t_2<*r@a+^D#0_N9@ihfb`(y+ zVfkgI8MDrwo_)V9$4KUYK?L7pqXam?yw|%}zs21)gjF>q&f|8YBiEHi>g{5%LGjHY z!i=V6O3{smx%?D@6|swE8qJ!EH_=)-ribDIl1#Q-Unmd9CL@Z z=d;e{_}tw45-&ud*Vt%(JUeTsyGLM=wADEOKKBIo{aW1h zh<)UCV9i(cs#abTeVZ^}!?w(kmzY$HMKtau|ba zr<6A516Zc>Cb1qdG{k5M`D8l_$!2cI&v3y?U&R=BEJhcc-g?eI(IEEdD%COK9VYs0iTU zA;x1c7wGP}jQPCb+D7MZVHr*76iKBw#8F~_k`j&eIzv7zR!OV#NW4-J?Bl28)hl*G z5w&LLx*cbc80!&^a>H-6F~Ksnrakk=bs6I4FHOBoCfRIEW;d*az5Z|ef_QI2y-R{J z==FH`R%$$!Kh)GPuI^BxgR5(HhW`gwZy6O=u&fOe5`qPX!6A5X3l2kYcXxO9;O_43 z7G!YO;O;WGySvLr&Ry%f@45TOtXU(yt9MsbS9euEPa?lrjAAZwe{bVu!{s2de7aoX zYLi!)e0YJE7l@byR8(ifY9?H-ObUO&w{+BY%yv#$KMkn1>KXNq``7?YTON2P&9)3^ ztgKU40?Fz5JA5;Sh87JOj(chAZdE4@m9R9a6I39Zm}u83nF|w)+qe)7=lOdu9ZTcm z5v06tG*$RqZ@S7E!GWuSp*2cFdI}JOG*}i=^SoLp$*hSOUZMEwNGWQiq?QN>9aT>% z=f_xl=J`BoIRnt{RJWH2ZWEO6%o!YHsC3IL*6UOQ=uNNO%2AiG6slCna=C<&Wr^3z zYxoy-$R90DmQYh;h;E%9iz%*C)6tbo5={&)n2MeL>{OOZ(~uQEwcI(xMn=auv1qtc zkIa)V5%|=xGbo~#prNXke|?1rhm?#x-=#2Extk=b!R`7aEF8>1x(e7|F?@RHeT?V& zf>nNb6^_N|DLm!AzDuN-5LNtd1-A|l zE%P6}t$1DME!vO?xe3epr|j-I9+@kdVZY`ROBop%MM9RucKP-@|DhS9&BG^4_$m?r z{X0zyohu~InUE+%tqTi{QB^VsNPwo=X%s(Gjvw1QrH4;c3*D-*VOXu{hR~s$rVNKWNJ4;% zj$=VaAm?>f2G-S&GI7efTrP-lMUg+Ru7;W8qq;pl1_uq7-=W2O{Xjs3t#5r-dp)a6 zS})VBI1F=7groV+T^QFw_WcxRauSOFsX67U_9r|05B6W;S-+f-NfTx@3T3tAW4vYf z0K_+Uh_Fb35DFjBR*o3S7PpA?=Yr8Ivfq?_kIiN)q!=_7hm79Rw&ViIaI|$s6)2={ zyNs$KY!t29GF$bWe>)NMZvL80>DY{do?a5kZeD-r9c}zr9{L)yJPe4XJ?}*dSgQ|tyd)T6kg`l#%eCM`oW)Q zrs13vXS{I%QTxevW9O(HyiTZcA3rzJ>eVdJX}W3h5QaC+Nsy}{c-wLAMOb@7bWPZp zl=WgS!0L1wP#0}4{RV5bj30}EiMLkey;Me`fgP=4Lb&YAiRs#y`JhWr7*Gl~jxRbQ zvNn}3?0B7b*MAj#3_RP5SHDI@-PdRyC}JfU`?GKVYm1iruYq z44cuCt=mP1RE!JbVd-98UZz2PPYY%vCSmJt76PmDYV3ydt%QJW$VwD z#Y4F<1H2EW9s}Rw0hWI)GqguJrT1CSYqKd978p*3vnCoe|LA3uw>7vgzMqooFtkb&lkOnuP8}gtc;=wTQ!_GU-FlR^tjkC4%oNUdZr6Tj;czh-|>g>GX>8 zoVM%3@2AXKLF1+QsKFDWhs#Tol4&cmGlh!cVqi0*x_Z1K@hxGba8>sz2(!xd(&-V8 zz#<_DWc?)===&^|6@%@)d~!MA=}!v#h@nWOHcw}jZ=%eLdJ@xalkw5m>XDIM{YePn z(JF?)V6KRY5RB~wZikybJO9f!(O z!8l#5q60OJPq5eR&#v{-FHgi--EUcJNA4-DaXvg4Pn-39ip1-;U@0CQ9h`5cYo%bdI=} zABVfBIE6eH^7*Jk5~Kk0&bVGeC^J1z3Zu?=YoIU?Z|~QeAp;Eh?^s0HS(-(hr#EG{ z`<=>kdaJ}{ZOot!m+2JMR=A}*nsgtV{ooRn$vRyv54UaJ1zf7JYoXN#B{4f)-}0@z zlgmWKrC;iH>Lcs$FgPp(BlP$|M(@?D2hvW?r`Pn}wa>yF_~SXCVU7;7wG$-kX|sOd zrBAfOII1v`q#56;c(mtRFW6ffG%YW*!u`JNK>ud*Q7?>pzt1={OmD3waJ4vXls;2- zU~{H)MU4x7@0XN=!amwtZl)+iA9i{5I592>i|W*M`RAx$RpTkpiK-hnCq{5xp=G_j z_y)OP#D+R`cV@bu2p?k;q=m_fCa$h3M#4suMpw*pyJBxhY3xqR=w0@d(s;r@FFWBg z=>Cd!4nkfD;~K5;6+?SgM;f+b+1SjwiM-#7Tt-yVw6tfthGB{}D%`UD)~WmVVXY{c zur@|a^v?VGC{CvmXI*)v2#Uk=9ahX&`D=;b>=68HU_vle!)Q2jKFhMiwf46xKblKs5y1iTR53N#%VPu} z-kRqOmw!$3YH^tmjCC4j@1N_O4D`4hh*OuU?^;3;6a{t3)V(T}`4G>BkG_ zSTyg3BuiI0??UqG%cgH`9kd^paKW>9W`vnsuJHYfQ`C^-;=Lv+4qn@5p;`G>f;()m!GZwJ-VGtxm^D z$W~2&(h*ZvD3RM^JVTcK80#P%+C=nKCqr&Qt^AazE0pJlbgr0}iDhKHAnLnHbu%KU zSVO#r&E?Nv1fJ5Ov{;$ExYlR&P8X}}uA9g&LvyC5$;}9M#8J-T6m<%(*?noh+cbwD%c3 zi-m~OX6W3qc_B$jXJ@W*m_nzA>p{`9>1jtLkjY%7A7_Xe(c@)KCrFU8?l9dx@! zc2mh~UhFy2521k>qOEgtJZuxWgyGSNV%rD}S>H9SnbWo&{)p?!E(IG%m1=Zjer6LPea?;Vh<#Cm6bQ zc9;({1te&Dzo`WpjqXLnJFnFaka!%d0L!#VwcPdw*ot36JUnDE3YX<(=w7qEN1HyS z=uz@-WxvyAMB0u%rvuMN&py^lXGo=I2W4R~{MN3TqTlh4L%DJx4v$u^ZgHp_$_`1M zfs@X2p%x)ci7n20M@tPn!5=%c#TNXn^Ll_K87JWe2T4 zU`!@Zb+mL_njWP2lOf5Y$n<@!W$P4y0y*~mdaXQbX1SKNH7Y6|(Eebn!;~s&=$b5( za<|zaQqEO84ycr-?|jx=fB0Td$TX>xwQL)eW@^piF?AK_-$t2OXE-nr4$f+#804_8 z*Uu0-BmsbaJImtDuW((AS?FNhaacUhLRUr{EkJI&a{=9AxnpHtGc`g5USOQ-(dr2o zFf-YSn>-k0c%l%?N~L+z#<)kU@kOaqvjv~fw(}gtanNs0lE3E}Gc`U$x+lLH#2M6&!UaGT%ff*Uy3z6 zwQ7^T;h#`MhulV&{;Uof(2u-zv}iZq3+rz0nf>9rr_ z{hxr!B7JduGQcSz-xtHLg|u}JaVLMBIA~KmTZvgnseri#{L3jRwyAxk7Q^Xm)PK&k zb3qyls#vn$5fQHkTz3T@LHsP@wwBKvZZWgQ3oC5F>Mpv6kE_MIHXm0}?laU#y4$`O@^W7ttJiSFLg9DJTR3XytT&JV zMvr~DJwf9B{2#0fg%Z@2XE~5dG4DG<@LaRXF_E7d=weo8c~Wso*~O0eu8R@AA)`DU z`L!RjVfn57?6;x5^6bpc+vn>Ro^WFi<;Fj(z+bGnv?V7Fb3PsM=sN2APhH4aKaWJX z9>Mqy>0mEyEY$)_g@e&vQ>^QemYTr{TymPS)P$WEqVvf!^~KljkhD$E*?PF>=nnz% z)?cCO#iOG9Kp>IU9#SY{1tRGkV!;v_!=(Q7M43np(U$p&K)d<%k7j${H1EVnEF({c zqNLRU5e12S!xzsn$N1!ubC0VRm>Eif>`jOF^Va=%TJpS`%UWeR4P*oRUM};U<>L-) zRC89f4SX)Iky0{}WpFs^MX2jZB>g-OudHDo5^C9TNeC$`FNW~9SL4-ZlI3&|7;Vnz zWw}f_x_tHPH`jVk&+TEe_U%-cx-WsYqKy=5k56;4PDfe~DVW+>35I7{$`p_gww)`n z?LSh7AF#@`zVi?4M~?@3t*$*jG!HIOoY&WNcD&#CV`gRWT*kU)TXO2-ymzfYkSz5r z%onyTI~8{Ohq%Kfq8V`cUFZ_$jb^iz8Z0BpNBi`;5>5~jd@mvw;PK}Kww|yI|0ec+ zp5mT&uF=1s&SqM|$n$c#e4ubB(bdm0UiTe^3s0y`Y8SRpQABgjtkZ3aFlQ*#<6&zs zX8wZUefz5BZ-{E=iO^r+5Ml6VFqzlgMArizg~{;y&1v09{O(AycbBP(BH~s|_4WQ_ zaTyJu)RN`p)&#n5$_z4i^nwRS4$yer$4w1ZB*nD%Vpo^vJ#L3u3F+3`-aQJ3`UWE;UV2VX zKxCm2xkB_pQc?z$`nZUSI_y$V%bQ95Sy_`KDuR_a2ZYI24}wcqS7?WUPULwp045oe ziuDKB>yIvy);tF^`yXr;vTlz`)&BLyTd}zdAoyzFw?!eD1OE0M-5lS-KQc-Jt5a7p z8bU_y^Abj4Cu^0~M1}rzd)hWdO+;`RlZ>H!=Stk&IU2=uTAxupHmk$k$s#-e*!6Ml zxy$ZzP<{S6lzI{FpP6W*=~o7?F27hkZO9@*#1tS&78T4YZElZzAZqFH;K>;|8G7m+ zVE*eu%dZ5LnQN4GoSpb+xg`uID0h(vH+$*wg>rvNhY3b1a7?p<*~bXwp8hRCa>`*H z1~u7sh<^U`4;bNB{0riKg&!^P$dCNzPePL`nowVeI+XbzESlECKFEUUT>fVE^B~p+7DSeELlZKr z5yk7ONXSY#amLevlI6bu*lD03%;4=94 zp`s%GYKoRvZvl<6HkdDh5d4oYt4&Urxme@EL|FoahZYTEO0$#Iu@)2iB>eeRFzw<; z3+npQ=Dw?6>zA7Ao9v8{urm%47ExZ^>*`WC@53ymtiBhNQ&G35l*pH$lU@eIce15qw_w`vg4~kmPw9;0x z&vt4k9O|=(V6rApalsY#LNnCdVpzqr!_(6n<&beFAM6>2Ylz`HQQ^kPW1iXREA9=h z-T7p?3}~V#uv+cho2A468QQa_(v#mdGyXO$kC^j@%K10pOcg;5#SH;gb_ZMdkBJe5 zBMmS?^f``Jvha^s;(}j4m{uy^_^dvwv|&#V7VIb0O|&F$ra)U7ob?s+a$nTe?E`kM#LtRiw}PD*XDEBz zZZ2h3`IGX>7$vX(>+_03@xlYo`8W3J&33aJQCi!o%f<7qUv7_ZQ?t}%?KU0H79LEg zu%tcT23@a?x@e!BFOqjGMl;SauyBO5LXL)OrayLOBgK{S71kLS0pkluB+z&9sXsG* zA+z4rH)h&$6*bQIz2l9_vbX+q!q0shZbv&`M4F0(V5SbVb;5c2xkB{0|p+nDq-m;LNaBmVy??zTEx1x?18OvuBkLcAzHIgT4lOK z#Ytk0clWYiK)TKLJOXFWi2{p$zx@C5jb(*pc#g2jJxfCsM{MfSa``<~P+eVJ-d|ms zFp6GW-rw`R(M%8NZy9>D`ZcuhDYU&9A%w~#*piu#hJLJG9N=~n1NW$*&*;r~-wTaK zlH0F!M`~%9AEkgBkC<8SZ~md}v;sd*L6PDipIZ^JWZroE>&S$_^hIZH*4sp67`{p={EpSO$$xAQ0-Y-HP!29eH{+9#fOc`)H#?vQ)tI%Ik^j zG_tu}6fH%6*x`EaPi78Gg=2tJ@3-a!TKDoK6Ks7HfX zkwT}WGzG^L(MsDL=Ig&~;jio5l9Qv2l${>yehjuo2AVhYayK2P45g}tq_y96Fs*gh zEJ#oi6U_*Nfy4lBH~=J65oN=-zn@q*G$=YRJgo%!Xe1Ap9@^?ehbnUG1y9FkCO?{8 zaS4DH>T@190IXi+P&`Fh-L!eO{?#DHVu>YC+*pcb(%TiI{2s#}?EI2Lort-L;Tr8# zwZvW|Pd%BKPvcUJ5*SdAdnejTghpPj{_x_}{Ok9bVwRgJF4B2##ij;)7}TQoehHzu z3Gsqyy}EH9YXtRKl6=Dl@%zo5jUn<>iT59q$X(`=A|)DTGYyOV-SEJm%p@F<*CBT+ zgi|?ho9pO=jFhqE9!{Hy2$c@w{#jMPA|1oub)CKKSl0)^TzFil`omRtt9ByER?Px2 zPWLCeZrXURA&*`jql286m1b_I3GYFF(aB10&C{m8%GB&TPBWD$$ywgp_fmufzgut8 z04;~w#NQHx?s2eJ9dx_Sb;^fikp%pl$=6bn zv!=y+`s1#P#yxnQwmF-WR1okKpE2(BPi-rrg>p3Mc=yoh=u-4fm842~ zf|dxU)1h^di?;8ESVmL^n}W#DgN1}bk6+jL{rz5bCq59PKk_isbp*00&`|I!WehYT z7&J+b;E_;&r7w?)Rkmnnt{Iyletm_uLbG&42Tj;g;IX?3C|MIt$6`xXMHLmcWXt+t z_j;*>i{2VGH+JUty+bf1r=a%HYImdW;@n^5Oizzte^m9~-HD$d{=Po^?ZIN?&82w9 zfRB+?v15L3_@$BC6j`_6@hvR&mrqI#q9jskFTbLHtaSeH$jHn*@?5>IraFF}^F5j> zewQ-gYTKc_nUv}Dj+mIS#V%}t)e|Z7*}9mzf`>}r((jz4%S%%gMq;|xEcKfs81rcA z_NS+31#MB1V4E%^kJ>%Tt;+LM{2(`HtG;Fj82b058#hOue(uNN1sE<$jRrHRrHGK) zWtoIF_NKzZ_{(-eZSAUOWvO)7b8m+B_^~ax8k@4)qNC)qwa9D7Q$PRDj|}M)bcAcd zDy^pu)XCqNEhhsVk0dxKv?pBj@ zxxOs7#=Do4vBB=%4H=xE<+JjX;-#W2z!d1I`8B~Q6a}5I|ITe&E04C|brFMTTt))d zMvS`TW~BLtPta8eq44WRUNILwG2{oQa|dY)o0HQ43luI;Q{q&PT+HKXSM1#j|@S*lWP<=7ykB=KXR+7<)YCVepNs^QnJN;UJScssj zT!}EDUg<<4=1R`-?*9G&sB3((Stol{EjTm`e!s?`QYXi6jsN+sT*pXD>5fvJGmb1O zjgc{QF))0m1)R)Vl&F5wgR9mDc#Ub>nBye9xiGCu8~)g!gK6^@=xfpx(CkRyYVj1A z(PXCIODVd6zbFR4_R1lRrO}aW;4*!v@}wm)k%hW1E};>kWPXkG(aUT(GulRq3{<%1 z8zQADp3DOmmPWD5~`hj5U~T}Tdz=NXm%S0r@Ek{uF}pr--#eda>e zN{jptVRc#_RpeP7R7QNezBUr1V_4E|)8Ef|;UK5QF#qOQqUo89>bYj}f}(?CUF;aXe`{tT^l zu48}bWn3i9mu(ecWXr?zUq%b*Rx=n%LQwazs)nlD0RErlh9M-+z4NfRU>D1=ef^J; zY-dQjbOIjk>G4F5Mh~9bpM(pMS@}qkILM7o+mVsf%JBr?0kQ#CTzgZDb^Kh%8VuI6 zj<L-%m)&NrUB8U=wKw9kWs_$+N+FnDJ(OJb z;*=jiuL51!2sx%H3pm6=z@&<-DT8Ya+asL8Tx8ufHV&j#-zqA3YB=Esg`i@W>U2vY zn-h0|gZ%=bnot47{i|)w9P>~tsxQ0#&4Tm#OKe~Q_Bx0varn?`*;X}K_1d#rFiNXO zk~Fg45T9dO4kU2v+UNJ;4;}U-4PSOli{VWO^eh&}oNx9{Rt7@yslI#VatzpzSoPm8 zFSlLfq^|q{W1cQwHU^!1Hs3MYFP8i`(2d51`OVyb+xE2A&bWC9!Up9z$;hCJ*@`0S zU}@%ER=s&|BAac-zax*6-@fNU?S_eiN(Pub)nRkta)BVaz;n_ zk^dAO{iU8C09a$z;lcOtS0GnwHpk=?`UU=07oAjt8mYE78w$|fHD=pHgc_l9d#iEI z_X}LX3JUzyTjS3AfO)avFu_tLQkRVMn7In=M)6^=A~0B)Ll@ElIUQCKd4{{WZ{{pa zY6ud#USXkWWaznir`K+Ru>7BIT9#sQh;rV(%;#0Y5Eo3?QuT)YS-d}ycnG8*I07sW zOB8^9v^@2O4-BU^ybUpTF7qg?;rZ7sB) z^pT;;PJOxB67$?{7g`#QYZF*jsa#Cu!(4ZMN@2azATd}iBRpJFWhlz0C3^z4DFuME5>f;ZkPP3{1DG^_P z9dqY>8Jm;!aXJ;ue7+Ujmu9m0dKYS>U3WM~Xw}x$cJ{L>TrTh0l*hKs@>aTeb&ioGXf7fH%Vk)So{NIYO8*gvdJdK&&wh~ zzhRt|$k?LJpI#jqwd`%HDmSU32zY4pPrD!k(7h`(lGxadB+nm`c*qer@A=HDf057| z&-;br{bZ8XcgCPT+u2{eYFLO9^Y4qD(wm3eM<_$5dgpKhb}9`d0SO2Q$fJfI?`qcx z-EVvskh726p^D!g9v_@AT&1%Yyhev&KZ^w77o#VKg>xmZwld##y~+8man{!`a9K^< zb%q^&r&2rTD+>QWtzy3&$abVed+KxjzPsz6{+<&EI03gdettHqW?2YrxIUia1)fIJ z^axF}F1m52j^`X;zItvcr@!T{X(t9Izb~}WPMpBURXNyK`ahb5{8)M!3JQ(pnPr?V zE+yr148WdttEwxtdKo-!cD`A&wB^svC=3omq+6eGoBlmJ4la=b4bp*49OcVp zznEMPtiualy7Hj#TH zynOJE({Mc)uJm|3OtL>}m^`mH338p4fFSvIzTfA*nK9{5llSuY!di7uh&Txsou+rp zqqCkn#9L+%%AK&erZN3M*bNhW>!noXBYgYS_TLey3;JInQoGc9uvjzlpq|-0zW~rc z-e1c$(z{=|+HU4WX$;G%7s&;6B#Ad${% zAI663cfqHbM$Em^`e0pmTi@SH|bo3N9j5;>*P8n1Z!!o3s*ot65+9*urqp^ zxwPzWuW8%2;s6=#8*^y1zPDr9gsJB~T0aV>xxR>$yh zHqX%gE<-D>xu|AW4VBN1tE}ON!Psf$}fr@Qg0XWmF@S9#D*SqTN(@w7CmTRgo96z zoCzG$tm3aLU-xq2*#ojlE8IVhqZBWZ?QuB7c>J@84WQ5W7*QWbZeB1Hft-x@ z>uFnT=q>LNyZ+jK`rGo<#0jq2y%}MJn}e}Ns8el-y=&x4CEQ>=VOjZFY}A=R*i+h`4j`&5^oeD0vV0fF9x zh;|w)_Y`-M?2=XQ4H>hWE0&~@v~U)GJa-^}58p%(;!O`y)eyeyc{b-TqyP!^yJIy>Z1S6eO0^OshaQwW zH^)ue>$^xopw*HtXMmz%TT9?7d53Bx;gbt4<86KO>%s_offyyTNU(SxIVLLgXG@*$ zKT5$Q;rlXYIJg+7s9Wdfgs5|NSGD2&w^b`-Q~65M4inN=6wJr=Z@HoI`@93aawq{h z&(aatL?8U4o`1QDB+66nlmsOx`DSJQDKp1T${0LAtZhQ4v{SSVE#WRfbb?R11N^^5% zDj n@LBS&HNvwSqGE3YHY)A;MTgsl;WA6Hy&g-obvR(3!k;PwlDvRG@fEqg6M|n z+sb6)L6mV{f<%PBik@(9){_Yd-BVHjOsL;VngYSy(|S96CWEbWI{+!wwmNL1fRqd8 zh+_vO41vBCN{LEJvDx2aRet$IuikPh3b9?CQ&kD($x)Hf=93r!^`DKjS1!8KHEcF= zhH$}S7s5-WHB^bpfr7vqh+>ryr@xkGB~;V`#EOOdxit{la2p`!rK2M%1oCSU?YB<| zEZMD7lvEg^A+zQ?xEs`t0*~UZc4E!q0&5w8Ssk?e0-ijlntd(qS8-uS=B0BnKiFw@ z#)qWRqZFQ<9-XpZy4mThi#apZiv{>|g9GdG6;!CuXT<~fk#uxQ@I~enlK|_KByDX! z=cwpAb0#N$J!_9Gz3vv(${7dz}n0>x{p0qxvNV9m=?IYp|H z?%3j4l`SI2ZL(EIVNCD04?KapQ%?^MLE=~+;UDO5=0q^v4}Pvvkx-JlYdG&9TzcfH z@edXxABB^p`m)$b_-h1x+t2D$Y>tQGtH6+5;#Yze{@rE zhnDUA+``$cODYek{37_y&=FlvO#>{r?5}D512+VrjqDm+Z;ji}=_IOZ7$Rn_HoO(s zH-fZ2x3SD7Zrg|~ZQOeVuM)Ja{pk}8_~P2WKweq=3KnL=GF}9FbdR8hc((K9RCP$> zRB%l)M5%VcQsKvKAs``Md{wDK5I{g?kjW_vz%h;E)Lp2PGfJmZG?#O=!)E>Z!eJo8 zvpHR zG6WmyTz==x10oc^Nu!sibF_EDs1DIYB6Z3fw`YZav)3370a$T^R=2MP-H&8M z6FX?mP``@Z;=4$#zOz;hKBO?#t#Biy>TPLniC2!z1+z-!whBG*`CCYDX=?>6*byyx z_8+7Y1jK+`QgY=xEG*6wq~NUkiGF^K(Nj2&?_w1tk?dyzV*%T;C{!_5*|t&my^c>g z&6|X%lmMOhfG79v+hs#!C`V7XsJ#I~7fcJ7f{%<5H5+$pfnGiZZ^kU#>_3#FoK)V! z2fDvd*PuumNQVzG9non3i~2ipa7)1`g)l1-7&Lyna1rws6Z1k9z2=b-4XhUUBi1KI z*Jbb?YX4TPKEji4L!Q%Z+#uzVQ*CrC1E!v;DuxRNT>T*Wm7g9&51$~?NH*KaJLln7 zCU;_l$!ULsp4+!Hgj|LN*R)IF5?w?EB?V)UHUEKB{q)wX%Ll>lMKKn`0qlh}woYm2 z&}l|PckwuL7b#W07@M`x-b9KX)wC>^pvZKo(g6(4q9Mp-t3Lhk(3dkFe^oyVuMFUvCC! z+Z*mCy1zlTc7JJyFFQ}eX~tX$aQG+K|9h;_JHRFVO|{p`H&`r*&C0fz7rM_h{vxRIPbvQQx}fhLTVA{@zPWh+HT zLMB#cst7hj|gwj(@ zA+*t#hTjxGVoub1llrLZr$&)DKMP~KOoy1j#Qms@{omXFd-sBi@{`h{>N%;+V)FZF zPmp{h5UQ4qz@hDca2-W!%wn62hYhTYhR|<+iR$;V`o$7srta)N0Q~Q>GIF|3_>0YMLV!{Ck zS*Dk13$Q6wBTl7=zCfeXK-*i(!vO?s{?8zv2i3M$+Fnmpvsa;UWguBg!r~c+kD<1D z*yB0a3D+uAWuR53Y1YG>ctu~OJJg~^{@VQS-fRGc|GGxe%~!8DwU_}jiXDIOSZi`) zIBWGS7Td^Hn;F9a%)B-h|k(6t{vejEwFI#^7?@riqL4NEN?S3%Uv*T-c4!w~^ z&9ITH!#fyL1Rvm1bv{z64$7gFiK+Cp!vXfLhx`8=TKVnHkN$U8AXWkgp^Q`|t57`v zeC&3do4sZ!w}%T|ei`G~%MwAE=15*g(-#5Uz9zMO<*5(mvGv5 zHd>M86NxVat7%{^$@n|w4-uhFf2KXxn`3Q1*+jnMaEccRJM~eeo!S{#Y>x(g0d( z*x5$!u5l6rwY=$^9~?11hQHyotArxc@(U}ZbZtZzlQOD;_k8K!+|2;~K(=FPfj{pK!6G3-2-~m(q3foedY=p|}bKr)P~NnK#jH%6;JPEDsJ3 zx9F4^vpfwyEdBg=Y4O5JVtfko$d&y82M?b+@jo)y!gKUqU0ubAe*{f2p*6%hvq&v9 zFpW%}77x}4Qys)y~_r(o;jnX%G5 z?9~v!HBPp$+pX{okok=(9m4|C7QIN{lahy(Q58ll=o3pa1kFrX>)x~hDH&p>fj1$| z8;)P&#*gk344YP&=3VKJXk_w>-Q{_M=9AC9={faYpmG_gw6rC<5$=Dynk{B*Y;1LP zH4q3SjtvQI((kU)J^wdDH5zzYFGj|yrW~6xoM;Mug{IJ_%NJsBDOn)xK3n?|V)O+` z&W4)O^STKl@>m)*0&XYmIBI$FPZ9cOk*30A)?H%NM<~DA{~lFS{RQ|)Q#ge*Ys~5l zj@S5DBgCHW@{h?+!hGtX{I_h??<4hFr-Y(X-Nhp~MJzynzzBa@6KZwHvv@G$ ziv7ddml2jmHC51mOXg!N(t|s*QG`yo(H7OG_ERZerWbGDh8lNNtDQY+-+l!y_y(g4 zP8kiYnqeetRtG(n;%1E5!J=c+1DfY#4{=lTF-Pmajdv0crAj=ogyBmB-orNTz7S1v zX5fc-3=?13#t+-R>T;Cm=8}`@bF9!*pj2nLNXM)3$LL<}%f2l@v9SQfAx`1`OGF=I zIQU06u)D8n<_;wO@p`##^Fky|eoLBkP8Ka?-zqUc1L^Qs42dqhcdO_0&(McE`4bhW zb_%)Qfs6BBEq!Dx1qH4!8lID&Ji=8M@VV6O6fzv(0LCs6$&ocp3RMsAf6W=FT|!jx z>b_PI1J=`Z57W0ieQXOBR|<6Xyr}+sOx z##5}q6FqxD^@|(_iD`Wq)A*QBkANGxij?LbD!gE|iuQjbQxjz6-E?D}Ng?!R{M1yP z%GS-gve2T+IIXU2dE}=>psS~rXEjjWfWW1WBCMm=Xog#uzd?=?Fr6|Is>Um;yRG?F zT4Zvnos`v>XMDNnvbYu4h5lXiC<}65~jX|?hX98o{ z2;zuQu3E)~-fP2vRaptJOqf%3;m}3+qWe!Fq+lcLT+NDFb}$*KW{z$kAxl`)k*Y{|>l69I^e@Dtx!CUpWDSh%Q|7h69*jG3oWuuhyG|a;f_J9xh zQ8^DDaZ+j5Q$dSzzfBV)UI=ym88J_x%;|l8`qRN5S>8YY(c+8X5Gmpw$6`AQ+z(OT2^@)x4@Zwa8I~MfkIaMJS5G9$VW#RTVfOXJ!uE5C;UIVTwCC8Lr zbFd77f(}BEl&vi>qPux)beYz|F(cTp`lv>G32JStT7+TEa3>!BS(*iiYY#Jlb z#Cs_#=yU6+S3zYk7Y+)`j%rUtyX*lG`jtLZs;2(x)*ihg*3vPZB|#8 z-sYySGydpe;-9i%&F9NK;sE{N39mXEbniwSwv}u4%Kr@NPwEBF%#z09*h4&~%9$Kv z3kXhHeHOaUNhTkPt=DbLAJMeD8dmFi8ia|xvk=rDN`2?jZ{Mf)lF?lM$kmNthlaJAr{VvFl&+>qbI-4JkeR>Er%Z9~6lwj`fc{8i= z=@liH`NY%g_n=uUBFQHnzy6f4MPeNieEGy3jMoj^n+j3GM;@4McN0WSauLpeqlLp! zC@aoKaHKc7YdY_{?Mx_txPl*=!rE^uVO*V&U~{Fjp-P~-YgmcUHN8K3`HyVeImspq1H&9>>cfLT#DKY@5^Tj95~knH4kqJ3PE4OX-?_es@}n; z#ly4?N#v2~oGtDJheUm2o+E4-!h_yNd4ZR}dCw5LzB!4$y0$cjU1KLfjee@kJo#KS zw+C?^Tn&ZQdbH&RyuqqJ38X*mx_4kN`Se>0YpHYc3jP#)QAwQBJy}v#K6~EFpRunI zygZ=L5jWS?3Ia8ljT_T-1!r>PC%JQc|_V!%Kd?6aJ8SngRU#Bq6B>WpPcYW+GFHPe>98h zaQPu4N7My=;phMUXVUcN?Jl(+%n4i3z&L`p!9;HR_VFIIg>_-a%}tnxMfC1;1c)B! zcF9||CWBa+ep@!QeEr~{Lv67>Ol&v@Wq$-ZT6%g1Dm@$}PuxXTog&R;zq_A=qopl5 zkjR>KXo?{hno$F2Cq3%DRGY>Qg{H@gv)@#umk%*eFmqdyXene~j&Bze&)=I0@^yN8 z*fN=(Ve+-adg9z0?{7onP^GLH@@gM=<+I-|w>`lY>tTiR)xxdtwxetD;4veN@BN8DxBD^D7YD`%k1(rip_r4)A?N-FVHf*{{Dp8=Mr*hj7U{+ zs?ot1d9%GE6wdkZQ-7me^7V3C(V$S2t4}mHcT^EzV)?e!e=v|OW=>YE#HnF^Olj{* zef$ky)8RqTc-lX+yr|@1-!f`5Z`)|@r$ExyR~-FyD)FBG$&v&Vxq^)r7c-8iXxfVD zxGxWnJ`2|?9nsGv%%~(4?HnX2xlnM&PR}`(@t;)+nmGplNXQZaD#US$H<@<0HR3et zfy}KB_&+oUVuMa~PzB)dXTg|iM;vA3P#)KgN(L?oO#((=L}I@RE>fxBH<$>AwnBD> z$dOdp0l!8Ma)w{?dYXGQ!OU+v!xic8|HsughDW+|YY&1+GMTVr+qP}nwr$MBwr!_l zYhv4;*tWl(z2EnobA9!v`&T{ds#;aGbl*!LhvBah@?n34{L$`yQ=~HL@<5g=CzfS! zGHa2zuqp|v?zx;g0t3U25Qd)%^Q%V|?pK70(TV2bGHVIL<26EoLY6BBH<0o%VuQiQ zUWmK)bMA~|~-;z2XtR__yuAL`JVXJ(bl|vJBa(v1m1B{dNA^ zL#U`top1X=T?qE~>s3+j*V+8Aa1It_$a5e4ghi9mHuFOM(o|y#Yg|TCK%%R7T+DU+ z2xvJ)W#oeM$ERCoK(5pWb;BsActdJ?ZO*xYaet=3K0eqvI`^yZ9P0$`TKx&tk?T_+ zHs*+Bal()#0ViV%Ke{ov=VJtxymW>9kOi-Kw+55wc#9 z8Pyg4ujbgnoRS>Z4C96eco~|*Sl(2a^t*eE88La& z>=*2PTht2jz@f2ddnZg&@>J^rZ}~IuPxCqH^q)U}78DTOA9>y#3uhU;T*-a|7s%8h zMF~x*`6*KjfEJ#s9sVMK$0r~_h5}uRS}#J37f|0POwtR+---Vr#8ELDPnf(iNS3;> zAtqZ+L0HNVB$JzgZJFBes1fOXm)v9m!rg>26|C16Zgy~V!df3LUo@Gb4~V6;-&&2N zEL)suJnc41?fuONrvIuAMU~SH2gk{Jgh>$v)Q%`P#94_o*$f#1dWvHrC7b z4Qt1#t(>GysB(J)G9J;>KrxQu_P1@lkq%~37ad?Duh%Sj;&G22ux=7=3rFT_I##pq0jqFxvTyW zLu$IWi&Bbx;n2^{nhKH1<_i)<{`~$gB?&~Gu#~(AaAJz7r51$BP#T*6=p$H5u} zuw)T&88QWp4U5@%8tJRqSr|}BZ8HHp`YvBkEu>u$Rk9+<#)(aFSGzVR!|zKiUL6Ue zpcr{_q7lYSPP=Bm1m-l!%80^>+03zDErwM;l(BK*?bWeoztf18vHvehq3N0%;)Q3& zXY`iLROoe3*fj#O(uI9AD^*zKBdeF6iNo31*&KcSig|92udzU2a3dlJ%da8Pi8g0K zd4|*yFo%98IDYDuC2U%^EI-~B1~RFlX$YI@6;4XZZ`1NloIJ%52Y5M{rWdOXiba)@ zW1}OXO+8K-5{=$1ud}@8jXcD}T46R)-y|$BMM45il z{1z`+NG~zic-m^wy3h_7md=}=OV?H|QONDTFDkd^{p&%t@5smqh7>wHJReD8wn%U) zc4f5~(ToKMc7#y~^(|MXidYB?=}7n+RIZNDH~7>HsnENSS0%Ax1n5IWu-z%)s#(RB z(gv1;{Sn#^x|2%nCC8ot-F^4?eC)eBX-uG8m(4X+tCNqzo2>$fI0oH8uN%J*&#8r=*;@Yvi2?C8YFNQp|Cpx zLI{f;iW)9-KGrJoA<3k_i^`xccHg==j1(jWFA{98l#Z1}ty|k<7Al(fwpi8mT*}m# z-W7WejU7f>b0sY6(tfmH@4&r(nu_q=))tUB_BnP6icDs>Z zFvAU_qAW0tac=w)OYEzc#Y@~@`ZRJiqp{^&#AP6-o0GaUO#Q9q5f$glI zV9_LNBi=gWKO`TtfJbG~?MrlWZ%xcilmCwp1e4}3Aqd1G!6=Uduy)Z**RdR!!DT~9 zRd%_ZYwe4bnC?w=c5|5#d*(lmLHuz#fa{umCuKhrLOvTWeJqSyZ?3ivq-E7DF@o3} zv3yZ^x6M8}wt!93-^33*c&I4ujfij|lFlGv>pZH@IK01f+*yUJzG?^NPM8$ur@18F zdq0y+WR5XjDH~ORa@533SmBB%+**p(%|E=UeWRvoUkXZfo|uOiDaK%~Jc%jbeD3Vi zdj9aej1Lw`vVME9ls;I~p3Ad1t&-SZW90N%O1DXp5-CbgkkWCAa{13I@k$fFFQk^x zTgc=z)QyE9F)2!|wJ&}8Y}8<4=p)q-w(;Z>m~J`*L-Kf1g5|Mf;P!(;}Zo?yhlP?HI!k*Pxl4+Uj0&Ua1yXeGFyT*_*vm?Ho(kJPQhtAmOc z@hM2WMD(GO5naXK43x_q(Nk{z+ z(V0ZpImg0Lpb~4ZLaefE92go80e7SAUJ&m%Gn|l;QH5?Nhh}M2Lp%^a>TI=pbEL_Y zf2lrMw;&}&ukw~7ID7q>kujU*j+M-F#BV3tr=e?RtwYEh1M~#Jnz0qJ-QGN?j=LB~z}b5KuDKX<+6cE8p|gyrmFw zE|65kwj2;S6b*|lYJZ$e3%6bYV^Q$hZX>HbX{2B-@&5WW`?hHDpgA)=(j-vcFumEw za~DXnAEsv7`-w-qsMrZCh2xJSTtBIx??VY<4E0p^1>- zyT3?%uzQa&x#k(bBq!qA6qCJde<%_A)Imwt$tdpi6Q3x2kWwB*59vkKR^MMaP=>0e zYbmoKJYF`?32jvCt?O+x2aHH=2C;1q>b@0P38DVI(?7ZXX(d(kS1be*ePZ=6r3(In zxwL!cRE^M>T9o^Vbu88zD>#k*D_hT2lC$A&C=n?tBvKLhQyOsK*#V<(@}OSsK%jn$ zo+FQ-D!$e{@EgHDK^GDUDrp&+s7xm7m3sWyrO9B8?{XoxDe&-fGO7_wfhE{=CB=B| zU?k5bgGJ(D2|amWU0QmyM+v)8c8sGc25!$I=oGJRucOb01FP7lkr#G)6sY7 zI9?(jQ(1`_V)<4`Nbr%}GXnH^|3YA)B$2+OX&WQD@Zq}kP}?X7`EWK{DfzO*@;MG7 zdo4>}UZ;f5(efcTPd!r#obfJ_@g?z#;_8_E`cS*@J8iRJhOkxS&u=y$$kpf6zz)B_ zxaKFxWEGutesEK~kKu3>41W`;hR&%hLtLDNepMmn?*kx!dPZR|;mqfFQ`jTEVKG+* zIO`=ZrC(Fa)V@yADJUd#n4~08nVB<$(3X^wR+=4?)i=^ZAxDbwJAQOPw(Hr&(nV!4 z&-`i($j7DYM5C#AStofs-c)=r{QnRP2msVeH-TcXp<%LzDN`&YBw8j27Desub$%EX zfsHo!jXWNJ5dYp+dmVOppK`fKL@unehcpN-are^)#oIUx{d`mI4CEbIkB605YD_p& zf;_Xu&M{ORDY_a)0yPlFgluV5apVkj*D;9jmeE=$C7DlGyV~_Fh^ZxsjJxAQAUXla zaP!M}sksHtHM-!m<#};HH%eQyM4&l6`T9xNDMl6#GvFmkA{#Yvy)vP z@XSo7!mL%M<+a9*tHfJyDT=YIbQ%k$QexeV*t3G;DgQ5$OjT z{hvTcqHj-)*fn}OaJxq*l4=)L)NI+#RwTM?(^^TvuOa z@xdp}DJBV0d#L+3jk<~!;-4YytTtJ%axnBP#@pQ`F3H{0oXjLOr9l+UtLa$EYzkwS zerZ)qm->e$A;G;_#AkQ0VK)=Bv_6j-Rnyj(^Z(ju4O6+ZKo-cDUZ{ z^+FjYOczlGqwnSp;;OTbOZgeK%S={7#A#n$GHbW4(WrLERMgrZI8S?C=>p{b`mNFl z{QeM7U@WHHL(vd$IBS<^Ur&Y-A_#~Pp}&1DRH8@P-qpVjkrN6zf|4Ua|LUGWp62x{ zW(&E8ZjeIn2gf6w4JM|AB?_u|9*5}68xp&61bCiVtCQ1J>La`b?Jp{BtSd?{qU z!{1QD_DWz7{Ev5@c0}IQTunJEQIQ+Ja<{f}2iWa%WnWL(^CLfl5xb{~y4Y&o%#&XD zG<)5=-xS4)NEN>J_F7E_NfH>EO-srv`qLj|6>y0Rs(nx8OkMz%Os@XOc$R}Ko2Q`3 zpv`lA*4HQH6bhA{#6jN|*2UXE$j{0c7OL0c@#z*A!J<2%*J#Uwj>cukrNsWoRiQ_a zKZYr;Qb6S&<(HK`>_0W%-TDWP{~Yiaj=!J!6s<|uNA91?z)NnM&9BkjtN4X@Nd=uUb!O$w#zYv{g#b;`X#!ojg%_zvvq6 zTJd@>T8{FjX+yMT!qptqShHYjS;%M)=THN8ey))MwT}dc`M!^kRr`6?v@e{zYJ<9E zQ2F17M5hMP+nAou?&{*D1)8FIY;dAF*lq-ZWecU5zD0k~q1==yILN+pJz4!bdbja} z3JDJzG@OAxqseM7Mq6aOKT4Y!64jiSL3MM-aP@f!SeFVP(chBSsoQVgz8$W&Jq*W_ zR{BWz2$AT;N2?SUgOPGGuS*5$K*h=fW>4;lpi^U{it?jk2GLt!rab`s`O}BoTqULK z5j8p&kG8g_r$(~0dB#x>m!>@DJUnk|$5(T`qP2TLfzuL>KPuh!rg8^2JS`W3*qvS(HYGdjat#;RW}Apxnhx#7PWf&CzrP?t4ql86 z&A~6HUdL9R?AAX5r=1N6bA`fUzT^(sbv;8i+^<=Kn;XqIO0Jk{OLTc{DX8e2jd)=Q z@0V()9?;0>vN1+EbMZ@C>xG5<@i8;LL_;JLp04ET$c+<#>q)L~B_`N?u#ihhsoORP zzGH>$NBTzK;e0jTCKt-YZ1UbOZ{2f}*Yd50mZqEbEki$$yJKIbQ|q^ycll$IOl7&W zNQ_7_AyKkwn(_W34}QuWmRlqXS*|W}Yh3V1bSA|iT}IlCDxjc{oUHEUR`OEmKs{sY zFRq*#q7+C)SqW5`FHvIVxWgvaZ!rBK4NcFzQM*Ve`H|gm^D{b6>f`anM!dE(J?7J| zfDL510`eSWasxqdS>cau{VlobpJ!Aduot_xLQ_aew_pIJOEVP?3Hi_bUuL?&zQyr< zF_)}uyYMw(S+?3Fzp!GteDO)5<9z9y)HHQ5@p<90mF4lKEXL^O*nR94ocV6KS(U%Y zUVTNJ-17Qu{;s`9u&phb&`PB|X}36jB>IcwB*MTb zah>Xd4|G7NDOu@BiGoI%U2>$MoZ}>fhEfW-Vp0*) zTXxMvloEx%{Q_Frff+K}G9;(0bFX^3V*4w$EHEF8$q9RQWC>M=otx2#uHrJ^AN7yq zRe$n>$Y`oUY|O`7zqcv6MV3=gb2aIAu0%J`;C|kM zF~v=3Z}CH1?NlzmZ@C|I!KQCLI!~2!MbuF5N4xSau#~bT62azjZ|?(YI=Qtyr0F** zOy~O*7HD*=pOQ&eOH9gHuDO0Og{x5<4{RVWeIr)=Mk|0N94LBkyiQPfJ+VL=z9~BFHQzvu(O{wQt2Q^uocWz8ZpDwRiq#U_EDR(WMCphqZ=Xfr=@_jfs!tm=trhRfq*NB_3qu(~h2ZJT z50vbLKS&3RK>egtfQoz~_vFRYf`e3%QR4+=JCNl5dYjPwg{4xfvq=v5@n&IibLr`# z17jejl?UyCLFQ;>(T?gQk@rBWOp#nq z#}oDDxph~CKR%~EvFHR{qG&^_{G5g<5eqDFC_tvVf+IXAf>Q}eRc0-wjpl%?A6LR2 zj~EdzRp};x|7%4b{#>@gq0{>f>igq;55pN4y{l%obVf5CTC+mSPW~Owk^97vEdZ5K zyj%x8a*o&lYB-SlM{$byqbXCXj}pOoX{SN#n-fdTQ{b|ZK29#D3l{ovuHbK`!TVPl z@7y9&Q?MU&W-?N?o5neptu}jIxS2FO7OzLi?YYvcV3czyF2e;+x;RUX`Zo)2q}Gki zyAal`TGLI6z5SDGcx!F_4L92B5(G5m!YIoQLyfM!SozlUY^v12(XgKi!dZVlvA z)5}^@T?giF_ql_aE;b&9J~q-i>M~~9wV#yN>&B8$sN+~|X>$W1Z^|Luc;sBz$`;KG zDk=)J){r*y3WbBy=5JHj>(m+RbRU#rA%?dr-KDvsaXI9jg_G2K`x`cgmaG+S#*)zT zx9ttcQ>_)od=h~cQ%v%PP4#URRqmsz%JbyqEglP9=dZSayDngA1civ;jw${^~!IvX%?(e>Z z4V^9*BNy}4?q}bt+6ZHP2S>OT+_HsFj@CdM_6b>6_44W5bzSP%Nk4bFSu439giK9H zg<(Kc)*u5wW&QTM{vlrkJp6J>^~+#kL6#!bGxD!*VQOn|AAg`x%Hif0eXBKp=NF0x2_=(+9xUWraV zBMMz+UH8^5YMPjmFCOK#vrr^B z`xAW}YP*%Q-frij_ZEFKu5EX@#i zmu&#hOs-K{E1r~_znw*q`o*LpIM+hRolR?sT|87+N2I0ASn>r8cK7 zbaKa;&T+gLkX*!JeQiBjWT}J`-yj=*b{kZ1+#*}=5=V@j;r!8Y6NCdK_67Sf(D8P) zAAfE{UW&dw#&t6|4Q+}Ut}01MNf|ngW;j>h7d>VHhOKLkGsF0DkHSgQxUkTt-s~z1 zh%}mXEFiKyxsD7DCC9mp`j1x}qd5gW;=l z=odqA*FQAN-i74X&hSUj)2p3=N{9G5eiqjS04L+r$0gek3a$0pJ%0+%AQQ&o8WbJW zPqV>ha5x95Hk4TTHV*ZaNcPNB=@s(~cIw(*S%HNM1fqaLl==Zmm zHq{bDmVhQ`ASIFbh*^nCx&yhIi2vh+l|fH1#8%rx^zIG}@v#KN@w{qcA@b&e&vW08^ zd|vt&>XbUWa8aI@k>~>n@5TGt`wZXc&Fa*G;ORBxlMaEBy|>p6%kI1glG0Y)Rfg5v zaZN`D2OqWi)|4kL2xXL4auI67q$Fv?KKha764j#H?q3)CHe*QG+TZsp%BvEQbHXw& zI&L;dHzhbIN86hlqFoRL=XmyRcW9N|-5VCB1=AN<>e@?FyyPa{sDuc{tW}!oH8C1G)3xj_pf(E=n$F{%vhron3O}H7-j$MO#bvtLOE-Q{gVOtsh~@Jub*8e5jn(+=iP?)Z5D-Q( z#ob0utvV3?vs(o{x?8hoj(6ahv*Lzhc{IdUaz~Z&Bf@=VO7pCoCh;kJ)qFzWlHIhd z8gSk@(YNCs7H1KfIBuSfHnG}eE2V4WvjcC&3qb^>5UNp~D|dYv1uo5Fd$e^X$Gx_+ zYi{jIUTl_4iD&Ap7r9WNJQS)>nOXlT(x#dsRnn}v z(1pnM&Q#r?%9{J&lITEm$5Wtf!85#JucWOFp$*Q9#mHPgXMuNP_JLGdgKz{DVAj{I z7;NIfd%uE4d%HhtSo2|;wA>{rgWHl?nX+{> zwi>(VR**NKM@Ce512|u|PxbNyO6RO(>3aLx*zgo&kLH8Qu~%oimOYvUER@uzV?n6O zOyYihLZY1!9`!8`(uaKW8<)N8QYWRXu~iPw0aTj1`o$Qa#20Z4+VffET9-fTM<1MW zcswYm7PZImsiQtiWJ)iS3_lh#L(e=LS=-L~9?s@@>^MZ7sTy8&S+Tek_wCriZW<>& zZ@OAGN)6I(53>lk8$R6MgEy^_MATDKUwqV`kDht^Q&ZPj6#2zO=w~wB_}<@p7upr` zZjw$dz|N+Ev^^vhCwz?dcsE{+Zh+jLK5tuHRk9zYF=Z!DKjW!;h?<(G8lGpfh%czM z8s3arnkTaJYqzHsE;ipUMHiv+#e0n&zDLi+qM1G3rp#DGGdGyZr%Kc5%t=097d~Z6 zr`K^`)cdo^pq{GVZ;=8W$HC7S@=c-t#$E8IjE!|$x}V(=1f0tXCWT@4u!Ljmv?VC4 zYI9(&N{(4aIE{by`Mgz4ck+7lBStKJ&S}_~-vVn{jD3MtYcQmyQ35Uw7vOl{9if>1 zEtvcx5sky`a<;O!B9}0YN9}0LQddUd-~Bs6WZzCM8BDl985jXkjLaL;VnpGJf!`rN z*ZQq6k2mR7MGIQ;Hrhbufj|wv1hI;PXq>QUIUg59O?JDB-Sd*a*>(HpAq$W|F|!jG zjpH;3U?RCoxs~e*?q>#4bY|a7UK~7XoV%Sc3Z@T2P_i}-|A7+R@4E7)Z?Jwp=$q~{ zzy~nHY_rk&zAfhh(^*$T1%VwwnS&DO@hEL!ZAZ%otZ#RP-T}amKg*arm+fqAUL#97 zVICV>ZzO-7DHBL^nU9Y<7`v!RcYjC2Y(7rk^giM@!McQs{Hhz>M34Hh7;)Tuv7Wg% zr_<~>ossFIDwOKFHZywmDR%zgyenbid7D&zV5!bzD~q}BAn2ztEcX27CM)nb7##QQ zk1wHj_hs?N%&=p0hsny+l98%YRgbl<^yYoyUS1cM&kndiG;GIPTE~Si;qB4{;Kl`b zIc9<5F#74i|9JoHyra|YdFG5bAmO9dYJzsuE4*|2p7Sm1bTLUsvi}$Oh4f83V@-XB ziQXuYFt^ni+ie+kTi2>(zNw4Pb(AVxUB$1jmVR;cD(LkXxuhv<$uqPknH{E;w?m!s z(czeAUyE2t8e1w7uiE^h!rHS4{ z$^?1h{ETrs8zKtUIIg@uUO1a;G)9NW`jj~Mjg%lN;$!93-RbJF>7v+jq+M{{D9Pi&4rl~|n04V+ih=mYAXjj(7&_)TH)#KHnnZKF( zu(Ujj(cy$1FPQodXN$jzY?fN?$ck+JJXc1`Uq1AKmx)yc0hQ8W77LD zjZ_2O-B)XL=I#wCdiJ@|DTPSbY`l^*JpI%K(fjksR7?X8Iyx(!!nLyl^_ifYG z0{3Mrdb@2;+O=8s^+`$IjvMWGlXeHsgP6ed=jimeSMIm+#0ZSDGXBL?6R&%P0*_2(j6j>9jO(#nyg+&XWUfjmb#s0I@Yy&IQ9Xc&qal)rNpSNP_T53b8Y?lkGi z8ucfk;5Ikt*GGAO&oqyo!SSw9t|C&jO{;YbWcU=s7 zt`K=dPJpYvoxP`5iNUcTQHF8l-=&V_ez{fO=`bF0%P%#d0$C#n`9K>iUG0dr`VAXuEFi@bbDX?#J9u7N1-^ZVA_E!@iPV zXI!{_>8rXvqkXaK+@4x1XN#*dQ7J1lVJ)|fqP9ny8|9N@?&Ck?8-@tVzQitKXc+i~mSjCmZRwCSAm&189t z5{egf`~sW37L_QwWGKC(&IHepj+Z4a^5gi>n?$_D=(19FeVtU~4*73FivUKu)yaHt z;jCw)uLbc$Ih3qApNzH;W1QiFiAn1VPh}1RqWrlxpgutkSg({nN8cZ$ z;wXpuF>n&!FQUDnWIvh9EMXgPW_ZL^PLX@ZbzRq;AN|1oa2buF*w1Hxd^2HjZxmXc zcHa*lVfB@IbH`p-))LAI7rUs!JMHtP_+(Tb-jZL>wzLA* z!=?1ubCf?t{(H=yf_bQtMOrho`1`gQOsqcs@&+mqA6~uD*4<4gC@6_?C1nk__MAg0 z_ri4>(xQU>N(~L_+)gve@Mv64k6H<3@MLFY|m^cc__^3(-0y?#*urLQs!YB#E_!MN@vhL4v`lixPZAKu9!Jb*SqL!GRT%~u+iH(BzZB23| zQ5$x%#r4)K)W3jb*o8Ke_`KoF%ksEqcKbKClQ(2}SQZ@1GJ%x)@US0Jzf16v?yxy} z8f;oeL=UKGcypMA{=B zm+Hw6NO#Yy)kO3rlo1lBjoZnCL4ASx^68lJ?fH(=>#vM7Q2EdW6|AI09m-2F87s=v7_DT4Jk<&`y<1e5s zm+Ll$RFuu=5z*$A4GRsCnOzOJA+K@^{op@W6Gy$iANVu>A zcSzOfkwJX{t2K$ITSVzmq$0P1CS(+r2A8jAXl4eF&X|SwO1%ojkpG{h#{xC=4nTf^ zJP%s|v|5}2TjoID!4AE=y#+ibv1O;dWoOrQZUKhQgQ&9T{`h-|9GffiAf>$j`AIQSvKm}^`kusZQ5q%`%BCsQV{ee44KE9;}h8xn0?$sNuI^kartpWZ$c!V zvJQaS+Tj>mJpSc#FW?QCN}B_e@;F7lN<07=YHPnO8bFN5j=e#!470i(M}-0?4Tab` z*HcY8oF!GuUN6M`tJpI6v*P1L6m7F+h0Vt+J9a&&U+8Zg~QqSA9|c>S?NJM|9s;vrmz0Fgdo!!>P^9o~Av+fi4OU za>8!lQ*myY`ur8Svr5X6a$Y0l)@w_w*Ml(>omH&8>ZLUT1@m*w^aa{o@F2Yd6(ie>0o`=7-g*&$sH0rDe^fI`Pp|n=R68cDf49#~T{Hsch|7R&UUK^|t5)K0Vs^#2Wvm z3mpr*Jt62CvQvEnDZXL#ToRUi%U;01pc#?rqEry43>%qXY*})r`#Z%ig2SX|q)2_b>b#<3(XOCrR`%H3QxG%Ltuf30Vx%e-O zgpY13(w3B!U8{>xf#qiIkrn!>m(Q0OmwkdCM;lWo#PPv;gI`x`g~gn`E8AWpd@7#A zFc@C0BkEKSM_FjqVErL^^O3JnwT{rU-V z^oGqDcQ#bZC`fcAz7rvpX3({-Rr}qRA0y0Q7b}sT^Qff(_-Vnz!8Q4*D3r0;-=j3F zkL5vpAR#0gl2~kZctU9^l^RgybIDsvAA7rzz;Q@i_nl6Ho9$_sKGvJfmpxv>m!bs{ zra>k`p}4AhDfG93Dp2lKYdG$Htg9{4Dbd=ciWd}pes}~t9nD)K`OtWr5QKYWTMgd3 zN5kT9opPbO=1-@@#UbM2UN~{Er!k{_H>q1T3kd%DDTgMvW?{dw^6;PP-k)@?c5BoO zUCB824MqpLNv9n2=-G@wX*sK3r5B*~7S&`p_OcnT2h=-UG0%T)Q5fL5>we)H)%`t^i&Pzt0#{|vkL9N|s_9N$FaokA0Hk!x*Zq}tUgfGEOMfa|grc>4-pbe88r%WhZh`0PZJ-p} z%LS?nG_E;=^=Rsg1km#4T%45^`NNjp;Cbp;Hd*>(D}_+5@iQ$iQvt{KrkZj8Ivp2Z zc%&W7%$IA`cbf-?97d?NB@}Ydy6OiF z+vxNmQzfFHB}W-}D0KpnsYttQg5Z)^$VzQx1R`FBDI-3=x5aYaMZPu=U?aU>TwhD(re{;bbl^fwIgcl^A8oWD+_;e9ein9G~nGq_H}JE$8X zQ)_3Uq_Ld6Jrqf6p4FVxefa3^W?uwrO~U`mdRIAHWx5piJRCxwY_dPE zY^-FUW{{mr8e`F>`{&5~l_`Ta_&!&2$a0(9c%D?9E8iLb$C#JNcYBEXlYX1@%D$Dcsw11e!yJa07zOkViuw8 zsm!IMp+|!VVg4O|UipC!%6ReEfaP;@j9}3BHdwfU30XR!9mXfWnA5#Kk?NYG-%Lv+$#JN)SNe?`#2~Jw`|OQ zH8|PoeJN-6a@!l5X3jqKx;WWyU?xlPuYh0!L0rc3(tI=(KU=@qJljit{YBL3r zBgUbe`C5({XSY5yS?k4W&-X>#r~FsmPZPLOWsX5^@FUvO1{UKCd&+UTDDkloPdvOo zF8S3sM_{%s`l>`zPT!Rh6h`3{l|xg4j*jE>zh@M&?ky2M{~?j0@w+0k_V;6x^;HfCC+az)mg!ub8iy4M-;IQLw^0-?Cjl z3U)`AqT=Rh9Yj;gUfV^hI%{Lp4eim5{a0SxgSMM-dw=R$-_=<6{t*6M!T&kdpAMiP zxFZ<2zmC(xx&sOi5~&!@6|o}e&~b+jWs6Ekm5Ed`=*0@}oO%s34osQc$K0OvI z`^A6Oe!5;>{2JPp@!U6`gB0v#uCDs{ci@1@$HQdTKgh6uR+LSa?$9Drc-c=}Dp+Bz zSbyYHNUED4w-?9b7G}11^)3_ei=;3~{dmrnnTqCaM?#xS&3V=VCDNpHKOqZ|$?vbl z4Wh%YUnL)3t!Xv!ytaOO^+POxZMmpAXSH;}x_Q2Al#RyG;T&+3-85amS@#meTP%NQ z#MXY9(B*#Mx;j4||L7zU+WjrmRGo;vqk~J?InNit^Plbhc>|_s6gEu=pT?0cA!uq3 zwMJT~&!jaGB8)(^ayF-?|N4cx42Lk$of!6Lcq>!KVsH#h;%lm;|lh@O>4@`i2*F><2GTVVz9?9 z$_lAoPbtf$7hdJK-cyk0LS9NtPD3Mp`_uT_&i|n014w@78}7f& zKy?b2ROgF&+||6G!4Yy4#9B&hd?LdjLHWh!_lrTu6OaF_VfU9TGQUP~qJAs(J(Y)M zPmK^4AE*pFG2kwQLKyeIx8^4cs0$i}=mM&u8wSaU;6^1}098vSxn|#ttrYW~a%Y7q zA3=d$>|X}~0$~(=%cG7=LsH6fK6Neib0kQvNBH;zKCwX<&bWJN!`}WsEUW~(JutM$ zGo-0fhaCT1CO(9b^TKsmzPlfEVuVJ&0;RB=zzHIMfu(+*okkIk8M$yruM>)Xj9j~> z&|09x!%FVS^T4IkvXA81QN*Rtb&_XPyOuGH{Q(@T-O2N4Ceh?VOPOJXb< z7tJ2oPlxIv{`YgApyi$-v@bsSuTWFg!V?+GeI3D0Ptlr{kO(b+OUldfWxy;Ae!y&M zR|qouMuav#*_y%61eZ*T5*^})Fs*d_v?Ek>q7L#G{&yUIp`jt*C{u5Ds=&)05=U=_ z_Mi^zXVNU0Wx^pZ&A>O}f>a7_r$GoWrJ+`cy*ie0wF^>8F$z<|_Y4&g=rfp}Yjdvq zl62n-dTRc6H0w|Gmf$EPxgbb7@-wJr%B$F@8An_lrncU8L#xW8Ts`Fw&??dz*xU6Q zconnRW^C;e<4mY2J^Tovm=6*@x0|}Efc@X;K7F#FKh*xTbDZ8k2^&r>j&+Mv)U4%x zW)P-{U1K_-5%`^%zQ9;Uj0U0PQneZIVzv>1N=jj-=Jn``?5uE z>MbQBHu>lxCH29Z07o%D%b*>~<&K4@@^rtR*gO#wvsjC}G|Byqll{;5lOdA&pxg(d zd5LXTkigglP#9A6{4}r^~s|`Z8VW7+s$PG*3g{lepBb;{{ zaA7w-hN>Y9ZO`Euu)9|T^(XY1M0~#nRQ&V?0C?0mTrrr)|2yN~-*1uPDin*eDj2s& z+ZQ^0k6b!GbQmVv@f8(50HtPRl1efs>_m1|o5J%C)O)ca=E0-2N7j<-OnPOtC1gWK zm0#PG&_(8V=Z(p}Vii7b6V2uD=f|#-GUXulP1IcaecwU^t?|!qS9goLlU`@&`n_BA zZLS!w+t{;9gGE}7dH0lJj9 zU(9yi0D;nXLfQ0b7a!f)1PO8V0RGyTVr#jF;)MO0WU7VQ)ukVnZCZl zf(WV+7cM@dwfhsze&4S5Vdj{tb!D|N{Zj)hqFB_`p>iSqp_>TFEv4rAjLPf26q(_~ z3fTPcZZMLQ$SGQ=l2Er;r9YqoH&6Srl+!&@0QN6?j32$tT611r9xpdMsQ6_1FT2p+ z0k1DbapT@+jYIw+4KH%Vtib)&Lq;;4^`KzMm}GJ+r!Crvr}-h(|a%i-CyRv zL|z?G5 zRanY^sD9#@96!SV;Xpq?DsL#1EeXN`xI^uOw zUKOm2_ZCB~ZupjZF2|q0RoXmp5#LBy(B&Ea=iL6J&GXuOvTL%z zou+Wcl2>(9j^fP4@f%gD4v7f#DA^_uDAMor5nNfXPPoNM+!l4vajO6dQ5Aj>`G!vs z&=t#Bg>NvZgiOYm%UNK9TyY~%?e`T_9Q*SoDlY#7jusbS$us{b5I0l*{9Fi7xP7MD#jV7A_)Z$sCpMZ*d?4a+LJ~sZwrqV_7*a*S8yflUTu zW73gSO5JYC`-pwJ$i^NynOmOmpXx49QK;|&wqT~2#@1d5UUaU5SM#%4{>)riDlx8`4Doh(TdWU_)tEx6hoh3)p!s$^POx5OA>n7Y@rBE8Yths!KZR?U?P5qf0%6cFlS$xv%jD6%Q?x(<>QEB%Do zLe;3C7rM)k+S7&ovj=)ifwbJnLN9P?u6=MI>%$Jmet$`(&0h{1XB)-@e5X`@m-1y& zva@^{_-Jvq@vF&|Cp)+ovG-_XP(^`AXGRUj?L07dyyn|~ILHoubnj6H>Ok9g;l?T~ zr<$h_C4fKw3~G`FGzzqtD%cIuQOt&5WlH^eBq|nId5-zOP?eZnjKIyONNywuQx#GS z8UR#g+^KiRA@Rx;r3?Z*fD~#z4TWhOe@`77DVrk`DNXE>)hqR1>2hb;CQjAb3uoR6 z+kL0YUSl&EzCJ-IC+=Tx+x5__m8r*JFb~VfGC|gw^19f$^2hq8NDaecnwXk^wO5r> zOl-ac8;vB)l_-xog^$6Yuh#g#z(^473`=X8Xm&_80#K9V0w7diKtSkGZkG`mDr=5L zNxGI3-IRyHY+{$TVI%V?rnZ{-?bN1%?MYtQu=(mt4rMKQi3t#5J5StX+9y*^#bols zSv%KlozQ9aXvV;wo2AqKO2$)Bz5MrmT99)oPuEaXPaJkM^Rkl+nc? zOjdGqV&XQIx$u-|okA&x%C#ziyG4IADHT{&JM+4EkA*~GUR%&;vq&YQkE?^Ze^PfUK?1CfAzj%|c*wo>Mg(}+%___(6ArXn3Q6*yz4=#zM0eLEY zGxM;cx_$};%Vw^<9K7Du0*&G|et^ILv#*2>c?W!dA3Qr9I}+dY2JD&_>bG8GAJ}K# zmVv?;B+sV6iNudlS9gQgKpe%QI}gL2+n&1eh0~{#p&#TMr6Vfsyv74FnOSWs-*Gt2 z*W}7YOHF@Nq&$z^VgqpQLM%N$3W|eWByP-}bo;d!R z1%c^=eRu5G%UJMi`*FE-kc923J(;D!xdUuq{5G0$d(|eO!!G>thivm|>TD8KC0e5U zHdrKFea0s1VJp*qj}xbB2R*l!6Bf_Lfm`1xv$jj3l@OCqQtA;O=gN4({&mnh@OGCCFfdyA#~q-GaM2d_!{b z-gAE5{4)da`TUqB z-pnPy?{D={|F%-((UKX2E86zPx;@oZ zzipye5AJnOm1%3EcGA~DZf>6lz&C`&=R;n6PvdNtgfZKLXRZ2qU;CGIq!4>7LpwKg z9`lGL8h0~%{=(M?<+4!fK|#gbH3IN>lazh(7ZQe%A}J8EVVr@lU=IVHXO^u{V)SHa zK`L3pFqLr_3Oe7|QsckQtPV1}0} zg6{o5sk7S(E!vR#1$wPhTzdM^{A{boR$7FYs#46s?;2N#nN6-(Q+0fTW?Z@1a)omw zG`)7CYGLxB=V?TA8|lVtI!4C3oKuKKctd9JV%)JauA#;IAzYl@a5y>S{g`yXywhYp zk$|fzDZNR?PCtdI~+4;qo5anHSXaKJU!WR@6Iv zyRu#+(t7W_H!U?@vK8Js!JWISIyJGeGmj_|c3+oRZj-$c65zyacNCjBTJmRL`A`Y9 za__hn;Z{jOn{A}9)hX}qsei*Tx46dLh>*jRS*J+#pL>AEzxCEg`c8#WjxCuaW*=`?YKWg909u2 z-bcK=aV76)Sf3xKINg~s#_<MoFb>J@XToASt%ii*2fFFDMSJtoPI)gKG0s5yX?krZG9`oawnX?l_8ZV^OIwIQoq8ncZJTpu|T>Z8CRGW?EZU=%x{D*Dm(&t95;Jq+Ph`@XF+0 zXHqGzUv0s|Qe>(=oQd%%IP#=L+VlJ!kOT{&KwWU0ydd*utqGjaU%SZLf&t-cop5`oAg#EVE7z(~l)U}3evmrz$UeiD*;j|9s1um2Ab(} zn>14suaNb>hg(|8da7fzWkTlDt39Y(Ho06qyfLJl1VL}Q#su=GCDleUauh^^l_`ZLn3 z+@R&dGOJds14j7(2V%;}h0d7a-t2f>eHo!a|UvnRmWxBiqH5v9aa1?fO@cYL)XEy4Q<^eNqKx_ zUPBetcmFj1r6p~(l)`aV_Za?>(Bl~qGs(Sga@iVqGPuWhyj2Q<1vCXS%e*4k{Y{$p zlAEl=&7O1=Gj!~?!1m|xXwCfy#g#;bh3OR%ib>Tn=NOZIJJ)LqQ@5go+#aAcFPWKg z$u9kRkhf+AE$MQFNz6>Ll~?u#DM31}>g_T1p&4q`OAb-US#avwCX@HdqoH6QX!#29 z>l^~YAW=C0kI_ssJYNP^oQ;=;20FG_4~>DDmz8?WD9{!kAx<^6Gziq*Cz7@xU(FbA z*FzL$rDC_xJAySJp3lD=d*a+NzC0UgjO$7IZso3!GR@!2qI38ZQXAhSfkxg|Mos`? zj1i5Lv^-b{zbJBd=)InOq`u$QqTtmujq1kUs^#m{a*tLk)u%$?55BNTq#Xg`B`lq> z)9ym7=S4I?F#3aocZRNQ*0a))m-w$8ry2MpJUgH7EbazRniX0RrF_VDlf+tR@ z0!=T$&;pE$0Xu3gy$5D8ZkJ9^&Bieec>D8p(8`h$C2v%BU?vj3Nfu3|` zCN?q=JlC2jYxLaYLzr&&-#`;H<3#ND1F2~!aBZS_$!N#TY!IBucG(H{^3=W~^KZ)2 zZzdxAq5HD*?_E2%o+g<6aiSijRKhn`P4n;VX>8wY5=W}a>@nsmO4pj~f0FLVYzf3h z4mnYvb=|bf`-wknFW9C2`;?Mqa}7F6}v%B zyVYwb+#~l3?b646KOCG#!inAlKV`0m-i~L_O@GOhTMv0O>W~GZ?H$$-V@@wF_xMPa z3Uf)&$&fi)+3%T{nJY6L12jW#i6C_rNq9N8TT^-$NLuy9Uq}~w5p#e>DXdxuBTNC!2FX}GkFRnF~es^(|jvt|2zZ9|9_bF>E*Hp16%61dD0 zM3v=JJ+aktZ+D;dQiuk3>d0du%eMx@`G?bs@_O|u>+_{#mB=`K2&Gh>00V)BaM2g& zd=ID)axEE2+c)cqK1FQ=b8?wX~&wQCKwaapRTXqZ+AW2Zb!Wf(cOW^ zOW5U~Zeo&^Kl&oM#Q{Ly%3vVT@YGMbXDF&jJ-8#tw;$aqZ3RDF)mmy*bl$6dL#KXR zlP$$r=(X_C93v4_+8)5^Nxz(Pzp{v#rXUDY=Wss8mV7uXCpFELP55GL-&%hjlvH9a zAD_NNJCz(AosL;)Jmjxmu>h(tA(>Tfa#&GUFPus%VzHE`nGPlGyKhlX>e+ZB=_RP9 zIUhsKWw4kUqdm2ekO4rR&R5>P5VF5^sHm_Rh+phKYK7$t${g9$-S$`21R{FfoFrfj z;x6-G8UM{*i&ULhry`ffN?uJz)rkd6mvp`RxFaJZlFxT&CDS7EaMp_`CI&eRBa4-i zr~VY!2sM!{3k*OfS-}c%w&1p$VD+#>6GApn+${*y7IxhKE(UxBtMSsK)!Mn z>Nn;rNZA{MYfZfHSB#t9D2*fX1)-R5dTjjJv2t~z3^;wNBA6AZN=y9)2Yh|8a*P^24ddMP&!*`Km@A zm^4>a&atK7X{m^%#&dCFx=Z@!Z>S}a%+{D>V1Ha|&)Z%8JiM0UmG+0nol^{TPp)do ziq~X+eZxp;HZ6*V#3;0AYv!w`dgEXx#5__H8KTxNxotkl@4P%g#f6GPZ2pA^$KpH5 z#CMp=o@_VN0BO}?UZoukfMx{UA-sZL;~}7nsKbE^f2#QFJJlC2k*u4rZlqVOLB1=N z`0+GJtEI|frD4!c^ya)N%5@0cC*AjYUr~%uNvL%@#Je({;P)b1w-bba&{1IUI5u}S zw`=R`6ldw&C*zJ^p5W?tFFPzrS8F&=EIqRd6;1V#@P4Ld?PS`Yly+ERFFUEUG^|eG z8ZK-e39s9nR=4)Fio!gnId6x}i9fj4IR%W!U(q}?Ms7{7J#(q-qDpB_io+4tI&L*- z7#CA?2Tyz6&h_AXH=RZ>z6Q_dcq;F{KkZ>(`R#VZTWOpYxxYcj8s~SQ#aJ%ZWuUE; ztqJ{EqhfRg+()uDH#`qSvm6;2;MCH{v_nY@l-x9R5qGY2B5^qL@%vCeNPi1YpQ!G* zk1lA^=3eTlC?+=1lAuz+2f1H%9!Z}5`11N1dCy`397Ze;qd%{OJ&|UQ@ljBNlbaH8 z_?a)%XUAfpP&c$l{T?ALg@oncx?f?Yo^;$k!b9bkjp|7$)w6DzKoQW=jyK_YF$FPyg)-8{u>4}gm?Mne+mNiCe7!G@R-L8| zWD-KS;7~MO3W50kAYL*GmKIEmA!sn4>R-5!4Q@{5Y zLVY&f3iof6ycm=j$;k$|PNdoU{;Lfi>#mT9_Z@D`=gsF5&2`@DyAtaIah!h9cW{ls zhSAy6#U%w6JT^nJD}|mK&yxp6+ROb4$qK*L@}c)(y(osEZ!@^>*Yj|KsR|1zgYA`C zcuj|oA(1k*RzpJY9S*nBtP+QhwhuSXcHwa!Q%ImbU;xri?nE=ZPP;GyM&7k}1ewlV z-#MHL*mV>IEI%2ECZ!>{&wU?)CF446h}=I%3lO}vnwo=Aer(ln{$*4Pi*|3g296kd zoTAee=%JPNdj`-vdn|qblr@p)lXYmmTxWc6bL!o=Ut|5u1~x*N4U)d|9tkc*M2nj} zyo&k2(!z2zHk8V>A9shrwu&PTWeYCW#@G&5YW9u}T29ha7vcf7W5 zp7vMN6oewYo(CUS?i24Mr7!9!Gt6m{-L~x!tvyUc2nbe2Jg)AO((%>sT+VWR_ozb6 zM)D}jXUuS>cIR7F;)n8oLcwZf)U961ZbVLhRR}E;y>8|?V7VAjxxwu+qNNp+XK$ zpwG?0Jnr|rsnuL1Vp646s(gG)w2^pWkHdxU6K_j-ZeqH|^El+FVve`4-{wwd$tMsV z{%qsie7?!)yu!fRCQ6uW zh~L*@*`uH?2OJ^I%CFZAKq;#`K3T0pcyvVBaI$!Xsj*D;y>HBlr1u}Co!;;4^_q)* zZYse{_~ZWPqlra{`ObFnz(X^2T2ixt5F=PUmRxFn%4x{|R}z~NVe_4I)Lhdg)B zC1b?KXFsTzQm6ZAOq@%v^A2Ty2Z?j3sh=B{bWM!Mchgq3#8xN1*BPyOtzC>!B<{W3 zBVH`GX4iS2IHaE{?jJH^w0%*&GJJ=){ewA zeLtV2uomXlPxVW4-)beD3gFvDB$&&F=pypEcq(U;zqRBkbDMlg`~siS=-g~CU)nYBt&7ZgxLeZF3w?)ogTv_NHq(X?No$U*Wa41q15C3vIMxm=^I`-)&)59B(Ir zbD#POa<9hMa#U!xbb^8J zFOVP!^@s>^Av*^l-Z6W}a*x|H1Y8bcQqp88(jUHKCQOrj6pq$)HK1*j>N-fUg&SAn zPfSBdWOI9k;lLY>ql(CDtA>?5b84jjp{DKhbicR4j{S4lw0VW$T(E#Ec-wnMV%-GG+YWfZ5wt15=+z+c3#%|cp)%}oZ+Ege~5Pn>o4WHajmL1oy(uV1=`RJ%$>xw z0e_4nukw#;MKXD}8ja!z757!#AikMO{=>UZbZMa`>M{dqw2G>VhPMK8knmxhkW1Fx|g>;_C?RsHY@lWUl%Cd zPGQ=k$YHBjrnELt*l>HSnv`~uLM4jpRgh232@JtbI>qGy#wZu4T5K;6B8KlVvt#yn z-QNDtiF2iz98=$?ZE#TP;_II}Anz965&6<6+rHp-8y{qqgJ4tiJ_9A7(VMIa9z44@ z3RrTHyw?H^Yv~3lXX(?C*InFE=D-(|n3cYSJ{#33#QNuh6rTijMZcv*NW;Y150;9!a1&rNY z_Xn+*wiU=u%4O=9a`o*@a^9J$7Fh-VJy`!odR#9&9wt3SNOmCT7nFc5ZLW9fuP-7d ziM@`6X6@xQ9`DF_AswF1E+vXA+2&xNvB7k=dhFlj$xPi{+pPh>*Nw2({MkQ{g9;9PIs4BuE60Jp>BjzKWN^qO3fMx&^6x&ps`n z;ASwFy`9VCh|7`*NhJND3o#Hd${=RYL|nVDyKmP(a`T%ZFHdEjo z!*z7eDQ%!aB4JS#R?z;_6Mr5^a*)YuH&c$eVpyJEs6p_lmp-fcOV*qWIDA|-t2rcb zh4b08O>sKr#I_^LZiqp;SwfLQrA?>^N!11K7xH%s@~G&qQK$tdekg?hgNE63|IFCk znJmZrq%8sO2NCRRWyx1r3dG?4}U3t zCz!K6jbj#}o@Zt#reqrnRVH&=FCsUmWg5Ced@gL%CGg+vUkxL8VQd};mYkyLSidod;hx7zC9|iaphuMt8bFhIn@Fo!Ow0+SH;GL8KdF#EekqcH z8Zt)48(4Of%E*E7BnSGY{e6HyCgu$JW1dkkX{^GwSa8syVQ=DrET#={T{Qhh;O_yo4X)KNF3w^Xru5Y5K?y3f>=-QuNj|VgUYF zQR%usm8v`Y+;T{3R_rVqgLUCYk$B;Q!3;uEtQ!8Qzb=%^gxI6{CHfA_@;K7v9V(@P zL7i1@#Vl`jUpAS6PPu@z27S?ziQUkkAlu`jq9P_tIOAM^pU1x?8P$oz4P3)imi3h7 z&;3FdlC0umrxYJ-6Y_D!>UaG3{aduxk1k?*fVPRo!w=Yd-PFcn;pFzr(_Qb4W0gq>Ji91@e6NkG96}ybI7bKb zedPC(og;A>jNx$X@VWlewUPh-NhEq<<7x-B&mns;RM8bk8@k_j%A;c{V!!+03vbXe z+K&3WOo|Iq^HXL|F%{9l>qisxyD|(Ymj%`ag5=twfhATj=&-q}XF(HU)kTb=lY{i^ zz%zDIADqrJWymicc2AtJ5wEs@fqoS&9iNgM>Y(Wx~cW}gM zm45ytxipaOaBS^3uuYMqEdK);_uoFigI?J6@rE`W?WvCj7$>RA(B3vcS>;z2f_3Up zzP^e8A|M&dpQT@cDrbuayXcXlma_pQ*SGzFrD)V?nP!z=r{EKmu?mISd|p)Vsz=W6 zox6?!(XM0}=9b7DrsAik)I_Jq)X&_HucMb^bE-)ZaOhn$j@Z&VF-2LN2>nD6$78a| z5QvH!mAyC9c_aWu^(3W~#a*lH;?noMh2NKqDnG?g3>&nY_->mFHvsqTBt8aNE}n<4 zEp3bvdBr}Kuj4be7)pNtB-=3G{WUG6>iafWp1C5ZZ6k8l*BSUle}-tg{upFD9(gH$ z2-PA?P2A849ebIdb`lbY;qY|h`Li85R3Be{t^muLMz+OA*dXu)N7_C322ys~9OZNW zO`Ru_sRda;Y={NaG;BA!j>UJi4rP$Yqi7ey8hHuY7Lhx6WJaXC3+7~`FV+vj#Ndz< zsJ&PvERfWVIxtm-q^ls%GJr?T2Qo=VPt37m4Td0kY=AE5z&->ZF5X;a^?>}??7rVcUKEXr+ zfl5WMHj%lv$iY2dYxsjElgJx5unttkuh@7(-mt7#UFAJ_<*HolyG`ZaD&6e8IZYLp zABo9D+X&AV%N*p(Ch;{KKXXK&_?76d#ZSu1NrauFWqpL~P}rWyIj_{EU?9$o(DsaG z&bnC#D0tMt;O+%@xU@Qh`~D$XvYqS@7Tji2orzidq95D;11E*m{DYGm#Q#4yY3N;Z zCI)@>zn|b<4Ss#Juh>Ty`y)^|ZZ4<^M`?$tfB%!&CSKP3cX5TT{EQierAsaad_D$) zvFfFgIsyiUuS@amFkqc18^U-znD|`bvWq?v&PF|`5d6v7XbnI%qfi>;br{B=PS#OfSw6YmzGGI$4AjOwl)`|I@dx zhDBYvH6lF9_%9KY3rIPmoI7CBL~;v>$;|MzWWr1+4MLY6k7l;IyQI((Joqb^Kd9ISx{#;m;il54!{3$`qg5@b5s?z6l)bz8l z6ZNwE?$h#8s|oO_7m)Ow%cxM|{2Gr<7sG0bT0AV-Adf8h?OY{3XIF zFVz7t&mSzPdhVbmI+*vFi-M)cX;0Bsn1Wprr8o%B$r6%t36M&GL76&Trs^aje^OGB z5I1-3t*K0|mH#n}S5wWRtcMhGv`cPC8ZQnKC|vvz5JGuy3a?%}B;~M8(>uUvyBC_4 zxIQsnQOW4pDWNP;MU5>}<<f#vQ^hpD#0>vRgx0dgqq#%r(+M5d7KQj{koIUe zXt6#gXn|>GArNWAo70I_a!}8PCIp4UEc!~Z*X_$*t`ClLTY!W+ov~F zD`h2>qT^~b8%l2tKZsmUE41jkS1_Vp+N}o@J=h zd!S2zWzMl$QbM-nG8xw8=xx;EHBUE+K}* z04cz^Vf-RQp``MlsM-QG*HEvLW_W!}2|c9{sKZ@CJP5z)5y1V- z$${*dcW4QGC`Id3>|w+?Q7&s!--ub@9#D9K6QYBHKpM8t)w%B=OE}Um;qhcMqTRc^ zxpZf;P^OhSpzLvTMk^RfU-%raxvN+cl$Y1Yas|nAwwS zkcHX#AWDlIaZAyihXTs8>S^1w6h+Aw|^cY;+}d8^h*rI z(>xq68LzC+Navrdx$QbV-jWEg=$uB<-WLsX&mVv1}`LuySKp*#)uf%G{zg-b2slgqqIr%F1z&Zl!00|A*b_ftZO#(dmt_SDewX#_Mr#^eRW7X89Xb$2RxJm28P*!6cR z$tI71B6jd=Hnugb{Ftt{@ilSOS&p5y=UZ(fTqqU_)EUJaqZ#E1u#epR4l*^vJf7Y{ zlIy>epIZ9CQuNgF1WnsLJC-6&8?JR z!Xr%BnC5t|QQ-p4gJZ|TWGbe0rwYIt$mu?B%=`X}fC3C5pOgA~V zU|m6oV<81b42iBTCtT*optUfXtOCD@jO515K)in#P39I%URzx0!+8q;enS=5#`^5# z5wCN2;5=Y|ghc7o7PyT8PnYa|oCXUURxks2I!-J((tr$Mkn0Am_hwoECn)GhDPU&U zG`MM(4H9W9m6_$viqO|3WgoFWCKcNJdFX8d8 z972)}gGhHOR2BWtMaX_uDsnqNKu>}Cj`C4DWKl>*{!9=u>k~>(O}0yJQEft9HT1&f*wX@8BmXpMuNi0`RgI zKv75|SA^@dbbmq^5_xJVU-^VF;BtV!!O0woQJlnB;&w7tBhRZe4;Cho&>xKEj2yXB z+lhNSBz_!OM!DU@@Qp|7nJ`HioJQ}vgdMr0~u2Zc=6_|C;IVM zn}_q-dvk}^EvH#Inn9O)To&yUKZG+X^(B=X7JJ7RuA}W9qhqv59*)W9s-MIz4_T@$ zA2m_f(xBW~gL0`8oJW-C<)7e7NomuSwg9d7y2{+5EpY$2{WcXAbFa(-{WnOPe~)2W zpo5yT6BTlU=rV9NJ8U$+b_{~?U zOeF^frm=kRT3*b7mED(OTG)~*$)PBRF#o}!TqM^;dK zOFuVP%y{ggvsVFBN7%%e3!s6!^>2So2fQmCmF#a==4;USmw1_edY!p$SWLf_&`SV3;d4<_D$j6Kaywa+B(t zj8Ym+H={vuEv2Rf&88rw`GxiLU^O4LpY?j*#VBF=SlYjCdte}#`F3~jC6Zcy zy||rvp2}!{oVLO>^4A`r{PvDxmbBRJRYRrJp|--Us4G>Gpnoq0i<`yBxKo2e|hy;>u$+#yob^ zN)9+{wn&M6VNX@-Pjhu7o58O;;gf&Mt0eEBM83#HIw*M9^?cIs(R{k@`|#nE5~v`* zn>0@|S0t-2%9=LexCO<}%6h@tVttE(f@Cr^t?BopL1s`C?$Hsw&ZvHIJF1quqQ3|5 zGI`TdwLxdi|FIvzxpr*%Fcp?3oniWiszk^9GJ5URMS&-UcjSi>-Ue2`h1spE1rDg^ z+v7uaPG@{C(6%F)g7*dsOC1q?^tjzE(Pm8)i7HvAW4(&T0*yEh^{L5iBF*RPgwit#+WVL+ps(z$v+F^fXq(7aQ5>Vy`mZMjWW2xm+FW>SP}+ z2OKD+IB-!YwT#QjYXGK8saqpc?B(!1v{$nF2hzv&>|Bh$HM%LZJQu86SOZjBmro_W zrLUBoE6T611!?aa|Qia-LiKT}9DU$POY$!9i zAj9*}Z62{=SqOZy-X5CUOPbl^Js%C~f(jox?Dp-v`u@9OVtJrQysjlHes;l%#9ZbU zl(QY{xOhDv+>T8`BIJ8RCK5?gUr>+)3i7tW2`aHqVABpmmB!&d-Jhzw`>+B3_h?b# zaiJ_kRUHwoBSBccXNMWDp&&5&K|vvH@fV%EBw_sf%L>X&{xpicIvcd9egY{I)iB{z z^Ibs%Rf3qj5Yov37BS=D3WxnpW#X#(*JM{o9+?$c<-@Z7<>{dBhqrR=8^?V$;MXdhFJQ)GG3n=o(J!1wy%;u{ueF}|`^;a8b zD|l=cvftKjceM(_GMyhdHO!KRjIw5MF5Ju*9e;}Xa&R*UGYYn4m3juM}t$vVS|Cvwd7L zD1IZ_TcA9XE^p1tRYAY)q<8q$ZvBQlBH@I;JSfP(5r#Xt!C6!-C|sC$>2t|Bk%xR+ zb>4*Y@sfLvZzHt1t2-4BpTv=vYw9BO1W$@=mBZx0;cbH;6)8=Gr?V_))8-$=@hdL; z2*wN?eF@H?kguQ|^?)Xy36oRgQ<_qvaUz)^DC*`e;-_EWp)t#elfw%ieCbFf6$2?aqER+Wi(eNLXj!IL2Wy8-k+|qRtu6na4 z0q(pS!p8vtTyr7TQ+}76jKPkt=}&QQzGTFhx3ZbkE=E-!%yvFS@LbL6$F#DyHHS8w zzRx7PU|m=X!LPlByHj} z(NW%PX*G2gme8Fc9&?vm%rIoJ7`Is96D!4@P549X!5k!&DlP?xA!bv?ze7I|-S}^A zYF40C{x&dtOes0A(-&G2W4~eY^c=M z=ARwS^VSFkyb>6pOKRkToFz0DlE_6>dN|iVQm8T?>krpX*X6MByT$UzFVhiZZ>anG zJ7F^_I~?>mD)5T&gw%4qDCJjm<71k(G)Dan)VuG^ft`A4>iTMv`L}gn4?7vx-y#o~ zHv$v0f&+rY!BSM#V&(S56e`Sj^m$9$fBw7B%F{uBE$)OoJ4a#Qe8f@W*mh%AUT*;a zhB-m>oU+d)@_L=)o-){`8Gd1ATrE$?<5Das>QIthI@mb<(3{bSb0HpV+j0)m2vV0^ zi*2zQASlTl_Qd;Cf<8ftm{h<0TNt)ngUjA#Zjx2#a{tO{sIK)pJ@=jHxep}E0x96x z!`Vw*RaM;UE+_~yrg%5tV4T&dGeBiEwx*J|s*lX{9ISM-ogJ)?dfSoHz`y!8Gw%*_=wDsi$f3&+k_zYbdT)x}>#_*j&RS-fzSuR<3wd z{hpBFO?x_PiXD8@5(^@?93aE7bG3%tJcWT!Ws$+_a1%nu!)x*@!dp{GzS1wz?>WNi z=}A%R^~?$^+4UgqQz>d#*7VzCXEejirDw0*4*c<8Y7aU(#~)ENMp4p$zV-Q~%_R^Bq1+bx8__^;1W)_7FA zUV4TrBL2;fdD6xht!UHA8YN*`&Qj((CiAQ+U{Su%t-slCEkXi338^&EY8>a{=+7wZ z1*E`XCh3Zg5|{PUs_)P|ghrD&B=k5Gij-a!U+=F7V6U=MLdOg%C5>^rUe?u2@72!d z60?W5V){Sg2%r{hBZRmUE`@I zaJ*F6dY_W4u#Cq#+XlE_YcBaT6A0s_cedY(;LaH`I!U*Tb>a7;Z9C(Da(Rha>@!!* zLdosI`L5q@tnBDRca%h?y}1SKz|xe(W!5MQ83HEvk#RZq_=eWs`5%KR?KSMHi`4o~ z{N+5Nx!pXL>$X3dvm91JSqkcBV9q~Hfj-m$hA=OQ>9NIwXgd4nH|Os4UGIxlh36 zF6-T0SHfX*O=o3~0t#}_;>ZSw8EVmv2T1BNi#sb1)58YB47_ronouOsy;u~^xXzow zP7_;Pk2eEU&=jg)%>b2C)>Sa=P(*)LP@ViKo@ZARw;9IJ!ms><<8{p%ekJoHbQWfq zj>3lXl3N?{T|Ykt#eV0@dD2mR6bX|AgeWs~{M-VnCiGFt_mWgaz7lX9g?*?#Ja_p* zm@hu^pO_LsTB7})u9QO~T%^62QJ#&p4yvou?s%xl(O%?WU&LQ-M=RC{$5?AN71qVN8r%*Q}fB)M;W8bV8mLwsny&Mc{FO5Iw2BR!Yh^s<7T;p_o0cmn_zBA>D@Pa zWYkGs%2jC^1}3)$RqQfdA8cgaUZ4qR^Vwi}J0!NYX203CUiHNIRp^D}>X|W?jc0K@ z*@JT@mG&tAY?8fdlH!E6V!zW)*WCk?bV?52SS{K#+8&Qvf>^M_C!09?r8#X;?6Qq? zO}y+;DbpnaTGIczq0ehVvtz}>-XwbTbwt4aB^pjxI?H>lnT>3ciKAfq$m!(eN%Qe` zl537V%Z6T@7+=}qUx3=4dHLsE6RB-0ADp+1%P608R_rKTiFI4tp7H^BGX5SUOG|bY@Vv|v^;A&n7+EX!J^>3| z94+CHiM=Y}mse1zZ^h+s_(nC^feC)9II|{Mlmt&v^3=j8@~n~5(P3n$P{+5ow-bqm zUmVO;DmB>Ooai1Mhp{fZ2qX4)vswj(jZo8_JKg0h*Cs(9TLtd~%}H@3QVx#LR3SAl>oEA4M5cAKI$ zO>kmTQ4G>CoFBr|C&BSZywRiWXJsszAg+t*+ljeXRHqB1??f#!WF-zmg}kAqC)2aMuU=TY)pMsHYHc;b-u1%l&YdU-=^b z7qj$EncJCn%T~h8f@$#b+IQbgV-El!dR2Q1XwbPHQOt4xI`SwaBImB$PdQFqiFZmt zO8Fv{V}g4ErArBk!m?n*PeDQN&?fV9qt1B9iDwj@dH!HLKe4q|p--xOg)i@Z?EknObj{&b*8ta~A#xPARG zMI%9yLQXh$^7gwas=KYSzAu1@ray<-Ex?u7#i8zC0 z^c6iGV3|QnTP&5zGNt@&&^A#uG#HX8>(#6DlkHNtH$F?tZvtyS#&SH<2Dmm>mc`q5 zM~%MtGG9uS&_LBY$HNo_S8XJPAt?&kra%Kt(FHxceUSW{Z>SD!q9j4S7EzAUozdDv zj%2Ky^O+@E?}+xGC`&C%Vj$!}q}ZtFSPZsGq$+%S*^5Ny*oX6xvuBjFTAVLxhq3*X zaWwam^l2SXJ@C4QC;#9alfZ7(YH*7Q=ty!BZZcD#Q0_R8PPix;v4b$W3Px=xji*#G zS=yK;Kr<;yMJT;ch&xoE+Jxrbb23-VtrQT~54=XD&Z35s6rkDT)$+*HK@NOjhrdzH z!#z`wGfwE0n>34!36qM<<{{s-Fl=5NwUUX*aWZwgPTa#c(?r6!C<8^75IRi1cd zE@Y}SxJ9`K>>1b4AyVtH(5xqncZUj*NEUN~iBH}8jgb!lI3^Vd`K8G!+p*F7+z1lB zKx)*HF@JsY_YC{$Rmn#O?1jottOxkC1Cv~X`YBWMrz+{StQxq9A`#B#1zuttUB&4-#xAHt=MAN`%>G)2m0%^)W2f9+o{5L-K4*M4NHzag zWNXK!q?m^({UDy#JrK!WN}J*he@Aq6*3PVi{17K$D{*lWt+`IHscLr3#E;wjZ%kvu zg%y6xKXvF=yr{*B11M_$2N6<6PwrPnX+bZ!3yzmyB!ATP4y+lnu@*Z0Ucp3`t0`F( z-EN}9YjC^H_)N9LaD+KRjkvOlt8%v4fW5N{HLvf@n>;^ zo7suAf|zk=S6_g2h0CGy61QDUG=pXo316Z<`wM%J>&Z#OO~(gznHlUP)uy`a`Hib7 zl>Kf+j3ZZx_AJ(FJM)|G{G#`-cVFa|d{!J#Rt&NtZWjvcKTk5e&lkDmRMr@8Hc~mE<}xd*lJ- zx!YKlz8}KU71(aGISg=;RGK}MqBe2rxaYe)$f(pW^8QnPO@)8au2&cB$E`xg?%!() z1VO%j-rgccMxIG*$b5u31(1>ReX=?f4Sg-|>)KJR2^YuW?!eZAohO_L|b zHneh`ei}OX_Hil&`H(z^83XGr)WgVCcq$P7XVhg`qbRh;I3^vo_A-al7t|DMM&s}c zX5NC7<*p^<<5;RS*TM3g^479H{E8A4ktNSBYZu{>^s#+)#h1X-!ub>4BZ1Sq_KeAO(ZspQSYf^k^b@nsc9+LEEoLrOuRdM5!;(OaOT8x9H3Elll)Sv83$ zGWfj^vkXmUI$|l(a|s-jB!!ZIW3*s&>Jo`%WYyibGalj18z|iujaXva^89q)WxukT z9;;3hhx6$0fID$=%6Q!R>{a)d3x`-FLaCHn&c1}PdB;d;cz8>qc><~SIn9n&uo$)8 zD`0PD%DL8)1aZ;yG-iOz0Ts!Vs@OT5DS^Vjg;WbL%40)h7(xmHud@aTjHJ?^!PJd% zWn5Jf={_KVv1pJEasnax)RSki%`jsp@MSGWCvv+xsj1~onZbYjn7^o^rdBwyOFNVf zfBW~H2x$+>655oS*|m+^?&g9jzA)NXcxu_L$~}+rakQw)L6mcTbI&r;cs3Ztj&Leg z!v#5ga|il}(q98&1U|y*Z54mxB47!k5qN1tWH zPjTCIkRnkPSkX{HSk9H#8WRr}+2C<5!7(jOkW2VJq!-bb&}u)r96LVb{Z}loj9iA- zXM*=H+R>)N`{_Ib7jg$1w;M4dqxt>S;S{V?22V32j}a~>X^quVuK7#}B$4!wiN6!$ z)vp>T|JB_AzB5Cc=tOA zm)S?T9K3A*v{RL^+Qrn=Wf+R_A+b3sHmf}HPfFioI6vf&t90srOQ4SR)9nM61(v?$ z$b7Nw*VRz;0-o$ACcZym;H-%4u@3OIp`{bMzCZ1@NRvoUxNWwUgl#F z2Y2_R&9h6Z1wUQS1CAeD3X6IIXO1k9H3Cwt$`fhOw;N^I@nl8Dv{P2pZivk1J;!J7 z;q2%@5_V2j%c+^-P#(Lh5KE%v$1`19f==||E3Bke82 z+S;Oa?RK|Nv{;cAEACL-p}4zCkWjoh1cw%PcXtWy4#kT*6xZOvT~FG*_bW%P?_B3w zf5NXd6XslF&h?CYJi`K0CbM>fM-$ewDEte4lc8n^1>1JZCwl5X&$*g3@|dZ|3U9s6 zx&7?)v?zMTgIrvU&JnJIJK^>- zu2uSzzc<_y40alQDJd`YTxtaP7~^Dc&1cB3C<-8R@~MKT5k}~*Ku(O+9t}V*sk$+d z_+_+aH3cIjXJS^MwE1NOGx$^V$LNnhW*cMj96l5o7_3!`(4C_!U`O)1jLM7}vtMfStbWJq|!{!x={r;Uzs5Anqp?;6hg^&`1g#>-W=?o$R2s7@15 zW0Sd&QU3JmhqAcZ&STlV#lAx4<|ow^|IzHsCprs_(>8cY0PZXpTR5~l@AmJ|QBG;` zxjm+xKDwn%;2Ah0!IL0egXCsS1!Oj-fN0Nx=NNsef)Zpji{Dl0@>W`l0hOdy6v^0K zGyFpA5#6|=LhcXk$yu~a+O{$C!ZiJT+22(KeIb;wL&rOh_Mtd1!h^+vM%qTl?Wp`T zMPiSg(PT(ipldWtT`zwgxN6<<79>7+a_OpD19`98+@$AjqsWk(2q*`}V?R8dCnBpu zw=<6Quk75$=1)7Do2S-%miUfr4DQ!iB~}8R|8Y&M3p(B^p*!n;80I82pnXRV7mg8; zqdFz(gETlaLM>T~JMiU=i#hhPnh0sa7K^M%aA?!d7m_onPW*xS7x@-Xl1i0edb5kS ziP&Mi;4-b9DKvDrxhf~J^zE7g5 zd1YXTJKpsOa{|A+n=Dn#|F(&@BN39$lb~flB9y%z9iA&EjteJ)bRJ57hoFK`_0HJj zAz1^6Xz?q@ST)G1<5spW_#6%*T7iaYwa}V8pvJ$QrlPgOi0xq9h2O^(TDp^y@X%3H zJ(i%^NErwq#>YC`$C*MWBEkTYV#AVT#3$0oQ;WH{P!jUzuU=Xnl_DRtwG2nAVt445 zM!*xlvJ7kbD05}dB$258Vjfrl6!YPOV)Qw65yl#I zi3s%zxK&F(xh(!F%Oheew4l_`aL~P#^Z6v6{*IoMq#Z0RKDjF*(;TE0FR3WsCZLP- zd-G~EX^e9ipAOLkmrZwn5hu||OhdUGXfk@kPaI!!Tu3kA(Y`r+9kU$?h;ny5xXK+v z`Kq$I9+Ja$9@cyGiN$u;zry+YAW5)td ztR1JFG<2C(#QXEf$?kKEE;dbVtcalmTvprPK;bL!`2u~{?#2sQ@>iVu8E+1bmx=iWk4?KUw=0r~@IUGyq;|M&PQ+7P z&_6`0W%6O`uGgmdEAVB??#lV@jO^o6O@w1JaFSnGcpIwpe#WONd*GW;lkUY5Y=30Z ziy@y)4e;ha5a1VZ{Ie9k@pwe%uUp*v#LW|>Bq^_AazkihIS9@ehK@oTM2eu<<4{aw z8D_dRJsOHS+RYriSSxAc)wZLVKFU}}dWYv^qq;p!mX$E=*6uQ-73d081Z3&lX_faUd>DlO1M{R_%SNMzS#Qt#lJu{SZKmtUZx^s72Ed%F zn#o6bxSBmLvM#I^X(@_=+#c$Y-)Qi&8&>%)byfVVJB3hD=HsgAia$Eo-XhzSTx?QJU=;tK7Xr8uyZ*!%wC+R zihjv~wpHeFS}e^@LCPcS5%YLnT`1v|U=#cP_p*?$nJR~u2dD#DHV}}k_fHngwx0ao zxZ7<*hKa@7>VzFBL(OmqN#5n{Xvh)PADKl%E#lF{a&TkBptPhpq->Vg)pnlu0(52? zeS_h92P;}>9`)7OdT;1hj!Zh;7P3n*?QC36PuqNm#F-w>`tw6x)@2CR>YKSL3&%Nf zs@E>1)fX%0eVK66Ao&hjnE4UUdhGv?al~})tr|sH_pIvb>1_B5gzeN%^c(k*^vYzN znyR^dtU8(sBU{&G(!xFO5)8p;xTf)?uORHj+9AR=*#O2INUJ!|bu_dHg)W-~6LBzY zBW3G_D5(%^is$J4cS`-a^SI_*TZkzj>c!#@j(M#M#Vgu<_}8|ox5@mg8QUFa$IB&z zBmsGP!Ip`H)XPtgRqIk^KD6>f-d2Ei=EM$r#IJ$|M?o;Rz$p#gPNt@1VT9rm9blk3bJJ-8}9J>H&oO&nWOHUkc(0^5GI zw_iRS0}FZ3TphYZ9(eR-H-^P>#qHZ90qyy_-?Oj&n(#MzqvCDkahRI%?)h;pPX2Ug|Rg|APw}&d04v$?#NoU1tZ^lNq2W> z>KG+H0MUEU+V@Vv0f2Z{G^VqjA*{bFC{V9#HyH^#UbgN8;6)H`7gG~O6$gjP%=xsSs;YC#ptxb88MfwuB!_h z`@=HKq`OnjY1^5<%XNOl2d?}WQjIqiTI5!12IVJ-@V0NpznZO*;p8=P&R;`Ajx|He$J#mb z(pwg~-CJ24#?vvxy_G=JJoQ$oAGi2;y_-J6pYw^%>EDC~QrF`ZBf6oWskB{4id)_ z&F*g1$66)a*Ksm?%|F8``|Fq8Ly|k`gFlROM8OhY-{=$pE+xcu=CH9;Aro`fA2)0Q zN&B$eK^n8S=S&hkR8-2|n@{kdc6RZ?Sg82y*9RYSfDY$0mIO2;YV9M)ZK#C|L>^Ae&#tyMrb-M>MxNP8G z=L6%@z}^Lxn0jz8xPHefm2E>z=q1bBl-q%Ce({EkQ!`wH>nTafW5gd~+v%<5t;kq4S98Adq_Afi6HC^uuHAWBL z{KL_i1=SC{s)(0SD*$MyQJ%$Q9w}J-o?oLgQ{N4dB2YqGe4ac8i-x+m5MnZBJdS~% z(p<|lTQE1|qK0m;m5M6QT6dQs-5?$gy0V_*lOKCofTasoxhPOMG4#>(fj!jagri$H zX?DW|ji(-|J5TJUM``As_|RChFMuP$-2Sj9$61kvUM?t&gR`XZ3%M(_RYrzYr&|`1 z?_0JOG|up|>AK!)U9Y52A2g+u+7%mJ!)i{j=Gw8eTFTFi?UabT;cb2+RqJk@Otmv* z6SJjLjBEkQGEI*|8{YPQ&oaO9C=rFlEBw?(S7~WPc2g=QS3#{t>!3qh=g617M3&fn z`n)$Y2V8f1pjdT+HCc*^XD(NMeR5KssvV=qx4R1q1KP2H`p+lHsU>x+D!W#zuwbEauI0y>&`t3Dk+q&-^Zol{fT9y*d*E?#YL4?Z1#)pV6P4=fFQ;eVE_iJ6#KWxzERRYJo3|h}p2O z_Ac?7uwm`9!?{kx5cj0U1~Hy3rAzDA(39xKM2wkcCcU-8*Bj_w;BWM(XneXd#Kw4tMoEeZasL#spDIj zS}6C#u%GI-e1P4z@k7PRd$Miso1Gn3r}U1(%$q zbbB8?6c(45%*x8jkZ2yw5H+MM|KQu?(0ZWa;cB)^GP+IO9{p035X%5_JNa?_0HFuM7m7p_AH=somR(9DdC#-1v}ck=LOYTssgL$ z>?I>v7#Y)-oi4M@$gL+l=5hU4MHPFPx`XK(MID)fW{O)W9&pEO%@^gMESc3`ZfUm0 zs6mchgWvCGo+(GI&0}$O<*}TN9h=bK&Rdio5DR`#UT6^h2+$o%q~!N*NK%j9jEqvL zDCf^#Y2WH(S+Y0n8pQG@;EsxsoobvT91EqCxX|8O2R9mZ50OscCM7YP;M zFFQ;b*|e{0ZtU)?KvvL)^fQR=VO3Dsn%RFQ_Jx@*C9EDp`)MK8yv+-xr1R=009{7T zJNdA;dFx87q_;dTE^h`#zpHO4n}rP2kX2EzfS9F_?aV7fi`l6o(q}d;G_l27BVU(s zyl-vI?qRRWV+!qU6DwoCWbUo}@cS_=`2gNjm|j{92_)W|&RM%&UR_ye{f0Jx$+>?E zeGpMdir9TXINzk8KMIaN6MQ_FpxoJQw{fed+rA2cwF|wT8==+Q@m|cJgjty5aXhTA zplM9puahWTZRj`|K8}U~0iR=2H*ddK1*(^Gt7|)*F?BeumB_hX!5d+2_>bm!-gV?& zEqXt%YBWv49KW30*Trq;LWo(Q4f;IIo2(H|?*5h4IdPdB9*>uEG|9bP@}8QiB?4ro ze0ZG=3{1zE`U0%}bSIYz#T#dUCvla7o;QWwWSlHq$e4k*-oz?^GFE-T01+z}n!dCex|qnYm>{7!#K?J~MJ=LT zXCw6s@>qgfv9mapFY>jw;RVa`?spDEW)jrnv`cLpLv5l=zU|m^6pgq)6a5)qbx`A7 z$6Q4t)8s{}et~}wMPa2dM(9jXuHUw~jecm10B+TmIY8DOV@TxXwh%0L^xm-vHpiOW zAU!eq?P6ZAz+)cFs>95;F2YmxcUaKaDvOjyD8wW(j&rndeQJ{5jY*aWf*cMI)&$6LAGvg*k3Wp+i9a>3jhj{s>$Na-z^bjc z9>(ZkO}fa`m=MiY)TZr0&-eLg-ja0HCv-~bvRb^LuGWh7yZxaN?=xK5eAA9wvw^}) zFP=w;D@`)ogJD8WEkov4^uJnjJ_I;;GZE-^W8Yll?$roB*GWp+JMZbYo#k0M2 z4qI+InaeD+ua=*eoNuox!Ry-_g&|! z-w50%nyMsJ!wE`hvNvOyjBf5zhZ8za;GbR2x5L|o8F&KQz3Y*(_nkRkm9>JXJqec| z9*bN5Drd%(KT76((5LyZOeyo89^=`B)2SY?tF?)#gG8d}CB$$X)KF&F3wsl=uEkE= z6Tc1${m$O}*hCX~vVhlC>9xhHLw{5IPwf6ySHULU9p*#p;5wi%PsN-5QK@-6;$b`3 zb$XgUH(zVO!au#HuNW{S?Zv!p5>6fL8CSi9ZI@X*b_rU?b4HYZhvzsUM|(+43L^kc zTR$d1p(Ix0MUK&uZsI}$D4}lVrXwEpzgL+CtyigVdo{1Q{%TQe+WhDx%$V0VA`5ZceAb-hPHdzehe`aC9$p>)hRF4v2jN9Z=v_~dVi7OmzUptli3YtYw};ci#; z@S^g}rZ$1~sT@0d{rY-Rk^??U4k=|`xoYU_nY6tSLlow&s5xhQJYux(3->0DZ_!q^QIj{so;8OKxzwl%!Tj6 z*TZp=KE^ER9DM%Tr2!>qrrUWjS!B8Foc>CU?KbCx%3Xb@#NRKriinfS_pjAW4?oRA zj3t2cbG;rOK^3e>V7kLJ3Hik^D_Ls~%veBlwIYK;8BqG{UJ6PX`yoJ#Tnrdor8qsM z8NHGhZ<7JLVid{ihCXK)A z9AhX$CoV*^DEwXvvYijvq187qjjvY~4{f7CGT%SaaR$`yKi{@&Vv>Ow-z1UPO~z1+ zIj;@9-lcoZ14iRORPedO)BhgSz|I?ITXIZ35#N7`>Q3D=&`g5_k<8ADU9 z8aq|9M^xE<;cXm$(plnBSvBg4S#PthUUi&1;anx4Reh&eMe#970n!gMdW63F_-x=? z$@~){?rW7B*NJKFAc4E?Z8WEos%9OwXWOt_bmY8pc(bS`W$ieB7EGD(mR?Tm0EZ&x>Ougi0iB|E}FSoT-*8^CkiG++CNx(f>V>+h2 zH@e;(*wg`o;Wn}Psqt4EDL4dbT4kT$P%o8#2!11YW?##gH?1=-YNB2p4$2<~nxU^# z=Wr<{Whz|cRgPm$a0zRvhsSh1kg{g(jo<;1ICl9HCIXXW%R@DXZ*ToQ5Jf6!dW!s! z>HTm0b?1gnfFYmuW^hU{zUGSi;3tO(G)W||XtrN)z@*qZ4ct@?D~qWDIZ4SNy^1-+ zo7>yHuZq%inL%T>mM?jG663u~%5kaNSztLgHIM6M=lu%#=zzhUawQuHEol)Fl+cM3 zu##{B-@ZK}IXKxMTJjkjCwI)jOb zY14$mk9(3Kqv+x8@}3+l+i4#!UkPT2(@I~7o!$5Mco_H{0AH*E5jajenMm}I1Cnln zvZBx-YVSp4`_N~MFW^=Dgj8^M^PS`>R$j3SS_lx*>%098XM9T$5AlGs)}lp41R&uQ z6Jp3*<*4kZLanMu3#xJS=|jc%f;Gcb-I1kK3xoXPtH}4*hGoK%s?{oVkCaUmg~T#= zHL*x>HfI3o*Juj8q&QB-x*CAaYMW1|vtL*L-mRSoy`Q!|pdW7Q^@Enh{h#rixDr(& zsXQ#XO$n~0|9p8EIY{9)=WK9vG)!x~`VECG%w}A=C#J*zQGc4QLOVQ2tW}I%ikfLo zK*?xc&9pdW7K{>03Ody%%JKJr2_?u;%IE=Ne8dp2UxgU`V9*Sxa&(IKCnU=SKQ39ZfUh)72^()Mq+l!4JK;HTxAoC0QoTjeAbv36QA%aUcE63J6ou$tOaWCX zim&p{`Sb?b?z|rtM6oK|o8AKJ=BaWyWTlu@Z9tz>9HCZ*YMrl-&0&14ou82V@jlb% zop(qq4(7F;$v(yB5(zk&xM*@Z@#;-2K>nMp4DDirAK^jyC9~wE8L)idns(vHNv01A zD+5kwy`|EpJ}zG+C8hsF_avQek%Z&WiY-YK9zM~Ze)ivc?TOU|(MC~hy-B!0rz6#2 zJbTwsU7PTe@B5?PT7DUMQdu7cM2 zyIww5+^V2vE?Rv$!$IdJX@gy?<)Js0p23YOg@@SmIpml!@I(tgtU!rM@H4=SCxp)P-j&b3U^@P|#{qFl9^lBy0x3u^ z>%5ho!FIKNVT6Rw)O)MG*ll>m4BK1$40KJMlEiotM^1hbrH9n~vxxrwxIt=b?#?zS z7K@dnm%|~BB`#9MhUPWm()!-yZ4IhS$&HXzf!TkEkmJ-r4L($uK~@-4+dchdGzU<+ zNoi*X&nEOR3O`d9;?XA>*9IV66}Nf*JGS3+(}dKQMWIhUTU$ zg(ooOlSqTb)Lqk=tx2o?>mXV3S%Jv=Z}-Wn*mTuos@ZpLNZ^L#%qKBg+=?v?MV-VZ zTef}in6yVIRn#jQ;p5jR8{jOgh3GjMPM0t!+elmFLdb@L9wbW_5V~Wg;ceu6e%EOK z$5n*2HZ;g10WCMqujOa#3V0`>QoN6FUcDO~7?OlGc+c`R)D3Vj6q>8rqs@%!%uG!0 z0rA2t(K?e>Cr=3cM6XY^S7YN_diEbTg#Do?@%cgxAlhA5S|ko737PGg%wuMWC<7;i zq#(ra$u&V$Yggh;P|>l_l>0MUXx5s-y3{JzLRYK969SO+n>0^DU0`S`j3Q}$8#TlD z8oWWhulsFUmOLR9zLD(sfST=~E7CnZ(q!wcx+aF~1-~ZA#9^~0}aLcE%vZ@qoyi0() zZm9sI^(x^2{-@^lp!%r95KxMVey(6g$j)j|J@6Q(C#KmCJJv!G%2*&YmxrQw8K1wz z5DF=8Rp-(w2>bW8zdt`Ozn;)=`cRfraEKZ*4SXG654AJ=E^VKg$f$a6k~v?hMC>TH z$X=V2!ZNv$85F?%3Q%y9yMk&_Ib;`56xMUbw+UtfL0C|RL|n@%$251F{#&m{MV*g# z;PP${6Ln9%gnA4>N{w*FTBi&>U&$cd&I&){nQaslOHiK*nRJ_gZ?!1 zL%4mqKgJ#N^mSLZu2Z$Xm^yWq?d~6Ig%Nt|r2mXt#9U5$J6Omvrnbskpd>ETFRt86 zqDFhKQ^q`PSCg8XbSMp}S1FGWW3Mh2-JO*0Ijfut-Bul`RS>1;ybFi%sv+HFAjT7( zaRKZqycRdXKW(rEhBNqi85pK4>fm3$R<)*QV3^r+Of(q__zzz$_5rmdzNi8E!y_eA z_x}BWgFKfy#*4Ws=4llkp@LRrIDmTHW1vg{fPc=sU5xEkPUrbD_R@>X$zVA&Ha#~Cm8TI$fmMuC$ z7o2`o8ruF=+J0X*A4)x);5NxHWGt5>7VOW8SxVGQ0ZgwUKtgIYHP-$o%U=?xxJ%$N zM<3DfAjlM8*pNr@_V_JL8%XE##GpmIq2~CXxLB;AJA3ABUvJ`ZYX(VJZUF}J@tQBh zz5kw{f4>vh#7A2h_gaZ#D_BC^gO99gH+~RW1?xs6xNi+U_x-mu;AfqW`mU`_ZI}tZ zhV4p~K?x>wv+2sjVfnH+XVXI4)y0gUPc4swdGz4)bI9FynhDh+m3;yIAkAuas%;Jn zSs9E_Hya|Qs{MGTSwY{3-|U7WW}qs8=}07e7?GPt!r?Y6LEeG zA0Z|=HN4r{Wj$k`uXw)F8&h!3xD-av_tgOikEc%-&m?{jc)o` zd_c%3JFHzqrk|mjw#jJfJN7Yub1fV`hF1{vep2aJ(m|e(0eUIbK&z%|5$JUzW)tJ2g);_;98N5)|cHswF12V2V%~&>lozNL!0IV zkP;N)jNPq|Q!J@eC5JGP4gGJ3xhb}xqyr0mjvfH`KZ{{qZq5tSnhjvcN#^UYO^ z*+S?1^ z?Ld2Jy&kz)p%(BC}&uV=h zf~kGV)D*q%%lNw638%sRC-;odaV+oBt)IPk9KXS!V->u(m! zuw`gm*1y5`+9~4h#tcp#BmFsu&7|Ume;anf$ zQ%?N{rjFX)9_0M3Gi9Ow)L<#PCsjv%9b#T)#LZMG$|m<939k4>-VhnbRUdlWC5Ty= z;B(a`xUvt#ep0#>zq?U2N+RW8WKR&mYH_m7gz34kR`DvnL zcr=Zsp5VN%Y4Y9wwSRyFM+C=vz&mnJh_WonBv**!40Uh9a3IWLTt88V7j;x4_ zi)c`=>NLTG=MOptS(`|`+Q6uXnp@-3&bQyqjQzR$;4q=qr_f2>M+ zh2!8wBKQi0GS&t-LoX(lkoJn#{uTr2lAWVD8aj)R|fnWotSm&&f<^#(s&qwGn!zLObGV$uWsyh`oW0&nR`jV zZPo(E_L=4gPdSuDn^r7QI=^UPjtS`aj$);kh_;O%+dcU}H)5vsh@v%8js2Ue9kN4q zB+cl1YAM|>5pYG$mamK9elMhJ-9c_M3o&|}xgFg-)&Ci}>$UGsKBgEUZuxk9HVc)= zeE+MUy&JLl{~ftwA^BgCJ7A%CMbmNXI9P8c;n1^W#mzx2;So-0YILd)R(bd-3*33W zs@--U=DElt$nMld22In^-ESo!ZT`MC(!~<`sJUfzYc|>y9(X9j(dO{8w8~Q!q4{G&E!p?7HA{EN;aNWE*WqfaE0|cx0V+$Jxz3x3Ow(7iD zMT9G^a5>btM!3u&JHgpJmUi27&#pGT^|4p8A8Nht(gz+%{fX2fOm-xQnrgR8Y6tc0 z*c;uCHk2;C@4x;^wS+z-o$TjGNrC2R0B)so=f`2K9pROnJF5zU9pRIy@MEbnobpRL zai=3#MjG}CL}}G$^HTs&;PO_qFRPf=WKEFO#_IZK=oGgrR@ zeYu;aRzYUQ%^M9qluq(rJ%YmBQnp-(8$_s*k|J|x)*4ZYVNCP{#t7vsy)ad1ZVv3wm!z?5%~6OLzI0kG2*s0D6>&B)h3t<;>ry@7`0ewj(N zDV24;v&s=e(UXGpqEYq4|x=X?&cCO^@RE19U+*$V~rca1NSW>F`uEGKd4uY zm_$#V`1vNhlg_(s*NJF$67SC*@Qn_BRa9T8r6Vt3>z#JsWuA5;w===_lv34mH+D9< zF@|Xw_n=f!5RpmF(hcqYD_nEzu>6m?Sw2o+K4_hPqbuuf)TNT}&%}dzjnKQCl@G^p zCsD=kh2WIpEfRkI5uv4##cYqe@R$D)?;tqU%0t#bu#|##%r@rU}0cO;nI!Yh?{QpDeakrOvc50wDG8{n;Y5u z09RhOIeokq?b+RSqVmcQ7r*{Y<6Qt_4NiCj+75Psptl~^-SUWNgQe$PCGO%}L&h`U zP<4mIM&2bzzkf(Ot;uX!vK+T~sNo&|r)fQpM%zP!eVedI&#Bmrj^~9MFQ>D;p`|PN zZV6SlkM27gbv~Uxt`wM(#w)6#t7FpYko9z$cq2_JT)n3VRonnfZArdUA*?tkw;J|{ z$1C4e5>d_Oen>bBGf}UP*vvgU*bYpow3C(2=Z)cz2Q=SwQnQkWWj-lWjh^FJf0lF= z+7D6HIR|ewnpQLZ(GFF(X5H)UwJL_T#O4nAtSenqcoiBR#?m8ZA8(M{9{5d2x z`ZR(uC8R?9n-UD_-cj_$p`=eAQ72>VLnUo6u`vf-bn7MW8mU81)vcg;`ONhh!pjv9!0E!N~sI9WS;A;NR zNc+|2pw01!W9gd@yrAHnxqEXj2_^wxP{HHz4{jX{hrw&!Thi~&9fHYo<&8TpFM)x> zfT&%U4@?5}bXtu?yBwg5EWjzD<;3OubwI6yhPz9ohRNGz)~;P93wW{qdMhkZAJZKL z+*-O7Vd2cGitDhAjP&_RF@*B!cZp|pc-CPySb7+QPLJtiI31kMM@!ckvTG^>TJ*+U zCPu0CWwb{OzQBOCXX5gn`{Qc|q_idvGw8)YY@vf}hQM_$BnQ-+^48<2{g*V_;RC7G z$q#;F`E4xs!>I1%ws0MU&}_%7)kQe@Lp8MP&c zS7&W`8UDVMf6%4|li}OrUudE*oH5#a>(v-4j&Sj?{5)goh0A6y@1`G%p)>zuEuI@_ zDhl^F+~%iEo*`No9_4U(X*U&;SpU%-2>CkmvLO3dV6J;>%$@!d-%~Gl1^XtGQce5v zz?-+?wepw|3yB%sXqK*>>swRUtJsR?PlB(VX5F5C>8*tKF$Ry&QpVWMO+n+6VLy$t z!s^+X@(SIgyL`(Cj%eGEA@ZLUg88GF3T&Vu&ue*eZ+SbTM10lT7S!vJBo$+EK#{qx z83O&3XenNdQ}uwOD-8nw~po7ytqvJ>a*I=~cLW6{wOMC$)-B>Tr9*EDyditI7TCUY^LU3C9 zzY&5YaS?qhtAc|o+VXeDT`vK5&Q8CG9OgtumeTf*_d&6~{$%}r&*6D%V=DbtX@fba zU9USHoJQ$ut6o-U>aKL$1ml``H?Uu-%4hiR=B6~jnxZsOfBy{#)+YRa0fLi{{{Vts z1GH{yep!i*UPs?0dC?RQy6)2r9<+brf`Fd;Pu*^Lh?z>`n6Gj`>) zIGSy-Eu)OM^m9yMZ|{6Dw)3^$ZCHIv@Dn4So(h$|Smyw|vKMaHmDOO5q3&r!1Bqv{ z+)EbpEAIVkptuzL@|i9&#qX@J`j+0?%aAjOn*lBFd|lUd&%Fi#RpG;(M+3F+*zs+; zzAE$~dKz;@el8V%A54kKi5*J{)%o@DG`c110sU|>bJLJ$7Q*~{_jvKb={3Kut(;P~ z6r8a-71PVS!*;H%CP$^0y3d~5!+mVSocJl=ni&ibFVcbEi z$G8%ET?KcLchDO6OZ`jik;DG3rgj<6cBpDB?AAYKdXkrXSR;~Oac0<@FLi7Wr?ZyX zmJ{8O< zQan42t0Hn+s9*inGoqp_UBu1nI&eSGWi0w7u*Dq(??SWW(-TiugWgIiLJiC}(WcgN3jv+}bkd=GblV@tZ13(9fzd$GZxoEXi=bqyW&ZtBF^Ff+Jc zSTn!297~I%i%hpthY(}cOG9Exu{H)}>C2(v1w70cGP<(yG7_8}g7=Scy2l-iP9NmZ z@af7Qho6Ip&KElzhrJRxCA(NZ%wE+u))gv1Wo~`>Y#%4uXqNgo8dhg%V49ZA*nHFd z2_1h0Z}J^}LschXaE>jteBQoF^yIiWDjv6-G*T!9d-dChzn|M8{OKSMf8*kPJa#7F zfKsKTT##mu75}h$m|NM5+BMkxm9l%DY!9jZY|ELprV+-_X&sX&y%sW%rd(1Bfcc=+ z#D#Ni<|mDBY%w*xzx&5-N%4RUHPYP9p@5k9QuHsMn~oNBxD0-Go?k^R*Cgx1KfgLN zPWKvr`(N0>V7OAhIGosS-d9UAsb`N0*rt{agLjt^8u=Fw0qKP?cYUsA^}WjbC)FF? z-aD+kbPVh=JXMSIcvzT=Z(@ez-I;1A*WHBDk!(2aX3kdYtrj!VS~(%2!Q%_lRwc>} zM{Z67t$E_#mjY7n6#(<6YXx$P&c`bbq={$?gRmk#3%$WH2|X>g(pcYR<$eN}yZ;Mz z5L88X5XlO2@i~~A9d1p#?S>t6Lz9&>MQhXz3+^t-g#U`LUiB0oO0C}YV+?;)5`GP# zWT53Ca$hX_0)>^fU8Ge<+aGx~9uWB&8+^4AMAix#VuNao}({>>Kt(ttc(tBhPAAUX=Yl1m`ZD@Gyjg{8$ z4CQas^mGw0P2)}MNpi2pBC!bNp8apF<*;%bJd-u_(&}B^iJUXod#*o3P3O^y4`mWO z8)}`I1McoJ&xtKCEQ(fxqUePq2nI+H-|`Fw16mIC17;&hVYYHS{4Gg!NMIh%#2py* zr=e_ebMmt<4enp}H@f;W4DSDTfKa69dquAYSIakcmgz{l&F^Wk!ZAD`fXFN7Hqzpx zWiCqD`>(v%Qjz%IpnQWO1dArsrb;L*&14 z0}mb$GF5G%dl$>@*F-eyj2=2GD}!GIqj&eP=JRLOU#dBbX}hV*p16qA*0O!e_*XLM zc4_Ih)BFmjo8NF+r=f5kB&u?^2skl`0Bwd*8&)FAmnhXjYP-n-_Fa7yj>($*;Oh-4YsNsK*EX0B9 zIDWdC-G=j!OlVXZv)Lhb>v9y`kw_wDGPF}DjeFkHkL2K%xb~c?0Wddcc%0}oby%x+ z@;onIv90Sq^i-~+U3)c#te~N}R^zUMkq?|r-W~jvvP3XesLoq!KT%33FvL!R4&#t_)o}H*S86AvjaTk%$aguN2=P<-| zXIpuy94Z?}R=P=O=NDI+a?QMhYN@NNTRCb-I^kK;K2}+Ax|IN-5gsxEQVeMP$8s1# zNX(di>|PI0*znBW3eOt-VZqdq+fcEIZBZzDF3*hmm4}E?YEZe<$Xrb`WSC5<#*j85 zz6F3>7xojoN>ziZ`&<@Gs1vRyxN3Rp09ynvin4Wx)6wNA_y(!>rq3Q6(kI=QTv8Oq zsH_Xs;BErj_qnGqI$lh+$!)JOO1Rb4XwzU2x6bC1@ytzVYy&L4wiK#S@5p<(o^3xJ zCRj80I~+GN1R^jm&ED&+BiXIqtUIW%oFi1F@i!UGR?_qnjxD?#IX*Pr>VODd%+uA( z9St$j(a~`~pNE)F&3=(|6#0B!o+Dp*zslr!BcdJcuOGbsWpJSiYuod3Mnm8FDHh=+ z+kZE?d-^1!w2+{{f$&mK3Dq?o($&eX`&~gWMd1EiKh#guK5;a^LhrUrQEJoHS0o?z z>F!)rewGHGMrTgW4YUhyn9nFu(ugjAAiD}#Z_jzthd@-RNsr5_f)j?vU_tVq1Y$PL ze9B$FZfX#Xo2@MJ6)awEgoaLzo#blS3K&5T ztIiX=N5gM26KM;}pSDMX;{%=A+Wc?GuVxwJ@v)j$fJGe9SKen#UbG2uwhV%mUQai@ zZ+f}C^)1b{Uhx03!8ayEC-b3~XaZ&`wP)UY#tkbP>Ivy{sLik2^LB0FQVu5C#UzxD zji9I`a44S7lm(TF)$0~W<$X}4x&-VsPGiUO^}H6Of~^+Yk&S-ZExMN2jmvQ9q4$}p zQO&$63&rvtNS!Yxe;MS<3&LgloLa7%ezn$}z(;H~QJuL26@%8mO`4#j|P0o<6AM1?!O8$?4Vv5M;LoAWS zj^>LO?1jVsn&vhC{9~He^jvYkOm4S#^G;Z$BkA@AR~G}egMz*DrcS zP~yj)TWMFI@q-^MJOBLS`F)3urtnxrGN<(jA9pT|m62~xq26J_L+2H&TJSU+1D~+K z?0)^(G|`utmW0OVaJ~CRv;S??Wl#9ihz(R$o&0>$_;8^-xmz@a*=-ELi3ja|^jgWvGTy#(+u8Z+n$Bu%B|yT(rG@2F5vSB%<`^FRMok0G!54 zB9a**Il(wJVaXE_)((Pe1NZyC2#Hdgzdk8GB~NtN8<~|nWp?l~GIudzr2Ws-A`UJM z((FC=4898>GBQE7g>F>H(%em@LJB!?$r8)JdY9GdO6D=wf5|O=LCs?<8@U6INm`{j zOWnqU<2t&B%PuhsfPoWza+v2|v9#J{GE>n$uv>kMF@YEvmQ=q#H1RyPlKz8J>EiiS zrc^A4m9Ufya-4EpLasC&f*7P70|TQoGekhIC@AM+(T&T;zj7jj&Jc+Bh>E`XAU$BX zD007!mZ1r%Ke3I=VNmoFmO=ZlKz_cuoM(RY6~t+^;I&er-Hqns&P8Cyc=r6KPys{(srIWf6uXJ?ddu%*nbu2 zKxYVyY}xicoyejiXhENUOT{9rvK#^|$EQcx`MKcSqHIH_uNrpZT_)2TcyMz`FP>`` zVJu$z;o&I_aWDF<2qO7g7pwJQy@wBF6*a}`i|XQcdc}k+5LigrKX#-fjSUdEnABjk zwtsk22EHkAM}X4i?4Vwq=j2$qUE)d;O*A;9+9t+$-|VGjL(_xz_6!m;rzk7 z0A1C9hL&LuWx|3yh|6EgmZg@LKRMgCX&>FS^^>#7E~bsQx)0N`TuE$01nYa#kL#(s zY2o%|NFO@2dLqWg#>!g6tjSl4f}jOoU`mRG0T)J4+_JQ^)T2`miiy5AG&C%;N>F1T`JIPI4fBicbg5cBAM2mKci7omN7k)4-{?c|~Gcu5Vt_;NS>1QN2H2Bg+LU9s5aW*{Y&xt{>pukvAohVT9`%^X^p zzk!bVpmYVhZGU`^&Rbn*yJJ08Ea$bpXQ<84D1`H}HG=W%$LR%1ZM-tG+(l7`#!^`< z{NrjTR|lk`$NuX6C3H-QVE*E4MB_&&AfGqP%Qhfn#Mnp=8!h2HghZo-8jqyj(MVK( zz*=`qA(Ofy#=1h!j_+s}q78OKMf0#Wyesg9N_z;QS-`2r+Gj5D@|^N@C$#ybvHEsq z)0;6kc_sbpUHCd;_Ymg(;_)$3EM%b8PZ|Bt8Wt*sUBQ8(CX63GI>uBZlu#UHEd^WK z;oL|14bJG1XKY@+kot=Nxqvkn8>2at1=Lb=xY`Biqa}7T3eBIBrL2%?M`d?J#lvY2 zt1_#W7(zP*dci)v-Vb(E^cYWxg)LmQ7Dwq8NL;SwB~Q|-i23vja2akV(o)4xcI}gb zNV_=`>3@R=g~N~z0PG>66c@vG&!A6v@!UxH57LY|MSiYOTgu{x5)~LNG1;6YH6b(H zk9p8EB_`$<-oKG)g_!6|%%EIuIf4R@er^JVTQgb`fM)r)HieK@`>V?VEo>Z(?cVZ$#wHfH)DGJEI?f$4K0V;;3g7!kV!hF2 zi>3Iv9$e{^sT8jzne`C1&HrD{2nI$to#)8YU{`tjw^bjYmRo}VO2vx|LzRi9N-wWw2ZzjGjB>fbshY>-XXyfVckq`4L|*`MXlXDMd9w2WolM zh-TgY=~6h;6P^uTr)P|4r7vVFeNU3Wh=XQJCA-STdz3|M5TW&DK-gAqBTU|jY%Fn7 zNtBep7R-9s`E#Uy=qfda$42Wy=1~cbGY__&*s+t9bOgfCadmBLreZK#SH7d3o}LFdb9rKqtv+tVnY~il(V$ zIj6QjZXfnG6d!47&!amZta!@tW<2G{g}! zQ2Vv;x6OqqKW4ptpq~b*UgN-c$<`r#QF4~k9=w5h{lzoK7%hDl*)zT|DYkiUl8 zqPDdL++xS~E!XGaOcCIb4HqkHvzF%2bg)^7pz z|GZ_aJi3B^VY4e!uu*qiGxAB#0M4NNjWGV51Se8(LDd=?K~y3iYA^q3woMNK)hm45 z`?mN#EamKel8-^k!mcV_a0aH?^!NMPkQU3@xA2Iq9}>L86ZiWgKc6a7>_Y#{DC<}T z=Pw*TG}wTmwmmvQk;S9_&^AbvFuC!cT0f zS4BSCVl2gyYE>cgcI!WL3tk!_xQpFC6R(lecmFOeMXQGPDoO+y((g7#|7(`6rOv)z`@{_svm2N;?X z-Aa|hZmkxMMc?K+b+pXfXt&k1Hxw;i_-}*Xr?&}wMwi$8<};V;yw+3Tva4ZKjd7@j z^+{WCW~o5AW15=+`4(EkeH$xM*$j^cu!M7`N`5ee^porBu^|)g`5WWBw=hEa?Sc&{ zj9B25=R1OX_I4NYXivANaol<`8d}g)^i_+h2t{b#k9GcL#z!|7u}7!-yAfBf4K_n! z)xg~UXs{+=8a$NAWkV3VuW~R8*fJGD3|0^@#<{RQtx$|rx)eA&fBw|($PJ5LeV!xt z{eU@(+$owohbzhHd^nqDY;7fp2nYIZmjI>Y&%sV}-k;JC>8mMaF>Hu_D;qnFJbuLH zliNj?{DHvyC3MshgTJHrn{{FO&jQ8a|I&NQ5MttnEOb2r|T z#`8IPtzH^F)WUs(KcBV1;-V@deamz28AH{dD4m0j0&n4O-{;D&V`reHVHN)7v$#{s zck{#VKtRoeORAJ`MjtYYYf{=xOTf)1CEevg*CP!R?=B#aBPJMIpDG~Dr5pEw&MRa+ z)0NhBMsaeGowZqLkecwNlyOeNPeKM6q*0ZAQZGViDWxyY$d(&{=vJQ&BMQJ=!JvZ0lZ&u=akmW z-B$G5x3YPC0V88_l8xs;Uzd}k2sPyW0Nk}3x-%x@HXCQo zunXhGARxaQPLh_9@ZTLpsB`Ts3T{PkK&KW-r0!ZD zk@xEkSbFx}wT!sVKg9=$y7(#|MJ5 z2aU6*e9ms2fc!J{&hNN%$FjSe21aU9(#^d%y2^nChtit*)qInX@|u=gPcdCPrl0d< zE(f6>bZ7spi#d8%gk5Fwkk8$le4(O{AO$O0w)a!;PS(RJxkY^`PC-4bZ6bq;c;;wh zkV8F3_}L2e_urs)t%Zq{91&Y8JaR9F7lSs#4UXUr?Oe6BZ;4*j15jT4<3=WfrQM)2 zutS@~EBe%VOL|Y-42Nw!xb^Dc0s>m?R{M-zcdVa{tp6zN*ivSg(Ku}XshMqXmh?4> z^7<;-9Hss3P0Zif#fGNWtA58aj()nD{jRh4w zTbc(OPZD4G{H2(1(*YXgfGFOWli9L8Bo78vy^Cy7%%YM*>T~NbD<0_S?p`=Fv?Sli zI6e-&;dOM8w9rPnbMWc`Wj8~n=7`-2Xe`szf;c&f7wiSLBaZ7Uk?s4z$YI>nZy5KwS< z(BjuR1nK^X76Qe@H&JtY-u?nsBHX{mOnIZ!UpT#P7{4aK1}6KiyX^n2EtMsh)1bNO zdRmIWd}df)68YI?_heE0Dn9O3tDzBMiFH~RzW9=rQYvUmzUpH0V!Idqc#R?6sIy9ONvfR?gF##R*x7abU8X+ z3b|vqI_rMwC-NKCSAjaOu8-V;%M2?l#-8mM*4?IS143U0J2^}Xa(PmnyJgY^8crW& z+vN~XNsAd@lx`OGlb5nd0)Rp;JH>$@NJ9+m$gL@nnMk_}^68Tsob@;Gcm?!9zsOZ}(-xYxPz=-GYK@JN68k;N?`Oi?*gf<(0I%w$8Ow z)i2R&%6Isn`-a#V>1=mHDea2uM%Z*08e(I&v6>j2DT2Do@!RxXJ(#%q@2&Jd=VPJ$pF zPyQQTYo}~GkWLqjtiL(MLK2F(2v#4n5Q91Jrb|t7XYzgss(cklMAtA@LJfwUO^>0D zm63UCsZMm$Opj=m>TNpd=ZLO3++YZOPglFV%SI8EF(N1L^A|p4{)U7`QM75G54^CE zJsoEGwj}2Tn0Of((pFYc(n41rr)*wEi2EJNh3_Z+=K>1Q$ZT}%k zp1~@vWK;%s%X@_4H8t;Wj^5rYOkmaI&sFM$e%IFDa};vl%YhD$uWSyV7!={-7w^C*h0Bsye4!r-%R$BQEGxePE%PF#?m` zdsojhVIb>lUOpU{-ZGvw-llIgUKUknuic~;9DBL^zWDm86iJVi_T)X_8IPfoBiAzJ zyJdZ|?L>8#Vw81nb$bSABfgrkb}&gpN9mNS^3EW+o5klY*0H!QbIUSPcVJ-UdqcdZ3r>z*Y;um-$o8J=Ne3=S~=lHFXl?EVvKl^Lw3r7}v&U=7$;vdf> z{bOr|MvAa_WTjrW?PeX(x>e7|Loo|9t3#%2O^g7I*RL+*erf)r7^QxqbtXYU41>{P>CaOJm^VQ3BR+d(Z5zY zt_9^|c>D~h^ZpFE+XH+-eDu}9nYpWSpW0lY3{y)wWN!&n#3&7d8ois(aTTeD z)d6q%(>RVt;haq<$4jbayoXB!1!ZH}a}dvAB71&#l7JVmtoOgW?b6(*#MhH%YevF{ zqb}T2cIx)p^uHwZx=Qh+ciul@3+|Ys zta+RK6ffSW?T>}{80$DHV@|6wA(#HgCdw z_h_17jT{g3K>U)wcx_1YJfwFgp7XGrX!5bS8nA~};R$5(*)44H4!R~Vw^;P`<<-vY zPifsT7m)1#$5rpJPk7zYEq>qO2Fs+yTT0()D}ySyX!(K!A=*aw@$r;Vt`kzFxL5S* z`5cSrIJ)Ay%DcFXNmU5!gAlsXcZZJ&{t!^w{dt4tu;;lDsKPG!I@G~g5ePP1Kru99 z-kZ`tuaFxQ*L;v6#TbhgV>|isvP;T-zh2$ubl>5Av-x6D#qKg-nZ@C#<7;SJCYAhLPyjF&jG>Eo zz+Ss5@(k{Wqiq+wnU7p#u&pH!iz4cEjcl{u&S~6Idz`7%rhTwmL7mE`=&yQkER4>$ z_KP{YZKHQj#FE-$^`LU}y?A*^ab_CJ*l0g{1|KK)Ee2+IcRO{HdFU>Q6)F{|RsK{w z+fQmPH&zGzJ*dIJkd!;15Qd01ZpY3uKUmerhK9$DY+V2wq|{KJ38U#DxyNl>j3(x6|*76?u96X?vpIhOe{M0Y2)F zfoc4yx9jd*Q%QG2kW5tC2M1(97~S$2vPuksQe)4kj zB|J*)drVnE0m>eF>5c7BJKr|@!|%GulWBXArtAu7zHvaJ zdV@kKz8XStj>lk)U!Qnox#V#+Rg@}ktb96rF5}aZ=1xNK{l(x5%33pH^=$8QE5E1Q z4QnL-4V?N;qPFiuy*(`=3b^+pndhw}McCrWVYHd7&Lu{(!G*`xEO-8=zoKQoVir<9 zwhT^dZr@o3IU&Zb4OlIn;gVU@Sl+=KC{EE2_!DRbzK%cdK|klbWI)9l7lq<)G<} zi>_y!YWYTeq|Kb^e&_mBe_37cdd60__EUBxx6Yyoq%OG&_;^>-AM%U#D+9;s3&178F%>5nwL|V%akAp^BlNS$<>uTl1q|e5!&UhsONgt9;g0Ln6AlP3B zg73>q5Ki2y*-|%bZ^D|q>I`<;US2uxHJT9gwRPGw3}kPwBB%x8j(JTQs7|A1&hlX^ zWaGa-BR=+gc)suI+U|txtDkXcFg2>~&(Kw^)D#G)58RkmDO9K-IyqJ#p7Xl?cPqm^ zG^Zfpi2M|QQl=sk$}NH_+z_g)$Q>hesH#L$I82<>z?7J+j!U6(rCI%Uvd8o75Em_` zYg9>c#>^kFK)kO0We)14{lKfvED5*I5YM6OkZy=TX}Ah*Ta4Y%CpxQ(HV!0@^4IxY7<0lo$L9o9@&vJ~iHFc&tlR&aVnZfW;TDyrB{ofiN2u z)h7XlNP#Nj0uXY0%9W9$6uFiKY9qTmAhRcF#D9r6H-8 z{s=52VukR~3ZBgBxHs3=GRcv$w&paMy%qn6$JWFH&VKC| zRL_jb_Wj`{Ej)=P-5kouAHQ907vJR6eOt&4;RS0S%8f>YmMhi8dr@hY{eNhAGdY1a z7hBh}&pljgv$$Zb8|N~syORM6RPcb|Q6!dwx3MN|g=`dQ-O!mf|!fzg&Z*%VKZ7Vvd zW17_62}BPj2@Xd+=RSPQ?mv5R>r))U^aX>ySO?Lk@l53Xw7)_q7EZc&@f9Y~e48zY zxmjdGPB$hj3TAEezxL|gf12wL7v-Y0i$kvh_)PU60dUHk6f)}7bn{aG9deRDSVEF@1 zZrAsv`vBd|Zlf8kF22roinXq0T9xvQ`CE>s!xueIp8)&W=uYr_F#zY{&0o9eGl|y$ zO0<2?v8Pu}y4FSz@;Z%6_+xZx-eE2sDr=;0mVz{ZiKxoCD>W zJSGMITDE6_`Tcge{B_@r=F(i+dvJ7{S!$_-1{ZEZ8pr_H_r1DN<7@5fWi11yqWgun z>3Z$8=c{td$}pjgPPhBO&xchCv<4suC z7tc@du_VsGH8IV2ehPBdR2l4R7HmEaZeg}RW` z+f7%^14`TIgCSOR+0^?*&MmK-J80ofYIey?57n(S%~jI|>^7l5(MDjr8h1Lsg ziL;}qRib$&QpEsr^mt2VbmYv9HC0#bKmh1(WCD&Khj04J!4P_Zn>`ZgDt>8umxm+5 zKW|s0PK10(Z*oP}4gB9AfdENbhrfQ)#k@oMT3ELN4ZE>@H5|w4a(tNYs1q$rz4Ax! zAkbYQIq63w!I9IlqMt9s=EP)iH5PatbL9qOSGo9m4zx#-VPhrw7*Aaqx0f+gY^6L4 zi8(^?&Dk#fjLIT_>!gokadY%(4dq6${0V1j_FkFMUYxIu&TiYBoNE`?Ss2`@WBC^` z(-@o7fUMP*k@$AOPs)6FN;f(hXs?vl(ZIixrNO z39%<+jV#;{!`o$qyg9pb+}De$vYTghzPo=AP>VH1R=g?-*bX<7seoAxH8D`BHNDlA zVP?twy{Qee==Wt7>3T25F1ECXNpqeiD%DGRbG-g0MB8if&9*l9JSrpA3p9YYuAQI- zPt>hiHf?WrP%pQ&))Y=+7$^K6za<>?k@rD*uQz5%2w=c12j8Ovs$*kcZ+us|!CE*M zPLwe{go$M!SozOA1MA0uj|K@z^w47nS!Z9qNG0i^fe#iIfZ*Wcmhn~ohelA!cH0-b zPhoN1Ox=EH8{(yiBja!4HKJk=_T1{zX1)H@UZ5%_PBlv%u`G%f|LO z>#nmvD*0!N#b1jk%!D@2!RB4=c3ND`I8`oq=+kj5E*BZWarhS~4WaLfe8I0d{cy8kIb7AJCW z4qs6{Dg#{LWCLeTf7sSzDQtV*u3OrCPB+TKp1gSVt3=EE+`q_4K!)LQ+mc8YXLZD3 z;TJqq#83e>SEg&UXvQ)yQISyHs$5-6hxy?p+6=`4pkf!A86>CwFvA3J)3(aqTtqnY z)Q$5tO}BkldB;(*#XboYE48h(Y5QIuViy8wU{06%wU+bl@kmwO^x5D-OS5fCGXAM4u_>*1m+NTdPh+oFa>VvA zig3p#8i)Dq61NapgWZVA+$f zzu+<_rZ!CM-r^C#kA+QT2sn3zSrds6taYd=2}ghu0!?*D8y5|x+oQX$hT@8gs(E^h z?DSucJ9%t}2291`V@vKFK6Dzh+!StQeHlYFa~q2&M0Jhiz#SVKrHKe&W9~ohKso6F zh|Tx^#b*+71!XAnCBL?59smAbcA9#mg+uCpE&A6pJnRY|KL6#X7VH>fyV@HVVWFdS z-5;wB)a>l4)l20IR*A%PbTX=uN>=(c4A7EIRvM57h>M%t@8S>`n7Ru6ZbDGoZ~lh~%UuoE;Imwu9KJeE< zM@!gG&3wCk_UMIj{)?6iQ^V)@!cDYd>~_yTJWzbfH3=@ezFcO?`or6!!bLHrx9V>e z>rG-kzMCl2CF_>|8E=@oH@)iUOd>VJ+i+^rZ}rA)1j9J3;0?9mdoc0rn^byzQqSPa zLn4HdAFMuc^CvcFRxp7uq3fX%p0Fyp4&d%^#G@N3B*v_Dlb8JkQ;N=v;l=|D6e{zY zyiw^J`=RvrM__t>T_&=H7<@b8kfp_Y8!3=OtoAFH0IvWiZb_XWO5eN=}3n zFlf5*LeE5lIlH?9Q{BWgzWTpP)&T7F;!XI1P6Sk+nqtb0%1-Pi*sK$=aNU`kj58Ji zOyVkQw`GeCfq;B15%C=EKl?R|Pz{L}1?~s*0FP;*M4S9sJZ~Y2t7gQLlvz8G;8C~4 z;V<;lz~3J|{Q8TNr%6!zK0t$o#xXoQ!6hAh@goIiI-et$8TtYO0-m&4)lqyL2ezqR zD3zhBgO}ZvNCWxbc9meV^r^$`-zI3U zwREnh!(8(4b}SVb79fK*b`YuRgBH1t95;BBBK-NK0wCXj+DiKeDO3awOZ}BHk9iVZe|6CRUXkAKZ zZH5cc@#OU0#KlQLuR6k`K@16B8r$S#6^{$D3r#v9QoJ`N; zi+?$z4=&zRzc(HF^8vEexZhM??=6&0mdh51DQ+cZ3KA*T!D~0OtEzt!QvS5#4zX7~?E8D#5cboM(aCSZqe6LbS)>~1 z2(s@OJ3F+nSf#R3RuN0O)0AGyMf3uG!`7DEdTGdhkdZOY-<_1pp@Q3c|5j+Y?7+8K zwH!o`FyU>g%akf(YHEt?R`#a`?60=CgoK2+xV)v)1*^u!p~S{7Scg!Qsl;jVGI0n{ z<`$ob^oj0(R%Krb&wz3iYowAqOfFnB@oUo2J*}?*=M49>P&K|;q^n}9r#S-bct=6* zBs@M)`z5%}g18t@x9(3X{$4r1uV$VRm~prjJe}2mF7q~#0Wt};Z&X_OV~EYBtUQnu z11CeGkD@%GRS<$i2QDT_kQ0-Hdf)Jl2Bk2dSVDJU4f^u|Y ztKp?K46^OwJ-)YEq)b62!4os3Ro-8;^vxOeM;CiiQ|sUhEyyoqGR$ zDj1kE@+oi8s|E}_7|`+u#9oFI%AisUamLbbbTm}7MHZZNqH&KhLhn{}a~$aK3^LV+ zYMGXynvegv<^`>A*V;sCm`cOMk9sJZpYX=@yz~Tm)=^8#iLojhPsMwn8IrB;EC0B= zs>E(QR@VJ9^yEZ9js_I@?=4^y^h@?_F3ugkmcPD%u~QeO7sIgF_RQ}>5r4&;Jo#Gq zVDv04mU=Fpx)CZRZ`wJsEBh}N@#h97h>d6XUaq7)VEOkiv&1=L!S3>9|7?czenQH= z@qFA$lG;Q}yo(|u#YJZ0+-1!on#R4&AW3X--9kpPll-4YfZ||{b=dD~b;|s5gzBHsieM0arysVj1H&|&i$j0amMju zxyE8MeBE!wsID4G`D!bLl8@E=;c};h^zX0y(#n^32n!=@4By5rbqdVk05_)$I&jSc zJSe-gN*>D9m{rWI)%X}N6`B^@XSLR<6JC|rG`>{_d9*@rFTn&&7_O+`3m-ryB(&An z)YL3hDwR5%6otOBf36&kQY-K16s)mA4j6DAIzx?C>ukp1yiwg2_GkHV5h7TO*znyO z=USL(QDOR>4J@eFnHYmgj*f=rY3&V&su`XAU)D)sY~H!qI<=Es;`?!1fPhBVB(q9> zTsb*ce&38igBe!9?>jI>{u@)MlBYlCMfUzTIR9xR=7IU?^=6G0GkKn%h$sSO=>K?qz-}u4!e~M@rKdlEvsuop?&-nAA~iWajjVVbogu3k zZCwNkp({!w2iUmZje0Udr4EUIyUo3U`s6&sO_=rR9+*L=yl2)f=MsdV%iB~|GjSEQ zi)^Ajhw>09bkUU-Ph0)#tFwq}z}aa$@S)AJSEvl(`#>8rZlp-LYW}}tA%rK|CC9X~ zw#!ZPOJBt&yJ0mnVYH(xwID|3*GBH!(0>#)KhjbSi}-7<{Xx!z$W%P60NLpm;nO-6 z?(CLn7SF^Pc#HdKYgd0hjr)Iry$6aB}4@Z(`bn+}KudMm5XX#2wh>V?H;AZ9ysP2!pJa%$+2`aKb)LQl_bYPn-gkG^g=k>d>NLog zDEUnFHBBVu6G4oBs7+`;4x7|p?t8|*O^?gR0je?N~!J?SvJvOMLC9US+ zcuZjY58P|lswWd?-cADE4I6t3#8U3iVxI`Q6R2)7~xW#(n;h&pN zSW7FIIS1R@9FN{2tR7Y`5VXK|k)$k_e;b%ixy-*M;uDj2`tPc024OSG}0tq}(w zYAs;WLsIv`-p>j~P;VfIOW-xi9}4+84&p#J7$_4ZXKH=dZ=oV!{wMUiWjRivxph(A z;QJO(@mOo{L(8vtlMi#i{967+*O2sNIE6NT?TF9Im=!BySWw$Bv}B=nVvT%&^Qz(d z-tl^dQ=u7y?9ZP@YvlfO)(3;EzS+$TS%T_1W{QfHdg>S>=<(e|n5F~CvLe<@Ey0MI z1Fhf1Zz4o0RXHVPY^|Zj=a;1Pd4~`D%!z=5S4|beFsrOtfp7XRZO1x3uPcZYI(H%i zgOn5$7901chBt1`4!Ewn3m*QPtvrFBK0mTr*hlT9j*iK~cH1!ShnN2s*_cEtjnx*8 zuWf0GI7)L6*Y#i!1m418g#L(~Z7S3=_I|vbIrCF0TPVv%1KIhH#XK%%VvE+!__wUX;`$ax>yv@}AL%4s!P1r53F5;w#_f}&% z_+!>{P{4jfA_y6=@gv0AcE$VOh=%Lwg#ciS|4?32!R>L#Mw}2QeRZBWvXBK>{#aeT z?7Zq=TJ5Qy>hj!pFDQSm`}$Nqc0(X8DtE?ynpoV%hFZ-%OSUzUcGXjhFvRv9(0Sz+ zObNz=p2H|NU!-!+4JPSRo`^w^%O2J)_qy_7u;^Ea*|ne=X3)m&d1(}Y@(MX7hCt)=kkvfi?{hK*_$p?#)z$fB#d-EyH959r;Mhu zjum;5UYNDZYstPI)oOb1z7WUjvGU0pvrb!}i`l~9-n|O{P$$2Q$?dE;p-!T*a!13= z)I>*j@gAqkmihg76^O|Dl&W_YP@R#8Z%cqS+j2O-n{-%G8Mw59=hf5eNw^_tQT%bs z!$yVIPw58K>m9s}JbH;hmNKJ;kFtMTLX!A5(1>jssv)I2a)Hf6H_W&D_N_K=Rd2|FLD~(HBuMY>6lt#oqcrH0;4dIOy9S>?+=~29m^3`rsmf)gQKtpK8n!mlK_ z*&54I<>ez`(8XaLFgfO{*XjSPFES-yInw#m$l)|r&dDW;h(r5iHM-ZdtZJg9rIqLW z0?k+)2LrPg55l1}#J~%fi!qX?E|!&h57D;J?JXjBbELdMWA4AC1L;?`_$~um7h|(B?tlEl?e0&MyOo|N;RJE3m(&7%U4jn_Ob4E-|miFuD!-Dvzw;f^jBBvzRK7D%>_r>$k#f0!eGcX%#Pe*BqCX8Gp zeVR?i1)e$^BNyPRWvuSt&}6mR=yp<~h>BQSF7Z5iwqQrM`qF(;?Vs zgw~U593j**tz5_oQu!I88>!1&d8= z_-7SD!)aVZmiD}KArq0^=#j#o4Yca4qOjtZ4$V(pBC$}-pB7Nx{awct^t}3)`=F`ItM#>&eeRh=*9{=P_r^x zY84uRH}nUbf(3iuDFv^Io>n)jy_vY_lYl690C_~J?#|Q_mx>8Oh{#e=1SZ|fQZiUj z$>cbGGlQqZn2w~Vq7hO5$>d9}CeYhq+y6?pFK<@=#pQh99nl7-F~6bYTr&M|NYhKc zSob5XBWaMp<6yqOaw}t=IckN6&mj6mzz^+~+eIHn6<3@($uIoQqXhiOcx7rg`dal% zRcIG(ag-CjHVOz^2Y?^gENj^M*l zQlTrL$xGy9ekxoI9+@%k)EluV(iT@zQ?#cHc8YOBRYbnh%Un4d_j~nEn$~|w6GvDk z&FHn)w~%0`vlF>@12)uuCV@n88Q?(tGjfiep5--5!8l-Dm(X>{Pnij8Jl};!W(oK@ zUr3eoeC2VhAnI)gRehZ{C~fpg8GSw0c#*Vhdhb2;2flds;V*m*X?y%#^J9M6ptXm z*+)0Ye<6lyw?7br2-icO91-_HAc>b?`rK*L{Leh4HAj0mp@%8`E;)#sbskzfv}_#9 zVPx_L#AYf_SA1$`87$t1R&Ed|JbZqcj9h(#JDql@J$t-SEa+ikPAuw_aU862+=kse zvCt{DISd5!`hqf>3Y$Kkt?3LeErg`EyIC?_s1_)|YtG)(6G>o+fy2+;<~qLzbm><- zv&wJa6n|24!Qs~9GQ~#0Z@6DWcQT`noI09tU0r*AW}Z}B&zO0V!KtvU&n`czC?{)a zh5s)KzBirfC0A62lUg@an#+(TMTbkQ(#u<+Nf(T=E6L z`25Ofc^f$rMVn^2EuzpUvfPyWO+pyOped z^7vfwd??F1AgyCJFKad749Pu5w1+M4#;$%r~>gx*Sz`PHLi+#|q- zB{I$-UXk?w0}N>$sNLVta_ds#@MW{!^~-L7by|8(n#7Gn>&n%bvCRIK#^u z7=8a>`|jhGGbEG1=wa$!8XGYn0F!7cg)}z|e8>oTrMXyjkdmch=)a7I>)Xj15K#1xj%#ijNh48?0w{*7-;7-acbPO8qeXNp@tMGtHIOq zH6}q>j1a#1CluWCkv3ztXNx!1bNkb;!ag-UU7gj8Za~3DOkNZ$85>M@ws7l6y3#4t zE3Z`A(aLyhiUzh86H|*BnFrt6nX~GBzupY|Q{m^y4YYr2_ldHlgsNOgVCASWa+vyS z=sD&4R9VA+qr@90!@S0>hsESo#YA3ACpaAalLJV}+$1~7?r!K+O@&T40NJeN+XZ&Z zpOC8d*Rd`6y$pL`8!w6!7r$+U1jdX??@v$dY~H80?tEPP zSjFsgt_6tJ_psfVqVkBaGS_vUNKsF65v;MCYtn6Z0ershv_z0MWALTDDejx{X6m8> znQNRiGSWl9pi1rL^YvCL)?(pd!>z@GXP@<}sp!p<8PYtS_n%X_HEAMZf`yLGo8y_mR01u)2ir|J z4BP}q3wL2G+^G*dA+K5%V6ubeM#`(Tc%TEe`4`;~qICA>CH*;QMf}x_!&*i?ZEC$_ z%O)|}KvDA%Lo(fsON+*ewoKs#>Rn-bXvZE5xgzZze_1S%NNL@l0(Ywx#Yy{OU^u-B znTQmUnn8($;Q*XO$_O2^ejU^Cu`yQNqJss3RvELrnNNAwUYICKa3`d|1|RTVQlF9Feq-ajk+vh=!ljs&3u zDEc8q9<%O;s6(F?er(MB*A9;vsxQ$PX^@F~4E|R}c8)Fz67!J4wf^KR$(Z?fgRZZR zz{|*Ii0dLckm)I-@Y?Ql+nJeJQGkQ(51w(W{vSMJRh;ENbOu^0=&}GBGWPDMsxcBj z3^iGp!dy>{?0gL3*a>T35S2ReKVmIEUuX2ToP?l2l3L@<9DH3*2V6I#&o}-TurZ@K z71Ct+Mfe(?+pFbQ0jKkL$#x5q4oQ1ntC|Wt@5bW^qCcaI57d1)`T$rCvmBwVixNl$xN@Yt3MHFJ}-�KM6= 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 Glm8Eh)%8XI literal 270696 zcmbTdWmH^C(p<{egEKha zoacR#bH2OYb${HwX3fm*T~%FOUDnmT!<7}KFwowhAt51Q$ViK;A|bsvKtg(^{o)Ct zB_3O}3-LqbDxvMF2C{JVFm^Ua5-|grn7@^=H?}laH8(c%avC%jKtg(IWv#C5s;wZ; zX9}`sG5#Bd#navqfsKSDAnfUAY-(%n`qsqU(%M0g@~E|w@~yR*Af*ed2hxq?@Gb`oW|A4sK z3R3j&V`cU5@L=)aWC1x_va<8?^8QVOgM%3X!R+GY z;A-s2?BGK6n}WExi>b4ZPXA{J_Kpe)e+WCc{8vy2k+FIj zJF>E~u(8_P|2?k%K)blAn*YPb{|fD*?&WCCs%q{6a&tCC%!dWlZ)Ak*{_h?A4T#`| zPs!ODF)7A&;viEudvga@8F4{M#1j@XYcoDJULJ%Lx!=N%pfgIvIY+~YKA_)9pb7NPKvpNW5_ge#$tw63I7b}qCTQN1@TY3d!Q)`F68yNnc zp#K@KxVf{nySbU9Gsyn!etBD|cUKoK4uE+98I7v+C(&Hi7c z#)@Eu^>6j~m%99JLg?V%fBz^y;^vR}m^&bB#u;H84VWnONXVoqGU6iYo{I-i6n$Jv za+Kp8j<*LCc^}atbn#xnE0$XD>TT>vD@kK!TUw@P74__IXI1p}OGv94G?`#Ft6x0-`GA}3wj&9eU_HQ*`EW3CNo{gUgj3m&=z4(tE|M%-7XZ&0A ze>Z$v80ZmZ72y2#CDWYIE_9;5?LAu)Eu&t?$};?1Y|I?5c#<`GqR&*w1yd7jjB14CB9 z!umiJ@h#eo=u_)I&)(C7vi~1EX%qE`cE2z~Pr`p=N>Tg|l{_=6@MlPhQKjE(QU0`k zoP7TZ9Vv@ol@L9imW{&jqwjmzZy|i!khPG|Rj27T?OCkaBjg7yTeHv=ad<3AF^yP1 zaSTNdos^0tqoJWGRcCZq95@U%HseUo%*?E=W`$?o5~M%>-3wTSut!uvjbYc~t>oz^ zG%ar?(=T9m*{90ZRYbB=Q&UD)bmLd|L0GmbAX>T+=ELS=l#A~2+v{r?u<9o9Zz}Wh zp@14;`}gR0KgS{#>t39B66i*p8EFpxC4iZV0k)~&S7Upn;!f z-&)2L2IQ$r%&9(m{#=$C=iT7nG2}nj5)WUPhG|Z2u>jQeS+Z`!{S;wat zM_)e+5yjT%{QjdD1zD>yB9k!b>_V(mwba$=%z`>097XjiVfeU_14dw!#xQ&jGnk^| z8{mhXN9z=G5_lKY0El0|?h!P4)&FNy#7SaufRuzTo|K~5EIgaep7eEJgwQMp3}bS6 zF7{h4;N;Z%W4>t#UH5k`n^rS<-;9HD9cJqwiR8UQT%Cu@q}BJ0KB;3=xjudA9#^pn zdO*9ye11Cr$A?e%w-|e?{X`yc_E^(oCFK2}@$Jp+?ZvjOwzjqarEGzK-E}F>B3?_9 z{cbGbI=1L7hGA%>USr2Cj6Xz_-E9xy6px&j69WSSP5Wa?XZHG{iBpeqUQvYg)>g<@ zeIN`Ukx=oRqi>TA?uU<2rSrR-*%Sk!W4}<;GBB$9lm2M!`>j7`1v? z%B$q*na>=P0|NtRJL8c~fOPp#oXa*u{TwprY}+C`GTBs%Qt%_b4` zVbH90ClnL5*j(U1Y9cMo2A^uzOEoz0nX53Md)|^m$}xZ%8>rf{4Zm3K`R4EE1OL^h zpkAuFHRrY;V9>d?@qDVHjIh4BO?&2P9x#yWVT(Yo(T-&-D#lK`(&mT9>f_IsP8AV_ z>IywWU;av&T|g&jS_MkgTsgyz;Ohfxi7V+WRYTV4HX<6n9SD`_JzQ$6#|B_w#g>$m zoI-Oyene{4uTp13M?=e^iIJdQ=8_>o{o<`f`-tViRJ05tdL+dSuT%Vp!Y$0fak}0c z2^iKa*No=J@A=~yzd6TXnuLF0U`KYdbuOEDXMA?w{EiPBpjuHZA{l_h+}{h)1T@z6 z%0Nho&FAphX!!islL)_iyG-t55DOT;!{}y%wz#69Vm_<}hUl-iVAp5z8+8VvJn;J7 zj%9!Q__4Uf6=%owXz{uldcqBY^0{tmFU>Mn3Sa>z4Pw(Hv{n^i0FuDCyFd#|I6}mQw~-Q?nnG?QlaB z`rZ8CN4E({EYDaJOi+NwiR;e4P0sN-LX6fvm;m-@!VC^+Bf{#*L#>(;uUXS?9i})C zn~UoGt68~m3{pGBKyWY3&xA_tGp0uA#5AkY>F}~+G_<7sb)XY&5XyP#Fjq)QrYB#+ z>Lhu9AweU={H%yWN5$A({u5h$G6o-Zg(h<*fzqt3a7@Uhwz<0@y_=`&E5<5*57m9_Z1CXXM5KpML$_9o(TtRS$m!98pxHU``g#7ZgOdreK2>vvv}wyI9$l0B zYBhUwldar`nx!AfUB>6vu;mn|_$JEV(#R5NjtcKh1M!<*|I!rAWL9{;f5Jn48Qno5 z>~-@HwD}`0&LNv$Hz7x^!FIgE2NiWWkU5jvEyr%*1P+_7t2?f#0aoh%8Vjhk-?tCA z_k+XQJ2&8$zGdZ{2W2A2!&lFgD+E(G`yhmt)=^+ClM!uV_fn}j7OET~6FDr4x~LeF zEC;eoh;!*#?7PdWS4$swjgjRx)tvw-TdbHVTT=ZvZwyZ`imZ(z0iifBH&k<9Ckm6PbDoq5?5}&p-WY-<>hcrL_0;17iFUw~cDI?orAi zMyFWaj^Q$Efr^y;be6-p0nWJHIb>F^B7Q#jS^Pk9Fxg14Z}!?+UC0Q*2B_?(j5eF; zuVZ8aA0gK0L-s@q5L@`%Dl;>^GT)knu9q)8z0e^wY^D8nw>a0r!UBgPkjwApe6ik^ zUytnO>Z;CWM%K=m@sf!?beTkH^pq5}jG?=>3&aJ_jJ1ORge@iL76_OPrU5FDWij1+ z3p4d~S#z{-spL>MTlEUz>e>I)a@FA%}yoaXUlZJ=38(+wc8+kN$5w zD4%jG{nh|WWlUyB9TaIcZAU7iuYQtrJK}XrD0t33&w(|tcQPoR?_^l3+PSlUideS; zhy7%$d@gIJ3Fxr!@JofI(KH@oKhi}KZ6hMQfRD@J^}RT>CACU_q1JCF?$iCSlQ47m z2O_pvvPfNDZ#nbAaL7WBKC^GVm8B+&Ue|bldBp^&Uh{~PeW)g?kI74#=llu-y~A4D ztT(Xv!9+w%QOG{dNG+CXPTkS4GTJhOL0h?PX93U`n&RTYF0iud>gstiIk4*1&W;Q2 za%T)E7Hl03o;7sHDCQJPQ@NmqyT~}RgkvN+G*uO4V1K>JBY@w^;&awq;I}kW(_HR-4F%!e(bfJ3YqRfd6?OW_Bg5lzns?7=C>Zn45^gT5cDPS* zBQdj;$E^4ma^$aJ--6Bk&ha(2%(oadg0Gy2a#Gz-hN7dSmc5tDXE3qQr-h%T6eIsv z*dG&kT4+*$$QnF20uvzdUMphj@J~#`Q(C%FyPBlCM85TQcmV;!bF{RIVkMz01VHf`as0;q{9gZ|q^(THg!1t#hj%4f@OzIH z%o5eDq56IDu^g925e|o&tuAXllU>Q0<>lofh0N_bgdZpD$5}EBcd9rzu={Liom4g> zib%LrATNbE@a1%jMMozcS-0Ks1us8FU#2z<6LM`?aNL!vi-srB)cV&OfJy~ioSn5h z_(g}0$P6TVPJ`6_uAz!S4cZHIk7>-*gq)IaxtDMux3d#C98PwXDS`{_A?0kpZ*RZ9 zZ(JRk8x#2Ilz!~7r&gJ9PRNy%d*=@mz9A*H%r)2?W!`~4B(fIuH(hgEO;>PLt9ZtF z6tfRIY%e6>{8n1o&0i_nCglw5uiPwUZ5Zb8fvZMQro49Y6)LRJE(x4kGB=cwOKlpZ z>J8Qllx-FmYSw#_(+;yuCnsvfK!+8e%@te!(dO>g9lOX6;jT-j64Pk0;W2e~yt)~I zZyTC8?)Pxoqpfx_?Vf4G$d;gBVN_kNoqKfXUL8j3H40^&T~E&$mJ_Q)BE3q8i#z_? zkYBL-aC0~AP8H9Us`X!cEX~a|YLshA#gJel1ifME#JK~pwM!jlh{ZFeSnjesTVs z6h{9LM<$=GWMlgWUES%*55Lr0ygRt<`4?)>-!F1P@9NGt;T1O#M#i1FJ`%5E8o+HW z&g<^5qeODdb;Zt>+Zsu;L!w%rlZIi^&^e|>)aJB@^V_2=d|DF|Q$KEpN$a;s%H0TfHmeB%blac&As}rZ#)qysvx6)fq~lKh|L12$&2MA zu{N2!zkei2kOeDY(SOPPMxoT7!{f*2|M+P|uoXr9#N*gMhi7n39rIvyF7!o8iQDkD z3>H~M?2_x$8Mn4PIf|9YxOq5Ks>$#Zj!c>5sJNsh7lT;%bLEz`Q1 zG$Ic2IT?OCZ|!xGWd@4wZDjUtf1Fuo9OHp2JSVnGwruJ%bm7zfuP58!bkPGNoW%t8 zE%QG5YYH5<^H^uq$00W2#Wk(Ot^`)V13V6Ueu~zB`&-FIiV&y$^TgHa^~T%TE0|#j zeC#~Yb_bO;hHNn~lL}*(=L;thu9AQH{cURZD%Tr(Z?Ef5%>_#*f={=hcV3dQ1lvZm zkL?%V_li5o4TrareoOsBQbilkuDAx5O1wAhX=tx5q^)Et0^##KVr05vqH!b%bx-cs zRmsZg_hw4Z*tUKxoIUE0^{nHdqw`68)(x;*?y9w$iykHOa_@Q2n=io7gn77ajSYP&HfIX#QTR(i_>(FGrL}bs4 z6n&OlpuH}%#f@q9A=0Tr>@%hl_u$>#om#P?;Pqx=%tBqpR<&3?8*NH!)Sw2=(2q4G zX&*b~fJIH`2a3ugxu^Q+_T0ZdzSus%hFyoO;FWv9yUfGh*5_#)GHy-# zq$T}=`S@tV`@@%BpjO(3q!!ba#$~ySVhoV@0B_I*`${`qhZ4|V{?e}#K6~|-v1$kF z_bt(zMk#J*Ci%- z?N_GXcT(@f(wWCQvg)b+Uh#iqKqTd;eyBLDv)-Jdauxl}{34nAOPO5ZrEgw)a#O4w zU9~H@?W&u2vk;}G=>_Z6EXVRWOlitmr(z!7-}^GlkP|9{#abIqZZAR0we|4ou7rOz?=buN%=55M;jpa2(v76tYMRN`aBF7@&1+_Jhae1!+S1gN1o8+( zWEs?keit)3UoF63Fk*e*?6RYn&H;AXh?pMS3_`(rxQ83Dzb;g!&v<9Oo_6T0r(R0r z@Ks%*%!hry`Uf@!VR~?u&wEYR)V6x9QNgV#^Z}QPbWp2wRpG<~c7hV-*BJrnAjYT+ zA9XbtH-zT0h?=_T-WFnZAgN#{VT zk@SDaWUc$L!og~9(4Z$!#VdHXLs+%emDSsC=?W?$uzxdO+wl2&bf0it##L_Ce9E7Q zDevt1N{;w@t=tlWlP9xrww1PVusS0t(DO&2gxqX}0l^#nO5>m#3!F!fwaGH0IYf&4 zTzHgOyTiwyo8n|X92cRZprf1=)|pPFYJ_*^b3NC#>s*;B-VtY(%Nm}dHB3-+n>*&o^9cM0;wuExV^|5Dx~#W;M)oP!^EVh7U7SNHQdUsTC-&WaRn z&VO~d^^dU<_?|8Q_zvas0IXEM1)n3dx!m7?Ni%$|83{+<5AB zs3$W%kS^6P)q5NjF)#JEod-h(e|)&#=#PzTv_)h$K>Nj} z)>eLJL>|*YlM+FbOI|b@B-5A?B`X0kXZwl;@m0!R*V4EW3QD&mj`MdTWDv_)5bur5 zFjBUkni)M7{%)U3^!|ZBO4DxsJnJ1y$-zBW;;E!jU0li8xX^FzDfoMbIi!1{Lk zyY>L(wfzM;?hd+xnV$L`i;EY7!)kaPoC$-cWAbGy)-*KeI}UgB?tx=7t!u{tM3s8C zzo2GgFg&{R0?mJNZluH^b9sOQIwOB&^by`Kr$fabERbf)=UAKmASlG8H^a!|XkiZW z-$L58Y;)3d-PfJRrPjxv&|qZy8;s0rT9F`=+mtyKM6M%vdgN59zF4eRAS4TU?e!%uuW)hO_EhFR- z-d z{4ozDhePm3id(}~@55wlYzc}@26XO@Y40S(MJD0SZ`>xd@uN1suw2Z&WPfKiY!aGe z#9$MW@^hw1V7JMzP$<_P*vVqMLc61B^PF|R@UbJ<$?1SF@q3$=$4Cxow#=pda*jdxY3BNY{zY%v|~*@>Fh!ya>Q?2O)U<`W?c1 zDG6PRN12Z13I|LU%8X`kxpW!0g}%W5tZ@dGtfU2e5n@RYuAaE=zq-`G>2h=l?(0w= z74)J{Op<_Ea{Ae*8HLm&X)mjTy{9G}=G9LZkQKs@xaaejz8lR1TH{Rpx*DA7 zSITMey|D3?q~)HP>u)v8=KFj9V3t~vG1=K#+8DXKZCyLo*#%CvxsTxk_Z=#GRu9fo zs)iwqVe&;Mg6UrF#z2FobCo5i=Sy#-b_SU+dhWz`>cuj8|J{|@I$Nh4GaqQxIMRbKYgDGlpT_^xkqL0 zZY{pn)ZChc4JCd1_6@!o@R+D1B%`Q^zoDH-r{vziqp7Mod9u-8dOYpo;Q^W|iq6c` zYd#2kFRE0Uvpo-5Ff5prGR612kSmdP>66 z6=}mJ@*4X*SXc4~KU{n#%^{K#!tXx)YIIOWnz(lpGvn0Nv#-?1={?aT>5)CgEIRNB zD<#LPH5(@F?L#17pZ6}l z+->ORP@b(nP%W)Plopp`)8Cm)myv8}`@qRZ#>ZoA=4-sA!%E*#g$TTxA!DNEX;M-D zdr5l{HtO!s1GUGH+iOjq^9VU_Wv@YgSv3^|8Ko z5R1%B7ld)kYLc|omNZx>N!s(AA58Fq{2NZ%fNM-Hz<>vLBjIkphIfCKs%gezugW)b zhDK^YOQroj?<-DKJDe&6Q81l5TisyV8Oe^3XCf6(QAo&pD0|nnDT2%_KB} z8PYS5$R?E11TcM-!Dv@wZhg~_#dTS2YV*x?LmQJSbr`WzrI;;vyO3(se%Ez|vA?A1 zkrf{gare{r_wR^p^zgc9k~l|9X=393^frn}v-`&OO_PG+^7G+lft2L-i~U*R`A#J< zeh;!2?gi1Nh&n^r%pN)cOA_c66AcYw-w9FIX>fqHEK_OUsikd2?yOSL^s#6b%@aRq zPi>l;2AFU!b9V%1V(V4cuH*;<8kAYwGdjW}LdcZ@Q z0_J+Rc&X9%#=!ddC}pp%d2-qcyL{|Nb6@E%DqyQ4(aJ?tq8|+%&SoBgrbhm20q7mg z?)QBMkE6_^-kZ+XOsqnz@zO&ldy{<-#9pBlTPK3MBFWcG8WYIqZMU=PX5d7`{!AvX z%l7Hkum+<>e+=ocho#MQNqj;=7j)G3pYRaMvyaq!SuEe=Ih+szqah%Wzp*-$*12sQ%z^R8&u}tQ!*-YW%ylX z&!gsOt*YEi5q*A-j8<839>}p6;KqkjN8ir*B?W9O+l;1qDTu+x;Y)LRe(ZU4$lY*w zv+56(V1h-^IkdAzb2a(ye!9MSFS+k1oOUKe!tF%uu@A`Ip9>-aA_yT+$Fd zECCB_z;J)7^JzRpyrZK7)OHfec@SP+ZdVa|S7^=Xtuy5SlcSbnxsNeR|(xS-zgMna0h<{4f=UA{44S})s6j{sVL zb!!$Vnr7p38zrNEUfPX(W4*L_+qz zMl%Hcsaz@J*uN@2*we6d7&cK0hAgkyJ z|8U@r#QUL{h#o0lI`iw$-K;ZCS7tG1NZ}2k~{#>k$A8 zfChfbjM@AilC*E(zma~jZ&O22Q+SY))6X@Tf7Q7XJ1Tz$y>Y7G2p*|F!stqeDTb;4 z)=MO$B#O_^AW`n}QVNA;0PWq|*BKr|gKJbhTfa-<`O8GnNysUzW-{iTiMwxanSx!lBp=go(I zIN>26s?D>T=(m<)7AEX*`Uz>0#XkDyF}<+j3Qi<`=0~(o>p#gYM>diw!~TuUumcL$ zh-XS*n(pAd6uuf-B8$vt*Olnxv8EAp_`?9w&LHG+4Be4c`cwGB4Q|y=+k_c&R42Q`O_FokBAl zdJ0NS(7+Ug>S)Zrlzk&sT2GUxir)AoRnh+TnPpRA1s<=71zEi>c||)q`}Ac&y`$dO z{fbS0_~8=4xbz>k=n3RfS!m2TXz^*1oKhw}_Po&xhoqZ>U9l_r<0?(XAnrDFR*X~Z zACeRGbIkQF8GHCh>-7LbB!4KV)2BR#ym`i{G-Ihd{h{odO~kMV`b-A}Ntu1~^^eDvYsCvXIzCS_o6yPeSSm2$gHw{MC`A12{20i5}fNLW|Dyr#<(4q*M0E0Y! z)=5tIe4$-}n(|}>Q5RvhEm2>~=GJGM0G*WSp8+;A?qMOsrgFO2VkK(ZG^X28OU3!w zkW!u$s?b-qLo(^fPMJew%wG!eSWA+0oBnEd-NRwwnrgN^pfqD{4J=JhtrRfQj8^LX zk#NM^>qVcY%sScBQm0i|XIUstJ2L`zv7GHV;5)y)YoBA2{YPtfNU20B*o;o0oH)=I zvt#J4mx>qB@A?n%@obMuq3bX2^<cOJqtPslMD6t+;qe`0gZL#}oCYN} z8+=kd}@O^fy3rH*XJa<9KfURQujr-(g{niBVubq))kF&|l_!^fxy zqS^Ew&;6o!eJ9qp3$%gS7KwFjQZ#G|(FI_I@U;~P)3${13EjDB? zQO)s&aa=R3Q`dK{vKbX9EKAxOWSD-?^flka#c0P1gA;uvS<}VoZ~S2+>uW4fg6)ED zPk-ywM>Y1BDCf5H*7=;6DTmkrb$>O{v9jy!^w2q6A33Lh^f|Cf;ZrqKtK@o@xV4 zA|zfLh;RyvknEc@SDORbO&n`2i=bj)-U)ub-pm@f#A%#n9qJJ(NsBQCsC{GY;MSoF zl#uznmo&Ixn@kk$4XQ1|ep+t!{IyYX*V)2A5_2k-Alhm2Mba)Iy?kpe_fbM7%cM-D z%u|p9)A{zr9V^Q~BxVK;X-E!ga}qZ!j+kYV!CqR7RbgnHRUq_Y?4>YJQ|IznYT{EK z_*&g==$yvF*%%#X>CJb_S-WXp)qdCXAzcrO`NK2?dw6%{GbJwNf#6mxaipojmd#BA zd>ZmUBuM$~~~qBaT+@@;>hFjx@l@Tby;5uJWDrq+%fuT9JMPN1ht#9`Men2XEHW-t-Qh zq_E_?U%ryD-phVc6xyTYlgGSlIO=u2VKVbs2d2L+wNgt{q}s5l_E4jJty0u)s>#9} zZVCYtkqp}rRcwl|=ypB!jfboe)iLIo*&fT-7mGFcvhnJ=>oE{-b9-}zJb2|CE=jx4 zT*M?%{_Du4VCk9wTvGx?Q33<~w5pQT?IB z@Q^$Nr9s}kHW-tbdEwXPnFP@SqkdrdpXor#`fQkN&Xt>wG?MO_MDKyIi?V>JChPi_;yQmR&6`W1$4j!AJN!bJ?1kqzVHRsvgp+&*Lngz0e-z1F#E zDSN8~o)fZvH1e24FK*wn3T+rVNq%=ZJEAVsbY*JOz>}>+?YD15ps?C;cDg2R|6YEg z`hx1v79e)9bW?I<)atUXTBLX-e=_;g_o3QYc2;_b;`(|2pa4reQD_oLf2|UIc`#9| z@Y0(PM7t7k8BG2_$(jfiJx3ho9?j_~i9=`QVi^9Gc@5Uh!k>i<5}hhx6?U0eP+Nqd z^w6xFR+-RU*optMX0N@r^TpBR7EU7j8NgP}v*zaO^j+#L;}#C_^=&0W>(W;5VO}r8 znTGWF+uSzOjk~+ij>;geYQYwtodge0+yML3AZjh}d}0aNE5L|~=gX?>+O3#J^vW<` zISYBan?{v&%1k>mt}c)8W8X}!0G}&Q#)Ez|l9S>gJ`2|)MPo>N>MxH7_p3%`@|>UC zYd7Kr7p$|d$0M|ocFOn9Iw@f-3|RLKTLl}ULpRTJXP#xS!=7E~YJ*tm)92x07BU$Pg`2?L3FTYw zq09EadJ?g*9IZGHeJ|n#=gH0AY?On*%S8V0+iI9>^R!tJr&6} zuDc_%{1gzbo@dnc0?sm@S`CLbCe6GAQ>4obrm$njYkg z82eP(QRXM~P_SLZhXt^$YWuX^?&ehOp|MK9M?Q z)Q5gFy5!7l+K6%XM4=NuBC@_ehM}CbTPB8UUwuH-zy4sE%;Ml5CC+j39<9@)<9Vua z59TKQzt0a#osN(_SO=_p0F!ByiVuZV3fV2-$*erxRk7b-49NAkwbn|iy*GM<`MsRv zw2U#+Cx5N2chz4WlV6!BJ{1!gC#6-*>@p`_SKD5Acwa9kvxM=><>Np}t;I#YK#GS| ze}Y0N|2aL^eN%p7f{NqR?&J>?-bJnXC--?#akXC~)`B#MCig5VPz#ON=Q>t5ojWHTq3BRv5j;1MI z^UR{^%@YovkOdi{$Z<_)DTIuU8Kdvb>(`o=7Ad;N;o~mpCU-p3IPPcs*GmT9Uh9>; zmD96j?Sb@M96O;3F?O@bL#mFiqzQ&!p(dBnh|{=T7*kI+SBp2_xjKyMFz#$HARpL{F33 zm`H1u(60spf`a#D&u1#;-j0lq-qho_@~LC3&pEV9+{p>V&Sp8C+&SFu9M2FlFmx3Z zDx?ZQo_aT)Go8VznA`8Z)NP%4-;9Yjc={!NX4=@Hs>VJb+qUj5_PUq|m}1?O&h?(; z7WbX{!V)H&<&`;*V`IS6zj?;&16>I8P4Cf{b$b}=)+v=_0v0BCV;d?wbxQ#ZEcpWH zq{@CREq3GX5PL|sE>md0AM{(fXL=$ZxZZHtV&hqLZ~R!#MnMWsH73C=ARxR&-{v@4= zd&2?E+}$>$o?B-$>VM-6?Gyb%|LeyITKfh+m-7EG`pR zK&gvt`(VEkm>_p!3&(t@Tk6g^@ii-#P}AwmS1RJ=CTn>GYEQ})>@7A!wbl>-m*Zgr zX4iJ~JtBc9>A_DZ@QEeWWaaJTOsXkwcw#~%m@~EjH2pD|W+nGWMq4q=vC(^&E;LcG z;AWrI$M+6r-bhG*)PVgXnKdQmiCizBkpIl&vqV%c%b8~0ZYn0&1a#qSu$V-)OE>Oazd?xyQ&=FM2Z5SzV<7<)kYEzR`|opTEXhu!r?FekT2U}HVZq&{W0^S zVBeP9w8`U5e7X1P+;f1%^SHP^h$(+Co<_?re!5v#ae$~i-m>4C7`Ym85m)U5gTe?u(huaG zDd7tjTd$F*9T`u4EhZZ^A1MYX`(G%{>Zp~^=i-wbob`WwFczg|+Hts<4`{-(n2R{E z+6u*u;V%?~#d(DrVUl+&E!&uEgO=DzwV3zam;R$RV|{N^NHcS4M85($LNJUH2d%VN zrfoSyr--{aaJXnnW0O=v&9?B`gx+>RFZ!16Q8}t#*^>-2K0}<>>|_wLLAKrzXz@C; zjHOqr6qqz9x95$Y$!~`lXu5p>kqujQJaUxUPoS6XDBT~8+qE|`Gzz+RsvBgvS8i~H zY>v@-gTjbi(XOhUM4UV1JZJLL(~BSVHYXQ9D9MBGX2JEGQZ*4r?-_#Rql(02i(YPM z&n8`&DG+$addUua*Jnbsn@(c|Y7Q)Z{ zl9Zt6pzz5)ZCLAs9ooMxLC`+qB}qj$b(D9m?b3xuIKzK3SpPY;Iwxnj zwPCLGfWWi@6-lon5ecbR8@Fe&+U_-|9bD~KD9Q{dx0vtJ#KCEYKl$H|=mQJ8aSc&e zmQ${#>wqydmu(&w%CN&vg%)cgQ&MDM-3q(gVQn?}DduJ;CmzLhaK@X(ogk)F3vN8M z9bBE1HA8HE<>?L^eLr{_PjgUQ7~^jygIzE!yN9Vc(I zj5g*m4jG_!e=e}IQVU7ZM*7g6>C$x*fn8zQ$$#A4WD>u$;tD-U1}|3XKDW zb_h_rX`xJWYw_$ot^lGww12%^uAqp|XdFLm?ddpPOu9G%lHNUoH~9T5Ay79d$x8|JdCeoclha!e(+nh zzrlojXX)a_)dIGGg(EKd9N8VEdsD+U)Tw^XLvp%^LPD@Se~TWMlRZl-Cbk_I_^9M0P~RCAwOE@4~fx9gX1u zT~D&8iPb{=yP|}iM=Gj=|G7A;4NKHA?1N5o)C$WYdtmP(FVpayKCc5b#a>7FRJ`|j zJ@B=!cn#7wU961&U!^7u!E2Sf45!1Fh8R}B^>N|q;Pbn*IOzT*^w+C$qwA8P7Pkv$ z)zFAnUhkv5u_vvWI-a4;- zY{8&xb6r&{Kdr5G4v#V-f?poa_MSB+rr+3SJRgckY4_TBprVN%hC=-x?`30h>I`LL(Wqc^3&Y-lp)HP7 z3~DlAtxEOBH^zM~IZTX=4zz;4J$QH?`~Znw^!!9k4;0Gt~xmXNOIF zclVQvIl8klwZ<<)BXlh7$Ym7(yuq`lxf*?Q&f?20Tv)5=-xY0?7kdYyQBMruD}GtFy``;pG{r0>bNCfkN8 zrS#2mBEkeV9yzBj2T?oRIL@i9%r=IbL*DpWW$kTd&n-i8nOAt3n|xjIVsaeq)&ZVF)JdMnrJcH}?Q{SvGX>xL3{ceOD(`F*XO-x&-m$>V)o z&Pw?%O{oQ30<-Yzouwve_ zf`!BuaskI(FdpIFqj_~3=~D9(dDh~ug7=ZLv>t0hoaI$CTf)BL;p(cK1I+XDjrm_W zPaTR%oTtTElAD@`ZPZ=ZPdDk6p^6$sW~JzbQQWDtKW3)1lZhM4n6T8BLs-w^X3DGB z68dT{eq!$0c?{&^q*VhRR=`_lUpw#iJ~xixICI1=(FqTGEWL9%DtF?3igW@qHE!{T zW!elnXxHx%mo4f!-zKNYBVNEsD6NO0)-H}AU-uvGjnxKAZB};63aNgVo_;CJgKw@K z{#mZOOlXI7)m1@Bjj^4u!#<(;W{O;EWx4Eir?eDH%f%*n5e|H{W7Tl}J8$#eti+wb zP3JWVegZ&2_E~DeWf@gzMuFnl1xWdIy6ofrdpyaih4n3$GDE4y=?-_5lcY6vj@nV- z$M)ojs75y^m~`U03p3N{sGWRV9sMM{tjv*S52Z0NR?rMBOe#gM#f(Ku6c*NIs9jnkg2vQGO=Q(Uo?{fP zj(ZSCPUu^a4oTqLX8F*RThgq%42KXOU-v81wHreYhhz9bi zp8FidLUe=;I#QgNC_W>jF`LScO^zf(giO~_iG*yQ>rh?tmMUCASB0bZ%jQ0fH@mrn zVzHA5gEtB~|FVE4FC>NjNyV3`rm_WgBlx#(Xcys5N# z%o^|r8-1S4mta14VW@3~JjFkqr5NV$kgEq@JEkDtO-=kHe2^*J;-|1&dVy#4aAQ?Q zHr*JI_!)z$w<=PuGph7)Ikxa&u9UzK{&n*dyZVdm5-rBwS8=$!t0%mFO(*4OHE2^^ zv}|`YVV3yzR^So0v4oONsd#K*886j-{f0E&`m_Zo@ScfG?7HzGN9d;1t0ndFSVy=w zD`4%=$W^vV8|?NGZxjjWkb$@ z&$IDNn2{1LAj})bn)_^FoJIPfSev8UM?rOwxU&fKBm7AX%prG9EA{68BkQfA;t0Dg z;TSFff&>T<+}$BG!GpWII|TP8A-GGRae_OIdqZ${X=p6CTjMk`yz|b?`oHzps*Ad- zi&MLfJbUj$u4UqWo&EM^?aTc)FPh4Q6fq28U;Mdy%U%D5lP>KcWQqftXHvb*MGnUR zG!Y+;nhDW6WQxZk&#G%Hw=4lkr*Z90;+HefRo2@px~lm~6t8c)9TMsP3?Yq^YqV3D z#`0cEC5fxrn@-5DsN|WiZ?-*EbY~pOet$4A?<8Gjb^E*atiaEg5zHh6TZfP~EXB>Z!s%=@ zb_Y~X=d!q#6eX)?=PANVQ3>^jqw@*QmKWMg&=QKfSGD9l7W0M0UR6t9Uty8nbt)@- zS{>Cu3a?)`7|zI_!8}}^V4r8N@HzUlRFlAQzIFgTS{#k;bN4oZ*PWaGB=!JX{)6cjw|Wi`#m9g7hO+5A-P#8GJG&A+eW)q(W4c-C z_&~Y12-f8f;+}DKJo^gda+&SFATN2283M2DJ<0sv8dwjBMcXz{owK77?KME^4icZn2iL zArd|LtTE?#!PMy}Ebm>_&&o*cqXOHGw=(zue<3eB#nKKz?r)@Bk&G% z5r>)2O}$sl027R1Nw`)NGs|ixt+BD4-vOz#By)UpSWj*~otZN4WOH%eb{!+q5IrLN z;xn{kj|Pj1IXecRC_f$5`P9*SsOfn3rL_XbjG*@@Hnueyq7Q9e&3r)w`7Mf-z|)YX zz$u9Q^6D1}{awC1XDP6<_U_0SaT)cFl0M73mgLvn{%E;<+a1m`8H&72>U+Bq-b#2uX zuU6`XYEv8={(DJ}bdsjiqcN1jbiafLH+^e#O|%c%S)+x;8_goQEi%C(xm!-IT7{xu zCPhWb(^yqPHq(FK!!_gD0bL+iI>Jd^~!!i#J*!dhL=~I zH}G^-maU@|6etkS=m6STy|r}}sZj~2z7J&$V+UWPsimb=Yc7{B`h7@lNz*Tugc+30 zLE}aVrvZo+RW`TgS~<7VC!QTN;Cy<{g!T#Cs_W`R#Fx}d=; zi3sp38s%6T z1k9wG-XUMicPwdFWCvJR?qGlAcD6zHku}KizMdd%=-WryyoaJ)fw*I26w=^HGvD-|K89JHvNgj63kV+FpsnxbT;L)M zR$Zhn=4qa_`kUmQlAv{tyoifaeNz`j-s3>>ExY-^bFH7%t{^P)?l?tMVZN66^VivC z&GvJGE}HJUjaveycD@rhPHw_KVfTgD6^jGI|_xXgf!T=cuF)rb1`PD~CzPFbcx5T@>B?z-lTy3%F#t*#>F|-P;6a1W9SUwQPn#;ZJc1&4fhbx5!_N`^ zH^XIU9__&}#x(6+&XMQLaqZKGTKCfT@vnGp>y_#9cyn@0DN}Y|S0&lrm)mnV4%AK^^H{3lK0UOr)#;sb>g2d3H!>+hyk3FNc0ot?y?LjCgTDU0PravgZTMq?5Z|ZK zDT!7d)T<I{Cg+n^3=YZG_l z$8frWD@Pf}k>Tn~_!PVHJwBd}=Y!dH*-!o^P{>@1(0{$$A&I8S|u zK@yKit z9o)3zaI~cr;s2`j`j9L>7O~Xp2^&$Ju1(gipZs{}AGuDXwPMIaaT0Wh%x2HzOv-?! zxqVJcPrb>!zVUG$aCv2;xDT(`Y1^Y+!^zFbAA@QT{--=G?vSuM%9&qWTM`Z5Jf*-V z8MmD^EqJk8p+C95Edmt6XsUy9;-x2OV^T9o3GtD zbTZE�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

    rI#PR~w;NRyoy!VzOY_|O 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

    u(x?vI$TKK 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 rB;W80NHKk($kt+i Date: Mon, 12 Oct 2015 20:45:21 -0400 Subject: [PATCH 0307/1046] Update README to assume the master branch Signed-off-by: Stephen Celis --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf5b882f..8178a6c3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A type-safe, [Swift][]-language layer over [SQLite3][]. [SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. -[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/swift-2.svg?style=flat +[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat [Travis]: https://travis-ci.org/stephencelis/SQLite.swift [Swift]: https://developer.apple.com/swift/ [SQLite3]: http://www.sqlite.org @@ -123,7 +123,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" "swift-2" + github "stephencelis/SQLite.swift" "master" ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. From e01cb82f63a7f59c28fc89a79b7f9cb59780a0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferran=20Vil=C3=A0=20Conesa?= Date: Fri, 9 Oct 2015 14:38:10 +0200 Subject: [PATCH 0308/1046] Solve [!] Invalid `SQLite.swift.podspec` file: cannot infer basepath The **require_relative** is not working, changed to **require** and setting the $LOAD_PATH previously --- SQLite.swift.podspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index f2f43fbe..34f4c3d1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,4 +1,5 @@ -require_relative 'Supporting Files/podspec.rb' +$LOAD_PATH << '.' +require 'Supporting Files/podspec.rb' Pod::Spec.new do |spec| spec.name = 'SQLite.swift' From c9d3082d1f5ddc2e55a792a8eb1a677b8a1b15f5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 22 Oct 2015 07:26:11 -0400 Subject: [PATCH 0309/1046] Resolve Podspec version to semantic version It's not 1.0.0 yet, but it will be soon. Fixes #258. Signed-off-by: Stephen Celis --- SQLiteCipher.swift.podspec | 1 - Supporting Files/podspec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/SQLiteCipher.swift.podspec b/SQLiteCipher.swift.podspec index 33279807..a0ad981f 100644 --- a/SQLiteCipher.swift.podspec +++ b/SQLiteCipher.swift.podspec @@ -2,7 +2,6 @@ require_relative 'Supporting Files/podspec.rb' Pod::Spec.new do |spec| spec.name = 'SQLiteCipher.swift' - spec.version = '1.0.0.pre' spec.summary = 'The SQLCipher flavor of SQLite.swift.' spec.description = <<-DESC diff --git a/Supporting Files/podspec.rb b/Supporting Files/podspec.rb index 33edda9e..24e9e4d4 100644 --- a/Supporting Files/podspec.rb +++ b/Supporting Files/podspec.rb @@ -1,4 +1,4 @@ -$podspec_version = '1.0.0.pre' +$podspec_version = '1.0.0' $podspec_source_git = 'https://github.com/stephencelis/SQLite.swift.git' def apply_shared_config spec, name From ec9ef6c457bc4e95c3c4b0bfa327ec200c743905 Mon Sep 17 00:00:00 2001 From: Rhys Powell Date: Wed, 18 Nov 2015 10:23:03 +1100 Subject: [PATCH 0310/1046] Ignore the Carthage directory This prevents an issue where any git repo that included SQLite.swift as a submodule using Carthage would be marked dirty --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e3ecdc74..18335db4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ xcuserdata/ # System .DS_Store + +# Carthage +Carthage/ From 575c88126920b35a907f3f7ec4ef84c13e4a6db2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 16 Nov 2015 07:12:12 -0500 Subject: [PATCH 0311/1046] Fix playground Signed-off-by: Stephen Celis --- SQLite.playground/Contents.swift | 2 +- SQLite.playground/contents.xcplayground | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 3a685679..adc2100a 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -2,7 +2,7 @@ import SQLite let db = try! Connection() -db.trace(print) +db.trace { print($0) } let users = Table("users") diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index 59b4af3e..fd676d5b 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file From 0913f5d356eb4a0a467236078d472a40f6d0520f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Nov 2015 13:15:25 -0500 Subject: [PATCH 0312/1046] Fix UIImage extension example Signed-off-by: Stephen Celis --- Documentation/Index.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index baa09de5..37c1d15d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1156,15 +1156,16 @@ We can bridge any type that can be initialized from and encoded to `NSData`. ``` swift // assumes NSData conformance, above extension UIImage: Value { - class var declaredDatatype: String { - return NSData.declaredDatatype + public class var declaredDatatype: String { + return Blob.declaredDatatype } - class func fromDatatypeValue(blobValue: Blob) -> Self { - return self(data: NSData.fromDatatypeValue(blobValue)) + public class func fromDatatypeValue(blobValue: Blob) -> UIImage { + return UIImage(data: NSData.fromDatatypeValue(blobValue))! } - var datatypeValue: Blob { - return UIImagePNGRepresentation(self).datatypeValue + public var datatypeValue: Blob { + return UIImagePNGRepresentation(self)!.datatypeValue } + } ``` From 85881c937faadebc62a1d7127d6990e4a9fada11 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Nov 2015 18:57:49 -0500 Subject: [PATCH 0313/1046] Check SQLCipher database integrity on `key()` Fixes #144. Signed-off-by: Stephen Celis --- Source/Cipher/Cipher.swift | 4 ++++ Tests/CipherTests.swift | 26 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Source/Cipher/Cipher.swift b/Source/Cipher/Cipher.swift index 48778b68..8cf8396c 100644 --- a/Source/Cipher/Cipher.swift +++ b/Source/Cipher/Cipher.swift @@ -26,6 +26,10 @@ extension Connection { public func key(key: String) throws { try check(sqlite3_key(handle, key, Int32(key.utf8.count))) + try execute( + "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + + "DROP TABLE \"__SQLCipher.swift__\";" + ) } public func rekey(key: String) throws { diff --git a/Tests/CipherTests.swift b/Tests/CipherTests.swift index 78e3ac1b..f9919999 100644 --- a/Tests/CipherTests.swift +++ b/Tests/CipherTests.swift @@ -15,12 +15,34 @@ class CipherTests: XCTestCase { } func test_key() { - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as! Int64) + XCTAssertEqual(1, db.scalar("SELECT count(*) FROM foo") as? Int64) } func test_rekey() { try! db.rekey("goodbye") - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as! Int64) + XCTAssertEqual(1, db.scalar("SELECT count(*) FROM foo") as? Int64) + } + + func test_keyFailure() { + let path = "\(NSTemporaryDirectory())/db.sqlite3" + _ = try? NSFileManager.defaultManager().removeItemAtPath(path) + + let connA = try! Connection(path) + defer { try! NSFileManager.defaultManager().removeItemAtPath(path) } + + try! connA.key("hello") + + let connB = try! Connection(path) + + var rc: Int32? + do { + try connB.key("world") + } catch Result.Error(_, let code, _) { + rc = code + } catch { + XCTFail() + } + XCTAssertEqual(SQLITE_NOTADB, rc) } } From fd7fba3a6384dab69fc118ad5e25650f881f7539 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Nov 2015 19:07:06 -0500 Subject: [PATCH 0314/1046] Fix `DROP VIRTUAL TABLE` regression An integrated test suite (which we had, before) would have caught this. Fixes #261. Signed-off-by: Stephen Celis --- Source/Typed/Schema.swift | 8 +++++++- Tests/SchemaTests.swift | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Typed/Schema.swift b/Source/Typed/Schema.swift index a768b740..f1f0287d 100644 --- a/Source/Typed/Schema.swift +++ b/Source/Typed/Schema.swift @@ -27,7 +27,7 @@ extension SchemaType { // MARK: - DROP TABLE / VIEW / VIRTUAL TABLE public func drop(ifExists ifExists: Bool = false) -> String { - return drop(Self.identifier, tableName(), ifExists) + return drop("TABLE", tableName(), ifExists) } } @@ -183,6 +183,12 @@ extension View { return " ".join(clauses.flatMap { $0 }).asSQL() } + // MARK: - DROP VIEW + + public func drop(ifExists ifExists: Bool = false) -> String { + return drop("VIEW", tableName(), ifExists) + } + } extension VirtualTable { diff --git a/Tests/SchemaTests.swift b/Tests/SchemaTests.swift index a269aee6..b3cb9a63 100644 --- a/Tests/SchemaTests.swift +++ b/Tests/SchemaTests.swift @@ -9,8 +9,8 @@ class SchemaTests : XCTestCase { } func test_drop_compilesDropVirtualTableExpression() { - XCTAssertEqual("DROP VIRTUAL TABLE \"virtual_table\"", virtualTable.drop()) - XCTAssertEqual("DROP VIRTUAL TABLE IF EXISTS \"virtual_table\"", virtualTable.drop(ifExists: true)) + XCTAssertEqual("DROP TABLE \"virtual_table\"", virtualTable.drop()) + XCTAssertEqual("DROP TABLE IF EXISTS \"virtual_table\"", virtualTable.drop(ifExists: true)) } func test_drop_compilesDropViewExpression() { From ac974894bb990db7d1a336ad7f926f26e02ffc52 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Nov 2015 19:22:23 -0500 Subject: [PATCH 0315/1046] Support comparable aggregations against base types Looks like we were limited, previously, by the specific expression types. Fixes #277. Signed-off-by: Stephen Celis --- Source/Typed/AggregateFunctions.swift | 4 ++-- Tests/AggregateFunctionsTests.swift | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Typed/AggregateFunctions.swift b/Source/Typed/AggregateFunctions.swift index aa482e41..5775e0fe 100644 --- a/Source/Typed/AggregateFunctions.swift +++ b/Source/Typed/AggregateFunctions.swift @@ -84,7 +84,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr } -extension ExpressionType where UnderlyingType : protocol { +extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Comparable { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. @@ -114,7 +114,7 @@ extension ExpressionType where UnderlyingType : protocol { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : protocol { +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Comparable { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. diff --git a/Tests/AggregateFunctionsTests.swift b/Tests/AggregateFunctionsTests.swift index 52d635a0..6b583ccf 100644 --- a/Tests/AggregateFunctionsTests.swift +++ b/Tests/AggregateFunctionsTests.swift @@ -25,6 +25,8 @@ class AggregateFunctionsTests : XCTestCase { AssertSQL("max(\"doubleOptional\")", doubleOptional.max) AssertSQL("max(\"string\")", string.max) AssertSQL("max(\"stringOptional\")", stringOptional.max) + AssertSQL("max(\"date\")", date.max) + AssertSQL("max(\"dateOptional\")", dateOptional.max) } func test_min_wrapsComparableExpressionsWithMinFunction() { @@ -34,6 +36,8 @@ class AggregateFunctionsTests : XCTestCase { AssertSQL("min(\"doubleOptional\")", doubleOptional.min) AssertSQL("min(\"string\")", string.min) AssertSQL("min(\"stringOptional\")", stringOptional.min) + AssertSQL("min(\"date\")", date.min) + AssertSQL("min(\"dateOptional\")", dateOptional.min) } func test_average_wrapsNumericExpressionsWithAvgFunction() { From ac517bff273a0a0801fc22ec2bb54f31de1b04e0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 22 Nov 2015 19:23:32 -0500 Subject: [PATCH 0316/1046] Fix required self Signed-off-by: Stephen Celis --- Tests/ConnectionTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/ConnectionTests.swift b/Tests/ConnectionTests.swift index 1b614d2b..646bbcd7 100644 --- a/Tests/ConnectionTests.swift +++ b/Tests/ConnectionTests.swift @@ -236,7 +236,7 @@ class ConnectionTests : SQLiteTestCase { done() } try! db.transaction { - try InsertUser("alice") + try self.InsertUser("alice") } XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) } From 9790d211d1ba802026ffaf102f1078a9cc602c82 Mon Sep 17 00:00:00 2001 From: "Patrick B. Gibson" Date: Mon, 23 Nov 2015 23:34:46 -0800 Subject: [PATCH 0317/1046] Add threading info to documentation. --- Documentation/Index.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 37c1d15d..174c9621 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -10,7 +10,7 @@ - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) - [In-Memory Databases](#in-memory-databases) - - [A Note on Thread-Safety](#a-note-on-thread-safety) + - [Thread-Safety](#thread-safety) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) - [Compound Expressions](#compound-expressions) @@ -155,7 +155,7 @@ import SQLite ### Connecting to a Database -Database connections are established using the `Database` class. A database is initialized with a path. SQLite will attempt to create the database file if it does not already exist. +Database connections are established using the `Connection` class. A connection is initialized with a path to a database. SQLite will attempt to create the database file if it does not already exist. ``` swift let db = try Connection("path/to/db.sqlite3") @@ -220,9 +220,24 @@ let db = try Connection(.Temporary) In-memory databases are automatically deleted when the database connection is closed. -### A Note on Thread-Safety +#### Thread-Safety -> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. +Every Connection comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. + +If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: + +```swift +db.busyTimeout = 5 + +db.busyHandler({ tries in + if tries >= 3 { + return false + } + return true +}) +``` + +> _Note:_ The default timeout is 0, so if you see `database is locked` errors, you may be trying to access the same database simultaneously from multiple connections. ## Building Type-Safe SQL From 08abe3e40ce47713a0b86fb98d146ec7fbeac73a Mon Sep 17 00:00:00 2001 From: kujenga Date: Mon, 21 Sep 2015 11:19:58 -0400 Subject: [PATCH 0318/1046] propagate errors in Statement initialization --- Source/Core/Connection.swift | 26 +++++++++++++------------- Source/Core/Statement.swift | 4 ++-- Source/Typed/Query.swift | 36 ++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Source/Core/Connection.swift b/Source/Core/Connection.swift index fdd19083..a1fb68c8 100644 --- a/Source/Core/Connection.swift +++ b/Source/Core/Connection.swift @@ -140,9 +140,9 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: A prepared statement. - @warn_unused_result public func prepare(statement: String, _ bindings: Binding?...) -> Statement { - if !bindings.isEmpty { return prepare(statement, bindings) } - return Statement(self, statement) + @warn_unused_result public func prepare(statement: String, _ bindings: Binding?...) throws -> Statement { + if !bindings.isEmpty { return try prepare(statement, bindings) } + return try Statement(self, statement) } /// Prepares a single SQL statement and binds parameters to it. @@ -154,8 +154,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: A prepared statement. - @warn_unused_result public func prepare(statement: String, _ bindings: [Binding?]) -> Statement { - return prepare(statement).bind(bindings) + @warn_unused_result public func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement { + return try prepare(statement).bind(bindings) } /// Prepares a single SQL statement and binds parameters to it. @@ -167,8 +167,8 @@ public final class Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: A prepared statement. - @warn_unused_result public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement { - return prepare(statement).bind(bindings) + @warn_unused_result public func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement { + return try prepare(statement).bind(bindings) } // MARK: - Run @@ -230,8 +230,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { - return scalar(statement, bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? { + return try scalar(statement, bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -244,8 +244,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { - return prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? { + return try prepare(statement).scalar(bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -258,8 +258,8 @@ public final class Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { - return prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? { + return try prepare(statement).scalar(bindings) } // MARK: - Transactions diff --git a/Source/Core/Statement.swift b/Source/Core/Statement.swift index d735921c..1143ee7c 100644 --- a/Source/Core/Statement.swift +++ b/Source/Core/Statement.swift @@ -29,9 +29,9 @@ public final class Statement { private let connection: Connection - init(_ connection: Connection, _ SQL: String) { + init(_ connection: Connection, _ SQL: String) throws { self.connection = connection - try! connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) + try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } deinit { diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift index 544a8633..6eb3ec91 100644 --- a/Source/Typed/Query.swift +++ b/Source/Typed/Query.swift @@ -857,23 +857,23 @@ public struct Delete : ExpressionType { extension Connection { - public func prepare(query: QueryType) -> AnySequence { + public func prepare(query: QueryType) throws -> AnySequence { let expression = query.expression - let statement = prepare(expression.template, expression.bindings) + let statement = try prepare(expression.template, expression.bindings) - let columnNames: [String: Int] = { + let columnNames: [String: Int] = try { var (columnNames, idx) = ([String: Int](), 0) column: for each in query.clauses.select.columns ?? [Expression(literal: "*")] { var names = each.expression.template.characters.split { $0 == "." }.map(String.init) let column = names.removeLast() let namespace = names.joinWithSeparator(".") - func expandGlob(namespace: Bool) -> QueryType -> Void { - return { query in + func expandGlob(namespace: Bool) -> (QueryType throws -> Void) { + return { (query: QueryType) throws -> (Void) in var q = query.dynamicType.init(query.clauses.from.name, database: query.clauses.from.database) q.clauses.select = query.clauses.select let e = q.expression - var names = self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } + var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } for name in names { columnNames[name] = idx++ } } @@ -886,14 +886,14 @@ extension Connection { if !namespace.isEmpty { for q in queries { if q.tableName().expression.template == namespace { - expandGlob(true)(q) + try expandGlob(true)(q) continue column } } fatalError("no such table: \(namespace)") } for q in queries { - expandGlob(query.clauses.join.count > 0)(q) + try expandGlob(query.clauses.join.count > 0)(q) } continue } @@ -908,30 +908,30 @@ extension Connection { } } - public func scalar(query: ScalarQuery) -> V { + public func scalar(query: ScalarQuery) throws -> V { let expression = query.expression - return value(scalar(expression.template, expression.bindings)) + return value(try scalar(expression.template, expression.bindings)) } - public func scalar(query: ScalarQuery) -> V.ValueType? { + public func scalar(query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression - guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func scalar(query: Select) -> V { + public func scalar(query: Select) throws -> V { let expression = query.expression - return value(scalar(expression.template, expression.bindings)) + return value(try scalar(expression.template, expression.bindings)) } - public func scalar(query: Select) -> V.ValueType? { + public func scalar(query: Select) throws -> V.ValueType? { let expression = query.expression - guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func pluck(query: QueryType) -> Row? { - return prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + public func pluck(query: QueryType) throws -> Row? { + return try prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() } /// Runs an `Insert` query. From 2ade01f464590236870d73ade10323445d0bd7e8 Mon Sep 17 00:00:00 2001 From: kujenga Date: Wed, 7 Oct 2015 16:31:46 -0400 Subject: [PATCH 0319/1046] tests handle possible errors --- Tests/ConnectionTests.swift | 42 ++++++++++++++++++------------------- Tests/FTS4Tests.swift | 2 +- Tests/QueryTests.swift | 12 +++++------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Tests/ConnectionTests.swift b/Tests/ConnectionTests.swift index 646bbcd7..d004f771 100644 --- a/Tests/ConnectionTests.swift +++ b/Tests/ConnectionTests.swift @@ -72,10 +72,10 @@ class ConnectionTests : SQLiteTestCase { } func test_prepare_preparesAndReturnsStatements() { - _ = db.prepare("SELECT * FROM users WHERE admin = 0") - _ = db.prepare("SELECT * FROM users WHERE admin = ?", 0) - _ = db.prepare("SELECT * FROM users WHERE admin = ?", [0]) - _ = db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + _ = try! db.prepare("SELECT * FROM users WHERE admin = 0") + _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0) + _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", [0]) + _ = try! db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) } func test_run_preparesRunsAndReturnsStatements() { @@ -87,10 +87,10 @@ class ConnectionTests : SQLiteTestCase { } func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } @@ -113,7 +113,7 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_beginsAndCommitsTransactions() { - let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") try! db.transaction { try stmt.run() @@ -126,7 +126,7 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_beginsAndRollsTransactionsBack() { - let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { try db.transaction { @@ -162,7 +162,7 @@ class ConnectionTests : SQLiteTestCase { func test_savepoint_beginsAndRollsSavepointsBack() { let db = self.db - let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { try db.savepoint("1") { @@ -238,7 +238,7 @@ class ConnectionTests : SQLiteTestCase { try! db.transaction { try self.InsertUser("alice") } - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -252,7 +252,7 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -268,36 +268,36 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as? String) - XCTAssert(db.scalar("SELECT hello(NULL)") == nil) + XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String) + XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil) } func test_createFunction_createsQuotableFunction() { db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as? String) - XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) + XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil) } func test_createCollation_createsCollation() { db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { db.createCollation("NO DIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } func test_interrupt_interruptsLongRunningQuery() { @@ -307,7 +307,7 @@ class ConnectionTests : SQLiteTestCase { return nil } - let stmt = db.prepare("SELECT *, sleep(?) FROM users", 0.1) + let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) try! stmt.run() dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) diff --git a/Tests/FTS4Tests.swift b/Tests/FTS4Tests.swift index a94d9073..c36c5739 100644 --- a/Tests/FTS4Tests.swift +++ b/Tests/FTS4Tests.swift @@ -71,7 +71,7 @@ class FTS4IntegrationTests : SQLiteTestCase { AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") try! db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) + XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) } } diff --git a/Tests/QueryTests.swift b/Tests/QueryTests.swift index c6f8b016..97dec77b 100644 --- a/Tests/QueryTests.swift +++ b/Tests/QueryTests.swift @@ -278,7 +278,7 @@ class QueryIntegrationTests : SQLiteTestCase { // MARK: - func test_select() { - for _ in db.prepare(users) { + for _ in try! db.prepare(users) { // FIXME } @@ -288,22 +288,22 @@ class QueryIntegrationTests : SQLiteTestCase { let alice = try! db.run(users.insert(email <- "alice@example.com")) try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - for user in db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { user[users[managerId]] } } func test_scalar() { - XCTAssertEqual(0, db.scalar(users.count)) - XCTAssertEqual(false, db.scalar(users.exists)) + XCTAssertEqual(0, try! db.scalar(users.count)) + XCTAssertEqual(false, try! db.scalar(users.exists)) try! InsertUsers("alice") - XCTAssertEqual(1, db.scalar(users.select(id.average))) + XCTAssertEqual(1, try! db.scalar(users.select(id.average))) } func test_pluck() { let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, db.pluck(users)![id]) + XCTAssertEqual(rowid, try! db.pluck(users)![id]) } func test_insert() { From 2144caa4a17f985b8cf63e3254e2c0f6f7a0744e Mon Sep 17 00:00:00 2001 From: kujenga Date: Fri, 23 Oct 2015 15:54:41 -0400 Subject: [PATCH 0320/1046] update the readme and playground with error handling --- README.md | 14 +++++++------- SQLite.playground/Contents.swift | 4 ++-- Source/Core/Connection.swift | 12 ++++++------ Source/Typed/Query.swift | 20 ++++++++++---------- Tests/ConnectionTests.swift | 26 +++++++++++++------------- Tests/FTS4Tests.swift | 2 +- Tests/QueryTests.swift | 8 ++++---- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 8178a6c3..d83ef04e 100644 --- a/README.md +++ b/README.md @@ -56,15 +56,15 @@ let insert = users.insert(name <- "Alice", email <- "alice@mac.com") let rowid = try db.run(insert) // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') -for user in db.prepare(users) { - println("id: \(user[id]), name: \(user[name]), email: \(user[email])") +for user in try db.prepare(users) { + print("id: \(user[id]), name: \(user[name]), email: \(user[email])") // id: 1, name: Optional("Alice"), email: alice@mac.com } // SELECT * FROM "users" let alice = users.filter(id == rowid) -try db.run(alice.update(email <- email.replace("mac.com", "me.com"))) +try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') // WHERE ("id" = 1) @@ -79,17 +79,17 @@ SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. ``` swift -let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") +let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") for email in ["betty@icloud.com", "cathy@icloud.com"] { - stmt.run(email) + try stmt.run(email) } db.totalChanges // 3 db.changes // 1 db.lastInsertRowid // 3 -for row in db.prepare("SELECT id, email FROM users") { - println("id: \(row[0]), email: \(row[1])") +for row in try db.prepare("SELECT id, email FROM users") { + print("id: \(row[0]), email: \(row[1])") // id: Optional(2), email: Optional("betty@icloud.com") // id: Optional(3), email: Optional("cathy@icloud.com") } diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index adc2100a..2015531a 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -19,7 +19,7 @@ try! db.run(users.create { t in let rowid = try! db.run(users.insert(email <- "alice@mac.com")) let alice = users.filter(id == rowid) -for user in db.prepare(users) { +for user in try! db.prepare(users) { print("id: \(user[id]), email: \(user[email])") } @@ -37,7 +37,7 @@ try! db.run(emails.insert( let row = db.pluck(emails.match("hello")) -let query = db.prepare(emails.match("hello")) +let query = try! db.prepare(emails.match("hello")) for row in query { print(row[subject]) } diff --git a/Source/Core/Connection.swift b/Source/Core/Connection.swift index a1fb68c8..0243317f 100644 --- a/Source/Core/Connection.swift +++ b/Source/Core/Connection.swift @@ -230,8 +230,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? { - return try scalar(statement, bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { + return scalar(statement, bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -244,8 +244,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? { - return try prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { + return try! prepare(statement).scalar(bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -258,8 +258,8 @@ public final class Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? { - return try prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { + return try! prepare(statement).scalar(bindings) } // MARK: - Transactions diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift index 6eb3ec91..bd829480 100644 --- a/Source/Typed/Query.swift +++ b/Source/Typed/Query.swift @@ -908,30 +908,30 @@ extension Connection { } } - public func scalar(query: ScalarQuery) throws -> V { + public func scalar(query: ScalarQuery) -> V { let expression = query.expression - return value(try scalar(expression.template, expression.bindings)) + return value(scalar(expression.template, expression.bindings)) } - public func scalar(query: ScalarQuery) throws -> V.ValueType? { + public func scalar(query: ScalarQuery) -> V.ValueType? { let expression = query.expression - guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func scalar(query: Select) throws -> V { + public func scalar(query: Select) -> V { let expression = query.expression - return value(try scalar(expression.template, expression.bindings)) + return value(scalar(expression.template, expression.bindings)) } - public func scalar(query: Select) throws -> V.ValueType? { + public func scalar(query: Select) -> V.ValueType? { let expression = query.expression - guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func pluck(query: QueryType) throws -> Row? { - return try prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + public func pluck(query: QueryType) -> Row? { + return try! prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() } /// Runs an `Insert` query. diff --git a/Tests/ConnectionTests.swift b/Tests/ConnectionTests.swift index d004f771..aeec9b72 100644 --- a/Tests/ConnectionTests.swift +++ b/Tests/ConnectionTests.swift @@ -87,10 +87,10 @@ class ConnectionTests : SQLiteTestCase { } func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } @@ -238,7 +238,7 @@ class ConnectionTests : SQLiteTestCase { try! db.transaction { try self.InsertUser("alice") } - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -252,7 +252,7 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -268,36 +268,36 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) } } func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String) - XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil) + XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as? String) + XCTAssert(db.scalar("SELECT hello(NULL)") == nil) } func test_createFunction_createsQuotableFunction() { db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String) - XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil) + XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) } func test_createCollation_createsCollation() { db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { db.createCollation("NO DIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } func test_interrupt_interruptsLongRunningQuery() { diff --git a/Tests/FTS4Tests.swift b/Tests/FTS4Tests.swift index c36c5739..a94d9073 100644 --- a/Tests/FTS4Tests.swift +++ b/Tests/FTS4Tests.swift @@ -71,7 +71,7 @@ class FTS4IntegrationTests : SQLiteTestCase { AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") try! db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) + XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) } } diff --git a/Tests/QueryTests.swift b/Tests/QueryTests.swift index 97dec77b..e0d50207 100644 --- a/Tests/QueryTests.swift +++ b/Tests/QueryTests.swift @@ -294,16 +294,16 @@ class QueryIntegrationTests : SQLiteTestCase { } func test_scalar() { - XCTAssertEqual(0, try! db.scalar(users.count)) - XCTAssertEqual(false, try! db.scalar(users.exists)) + XCTAssertEqual(0, db.scalar(users.count)) + XCTAssertEqual(false, db.scalar(users.exists)) try! InsertUsers("alice") - XCTAssertEqual(1, try! db.scalar(users.select(id.average))) + XCTAssertEqual(1, db.scalar(users.select(id.average))) } func test_pluck() { let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, try! db.pluck(users)![id]) + XCTAssertEqual(rowid, db.pluck(users)![id]) } func test_insert() { From ad4b7a99a5f832fd9b7cb4026d513a5efa6fca48 Mon Sep 17 00:00:00 2001 From: Caesar Wirth Date: Wed, 6 Jan 2016 09:33:57 +0900 Subject: [PATCH 0321/1046] Fix query order documentation --- Source/Typed/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift index bd829480..57260f20 100644 --- a/Source/Typed/Query.swift +++ b/Source/Typed/Query.swift @@ -390,7 +390,7 @@ extension QueryType { /// /// let users = Table("users") /// let email = Expression("email") - /// let email = Expression("name") + /// let name = Expression("name") /// /// users.order(email.desc, name.asc) /// // SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC From 9f39683362596bba8f6c5d4821f0db09497e6369 Mon Sep 17 00:00:00 2001 From: Caesar Wirth Date: Wed, 6 Jan 2016 09:34:37 +0900 Subject: [PATCH 0322/1046] Add order method to query that takes an array arg --- Source/Typed/Query.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift index 57260f20..634b1b6a 100644 --- a/Source/Typed/Query.swift +++ b/Source/Typed/Query.swift @@ -399,6 +399,22 @@ extension QueryType { /// /// - Returns: A query with the given `ORDER BY` clause applied. public func order(by: Expressible...) -> Self { + return order(by) + } + + /// Sets an `ORDER BY` clause on the query. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// let name = Expression("name") + /// + /// users.order([email.desc, name.asc]) + /// // SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC + /// + /// - Parameter by: An ordered list of columns and directions to sort by. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func order(by: [Expressible]) -> Self { var query = self query.clauses.order = by return query From 722617703d6eb69d2e3cca11c66a9c05d2794e75 Mon Sep 17 00:00:00 2001 From: Caesar Wirth Date: Wed, 6 Jan 2016 09:36:59 +0900 Subject: [PATCH 0323/1046] Add test for order clause with array arg --- Tests/QueryTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/QueryTests.swift b/Tests/QueryTests.swift index e0d50207..b25f2620 100644 --- a/Tests/QueryTests.swift +++ b/Tests/QueryTests.swift @@ -120,6 +120,10 @@ class QueryTests : XCTestCase { AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) } + func test_order_withArrayExpressionNames_compilesOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order([age, email])) + } + func test_order_withExpressionAndSortDirection_compilesOrderClause() { // AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc)) } From 8d418fd863daee82bd3a07f7e59ac8285aa698d0 Mon Sep 17 00:00:00 2001 From: mikemee Date: Tue, 5 Jan 2016 20:57:29 -0800 Subject: [PATCH 0324/1046] Add Carthage install info to main documentation Copied the Carthage install from the readme for people who end up here directly. _Not terribly DRY, but short of trimming the front page or linking back to it, it's arguably an improvement._ --- Documentation/Index.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 174c9621..ac882a09 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1,6 +1,7 @@ # SQLite.swift Documentation - [Installation](#installation) + - [Carthage](#carthage) - [CocoaPods](#cocoapods) - [Manual](#manual) - [SQLCipher](#sqlcipher) @@ -68,6 +69,27 @@ > _Note:_ SQLite.swift requires Swift 2 (and [Xcode 7](https://developer.apple.com/xcode/downloads/)) or greater. +### Carthage + +[Carthage][] is a simple, decentralized dependency manager for Cocoa. To +install SQLite.swift with Carthage: + + 1. Make sure Carthage is [installed][Carthage Installation]. + + 2. Update your Cartfile to include the following: + + ``` + github "stephencelis/SQLite.swift" "master" + ``` + + 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. + + +[Carthage]: https://github.com/Carthage/Carthage +[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage +[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application + + ### CocoaPods [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: From cc986766c44d450b65b8cb7ed43e3df8f98c9a90 Mon Sep 17 00:00:00 2001 From: mikemee Date: Wed, 6 Jan 2016 16:07:15 -0800 Subject: [PATCH 0325/1046] Link to demo project, fixes #130 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d83ef04e..a0a18e2d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ interactively, from the Xcode project’s playground. ![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) +For a more comprehensive example, see [this article](http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html) and the [companion repository](https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master). ## Installation From ac0fa8f92b522ec242733e6eca12f0c80e1bb673 Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Fri, 8 Jan 2016 08:36:42 -0800 Subject: [PATCH 0326/1046] Prep for public release to CocoaPods * Remove `SQLiteCipher` (for now) * rebuild clean from "New Project" with Xcode 7.2 * fix build glitches with Carthage * fix build glitches with CocoaPods * separate build targets from test targets with @testable import * make sqlite3 a Swift system module so `import SQLite3` works in `.swift` files (for this framework) * use an Xcode project file instead of Workspace * folder structure rearranged * CocoaPod hassles: doesn't generate its own module.modulemap file, has spurious warning about missing include --- .gitignore | 25 +- .gitmodules | 3 - Makefile | 2 +- SQLite.swift.podspec | 35 +- SQLite.xcodeproj/project.pbxproj | 1350 +++++------------ .../xcshareddata/SQLite.xcscmblueprint | 30 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcschemes/SQLite Mac.xcscheme | 16 +- .../xcschemes/SQLite iOS.xcscheme | 24 +- .../xcschemes/SQLiteCipher Mac.xcscheme | 80 - .../xcschemes/SQLiteCipher iOS.xcscheme | 100 -- {Source => SQLite}/Core/Blob.swift | 2 + {Source => SQLite}/Core/Connection.swift | 1 + {Source => SQLite}/Core/SQLite-Bridging.m | 6 +- {Source => SQLite}/Core/Statement.swift | 2 + {Source => SQLite}/Core/Value.swift | 0 {Source => SQLite}/Core/fts3_tokenizer.h | 2 +- {Source => SQLite}/Extensions/FTS4.swift | 0 {Source => SQLite}/Extensions/R*Tree.swift | 0 {Source => SQLite}/Foundation.swift | 0 {Source => SQLite}/Helpers.swift | 2 + .../SQLite => SQLite}/Info.plist | 2 +- SQLite/SQLite.h | 11 + .../Typed/AggregateFunctions.swift | 0 {Source => SQLite}/Typed/Collation.swift | 0 {Source => SQLite}/Typed/CoreFunctions.swift | 0 .../Typed/CustomFunctions.swift | 0 {Source => SQLite}/Typed/Expression.swift | 0 {Source => SQLite}/Typed/Operators.swift | 0 {Source => SQLite}/Typed/Query.swift | 0 {Source => SQLite}/Typed/Schema.swift | 0 {Source => SQLite}/Typed/Setter.swift | 0 SQLiteCipher.swift.podspec | 17 - .../AggregateFunctionsTests.swift | 2 +- {Tests => SQLiteTests}/BlobTests.swift | 2 +- {Tests => SQLiteTests}/ConnectionTests.swift | 2 +- .../CoreFunctionsTests.swift | 2 +- .../CustomFunctionsTests.swift | 2 +- {Tests => SQLiteTests}/ExpressionTests.swift | 2 +- {Tests => SQLiteTests}/FTS4Tests.swift | 2 +- {Tests => SQLiteTests}/Info.plist | 0 {Tests => SQLiteTests}/OperatorsTests.swift | 2 +- {Tests => SQLiteTests}/QueryTests.swift | 2 +- {Tests => SQLiteTests}/R*TreeTests.swift | 2 +- {Tests => SQLiteTests}/SchemaTests.swift | 2 +- {Tests => SQLiteTests}/SetterTests.swift | 2 +- {Tests => SQLiteTests}/StatementTests.swift | 3 +- {Tests => SQLiteTests}/TestHelpers.swift | 2 +- {Tests => SQLiteTests}/ValueTests.swift | 2 +- Source/Cipher/Cipher.swift | 39 - Source/Core/SQLite-Bridging.h | 37 - Supporting Files/Base.xcconfig | 5 - Supporting Files/SQLite/SQLite.h | 9 - Supporting Files/SQLite/SQLite.xcconfig | 5 - Supporting Files/SQLite/module.modulemap | 12 - Supporting Files/SQLiteCipher/Info.plist | 26 - Supporting Files/SQLiteCipher/SQLiteCipher.h | 9 - .../SQLiteCipher/SQLiteCipher.xcconfig | 5 - .../SQLiteCipher/module.modulemap | 12 - Supporting Files/Test.xcconfig | 1 - Supporting Files/podspec.rb | 25 - Tests/CipherTests.swift | 48 - Vendor/sqlcipher | 1 - module.modulemap | 12 + 64 files changed, 516 insertions(+), 1479 deletions(-) delete mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint delete mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme delete mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme rename {Source => SQLite}/Core/Blob.swift (99%) rename {Source => SQLite}/Core/Connection.swift (99%) rename {Source => SQLite}/Core/SQLite-Bridging.m (98%) rename {Source => SQLite}/Core/Statement.swift (99%) rename {Source => SQLite}/Core/Value.swift (100%) rename {Source => SQLite}/Core/fts3_tokenizer.h (99%) rename {Source => SQLite}/Extensions/FTS4.swift (100%) rename {Source => SQLite}/Extensions/R*Tree.swift (100%) rename {Source => SQLite}/Foundation.swift (100%) rename {Source => SQLite}/Helpers.swift (99%) rename {Supporting Files/SQLite => SQLite}/Info.plist (97%) create mode 100644 SQLite/SQLite.h rename {Source => SQLite}/Typed/AggregateFunctions.swift (100%) rename {Source => SQLite}/Typed/Collation.swift (100%) rename {Source => SQLite}/Typed/CoreFunctions.swift (100%) rename {Source => SQLite}/Typed/CustomFunctions.swift (100%) rename {Source => SQLite}/Typed/Expression.swift (100%) rename {Source => SQLite}/Typed/Operators.swift (100%) rename {Source => SQLite}/Typed/Query.swift (100%) rename {Source => SQLite}/Typed/Schema.swift (100%) rename {Source => SQLite}/Typed/Setter.swift (100%) delete mode 100644 SQLiteCipher.swift.podspec rename {Tests => SQLiteTests}/AggregateFunctionsTests.swift (99%) rename {Tests => SQLiteTests}/BlobTests.swift (90%) rename {Tests => SQLiteTests}/ConnectionTests.swift (99%) rename {Tests => SQLiteTests}/CoreFunctionsTests.swift (99%) rename {Tests => SQLiteTests}/CustomFunctionsTests.swift (71%) rename {Tests => SQLiteTests}/ExpressionTests.swift (69%) rename {Tests => SQLiteTests}/FTS4Tests.swift (99%) rename {Tests => SQLiteTests}/Info.plist (100%) rename {Tests => SQLiteTests}/OperatorsTests.swift (99%) rename {Tests => SQLiteTests}/QueryTests.swift (99%) rename {Tests => SQLiteTests}/R*TreeTests.swift (96%) rename {Tests => SQLiteTests}/SchemaTests.swift (99%) rename {Tests => SQLiteTests}/SetterTests.swift (99%) rename {Tests => SQLiteTests}/StatementTests.swift (68%) rename {Tests => SQLiteTests}/TestHelpers.swift (99%) rename {Tests => SQLiteTests}/ValueTests.swift (67%) delete mode 100644 Source/Cipher/Cipher.swift delete mode 100644 Source/Core/SQLite-Bridging.h delete mode 100644 Supporting Files/Base.xcconfig delete mode 100644 Supporting Files/SQLite/SQLite.h delete mode 100644 Supporting Files/SQLite/SQLite.xcconfig delete mode 100644 Supporting Files/SQLite/module.modulemap delete mode 100644 Supporting Files/SQLiteCipher/Info.plist delete mode 100644 Supporting Files/SQLiteCipher/SQLiteCipher.h delete mode 100644 Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig delete mode 100644 Supporting Files/SQLiteCipher/module.modulemap delete mode 100644 Supporting Files/Test.xcconfig delete mode 100644 Supporting Files/podspec.rb delete mode 100644 Tests/CipherTests.swift delete mode 160000 Vendor/sqlcipher create mode 100644 module.modulemap diff --git a/.gitignore b/.gitignore index 18335db4..99e4f5e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,23 @@ +# OS X +.DS_Store + # Xcode build/ -timeline.xctimeline -xcuserdata/ - -# System -.DS_Store +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate # Carthage -Carthage/ +Carthage diff --git a/.gitmodules b/.gitmodules index 2c40393d..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "sqlcipher"] - path = Vendor/sqlcipher - url = https://github.com/sqlcipher/sqlcipher.git diff --git a/Makefile b/Makefile index ff0a2054..5a452b87 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,6 @@ repl: swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' sloc: - @zsh -c "grep -vE '^ *//|^$$' SQLite/*.{swift,h,c} | wc -l" + @zsh -c "grep -vE '^ *//|^$$' SQLite/*/*.{swift,h,m} | wc -l" .PHONY: test coverage clean repl sloc diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 34f4c3d1..72a0d562 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,17 +1,32 @@ -$LOAD_PATH << '.' -require 'Supporting Files/podspec.rb' +# +# `pod lib lint SQLite.swift.podspec' fails - see +# https://github.com/CocoaPods/CocoaPods/issues/4607 +# -Pod::Spec.new do |spec| - spec.name = 'SQLite.swift' - spec.summary = 'A type-safe, Swift-language layer over SQLite3.' +Pod::Spec.new do |s| + s.name = "SQLite.swift" + s.version = "0.9.0" + s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." - spec.description = <<-DESC + s.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and intent. - DESC + DESC - apply_shared_config spec, 'SQLite' + s.homepage = "https://github.com/stephencelis/SQLite.swift" + s.license = 'MIT' + s.author = { "Stephen Celis" => "stephen@stephencelis.com" } + s.source = { :git => "https://github.com/stephencelis/SQLite.swift.git", :tag => s.version.to_s } + s.social_media_url = 'https://twitter.com/stephencelis' + + s.module_name = 'SQLite' + s.module_map = 'module.modulemap' + + s.source_files = 'SQLite/**/*' + + # make the sqlite3 C library behave like a module + s.libraries = 'sqlite3' + s.xcconfig = { 'SWIFT_INCLUDE_PATHS' => '${PODS_ROOT}/SQLite.swift/SQLite3' } + s.preserve_path = 'SQLite3/*' - spec.exclude_files = 'Source/Cipher/Cipher.swift' - spec.library = 'sqlite3' end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 74ff688f..5d2d8bf4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -3,529 +3,346 @@ archiveVersion = 1; classes = { }; - objectVersion = 47; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ - DC0B8BAB1B485B620084D886 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; - DC0B8BAD1B497A400084D886 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; - DC2609C01B47504200C3E6B5 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; - DC2609C21B475C9900C3E6B5 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; - DC2609C41B475CA500C3E6B5 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; - DC2B29ED1B26F718001C60EA /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2B29E21B26F718001C60EA /* SQLite.framework */; }; - DC2B2A161B26FE7F001C60EA /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; - DC2D38BE1B2C7A73003EE2F2 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; - DC303D6E1B2CFA1F00469AEA /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; - DC303D701B2CFA4700469AEA /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; - DC303D731B2E2ECD00469AEA /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D711B2E2E8600469AEA /* ConnectionTests.swift */; }; - DC303D761B2E306000469AEA /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D751B2E306000469AEA /* TestHelpers.swift */; }; - DC32A7861B575FD7002FFBE2 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; - DC32A7871B575FD7002FFBE2 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; - DC32A7961B576715002FFBE2 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; - DC32A7971B57672E002FFBE2 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; - DC32A7981B57672E002FFBE2 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; - DC32A7991B57672E002FFBE2 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; - DC32A79A1B57672E002FFBE2 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC32A79B1B57672E002FFBE2 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; - DC32A79C1B57672E002FFBE2 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; - DC32A79D1B57672E002FFBE2 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; - DC32A79E1B57672E002FFBE2 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; - DC32A79F1B57672E002FFBE2 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; - DC32A7A01B57672E002FFBE2 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; - DC32A7A11B57672E002FFBE2 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; - DC32A7A21B57672E002FFBE2 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; - DC32A7A31B57672E002FFBE2 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; - DC32A7A41B57672E002FFBE2 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; - DC32A7A51B57672E002FFBE2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; - DC32A7A91B5AAE75002FFBE2 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; - DC32A7AA1B5AAE76002FFBE2 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; - DC32A7AE1B5AC2C5002FFBE2 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A78D1B576209002FFBE2 /* StatementTests.swift */; }; - DC32A7AF1B5AC2C5002FFBE2 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7901B576219002FFBE2 /* ValueTests.swift */; }; - DC32A7B01B5AC2C5002FFBE2 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7931B57622C002FFBE2 /* BlobTests.swift */; }; - DC32A7B11B5AC2C5002FFBE2 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */; }; - DC32A7B21B5AC2C5002FFBE2 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B81B4A012A0010A7E9 /* QueryTests.swift */; }; - DC32A7B31B5AC2C5002FFBE2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A91B53216000B12E66 /* SchemaTests.swift */; }; - DC32A7B41B5AC2C5002FFBE2 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */; }; - DC32A7B51B5AC2C5002FFBE2 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */; }; - DC32A7B61B5AC2C5002FFBE2 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */; }; - DC32A7B71B5AC2C5002FFBE2 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B61B49DCE10010A7E9 /* SetterTests.swift */; }; - DC32A7B81B5AC2C5002FFBE2 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */; }; - DC32A7B91B5AC2C5002FFBE2 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */; }; - DC32A7BA1B5C1769002FFBE2 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A78D1B576209002FFBE2 /* StatementTests.swift */; }; - DC32A7BB1B5C1769002FFBE2 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7901B576219002FFBE2 /* ValueTests.swift */; }; - DC32A7BC1B5C1769002FFBE2 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7931B57622C002FFBE2 /* BlobTests.swift */; }; - DC32A7BD1B5C1769002FFBE2 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */; }; - DC32A7BE1B5C1769002FFBE2 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B81B4A012A0010A7E9 /* QueryTests.swift */; }; - DC32A7BF1B5C1769002FFBE2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A91B53216000B12E66 /* SchemaTests.swift */; }; - DC32A7C01B5C1769002FFBE2 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */; }; - DC32A7C11B5C1769002FFBE2 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */; }; - DC32A7C21B5C1769002FFBE2 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */; }; - DC32A7C31B5C1769002FFBE2 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B61B49DCE10010A7E9 /* SetterTests.swift */; }; - DC32A7C41B5C1769002FFBE2 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */; }; - DC32A7C51B5C1769002FFBE2 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */; }; - DC32A7C91B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */; }; - DC32A7CA1B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */; }; - DC461BB81B2E687900E9DABD /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC461BB91B2E687900E9DABD /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; - DC461BBB1B2E68BC00E9DABD /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; - DC47D0661B52C9D200B12E66 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; - DC47D0671B52C9D200B12E66 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; - DC47D0A61B52F5B900B12E66 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; - DC47D0A71B52F5B900B12E66 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; - DC6C03541B74451F00EFC04E /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BB721B7434B70040D364 /* Cipher.swift */; }; - DC6C03551B74453300EFC04E /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; - DC6C03561B74453300EFC04E /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; - DC6C03571B74453300EFC04E /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; - DC6C03581B74453300EFC04E /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; - DC6C03591B74454100EFC04E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; - DC6C035A1B74454100EFC04E /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; - DC6C035B1B74454100EFC04E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; - DC6C035C1B74454100EFC04E /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; - DC6C035D1B74454100EFC04E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; - DC6C035E1B74454100EFC04E /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; - DC6C035F1B74454100EFC04E /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; - DC6C03601B74454100EFC04E /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; - DC6C03611B74454100EFC04E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; - DC6C03621B74454100EFC04E /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; - DC6C03631B74454100EFC04E /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; - DC6C03641B74454100EFC04E /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; - DC6C03651B74454100EFC04E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; - DC6C03661B74454D00EFC04E /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; - DC6C03671B74454D00EFC04E /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; - DC6C03681B74454D00EFC04E /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; - DC6C03691B74454D00EFC04E /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; - DC6C036A1B74454D00EFC04E /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; - DC6C036B1B74454D00EFC04E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; - DC6C036C1B74454D00EFC04E /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; - DC6C036D1B74454D00EFC04E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; - DC6C036E1B74454D00EFC04E /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; - DC6C036F1B74454D00EFC04E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; - DC6C03701B74454D00EFC04E /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; - DC6C03711B74454D00EFC04E /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; - DC6C03721B74454D00EFC04E /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; - DC6C03731B74454D00EFC04E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; - DC6C03741B74454D00EFC04E /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; - DC6C03751B74454D00EFC04E /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; - DC6C03761B74454D00EFC04E /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; - DC6C03771B74454D00EFC04E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; - DC6C03791B74459C00EFC04E /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC6C037A1B74459F00EFC04E /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC6C037B1B7445A100EFC04E /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; - DC6C037C1B7445AC00EFC04E /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC6C037D1B7445B200EFC04E /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC6C037E1B7445B400EFC04E /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; - DC6C037F1B74461300EFC04E /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; - DC72349E1B49D3AB007F513E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; - DC7253B61B52ADEC009B38B1 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC7253AC1B52ADEB009B38B1 /* SQLite.framework */; }; - DC7489A41B69A66C0032CF28 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC7489A51B69A66C0032CF28 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC795C561B55AC8100DA0EF9 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D751B2E306000469AEA /* TestHelpers.swift */; }; - DC795C571B55AC8100DA0EF9 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D711B2E2E8600469AEA /* ConnectionTests.swift */; }; - DCA7B9E81B4D4F30006A9F04 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; - DCAA22861B4A9CB2005E00A2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; - DCDF55B31B49ACBC00E1A168 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; - DCE7BB841B7434F90040D364 /* SQLiteCipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */; }; - DCE7BB911B74350E0040D364 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BB721B7434B70040D364 /* Cipher.swift */; }; - DCE7BB9C1B7435E90040D364 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BB971B7435E90040D364 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCE7BB9D1B7435E90040D364 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BB971B7435E90040D364 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCE7BBA21B7436140040D364 /* SQLiteCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BBA01B7436140040D364 /* SQLiteCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCE7BBBA1B7438620040D364 /* SQLiteCipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */; }; - DCE7BBC71B7438740040D364 /* SQLiteCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BBA01B7436140040D364 /* SQLiteCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCE7BBC91B743A320040D364 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BBC81B743A320040D364 /* CipherTests.swift */; }; - DCE7BBCA1B743A320040D364 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BBC81B743A320040D364 /* CipherTests.swift */; }; - DCE7BBD41B743C970040D364 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB711B7434310040D364 /* libsqlcipher.a */; }; - DCE7BBD51B743C9C0040D364 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB711B7434310040D364 /* libsqlcipher.a */; }; + EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; + EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; + EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; + EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; + EE247B081C3F06E900AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; + EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; + EE247B0A1C3F06E900AE3E12 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* R*Tree.swift */; }; + EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; + EE247B0C1C3F06E900AE3E12 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; + EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; + EE247B0E1C3F06E900AE3E12 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; + EE247B0F1C3F06E900AE3E12 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; + EE247B101C3F06E900AE3E12 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */; }; + EE247B111C3F06E900AE3E12 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFE1C3F06E900AE3E12 /* Expression.swift */; }; + EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFF1C3F06E900AE3E12 /* Operators.swift */; }; + EE247B131C3F06E900AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; + EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; + EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; + EE247B171C3F127200AE3E12 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; + EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; + EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; + EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; + EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; + EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; + EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; + EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; + EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; + EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; + EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; + EE247B301C3F141E00AE3E12 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */; }; + EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; + EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; + EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; + EE247B461C3F3ED000AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */; }; + EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; + EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; + EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; + EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; + EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; + EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; + EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; + EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; + EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; + EE247B5C1C3F3FC700AE3E12 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */; }; + EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; + EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; + EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; + EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; + EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; + EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; + EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; + EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; + EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; + EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; + EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; + EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; + EE247B6C1C3F3FEC00AE3E12 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* R*Tree.swift */; }; + EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; + EE247B6E1C3F3FEC00AE3E12 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; + EE247B6F1C3F3FEC00AE3E12 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; + EE247B701C3F3FEC00AE3E12 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */; }; + EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFE1C3F06E900AE3E12 /* Expression.swift */; }; + EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFF1C3F06E900AE3E12 /* Operators.swift */; }; + EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; + EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; + EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - DC2B29EE1B26F718001C60EA /* PBXContainerItemProxy */ = { + EE247ADF1C3F04ED00AE3E12 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; proxyType = 1; - remoteGlobalIDString = DC2B29E11B26F718001C60EA; + remoteGlobalIDString = EE247AD21C3F04ED00AE3E12; remoteInfo = SQLite; }; - DC7253B71B52ADEC009B38B1 /* PBXContainerItemProxy */ = { + EE247B471C3F3ED000AE3E12 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; proxyType = 1; - remoteGlobalIDString = DC7253AB1B52ADEB009B38B1; - remoteInfo = "SQLite Mac"; - }; - DCE7BB701B7434310040D364 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = D2AAC046055464E500DB518D; - remoteInfo = sqlcipher; - }; - DCE7BB851B7434F90040D364 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DC2B29D91B26F718001C60EA /* Project object */; - proxyType = 1; - remoteGlobalIDString = DCE7BB791B7434F90040D364; - remoteInfo = SQLiteCipher; - }; - DCE7BBA51B7436D10040D364 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = D2AAC045055464E500DB518D; - remoteInfo = sqlcipher; - }; - DCE7BBBB1B7438620040D364 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DC2B29D91B26F718001C60EA /* Project object */; - proxyType = 1; - remoteGlobalIDString = DCE7BBAF1B7438610040D364; - remoteInfo = "SQLiteCipher Mac"; - }; - DCE7BBD01B743C220040D364 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = D2AAC045055464E500DB518D; - remoteInfo = sqlcipher; + remoteGlobalIDString = EE247B3B1C3F3ED000AE3E12; + remoteInfo = SQlite; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; - DC0B8BAC1B497A400084D886 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; - DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; - DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; - DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; - DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; - DC2609C11B475C9900C3E6B5 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; - DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; - DC2763B61B49DCE10010A7E9 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; - DC2763B81B4A012A0010A7E9 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - DC2B29E21B26F718001C60EA /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DC2B2A011B26FD44001C60EA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - DC2B2A151B26FE7F001C60EA /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; - DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; - DC303D6D1B2CFA1F00469AEA /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; - DC303D6F1B2CFA4700469AEA /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; - DC303D711B2E2E8600469AEA /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; - DC303D751B2E306000469AEA /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; - DC32A7851B575FD7002FFBE2 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; - DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; - DC32A78D1B576209002FFBE2 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; - DC32A7901B576219002FFBE2 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; - DC32A7931B57622C002FFBE2 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; - DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; - DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*TreeTests.swift"; sourceTree = ""; }; - DC32A7C61B5C26A6002FFBE2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; - DC40A4C61B7D2B3C005BA12D /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; - DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; - DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; - DC47D0651B52C9D200B12E66 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; - DC47D0A51B52F5B900B12E66 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; - DC47D0A91B53216000B12E66 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; - DC72349D1B49D3AB007F513E /* Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setter.swift; sourceTree = ""; }; - DC7253AC1B52ADEB009B38B1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Mac Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DC7489A31B69A66C0032CF28 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; - DCA7B9E71B4D4F30006A9F04 /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; - DCAA22851B4A9CB2005E00A2 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; - DCDF55B21B49ACBC00E1A168 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; - DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = Vendor/sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; - DCE7BB721B7434B70040D364 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; - DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DCE7BB951B7435E90040D364 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; - DCE7BB961B7435E90040D364 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DCE7BB971B7435E90040D364 /* SQLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; - DCE7BB9F1B7436140040D364 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DCE7BBA01B7436140040D364 /* SQLiteCipher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLiteCipher.h; sourceTree = ""; }; - DCE7BBA91B7437780040D364 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; - DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SQLiteCipher.xcconfig; sourceTree = ""; }; - DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Mac Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DCE7BBC81B743A320040D364 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; - DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; + EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; + EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; + EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; + EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; + EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; + EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; + EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; + EE247AF61C3F06E900AE3E12 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; + EE247AF71C3F06E900AE3E12 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; + EE247AF81C3F06E900AE3E12 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; + EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; + EE247AFB1C3F06E900AE3E12 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; + EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; + EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; + EE247AFE1C3F06E900AE3E12 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; + EE247AFF1C3F06E900AE3E12 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; + EE247B001C3F06E900AE3E12 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; + EE247B011C3F06E900AE3E12 /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; + EE247B021C3F06E900AE3E12 /* Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setter.swift; sourceTree = ""; }; + EE247B161C3F127200AE3E12 /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; + EE247B181C3F134A00AE3E12 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; + EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; + EE247B1B1C3F137700AE3E12 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; + EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; + EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; + EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; + EE247B201C3F137700AE3E12 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; + EE247B211C3F137700AE3E12 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; + EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; + EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; + EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*TreeTests.swift"; sourceTree = ""; }; + EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; + EE247B321C3F142E00AE3E12 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; + EE247B331C3F142E00AE3E12 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; + EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + EE247B771C3F40D700AE3E12 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; + EE247B8C1C3F821200AE3E12 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; + EE247B8D1C3F821200AE3E12 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + EE247B8F1C3F822500AE3E12 /* Index.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Index.md; sourceTree = ""; }; + EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; + EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; + EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SQLite.swift.podspec; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - DC2B29DE1B26F718001C60EA /* Frameworks */ = { + EE247ACF1C3F04ED00AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DC2B29E91B26F718001C60EA /* Frameworks */ = { + EE247ADA1C3F04ED00AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC2B29ED1B26F718001C60EA /* SQLite.framework in Frameworks */, + EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DC7253A81B52ADEB009B38B1 /* Frameworks */ = { + EE247B381C3F3ED000AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DC7253B21B52ADEC009B38B1 /* Frameworks */ = { + EE247B421C3F3ED000AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC7253B61B52ADEC009B38B1 /* SQLite.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BB761B7434F90040D364 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BBD41B743C970040D364 /* libsqlcipher.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BB801B7434F90040D364 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BB841B7434F90040D364 /* SQLiteCipher.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BBAC1B7438610040D364 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BBD51B743C9C0040D364 /* libsqlcipher.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BBB61B7438620040D364 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BBBA1B7438620040D364 /* SQLiteCipher.framework in Frameworks */, + EE247B461C3F3ED000AE3E12 /* SQLite.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - DC2B29D81B26F718001C60EA = { + EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( - DC32A7C61B5C26A6002FFBE2 /* README.md */, - DC40A4C61B7D2B3C005BA12D /* SQLite.playground */, - DC2B29FF1B26FD44001C60EA /* Source */, - DC2B2A001B26FD44001C60EA /* Tests */, - DC2B2A0C1B26FDEF001C60EA /* Supporting Files */, - DC2B29E31B26F718001C60EA /* Products */, - DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */, + EE247AD51C3F04ED00AE3E12 /* SQLite */, + EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, + EE247B8A1C3F81D000AE3E12 /* Metadata */, + EE247AD41C3F04ED00AE3E12 /* Products */, ); sourceTree = ""; }; - DC2B29E31B26F718001C60EA /* Products */ = { + EE247AD41C3F04ED00AE3E12 /* Products */ = { isa = PBXGroup; children = ( - DC2B29E21B26F718001C60EA /* SQLite.framework */, - DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */, - DC7253AC1B52ADEB009B38B1 /* SQLite.framework */, - DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */, - DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */, - DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */, - DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */, - DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */, + EE247AD31C3F04ED00AE3E12 /* SQLite.framework */, + EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */, + EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */, + EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */, ); name = Products; sourceTree = ""; }; - DC2B29FF1B26FD44001C60EA /* Source */ = { + EE247AD51C3F04ED00AE3E12 /* SQLite */ = { isa = PBXGroup; children = ( - DC2B2A141B26FE5C001C60EA /* Core */, - DC2D38BC1B2C7A39003EE2F2 /* Typed */, - DC2D38BA1B2C7A39003EE2F2 /* Extensions */, - DC2D38B91B2C7A39003EE2F2 /* Cipher */, - DC47D0A51B52F5B900B12E66 /* Foundation.swift */, - DCAA22851B4A9CB2005E00A2 /* Helpers.swift */, + EE247AD61C3F04ED00AE3E12 /* SQLite.h */, + EE247AF71C3F06E900AE3E12 /* Foundation.swift */, + EE247AF81C3F06E900AE3E12 /* Helpers.swift */, + EE247AD81C3F04ED00AE3E12 /* Info.plist */, + EE247AED1C3F06E900AE3E12 /* Core */, + EE247AF41C3F06E900AE3E12 /* Extensions */, + EE247AF91C3F06E900AE3E12 /* Typed */, ); - path = Source; - sourceTree = ""; - }; - DC2B2A001B26FD44001C60EA /* Tests */ = { - isa = PBXGroup; - children = ( - DC2B2A011B26FD44001C60EA /* Info.plist */, - DC303D751B2E306000469AEA /* TestHelpers.swift */, - DC303D711B2E2E8600469AEA /* ConnectionTests.swift */, - DC32A78D1B576209002FFBE2 /* StatementTests.swift */, - DC32A7901B576219002FFBE2 /* ValueTests.swift */, - DC32A7931B57622C002FFBE2 /* BlobTests.swift */, - DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */, - DC2763B81B4A012A0010A7E9 /* QueryTests.swift */, - DC47D0A91B53216000B12E66 /* SchemaTests.swift */, - DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */, - DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */, - DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */, - DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */, - DC2763B61B49DCE10010A7E9 /* SetterTests.swift */, - DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */, - DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */, - DCE7BBC81B743A320040D364 /* CipherTests.swift */, - ); - path = Tests; + path = SQLite; sourceTree = ""; }; - DC2B2A0C1B26FDEF001C60EA /* Supporting Files */ = { + EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( - DCE7BBA91B7437780040D364 /* Base.xcconfig */, - DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */, - DCE7BB941B7435E90040D364 /* SQLite */, - DCE7BB9E1B7436140040D364 /* SQLiteCipher */, - ); - path = "Supporting Files"; + EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, + EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, + EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, + EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, + EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, + EE247B201C3F137700AE3E12 /* ExpressionTests.swift */, + EE247B211C3F137700AE3E12 /* FTS4Tests.swift */, + EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */, + EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */, + EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */, + EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */, + EE247B181C3F134A00AE3E12 /* SetterTests.swift */, + EE247B321C3F142E00AE3E12 /* StatementTests.swift */, + EE247B331C3F142E00AE3E12 /* ValueTests.swift */, + EE247B161C3F127200AE3E12 /* TestHelpers.swift */, + EE247AE41C3F04ED00AE3E12 /* Info.plist */, + ); + path = SQLiteTests; sourceTree = ""; }; - DC2B2A141B26FE5C001C60EA /* Core */ = { + EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - DC2B2A151B26FE7F001C60EA /* Connection.swift */, - DC303D6D1B2CFA1F00469AEA /* Statement.swift */, - DC303D6F1B2CFA4700469AEA /* Value.swift */, - DC47D0651B52C9D200B12E66 /* Blob.swift */, - DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */, - DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */, - DC7489A31B69A66C0032CF28 /* sqlite3.h */, - DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */, + EE247AEE1C3F06E900AE3E12 /* Blob.swift */, + EE247AEF1C3F06E900AE3E12 /* Connection.swift */, + EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, + EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, + EE247AF21C3F06E900AE3E12 /* Statement.swift */, + EE247AF31C3F06E900AE3E12 /* Value.swift */, ); path = Core; sourceTree = ""; }; - DC2D38B91B2C7A39003EE2F2 /* Cipher */ = { - isa = PBXGroup; - children = ( - DCE7BB721B7434B70040D364 /* Cipher.swift */, - ); - path = Cipher; - sourceTree = ""; - }; - DC2D38BA1B2C7A39003EE2F2 /* Extensions */ = { + EE247AF41C3F06E900AE3E12 /* Extensions */ = { isa = PBXGroup; children = ( - DC32A7851B575FD7002FFBE2 /* FTS4.swift */, - DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */, + EE247AF51C3F06E900AE3E12 /* FTS4.swift */, + EE247AF61C3F06E900AE3E12 /* R*Tree.swift */, ); path = Extensions; sourceTree = ""; }; - DC2D38BC1B2C7A39003EE2F2 /* Typed */ = { + EE247AF91C3F06E900AE3E12 /* Typed */ = { isa = PBXGroup; children = ( - DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */, - DC0B8BAC1B497A400084D886 /* Query.swift */, - DCA7B9E71B4D4F30006A9F04 /* Schema.swift */, - DCDF55B21B49ACBC00E1A168 /* Collation.swift */, - DC2609C11B475C9900C3E6B5 /* Operators.swift */, - DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */, - DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */, - DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */, - DC72349D1B49D3AB007F513E /* Setter.swift */, + EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */, + EE247AFB1C3F06E900AE3E12 /* Collation.swift */, + EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */, + EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */, + EE247AFE1C3F06E900AE3E12 /* Expression.swift */, + EE247AFF1C3F06E900AE3E12 /* Operators.swift */, + EE247B001C3F06E900AE3E12 /* Query.swift */, + EE247B011C3F06E900AE3E12 /* Schema.swift */, + EE247B021C3F06E900AE3E12 /* Setter.swift */, ); path = Typed; sourceTree = ""; }; - DCE7BB6C1B7434310040D364 /* Products */ = { + EE247B8A1C3F81D000AE3E12 /* Metadata */ = { isa = PBXGroup; children = ( - DCE7BB711B7434310040D364 /* libsqlcipher.a */, - ); - name = Products; + EE247B771C3F40D700AE3E12 /* README.md */, + EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, + EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, + EE247B8C1C3F821200AE3E12 /* .travis.yml */, + EE247B8D1C3F821200AE3E12 /* Makefile */, + EE247B8E1C3F822500AE3E12 /* Documentation */, + ); + name = Metadata; sourceTree = ""; }; - DCE7BB941B7435E90040D364 /* SQLite */ = { + EE247B8E1C3F822500AE3E12 /* Documentation */ = { isa = PBXGroup; children = ( - DCE7BB971B7435E90040D364 /* SQLite.h */, - DCE7BB961B7435E90040D364 /* Info.plist */, - DCE7BB951B7435E90040D364 /* SQLite.xcconfig */, + EE247B8F1C3F822500AE3E12 /* Index.md */, + EE247B901C3F822500AE3E12 /* Resources */, ); - path = SQLite; + path = Documentation; sourceTree = ""; }; - DCE7BB9E1B7436140040D364 /* SQLiteCipher */ = { + EE247B901C3F822500AE3E12 /* Resources */ = { isa = PBXGroup; children = ( - DCE7BBA01B7436140040D364 /* SQLiteCipher.h */, - DCE7BB9F1B7436140040D364 /* Info.plist */, - DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */, + EE247B911C3F822500AE3E12 /* installation@2x.png */, + EE247B921C3F822600AE3E12 /* playground@2x.png */, ); - path = SQLiteCipher; + path = Resources; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - DC2B29DF1B26F718001C60EA /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BB9C1B7435E90040D364 /* SQLite.h in Headers */, - DC461BB81B2E687900E9DABD /* SQLite-Bridging.h in Headers */, - DC7489A41B69A66C0032CF28 /* sqlite3.h in Headers */, - DC461BBB1B2E68BC00E9DABD /* fts3_tokenizer.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC7253A91B52ADEB009B38B1 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BB9D1B7435E90040D364 /* SQLite.h in Headers */, - DC32A79A1B57672E002FFBE2 /* SQLite-Bridging.h in Headers */, - DC32A79C1B57672E002FFBE2 /* fts3_tokenizer.h in Headers */, - DC7489A51B69A66C0032CF28 /* sqlite3.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BB771B7434F90040D364 /* Headers */ = { + EE247AD01C3F04ED00AE3E12 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DCE7BBA21B7436140040D364 /* SQLiteCipher.h in Headers */, - DC6C03791B74459C00EFC04E /* SQLite-Bridging.h in Headers */, - DC6C037A1B74459F00EFC04E /* sqlite3.h in Headers */, - DC6C037B1B7445A100EFC04E /* fts3_tokenizer.h in Headers */, + EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, + EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BBAD1B7438610040D364 /* Headers */ = { + EE247B391C3F3ED000AE3E12 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DCE7BBC71B7438740040D364 /* SQLiteCipher.h in Headers */, - DC6C037C1B7445AC00EFC04E /* SQLite-Bridging.h in Headers */, - DC6C037D1B7445B200EFC04E /* sqlite3.h in Headers */, - DC6C037E1B7445B400EFC04E /* fts3_tokenizer.h in Headers */, + EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, + EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - DC2B29E11B26F718001C60EA /* SQLite iOS */ = { + EE247AD21C3F04ED00AE3E12 /* SQLite iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = DC2B29F61B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS" */; + buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; buildPhases = ( - DC2B29DD1B26F718001C60EA /* Sources */, - DC2B29DE1B26F718001C60EA /* Frameworks */, - DC2B29DF1B26F718001C60EA /* Headers */, - DC2B29E01B26F718001C60EA /* Resources */, + EE247ACE1C3F04ED00AE3E12 /* Sources */, + EE247ACF1C3F04ED00AE3E12 /* Frameworks */, + EE247AD01C3F04ED00AE3E12 /* Headers */, + EE247AD11C3F04ED00AE3E12 /* Resources */, ); buildRules = ( ); @@ -533,264 +350,129 @@ ); name = "SQLite iOS"; productName = SQLite; - productReference = DC2B29E21B26F718001C60EA /* SQLite.framework */; + productReference = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; - DC2B29EB1B26F718001C60EA /* SQLite iOS Tests */ = { + EE247ADC1C3F04ED00AE3E12 /* SQLiteTests iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = DC2B29F91B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS Tests" */; + buildConfigurationList = EE247AEA1C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLiteTests iOS" */; buildPhases = ( - DC2B29E81B26F718001C60EA /* Sources */, - DC2B29E91B26F718001C60EA /* Frameworks */, - DC2B29EA1B26F718001C60EA /* Resources */, + EE247AD91C3F04ED00AE3E12 /* Sources */, + EE247ADA1C3F04ED00AE3E12 /* Frameworks */, + EE247ADB1C3F04ED00AE3E12 /* Resources */, ); buildRules = ( ); dependencies = ( - DC2B29EF1B26F718001C60EA /* PBXTargetDependency */, + EE247AE01C3F04ED00AE3E12 /* PBXTargetDependency */, ); - name = "SQLite iOS Tests"; + name = "SQLiteTests iOS"; productName = SQLiteTests; - productReference = DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */; + productReference = EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - DC7253AB1B52ADEB009B38B1 /* SQLite Mac */ = { + EE247B3B1C3F3ED000AE3E12 /* SQLite Mac */ = { isa = PBXNativeTarget; - buildConfigurationList = DC7253C11B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac" */; + buildConfigurationList = EE247B511C3F3ED000AE3E12 /* Build configuration list for PBXNativeTarget "SQLite Mac" */; buildPhases = ( - DC7253A71B52ADEB009B38B1 /* Sources */, - DC7253A81B52ADEB009B38B1 /* Frameworks */, - DC7253A91B52ADEB009B38B1 /* Headers */, - DC7253AA1B52ADEB009B38B1 /* Resources */, + EE247B371C3F3ED000AE3E12 /* Sources */, + EE247B381C3F3ED000AE3E12 /* Frameworks */, + EE247B391C3F3ED000AE3E12 /* Headers */, + EE247B3A1C3F3ED000AE3E12 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "SQLite Mac"; - productName = "SQLite Mac"; - productReference = DC7253AC1B52ADEB009B38B1 /* SQLite.framework */; + productName = SQlite; + productReference = EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; - DC7253B41B52ADEC009B38B1 /* SQLite Mac Tests */ = { + EE247B441C3F3ED000AE3E12 /* SQLiteTests Mac */ = { isa = PBXNativeTarget; - buildConfigurationList = DC7253C21B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac Tests" */; + buildConfigurationList = EE247B521C3F3ED000AE3E12 /* Build configuration list for PBXNativeTarget "SQLiteTests Mac" */; buildPhases = ( - DC7253B11B52ADEC009B38B1 /* Sources */, - DC7253B21B52ADEC009B38B1 /* Frameworks */, - DC7253B31B52ADEC009B38B1 /* Resources */, + EE247B411C3F3ED000AE3E12 /* Sources */, + EE247B421C3F3ED000AE3E12 /* Frameworks */, + EE247B431C3F3ED000AE3E12 /* Resources */, ); buildRules = ( ); dependencies = ( - DC7253B81B52ADEC009B38B1 /* PBXTargetDependency */, + EE247B481C3F3ED000AE3E12 /* PBXTargetDependency */, ); - name = "SQLite Mac Tests"; - productName = "SQLite MacTests"; - productReference = DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCE7BB8B1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS" */; - buildPhases = ( - DCE7BB751B7434F90040D364 /* Sources */, - DCE7BB761B7434F90040D364 /* Frameworks */, - DCE7BB771B7434F90040D364 /* Headers */, - DCE7BB781B7434F90040D364 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - DCE7BBA61B7436D10040D364 /* PBXTargetDependency */, - ); - name = "SQLiteCipher iOS"; - productName = SQLiteCipher; - productReference = DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */; - productType = "com.apple.product-type.framework"; - }; - DCE7BB821B7434F90040D364 /* SQLiteCipher iOS Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCE7BB8E1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS Tests" */; - buildPhases = ( - DCE7BB7F1B7434F90040D364 /* Sources */, - DCE7BB801B7434F90040D364 /* Frameworks */, - DCE7BB811B7434F90040D364 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - DCE7BB861B7434F90040D364 /* PBXTargetDependency */, - ); - name = "SQLiteCipher iOS Tests"; - productName = SQLiteCipherTests; - productReference = DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCE7BBC11B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac" */; - buildPhases = ( - DCE7BBAB1B7438610040D364 /* Sources */, - DCE7BBAC1B7438610040D364 /* Frameworks */, - DCE7BBAD1B7438610040D364 /* Headers */, - DCE7BBAE1B7438610040D364 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - DCE7BBD11B743C220040D364 /* PBXTargetDependency */, - ); - name = "SQLiteCipher Mac"; - productName = "SQLiteCipher Mac"; - productReference = DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */; - productType = "com.apple.product-type.framework"; - }; - DCE7BBB81B7438620040D364 /* SQLiteCipher Mac Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCE7BBC41B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac Tests" */; - buildPhases = ( - DCE7BBB51B7438620040D364 /* Sources */, - DCE7BBB61B7438620040D364 /* Frameworks */, - DCE7BBB71B7438620040D364 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - DCE7BBBC1B7438620040D364 /* PBXTargetDependency */, - ); - name = "SQLiteCipher Mac Tests"; - productName = "SQLiteCipher MacTests"; - productReference = DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */; + name = "SQLiteTests Mac"; + productName = SQliteTests; + productReference = EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - DC2B29D91B26F718001C60EA /* Project object */ = { + EE247ACA1C3F04ED00AE3E12 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; - ORGANIZATIONNAME = stephencelis; + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0720; TargetAttributes = { - DC2B29E11B26F718001C60EA = { - CreatedOnToolsVersion = 7.0; - }; - DC2B29EB1B26F718001C60EA = { - CreatedOnToolsVersion = 7.0; - }; - DC7253AB1B52ADEB009B38B1 = { - CreatedOnToolsVersion = 7.0; - }; - DC7253B41B52ADEC009B38B1 = { - CreatedOnToolsVersion = 7.0; - }; - DCE7BB791B7434F90040D364 = { - CreatedOnToolsVersion = 7.0; + EE247AD21C3F04ED00AE3E12 = { + CreatedOnToolsVersion = 7.2; }; - DCE7BB821B7434F90040D364 = { - CreatedOnToolsVersion = 7.0; + EE247ADC1C3F04ED00AE3E12 = { + CreatedOnToolsVersion = 7.2; }; - DCE7BBAF1B7438610040D364 = { - CreatedOnToolsVersion = 7.0; + EE247B3B1C3F3ED000AE3E12 = { + CreatedOnToolsVersion = 7.2; }; - DCE7BBB81B7438620040D364 = { - CreatedOnToolsVersion = 7.0; + EE247B441C3F3ED000AE3E12 = { + CreatedOnToolsVersion = 7.2; }; }; }; - buildConfigurationList = DC2B29DC1B26F718001C60EA /* Build configuration list for PBXProject "SQLite" */; - compatibilityVersion = "Xcode 6.3"; + buildConfigurationList = EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */; + compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); - mainGroup = DC2B29D81B26F718001C60EA; - productRefGroup = DC2B29E31B26F718001C60EA /* Products */; + mainGroup = EE247AC91C3F04ED00AE3E12; + productRefGroup = EE247AD41C3F04ED00AE3E12 /* Products */; projectDirPath = ""; - projectReferences = ( - { - ProductGroup = DCE7BB6C1B7434310040D364 /* Products */; - ProjectRef = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; - }, - ); projectRoot = ""; targets = ( - DC2B29E11B26F718001C60EA /* SQLite iOS */, - DC2B29EB1B26F718001C60EA /* SQLite iOS Tests */, - DC7253AB1B52ADEB009B38B1 /* SQLite Mac */, - DC7253B41B52ADEC009B38B1 /* SQLite Mac Tests */, - DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */, - DCE7BB821B7434F90040D364 /* SQLiteCipher iOS Tests */, - DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */, - DCE7BBB81B7438620040D364 /* SQLiteCipher Mac Tests */, + EE247AD21C3F04ED00AE3E12 /* SQLite iOS */, + EE247ADC1C3F04ED00AE3E12 /* SQLiteTests iOS */, + EE247B3B1C3F3ED000AE3E12 /* SQLite Mac */, + EE247B441C3F3ED000AE3E12 /* SQLiteTests Mac */, ); }; /* End PBXProject section */ -/* Begin PBXReferenceProxy section */ - DCE7BB711B7434310040D364 /* libsqlcipher.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libsqlcipher.a; - remoteRef = DCE7BB701B7434310040D364 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - /* Begin PBXResourcesBuildPhase section */ - DC2B29E01B26F718001C60EA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC2B29EA1B26F718001C60EA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC7253AA1B52ADEB009B38B1 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC7253B31B52ADEC009B38B1 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BB781B7434F90040D364 /* Resources */ = { + EE247AD11C3F04ED00AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BB811B7434F90040D364 /* Resources */ = { + EE247ADB1C3F04ED00AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BBAE1B7438610040D364 /* Resources */ = { + EE247B3A1C3F3ED000AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BBB71B7438620040D364 /* Resources */ = { + EE247B431C3F3ED000AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -800,205 +482,117 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - DC2B29DD1B26F718001C60EA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DC2B2A161B26FE7F001C60EA /* Connection.swift in Sources */, - DC303D6E1B2CFA1F00469AEA /* Statement.swift in Sources */, - DC303D701B2CFA4700469AEA /* Value.swift in Sources */, - DC47D0661B52C9D200B12E66 /* Blob.swift in Sources */, - DC461BB91B2E687900E9DABD /* SQLite-Bridging.m in Sources */, - DC2D38BE1B2C7A73003EE2F2 /* Expression.swift in Sources */, - DC0B8BAD1B497A400084D886 /* Query.swift in Sources */, - DCA7B9E81B4D4F30006A9F04 /* Schema.swift in Sources */, - DCDF55B31B49ACBC00E1A168 /* Collation.swift in Sources */, - DC2609C21B475C9900C3E6B5 /* Operators.swift in Sources */, - DC0B8BAB1B485B620084D886 /* AggregateFunctions.swift in Sources */, - DC2609C41B475CA500C3E6B5 /* CoreFunctions.swift in Sources */, - DC2609C01B47504200C3E6B5 /* CustomFunctions.swift in Sources */, - DC72349E1B49D3AB007F513E /* Setter.swift in Sources */, - DC32A7861B575FD7002FFBE2 /* FTS4.swift in Sources */, - DC32A7AA1B5AAE76002FFBE2 /* R*Tree.swift in Sources */, - DC47D0A61B52F5B900B12E66 /* Foundation.swift in Sources */, - DCAA22861B4A9CB2005E00A2 /* Helpers.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC2B29E81B26F718001C60EA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DC32A7BF1B5C1769002FFBE2 /* SchemaTests.swift in Sources */, - DC32A7C51B5C1769002FFBE2 /* R*TreeTests.swift in Sources */, - DC303D761B2E306000469AEA /* TestHelpers.swift in Sources */, - DC32A7BE1B5C1769002FFBE2 /* QueryTests.swift in Sources */, - DC303D731B2E2ECD00469AEA /* ConnectionTests.swift in Sources */, - DC32A7C21B5C1769002FFBE2 /* CoreFunctionsTests.swift in Sources */, - DC32A7BA1B5C1769002FFBE2 /* StatementTests.swift in Sources */, - DC32A7BB1B5C1769002FFBE2 /* ValueTests.swift in Sources */, - DC32A7C91B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */, - DC32A7C41B5C1769002FFBE2 /* FTS4Tests.swift in Sources */, - DC32A7BC1B5C1769002FFBE2 /* BlobTests.swift in Sources */, - DC32A7C11B5C1769002FFBE2 /* AggregateFunctionsTests.swift in Sources */, - DC32A7BD1B5C1769002FFBE2 /* ExpressionTests.swift in Sources */, - DC32A7C31B5C1769002FFBE2 /* SetterTests.swift in Sources */, - DC32A7C01B5C1769002FFBE2 /* OperatorsTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC7253A71B52ADEB009B38B1 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DC32A7971B57672E002FFBE2 /* Connection.swift in Sources */, - DC32A7981B57672E002FFBE2 /* Statement.swift in Sources */, - DC32A7991B57672E002FFBE2 /* Value.swift in Sources */, - DC47D0671B52C9D200B12E66 /* Blob.swift in Sources */, - DC32A79B1B57672E002FFBE2 /* SQLite-Bridging.m in Sources */, - DC32A79D1B57672E002FFBE2 /* Expression.swift in Sources */, - DC32A79E1B57672E002FFBE2 /* Query.swift in Sources */, - DC32A7961B576715002FFBE2 /* Schema.swift in Sources */, - DC32A79F1B57672E002FFBE2 /* Collation.swift in Sources */, - DC32A7A01B57672E002FFBE2 /* Operators.swift in Sources */, - DC32A7A11B57672E002FFBE2 /* AggregateFunctions.swift in Sources */, - DC32A7A21B57672E002FFBE2 /* CoreFunctions.swift in Sources */, - DC32A7A31B57672E002FFBE2 /* CustomFunctions.swift in Sources */, - DC32A7A41B57672E002FFBE2 /* Setter.swift in Sources */, - DC32A7871B575FD7002FFBE2 /* FTS4.swift in Sources */, - DC32A7A91B5AAE75002FFBE2 /* R*Tree.swift in Sources */, - DC47D0A71B52F5B900B12E66 /* Foundation.swift in Sources */, - DC32A7A51B57672E002FFBE2 /* Helpers.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DC7253B11B52ADEC009B38B1 /* Sources */ = { + EE247ACE1C3F04ED00AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DC32A7B31B5AC2C5002FFBE2 /* SchemaTests.swift in Sources */, - DC32A7B91B5AC2C5002FFBE2 /* R*TreeTests.swift in Sources */, - DC795C561B55AC8100DA0EF9 /* TestHelpers.swift in Sources */, - DC32A7B21B5AC2C5002FFBE2 /* QueryTests.swift in Sources */, - DC795C571B55AC8100DA0EF9 /* ConnectionTests.swift in Sources */, - DC32A7B61B5AC2C5002FFBE2 /* CoreFunctionsTests.swift in Sources */, - DC32A7AE1B5AC2C5002FFBE2 /* StatementTests.swift in Sources */, - DC32A7AF1B5AC2C5002FFBE2 /* ValueTests.swift in Sources */, - DC32A7CA1B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */, - DC32A7B81B5AC2C5002FFBE2 /* FTS4Tests.swift in Sources */, - DC32A7B01B5AC2C5002FFBE2 /* BlobTests.swift in Sources */, - DC32A7B51B5AC2C5002FFBE2 /* AggregateFunctionsTests.swift in Sources */, - DC32A7B11B5AC2C5002FFBE2 /* ExpressionTests.swift in Sources */, - DC32A7B71B5AC2C5002FFBE2 /* SetterTests.swift in Sources */, - DC32A7B41B5AC2C5002FFBE2 /* OperatorsTests.swift in Sources */, + EE247B0F1C3F06E900AE3E12 /* CoreFunctions.swift in Sources */, + EE247B0A1C3F06E900AE3E12 /* R*Tree.swift in Sources */, + EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */, + EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */, + EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */, + EE247B111C3F06E900AE3E12 /* Expression.swift in Sources */, + EE247B0C1C3F06E900AE3E12 /* Helpers.swift in Sources */, + EE247B0E1C3F06E900AE3E12 /* Collation.swift in Sources */, + EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */, + EE247B101C3F06E900AE3E12 /* CustomFunctions.swift in Sources */, + EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */, + EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, + EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, + EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, + EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, + EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, + EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, + EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BB751B7434F90040D364 /* Sources */ = { + EE247AD91C3F04ED00AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DC6C03551B74453300EFC04E /* Connection.swift in Sources */, - DC6C03561B74453300EFC04E /* Statement.swift in Sources */, - DC6C03571B74453300EFC04E /* Value.swift in Sources */, - DC6C037F1B74461300EFC04E /* SQLite-Bridging.m in Sources */, - DC6C03581B74453300EFC04E /* Blob.swift in Sources */, - DC6C03591B74454100EFC04E /* Expression.swift in Sources */, - DC6C035A1B74454100EFC04E /* Query.swift in Sources */, - DC6C035B1B74454100EFC04E /* Schema.swift in Sources */, - DC6C035C1B74454100EFC04E /* Collation.swift in Sources */, - DC6C035D1B74454100EFC04E /* Operators.swift in Sources */, - DC6C035E1B74454100EFC04E /* AggregateFunctions.swift in Sources */, - DC6C035F1B74454100EFC04E /* CoreFunctions.swift in Sources */, - DC6C03601B74454100EFC04E /* CustomFunctions.swift in Sources */, - DC6C03611B74454100EFC04E /* Setter.swift in Sources */, - DC6C03621B74454100EFC04E /* FTS4.swift in Sources */, - DC6C03631B74454100EFC04E /* R*Tree.swift in Sources */, - DC6C03641B74454100EFC04E /* Foundation.swift in Sources */, - DC6C03651B74454100EFC04E /* Helpers.swift in Sources */, - DCE7BB911B74350E0040D364 /* Cipher.swift in Sources */, + EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, + EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, + EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, + EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */, + EE247B171C3F127200AE3E12 /* TestHelpers.swift in Sources */, + EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */, + EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */, + EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */, + EE247B301C3F141E00AE3E12 /* R*TreeTests.swift in Sources */, + EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */, + EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, + EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, + EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, + EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, + EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BB7F1B7434F90040D364 /* Sources */ = { + EE247B371C3F3ED000AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCE7BBC91B743A320040D364 /* CipherTests.swift in Sources */, + EE247B6F1C3F3FEC00AE3E12 /* CoreFunctions.swift in Sources */, + EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, + EE247B6C1C3F3FEC00AE3E12 /* R*Tree.swift in Sources */, + EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */, + EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, + EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, + EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */, + EE247B6E1C3F3FEC00AE3E12 /* Collation.swift in Sources */, + EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */, + EE247B701C3F3FEC00AE3E12 /* CustomFunctions.swift in Sources */, + EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */, + EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, + EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, + EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, + EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */, + EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, + EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, + EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCE7BBAB1B7438610040D364 /* Sources */ = { + EE247B411C3F3ED000AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DC6C03661B74454D00EFC04E /* Connection.swift in Sources */, - DC6C03671B74454D00EFC04E /* Statement.swift in Sources */, - DC6C03681B74454D00EFC04E /* Value.swift in Sources */, - DC6C03691B74454D00EFC04E /* Blob.swift in Sources */, - DC6C036A1B74454D00EFC04E /* SQLite-Bridging.m in Sources */, - DC6C036B1B74454D00EFC04E /* Expression.swift in Sources */, - DC6C036C1B74454D00EFC04E /* Query.swift in Sources */, - DC6C036D1B74454D00EFC04E /* Schema.swift in Sources */, - DC6C036E1B74454D00EFC04E /* Collation.swift in Sources */, - DC6C036F1B74454D00EFC04E /* Operators.swift in Sources */, - DC6C03701B74454D00EFC04E /* AggregateFunctions.swift in Sources */, - DC6C03711B74454D00EFC04E /* CoreFunctions.swift in Sources */, - DC6C03721B74454D00EFC04E /* CustomFunctions.swift in Sources */, - DC6C03731B74454D00EFC04E /* Setter.swift in Sources */, - DC6C03741B74454D00EFC04E /* FTS4.swift in Sources */, - DC6C03751B74454D00EFC04E /* R*Tree.swift in Sources */, - DC6C03761B74454D00EFC04E /* Foundation.swift in Sources */, - DC6C03771B74454D00EFC04E /* Helpers.swift in Sources */, - DC6C03541B74451F00EFC04E /* Cipher.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - DCE7BBB51B7438620040D364 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DCE7BBCA1B743A320040D364 /* CipherTests.swift in Sources */, + EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, + EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, + EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, + EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */, + EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */, + EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */, + EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */, + EE247B5C1C3F3FC700AE3E12 /* R*TreeTests.swift in Sources */, + EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */, + EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */, + EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, + EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, + EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, + EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, + EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - DC2B29EF1B26F718001C60EA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DC2B29E11B26F718001C60EA /* SQLite iOS */; - targetProxy = DC2B29EE1B26F718001C60EA /* PBXContainerItemProxy */; - }; - DC7253B81B52ADEC009B38B1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DC7253AB1B52ADEB009B38B1 /* SQLite Mac */; - targetProxy = DC7253B71B52ADEC009B38B1 /* PBXContainerItemProxy */; - }; - DCE7BB861B7434F90040D364 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */; - targetProxy = DCE7BB851B7434F90040D364 /* PBXContainerItemProxy */; - }; - DCE7BBA61B7436D10040D364 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = sqlcipher; - targetProxy = DCE7BBA51B7436D10040D364 /* PBXContainerItemProxy */; - }; - DCE7BBBC1B7438620040D364 /* PBXTargetDependency */ = { + EE247AE01C3F04ED00AE3E12 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */; - targetProxy = DCE7BBBB1B7438620040D364 /* PBXContainerItemProxy */; + target = EE247AD21C3F04ED00AE3E12 /* SQLite iOS */; + targetProxy = EE247ADF1C3F04ED00AE3E12 /* PBXContainerItemProxy */; }; - DCE7BBD11B743C220040D364 /* PBXTargetDependency */ = { + EE247B481C3F3ED000AE3E12 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - name = sqlcipher; - targetProxy = DCE7BBD01B743C220040D364 /* PBXContainerItemProxy */; + target = EE247B3B1C3F3ED000AE3E12 /* SQLite Mac */; + targetProxy = EE247B471C3F3ED000AE3E12 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - DC2B29F41B26F718001C60EA /* Debug */ = { + EE247AE51C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1038,6 +632,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1046,7 +641,7 @@ }; name = Debug; }; - DC2B29F51B26F718001C60EA /* Release */ = { + EE247AE61C3F04ED00AE3E12 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1079,6 +674,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = ""; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1087,238 +683,132 @@ }; name = Release; }; - DC2B29F71B26F718001C60EA /* Debug */ = { + EE247AE81C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/SQLite3"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DC2B29F81B26F718001C60EA /* Release */ = { + EE247AE91C3F04ED00AE3E12 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/SQLite3"; }; name = Release; }; - DC2B29FA1B26F718001C60EA /* Debug */ = { + EE247AEB1C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { + INFOPLIST_FILE = SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; - DC2B29FB1B26F718001C60EA /* Release */ = { + EE247AEC1C3F04ED00AE3E12 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { + INFOPLIST_FILE = SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; - DC7253BD1B52ADEC009B38B1 /* Debug */ = { + EE247B4D1C3F3ED000AE3E12 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { - CLANG_ENABLE_MODULES = YES; - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - DC7253BE1B52ADEC009B38B1 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SQlite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; - SDKROOT = macosx; - SKIP_INSTALL = YES; - }; - name = Release; - }; - DC7253BF1B52ADEC009B38B1 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - COMBINE_HIDPI_IMAGES = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - DC7253C01B52ADEC009B38B1 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - COMBINE_HIDPI_IMAGES = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQlite; + PRODUCT_NAME = SQLite; SDKROOT = macosx; - }; - name = Release; - }; - DCE7BB8C1B7434F90040D364 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; - buildSettings = { - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "${SRCROOT}/SQLite3"; }; name = Debug; }; - DCE7BB8D1B7434F90040D364 /* Release */ = { + EE247B4E1C3F3ED000AE3E12 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; - buildSettings = { - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - SKIP_INSTALL = YES; - }; - name = Release; - }; - DCE7BB8F1B7434F90040D364 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteCipherTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - DCE7BB901B7434F90040D364 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; - buildSettings = { - CLANG_ENABLE_MODULES = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteCipherTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - DCE7BBC21B7438620040D364 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_VERSION = A; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; - SDKROOT = macosx; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - DCE7BBC31B7438620040D364 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; + INFOPLIST_FILE = SQlite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQlite; + PRODUCT_NAME = SQLite; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "${SRCROOT}/SQLite3"; }; name = Release; }; - DCE7BBC51B7438620040D364 /* Debug */ = { + EE247B4F1C3F3ED000AE3E12 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = SQliteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = "com.stephencelis.SQLiteCipher-MacTests"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQliteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DCE7BBC61B7438620040D364 /* Release */ = { + EE247B501C3F3ED000AE3E12 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = SQliteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = "com.stephencelis.SQLiteCipher-MacTests"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQliteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; @@ -1327,88 +817,52 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - DC2B29DC1B26F718001C60EA /* Build configuration list for PBXProject "SQLite" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DC2B29F41B26F718001C60EA /* Debug */, - DC2B29F51B26F718001C60EA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - DC2B29F61B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DC2B29F71B26F718001C60EA /* Debug */, - DC2B29F81B26F718001C60EA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - DC2B29F91B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DC2B29FA1B26F718001C60EA /* Debug */, - DC2B29FB1B26F718001C60EA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - DC7253C11B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DC7253BD1B52ADEC009B38B1 /* Debug */, - DC7253BE1B52ADEC009B38B1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - DC7253C21B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac Tests" */ = { + EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC7253BF1B52ADEC009B38B1 /* Debug */, - DC7253C01B52ADEC009B38B1 /* Release */, + EE247AE51C3F04ED00AE3E12 /* Debug */, + EE247AE61C3F04ED00AE3E12 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCE7BB8B1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS" */ = { + EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCE7BB8C1B7434F90040D364 /* Debug */, - DCE7BB8D1B7434F90040D364 /* Release */, + EE247AE81C3F04ED00AE3E12 /* Debug */, + EE247AE91C3F04ED00AE3E12 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCE7BB8E1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS Tests" */ = { + EE247AEA1C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLiteTests iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCE7BB8F1B7434F90040D364 /* Debug */, - DCE7BB901B7434F90040D364 /* Release */, + EE247AEB1C3F04ED00AE3E12 /* Debug */, + EE247AEC1C3F04ED00AE3E12 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCE7BBC11B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac" */ = { + EE247B511C3F3ED000AE3E12 /* Build configuration list for PBXNativeTarget "SQLite Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCE7BBC21B7438620040D364 /* Debug */, - DCE7BBC31B7438620040D364 /* Release */, + EE247B4D1C3F3ED000AE3E12 /* Debug */, + EE247B4E1C3F3ED000AE3E12 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCE7BBC41B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac Tests" */ = { + EE247B521C3F3ED000AE3E12 /* Build configuration list for PBXNativeTarget "SQLiteTests Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCE7BBC51B7438620040D364 /* Debug */, - DCE7BBC61B7438620040D364 /* Release */, + EE247B4F1C3F3ED000AE3E12 /* Debug */, + EE247B501C3F3ED000AE3E12 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = DC2B29D91B26F718001C60EA /* Project object */; + rootObject = EE247ACA1C3F04ED00AE3E12 /* Project object */; } diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint deleted file mode 100644 index 9029cbd1..00000000 --- a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint +++ /dev/null @@ -1,30 +0,0 @@ -{ - "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD", - "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { - - }, - "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { - "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" : 0, - "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" : 0 - }, - "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7DE16DEF-6846-4DBA-8E4B-370D97AACD60", - "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { - "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" : "SQLite.swift\/Vendor\/sqlcipher\/", - "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" : "SQLite.swift\/" - }, - "DVTSourceControlWorkspaceBlueprintNameKey" : "SQLite", - "DVTSourceControlWorkspaceBlueprintVersion" : 204, - "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SQLite.xcodeproj", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:stephencelis\/SQLite.swift.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/sqlcipher\/sqlcipher.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" - } - ] -} \ No newline at end of file diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 08de0be8..00000000 --- a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - - - diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index d4751729..fa91858e 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ @@ -33,9 +33,9 @@ skipped = "NO"> @@ -43,7 +43,7 @@ @@ -65,7 +65,7 @@ @@ -83,7 +83,7 @@ diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme index 4c110773..534df7df 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -1,6 +1,6 @@ @@ -23,19 +23,19 @@ + codeCoverageEnabled = "YES"> @@ -43,7 +43,7 @@ @@ -53,11 +53,11 @@ @@ -75,15 +75,15 @@ diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme deleted file mode 100644 index 8ff655e1..00000000 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme deleted file mode 100644 index 3e5b96b9..00000000 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/Core/Blob.swift b/SQLite/Core/Blob.swift similarity index 99% rename from Source/Core/Blob.swift rename to SQLite/Core/Blob.swift index 1b30ffa1..69b1161f 100644 --- a/Source/Core/Blob.swift +++ b/SQLite/Core/Blob.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import SQLite3 + public struct Blob { public let bytes: [UInt8] diff --git a/Source/Core/Connection.swift b/SQLite/Core/Connection.swift similarity index 99% rename from Source/Core/Connection.swift rename to SQLite/Core/Connection.swift index 0243317f..3574e060 100644 --- a/Source/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -23,6 +23,7 @@ // import Dispatch +import SQLite3 /// A connection to SQLite. public final class Connection { diff --git a/Source/Core/SQLite-Bridging.m b/SQLite/Core/SQLite-Bridging.m similarity index 98% rename from Source/Core/SQLite-Bridging.m rename to SQLite/Core/SQLite-Bridging.m index ac5e54db..771a4123 100644 --- a/Source/Core/SQLite-Bridging.m +++ b/SQLite/Core/SQLite-Bridging.m @@ -22,13 +22,11 @@ // THE SOFTWARE. // -#import "SQLite-Bridging.h" +#import "fts3_tokenizer.h" -#ifdef COCOAPODS #import "sqlite3.h" -#endif -#import "fts3_tokenizer.h" +#import #pragma mark - FTS diff --git a/Source/Core/Statement.swift b/SQLite/Core/Statement.swift similarity index 99% rename from Source/Core/Statement.swift rename to SQLite/Core/Statement.swift index 1143ee7c..5de78fe6 100644 --- a/Source/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import SQLite3 + /// A single SQL statement. public final class Statement { diff --git a/Source/Core/Value.swift b/SQLite/Core/Value.swift similarity index 100% rename from Source/Core/Value.swift rename to SQLite/Core/Value.swift diff --git a/Source/Core/fts3_tokenizer.h b/SQLite/Core/fts3_tokenizer.h similarity index 99% rename from Source/Core/fts3_tokenizer.h rename to SQLite/Core/fts3_tokenizer.h index 4a40b2b3..d8a1e44b 100644 --- a/Source/Core/fts3_tokenizer.h +++ b/SQLite/Core/fts3_tokenizer.h @@ -24,7 +24,7 @@ ** If tokenizers are to be allowed to call sqlite3_*() functions, then ** we will need a way to register the API consistently. */ -#include "sqlite3.h" +#import "sqlite3.h" /* ** Structures used by the tokenizer interface. When a new tokenizer diff --git a/Source/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift similarity index 100% rename from Source/Extensions/FTS4.swift rename to SQLite/Extensions/FTS4.swift diff --git a/Source/Extensions/R*Tree.swift b/SQLite/Extensions/R*Tree.swift similarity index 100% rename from Source/Extensions/R*Tree.swift rename to SQLite/Extensions/R*Tree.swift diff --git a/Source/Foundation.swift b/SQLite/Foundation.swift similarity index 100% rename from Source/Foundation.swift rename to SQLite/Foundation.swift diff --git a/Source/Helpers.swift b/SQLite/Helpers.swift similarity index 99% rename from Source/Helpers.swift rename to SQLite/Helpers.swift index c1775e68..90febd89 100644 --- a/Source/Helpers.swift +++ b/SQLite/Helpers.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import SQLite3 + public typealias Star = (Expression?, Expression?) -> Expression public func *(_: Expression?, _: Expression?) -> Expression { diff --git a/Supporting Files/SQLite/Info.plist b/SQLite/Info.plist similarity index 97% rename from Supporting Files/SQLite/Info.plist rename to SQLite/Info.plist index d3de8eef..76d3d186 100644 --- a/Supporting Files/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 0.9.0 CFBundleSignature ???? CFBundleVersion diff --git a/SQLite/SQLite.h b/SQLite/SQLite.h new file mode 100644 index 00000000..a3a5eef2 --- /dev/null +++ b/SQLite/SQLite.h @@ -0,0 +1,11 @@ +@import Foundation; + +FOUNDATION_EXPORT double SQLiteVersionNumber; +FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; + +typedef struct SQLiteHandle SQLiteHandle; // ??? - CocoaPods workaround + +NS_ASSUME_NONNULL_BEGIN +typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/Typed/AggregateFunctions.swift b/SQLite/Typed/AggregateFunctions.swift similarity index 100% rename from Source/Typed/AggregateFunctions.swift rename to SQLite/Typed/AggregateFunctions.swift diff --git a/Source/Typed/Collation.swift b/SQLite/Typed/Collation.swift similarity index 100% rename from Source/Typed/Collation.swift rename to SQLite/Typed/Collation.swift diff --git a/Source/Typed/CoreFunctions.swift b/SQLite/Typed/CoreFunctions.swift similarity index 100% rename from Source/Typed/CoreFunctions.swift rename to SQLite/Typed/CoreFunctions.swift diff --git a/Source/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift similarity index 100% rename from Source/Typed/CustomFunctions.swift rename to SQLite/Typed/CustomFunctions.swift diff --git a/Source/Typed/Expression.swift b/SQLite/Typed/Expression.swift similarity index 100% rename from Source/Typed/Expression.swift rename to SQLite/Typed/Expression.swift diff --git a/Source/Typed/Operators.swift b/SQLite/Typed/Operators.swift similarity index 100% rename from Source/Typed/Operators.swift rename to SQLite/Typed/Operators.swift diff --git a/Source/Typed/Query.swift b/SQLite/Typed/Query.swift similarity index 100% rename from Source/Typed/Query.swift rename to SQLite/Typed/Query.swift diff --git a/Source/Typed/Schema.swift b/SQLite/Typed/Schema.swift similarity index 100% rename from Source/Typed/Schema.swift rename to SQLite/Typed/Schema.swift diff --git a/Source/Typed/Setter.swift b/SQLite/Typed/Setter.swift similarity index 100% rename from Source/Typed/Setter.swift rename to SQLite/Typed/Setter.swift diff --git a/SQLiteCipher.swift.podspec b/SQLiteCipher.swift.podspec deleted file mode 100644 index a0ad981f..00000000 --- a/SQLiteCipher.swift.podspec +++ /dev/null @@ -1,17 +0,0 @@ -require_relative 'Supporting Files/podspec.rb' - -Pod::Spec.new do |spec| - spec.name = 'SQLiteCipher.swift' - spec.summary = 'The SQLCipher flavor of SQLite.swift.' - - spec.description = <<-DESC - SQLiteCipher.swift is SQLite.swift built on top of SQLCipher. - DESC - - apply_shared_config spec, 'SQLiteCipher' - - spec.dependency 'SQLCipher' - spec.xcconfig = { - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' - } -end diff --git a/Tests/AggregateFunctionsTests.swift b/SQLiteTests/AggregateFunctionsTests.swift similarity index 99% rename from Tests/AggregateFunctionsTests.swift rename to SQLiteTests/AggregateFunctionsTests.swift index 6b583ccf..ac0fc944 100644 --- a/Tests/AggregateFunctionsTests.swift +++ b/SQLiteTests/AggregateFunctionsTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class AggregateFunctionsTests : XCTestCase { diff --git a/Tests/BlobTests.swift b/SQLiteTests/BlobTests.swift similarity index 90% rename from Tests/BlobTests.swift rename to SQLiteTests/BlobTests.swift index ba0a7c54..89c98e28 100644 --- a/Tests/BlobTests.swift +++ b/SQLiteTests/BlobTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class BlobTests : XCTestCase { diff --git a/Tests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift similarity index 99% rename from Tests/ConnectionTests.swift rename to SQLiteTests/ConnectionTests.swift index aeec9b72..5a976e0f 100644 --- a/Tests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class ConnectionTests : SQLiteTestCase { diff --git a/Tests/CoreFunctionsTests.swift b/SQLiteTests/CoreFunctionsTests.swift similarity index 99% rename from Tests/CoreFunctionsTests.swift rename to SQLiteTests/CoreFunctionsTests.swift index 8f7460d5..a2039f18 100644 --- a/Tests/CoreFunctionsTests.swift +++ b/SQLiteTests/CoreFunctionsTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class CoreFunctionsTests : XCTestCase { diff --git a/Tests/CustomFunctionsTests.swift b/SQLiteTests/CustomFunctionsTests.swift similarity index 71% rename from Tests/CustomFunctionsTests.swift rename to SQLiteTests/CustomFunctionsTests.swift index 67150ccf..7f6b58ee 100644 --- a/Tests/CustomFunctionsTests.swift +++ b/SQLiteTests/CustomFunctionsTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class CustomFunctionsTests : XCTestCase { diff --git a/Tests/ExpressionTests.swift b/SQLiteTests/ExpressionTests.swift similarity index 69% rename from Tests/ExpressionTests.swift rename to SQLiteTests/ExpressionTests.swift index 036e10ce..518533ee 100644 --- a/Tests/ExpressionTests.swift +++ b/SQLiteTests/ExpressionTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class ExpressionTests : XCTestCase { diff --git a/Tests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift similarity index 99% rename from Tests/FTS4Tests.swift rename to SQLiteTests/FTS4Tests.swift index a94d9073..33a92b4c 100644 --- a/Tests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class FTS4Tests : XCTestCase { diff --git a/Tests/Info.plist b/SQLiteTests/Info.plist similarity index 100% rename from Tests/Info.plist rename to SQLiteTests/Info.plist diff --git a/Tests/OperatorsTests.swift b/SQLiteTests/OperatorsTests.swift similarity index 99% rename from Tests/OperatorsTests.swift rename to SQLiteTests/OperatorsTests.swift index 86aa34ed..67adcd7c 100644 --- a/Tests/OperatorsTests.swift +++ b/SQLiteTests/OperatorsTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class OperatorsTests : XCTestCase { diff --git a/Tests/QueryTests.swift b/SQLiteTests/QueryTests.swift similarity index 99% rename from Tests/QueryTests.swift rename to SQLiteTests/QueryTests.swift index b25f2620..15e35df4 100644 --- a/Tests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class QueryTests : XCTestCase { diff --git a/Tests/R*TreeTests.swift b/SQLiteTests/R*TreeTests.swift similarity index 96% rename from Tests/R*TreeTests.swift rename to SQLiteTests/R*TreeTests.swift index 7147533e..ab1817e4 100644 --- a/Tests/R*TreeTests.swift +++ b/SQLiteTests/R*TreeTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class RTreeTests : XCTestCase { diff --git a/Tests/SchemaTests.swift b/SQLiteTests/SchemaTests.swift similarity index 99% rename from Tests/SchemaTests.swift rename to SQLiteTests/SchemaTests.swift index b3cb9a63..d7b116e9 100644 --- a/Tests/SchemaTests.swift +++ b/SQLiteTests/SchemaTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class SchemaTests : XCTestCase { diff --git a/Tests/SetterTests.swift b/SQLiteTests/SetterTests.swift similarity index 99% rename from Tests/SetterTests.swift rename to SQLiteTests/SetterTests.swift index d4f189d7..9ec8df00 100644 --- a/Tests/SetterTests.swift +++ b/SQLiteTests/SetterTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class SetterTests : XCTestCase { diff --git a/Tests/StatementTests.swift b/SQLiteTests/StatementTests.swift similarity index 68% rename from Tests/StatementTests.swift rename to SQLiteTests/StatementTests.swift index 44a7ae17..7b5ad055 100644 --- a/Tests/StatementTests.swift +++ b/SQLiteTests/StatementTests.swift @@ -1,6 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class StatementTests : XCTestCase { - } diff --git a/Tests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift similarity index 99% rename from Tests/TestHelpers.swift rename to SQLiteTests/TestHelpers.swift index e41af0f8..bc0b244a 100644 --- a/Tests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -1,6 +1,6 @@ import XCTest -import SQLite +@testable import SQLite class SQLiteTestCase : XCTestCase { diff --git a/Tests/ValueTests.swift b/SQLiteTests/ValueTests.swift similarity index 67% rename from Tests/ValueTests.swift rename to SQLiteTests/ValueTests.swift index bda2b4b3..96476328 100644 --- a/Tests/ValueTests.swift +++ b/SQLiteTests/ValueTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class ValueTests : XCTestCase { diff --git a/Source/Cipher/Cipher.swift b/Source/Cipher/Cipher.swift deleted file mode 100644 index 8cf8396c..00000000 --- a/Source/Cipher/Cipher.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -extension Connection { - - public func key(key: String) throws { - try check(sqlite3_key(handle, key, Int32(key.utf8.count))) - try execute( - "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + - "DROP TABLE \"__SQLCipher.swift__\";" - ) - } - - public func rekey(key: String) throws { - try check(sqlite3_rekey(handle, key, Int32(key.utf8.count))) - } - -} diff --git a/Source/Core/SQLite-Bridging.h b/Source/Core/SQLite-Bridging.h deleted file mode 100644 index d15e8d56..00000000 --- a/Source/Core/SQLite-Bridging.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@import Foundation; - -#ifndef COCOAPODS -#import "sqlite3.h" -#endif - -typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround - -NS_ASSUME_NONNULL_BEGIN -typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); -NS_ASSUME_NONNULL_END - diff --git a/Supporting Files/Base.xcconfig b/Supporting Files/Base.xcconfig deleted file mode 100644 index bc31c8a3..00000000 --- a/Supporting Files/Base.xcconfig +++ /dev/null @@ -1,5 +0,0 @@ -INFOPLIST_FILE = Supporting Files/$(PRODUCT_NAME)/Info.plist - -PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.$(PRODUCT_NAME:c99extidentifier) - -APPLICATION_EXTENSION_API_ONLY = YES diff --git a/Supporting Files/SQLite/SQLite.h b/Supporting Files/SQLite/SQLite.h deleted file mode 100644 index f6fa54c8..00000000 --- a/Supporting Files/SQLite/SQLite.h +++ /dev/null @@ -1,9 +0,0 @@ -@import Foundation; - -//! Project version number for SQLite. -FOUNDATION_EXPORT double SQLiteVersionNumber; - -//! Project version string for SQLite. -FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; - -#import diff --git a/Supporting Files/SQLite/SQLite.xcconfig b/Supporting Files/SQLite/SQLite.xcconfig deleted file mode 100644 index 05f740a1..00000000 --- a/Supporting Files/SQLite/SQLite.xcconfig +++ /dev/null @@ -1,5 +0,0 @@ -#include "../Base.xcconfig" - -PRODUCT_NAME = SQLite - -OTHER_LDFLAGS = -lsqlite3 diff --git a/Supporting Files/SQLite/module.modulemap b/Supporting Files/SQLite/module.modulemap deleted file mode 100644 index 122acd2e..00000000 --- a/Supporting Files/SQLite/module.modulemap +++ /dev/null @@ -1,12 +0,0 @@ -framework module SQLite { - umbrella header "SQLite.h" - - // Load the SDK header alongside SQLite.swift. Alternate headers: - // - // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - - export * - module * { export * } -} diff --git a/Supporting Files/SQLiteCipher/Info.plist b/Supporting Files/SQLiteCipher/Info.plist deleted file mode 100644 index d3de8eef..00000000 --- a/Supporting Files/SQLiteCipher/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/Supporting Files/SQLiteCipher/SQLiteCipher.h b/Supporting Files/SQLiteCipher/SQLiteCipher.h deleted file mode 100644 index f34ba14a..00000000 --- a/Supporting Files/SQLiteCipher/SQLiteCipher.h +++ /dev/null @@ -1,9 +0,0 @@ -@import Foundation; - -//! Project version number for SQLite. -FOUNDATION_EXPORT double SQLiteVersionNumber; - -//! Project version string for SQLite. -FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; - -#import diff --git a/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig b/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig deleted file mode 100644 index 4af62038..00000000 --- a/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig +++ /dev/null @@ -1,5 +0,0 @@ -#include "../Base.xcconfig" - -PRODUCT_NAME = SQLiteCipher - -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) SQLITE_HAS_CODEC=1 diff --git a/Supporting Files/SQLiteCipher/module.modulemap b/Supporting Files/SQLiteCipher/module.modulemap deleted file mode 100644 index 0169f9bf..00000000 --- a/Supporting Files/SQLiteCipher/module.modulemap +++ /dev/null @@ -1,12 +0,0 @@ -framework module SQLiteCipher { - umbrella header "SQLiteCipher.h" - - // Load the SDK header alongside SQLite.swift. Alternate headers: - // - // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - - export * - module * { export * } -} diff --git a/Supporting Files/Test.xcconfig b/Supporting Files/Test.xcconfig deleted file mode 100644 index 40e5c38b..00000000 --- a/Supporting Files/Test.xcconfig +++ /dev/null @@ -1 +0,0 @@ -INFOPLIST_FILE = Tests/Info.plist diff --git a/Supporting Files/podspec.rb b/Supporting Files/podspec.rb deleted file mode 100644 index 24e9e4d4..00000000 --- a/Supporting Files/podspec.rb +++ /dev/null @@ -1,25 +0,0 @@ -$podspec_version = '1.0.0' -$podspec_source_git = 'https://github.com/stephencelis/SQLite.swift.git' - -def apply_shared_config spec, name - spec.version = $podspec_version - spec.source = { git: $podspec_source_git, tag: $podspec_version } - - spec.homepage = 'https://github.com/stephencelis/SQLite.swift' - spec.license = { type: 'MIT', file: 'LICENSE.txt' } - - spec.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } - spec.social_media_url = 'https://twitter.com/stephencelis' - - # TODO: separate SQLiteCipher.swift and share `SQLite` module name - # spec.module_name = 'SQLite' - # spec.module_map = 'Supporting Files/module.modulemap' - spec.module_name = name - spec.module_map = "Supporting Files/#{name}/module.modulemap" - - spec.source_files = [ - "Supporting Files/#{name}/#{name}.h", 'Source/**/*.{swift,c,h,m}' - ] - - spec.private_header_files = 'Source/Core/fts3_tokenizer.h' -end diff --git a/Tests/CipherTests.swift b/Tests/CipherTests.swift deleted file mode 100644 index f9919999..00000000 --- a/Tests/CipherTests.swift +++ /dev/null @@ -1,48 +0,0 @@ -import XCTest -import SQLiteCipher - -class CipherTests: XCTestCase { - - let db = try! Connection() - - override func setUp() { - try! db.key("hello") - - try! db.run("CREATE TABLE foo (bar TEXT)") - try! db.run("INSERT INTO foo (bar) VALUES ('world')") - - super.setUp() - } - - func test_key() { - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM foo") as? Int64) - } - - func test_rekey() { - try! db.rekey("goodbye") - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM foo") as? Int64) - } - - func test_keyFailure() { - let path = "\(NSTemporaryDirectory())/db.sqlite3" - _ = try? NSFileManager.defaultManager().removeItemAtPath(path) - - let connA = try! Connection(path) - defer { try! NSFileManager.defaultManager().removeItemAtPath(path) } - - try! connA.key("hello") - - let connB = try! Connection(path) - - var rc: Int32? - do { - try connB.key("world") - } catch Result.Error(_, let code, _) { - rc = code - } catch { - XCTFail() - } - XCTAssertEqual(SQLITE_NOTADB, rc) - } - -} diff --git a/Vendor/sqlcipher b/Vendor/sqlcipher deleted file mode 160000 index c01b94fb..00000000 --- a/Vendor/sqlcipher +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c01b94fb6a6b54e00b9839e7314893f72c9dab22 diff --git a/module.modulemap b/module.modulemap new file mode 100644 index 00000000..29301561 --- /dev/null +++ b/module.modulemap @@ -0,0 +1,12 @@ +# This file is required by current CocoaPods (0.39.0). +# - Generates warning: Umbrella header for module 'SQLite' does not include header 'fts3_tokenizer.h' + +# Xcode generates its own correct ModuleMap, and Carthage does the same + +framework module SQLite { + umbrella header "SQLite.h" + + export * + module * { export * } +} + From a35ae295897ef660a0e37104e6eac97aef5e2bd3 Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Fri, 8 Jan 2016 08:42:20 -0800 Subject: [PATCH 0327/1046] Add accidentally omitted file. --- SQLite3/module.modulemap | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 SQLite3/module.modulemap diff --git a/SQLite3/module.modulemap b/SQLite3/module.modulemap new file mode 100644 index 00000000..d55e7697 --- /dev/null +++ b/SQLite3/module.modulemap @@ -0,0 +1,5 @@ +module SQLite3 [system] { + header "/usr/include/sqlite3.h" + link "sqlite3" + export * +} From 1ae20631b6a7942ae375d881dbfafbec8449bc58 Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Fri, 8 Jan 2016 08:56:33 -0800 Subject: [PATCH 0328/1046] Use valid comment chars to keep pod happy _No good deed goes unpunished._ --- module.modulemap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module.modulemap b/module.modulemap index 29301561..bf84a22e 100644 --- a/module.modulemap +++ b/module.modulemap @@ -1,7 +1,7 @@ -# This file is required by current CocoaPods (0.39.0). -# - Generates warning: Umbrella header for module 'SQLite' does not include header 'fts3_tokenizer.h' +// This file is required by current CocoaPods (0.39.0). +// - Generates warning: Umbrella header for module 'SQLite' does not include header 'fts3_tokenizer.h' -# Xcode generates its own correct ModuleMap, and Carthage does the same +// Xcode generates its own correct ModuleMap, and Carthage does the same framework module SQLite { umbrella header "SQLite.h" From d76092cd450109365080773068f7aacc0b79b74c Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Sat, 9 Jan 2016 18:24:23 -0800 Subject: [PATCH 0329/1046] Start moving issues into tracking document, minor docs example tweak --- Documentation/Index.md | 8 +++++--- Documentation/Planning.md | 33 +++++++++++++++++++++++++++++++++ README.md | 3 +++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 Documentation/Planning.md diff --git a/Documentation/Index.md b/Documentation/Index.md index ac882a09..a244ee12 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1450,13 +1450,15 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building db.changes // -> {Some 1} ``` - Statements with results may be iterated over. + Statements with results may be iterated over, using the columnNames if useful. ``` swift let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { - print("id: \(row[0]), email: \(row[1])") - // id: Optional(1), email: Optional("alice@mac.com") + for (index, name) in stmt.columnNames.enumerate() { + print ("\(name)=\(row[index]!)") + // id: Optional(1), email: Optional("alice@mac.com") + } } ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md new file mode 100644 index 00000000..f10babe7 --- /dev/null +++ b/Documentation/Planning.md @@ -0,0 +1,33 @@ +# SQLite.swift Planning + +This document captures both near term steps (aka Roadmap) and feature requests. +The goal is to add some visibility and guidance for future additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. + +## Roadmap + +_Lists agreed upon next steps in approximate priority order._ + + * publish to the CocoaPods directory at https://cocoapods.org/, per #257 + * add SQLCipher back into the product as a separate repo, per #311 + + +## Feature Requests + +_A gathering point for ideas for new features. In general, the corresponding issue will be closed once it is added here, with the assumption that it will be referred to when it comes time to add the corresponding feature._ + +### Packaging + + * add TV OS support, per #272 - currently pending SQLiteCipher merge and/or adding ability for user to pick their preferred [SQLCipher](https://github.com/sqlcipher/sqlcipher) branch + * linux support via Swift Package Manager, per #315 + +### Features + + * provide separate threads for update vs read, so updates don't block reads, per #236 + * expose more FTS4 options, e.g. virtual table support per #164 + * expose triggers, per #164 + +## Suspended Feature Requests + +_Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._ + + * provide a mechanism for INSERT INTO multiple values, per #168 diff --git a/README.md b/README.md index a0a18e2d..0f4f285c 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,8 @@ To install SQLite.swift with [SQLCipher][] support: ## Communication +[See the planning document] for a roadmap and existing feature requests. + [Read the contributing guidelines][]. The _TL;DR_ (but please; _R_): - Need **help** or have a **general question**? [Ask on Stack @@ -220,6 +222,7 @@ To install SQLite.swift with [SQLCipher][] support: - Found a **bug** or have a **feature request**? [Open an issue][]. - Want to **contribute**? [Submit a pull request][]. +[See the planning document]: /Documentation/Planning.md [Read the contributing guidelines]: ./CONTRIBUTING.md#contributing [Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new From c400615b93bb96e2139d1e3ee2573bbf6a8c6205 Mon Sep 17 00:00:00 2001 From: agisboye Date: Sun, 10 Jan 2016 19:46:00 +0100 Subject: [PATCH 0330/1046] SQLiteCipher NSData key support --- Source/Cipher/Cipher.swift | 12 ++++++++++++ Tests/CipherTests.swift | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Source/Cipher/Cipher.swift b/Source/Cipher/Cipher.swift index 8cf8396c..2596f239 100644 --- a/Source/Cipher/Cipher.swift +++ b/Source/Cipher/Cipher.swift @@ -36,4 +36,16 @@ extension Connection { try check(sqlite3_rekey(handle, key, Int32(key.utf8.count))) } + public func key(key: Blob) throws { + try check(sqlite3_key(handle, key.bytes, Int32(key.bytes.count))) + try execute( + "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + + "DROP TABLE \"__SQLCipher.swift__\";" + ) + } + + public func rekey(key: Blob) throws { + try check(sqlite3_rekey(handle, key.bytes, Int32(key.bytes.count))) + } + } diff --git a/Tests/CipherTests.swift b/Tests/CipherTests.swift index f9919999..e2977678 100644 --- a/Tests/CipherTests.swift +++ b/Tests/CipherTests.swift @@ -4,12 +4,22 @@ import SQLiteCipher class CipherTests: XCTestCase { let db = try! Connection() + let db2 = try! Connection() override func setUp() { + // db try! db.key("hello") try! db.run("CREATE TABLE foo (bar TEXT)") try! db.run("INSERT INTO foo (bar) VALUES ('world')") + + // db2 + let keyData = NSMutableData(length: 64)! + let _ = SecRandomCopyBytes(kSecRandomDefault, 64, UnsafeMutablePointer(keyData.mutableBytes)) + try! db2.key("hello") + + try! db2.run("CREATE TABLE foo (bar TEXT)") + try! db2.run("INSERT INTO foo (bar) VALUES ('world')") super.setUp() } @@ -22,6 +32,18 @@ class CipherTests: XCTestCase { try! db.rekey("goodbye") XCTAssertEqual(1, db.scalar("SELECT count(*) FROM foo") as? Int64) } + + func test_data_key() { + XCTAssertEqual(1, db2.scalar("SELECT count(*) FROM foo") as? Int64) + } + + func test_data_rekey() { + let keyData = NSMutableData(length: 64)! + SecRandomCopyBytes(kSecRandomDefault, 64, UnsafeMutablePointer(keyData.mutableBytes)) + + try! db2.rekey(Blob(bytes: keyData.bytes, length: keyData.length)) + XCTAssertEqual(1, db2.scalar("SELECT count(*) FROM foo") as? Int64) + } func test_keyFailure() { let path = "\(NSTemporaryDirectory())/db.sqlite3" From 21b3bf5a1006ed65a89809caee428858c18b0d23 Mon Sep 17 00:00:00 2001 From: agisboye Date: Sun, 10 Jan 2016 20:53:05 +0100 Subject: [PATCH 0331/1046] Fix typo --- Tests/CipherTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CipherTests.swift b/Tests/CipherTests.swift index e2977678..2c4dde0e 100644 --- a/Tests/CipherTests.swift +++ b/Tests/CipherTests.swift @@ -16,7 +16,7 @@ class CipherTests: XCTestCase { // db2 let keyData = NSMutableData(length: 64)! let _ = SecRandomCopyBytes(kSecRandomDefault, 64, UnsafeMutablePointer(keyData.mutableBytes)) - try! db2.key("hello") + try! db2.key(Blob(bytes: keyData.bytes, length: keyData.length)) try! db2.run("CREATE TABLE foo (bar TEXT)") try! db2.run("INSERT INTO foo (bar) VALUES ('world')") From 1cd2c457427424c6c6cd15b8eef9d5c7eb119bac Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 10 Jan 2016 21:30:25 -0500 Subject: [PATCH 0332/1046] Lock installation to tag Finally. Signed-off-by: Stephen Celis --- Documentation/Index.md | 5 ++--- README.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index aa380957..b3088292 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" "master" + github "stephencelis/SQLite.swift" ~> 0.9.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -100,8 +100,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', - git: 'https://github.com/stephencelis/SQLite.swift.git' + pod 'SQLite.swift', '~> 0.9.0' ``` 3. Run `pod install`. diff --git a/README.md b/README.md index af55b52c..b87780c2 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" "master" + github "stephencelis/SQLite.swift" ~> 0.9.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -148,8 +148,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', - git: 'https://github.com/stephencelis/SQLite.swift.git' + pod 'SQLite.swift', '~> 0.9.0' ``` 3. Run `pod install`. From 8d99291cf0a66a32e87f1b7db0124400a1f52dc5 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 11 Jan 2016 08:43:51 -0500 Subject: [PATCH 0333/1046] Use hard-coded SDK path for system module map We've been down this path before. Unfortunately, `/usr/include/sqlite3.h` isn't reliably available on all Macs. Because module maps don't yet have dynamic support (something that the Swift Package Manager intends to have solved soon), we need to hardcode the SDK path (or use a script to substitute it). Fixes #319. Signed-off-by: Stephen Celis --- SQLite3/module.modulemap | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SQLite3/module.modulemap b/SQLite3/module.modulemap index d55e7697..c48efe1c 100644 --- a/SQLite3/module.modulemap +++ b/SQLite3/module.modulemap @@ -1,5 +1,8 @@ module SQLite3 [system] { - header "/usr/include/sqlite3.h" + // Requires latest Xcode installed in standard path. + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" + link "sqlite3" + export * } From eb27d4d28d01c09cf51c74a541afd9955b9ecf6f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 11 Jan 2016 10:15:57 -0500 Subject: [PATCH 0334/1046] Make fts3_tokenizer.h private Signed-off-by: Stephen Celis --- SQLite.swift.podspec | 3 +-- module.modulemap | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 module.modulemap diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 877ae966..1e8c55d8 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -20,14 +20,13 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/stephencelis' s.module_name = 'SQLite' - s.module_map = 'module.modulemap' s.ios.deployment_target = "8.0" s.source_files = 'SQLite/**/*' + s.private_header_files = 'SQLite/Core/fts3_tokenizer.h' # make the sqlite3 C library behave like a module s.libraries = 'sqlite3' s.xcconfig = { 'SWIFT_INCLUDE_PATHS' => '${PODS_ROOT}/SQLite.swift/SQLite3' } s.preserve_path = 'SQLite3/*' - end diff --git a/module.modulemap b/module.modulemap deleted file mode 100644 index bf84a22e..00000000 --- a/module.modulemap +++ /dev/null @@ -1,12 +0,0 @@ -// This file is required by current CocoaPods (0.39.0). -// - Generates warning: Umbrella header for module 'SQLite' does not include header 'fts3_tokenizer.h' - -// Xcode generates its own correct ModuleMap, and Carthage does the same - -framework module SQLite { - umbrella header "SQLite.h" - - export * - module * { export * } -} - From acae812bd1d511d78ec4c0c4f9fe1862194d60c9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 11 Jan 2016 10:36:57 -0500 Subject: [PATCH 0335/1046] Update OS X deployment target 10.9 should be fine. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 5d2d8bf4..ae598ffe 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -630,6 +630,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -673,6 +674,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; From c717ce16fe95e16ca98b51d265716e7da4a5de80 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 11 Jan 2016 10:38:43 -0500 Subject: [PATCH 0336/1046] Re-enable use with app extension APIs Was disabled during some project file restructuring. Signed-off-by: Stephen Celis --- SQLite.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ae598ffe..7f5ee2a0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -688,6 +688,7 @@ EE247AE81C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -708,6 +709,7 @@ EE247AE91C3F04ED00AE3E12 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -747,6 +749,7 @@ EE247B4D1C3F3ED000AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; @@ -769,6 +772,7 @@ EE247B4E1C3F3ED000AE3E12 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; From ae7b879bfbf759eec8d23e0867972c876b1d731c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 11 Jan 2016 10:28:02 -0500 Subject: [PATCH 0337/1046] 0.9.1 - Fixes CocoaPods/Carthage installation issues around the module map path. - Fixes CocoaPods-related warning. - Re-enables use with app extension APIs. - Updates OS X deployment target. Signed-off-by: Stephen Celis --- SQLite.swift.podspec | 2 +- SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 1e8c55d8..1abb8e04 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.9.0" + s.version = "0.9.1" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/SQLite/Info.plist b/SQLite/Info.plist index 76d3d186..70b8293c 100644 --- a/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.0 + 0.9.1 CFBundleSignature ???? CFBundleVersion From 1c86f589dbfaedd96dabb23336c95271677bc5a3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 11 Jan 2016 10:54:45 -0500 Subject: [PATCH 0338/1046] Bump docs to 0.9.1 Signed-off-by: Stephen Celis --- Documentation/Index.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index b3088292..62f6a82f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.9.0 + github "stephencelis/SQLite.swift" ~> 0.9.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -100,7 +100,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.9.0' + pod 'SQLite.swift', '~> 0.9.1' ``` 3. Run `pod install`. diff --git a/README.md b/README.md index b87780c2..78e394d2 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.9.0 + github "stephencelis/SQLite.swift" ~> 0.9.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -148,7 +148,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.9.0' + pod 'SQLite.swift', '~> 0.9.1' ``` 3. Run `pod install`. From 434b88271bac7ad08396fe1cf4d0bbf913f1db12 Mon Sep 17 00:00:00 2001 From: mikemee Date: Mon, 11 Jan 2016 15:54:59 -0800 Subject: [PATCH 0339/1046] Move issue #30 to feature request list, add links --- Documentation/Planning.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index f10babe7..12555703 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -7,8 +7,8 @@ The goal is to add some visibility and guidance for future additions and Pull Re _Lists agreed upon next steps in approximate priority order._ - * publish to the CocoaPods directory at https://cocoapods.org/, per #257 - * add SQLCipher back into the product as a separate repo, per #311 + * ~~publish to the CocoaPods directory at https://cocoapods.org/, per [#257](https://github.com/stephencelis/SQLite.swift/issues/257)~~ _jan 7, 2016_) + * add SQLCipher back into the product as a separate repo, per [#311](https://github.com/stephencelis/SQLite.swift/issues/311), _in progress jan 6, 2016_ ## Feature Requests @@ -17,17 +17,18 @@ _A gathering point for ideas for new features. In general, the corresponding iss ### Packaging - * add TV OS support, per #272 - currently pending SQLiteCipher merge and/or adding ability for user to pick their preferred [SQLCipher](https://github.com/sqlcipher/sqlcipher) branch - * linux support via Swift Package Manager, per #315 + * add TV OS support, per [#272](https://github.com/stephencelis/SQLite.swift/issues/272) - currently pending SQLiteCipher merge and/or adding ability for user to pick their preferred [SQLCipher](https://github.com/sqlcipher/sqlcipher) branch + * linux support via Swift Package Manager, per [#315](https://github.com/stephencelis/SQLite.swift/issues/315) ### Features - * provide separate threads for update vs read, so updates don't block reads, per #236 - * expose more FTS4 options, e.g. virtual table support per #164 - * expose triggers, per #164 + * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per [#30](https://github.com/stephencelis/SQLite.swift/issues/30) + * provide separate threads for update vs read, so updates don't block reads, per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) + * expose more FTS4 options, e.g. virtual table support per [#164](https://github.com/stephencelis/SQLite.swift/issues/164) + * expose triggers, per [#164](https://github.com/stephencelis/SQLite.swift/issues/164) ## Suspended Feature Requests _Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._ - * provide a mechanism for INSERT INTO multiple values, per #168 + * provide a mechanism for INSERT INTO multiple values, per [#168](https://github.com/stephencelis/SQLite.swift/issues/168) From 07cdf38b351c41344dd74025788d4f2b8f13ee18 Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Wed, 13 Jan 2016 16:37:48 -0800 Subject: [PATCH 0340/1046] 0.9.2 candidate. Hopefully fixes: #324, #325, #326 --- Documentation/Index.md | 4 ++-- README.md | 8 ++++--- SQLite.swift.podspec | 11 +++++----- SQLite.xcodeproj/project.pbxproj | 28 ++++++++++++++++++++---- SQLite/Core/Blob.swift | 2 -- SQLite/Core/Connection.swift | 1 - SQLite/Core/SQLite-Bridging.h | 37 ++++++++++++++++++++++++++++++++ SQLite/Core/SQLite-Bridging.m | 6 ++---- SQLite/Core/Statement.swift | 2 -- SQLite/Helpers.swift | 2 -- SQLite/Info.plist | 2 +- SQLite/SQLite.h | 7 +----- SQLite3/module.modulemap | 8 ------- podstuff/module.modulemap | 12 +++++++++++ 14 files changed, 89 insertions(+), 41 deletions(-) create mode 100644 SQLite/Core/SQLite-Bridging.h delete mode 100644 SQLite3/module.modulemap create mode 100644 podstuff/module.modulemap diff --git a/Documentation/Index.md b/Documentation/Index.md index 62f6a82f..8819ea40 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.9.1 + github "stephencelis/SQLite.swift" ~> 0.9.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -100,7 +100,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.9.1' + pod 'SQLite.swift', '~> 0.9.2' ``` 3. Run `pod install`. diff --git a/README.md b/README.md index 78e394d2..7f40538b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# SQLite.swift [![Build Status][Badge]][Travis] +# SQLite.swift + +[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -124,7 +126,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.9.1 + github "stephencelis/SQLite.swift" ~> 0.9.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -148,7 +150,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.9.1' + pod 'SQLite.swift', '~> 0.9.2' ``` 3. Run `pod install`. diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 1abb8e04..24d2211a 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.9.1" + s.version = "0.9.2" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,12 +21,11 @@ Pod::Spec.new do |s| s.module_name = 'SQLite' s.ios.deployment_target = "8.0" + s.tvos.deployment_target = "9.0" + s.osx.deployment_target = "10.9" + s.module_map = "podstuff/module.modulemap" + s.xcconfig = { 'OTHER_LDFLAGS': '-lsqlite3' } s.source_files = 'SQLite/**/*' s.private_header_files = 'SQLite/Core/fts3_tokenizer.h' - - # make the sqlite3 C library behave like a module - s.libraries = 'sqlite3' - s.xcconfig = { 'SWIFT_INCLUDE_PATHS' => '${PODS_ROOT}/SQLite.swift/SQLite3' } - s.preserve_path = 'SQLite3/*' end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 7f5ee2a0..377d0fc7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -79,6 +79,12 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; + EE91808C1C46E34A0038162A /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE9180901C46E8980038162A /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; + EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -148,6 +154,10 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SQLite.swift.podspec; sourceTree = ""; }; + EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; + EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -155,6 +165,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -170,6 +181,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -208,6 +220,7 @@ EE247AD51C3F04ED00AE3E12 /* SQLite */ = { isa = PBXGroup; children = ( + EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */, EE247AD61C3F04ED00AE3E12 /* SQLite.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, @@ -245,6 +258,7 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( + EE91808D1C46E5230038162A /* SQLite-Bridging.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, @@ -288,6 +302,8 @@ EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, EE247B8C1C3F821200AE3E12 /* .travis.yml */, EE247B8D1C3F821200AE3E12 /* Makefile */, + EE9180931C46EA210038162A /* libsqlite3.tbd */, + EE9180911C46E9D30038162A /* libsqlite3.tbd */, EE247B8E1C3F822500AE3E12 /* Documentation */, ); name = Metadata; @@ -318,7 +334,9 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, + EE91808C1C46E34A0038162A /* usr/include/sqlite3.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -327,8 +345,10 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + EE9180901C46E8980038162A /* usr/include/sqlite3.h in Headers */, EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, + EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -701,7 +721,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_INCLUDE_PATHS = "$(SRCROOT)/SQLite3"; + SWIFT_INCLUDE_PATHS = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -722,7 +742,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_INCLUDE_PATHS = "$(SRCROOT)/SQLite3"; + SWIFT_INCLUDE_PATHS = ""; }; name = Release; }; @@ -765,7 +785,7 @@ PRODUCT_NAME = SQLite; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_INCLUDE_PATHS = "${SRCROOT}/SQLite3"; + SWIFT_INCLUDE_PATHS = ""; }; name = Debug; }; @@ -788,7 +808,7 @@ PRODUCT_NAME = SQLite; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_INCLUDE_PATHS = "${SRCROOT}/SQLite3"; + SWIFT_INCLUDE_PATHS = ""; }; name = Release; }; diff --git a/SQLite/Core/Blob.swift b/SQLite/Core/Blob.swift index 69b1161f..1b30ffa1 100644 --- a/SQLite/Core/Blob.swift +++ b/SQLite/Core/Blob.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import SQLite3 - public struct Blob { public let bytes: [UInt8] diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 3574e060..0243317f 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -23,7 +23,6 @@ // import Dispatch -import SQLite3 /// A connection to SQLite. public final class Connection { diff --git a/SQLite/Core/SQLite-Bridging.h b/SQLite/Core/SQLite-Bridging.h new file mode 100644 index 00000000..d15e8d56 --- /dev/null +++ b/SQLite/Core/SQLite-Bridging.h @@ -0,0 +1,37 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@import Foundation; + +#ifndef COCOAPODS +#import "sqlite3.h" +#endif + +typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround + +NS_ASSUME_NONNULL_BEGIN +typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); +NS_ASSUME_NONNULL_END + diff --git a/SQLite/Core/SQLite-Bridging.m b/SQLite/Core/SQLite-Bridging.m index 771a4123..a5702cc7 100644 --- a/SQLite/Core/SQLite-Bridging.m +++ b/SQLite/Core/SQLite-Bridging.m @@ -22,11 +22,9 @@ // THE SOFTWARE. // -#import "fts3_tokenizer.h" - +#import "SQLite-Bridging.h" #import "sqlite3.h" - -#import +#import "fts3_tokenizer.h" #pragma mark - FTS diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 5de78fe6..1143ee7c 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import SQLite3 - /// A single SQL statement. public final class Statement { diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index 90febd89..c1775e68 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -22,8 +22,6 @@ // THE SOFTWARE. // -import SQLite3 - public typealias Star = (Expression?, Expression?) -> Expression public func *(_: Expression?, _: Expression?) -> Expression { diff --git a/SQLite/Info.plist b/SQLite/Info.plist index 70b8293c..230b60f6 100644 --- a/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.1 + 0.9.2 CFBundleSignature ???? CFBundleVersion diff --git a/SQLite/SQLite.h b/SQLite/SQLite.h index a3a5eef2..693ce323 100644 --- a/SQLite/SQLite.h +++ b/SQLite/SQLite.h @@ -3,9 +3,4 @@ FOUNDATION_EXPORT double SQLiteVersionNumber; FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; -typedef struct SQLiteHandle SQLiteHandle; // ??? - CocoaPods workaround - -NS_ASSUME_NONNULL_BEGIN -typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); -NS_ASSUME_NONNULL_END \ No newline at end of file +#import diff --git a/SQLite3/module.modulemap b/SQLite3/module.modulemap deleted file mode 100644 index c48efe1c..00000000 --- a/SQLite3/module.modulemap +++ /dev/null @@ -1,8 +0,0 @@ -module SQLite3 [system] { - // Requires latest Xcode installed in standard path. - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" - - link "sqlite3" - - export * -} diff --git a/podstuff/module.modulemap b/podstuff/module.modulemap new file mode 100644 index 00000000..122acd2e --- /dev/null +++ b/podstuff/module.modulemap @@ -0,0 +1,12 @@ +framework module SQLite { + umbrella header "SQLite.h" + + // Load the SDK header alongside SQLite.swift. Alternate headers: + // + // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + + export * + module * { export * } +} From fb3afee504a80a897127c50a80c3478249c3ae40 Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Wed, 13 Jan 2016 16:52:01 -0800 Subject: [PATCH 0341/1046] A little cleaner / more correct way to do this. --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 24d2211a..4a03be5c 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -25,7 +25,7 @@ Pod::Spec.new do |s| s.osx.deployment_target = "10.9" s.module_map = "podstuff/module.modulemap" - s.xcconfig = { 'OTHER_LDFLAGS': '-lsqlite3' } + s.libraries = 'sqlite3' s.source_files = 'SQLite/**/*' s.private_header_files = 'SQLite/Core/fts3_tokenizer.h' end From 13e7d774d6f3e842e7940e36ff6707039a695cf8 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 13 Jan 2016 22:42:48 -0500 Subject: [PATCH 0342/1046] Scope podspec source files Signed-off-by: Stephen Celis --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4a03be5c..b8160396 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -26,6 +26,6 @@ Pod::Spec.new do |s| s.module_map = "podstuff/module.modulemap" s.libraries = 'sqlite3' - s.source_files = 'SQLite/**/*' + s.source_files = 'SQLite/**/*.{c,h,m,swift}' s.private_header_files = 'SQLite/Core/fts3_tokenizer.h' end From 632b9b576deb3c87205603e452050eb8195f7373 Mon Sep 17 00:00:00 2001 From: mikemee Date: Wed, 13 Jan 2016 20:47:55 -0800 Subject: [PATCH 0343/1046] Add support links to features, add link to SQLiteCipher, Gitter link Fixes #328 --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f40538b..da121e70 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -25,7 +25,8 @@ syntax _and_ intent. - [Full-text search][] support - [Well-documented][See Documentation] - Extensively tested - - SQLCipher support will be available again soon (see #311) + - Companion project has [SQLCipher support](https://github.com/stephencelis/SQLiteCipher.swift) + - Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) [Full-text search]: Documentation/Index.md#full-text-search [See Documentation]: Documentation/Index.md#sqliteswift-documentation From fe02a3f52ecaaadcff170229e21c300898ec246f Mon Sep 17 00:00:00 2001 From: mikemee Date: Tue, 19 Jan 2016 16:25:55 -0800 Subject: [PATCH 0344/1046] Add links to SO articles with more iOS info Based on the recent #332 issue, and earlier similar questions, I've added this note to the documentation. Linking to SO is a short-term measure, but if it stops new issues being opened, then everyone wins imnsho. --- Documentation/Index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 8819ea40..5906ccff 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -201,7 +201,8 @@ let db = try Connection(path, readonly: true) ``` > _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). - +> +> See these two Stack Overflow questions for more information about iOS apps with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). We welcome sample code to show how to successfully copy and use a bundled "seed" database for writing in an app. #### In-Memory Databases From c25ddfe3516153f1d85ee52fc00dba3189caa848 Mon Sep 17 00:00:00 2001 From: mikemee Date: Tue, 19 Jan 2016 16:57:40 -0800 Subject: [PATCH 0345/1046] Create a new "Related" section --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index da121e70..02291cbe 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,13 @@ To install SQLite.swift as an Xcode sub-project: SQLite.swift is available under the MIT license. See [the LICENSE file](./LICENSE.txt) for more information. +## Related + +These projects enhance or use SQLite.swift: + + - [SQLiteCipher.swift](https://github.com/stephencelis/SQLiteCipher.swift) + - [SQLiteMigrationManager.swift](https://github.com/garriguv/SQLiteMigrationManager.swift) (inspired by [FMDBMigrationManager](https://github.com/layerhq/FMDBMigrationManager)) + ## Alternatives From 36f311b214118e9571eea63298171e365fdce5a7 Mon Sep 17 00:00:00 2001 From: Michael Mee Date: Thu, 21 Jan 2016 10:56:39 -0800 Subject: [PATCH 0346/1046] Fixes #335 - allow OSX 10.9 and above instead of 10.11 --- SQLite.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 377d0fc7..35acd29c 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -780,7 +780,7 @@ INFOPLIST_FILE = SQlite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQlite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -803,7 +803,7 @@ INFOPLIST_FILE = SQlite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQlite; PRODUCT_NAME = SQLite; SDKROOT = macosx; From 21aaaadae4d26ab51aae05d549ca77de6e80b57e Mon Sep 17 00:00:00 2001 From: banxi Date: Tue, 2 Feb 2016 15:25:51 +0800 Subject: [PATCH 0347/1046] Fix no Foundation import error withouth import Foundation sometimes will cause Compile Error --- SQLite/Foundation.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SQLite/Foundation.swift b/SQLite/Foundation.swift index ef39d3bf..52b2ea02 100644 --- a/SQLite/Foundation.swift +++ b/SQLite/Foundation.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import Foundation + extension NSData : Value { public class var declaredDatatype: String { @@ -101,4 +103,4 @@ extension Row { return get(column) } -} \ No newline at end of file +} From 94838a098893ac85b2b37d571e40fd5aca02ace3 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sun, 7 Feb 2016 10:25:21 -0600 Subject: [PATCH 0348/1046] Fix Carthage build errors on case-sensitive filesystem --- SQLite.xcodeproj/project.pbxproj | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 35acd29c..4ec5adab 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -100,7 +100,7 @@ containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; proxyType = 1; remoteGlobalIDString = EE247B3B1C3F3ED000AE3E12; - remoteInfo = SQlite; + remoteInfo = SQLite; }; /* End PBXContainerItemProxy section */ @@ -405,7 +405,7 @@ dependencies = ( ); name = "SQLite Mac"; - productName = SQlite; + productName = SQLite; productReference = EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; @@ -423,7 +423,7 @@ EE247B481C3F3ED000AE3E12 /* PBXTargetDependency */, ); name = "SQLiteTests Mac"; - productName = SQliteTests; + productName = SQLiteTests; productReference = EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; @@ -777,11 +777,11 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SQlite/Info.plist; + INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQlite; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; SKIP_INSTALL = YES; @@ -800,11 +800,11 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SQlite/Info.plist; + INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQlite; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; SKIP_INSTALL = YES; @@ -817,10 +817,10 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = SQliteTests/Info.plist; + INFOPLIST_FILE = SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQliteTests; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; @@ -831,10 +831,10 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = SQliteTests/Info.plist; + INFOPLIST_FILE = SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; - PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQliteTests; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; }; From 19f467a434f047466e73031d456b2e2ec4af6446 Mon Sep 17 00:00:00 2001 From: Erik Holley Date: Wed, 10 Feb 2016 11:37:51 -0700 Subject: [PATCH 0349/1046] Added tvOS support. --- SQLite.xcodeproj/project.pbxproj | 269 +++++++++++++++++- .../xcschemes/SQLite tvOS.xcscheme | 100 +++++++ 2 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 4ec5adab..d73ebb51 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -7,6 +7,45 @@ objects = { /* Begin PBXBuildFile section */ + 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; + 03A65E711C6BB2CD0062603F /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; + 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; + 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03A65E761C6BB2E60062603F /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; + 03A65E771C6BB2E60062603F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; + 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; + 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; + 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; + 03A65E7D1C6BB2F70062603F /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* R*Tree.swift */; }; + 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; + 03A65E7F1C6BB2FB0062603F /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; + 03A65E801C6BB2FB0062603F /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; + 03A65E811C6BB2FB0062603F /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */; }; + 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFE1C3F06E900AE3E12 /* Expression.swift */; }; + 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFF1C3F06E900AE3E12 /* Operators.swift */; }; + 03A65E841C6BB2FB0062603F /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; + 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; + 03A65E861C6BB2FB0062603F /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; + 03A65E871C6BB3030062603F /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; + 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; + 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; + 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; + 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; + 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; + 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; + 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; + 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; + 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */; }; + 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; + 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; + 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; + 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; + 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; + 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -88,6 +127,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 03A65E651C6BB0F60062603F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03A65E591C6BB0F50062603F; + remoteInfo = "SQLite tvOS"; + }; EE247ADF1C3F04ED00AE3E12 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; @@ -105,6 +151,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.1.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -161,6 +210,22 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 03A65E561C6BB0F50062603F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03A65E601C6BB0F60062603F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACF1C3F04ED00AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -213,6 +278,8 @@ EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */, EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */, EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */, + 03A65E5A1C6BB0F50062603F /* SQLite.framework */, + 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */, ); name = Products; sourceTree = ""; @@ -304,6 +371,7 @@ EE247B8D1C3F821200AE3E12 /* Makefile */, EE9180931C46EA210038162A /* libsqlite3.tbd */, EE9180911C46E9D30038162A /* libsqlite3.tbd */, + 03A65E961C6BB3210062603F /* libsqlite3.tbd */, EE247B8E1C3F822500AE3E12 /* Documentation */, ); name = Metadata; @@ -330,6 +398,17 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 03A65E571C6BB0F50062603F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, + 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */, + 03A65E711C6BB2CD0062603F /* usr/include/sqlite3.h in Headers */, + 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD01C3F04ED00AE3E12 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -355,6 +434,42 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 03A65E591C6BB0F50062603F /* SQLite tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03A65E6F1C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLite tvOS" */; + buildPhases = ( + 03A65E551C6BB0F50062603F /* Sources */, + 03A65E561C6BB0F50062603F /* Frameworks */, + 03A65E571C6BB0F50062603F /* Headers */, + 03A65E581C6BB0F50062603F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "SQLite tvOS"; + productName = "SQLite tvOS"; + productReference = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; + productType = "com.apple.product-type.framework"; + }; + 03A65E621C6BB0F60062603F /* SQLiteTests tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03A65E701C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLiteTests tvOS" */; + buildPhases = ( + 03A65E5F1C6BB0F60062603F /* Sources */, + 03A65E601C6BB0F60062603F /* Frameworks */, + 03A65E611C6BB0F60062603F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 03A65E661C6BB0F60062603F /* PBXTargetDependency */, + ); + name = "SQLiteTests tvOS"; + productName = "SQLite tvOSTests"; + productReference = 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; EE247AD21C3F04ED00AE3E12 /* SQLite iOS */ = { isa = PBXNativeTarget; buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; @@ -436,6 +551,12 @@ LastSwiftUpdateCheck = 0720; LastUpgradeCheck = 0720; TargetAttributes = { + 03A65E591C6BB0F50062603F = { + CreatedOnToolsVersion = 7.2; + }; + 03A65E621C6BB0F60062603F = { + CreatedOnToolsVersion = 7.2; + }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; }; @@ -466,11 +587,27 @@ EE247ADC1C3F04ED00AE3E12 /* SQLiteTests iOS */, EE247B3B1C3F3ED000AE3E12 /* SQLite Mac */, EE247B441C3F3ED000AE3E12 /* SQLiteTests Mac */, + 03A65E591C6BB0F50062603F /* SQLite tvOS */, + 03A65E621C6BB0F60062603F /* SQLiteTests tvOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 03A65E581C6BB0F50062603F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03A65E611C6BB0F60062603F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD11C3F04ED00AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -502,6 +639,53 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 03A65E551C6BB0F50062603F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03A65E801C6BB2FB0062603F /* CoreFunctions.swift in Sources */, + 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, + 03A65E7D1C6BB2F70062603F /* R*Tree.swift in Sources */, + 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */, + 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, + 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, + 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, + 03A65E7F1C6BB2FB0062603F /* Collation.swift in Sources */, + 03A65E861C6BB2FB0062603F /* Setter.swift in Sources */, + 03A65E811C6BB2FB0062603F /* CustomFunctions.swift in Sources */, + 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */, + 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, + 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, + 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, + 03A65E841C6BB2FB0062603F /* Query.swift in Sources */, + 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, + 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, + 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03A65E5F1C6BB0F60062603F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, + 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */, + 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, + 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, + 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */, + 03A65E871C6BB3030062603F /* AggregateFunctionsTests.swift in Sources */, + 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */, + 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */, + 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */, + 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */, + 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, + 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, + 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, + 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, + 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACE1C3F04ED00AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -599,6 +783,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 03A65E661C6BB0F60062603F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03A65E591C6BB0F50062603F /* SQLite tvOS */; + targetProxy = 03A65E651C6BB0F60062603F /* PBXContainerItemProxy */; + }; EE247AE01C3F04ED00AE3E12 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = EE247AD21C3F04ED00AE3E12 /* SQLite iOS */; @@ -612,6 +801,66 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 03A65E6B1C6BB0F60062603F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SQLite/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TVOS_DEPLOYMENT_TARGET = 9.1; + }; + name = Debug; + }; + 03A65E6C1C6BB0F60062603F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SQLite/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + TVOS_DEPLOYMENT_TARGET = 9.1; + }; + name = Release; + }; + 03A65E6D1C6BB0F60062603F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = SQLite/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 9.1; + }; + name = Debug; + }; + 03A65E6E1C6BB0F60062603F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = SQLite/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TVOS_DEPLOYMENT_TARGET = 9.1; + }; + name = Release; + }; EE247AE51C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -656,7 +905,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -698,7 +947,7 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -843,6 +1092,22 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 03A65E6F1C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLite tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03A65E6B1C6BB0F60062603F /* Debug */, + 03A65E6C1C6BB0F60062603F /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + 03A65E701C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLiteTests tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03A65E6D1C6BB0F60062603F /* Debug */, + 03A65E6E1C6BB0F60062603F /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme new file mode 100644 index 00000000..f500a2ae --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c76c7e8b30b3bc36dadd5b7b4e0680defde4c2e3 Mon Sep 17 00:00:00 2001 From: Hilton Campbell Date: Wed, 24 Feb 2016 11:43:33 -0700 Subject: [PATCH 0350/1046] Fix namespacing all of a table's columns using * --- SQLite/Typed/Query.swift | 8 ++++---- SQLiteTests/QueryTests.swift | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 634b1b6a..e5319533 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -50,8 +50,8 @@ extension SchemaType { /// - Parameter all: A list of expressions to select. /// /// - Returns: A query with the given `SELECT` clause applied. - public func select(column1: Expressible, _ column2: Expressible, _ more: Expressible...) -> Self { - return select(false, [column1, column2] + more) + public func select(column1: Expressible, _ more: Expressible...) -> Self { + return select(false, [column1] + more) } /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. @@ -65,8 +65,8 @@ extension SchemaType { /// - Parameter columns: A list of expressions to select. /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. - public func select(distinct column1: Expressible, _ column2: Expressible, _ more: Expressible...) -> Self { - return select(true, [column1, column2] + more) + public func select(distinct column1: Expressible, _ more: Expressible...) -> Self { + return select(true, [column1] + more) } /// Builds a copy of the query with the `SELECT` clause applied. diff --git a/SQLiteTests/QueryTests.swift b/SQLiteTests/QueryTests.swift index b25f2620..b76da61e 100644 --- a/SQLiteTests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -20,6 +20,14 @@ class QueryTests : XCTestCase { func test_select_withExpression_compilesSelectClause() { AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) } + + func test_select_withStarExpression_compilesSelectClause() { + AssertSQL("SELECT * FROM \"users\"", users.select(*)) + } + + func test_select_withNamespacedStarExpression_compilesSelectClause() { + AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) + } func test_select_withVariadicExpressions_compilesSelectClause() { AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) From 4ce221a1aa9531fd2fdcd9dae4f4d8547a1feba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Osin=CC=81ski?= Date: Thu, 17 Mar 2016 06:31:27 +0100 Subject: [PATCH 0351/1046] Make tests compile again. Change __FILE__ and __LINE__ to #file and #line --- SQLiteTests/TestHelpers.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index f7a503d6..c0ff9bb9 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -47,7 +47,7 @@ class SQLiteTestCase : XCTestCase { ) } - func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual( executions, trace[SQL] ?? 0, message ?? SQL, @@ -55,7 +55,7 @@ class SQLiteTestCase : XCTestCase { ) } - func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { try! statement.run() AssertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } @@ -96,11 +96,11 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") -func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () -> Expressible, file: String = __FILE__, line: UInt = __LINE__) { +func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () -> Expressible, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } -func AssertThrows(@autoclosure expression: () throws -> T, file: String = __FILE__, line: UInt = __LINE__) { +func AssertThrows(@autoclosure expression: () throws -> T, file: StaticString = #file, line: UInt = #line) { do { try expression() XCTFail("expression expected to throw", file: file, line: line) From b80e523fdaab66f68608d98f6a90d698f1fe02b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Osin=CC=81ski?= Date: Thu, 17 Mar 2016 06:32:45 +0100 Subject: [PATCH 0352/1046] Change __FUNCTION__ to #function --- SQLite/Helpers.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index c1775e68..53ea822e 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -88,15 +88,15 @@ extension String { } -@warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = __FUNCTION__) -> Expression { +@warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = #function) -> Expression { return function.infix(lhs, rhs, wrap: wrap) } -@warn_unused_result func wrap(expression: Expressible, function: String = __FUNCTION__) -> Expression { +@warn_unused_result func wrap(expression: Expressible, function: String = #function) -> Expression { return function.wrap(expression) } -@warn_unused_result func wrap(expressions: [Expressible], function: String = __FUNCTION__) -> Expression { +@warn_unused_result func wrap(expressions: [Expressible], function: String = #function) -> Expression { return function.wrap(", ".join(expressions)) } From d09a1c49b955d44bfa1ff4c6f94ac9007eb85cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Osin=CC=81ski?= Date: Thu, 17 Mar 2016 06:34:00 +0100 Subject: [PATCH 0353/1046] Change typealias to associatedtype --- SQLite/Core/Value.swift | 4 ++-- SQLite/Helpers.swift | 2 +- SQLite/Typed/Expression.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite/Core/Value.swift b/SQLite/Core/Value.swift index 96e80371..2cfb45ed 100644 --- a/SQLite/Core/Value.swift +++ b/SQLite/Core/Value.swift @@ -33,9 +33,9 @@ public protocol Number : Binding {} public protocol Value : Expressible { // extensions cannot have inheritance clauses - typealias ValueType = Self + associatedtype ValueType = Self - typealias Datatype : Binding + associatedtype Datatype : Binding static var declaredDatatype: String { get } diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index 53ea822e..c4d3df65 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -30,7 +30,7 @@ public func *(_: Expression?, _: Expression?) -> Expression Date: Thu, 17 Mar 2016 06:36:40 +0100 Subject: [PATCH 0354/1046] Rename anyGenerator to AnyGenerator --- SQLite/Core/Statement.swift | 2 +- SQLite/Typed/Query.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 1143ee7c..ccfc314b 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -271,7 +271,7 @@ extension Cursor : SequenceType { public func generate() -> AnyGenerator { var idx = 0 - return anyGenerator { + return AnyGenerator { idx >= self.columnCount ? Optional.None : self[idx++] } } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index e5319533..a4e0d957 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -920,7 +920,7 @@ extension Connection { }() return AnySequence { - anyGenerator { statement.next().map { Row(columnNames, $0) } } + AnyGenerator { statement.next().map { Row(columnNames, $0) } } } } From 89e3bfc5419210e8b2c250038a752092206fe53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Osin=CC=81ski?= Date: Thu, 17 Mar 2016 06:38:42 +0100 Subject: [PATCH 0355/1046] Get rid of variable function argument --- SQLite/Extensions/FTS4.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index a755f96b..e5a1d815 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -28,7 +28,9 @@ extension Module { return FTS4([column] + more) } - @warn_unused_result public static func FTS4(var columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { + @warn_unused_result public static func FTS4(columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { + var columns = columns + if let tokenizer = tokenizer { columns.append("=".join([Expression(literal: "tokenize"), Expression(literal: tokenizer.description)])) } From be24c3895d8496fc66eea547b828da35213d80c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Osin=CC=81ski?= Date: Thu, 17 Mar 2016 06:54:16 +0100 Subject: [PATCH 0356/1046] Fix suffix incremental operator deprecation warnings --- SQLite/Core/Statement.swift | 7 ++++++- SQLite/Typed/Expression.swift | 10 +++++++++- SQLite/Typed/Query.swift | 5 +++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index ccfc314b..9fb87c49 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -272,7 +272,12 @@ extension Cursor : SequenceType { public func generate() -> AnyGenerator { var idx = 0 return AnyGenerator { - idx >= self.columnCount ? Optional.None : self[idx++] + if idx >= self.columnCount { + return Optional.None + } else { + idx += 1 + return self[idx - 1] + } } } diff --git a/SQLite/Typed/Expression.swift b/SQLite/Typed/Expression.swift index 5687487d..2e71b970 100644 --- a/SQLite/Typed/Expression.swift +++ b/SQLite/Typed/Expression.swift @@ -78,7 +78,15 @@ extension Expressible { let expressed = expression var idx = 0 return expressed.template.characters.reduce("") { template, character in - return template + (character == "?" ? transcode(expressed.bindings[idx++]) : String(character)) + let transcoded: String + + if character == "?" { + transcoded = transcode(expressed.bindings[idx]) + idx += 1 + } else { + transcoded = String(character) + } + return template + transcoded } } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index a4e0d957..f5803770 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -891,7 +891,7 @@ extension Connection { let e = q.expression var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } - for name in names { columnNames[name] = idx++ } + for name in names { columnNames[name] = idx; idx += 1 } } } @@ -914,7 +914,8 @@ extension Connection { continue } - columnNames[each.expression.template] = idx++ + columnNames[each.expression.template] = idx + idx += 1 } return columnNames }() From ddf653b8d02bee8b540b295c205c894eb1812557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Osin=CC=81ski?= Date: Sat, 19 Mar 2016 17:49:31 +0100 Subject: [PATCH 0357/1046] Update xcode version to 7.3 in travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 37785da9..1964faf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,4 @@ before_install: - gem install xcpretty --no-document script: - make test -osx_image: xcode7 +osx_image: xcode7.3 From d822ad2b77ec772f2ae5e9ca631b38c5e175764a Mon Sep 17 00:00:00 2001 From: Stefan Mayer-Popp Date: Wed, 23 Mar 2016 11:41:10 +0100 Subject: [PATCH 0358/1046] Update module.modulemap Imports the SDK based on current architecture. Fixes incompatibility problems for Xcode 7.3. Also includes link flag for convenience. --- podstuff/module.modulemap | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/podstuff/module.modulemap b/podstuff/module.modulemap index 122acd2e..443b93e9 100644 --- a/podstuff/module.modulemap +++ b/podstuff/module.modulemap @@ -5,7 +5,18 @@ framework module SQLite { // // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + module arm { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + link "sqlite3" + requires arm + } + + module x86 { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + link "sqlite3" + requires x86 + } export * module * { export * } From d0277040a8bc3ece266f3651a73445080a6a5022 Mon Sep 17 00:00:00 2001 From: Stefan Mayer-Popp Date: Wed, 23 Mar 2016 12:10:00 +0100 Subject: [PATCH 0359/1046] Fixes missing arm64 compatibility I've just missed arm64 compatibility. Works now fine with Xcode 7.2.1 --- podstuff/module.modulemap | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/podstuff/module.modulemap b/podstuff/module.modulemap index 443b93e9..50c31a61 100644 --- a/podstuff/module.modulemap +++ b/podstuff/module.modulemap @@ -8,15 +8,20 @@ framework module SQLite { // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" module arm { header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - link "sqlite3" requires arm } + module arm64 { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + requires arm64 + } + module x86 { header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - link "sqlite3" requires x86 } + + link "sqlite3" export * module * { export * } From 8d2903bd70029379184e77e2b6916cf2d884b6de Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 23 Mar 2016 10:59:24 -0400 Subject: [PATCH 0360/1046] CocoaPods fix: Use platform-specific module maps Xcode 7.3 got more strict about SDK headers. We need to use multiple module maps to accommodate various platforms and we need to scope `requires` to accommodate various architecturers. Signed-off-by: Stephen Celis --- .../ios.modulemap | 11 +++-------- CocoaPods/osx.modulemap | 10 ++++++++++ CocoaPods/tvos.modulemap | 18 ++++++++++++++++++ SQLite.swift.podspec | 5 ++++- SQLite.xcodeproj/project.pbxproj | 2 +- 5 files changed, 36 insertions(+), 10 deletions(-) rename podstuff/module.modulemap => CocoaPods/ios.modulemap (56%) create mode 100644 CocoaPods/osx.modulemap create mode 100644 CocoaPods/tvos.modulemap diff --git a/podstuff/module.modulemap b/CocoaPods/ios.modulemap similarity index 56% rename from podstuff/module.modulemap rename to CocoaPods/ios.modulemap index 50c31a61..502383f3 100644 --- a/podstuff/module.modulemap +++ b/CocoaPods/ios.modulemap @@ -1,26 +1,21 @@ framework module SQLite { umbrella header "SQLite.h" - // Load the SDK header alongside SQLite.swift. Alternate headers: - // - // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" - // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" module arm { header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" requires arm } - + module arm64 { header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" requires arm64 } - + module x86 { header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" requires x86 } - + link "sqlite3" export * diff --git a/CocoaPods/osx.modulemap b/CocoaPods/osx.modulemap new file mode 100644 index 00000000..80628c7b --- /dev/null +++ b/CocoaPods/osx.modulemap @@ -0,0 +1,10 @@ +framework module SQLite { + umbrella header "SQLite.h" + + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" + + link "sqlite3" + + export * + module * { export * } +} diff --git a/CocoaPods/tvos.modulemap b/CocoaPods/tvos.modulemap new file mode 100644 index 00000000..480ff6f3 --- /dev/null +++ b/CocoaPods/tvos.modulemap @@ -0,0 +1,18 @@ +framework module SQLite { + umbrella header "SQLite.h" + + module arm64 { + header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/include/sqlite3.h" + requires arm64 + } + + module x86 { + header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk/usr/include/sqlite3.h" + requires x86 + } + + link "sqlite3" + + export * + module * { export * } +} diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b8160396..a4a79ba4 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -24,7 +24,10 @@ Pod::Spec.new do |s| s.tvos.deployment_target = "9.0" s.osx.deployment_target = "10.9" - s.module_map = "podstuff/module.modulemap" + s.ios.module_map = "CocoaPods/ios.modulemap" + s.tvos.module_map = "CocoaPods/tvos.modulemap" + s.osx.module_map = "CocoaPods/osx.modulemap" + s.libraries = 'sqlite3' s.source_files = 'SQLite/**/*.{c,h,m,swift}' s.private_header_files = 'SQLite/Core/fts3_tokenizer.h' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d73ebb51..4960525a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -153,7 +153,7 @@ /* Begin PBXFileReference section */ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.1.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; From 82a5f8ce62013c73775a5f0aaa78c792b60efbd7 Mon Sep 17 00:00:00 2001 From: Andrew Crookston Date: Wed, 23 Mar 2016 16:36:59 -0700 Subject: [PATCH 0361/1046] Adds watchOS support --- CocoaPods/watchos.modulemap | 18 ++++++ SQLite.swift.podspec | 2 + SQLite.xcodeproj/project.pbxproj | 104 +++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 CocoaPods/watchos.modulemap diff --git a/CocoaPods/watchos.modulemap b/CocoaPods/watchos.modulemap new file mode 100644 index 00000000..59660448 --- /dev/null +++ b/CocoaPods/watchos.modulemap @@ -0,0 +1,18 @@ +framework module SQLite { + umbrella header "SQLite.h" + + module arm64 { + header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" + requires arm64 + } + + module x86 { + header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/usr/include/sqlite3.h" + requires x86 + } + + link "sqlite3" + + export * + module * { export * } +} diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index a4a79ba4..a7dc8f6e 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -23,10 +23,12 @@ Pod::Spec.new do |s| s.ios.deployment_target = "8.0" s.tvos.deployment_target = "9.0" s.osx.deployment_target = "10.9" + s.watchos.deployment_target = "2.0" s.ios.module_map = "CocoaPods/ios.modulemap" s.tvos.module_map = "CocoaPods/tvos.modulemap" s.osx.module_map = "CocoaPods/osx.modulemap" + s.watchos.module_map = "CocoaPods/watchos.modulemap" s.libraries = 'sqlite3' s.source_files = 'SQLite/**/*.{c,h,m,swift}' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 4960525a..bc23874a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -154,6 +154,7 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -226,6 +227,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A121AC411CA35C79005A31D1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACF1C3F04ED00AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -280,6 +288,7 @@ EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */, 03A65E5A1C6BB0F50062603F /* SQLite.framework */, 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */, + A121AC451CA35C79005A31D1 /* SQLite.framework */, ); name = Products; sourceTree = ""; @@ -409,6 +418,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A121AC421CA35C79005A31D1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD01C3F04ED00AE3E12 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -470,6 +486,24 @@ productReference = 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + A121AC441CA35C79005A31D1 /* SQLite watchOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = A121AC4C1CA35C79005A31D1 /* Build configuration list for PBXNativeTarget "SQLite watchOS" */; + buildPhases = ( + A121AC401CA35C79005A31D1 /* Sources */, + A121AC411CA35C79005A31D1 /* Frameworks */, + A121AC421CA35C79005A31D1 /* Headers */, + A121AC431CA35C79005A31D1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "SQLite watchOS"; + productName = "SQLite watchOS"; + productReference = A121AC451CA35C79005A31D1 /* SQLite.framework */; + productType = "com.apple.product-type.framework"; + }; EE247AD21C3F04ED00AE3E12 /* SQLite iOS */ = { isa = PBXNativeTarget; buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; @@ -557,6 +591,9 @@ 03A65E621C6BB0F60062603F = { CreatedOnToolsVersion = 7.2; }; + A121AC441CA35C79005A31D1 = { + CreatedOnToolsVersion = 7.3; + }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; }; @@ -589,6 +626,7 @@ EE247B441C3F3ED000AE3E12 /* SQLiteTests Mac */, 03A65E591C6BB0F50062603F /* SQLite tvOS */, 03A65E621C6BB0F60062603F /* SQLiteTests tvOS */, + A121AC441CA35C79005A31D1 /* SQLite watchOS */, ); }; /* End PBXProject section */ @@ -608,6 +646,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A121AC431CA35C79005A31D1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD11C3F04ED00AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -686,6 +731,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A121AC401CA35C79005A31D1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACE1C3F04ED00AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -861,6 +913,48 @@ }; name = Release; }; + A121AC4A1CA35C79005A31D1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SQLite/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 2.2; + }; + name = Debug; + }; + A121AC4B1CA35C79005A31D1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = SQLite/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 2.2; + }; + name = Release; + }; EE247AE51C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1099,6 +1193,7 @@ 03A65E6C1C6BB0F60062603F /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 03A65E701C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLiteTests tvOS" */ = { isa = XCConfigurationList; @@ -1107,6 +1202,15 @@ 03A65E6E1C6BB0F60062603F /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A121AC4C1CA35C79005A31D1 /* Build configuration list for PBXNativeTarget "SQLite watchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A121AC4A1CA35C79005A31D1 /* Debug */, + A121AC4B1CA35C79005A31D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; }; EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */ = { isa = XCConfigurationList; From 4243bc42d4aec145607d1b9af275883382681ab9 Mon Sep 17 00:00:00 2001 From: Andrew Crookston Date: Thu, 24 Mar 2016 16:12:38 -0700 Subject: [PATCH 0362/1046] Use armv7k architecture, not arm64, for the WatchOS SDK --- CocoaPods/watchos.modulemap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CocoaPods/watchos.modulemap b/CocoaPods/watchos.modulemap index 59660448..7de53767 100644 --- a/CocoaPods/watchos.modulemap +++ b/CocoaPods/watchos.modulemap @@ -1,7 +1,7 @@ framework module SQLite { umbrella header "SQLite.h" - module arm64 { + module armv7k { header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" requires arm64 } From 5d09ab210525f6f21b37eaf7e5263bb3f9779ddc Mon Sep 17 00:00:00 2001 From: Andrew Crookston Date: Thu, 24 Mar 2016 16:21:47 -0700 Subject: [PATCH 0363/1046] Changes a missed arm64 reference to armv7k from previous commit --- CocoaPods/watchos.modulemap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CocoaPods/watchos.modulemap b/CocoaPods/watchos.modulemap index 7de53767..ff9080ab 100644 --- a/CocoaPods/watchos.modulemap +++ b/CocoaPods/watchos.modulemap @@ -3,7 +3,7 @@ framework module SQLite { module armv7k { header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" - requires arm64 + requires armv7k } module x86 { From fb15120895170a2d336d8bc09f68e506f8471777 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 23 Mar 2016 16:48:59 -0400 Subject: [PATCH 0364/1046] Update CocoaPods requirements Signed-off-by: Stephen Celis --- Documentation/Index.md | 8 +++++++- README.md | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 5906ccff..9fbd4640 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -93,7 +93,13 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 0.37 or greater). + 1. Make sure the latest CocoaPods beta is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0.beta.6 or greater.) + + ``` sh + # Using the default Ruby install will require you to use sudo when + # installing and updating gems. + sudo gem install --pre cocoapods + ``` 2. Update your Podfile to include the following: diff --git a/README.md b/README.md index 02291cbe..d07aa06a 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,14 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift - requires version 0.37 or greater.) + 1. Make sure the latest CocoaPods beta is [installed][CocoaPods + Installation]. (SQLite.swift requires version 1.0.0.beta.6 or greater.) + + ``` sh + # Using the default Ruby install will require you to use sudo when + # installing and updating gems. + sudo gem install --pre cocoapods + ``` 2. Update your Podfile to include the following: From 29c2fafc91a722ba8dba77816726e60cf38b916b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 25 Mar 2016 14:48:30 -0400 Subject: [PATCH 0365/1046] Bump to 0.10.0 Signed-off-by: Stephen Celis --- Documentation/Index.md | 4 ++-- README.md | 4 ++-- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 1 + SQLite/Info.plist | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9fbd4640..fe1a4084 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.9.2 + github "stephencelis/SQLite.swift" ~> 0.10.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -106,7 +106,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.9.2' + pod 'SQLite.swift', '~> 0.10.0' ``` 3. Run `pod install`. diff --git a/README.md b/README.md index d07aa06a..89d9aef4 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.9.2 + github "stephencelis/SQLite.swift" ~> 0.10.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -157,7 +157,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.9.2' + pod 'SQLite.swift', '~> 0.10.0' ``` 3. Run `pod install`. diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index a7dc8f6e..ad211a3a 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.9.2" + s.version = "0.10.0" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index bc23874a..55385ce3 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1211,6 +1211,7 @@ A121AC4B1CA35C79005A31D1 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */ = { isa = XCConfigurationList; diff --git a/SQLite/Info.plist b/SQLite/Info.plist index 230b60f6..378e663f 100644 --- a/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.2 + 0.10.0 CFBundleSignature ???? CFBundleVersion From 5652283be519024b32578f58f869ee8f0103f2db Mon Sep 17 00:00:00 2001 From: Caesar Wirth Date: Sat, 26 Mar 2016 12:38:06 +0900 Subject: [PATCH 0366/1046] [CocoaPods] Add SQLite module map for each architecture --- CocoaPods/appletvos/module.modulemap | 4 + CocoaPods/appletvsimulator/module.modulemap | 4 + CocoaPods/ios.modulemap | 23 ----- CocoaPods/iphoneos/module.modulemap | 4 + CocoaPods/iphonesimulator/module.modulemap | 4 + .../module.modulemap} | 8 +- CocoaPods/tvos.modulemap | 18 ---- CocoaPods/watchos.modulemap | 18 ---- CocoaPods/watchos/module.modulemap | 4 + CocoaPods/watchsimulator/module.modulemap | 4 + SQLite.swift.podspec | 14 ++- SQLite.xcodeproj/project.pbxproj | 92 ++++++++++++++++++- SQLite/Core/Connection.swift | 1 + SQLite/Core/Statement.swift | 2 + SQLite/Helpers.swift | 2 + 15 files changed, 129 insertions(+), 73 deletions(-) create mode 100644 CocoaPods/appletvos/module.modulemap create mode 100644 CocoaPods/appletvsimulator/module.modulemap delete mode 100644 CocoaPods/ios.modulemap create mode 100644 CocoaPods/iphoneos/module.modulemap create mode 100644 CocoaPods/iphonesimulator/module.modulemap rename CocoaPods/{osx.modulemap => macosx/module.modulemap} (58%) delete mode 100644 CocoaPods/tvos.modulemap delete mode 100644 CocoaPods/watchos.modulemap create mode 100644 CocoaPods/watchos/module.modulemap create mode 100644 CocoaPods/watchsimulator/module.modulemap diff --git a/CocoaPods/appletvos/module.modulemap b/CocoaPods/appletvos/module.modulemap new file mode 100644 index 00000000..637d9935 --- /dev/null +++ b/CocoaPods/appletvos/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/appletvsimulator/module.modulemap b/CocoaPods/appletvsimulator/module.modulemap new file mode 100644 index 00000000..f8b9b671 --- /dev/null +++ b/CocoaPods/appletvsimulator/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/ios.modulemap b/CocoaPods/ios.modulemap deleted file mode 100644 index 502383f3..00000000 --- a/CocoaPods/ios.modulemap +++ /dev/null @@ -1,23 +0,0 @@ -framework module SQLite { - umbrella header "SQLite.h" - - module arm { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - requires arm - } - - module arm64 { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - requires arm64 - } - - module x86 { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - requires x86 - } - - link "sqlite3" - - export * - module * { export * } -} diff --git a/CocoaPods/iphoneos/module.modulemap b/CocoaPods/iphoneos/module.modulemap new file mode 100644 index 00000000..043db6c4 --- /dev/null +++ b/CocoaPods/iphoneos/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/iphonesimulator/module.modulemap b/CocoaPods/iphonesimulator/module.modulemap new file mode 100644 index 00000000..a7b14cbb --- /dev/null +++ b/CocoaPods/iphonesimulator/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/osx.modulemap b/CocoaPods/macosx/module.modulemap similarity index 58% rename from CocoaPods/osx.modulemap rename to CocoaPods/macosx/module.modulemap index 80628c7b..9e091297 100644 --- a/CocoaPods/osx.modulemap +++ b/CocoaPods/macosx/module.modulemap @@ -1,10 +1,4 @@ -framework module SQLite { - umbrella header "SQLite.h" - +module CSQLite [system] { header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" - - link "sqlite3" - export * - module * { export * } } diff --git a/CocoaPods/tvos.modulemap b/CocoaPods/tvos.modulemap deleted file mode 100644 index 480ff6f3..00000000 --- a/CocoaPods/tvos.modulemap +++ /dev/null @@ -1,18 +0,0 @@ -framework module SQLite { - umbrella header "SQLite.h" - - module arm64 { - header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/include/sqlite3.h" - requires arm64 - } - - module x86 { - header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk/usr/include/sqlite3.h" - requires x86 - } - - link "sqlite3" - - export * - module * { export * } -} diff --git a/CocoaPods/watchos.modulemap b/CocoaPods/watchos.modulemap deleted file mode 100644 index ff9080ab..00000000 --- a/CocoaPods/watchos.modulemap +++ /dev/null @@ -1,18 +0,0 @@ -framework module SQLite { - umbrella header "SQLite.h" - - module armv7k { - header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" - requires armv7k - } - - module x86 { - header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/usr/include/sqlite3.h" - requires x86 - } - - link "sqlite3" - - export * - module * { export * } -} diff --git a/CocoaPods/watchos/module.modulemap b/CocoaPods/watchos/module.modulemap new file mode 100644 index 00000000..62a6c4ee --- /dev/null +++ b/CocoaPods/watchos/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/watchsimulator/module.modulemap b/CocoaPods/watchsimulator/module.modulemap new file mode 100644 index 00000000..086fbab2 --- /dev/null +++ b/CocoaPods/watchsimulator/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/usr/include/sqlite3.h" + export * +} diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index ad211a3a..ac2b3d24 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -25,10 +25,16 @@ Pod::Spec.new do |s| s.osx.deployment_target = "10.9" s.watchos.deployment_target = "2.0" - s.ios.module_map = "CocoaPods/ios.modulemap" - s.tvos.module_map = "CocoaPods/tvos.modulemap" - s.osx.module_map = "CocoaPods/osx.modulemap" - s.watchos.module_map = "CocoaPods/watchos.modulemap" + s.preserve_paths = 'CocoaPods/**/*' + s.pod_target_xcconfig = { + 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', + 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', + 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', + 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', + 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', + 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', + 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' + } s.libraries = 'sqlite3' s.source_files = 'SQLite/**/*.{c,h,m,swift}' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 55385ce3..d2ac9df4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -154,6 +154,13 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 39548A691CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 39548A6B1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -203,7 +210,7 @@ EE247B8F1C3F822500AE3E12 /* Index.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Index.md; sourceTree = ""; }; EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; - EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = SQLite.swift.podspec; sourceTree = ""; }; + EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; @@ -269,6 +276,76 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 39548A611CA63C740003E3B5 /* CocoaPods */ = { + isa = PBXGroup; + children = ( + 39548A621CA63C740003E3B5 /* appletvos */, + 39548A641CA63C740003E3B5 /* appletvsimulator */, + 39548A661CA63C740003E3B5 /* iphoneos */, + 39548A681CA63C740003E3B5 /* iphonesimulator */, + 39548A6A1CA63C740003E3B5 /* macosx */, + 39548A6C1CA63C740003E3B5 /* watchos */, + 39548A6E1CA63C740003E3B5 /* watchsimulator */, + ); + path = CocoaPods; + sourceTree = ""; + }; + 39548A621CA63C740003E3B5 /* appletvos */ = { + isa = PBXGroup; + children = ( + 39548A631CA63C740003E3B5 /* module.modulemap */, + ); + path = appletvos; + sourceTree = ""; + }; + 39548A641CA63C740003E3B5 /* appletvsimulator */ = { + isa = PBXGroup; + children = ( + 39548A651CA63C740003E3B5 /* module.modulemap */, + ); + path = appletvsimulator; + sourceTree = ""; + }; + 39548A661CA63C740003E3B5 /* iphoneos */ = { + isa = PBXGroup; + children = ( + 39548A671CA63C740003E3B5 /* module.modulemap */, + ); + path = iphoneos; + sourceTree = ""; + }; + 39548A681CA63C740003E3B5 /* iphonesimulator */ = { + isa = PBXGroup; + children = ( + 39548A691CA63C740003E3B5 /* module.modulemap */, + ); + path = iphonesimulator; + sourceTree = ""; + }; + 39548A6A1CA63C740003E3B5 /* macosx */ = { + isa = PBXGroup; + children = ( + 39548A6B1CA63C740003E3B5 /* module.modulemap */, + ); + path = macosx; + sourceTree = ""; + }; + 39548A6C1CA63C740003E3B5 /* watchos */ = { + isa = PBXGroup; + children = ( + 39548A6D1CA63C740003E3B5 /* module.modulemap */, + ); + path = watchos; + sourceTree = ""; + }; + 39548A6E1CA63C740003E3B5 /* watchsimulator */ = { + isa = PBXGroup; + children = ( + 39548A6F1CA63C740003E3B5 /* module.modulemap */, + ); + path = watchsimulator; + sourceTree = ""; + }; EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( @@ -373,6 +450,7 @@ EE247B8A1C3F81D000AE3E12 /* Metadata */ = { isa = PBXGroup; children = ( + 39548A611CA63C740003E3B5 /* CocoaPods */, EE247B771C3F40D700AE3E12 /* README.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, @@ -867,6 +945,8 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; + "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; + "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -885,6 +965,8 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; + "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; + "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1064,7 +1146,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_INCLUDE_PATHS = ""; + "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/CocoaPods/iphoneos"; + "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -1085,7 +1168,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_INCLUDE_PATHS = ""; + "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/CocoaPods/iphoneos"; + "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; }; name = Release; }; @@ -1129,6 +1213,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; + "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; }; name = Debug; }; @@ -1152,6 +1237,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; + "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; }; name = Release; }; diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 0243317f..7d67b051 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -23,6 +23,7 @@ // import Dispatch +import CSQLite /// A connection to SQLite. public final class Connection { diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 9fb87c49..39fb000d 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import CSQLite + /// A single SQL statement. public final class Statement { diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index c4d3df65..33fc6a62 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import CSQLite + public typealias Star = (Expression?, Expression?) -> Expression public func *(_: Expression?, _: Expression?) -> Expression { From b43a31f9020c42790d763a2cbf4af0b57aed5ab9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sun, 27 Mar 2016 14:27:09 -0400 Subject: [PATCH 0367/1046] Bump to 0.10.1 Includes installation fix still affecting some CocoaPods users. Signed-off-by: Stephen Celis --- Documentation/Index.md | 4 ++-- README.md | 4 ++-- SQLite.swift.podspec | 2 +- SQLite/Info.plist | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index fe1a4084..52d04c69 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.10.0 + github "stephencelis/SQLite.swift" ~> 0.10.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -106,7 +106,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.10.0' + pod 'SQLite.swift', '~> 0.10.1' ``` 3. Run `pod install`. diff --git a/README.md b/README.md index 89d9aef4..56d4374d 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.10.0 + github "stephencelis/SQLite.swift" ~> 0.10.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -157,7 +157,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.10.0' + pod 'SQLite.swift', '~> 0.10.1' ``` 3. Run `pod install`. diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index ac2b3d24..fe814100 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.10.0" + s.version = "0.10.1" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/SQLite/Info.plist b/SQLite/Info.plist index 378e663f..d93473a7 100644 --- a/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.10.0 + 0.10.1 CFBundleSignature ???? CFBundleVersion From 993295ad4ad4c23d2093445170d67f95e81b13dc Mon Sep 17 00:00:00 2001 From: James Lawton Date: Sun, 3 Apr 2016 16:55:51 -0700 Subject: [PATCH 0368/1046] Don't use qualified table name when creating an index --- SQLite/Typed/Query.swift | 7 +++++++ SQLite/Typed/Schema.swift | 2 +- SQLiteTests/SchemaTests.swift | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index f5803770..e45034ca 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -744,6 +744,13 @@ extension QueryType { ]) } + func tableName(qualified qualified: Bool) -> Expressible { + if qualified { + return tableName() + } + return Expression(clauses.from.alias ?? clauses.from.name) + } + func database(namespace name: String) -> Expressible { let name = Expression(name) diff --git a/SQLite/Typed/Schema.swift b/SQLite/Typed/Schema.swift index f1f0287d..bc204bdf 100644 --- a/SQLite/Typed/Schema.swift +++ b/SQLite/Typed/Schema.swift @@ -134,7 +134,7 @@ extension Table { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .Unique : nil, ifNotExists), Expression(literal: "ON"), - tableName(), + tableName(qualified: false), "".wrap(columns) as Expression ] diff --git a/SQLiteTests/SchemaTests.swift b/SQLiteTests/SchemaTests.swift index b3cb9a63..0c6e2135 100644 --- a/SQLiteTests/SchemaTests.swift +++ b/SQLiteTests/SchemaTests.swift @@ -724,6 +724,10 @@ class SchemaTests : XCTestCase { "CREATE UNIQUE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", table.createIndex([int64], unique: true, ifNotExists: true) ) + XCTAssertEqual( + "CREATE UNIQUE INDEX IF NOT EXISTS \"main\".\"index_table_on_int64\" ON \"table\" (\"int64\")", + Table("table", database: "main").createIndex([int64], unique: true, ifNotExists: true) + ) } func test_dropIndex_compilesCreateIndexExpression() { From 3825894ae7e499e5b74f0cf7cd73dcb235104b59 Mon Sep 17 00:00:00 2001 From: James Lawton Date: Sun, 3 Apr 2016 17:12:42 -0700 Subject: [PATCH 0369/1046] Don't use qualified table name when creating a foreign key reference --- SQLite/Typed/Schema.swift | 2 +- SQLiteTests/SchemaTests.swift | 6 +++++- SQLiteTests/TestHelpers.swift | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/SQLite/Typed/Schema.swift b/SQLite/Typed/Schema.swift index bc204bdf..16e11c16 100644 --- a/SQLite/Typed/Schema.swift +++ b/SQLite/Typed/Schema.swift @@ -505,7 +505,7 @@ private func definition(column: Expressible, _ datatype: String, _ primaryKey: P private func reference(primary: (QueryType, Expressible)) -> Expressible { return " ".join([ Expression(literal: "REFERENCES"), - primary.0.tableName(), + primary.0.tableName(qualified: false), "".wrap(primary.1) as Expression ]) } diff --git a/SQLiteTests/SchemaTests.swift b/SQLiteTests/SchemaTests.swift index 0c6e2135..416b3646 100644 --- a/SQLiteTests/SchemaTests.swift +++ b/SQLiteTests/SchemaTests.swift @@ -300,6 +300,10 @@ class SchemaTests : XCTestCase { "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, references: qualifiedTable, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, unique: true, references: table, int64) } @@ -726,7 +730,7 @@ class SchemaTests : XCTestCase { ) XCTAssertEqual( "CREATE UNIQUE INDEX IF NOT EXISTS \"main\".\"index_table_on_int64\" ON \"table\" (\"int64\")", - Table("table", database: "main").createIndex([int64], unique: true, ifNotExists: true) + qualifiedTable.createIndex([int64], unique: true, ifNotExists: true) ) } diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index c0ff9bb9..464b9c27 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -110,5 +110,6 @@ func AssertThrows(@autoclosure expression: () throws -> T, file: StaticString } let table = Table("table") +let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") let _view = View("view") // avoid Mac XCTestCase collision From 709d137ef7255734ce4775c685352f2a4f286ee1 Mon Sep 17 00:00:00 2001 From: Will Richardson Date: Wed, 6 Apr 2016 23:24:12 +1200 Subject: [PATCH 0370/1046] Changed scalar functions to throw errors --- SQLite/Core/Connection.swift | 16 ++++++++-------- SQLite/Core/Statement.swift | 14 +++++++------- SQLite/Typed/Query.swift | 20 ++++++++++---------- SQLiteTests/ConnectionTests.swift | 30 +++++++++++++++--------------- SQLiteTests/FTS4Tests.swift | 2 +- SQLiteTests/QueryTests.swift | 8 ++++---- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 7d67b051..e982673a 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -231,8 +231,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { - return scalar(statement, bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? { + return try scalar(statement, bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -245,8 +245,8 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { - return try! prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? { + return try prepare(statement).scalar(bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -259,8 +259,8 @@ public final class Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { - return try! prepare(statement).scalar(bindings) + @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? { + return try prepare(statement).scalar(bindings) } // MARK: - Transactions @@ -551,11 +551,11 @@ public final class Connection { /// /// - block: A collation function that takes two strings and returns the /// comparison result. - public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) { + public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) throws { let box: Collation = { lhs, rhs in Int32(block(lhs: String.fromCString(UnsafePointer(lhs))!, rhs: String.fromCString(UnsafePointer(rhs))!).rawValue) } - try! check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in + try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in unsafeBitCast(callback, Collation.self)(lhs, rhs) }, nil)) collations[collation] = box diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 39fb000d..62fc95eb 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -148,21 +148,21 @@ public final class Statement { /// - Parameter bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: Binding?...) -> Binding? { + @warn_unused_result public func scalar(bindings: Binding?...) throws -> Binding? { guard bindings.isEmpty else { - return scalar(bindings) + return try scalar(bindings) } reset(clearBindings: false) - try! step() + try step() return row[0] } /// - Parameter bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: [Binding?]) -> Binding? { - return bind(bindings).scalar() + @warn_unused_result public func scalar(bindings: [Binding?]) throws -> Binding? { + return try bind(bindings).scalar() } @@ -170,8 +170,8 @@ public final class Statement { /// statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: [String: Binding?]) -> Binding? { - return bind(bindings).scalar() + @warn_unused_result public func scalar(bindings: [String: Binding?]) throws -> Binding? { + return try bind(bindings).scalar() } public func step() throws -> Bool { diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index e45034ca..36b3b2e1 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -932,30 +932,30 @@ extension Connection { } } - public func scalar(query: ScalarQuery) -> V { + public func scalar(query: ScalarQuery) throws -> V { let expression = query.expression - return value(scalar(expression.template, expression.bindings)) + return value(try scalar(expression.template, expression.bindings)) } - public func scalar(query: ScalarQuery) -> V.ValueType? { + public func scalar(query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression - guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func scalar(query: Select) -> V { + public func scalar(query: Select) throws -> V { let expression = query.expression - return value(scalar(expression.template, expression.bindings)) + return value(try scalar(expression.template, expression.bindings)) } - public func scalar(query: Select) -> V.ValueType? { + public func scalar(query: Select) throws -> V.ValueType? { let expression = query.expression - guard let value = scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } + guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func pluck(query: QueryType) -> Row? { - return try! prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + public func pluck(query: QueryType) throws -> Row? { + return try prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() } /// Runs an `Insert` query. diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index aeec9b72..3d7dd3eb 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -87,10 +87,10 @@ class ConnectionTests : SQLiteTestCase { } func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } @@ -238,7 +238,7 @@ class ConnectionTests : SQLiteTestCase { try! db.transaction { try self.InsertUser("alice") } - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -252,7 +252,7 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } @@ -268,36 +268,36 @@ class ConnectionTests : SQLiteTestCase { } } catch { } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) } } func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as? String) - XCTAssert(db.scalar("SELECT hello(NULL)") == nil) + XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String) + XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil) } func test_createFunction_createsQuotableFunction() { db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as? String) - XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) + XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil) } func test_createCollation_createsCollation() { - db.createCollation("NODIACRITIC") { lhs, rhs in + try! db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { - db.createCollation("NO DIACRITIC") { lhs, rhs in + try! db.createCollation("NO DIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } func test_interrupt_interruptsLongRunningQuery() { diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index a94d9073..c36c5739 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -71,7 +71,7 @@ class FTS4IntegrationTests : SQLiteTestCase { AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") try! db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) + XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) } } diff --git a/SQLiteTests/QueryTests.swift b/SQLiteTests/QueryTests.swift index b76da61e..f584bfcf 100644 --- a/SQLiteTests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -306,16 +306,16 @@ class QueryIntegrationTests : SQLiteTestCase { } func test_scalar() { - XCTAssertEqual(0, db.scalar(users.count)) - XCTAssertEqual(false, db.scalar(users.exists)) + XCTAssertEqual(0, try! db.scalar(users.count)) + XCTAssertEqual(false, try! db.scalar(users.exists)) try! InsertUsers("alice") - XCTAssertEqual(1, db.scalar(users.select(id.average))) + XCTAssertEqual(1, try! db.scalar(users.select(id.average))) } func test_pluck() { let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, db.pluck(users)![id]) + XCTAssertEqual(rowid, try! db.pluck(users)![id]) } func test_insert() { From 68815609ab1173ec36e4978b2a060ff7c0a8a013 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 29 Nov 2015 01:54:06 -0700 Subject: [PATCH 0371/1046] Add connection pool for concurrent access Uses WAL mode to support multiple reads and a single writer. --- SQLite.xcodeproj/project.pbxproj | 28 +++ SQLite/Core/Connection.swift | 256 +++++++++++++++++++++++--- SQLite/Core/ConnectionPool.swift | 144 +++++++++++++++ SQLite/Core/Dispatcher.swift | 55 ++++++ SQLiteTests/ConnectionPoolTests.swift | 57 ++++++ 5 files changed, 511 insertions(+), 29 deletions(-) create mode 100644 SQLite/Core/ConnectionPool.swift create mode 100644 SQLite/Core/Dispatcher.swift create mode 100644 SQLiteTests/ConnectionPoolTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d2ac9df4..edc79e3f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,17 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + AA780B3D1CC201A700E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; + AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; + AA780B411CC202C800E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; + AA780B421CC202C900E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; + AA780B431CC202CA00E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; + AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; + AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; + AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; + AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; + AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; + AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -162,6 +173,9 @@ 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPool.swift; sourceTree = ""; }; + AA780B3C1CC201A700E0E95E /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = ""; }; + AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPoolTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -388,6 +402,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( + AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -411,6 +426,8 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( + AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */, + AA780B3C1CC201A700E0E95E /* Dispatcher.swift */, EE91808D1C46E5230038162A /* SQLite-Bridging.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, @@ -780,9 +797,11 @@ 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, + AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */, 03A65E841C6BB2FB0062603F /* Query.swift in Sources */, 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, + AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -792,6 +811,7 @@ buildActionMask = 2147483647; files = ( 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, + AA780B431CC202CA00E0E95E /* ConnectionPoolTests.swift in Sources */, 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */, 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, @@ -813,6 +833,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */, + AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -834,9 +856,11 @@ EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, + AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, + AA780B3D1CC201A700E0E95E /* ConnectionPool.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -846,6 +870,7 @@ buildActionMask = 2147483647; files = ( EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, + AA780B411CC202C800E0E95E /* ConnectionPoolTests.swift in Sources */, EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */, @@ -881,9 +906,11 @@ EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, + AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */, EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */, EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, + AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -893,6 +920,7 @@ buildActionMask = 2147483647; files = ( EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, + AA780B421CC202C900E0E95E /* ConnectionPoolTests.swift in Sources */, EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */, diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 7d67b051..1b7b3d59 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -25,8 +25,207 @@ import Dispatch import CSQLite + +/// The mode in which a transaction acquires a lock. +public enum TransactionMode : String { + + /// Defers locking the database till the first read/write executes. + case Deferred = "DEFERRED" + + /// Immediately acquires a reserved lock on the database. + case Immediate = "IMMEDIATE" + + /// Immediately acquires an exclusive lock on all databases. + case Exclusive = "EXCLUSIVE" + +} + + +/// Protocol to an SQLite connection +public protocol ConnectionType { + + /// Whether or not the database was opened in a read-only state. + var readonly : Bool { get } + + /// The last rowid inserted into the database via this connection. + var lastInsertRowid : Int64? { get } + + /// The last number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + var changes : Int { get } + + /// The total number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + var totalChanges : Int { get } + + // MARK: - Execute + + /// Executes a batch of SQL statements. + /// + /// - Parameter SQL: A batch of zero or more semicolon-separated SQL + /// statements. + /// + /// - Throws: `Result.Error` if query execution fails. + func execute(SQL: String) throws + + // MARK: - Prepare + + /// Prepares a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement + + // MARK: - Run + + /// Runs a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + func run(statement: String, _ bindings: Binding?...) throws -> Statement + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + func run(statement: String, _ bindings: [Binding?]) throws -> Statement + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement + + // MARK: - Scalar + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? + + // MARK: - Transactions + + // TODO: Consider not requiring a throw to roll back? + /// Runs a transaction with the given mode. + /// + /// - Note: Transactions cannot be nested. To nest transactions, see + /// `savepoint()`, instead. + /// + /// - Parameters: + /// + /// - mode: The mode in which a transaction acquires a lock. + /// + /// Default: `.Deferred` + /// + /// - block: A closure to run SQL statements within the transaction. + /// The transaction will be committed when the block returns. The block + /// must throw to roll the transaction back. + /// + /// - Throws: `Result.Error`, and rethrows. + func transaction(mode: TransactionMode, block: () throws -> Void) throws + + // TODO: Consider not requiring a throw to roll back? + // TODO: Consider removing ability to set a name? + /// Runs a transaction with the given savepoint name (if omitted, it will + /// generate a UUID). + /// + /// - SeeAlso: `transaction()`. + /// + /// - Parameters: + /// + /// - savepointName: A unique identifier for the savepoint (optional). + /// + /// - block: A closure to run SQL statements within the transaction. + /// The savepoint will be released (committed) when the block returns. + /// The block must throw to roll the savepoint back. + /// + /// - Throws: `SQLite.Result.Error`, and rethrows. + func savepoint(name: String, block: () throws -> Void) throws + +} + + /// A connection to SQLite. -public final class Connection { +public final class Connection : ConnectionType, Equatable { /// The location of a SQLite database. public enum Location { @@ -67,10 +266,27 @@ public final class Connection { /// Default: `false`. /// /// - Returns: A new database connection. - public init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) - dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) + try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection")) + } + + /// Initializes a new SQLite connection. + /// + /// - Parameters: + /// + /// - location: The location of the database. Creates a new database if it + /// doesn’t already exist (unless in read-only mode). + /// + /// - flags: SQLite open flags + /// + /// - dispatcher: Dispatcher synchronization blocks + /// + /// - Returns: A new database connection. + public init(_ location: Location, flags: Int32, dispatcher: Dispatcher) throws { + self.dispatcher = dispatcher + try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) + try check(sqlite3_extended_result_codes(handle, 1)) } /// Initializes a new connection to a database. @@ -265,20 +481,6 @@ public final class Connection { // MARK: - Transactions - /// The mode in which a transaction acquires a lock. - public enum TransactionMode : String { - - /// Defers locking the database till the first read/write executes. - case Deferred = "DEFERRED" - - /// Immediately acquires a reserved lock on the database. - case Immediate = "IMMEDIATE" - - /// Immediately acquires an exclusive lock on all databases. - case Exclusive = "EXCLUSIVE" - - } - // TODO: Consider not requiring a throw to roll back? /// Runs a transaction with the given mode. /// @@ -577,11 +779,7 @@ public final class Connection { } } - if dispatch_get_specific(Connection.queueKey) == queueContext { - box() - } else { - dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 - } + dispatcher.dispatch(box) if let failure = failure { try { () -> Void in throw failure }() @@ -597,12 +795,8 @@ public final class Connection { throw error } - - private var queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) - - private static let queueKey = unsafeBitCast(Connection.self, UnsafePointer.self) - - private lazy var queueContext: UnsafeMutablePointer = unsafeBitCast(self, UnsafeMutablePointer.self) + + private var dispatcher : Dispatcher } @@ -629,6 +823,10 @@ extension Connection.Location : CustomStringConvertible { } +public func ==(lhs: Connection, rhs: Connection) -> Bool { + return lhs === rhs +} + /// An SQL operation passed to update callbacks. public enum Operation { diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift new file mode 100644 index 00000000..5a6ee5b9 --- /dev/null +++ b/SQLite/Core/ConnectionPool.swift @@ -0,0 +1,144 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + + +/// Connection pool delegate +public protocol ConnectionPoolDelegate { + + func pool(pool: ConnectionPool, shouldAddConnection: Connection) + func pool(pool: ConnectionPool, didAddConnection: Connection) + +} + + +// Connection pool for accessing an SQLite database +// with multiple readers & a single writer. Utilizes +// WAL mode. +public final class ConnectionPool { + + private let location : Connection.Location + private var availableReadConnections = [Connection]() + private var unavailableReadConnections = [Connection]() + private let lockQueue : dispatch_queue_t + private var writeConnection : Connection! + + public init(_ location: Connection.Location) throws { + self.location = location + self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool", DISPATCH_QUEUE_SERIAL) + + try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") + } + + // Connection that automatically returns itself + // to the pool when it goes out of scope + private class BorrowedConnection : ConnectionType, Equatable { + + let pool : ConnectionPool + let connection : Connection + + init(pool: ConnectionPool, connection: Connection) { + self.pool = pool + self.connection = connection + } + + deinit { + dispatch_sync(pool.lockQueue) { + if let index = self.pool.unavailableReadConnections.indexOf(self.connection) { + self.pool.unavailableReadConnections.removeAtIndex(index) + } + self.pool.availableReadConnections.append(self.connection) + } + } + + var readonly : Bool { return connection.readonly } + var lastInsertRowid : Int64? { return connection.lastInsertRowid } + var changes : Int { return connection.changes } + var totalChanges : Int { return connection.totalChanges } + + func execute(SQL: String) throws { return try connection.execute(SQL) } + @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.prepare(statement, bindings) } + @warn_unused_result func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.prepare(statement, bindings) } + @warn_unused_result func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.prepare(statement, bindings) } + + func run(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.run(statement, bindings) } + func run(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.run(statement, bindings) } + func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.run(statement, bindings) } + + @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? { return connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return connection.scalar(statement, bindings) } + @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return connection.scalar(statement, bindings) } + + func transaction(mode: TransactionMode, block: () throws -> Void) throws { return try connection.transaction(mode, block: block) } + func savepoint(name: String, block: () throws -> Void) throws { return try connection.savepoint(name, block: block) } + + } + + + // Acquires a read/write connection to the database + public var writable : Connection { + + var writeConnectionInit = dispatch_once_t() + dispatch_once(&writeConnectionInit) { + let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL + self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) + self.writeConnection.busyTimeout = 2 + } + + return writeConnection + } + + // Acquires a read only connection to the database + public var readable : ConnectionType { + + var borrowed : BorrowedConnection! + + dispatch_sync(lockQueue) { + + let connection : Connection + + if let availableConnection = self.availableReadConnections.popLast() { + connection = availableConnection + } + else { + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL + connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + connection.busyTimeout = 2 + } + + self.unavailableReadConnections.append(connection) + + borrowed = BorrowedConnection(pool: self, connection: connection) + } + + return borrowed + } + +} + + +private func ==(lhs: ConnectionPool.BorrowedConnection, rhs: ConnectionPool.BorrowedConnection) -> Bool { + return lhs.connection == rhs.connection +} diff --git a/SQLite/Core/Dispatcher.swift b/SQLite/Core/Dispatcher.swift new file mode 100644 index 00000000..b5010488 --- /dev/null +++ b/SQLite/Core/Dispatcher.swift @@ -0,0 +1,55 @@ +// +// Dispatcher.swift +// SQLite +// +// Created by Kevin Wooten on 11/28/15. +// Copyright © 2015 stephencelis. All rights reserved. +// + +import Foundation + + +/// Block dispatch method +public protocol Dispatcher { + + /// Dispatches the provided block + func dispatch(block: dispatch_block_t) + +} + + +/// Dispatches block immediately on current thread +public final class ImmediateDispatcher : Dispatcher { + + public func dispatch(block: dispatch_block_t) { + block() + } + +} + + +/// Synchronously dispatches block on a serial +/// queue. Specifically allows reentrant calls +public final class ReentrantDispatcher : Dispatcher { + + static let queueKey = unsafeBitCast(ReentrantDispatcher.self, UnsafePointer.self) + + let queue : dispatch_queue_t + + let queueContext : UnsafeMutablePointer! + + public init(_ name: String) { + queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL) + queueContext = unsafeBitCast(queue, UnsafeMutablePointer.self) + dispatch_queue_set_specific(queue, ReentrantDispatcher.queueKey, queueContext, nil) + } + + public func dispatch(block: dispatch_block_t) { + if dispatch_get_specific(ReentrantDispatcher.queueKey) == self.queueContext { + block() + } else { + dispatch_sync(self.queue, block) // FIXME: rdar://problem/21389236 + } + } + +} diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift new file mode 100644 index 00000000..e79d3a8c --- /dev/null +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -0,0 +1,57 @@ +import XCTest +import SQLite + +class ConnectionPoolTests : SQLiteTestCase { + + override func setUp() { + super.setUp() + } + + func testConcurrentAccess() { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + + let conn = pool.writable + try! conn.execute("CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)") + try! conn.execute("DELETE FROM test") + try! conn.execute("INSERT INTO test(id,name) VALUES(0, 'test0')") + try! conn.execute("INSERT INTO test(id,name) VALUES(1, 'test1')") + try! conn.execute("INSERT INTO test(id,name) VALUES(2, 'test2')") + try! conn.execute("INSERT INTO test(id,name) VALUES(3, 'test3')") + try! conn.execute("INSERT INTO test(id,name) VALUES(4, 'test4')") + + var quit = false + let queue = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT) + for x in 0..<5 { + var reads = 0 + let ex = expectationWithDescription("thread" + String(x)) + dispatch_async(queue) { + print("started", x) + let conn = pool.readable + var curr = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + while !quit { + let now = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + if now != curr { + print(now) + curr = now + } + reads += 1 + } + print("ended at", reads, "reads") + ex.fulfill() + } + } + + for x in 10..<500 { + let name = "test" + String(x) + let idx = Int(rand()) % 5 + try! conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + usleep(15000) + } + + quit = true + waitForExpectationsWithTimeout(1000, handler: nil) + } + +} From 42ebd18a06e06319933ce866dd9fbc84faf2e03a Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 29 Nov 2015 03:06:14 -0700 Subject: [PATCH 0372/1046] Add delegate to connection pool Allows customizing new connections once created --- SQLite/Core/ConnectionPool.swift | 53 +++++++++++++++++++-------- SQLiteTests/ConnectionPoolTests.swift | 38 +++++++++++++++++-- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 5a6ee5b9..d4a0c3f5 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -28,7 +28,7 @@ import Foundation /// Connection pool delegate public protocol ConnectionPoolDelegate { - func pool(pool: ConnectionPool, shouldAddConnection: Connection) + func poolShouldAddConnection(pool: ConnectionPool) -> Bool func pool(pool: ConnectionPool, didAddConnection: Connection) } @@ -45,6 +45,8 @@ public final class ConnectionPool { private let lockQueue : dispatch_queue_t private var writeConnection : Connection! + public var delegate : ConnectionPoolDelegate? + public init(_ location: Connection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool", DISPATCH_QUEUE_SERIAL) @@ -52,6 +54,14 @@ public final class ConnectionPool { try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") } + public var totalReadableConnectionCount : Int { + return availableReadConnections.count + unavailableReadConnections.count + } + + public var availableReadableConnectionCount : Int { + return availableReadConnections.count + } + // Connection that automatically returns itself // to the pool when it goes out of scope private class BorrowedConnection : ConnectionType, Equatable { @@ -98,7 +108,7 @@ public final class ConnectionPool { // Acquires a read/write connection to the database - public var writable : Connection { + public var writable : ConnectionType { var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { @@ -115,23 +125,34 @@ public final class ConnectionPool { var borrowed : BorrowedConnection! - dispatch_sync(lockQueue) { - - let connection : Connection + repeat { - if let availableConnection = self.availableReadConnections.popLast() { - connection = availableConnection - } - else { - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL - connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) - connection.busyTimeout = 2 + dispatch_sync(lockQueue) { + + let connection : Connection + + if let availableConnection = self.availableReadConnections.popLast() { + connection = availableConnection + } + else if self.delegate?.poolShouldAddConnection(self) ?? true { + + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL + connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + connection.busyTimeout = 2 + + self.delegate?.pool(self, didAddConnection: connection) + + } + else { + return + } + + self.unavailableReadConnections.append(connection) + + borrowed = BorrowedConnection(pool: self, connection: connection) } - self.unavailableReadConnections.append(connection) - - borrowed = BorrowedConnection(pool: self, connection: connection) - } + } while borrowed == nil return borrowed } diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index e79d3a8c..12e6402f 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -9,7 +9,7 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite") + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) let conn = pool.writable @@ -25,33 +25,63 @@ class ConnectionPoolTests : SQLiteTestCase { let queue = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT) for x in 0..<5 { var reads = 0 + let ex = expectationWithDescription("thread" + String(x)) + dispatch_async(queue) { + print("started", x) + let conn = pool.readable - var curr = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + + let stmt = conn.prepare("SELECT name FROM test WHERE id = ?") + var curr = stmt.scalar(x) as! String while !quit { - let now = conn.scalar("SELECT name FROM test WHERE id = ?", x) as! String + + let now = stmt.scalar(x) as! String if now != curr { print(now) curr = now } reads += 1 } + print("ended at", reads, "reads") + ex.fulfill() } + } for x in 10..<500 { + let name = "test" + String(x) let idx = Int(rand()) % 5 - try! conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + + do { + try conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + } + catch let error { + XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") + } + usleep(15000) } quit = true waitForExpectationsWithTimeout(1000, handler: nil) } + + func testAutoRelease() { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + + do { + try! pool.readable.execute("SELECT 1") + } + + XCTAssertEqual(pool.totalReadableConnectionCount, pool.availableReadableConnectionCount) + } } From 68f46d5069e0e146055103c6501cc36369181484 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Fri, 15 Apr 2016 21:46:45 -0700 Subject: [PATCH 0373/1046] Better names for protocols and classes ConnectionType is now Connection and Connection is now DBConnection --- SQLite/Core/Connection.swift | 38 +++++++++++++------------- SQLite/Core/ConnectionPool.swift | 39 +++++++++++++++------------ SQLite/Core/Statement.swift | 4 +-- SQLite/Extensions/FTS4.swift | 2 +- SQLite/Typed/CustomFunctions.swift | 2 +- SQLite/Typed/Query.swift | 2 +- SQLiteTests/ConnectionPoolTests.swift | 8 +++--- SQLiteTests/ConnectionTests.swift | 38 +++++++++++++------------- SQLiteTests/TestHelpers.swift | 2 +- 9 files changed, 71 insertions(+), 64 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 1b7b3d59..e1b82b76 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -42,7 +42,7 @@ public enum TransactionMode : String { /// Protocol to an SQLite connection -public protocol ConnectionType { +public protocol Connection { /// Whether or not the database was opened in a read-only state. var readonly : Bool { get } @@ -201,8 +201,8 @@ public protocol ConnectionType { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - func transaction(mode: TransactionMode, block: () throws -> Void) throws - + func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws + // TODO: Consider not requiring a throw to roll back? // TODO: Consider removing ability to set a name? /// Runs a transaction with the given savepoint name (if omitted, it will @@ -219,13 +219,15 @@ public protocol ConnectionType { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - func savepoint(name: String, block: () throws -> Void) throws + func savepoint(name: String, block: (Connection) throws -> Void) throws + func sync(block: () throws -> T) rethrows -> T + } /// A connection to SQLite. -public final class Connection : ConnectionType, Equatable { +public final class DBConnection : Connection, Equatable { /// The location of a SQLite database. public enum Location { @@ -498,10 +500,10 @@ public final class Connection : ConnectionType, Equatable { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { + public func transaction(mode: TransactionMode = .Deferred, block: (Connection) throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } - + // TODO: Consider not requiring a throw to roll back? // TODO: Consider removing ability to set a name? /// Runs a transaction with the given savepoint name (if omitted, it will @@ -518,18 +520,18 @@ public final class Connection : ConnectionType, Equatable { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { + public func savepoint(name: String = NSUUID().UUIDString, block: (Connection) throws -> Void) throws { let name = name.quote("'") let savepoint = "SAVEPOINT \(name)" - + try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") } - - private func transaction(begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { + + private func transaction(begin: String, _ block: (Connection) throws -> Void, _ commit: String, or rollback: String) throws { return try sync { try self.run(begin) do { - try block() + try block(self) } catch { try self.run(rollback) throw error @@ -537,7 +539,7 @@ public final class Connection : ConnectionType, Equatable { try self.run(commit) } } - + /// Interrupts any long-running queries. public func interrupt() { sqlite3_interrupt(handle) @@ -767,7 +769,7 @@ public final class Connection : ConnectionType, Equatable { // MARK: - Error Handling - func sync(block: () throws -> T) rethrows -> T { + public func sync(block: () throws -> T) rethrows -> T { var success: T? var failure: ErrorType? @@ -800,7 +802,7 @@ public final class Connection : ConnectionType, Equatable { } -extension Connection : CustomStringConvertible { +extension DBConnection : CustomStringConvertible { public var description: String { return String.fromCString(sqlite3_db_filename(handle, nil))! @@ -808,7 +810,7 @@ extension Connection : CustomStringConvertible { } -extension Connection.Location : CustomStringConvertible { +extension DBConnection.Location : CustomStringConvertible { public var description: String { switch self { @@ -823,7 +825,7 @@ extension Connection.Location : CustomStringConvertible { } -public func ==(lhs: Connection, rhs: Connection) -> Bool { +public func ==(lhs: DBConnection, rhs: DBConnection) -> Bool { return lhs === rhs } @@ -860,7 +862,7 @@ public enum Result : ErrorType { case Error(message: String, code: Int32, statement: Statement?) - init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { + init?(errorCode: Int32, connection: DBConnection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String.fromCString(sqlite3_errmsg(connection.handle))! diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index d4a0c3f5..87c12845 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -39,18 +39,19 @@ public protocol ConnectionPoolDelegate { // WAL mode. public final class ConnectionPool { - private let location : Connection.Location - private var availableReadConnections = [Connection]() - private var unavailableReadConnections = [Connection]() + private let location : DBConnection.Location + private var availableReadConnections = [DBConnection]() + private var unavailableReadConnections = [DBConnection]() private let lockQueue : dispatch_queue_t - private var writeConnection : Connection! + private var writeConnection : DBConnection! + private let writeQueue : dispatch_queue_t public var delegate : ConnectionPoolDelegate? - public init(_ location: Connection.Location) throws { + public init(_ location: DBConnection.Location) throws { self.location = location - self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool", DISPATCH_QUEUE_SERIAL) - + self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) + self.writeQueue = dispatch_queue_create("SQLite.ConnectionPool.Write", DISPATCH_QUEUE_SERIAL) try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") } @@ -64,12 +65,12 @@ public final class ConnectionPool { // Connection that automatically returns itself // to the pool when it goes out of scope - private class BorrowedConnection : ConnectionType, Equatable { + private class BorrowedConnection : Connection, Equatable { let pool : ConnectionPool - let connection : Connection + let connection : DBConnection - init(pool: ConnectionPool, connection: Connection) { + init(pool: ConnectionPool, connection: DBConnection) { self.pool = pool self.connection = connection } @@ -101,19 +102,23 @@ public final class ConnectionPool { @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return connection.scalar(statement, bindings) } @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return connection.scalar(statement, bindings) } - func transaction(mode: TransactionMode, block: () throws -> Void) throws { return try connection.transaction(mode, block: block) } - func savepoint(name: String, block: () throws -> Void) throws { return try connection.savepoint(name, block: block) } + func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws { return try connection.transaction(mode, block: block) } + func savepoint(name: String, block: (Connection) throws -> Void) throws { return try connection.savepoint(name, block: block) } + + func sync(block: () throws -> T) rethrows -> T { return try connection.sync(block) } + func check(resultCode: Int32, statement: Statement? = nil) throws -> Int32 { return try connection.check(resultCode, statement: statement) } } // Acquires a read/write connection to the database - public var writable : ConnectionType { + public var writable : DBConnection { + var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL - self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) + self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) self.writeConnection.busyTimeout = 2 } @@ -121,7 +126,7 @@ public final class ConnectionPool { } // Acquires a read only connection to the database - public var readable : ConnectionType { + public var readable : Connection { var borrowed : BorrowedConnection! @@ -129,7 +134,7 @@ public final class ConnectionPool { dispatch_sync(lockQueue) { - let connection : Connection + let connection : DBConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection @@ -137,7 +142,7 @@ public final class ConnectionPool { else if self.delegate?.poolShouldAddConnection(self) ?? true { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL - connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) connection.busyTimeout = 2 self.delegate?.pool(self, didAddConnection: connection) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 39fb000d..07942ae0 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -29,9 +29,9 @@ public final class Statement { private var handle: COpaquePointer = nil - private let connection: Connection + private let connection: DBConnection - init(_ connection: Connection, _ SQL: String) throws { + init(_ connection: DBConnection, _ SQL: String) throws { self.connection = connection try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index e5a1d815..351bb721 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -140,7 +140,7 @@ extension Tokenizer : CustomStringConvertible { } -extension Connection { +extension DBConnection { public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index 068d0340..e32ab3c4 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public extension Connection { +public extension DBConnection { /// Creates or redefines a custom SQL function. /// diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index e45034ca..9f966250 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -1051,7 +1051,7 @@ public struct Row { } // FIXME: rdar://problem/18673897 // subscript… - + public subscript(column: Expression) -> Blob { return get(column) } diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 12e6402f..349891ec 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -10,7 +10,7 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) let conn = pool.writable try! conn.execute("CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)") @@ -53,7 +53,7 @@ class ConnectionPoolTests : SQLiteTestCase { } - for x in 10..<500 { + for x in 10..<50000 { let name = "test" + String(x) let idx = Int(rand()) % 5 @@ -75,7 +75,7 @@ class ConnectionPoolTests : SQLiteTestCase { func testAutoRelease() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLiteswiftTests.sqlite")) + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) do { try! pool.readable.execute("SELECT 1") @@ -83,5 +83,5 @@ class ConnectionPoolTests : SQLiteTestCase { XCTAssertEqual(pool.totalReadableConnectionCount, pool.availableReadableConnectionCount) } - + } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index aeec9b72..ab43c52d 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -10,27 +10,27 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! Connection(.InMemory) + let db = try! DBConnection(.InMemory) XCTAssertEqual("", db.description) } func test_init_returnsInMemoryByDefault() { - let db = try! Connection() + let db = try! DBConnection() XCTAssertEqual("", db.description) } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! Connection(.Temporary) + let db = try! DBConnection(.Temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! Connection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + let db = try! DBConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } func test_init_withString_returnsURIConnection() { - let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + let db = try! DBConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } @@ -39,7 +39,7 @@ class ConnectionTests : SQLiteTestCase { } func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! Connection(readonly: true) + let db = try! DBConnection(readonly: true) XCTAssertTrue(db.readonly) } @@ -95,19 +95,19 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_executesBeginDeferred() { - try! db.transaction(.Deferred) {} + try! db.transaction(.Deferred) {_ in } AssertSQL("BEGIN DEFERRED TRANSACTION") } func test_transaction_executesBeginImmediate() { - try! db.transaction(.Immediate) {} + try! db.transaction(.Immediate) {_ in } AssertSQL("BEGIN IMMEDIATE TRANSACTION") } func test_transaction_executesBeginExclusive() { - try! db.transaction(.Exclusive) {} + try! db.transaction(.Exclusive) {_ in } AssertSQL("BEGIN EXCLUSIVE TRANSACTION") } @@ -115,7 +115,7 @@ class ConnectionTests : SQLiteTestCase { func test_transaction_beginsAndCommitsTransactions() { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") - try! db.transaction { + try! db.transaction {_ in try stmt.run() } @@ -129,7 +129,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { - try db.transaction { + try db.transaction {_ in try stmt.run() try stmt.run() } @@ -145,8 +145,8 @@ class ConnectionTests : SQLiteTestCase { func test_savepoint_beginsAndCommitsSavepoints() { let db = self.db - try! db.savepoint("1") { - try db.savepoint("2") { + try! db.savepoint("1") {_ in + try db.savepoint("2") {_ in try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") } } @@ -165,13 +165,13 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { - try db.savepoint("1") { - try db.savepoint("2") { + try db.savepoint("1") {_ in + try db.savepoint("2") {_ in try stmt.run() try stmt.run() try stmt.run() } - try db.savepoint("2") { + try db.savepoint("2") {_ in try stmt.run() try stmt.run() try stmt.run() @@ -235,7 +235,7 @@ class ConnectionTests : SQLiteTestCase { db.commitHook { done() } - try! db.transaction { + try! db.transaction {_ in try self.InsertUser("alice") } XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) @@ -246,7 +246,7 @@ class ConnectionTests : SQLiteTestCase { async { done in db.rollbackHook(done) do { - try db.transaction { + try db.transaction {_ in try self.InsertUser("alice") try self.InsertUser("alice") // throw } @@ -263,7 +263,7 @@ class ConnectionTests : SQLiteTestCase { } db.rollbackHook(done) do { - try db.transaction { + try db.transaction {_ in try self.InsertUser("alice") } } catch { diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 464b9c27..0854b410 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -5,7 +5,7 @@ class SQLiteTestCase : XCTestCase { var trace = [String: Int]() - let db = try! Connection() + let db = try! DBConnection() let users = Table("users") From 66b82b0e0ac957518ce8afe115538c41a080f47f Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Fri, 15 Apr 2016 23:25:26 -0700 Subject: [PATCH 0374/1046] Use vfs for exclusivity vfs seems to be a more reliable handling of exclusive mode. --- SQLite/Core/Connection.swift | 13 +++++++++---- SQLite/Core/ConnectionPool.swift | 22 ++++++++++++++-------- SQLiteTests/ConnectionPoolTests.swift | 4 ++-- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index e1b82b76..a2574af2 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -268,9 +268,9 @@ public final class DBConnection : Connection, Equatable { /// Default: `false`. /// /// - Returns: A new database connection. - public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public convenience init(_ location: Location = .InMemory, readonly: Bool = false, vfsName: String? = nil) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection")) + try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection"), vfsName: vfsName) } /// Initializes a new SQLite connection. @@ -285,9 +285,14 @@ public final class DBConnection : Connection, Equatable { /// - dispatcher: Dispatcher synchronization blocks /// /// - Returns: A new database connection. - public init(_ location: Location, flags: Int32, dispatcher: Dispatcher) throws { + public init(_ location: Location, flags: Int32, dispatcher: Dispatcher, vfsName: String? = nil) throws { self.dispatcher = dispatcher - try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) + if let vfsName = vfsName { + try check(sqlite3_open_v2(location.description, &_handle, flags, vfsName)) + } + else { + try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) + } try check(sqlite3_extended_result_codes(handle, 1)) } diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 87c12845..c231f4ce 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -25,6 +25,9 @@ import Foundation +private let vfsName = "unix-excl" + + /// Connection pool delegate public protocol ConnectionPoolDelegate { @@ -44,15 +47,13 @@ public final class ConnectionPool { private var unavailableReadConnections = [DBConnection]() private let lockQueue : dispatch_queue_t private var writeConnection : DBConnection! - private let writeQueue : dispatch_queue_t public var delegate : ConnectionPoolDelegate? public init(_ location: DBConnection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) - self.writeQueue = dispatch_queue_create("SQLite.ConnectionPool.Write", DISPATCH_QUEUE_SERIAL) - try writable.execute("PRAGMA locking_mode = EXCLUSIVE; PRAGMA journal_mode = WAL;") + try writable.execute("PRAGMA journal_mode = WAL;") } public var totalReadableConnectionCount : Int { @@ -114,12 +115,16 @@ public final class ConnectionPool { // Acquires a read/write connection to the database public var writable : DBConnection { - var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { - let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL - self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.WriteConnection")) + + let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX + self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 + + if let delegate = self.delegate { + delegate.pool(self, didAddConnection: self.writeConnection) + } } return writeConnection @@ -141,8 +146,9 @@ public final class ConnectionPool { } else if self.delegate?.poolShouldAddConnection(self) ?? true { - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL - connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher()) + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX + + connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 self.delegate?.pool(self, didAddConnection: connection) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 349891ec..47ee1f31 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -34,7 +34,7 @@ class ConnectionPoolTests : SQLiteTestCase { let conn = pool.readable - let stmt = conn.prepare("SELECT name FROM test WHERE id = ?") + let stmt = try! conn.prepare("SELECT name FROM test WHERE id = ?") var curr = stmt.scalar(x) as! String while !quit { @@ -65,7 +65,7 @@ class ConnectionPoolTests : SQLiteTestCase { XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") } - usleep(15000) + usleep(1500) } quit = true From dd2bd5c67d9908a994305ee5d140567ef7a0488e Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sat, 16 Apr 2016 00:01:06 -0700 Subject: [PATCH 0375/1046] Set project tab & indent settings --- SQLite.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d2ac9df4..786c2c77 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -354,7 +354,9 @@ EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; }; EE247AD41C3F04ED00AE3E12 /* Products */ = { isa = PBXGroup; From ccc20e574a6bab6be91f452f0ecf141b18011deb Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sat, 16 Apr 2016 01:15:49 -0700 Subject: [PATCH 0376/1046] Added alternate concurrency test --- SQLiteTests/ConnectionPoolTests.swift | 52 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 47ee1f31..ce460fe5 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -7,13 +7,13 @@ class ConnectionPoolTests : SQLiteTestCase { super.setUp() } - func testConcurrentAccess() { + func testConcurrentAccess2() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) let conn = pool.writable - try! conn.execute("CREATE TABLE IF NOT EXISTS test(id INTEGER PRIMARY KEY, name TEXT)") + try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") try! conn.execute("DELETE FROM test") try! conn.execute("INSERT INTO test(id,name) VALUES(0, 'test0')") try! conn.execute("INSERT INTO test(id,name) VALUES(1, 'test1')") @@ -40,7 +40,7 @@ class ConnectionPoolTests : SQLiteTestCase { let now = stmt.scalar(x) as! String if now != curr { - print(now) + //print(now) curr = now } reads += 1 @@ -53,7 +53,7 @@ class ConnectionPoolTests : SQLiteTestCase { } - for x in 10..<50000 { + for x in 10..<5000 { let name = "test" + String(x) let idx = Int(rand()) % 5 @@ -65,13 +65,55 @@ class ConnectionPoolTests : SQLiteTestCase { XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") } - usleep(1500) + usleep(500) } quit = true waitForExpectationsWithTimeout(1000, handler: nil) } + func testConcurrentAccess() throws { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) + + try! pool.writable.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try! pool.writable.run("INSERT INTO test(value) VALUES(?)", 0) + + let q = dispatch_queue_create("Readers/Writers", DISPATCH_QUEUE_CONCURRENT); + var finished = false + + for _ in 0..<5 { + + dispatch_async(q) { + + while !finished { + + let val = pool.readable.scalar("SELECT value FROM test") + assert(val != nil, "DB query returned nil result set") + + } + + } + + } + + for c in 0..<5000 { + + try pool.writable.run("INSERT INTO test(value) VALUES(?)", c) + + usleep(100); + + } + + finished = true + + // Wait for readers to finish + dispatch_barrier_sync(q) { + } + + } + func testAutoRelease() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") From 18f612fd112bdedc6fecec558fb27cd119a2c59e Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sat, 16 Apr 2016 01:23:10 -0700 Subject: [PATCH 0377/1046] Fix imports in ConnectionPool.swift --- SQLite/Core/ConnectionPool.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index c231f4ce..5166e6eb 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -22,7 +22,8 @@ // THE SOFTWARE. // -import Foundation +import Dispatch +import CSQLite private let vfsName = "unix-excl" From 2a5908b513d5b44b1070f2667011f1972482bbd4 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 17 Apr 2016 15:55:59 -0700 Subject: [PATCH 0378/1046] Rename `DBConnection` to `DirectConnection` --- SQLite.xcodeproj/project.pbxproj | 12 ++++++------ SQLite/Core/Connection.swift | 10 +++++----- SQLite/Core/ConnectionPool.swift | 22 +++++++++++----------- SQLite/Core/Statement.swift | 4 ++-- SQLite/Extensions/FTS4.swift | 2 +- SQLite/Typed/CustomFunctions.swift | 2 +- SQLiteTests/ConnectionTests.swift | 12 ++++++------ SQLiteTests/TestHelpers.swift | 2 +- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index edc79e3f..6b145ca1 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -173,7 +173,7 @@ 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPool.swift; sourceTree = ""; }; + AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionPool.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; AA780B3C1CC201A700E0E95E /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = ""; }; AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPoolTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -182,19 +182,19 @@ EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; - EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; + EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Connection.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; + EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; - EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; + EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FTS4.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; EE247AF61C3F06E900AE3E12 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; EE247AF71C3F06E900AE3E12 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; EE247AF81C3F06E900AE3E12 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; EE247AFB1C3F06E900AE3E12 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; - EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; + EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CustomFunctions.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; EE247AFE1C3F06E900AE3E12 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; EE247AFF1C3F06E900AE3E12 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; EE247B001C3F06E900AE3E12 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; @@ -204,7 +204,7 @@ EE247B181C3F134A00AE3E12 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; EE247B1B1C3F137700AE3E12 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; + EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; EE247B201C3F137700AE3E12 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index a2574af2..4ae468dd 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -227,7 +227,7 @@ public protocol Connection { /// A connection to SQLite. -public final class DBConnection : Connection, Equatable { +public final class DirectConnection : Connection, Equatable { /// The location of a SQLite database. public enum Location { @@ -807,7 +807,7 @@ public final class DBConnection : Connection, Equatable { } -extension DBConnection : CustomStringConvertible { +extension DirectConnection : CustomStringConvertible { public var description: String { return String.fromCString(sqlite3_db_filename(handle, nil))! @@ -815,7 +815,7 @@ extension DBConnection : CustomStringConvertible { } -extension DBConnection.Location : CustomStringConvertible { +extension DirectConnection.Location : CustomStringConvertible { public var description: String { switch self { @@ -830,7 +830,7 @@ extension DBConnection.Location : CustomStringConvertible { } -public func ==(lhs: DBConnection, rhs: DBConnection) -> Bool { +public func ==(lhs: DirectConnection, rhs: DirectConnection) -> Bool { return lhs === rhs } @@ -867,7 +867,7 @@ public enum Result : ErrorType { case Error(message: String, code: Int32, statement: Statement?) - init?(errorCode: Int32, connection: DBConnection, statement: Statement? = nil) { + init?(errorCode: Int32, connection: DirectConnection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String.fromCString(sqlite3_errmsg(connection.handle))! diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 5166e6eb..931e5874 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -43,15 +43,15 @@ public protocol ConnectionPoolDelegate { // WAL mode. public final class ConnectionPool { - private let location : DBConnection.Location - private var availableReadConnections = [DBConnection]() - private var unavailableReadConnections = [DBConnection]() + private let location : DirectConnection.Location + private var availableReadConnections = [DirectConnection]() + private var unavailableReadConnections = [DirectConnection]() private let lockQueue : dispatch_queue_t - private var writeConnection : DBConnection! + private var writeConnection : DirectConnection! public var delegate : ConnectionPoolDelegate? - public init(_ location: DBConnection.Location) throws { + public init(_ location: DirectConnection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) try writable.execute("PRAGMA journal_mode = WAL;") @@ -70,9 +70,9 @@ public final class ConnectionPool { private class BorrowedConnection : Connection, Equatable { let pool : ConnectionPool - let connection : DBConnection + let connection : DirectConnection - init(pool: ConnectionPool, connection: DBConnection) { + init(pool: ConnectionPool, connection: DirectConnection) { self.pool = pool self.connection = connection } @@ -114,13 +114,13 @@ public final class ConnectionPool { // Acquires a read/write connection to the database - public var writable : DBConnection { + public var writable : DirectConnection { var writeConnectionInit = dispatch_once_t() dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - self.writeConnection = try! DBConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) + self.writeConnection = try! DirectConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 if let delegate = self.delegate { @@ -140,7 +140,7 @@ public final class ConnectionPool { dispatch_sync(lockQueue) { - let connection : DBConnection + let connection : DirectConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection @@ -149,7 +149,7 @@ public final class ConnectionPool { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - connection = try! DBConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) + connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 self.delegate?.pool(self, didAddConnection: connection) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 07942ae0..d7652c1a 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -29,9 +29,9 @@ public final class Statement { private var handle: COpaquePointer = nil - private let connection: DBConnection + private let connection: DirectConnection - init(_ connection: DBConnection, _ SQL: String) throws { + init(_ connection: DirectConnection, _ SQL: String) throws { self.connection = connection try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index 351bb721..045e5303 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -140,7 +140,7 @@ extension Tokenizer : CustomStringConvertible { } -extension DBConnection { +extension DirectConnection { public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index e32ab3c4..d004f75e 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public extension DBConnection { +public extension DirectConnection { /// Creates or redefines a custom SQL function. /// diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index ab43c52d..bbad19a8 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -10,27 +10,27 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! DBConnection(.InMemory) + let db = try! DirectConnection(.InMemory) XCTAssertEqual("", db.description) } func test_init_returnsInMemoryByDefault() { - let db = try! DBConnection() + let db = try! DirectConnection() XCTAssertEqual("", db.description) } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! DBConnection(.Temporary) + let db = try! DirectConnection(.Temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! DBConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + let db = try! DirectConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } func test_init_withString_returnsURIConnection() { - let db = try! DBConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + let db = try! DirectConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } @@ -39,7 +39,7 @@ class ConnectionTests : SQLiteTestCase { } func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! DBConnection(readonly: true) + let db = try! DirectConnection(readonly: true) XCTAssertTrue(db.readonly) } diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 0854b410..855bf7bd 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -5,7 +5,7 @@ class SQLiteTestCase : XCTestCase { var trace = [String: Int]() - let db = try! DBConnection() + let db = try! DirectConnection() let users = Table("users") From 9548d172655ed841be1ba31600cf47417c58389c Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Mon, 18 Apr 2016 14:41:19 -0700 Subject: [PATCH 0379/1046] Replace pool delegate with setup closures --- SQLite/Core/ConnectionPool.swift | 58 ++++++++++++++++++--------- SQLiteTests/ConnectionPoolTests.swift | 13 ++++++ 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 931e5874..d430d7cf 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -29,15 +29,6 @@ import CSQLite private let vfsName = "unix-excl" -/// Connection pool delegate -public protocol ConnectionPoolDelegate { - - func poolShouldAddConnection(pool: ConnectionPool) -> Bool - func pool(pool: ConnectionPool, didAddConnection: Connection) - -} - - // Connection pool for accessing an SQLite database // with multiple readers & a single writer. Utilizes // WAL mode. @@ -49,12 +40,29 @@ public final class ConnectionPool { private let lockQueue : dispatch_queue_t private var writeConnection : DirectConnection! - public var delegate : ConnectionPoolDelegate? + public var foreignKeys : Bool { + get { + return internalSetup[.ForeignKeys] != nil + } + set { + internalSetup[.ForeignKeys] = newValue ? { try $0.execute("PRAGMA foreign_keys = ON;") } : nil + } + } + + public typealias ConnectionProcessor = Connection throws -> Void + public var setup = [ConnectionProcessor]() + + private enum InternalOption { + case WriteAheadLogging + case ForeignKeys + } + + private var internalSetup = [InternalOption: ConnectionProcessor]() public init(_ location: DirectConnection.Location) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) - try writable.execute("PRAGMA journal_mode = WAL;") + self.internalSetup[.WriteAheadLogging] = { try $0.execute("PRAGMA journal_mode = WAL;") } } public var totalReadableConnectionCount : Int { @@ -123,9 +131,14 @@ public final class ConnectionPool { self.writeConnection = try! DirectConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 - if let delegate = self.delegate { - delegate.pool(self, didAddConnection: self.writeConnection) + for setupProcessor in self.internalSetup.values { + try! setupProcessor(self.writeConnection) } + + for setupProcessor in self.setup { + try! setupProcessor(self.writeConnection) + } + } return writeConnection @@ -140,23 +153,32 @@ public final class ConnectionPool { dispatch_sync(lockQueue) { + // Ensure database is open + self.writable + let connection : DirectConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection } - else if self.delegate?.poolShouldAddConnection(self) ?? true { + else { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 - self.delegate?.pool(self, didAddConnection: connection) + for (type, setupProcessor) in self.internalSetup { + if type == .WriteAheadLogging { + continue + } + try! setupProcessor(connection) + } + + for setupProcessor in self.setup { + try! setupProcessor(connection) + } - } - else { - return } self.unavailableReadConnections.append(connection) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index ce460fe5..b621396c 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -6,6 +6,19 @@ class ConnectionPoolTests : SQLiteTestCase { override func setUp() { super.setUp() } + + func testConnectionSetupClosures() { + + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) + + pool.foreignKeys = true + pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } + + XCTAssertTrue(try pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) + try! pool.writable.execute("INSERT INTO test(value) VALUES (1)") + try! pool.readable.execute("SELECT value FROM test") + } func testConcurrentAccess2() { From ffb92745ad76b7a5eb2e3e293da755fa8d760a7a Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Mon, 18 Apr 2016 17:18:22 -0700 Subject: [PATCH 0380/1046] Fix writable connection initialization Was using incorrect dispatch_once token. --- SQLite/Core/ConnectionPool.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index d430d7cf..dd644304 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -122,9 +122,11 @@ public final class ConnectionPool { // Acquires a read/write connection to the database + + var writeConnectionInit = dispatch_once_t() + public var writable : DirectConnection { - - var writeConnectionInit = dispatch_once_t() + dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX From b7a0eb1a58936071a6d171036f3e36f75af8f401 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Mon, 18 Apr 2016 17:51:38 -0700 Subject: [PATCH 0381/1046] Cleanup connection pool tests # Conflicts: # SQLiteTests/ConnectionPoolTests.swift --- SQLiteTests/ConnectionPoolTests.swift | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index b621396c..74351527 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -3,28 +3,25 @@ import SQLite class ConnectionPoolTests : SQLiteTestCase { + var pool : ConnectionPool! + override func setUp() { - super.setUp() + let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) } func testConnectionSetupClosures() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - pool.foreignKeys = true pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } - XCTAssertTrue(try pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) + XCTAssertTrue(pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) try! pool.writable.execute("INSERT INTO test(value) VALUES (1)") try! pool.readable.execute("SELECT value FROM test") } func testConcurrentAccess2() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - let conn = pool.writable try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") try! conn.execute("DELETE FROM test") @@ -45,7 +42,7 @@ class ConnectionPoolTests : SQLiteTestCase { print("started", x) - let conn = pool.readable + let conn = self.pool.readable let stmt = try! conn.prepare("SELECT name FROM test WHERE id = ?") var curr = stmt.scalar(x) as! String @@ -87,9 +84,6 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess() throws { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - try! pool.writable.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! pool.writable.run("INSERT INTO test(value) VALUES(?)", 0) @@ -102,7 +96,7 @@ class ConnectionPoolTests : SQLiteTestCase { while !finished { - let val = pool.readable.scalar("SELECT value FROM test") + let val = self.pool.readable.scalar("SELECT value FROM test") assert(val != nil, "DB query returned nil result set") } @@ -129,9 +123,6 @@ class ConnectionPoolTests : SQLiteTestCase { func testAutoRelease() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - let pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) - do { try! pool.readable.execute("SELECT 1") } From 0eeedfeedafce317d2bb2770ca72f3cbded9f80c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 May 2016 17:39:40 +0100 Subject: [PATCH 0382/1046] Split into standard / standalone subspecs The standard subspec is the default and behaves as previously; the standalone subspec adds a dependency to the sqlite3 pod which downloads and compiles a version of SQLite instead of using the system-supplied version. The version of SQLite used can be further customized by requiring another sqlite3 subspec from the main Podfile. --- SQLite.swift.podspec | 39 +++++++++++++++++++++++------------- SQLite/Core/Connection.swift | 4 ++++ SQLite/Core/Statement.swift | 4 ++++ SQLite/Helpers.swift | 4 ++++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index fe814100..6e5e25d5 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -24,19 +24,30 @@ Pod::Spec.new do |s| s.tvos.deployment_target = "9.0" s.osx.deployment_target = "10.9" s.watchos.deployment_target = "2.0" + s.default_subspec = 'standard' - s.preserve_paths = 'CocoaPods/**/*' - s.pod_target_xcconfig = { - 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', - 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', - 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', - 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', - 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', - 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', - 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' - } - - s.libraries = 'sqlite3' - s.source_files = 'SQLite/**/*.{c,h,m,swift}' - s.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + s.subspec 'standard' do |ss| + ss.source_files = 'SQLite/**/*.{c,h,m,swift}' + ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + + ss.library = 'sqlite3' + ss.preserve_paths = 'CocoaPods/**/*' + ss.pod_target_xcconfig = { + 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', + 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', + 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', + 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', + 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', + 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', + 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' + } + end + + s.subspec 'standalone' do |ss| + ss.source_files = 'SQLite/**/*.{c,h,m,swift}' + ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' } + + ss.dependency 'sqlite3' + end end diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 7d67b051..f4be7983 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -23,7 +23,11 @@ // import Dispatch +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#else import CSQLite +#endif /// A connection to SQLite. public final class Connection { diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 39fb000d..9a4bfa1e 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -22,7 +22,11 @@ // THE SOFTWARE. // +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#else import CSQLite +#endif /// A single SQL statement. public final class Statement { diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index 33fc6a62..cc6da27c 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -22,7 +22,11 @@ // THE SOFTWARE. // +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#else import CSQLite +#endif public typealias Star = (Expression?, Expression?) -> Expression From c29ec30fe86dd48d8a510b53fb04b43979f84e5e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 May 2016 18:02:33 +0100 Subject: [PATCH 0383/1046] Add documentation --- Documentation/Index.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 52d04c69..700deb9f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -111,8 +111,26 @@ install SQLite.swift with Carthage: 3. Run `pod install`. + #### Requiring a specific version of SQLite + + If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: + +``` ruby + pod 'SQLite.swift/standalone', '~> 0.10.1' +``` + +By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: + +``` ruby + pod 'SQLite.swift/standalone', '~> 0.10.1' + pod 'sqlite3/fts5', '= 3.11.1' # SQLite 3.11.1 with FTS5 enabled +``` + +See the [sqlite3 podspec][sqlite3pod] for more details. + [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started +[sqlite3pod]: https://github.com/clemensg/sqlite3pod ### Manual From aacdde0778264c652048dffbf01598dfbcac8e82 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 May 2016 19:30:15 +0100 Subject: [PATCH 0384/1046] [CocoaPods] integration tests --- .travis.yml | 1 + CocoaPodsTests/.gitignore | 1 + CocoaPodsTests/Gemfile | 4 ++ CocoaPodsTests/Gemfile.lock | 65 +++++++++++++++++ CocoaPodsTests/Makefile | 10 +++ CocoaPodsTests/integration_test.rb | 33 +++++++++ CocoaPodsTests/test_running_validator.rb | 91 ++++++++++++++++++++++++ SQLiteTests/FTS4Tests.swift | 4 +- 8 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 CocoaPodsTests/.gitignore create mode 100644 CocoaPodsTests/Gemfile create mode 100644 CocoaPodsTests/Gemfile.lock create mode 100644 CocoaPodsTests/Makefile create mode 100755 CocoaPodsTests/integration_test.rb create mode 100644 CocoaPodsTests/test_running_validator.rb diff --git a/.travis.yml b/.travis.yml index 1964faf9..ad0f18b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,5 @@ before_install: - gem install xcpretty --no-document script: - make test + - cd CocoaPodsTests && make test osx_image: xcode7.3 diff --git a/CocoaPodsTests/.gitignore b/CocoaPodsTests/.gitignore new file mode 100644 index 00000000..4cf24de2 --- /dev/null +++ b/CocoaPodsTests/.gitignore @@ -0,0 +1 @@ +gems/ diff --git a/CocoaPodsTests/Gemfile b/CocoaPodsTests/Gemfile new file mode 100644 index 00000000..0a6af5c8 --- /dev/null +++ b/CocoaPodsTests/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +gem 'cocoapods' +gem 'minitest' diff --git a/CocoaPodsTests/Gemfile.lock b/CocoaPodsTests/Gemfile.lock new file mode 100644 index 00000000..7173e2c4 --- /dev/null +++ b/CocoaPodsTests/Gemfile.lock @@ -0,0 +1,65 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.2.6) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + claide (1.0.0) + cocoapods (1.0.0) + activesupport (>= 4.0.2) + claide (>= 1.0.0, < 2.0) + cocoapods-core (= 1.0.0) + cocoapods-deintegrate (>= 1.0.0, < 2.0) + cocoapods-downloader (>= 1.0.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.0.0, < 2.0) + cocoapods-try (>= 1.0.0, < 2.0) + colored (~> 1.2) + escape (~> 0.0.4) + fourflusher (~> 0.3.0) + molinillo (~> 0.4.5) + nap (~> 1.0) + xcodeproj (>= 1.0.0, < 2.0) + cocoapods-core (1.0.0) + activesupport (>= 4.0.2) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.0) + cocoapods-downloader (1.0.0) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.0.0) + cocoapods-trunk (1.0.0) + nap (>= 0.8, < 2.0) + netrc (= 0.7.8) + cocoapods-try (1.0.0) + colored (1.2) + escape (0.0.4) + fourflusher (0.3.0) + fuzzy_match (2.0.4) + i18n (0.7.0) + json (1.8.3) + minitest (5.8.4) + molinillo (0.4.5) + nap (1.1.0) + netrc (0.7.8) + thread_safe (0.3.5) + tzinfo (1.2.2) + thread_safe (~> 0.1) + xcodeproj (1.0.0) + activesupport (>= 3) + claide (>= 1.0.0, < 2.0) + colored (~> 1.2) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + minitest diff --git a/CocoaPodsTests/Makefile b/CocoaPodsTests/Makefile new file mode 100644 index 00000000..c9a3182e --- /dev/null +++ b/CocoaPodsTests/Makefile @@ -0,0 +1,10 @@ +test: install + @set -e; \ + for test in *_test.rb; do \ + bundle exec ./$$test; \ + done + +install: + @bundle install --path gems + +.PHONY: test install diff --git a/CocoaPodsTests/integration_test.rb b/CocoaPodsTests/integration_test.rb new file mode 100755 index 00000000..325b8493 --- /dev/null +++ b/CocoaPodsTests/integration_test.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +require 'minitest/autorun' +require_relative 'test_running_validator' + +class IntegrationTest < Minitest::Test + + def test_validate_project + assert validator.validate, "validation failed: #{validator.failure_reason}" + end + + private + + def validator + @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| + validator.test_files = Dir["#{project_test_dir}/*.swift"] + validator.config.verbose = true + validator.no_clean = true + validator.use_frameworks = true + validator.fail_fast = true + validator.local = true + validator.allow_warnings = true + end + end + + def podspec + File.expand_path(File.dirname(__FILE__) + '/../SQLite.swift.podspec') + end + + def project_test_dir + File.expand_path(File.dirname(__FILE__) + '/../SQLiteTests') + end +end diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb new file mode 100644 index 00000000..10f4db61 --- /dev/null +++ b/CocoaPodsTests/test_running_validator.rb @@ -0,0 +1,91 @@ +require 'cocoapods' +require 'cocoapods/validator' +require 'fileutils' + +class TestRunningValidator < Pod::Validator + APP_TARGET = 'App' + TEST_TARGET = 'Tests' + + attr_accessor :test_files + + def create_app_project + super.tap do + project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj") + create_test_target(project) + end + end + + def install_pod + super.tap do + if local? + FileUtils.ln_s file.dirname, validation_dir + "Pods/#{spec.name}" + end + end + end + + def podfile_from_spec(*args) + super(*args).tap do |pod_file| + add_test_target(pod_file) + end + end + + def build_pod + super + Pod::UI.message "\Testing with xcodebuild.\n".yellow do + run_tests + end + end + + private + def create_test_target(project) + test_target = project.new_target(:unit_test_bundle, TEST_TARGET, consumer.platform_name, deployment_target) + group = project.new_group(TEST_TARGET) + test_target.add_file_references(test_files.map { |file| group.new_file(file) }) + project.save + create_test_scheme(project, test_target) + project + end + + def create_test_scheme(project, test_target) + project.recreate_user_schemes + test_scheme = Xcodeproj::XCScheme.new(test_scheme_path(project)) + test_scheme.add_test_target(test_target) + test_scheme.save! + end + + def test_scheme_path(project) + Xcodeproj::XCScheme.user_data_dir(project.path) + "#{TEST_TARGET}.xcscheme" + end + + def add_test_target(pod_file) + app_target = pod_file.target_definitions[APP_TARGET] + Pod::Podfile::TargetDefinition.new(TEST_TARGET, app_target) + end + + def run_tests + command = %W(clean test -workspace #{APP_TARGET}.xcworkspace -scheme #{TEST_TARGET} -configuration Debug) + case consumer.platform_name + when :ios + command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) + command += Fourflusher::SimControl.new.destination('iPhone 4s', deployment_target) + when :osx + command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks) + when :tvos + command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) + command += Fourflusher::SimControl.new.destination('Apple TV 1080p', deployment_target) + else + return # skip watchos + end + + output, status = Dir.chdir(validation_dir) { _xcodebuild(command) } + unless status.success? + message = 'Returned an unsuccessful exit code.' + if config.verbose? + message += "\nXcode output: \n#{output}\n" + else + message += ' You can use `--verbose` for more information.' + end + error('xcodebuild', message) + end + end +end diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index a94d9073..7f889955 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -45,7 +45,7 @@ class FTS4Tests : XCTestCase { } class FTS4IntegrationTests : SQLiteTestCase { - +#if !SQLITE_SWIFT_STANDALONE func test_registerTokenizer_registersTokenizer() { let emails = VirtualTable("emails") let subject = Expression("subject") @@ -73,5 +73,5 @@ class FTS4IntegrationTests : SQLiteTestCase { try! db.run(emails.insert(subject <- "Aún más cáfe!")) XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) } - +#endif } From 77a0f8ea116db5526702acf33b4ce4bb12c1ece0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 19 May 2016 22:16:59 +0100 Subject: [PATCH 0385/1046] Full FTS4/5 configuration --- Documentation/Index.md | 32 +++++ SQLite.xcodeproj/project.pbxproj | 16 +++ SQLite/Extensions/FTS4.swift | 196 ++++++++++++++++++++++++++++++- SQLite/Extensions/FTS5.swift | 97 +++++++++++++++ SQLiteTests/FTS4Tests.swift | 131 +++++++++++++++++++++ SQLiteTests/FTS5Tests.swift | 124 +++++++++++++++++++ 6 files changed, 590 insertions(+), 6 deletions(-) create mode 100644 SQLite/Extensions/FTS5.swift create mode 100644 SQLiteTests/FTS5Tests.swift diff --git a/Documentation/Index.md b/Documentation/Index.md index 700deb9f..2c64fdad 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1399,6 +1399,22 @@ try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) ``` +We can set the full range of parameters by creating a `FTS4Config` object. + +``` swift +let emails = VirtualTable("emails") +let subject = Expression("subject") +let body = Expression("body") +let config = FTS4Config() + .column(subject) + .column(body, indexed: false) + .languageId("lid") + .order(.Desc) + +try db.run(emails.create(.FTS4(config)) +// CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc") +``` + Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. ``` swift @@ -1414,6 +1430,22 @@ let replies = emails.filter(subject.match("Re:*")) // SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*' ``` +### FTS5 + +When linking against a version of SQLite with [FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual table +in a similar fashion. + +```swift +let emails = VirtualTable("emails") +let subject = Expression("subject") +let body = Expression("body") +let config = FTS5Config() + .column(subject) + .column(body, indexed: false) + +try db.run(emails.create(.FTS5(config)) +// CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED) +``` ## Executing Arbitrary SQL diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 786c2c77..88d74dc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,12 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -154,6 +160,8 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -406,6 +414,7 @@ EE247B331C3F142E00AE3E12 /* ValueTests.swift */, EE247B161C3F127200AE3E12 /* TestHelpers.swift */, EE247AE41C3F04ED00AE3E12 /* Info.plist */, + 19A1721B8984686B9963B45D /* FTS5Tests.swift */, ); path = SQLiteTests; sourceTree = ""; @@ -429,6 +438,7 @@ children = ( EE247AF51C3F06E900AE3E12 /* FTS4.swift */, EE247AF61C3F06E900AE3E12 /* R*Tree.swift */, + 19A1730E4390C775C25677D1 /* FTS5.swift */, ); path = Extensions; sourceTree = ""; @@ -786,6 +796,7 @@ 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, + 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -808,6 +819,7 @@ 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, + 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -840,6 +852,7 @@ EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, + 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -862,6 +875,7 @@ EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, + 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -887,6 +901,7 @@ EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, + 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -909,6 +924,7 @@ EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, + 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index e5a1d815..fc71124a 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -29,14 +29,12 @@ extension Module { } @warn_unused_result public static func FTS4(columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { - var columns = columns - - if let tokenizer = tokenizer { - columns.append("=".join([Expression(literal: "tokenize"), Expression(literal: tokenizer.description)])) - } - return Module(name: "fts4", arguments: columns) + return FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) } + @warn_unused_result public static func FTS4(config: FTS4Config) -> Module { + return Module(name: "fts4", arguments: config.arguments()) + } } extension VirtualTable { @@ -156,3 +154,189 @@ extension Connection { } } + +/// Configuration options shared between the [FTS4](https://www.sqlite.org/fts3.html) and +/// [FTS5](https://www.sqlite.org/fts5.html) extensions. +public class FTSConfig { + typealias ColumnDefinition = (Expressible, Bool) + var columnDefinitions = [ColumnDefinition]() + var tokenizer: Tokenizer? + var prefixes = [Int]() + var externalContentSchema: SchemaType? + var isContentless: Bool = false + + /// Adds an indexed column definition + public func column(column: Expressible) -> Self { + return self.column(column, indexed: true) + } + + /// Adds a column definition + public func column(column: Expressible, indexed: Bool) -> Self { + self.columnDefinitions.append((column, indexed)) + return self + } + + public func columns(columns: [Expressible]) -> Self { + for column in columns { + self.column(column) + } + return self + } + + /// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer) + public func tokenizer(tokenizer: Tokenizer?) -> Self { + self.tokenizer = tokenizer + return self + } + + /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) + public func prefix(prefix: [Int]) -> Self { + self.prefixes += prefix + return self + } + + /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) + public func externalContent(schema: SchemaType) -> Self { + self.externalContentSchema = schema + return self + } + + /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) + public func contentless() -> Self { + self.isContentless = true + return self + } + + func formatColumnDefinitions() -> [Expressible] { + return columnDefinitions.map { $0.0 } + } + + func arguments() -> [Expressible] { + return options().arguments + } + + func options() -> Options { + var options = Options() + options.append(formatColumnDefinitions()) + if let tokenizer = tokenizer { + options.append("tokenize", value: Expression(literal: tokenizer.description)) + } + options.appendCommaSeparated("prefix", values:prefixes.sort().map { String($0) }) + if isContentless { + options.append("content", value: "") + } else if let externalContentSchema = externalContentSchema { + options.append("content", value: externalContentSchema.tableName()) + } + return options + } + + struct Options { + var arguments = [Expressible]() + + mutating func append(columns: [Expressible]) -> Options { + arguments.appendContentsOf(columns) + return self + } + + mutating func appendCommaSeparated(key: String, values: [String]) -> Options { + if values.isEmpty { + return self + } else { + return append(key, value: values.joinWithSeparator(",")) + } + } + + mutating func append(key: String, value: CustomStringConvertible?) -> Options { + return append(key, value: value?.description) + } + + mutating func append(key: String, value: String?) -> Options { + return append(key, value: value.map { Expression($0) }) + } + + mutating func append(key: String, value: Expressible?) -> Options { + if let value = value { + arguments.append("=".join([Expression(literal: key), value])) + } + return self + } + } +} + +/// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. +public class FTS4Config : FTSConfig { + /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) + public enum MatchInfo : CustomStringConvertible { + case FTS3 + public var description: String { + return "fts3" + } + } + + /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) + public enum Order : CustomStringConvertible { + /// Data structures are optimized for returning results in ascending order by docid (default) + case Asc + /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid. + case Desc + + public var description: String { + switch self { + case Asc: return "asc" + case Desc: return "desc" + } + } + } + + var compressFunction: String? + var uncompressFunction: String? + var languageId: String? + var matchInfo: MatchInfo? + var order: Order? + + override public init() { + } + + /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) + public func compress(functionName: String) -> Self { + self.compressFunction = functionName + return self + } + + /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) + public func uncompress(functionName: String) -> Self { + self.uncompressFunction = functionName + return self + } + + /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) + public func languageId(columnName: String) -> Self { + self.languageId = columnName + return self + } + + /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) + public func matchInfo(matchInfo: MatchInfo) -> Self { + self.matchInfo = matchInfo + return self + } + + /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) + public func order(order: Order) -> Self { + self.order = order + return self + } + + override func options() -> Options { + var options = super.options() + for notIndexedColumn in (columnDefinitions.filter { !$0.1 }.map { $0.0 }) { + options.append("notindexed", value: notIndexedColumn) + } + options.append("languageid", value: languageId) + options.append("compress", value: compressFunction) + options.append("uncompress", value: uncompressFunction) + options.append("matchinfo", value: matchInfo) + options.append("order", value: order) + return options + } +} diff --git a/SQLite/Extensions/FTS5.swift b/SQLite/Extensions/FTS5.swift new file mode 100644 index 00000000..e7e21403 --- /dev/null +++ b/SQLite/Extensions/FTS5.swift @@ -0,0 +1,97 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension Module { + @warn_unused_result public static func FTS5(config: FTS5Config) -> Module { + return Module(name: "fts5", arguments: config.arguments()) + } +} + +/// Configuration for the [FTS5](https://www.sqlite.org/fts5.html) extension. +/// +/// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version +/// of SQLite. +public class FTS5Config : FTSConfig { + public enum Detail : CustomStringConvertible { + /// store rowid, column number, term offset + case Full + /// store rowid, column number + case Column + /// store rowid + case None + + public var description: String { + switch self { + case Full: return "full" + case Column: return "column" + case None: return "none" + } + } + } + + var detail: Detail? + var contentRowId: Expressible? + var columnSize: Int? + + override public init() { + } + + /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) + public func contentRowId(column: Expressible) -> Self { + self.contentRowId = column + return self + } + + /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) + public func columnSize(size: Int) -> Self { + self.columnSize = size + return self + } + + /// [The Detail Option](https://www.sqlite.org/fts5.html#section_4_6) + public func detail(detail: Detail) -> Self { + self.detail = detail + return self + } + + override func options() -> Options { + var options = super.options() + options.append("content_rowid", value: contentRowId) + if let columnSize = columnSize { + options.append("columnsize", value: Expression(value: columnSize)) + } + options.append("detail", value: detail) + return options + } + + override func formatColumnDefinitions() -> [Expressible] { + return columnDefinitions.map { definition in + if definition.1 { + return definition.0 + } else { + return " ".join([definition.0, Expression(literal: "UNINDEXED")]) + } + } + } +} diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index 7f889955..ca46aa13 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -44,6 +44,137 @@ class FTS4Tests : XCTestCase { } +class FTS4ConfigTests : XCTestCase { + var config: FTS4Config! + + override func setUp() { + super.setUp() + config = FTS4Config() + } + + func test_empty_config() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4()", + sql(config)) + } + + func test_config_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\")", + sql(config.column(string))) + } + + func test_config_columns() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", \"int\")", + sql(config.columns([string, int]))) + } + + func test_config_unindexed_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", notindexed=\"string\")", + sql(config.column(string, indexed: false))) + } + + func test_external_content_view() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"view\")", + sql(config.externalContent(_view ))) + } + + func test_external_content_virtual_table() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"virtual_table\")", + sql(config.externalContent(virtualTable))) + } + + func test_tokenizer_simple() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=simple)", + sql(config.tokenizer(.Simple))) + } + + func test_tokenizer_porter() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=porter)", + sql(config.tokenizer(.Porter))) + } + + func test_tokenizer_unicode61() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61)", + sql(config.tokenizer(.Unicode61()))) + } + + func test_tokenizer_unicode61_with_options() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) + } + + func test_content_less() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(content=\"\")", + sql(config.contentless())) + } + + func test_config_matchinfo() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(matchinfo=\"fts3\")", + sql(config.matchInfo(.FTS3))) + } + + func test_config_order_asc() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"asc\")", + sql(config.order(.Asc))) + } + + func test_config_order_desc() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"desc\")", + sql(config.order(.Desc))) + } + + func test_config_compress() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(compress=\"compress_foo\")", + sql(config.compress("compress_foo"))) + } + + func test_config_uncompress() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(uncompress=\"uncompress_foo\")", + sql(config.uncompress("uncompress_foo"))) + } + + func test_config_languageId() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(languageid=\"lid\")", + sql(config.languageId("lid"))) + } + + func test_config_all() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\")", + sql(config + .tokenizer(.Porter) + .column(int) + .column(string, indexed: false) + .column(date, indexed: false) + .externalContent(table) + .matchInfo(.FTS3) + .languageId("lid") + .order(.Desc) + .prefix([2, 4])) + ) + } + + func sql(config: FTS4Config) -> String { + return virtualTable.create(.FTS4(config)) + } +} + class FTS4IntegrationTests : SQLiteTestCase { #if !SQLITE_SWIFT_STANDALONE func test_registerTokenizer_registersTokenizer() { diff --git a/SQLiteTests/FTS5Tests.swift b/SQLiteTests/FTS5Tests.swift new file mode 100644 index 00000000..c23b4726 --- /dev/null +++ b/SQLiteTests/FTS5Tests.swift @@ -0,0 +1,124 @@ +import XCTest +import SQLite + +class FTS5Tests: XCTestCase { + var config: FTS5Config! + + override func setUp() { + super.setUp() + config = FTS5Config() + } + + func test_empty_config() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5()", + sql(config)) + } + + func test_config_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\")", + sql(config.column(string))) + } + + func test_config_columns() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\", \"int\")", + sql(config.columns([string, int]))) + } + + func test_config_unindexed_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\" UNINDEXED)", + sql(config.column(string, indexed: false))) + } + + func test_external_content_table() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"table\")", + sql(config.externalContent(table))) + } + + func test_external_content_view() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"view\")", + sql(config.externalContent(_view))) + } + + func test_external_content_virtual_table() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"virtual_table\")", + sql(config.externalContent(virtualTable))) + } + + func test_content_less() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content=\"\")", + sql(config.contentless())) + } + + func test_content_rowid() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(content_rowid=\"string\")", + sql(config.contentRowId(string))) + } + + func test_tokenizer_porter() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=porter)", + sql(config.tokenizer(.Porter))) + } + + func test_tokenizer_unicode61() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61)", + sql(config.tokenizer(.Unicode61()))) + } + + func test_tokenizer_unicode61_with_options() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) + } + + func test_column_size() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(columnsize=1)", + sql(config.columnSize(1))) + } + + func test_detail_full() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"full\")", + sql(config.detail(.Full))) + } + + func test_detail_column() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"column\")", + sql(config.detail(.Column))) + } + + func test_detail_none() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"none\")", + sql(config.detail(.None))) + } + + func test_fts5_config_all() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, tokenize=porter, prefix=\"2,4\", content=\"table\")", + sql(config + .tokenizer(.Porter) + .column(int) + .column(string, indexed: false) + .column(date, indexed: false) + .externalContent(table) + .prefix([2, 4])) + ) + } + + func sql(config: FTS5Config) -> String { + return virtualTable.create(.FTS5(config)) + } +} From f4b9bc619b8cf9c2f670e3fdba5813cacceda224 Mon Sep 17 00:00:00 2001 From: Pascal Pfiffner Date: Wed, 25 May 2016 17:50:57 +0200 Subject: [PATCH 0386/1046] Add missing imports --- SQLite/Core/Connection.swift | 1 + SQLite/Typed/CoreFunctions.swift | 3 +++ 2 files changed, 4 insertions(+) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index f4be7983..f83ab812 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -22,6 +22,7 @@ // THE SOFTWARE. // +import Foundation import Dispatch #if SQLITE_SWIFT_STANDALONE import sqlite3 diff --git a/SQLite/Typed/CoreFunctions.swift b/SQLite/Typed/CoreFunctions.swift index b929e40a..5332556d 100644 --- a/SQLite/Typed/CoreFunctions.swift +++ b/SQLite/Typed/CoreFunctions.swift @@ -22,6 +22,9 @@ // THE SOFTWARE. // +import Foundation + + extension ExpressionType where UnderlyingType : Number { /// Builds a copy of the expression wrapped with the `abs` function. From fa5626e90441e40ed1d6358db9745c0ba463d4de Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 28 May 2016 11:41:43 +0100 Subject: [PATCH 0387/1046] Change column config to `.column(body, [.unindexed])` syntax --- Documentation/Index.md | 2 +- SQLite/Extensions/FTS4.swift | 20 ++++++++++---------- SQLite/Extensions/FTS5.swift | 6 +++--- SQLiteTests/FTS4Tests.swift | 6 +++--- SQLiteTests/FTS5Tests.swift | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2c64fdad..41e1c382 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1407,7 +1407,7 @@ let subject = Expression("subject") let body = Expression("body") let config = FTS4Config() .column(subject) - .column(body, indexed: false) + .column(body, [.unindexed]) .languageId("lid") .order(.Desc) diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index fc71124a..466c42c7 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -158,21 +158,21 @@ extension Connection { /// Configuration options shared between the [FTS4](https://www.sqlite.org/fts3.html) and /// [FTS5](https://www.sqlite.org/fts5.html) extensions. public class FTSConfig { - typealias ColumnDefinition = (Expressible, Bool) + public enum ColumnOption { + /// [The notindexed= option](https://www.sqlite.org/fts3.html#section_6_5) + case unindexed + } + + typealias ColumnDefinition = (Expressible, options: [ColumnOption]) var columnDefinitions = [ColumnDefinition]() var tokenizer: Tokenizer? var prefixes = [Int]() var externalContentSchema: SchemaType? var isContentless: Bool = false - /// Adds an indexed column definition - public func column(column: Expressible) -> Self { - return self.column(column, indexed: true) - } - /// Adds a column definition - public func column(column: Expressible, indexed: Bool) -> Self { - self.columnDefinitions.append((column, indexed)) + public func column(column: Expressible, _ options: [ColumnOption] = []) -> Self { + self.columnDefinitions.append((column, options)) return self } @@ -329,8 +329,8 @@ public class FTS4Config : FTSConfig { override func options() -> Options { var options = super.options() - for notIndexedColumn in (columnDefinitions.filter { !$0.1 }.map { $0.0 }) { - options.append("notindexed", value: notIndexedColumn) + for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { + options.append("notindexed", value: column) } options.append("languageid", value: languageId) options.append("compress", value: compressFunction) diff --git a/SQLite/Extensions/FTS5.swift b/SQLite/Extensions/FTS5.swift index e7e21403..97056819 100644 --- a/SQLite/Extensions/FTS5.swift +++ b/SQLite/Extensions/FTS5.swift @@ -87,10 +87,10 @@ public class FTS5Config : FTSConfig { override func formatColumnDefinitions() -> [Expressible] { return columnDefinitions.map { definition in - if definition.1 { - return definition.0 - } else { + if definition.options.contains(.unindexed) { return " ".join([definition.0, Expression(literal: "UNINDEXED")]) + } else { + return definition.0 } } } diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index ca46aa13..c1071972 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -73,7 +73,7 @@ class FTS4ConfigTests : XCTestCase { func test_config_unindexed_column() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", notindexed=\"string\")", - sql(config.column(string, indexed: false))) + sql(config.column(string, [.unindexed]))) } func test_external_content_view() { @@ -160,8 +160,8 @@ class FTS4ConfigTests : XCTestCase { sql(config .tokenizer(.Porter) .column(int) - .column(string, indexed: false) - .column(date, indexed: false) + .column(string, [.unindexed]) + .column(date, [.unindexed]) .externalContent(table) .matchInfo(.FTS3) .languageId("lid") diff --git a/SQLiteTests/FTS5Tests.swift b/SQLiteTests/FTS5Tests.swift index c23b4726..aa3965bd 100644 --- a/SQLiteTests/FTS5Tests.swift +++ b/SQLiteTests/FTS5Tests.swift @@ -30,7 +30,7 @@ class FTS5Tests: XCTestCase { func test_config_unindexed_column() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"string\" UNINDEXED)", - sql(config.column(string, indexed: false))) + sql(config.column(string, [.unindexed]))) } func test_external_content_table() { @@ -111,8 +111,8 @@ class FTS5Tests: XCTestCase { sql(config .tokenizer(.Porter) .column(int) - .column(string, indexed: false) - .column(date, indexed: false) + .column(string, [.unindexed]) + .column(date, [.unindexed]) .externalContent(table) .prefix([2, 4])) ) From b3a7e6c5815ad3d0979006a12c67800f6bc34444 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 28 May 2016 21:49:58 +0100 Subject: [PATCH 0388/1046] Update documentation --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 41e1c382..0579e6f3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1441,7 +1441,7 @@ let subject = Expression("subject") let body = Expression("body") let config = FTS5Config() .column(subject) - .column(body, indexed: false) + .column(body, [.unindexed]) try db.run(emails.create(.FTS5(config)) // CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED) From 341ecec8eb6fab7a26fc0b414b5c23d341154d11 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 30 May 2016 10:32:35 +0100 Subject: [PATCH 0389/1046] Add GRDB to list of alternatives --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 56d4374d..242567f0 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [Camembert](https://github.com/remirobert/Camembert) - [EonilSQLite3](https://github.com/Eonil/SQLite3) + - [GRDB](https://github.com/groue/GRDB.swift) - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - [Squeal](https://github.com/nerdyc/Squeal) - [SwiftData](https://github.com/ryanfowler/SwiftData) From c85fe382d67dc664a0675edb601d377364a4e740 Mon Sep 17 00:00:00 2001 From: FrainL Date: Mon, 30 May 2016 22:54:15 +0800 Subject: [PATCH 0390/1046] prevent from crash when lastInsertRowId is negative --- SQLite/Core/Connection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index f4be7983..85817025 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -107,7 +107,7 @@ public final class Connection { /// The last rowid inserted into the database via this connection. public var lastInsertRowid: Int64? { let rowid = sqlite3_last_insert_rowid(handle) - return rowid > 0 ? rowid : nil + return rowid != 0 ? rowid : nil } /// The last number of changes (inserts, updates, or deletes) made to the From f52bdc2fa499636d090d29aa4f88c5f5b0498399 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 1 Jun 2016 15:26:09 -0400 Subject: [PATCH 0391/1046] Remove stale recommendation Will re-add if updated for a modern version of Swift. Signed-off-by: Stephen Celis --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 242567f0..850d3374 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,6 @@ These projects enhance or use SQLite.swift: Looking for something else? Try another Swift wrapper (or [FMDB][]): - [Camembert](https://github.com/remirobert/Camembert) - - [EonilSQLite3](https://github.com/Eonil/SQLite3) - [GRDB](https://github.com/groue/GRDB.swift) - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - [Squeal](https://github.com/nerdyc/Squeal) From ee06b9661af3097cb9e7de3bfeecefe0a0b1ee5a Mon Sep 17 00:00:00 2001 From: Pascal Pfiffner Date: Mon, 13 Jun 2016 08:59:09 +0200 Subject: [PATCH 0392/1046] More precise Foundation imports --- SQLite/Core/Connection.swift | 2 +- SQLite/Typed/CoreFunctions.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index f83ab812..58c5a7ab 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -import Foundation +import Foundation.NSUUID import Dispatch #if SQLITE_SWIFT_STANDALONE import sqlite3 diff --git a/SQLite/Typed/CoreFunctions.swift b/SQLite/Typed/CoreFunctions.swift index 5332556d..b597f3ba 100644 --- a/SQLite/Typed/CoreFunctions.swift +++ b/SQLite/Typed/CoreFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -import Foundation +import Foundation.NSData extension ExpressionType where UnderlyingType : Number { From 960309eb02724ae0f18f721942fb22473bb4b602 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Thu, 16 Jun 2016 15:48:36 -0600 Subject: [PATCH 0393/1046] Implement connection limit. --- SQLite/Core/ConnectionPool.swift | 55 +++++++++++++-------------- SQLiteTests/ConnectionPoolTests.swift | 21 +++++----- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index dd644304..77cf047e 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -39,6 +39,7 @@ public final class ConnectionPool { private var unavailableReadConnections = [DirectConnection]() private let lockQueue : dispatch_queue_t private var writeConnection : DirectConnection! + private let connectionSemaphore = dispatch_semaphore_create(5) public var foreignKeys : Bool { get { @@ -91,6 +92,7 @@ public final class ConnectionPool { self.pool.unavailableReadConnections.removeAtIndex(index) } self.pool.availableReadConnections.append(self.connection) + dispatch_semaphore_signal(self.pool.connectionSemaphore) } } @@ -151,44 +153,41 @@ public final class ConnectionPool { var borrowed : BorrowedConnection! - repeat { + dispatch_semaphore_wait(connectionSemaphore, DISPATCH_TIME_FOREVER) + dispatch_sync(lockQueue) { - dispatch_sync(lockQueue) { - - // Ensure database is open - self.writable + // Ensure database is open + self.writable + + let connection : DirectConnection + + if let availableConnection = self.availableReadConnections.popLast() { + connection = availableConnection + } + else { - let connection : DirectConnection + let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - if let availableConnection = self.availableReadConnections.popLast() { - connection = availableConnection - } - else { - - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - - connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) - connection.busyTimeout = 2 + connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) + connection.busyTimeout = 2 - for (type, setupProcessor) in self.internalSetup { - if type == .WriteAheadLogging { - continue - } - try! setupProcessor(connection) - } - - for setupProcessor in self.setup { - try! setupProcessor(connection) + for (type, setupProcessor) in self.internalSetup { + if type == .WriteAheadLogging { + continue } - + try! setupProcessor(connection) } - self.unavailableReadConnections.append(connection) + for setupProcessor in self.setup { + try! setupProcessor(connection) + } - borrowed = BorrowedConnection(pool: self, connection: connection) } - } while borrowed == nil + self.unavailableReadConnections.append(connection) + + borrowed = BorrowedConnection(pool: self, connection: connection) + } return borrowed } diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 74351527..b7f15171 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -22,33 +22,32 @@ class ConnectionPoolTests : SQLiteTestCase { func testConcurrentAccess2() { + let threadCount = 20 let conn = pool.writable try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") try! conn.execute("DELETE FROM test") - try! conn.execute("INSERT INTO test(id,name) VALUES(0, 'test0')") - try! conn.execute("INSERT INTO test(id,name) VALUES(1, 'test1')") - try! conn.execute("INSERT INTO test(id,name) VALUES(2, 'test2')") - try! conn.execute("INSERT INTO test(id,name) VALUES(3, 'test3')") - try! conn.execute("INSERT INTO test(id,name) VALUES(4, 'test4')") + for threadNumber in 0.. Date: Wed, 22 Jun 2016 12:37:10 -0600 Subject: [PATCH 0394/1046] Fix import problem for standalone. --- SQLite/Core/ConnectionPool.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 77cf047e..3f4a01f4 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -23,7 +23,11 @@ // import Dispatch -import CSQLite +#if SQLITE_SWIFT_STANDALONE + import sqlite3 +#else + import CSQLite +#endif private let vfsName = "unix-excl" From e32db0905f244a46a4c61957e6c69bf856c7b155 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 22 Jun 2016 16:04:46 -0600 Subject: [PATCH 0395/1046] Shorten testConcurrentAccess2 time as it is making the travis tests take too long. --- SQLiteTests/ConnectionPoolTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index b7f15171..301f0482 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -62,10 +62,10 @@ class ConnectionPoolTests : SQLiteTestCase { } - for x in 10..<5000 { + for x in 10..<1000 { let name = "test" + String(x) - let idx = Int(rand()) % 5 + let idx = Int(rand()) % threadCount do { try conn.run("UPDATE test SET name=? WHERE id=?", name, idx) From 2fd69b87211c10428e75dbc6cdbce647ff866194 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Thu, 23 Jun 2016 08:59:13 -0600 Subject: [PATCH 0396/1046] Tests are still taking too long. --- SQLite/Core/ConnectionPool.swift | 4 ++-- SQLiteTests/ConnectionPoolTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 3f4a01f4..1e6a4b44 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -24,9 +24,9 @@ import Dispatch #if SQLITE_SWIFT_STANDALONE - import sqlite3 +import sqlite3 #else - import CSQLite +import CSQLite #endif diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 301f0482..190b70a0 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -62,7 +62,7 @@ class ConnectionPoolTests : SQLiteTestCase { } - for x in 10..<1000 { + for x in 10..<100 { let name = "test" + String(x) let idx = Int(rand()) % threadCount From 9bf4c06c184281c3c02c7118d5049d3b25364467 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Thu, 23 Jun 2016 14:09:03 -0600 Subject: [PATCH 0397/1046] Closes #457: Split up tests into more Travis jobs. --- .travis.yml | 13 ++++++++----- CocoaPodsTests/integration_test.rb | 6 ++++++ run-tests.sh | 7 +++++++ 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 run-tests.sh diff --git a/.travis.yml b/.travis.yml index ad0f18b9..b33f9af3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ language: objective-c -env: - - BUILD_SCHEME="SQLite iOS" - - BUILD_SCHEME="SQLite Mac" +matrix: + include: + - env: BUILD_SCHEME="SQLite iOS" + - env: BUILD_SCHEME="SQLite Mac" + - env: VALIDATOR_SUBSPEC="none" + - env: VALIDATOR_SUBSPEC="standard" + - env: VALIDATOR_SUBSPEC="standalone" before_install: - gem install xcpretty --no-document script: - - make test - - cd CocoaPodsTests && make test + - ./run-tests.sh osx_image: xcode7.3 diff --git a/CocoaPodsTests/integration_test.rb b/CocoaPodsTests/integration_test.rb index 325b8493..429a9c77 100755 --- a/CocoaPodsTests/integration_test.rb +++ b/CocoaPodsTests/integration_test.rb @@ -13,6 +13,7 @@ def test_validate_project def validator @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| + subspec = ENV["VALIDATOR_SUBSPEC"] validator.test_files = Dir["#{project_test_dir}/*.swift"] validator.config.verbose = true validator.no_clean = true @@ -20,6 +21,11 @@ def validator validator.fail_fast = true validator.local = true validator.allow_warnings = true + if subspec == "none" + validator.no_subspecs = true + else + validator.only_subspec = subspec + end end end diff --git a/run-tests.sh b/run-tests.sh new file mode 100644 index 00000000..244828bf --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -ev +if [ -n "$BUILD_SCHEME" ]; then + make test +elif [ -n "$VALIDATOR_SUBSPEC" ]; then + cd CocoaPodsTests && make test +fi \ No newline at end of file From 38dd7e52cc50beb3521f3a2c77a98632040f6d26 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Thu, 23 Jun 2016 14:23:04 -0600 Subject: [PATCH 0398/1046] Make run-tests.sh executable. --- run-tests.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 run-tests.sh diff --git a/run-tests.sh b/run-tests.sh old mode 100644 new mode 100755 From 6759aad915b5bfc39f9b6f733785690ab69ef5e0 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Thu, 23 Jun 2016 15:05:00 -0600 Subject: [PATCH 0399/1046] Run desired unit tests instead of only Mac. --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5a452b87..b6d844c6 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ BUILD_TOOL = xcodebuild -BUILD_SCHEME = SQLite Mac -BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" +ifeq ($(BUILD_SCHEME),SQLite iOS) + BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -sdk iphonesimulator +else + BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" +endif XCPRETTY := $(shell command -v xcpretty) SWIFTCOV := $(shell command -v swiftcov) From 0de26029a753713669775a21204fc18d30d3f845 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 10 Jul 2016 09:58:42 +0200 Subject: [PATCH 0400/1046] Makefile: re-add scheme default --- Makefile | 1 + run-tests.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b6d844c6..7257b9b1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ BUILD_TOOL = xcodebuild +BUILD_SCHEME = SQLite Mac ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -sdk iphonesimulator else diff --git a/run-tests.sh b/run-tests.sh index 244828bf..7a35fb02 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,7 +1,7 @@ #!/bin/bash set -ev if [ -n "$BUILD_SCHEME" ]; then - make test + make test BUILD_SCHEME="$BUILD_SCHEME" elif [ -n "$VALIDATOR_SUBSPEC" ]; then cd CocoaPodsTests && make test -fi \ No newline at end of file +fi From 403077f94f884dadc40f4f99deedb8cdaaba1ba0 Mon Sep 17 00:00:00 2001 From: tasanobu Date: Sat, 16 Jul 2016 11:52:17 +0900 Subject: [PATCH 0401/1046] Fix the error handling in Query.pluck() --- SQLite/Typed/Query.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index e45034ca..b81c3372 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -955,7 +955,10 @@ extension Connection { } public func pluck(query: QueryType) -> Row? { - return try! prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + guard let rows = try? prepare(query.limit(1, query.clauses.limit?.offset)) else { + return nil + } + return rows.generate().next() } /// Runs an `Insert` query. From a688d1841f3d88e49a122221c9de309b467965b7 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Mon, 18 Jul 2016 14:26:48 -0600 Subject: [PATCH 0402/1046] Clean up some spacing. --- SQLite/Core/Connection.swift | 8 ++-- SQLite/Core/ConnectionPool.swift | 26 +++++----- SQLite/Core/Dispatcher.swift | 82 +++++++++++++++++++------------- SQLite/Typed/Query.swift | 1 - 4 files changed, 66 insertions(+), 51 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 5071c7ae..f66fb064 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -50,14 +50,14 @@ public enum TransactionMode : String { public protocol Connection { /// Whether or not the database was opened in a read-only state. - var readonly : Bool { get } + var readonly: Bool { get } /// The last rowid inserted into the database via this connection. - var lastInsertRowid : Int64? { get } + var lastInsertRowid: Int64? { get } /// The last number of changes (inserts, updates, or deletes) made to the /// database via this connection. - var changes : Int { get } + var changes: Int { get } /// The total number of changes (inserts, updates, or deletes) made to the /// database via this connection. @@ -808,7 +808,7 @@ public final class DirectConnection : Connection, Equatable { throw error } - private var dispatcher : Dispatcher + private var dispatcher: Dispatcher } diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 1e6a4b44..035c706a 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -38,11 +38,11 @@ private let vfsName = "unix-excl" // WAL mode. public final class ConnectionPool { - private let location : DirectConnection.Location + private let location: DirectConnection.Location private var availableReadConnections = [DirectConnection]() private var unavailableReadConnections = [DirectConnection]() - private let lockQueue : dispatch_queue_t - private var writeConnection : DirectConnection! + private let lockQueue: dispatch_queue_t + private var writeConnection: DirectConnection! private let connectionSemaphore = dispatch_semaphore_create(5) public var foreignKeys : Bool { @@ -82,8 +82,8 @@ public final class ConnectionPool { // to the pool when it goes out of scope private class BorrowedConnection : Connection, Equatable { - let pool : ConnectionPool - let connection : DirectConnection + let pool: ConnectionPool + let connection: DirectConnection init(pool: ConnectionPool, connection: DirectConnection) { self.pool = pool @@ -100,10 +100,10 @@ public final class ConnectionPool { } } - var readonly : Bool { return connection.readonly } - var lastInsertRowid : Int64? { return connection.lastInsertRowid } - var changes : Int { return connection.changes } - var totalChanges : Int { return connection.totalChanges } + var readonly: Bool { return connection.readonly } + var lastInsertRowid: Int64? { return connection.lastInsertRowid } + var changes: Int { return connection.changes } + var totalChanges: Int { return connection.totalChanges } func execute(SQL: String) throws { return try connection.execute(SQL) } @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.prepare(statement, bindings) } @@ -131,7 +131,7 @@ public final class ConnectionPool { var writeConnectionInit = dispatch_once_t() - public var writable : DirectConnection { + public var writable: DirectConnection { dispatch_once(&writeConnectionInit) { @@ -153,9 +153,9 @@ public final class ConnectionPool { } // Acquires a read only connection to the database - public var readable : Connection { + public var readable: Connection { - var borrowed : BorrowedConnection! + var borrowed: BorrowedConnection! dispatch_semaphore_wait(connectionSemaphore, DISPATCH_TIME_FOREVER) dispatch_sync(lockQueue) { @@ -163,7 +163,7 @@ public final class ConnectionPool { // Ensure database is open self.writable - let connection : DirectConnection + let connection: DirectConnection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection diff --git a/SQLite/Core/Dispatcher.swift b/SQLite/Core/Dispatcher.swift index b5010488..f6cff005 100644 --- a/SQLite/Core/Dispatcher.swift +++ b/SQLite/Core/Dispatcher.swift @@ -1,9 +1,25 @@ // -// Dispatcher.swift -// SQLite +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. // -// Created by Kevin Wooten on 11/28/15. -// Copyright © 2015 stephencelis. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. // import Foundation @@ -11,45 +27,45 @@ import Foundation /// Block dispatch method public protocol Dispatcher { - - /// Dispatches the provided block - func dispatch(block: dispatch_block_t) - + + /// Dispatches the provided block + func dispatch(block: dispatch_block_t) + } /// Dispatches block immediately on current thread public final class ImmediateDispatcher : Dispatcher { - - public func dispatch(block: dispatch_block_t) { - block() - } - + + public func dispatch(block: dispatch_block_t) { + block() + } + } /// Synchronously dispatches block on a serial /// queue. Specifically allows reentrant calls public final class ReentrantDispatcher : Dispatcher { - - static let queueKey = unsafeBitCast(ReentrantDispatcher.self, UnsafePointer.self) - - let queue : dispatch_queue_t - - let queueContext : UnsafeMutablePointer! - - public init(_ name: String) { - queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL) - queueContext = unsafeBitCast(queue, UnsafeMutablePointer.self) - dispatch_queue_set_specific(queue, ReentrantDispatcher.queueKey, queueContext, nil) - } - - public func dispatch(block: dispatch_block_t) { - if dispatch_get_specific(ReentrantDispatcher.queueKey) == self.queueContext { - block() - } else { - dispatch_sync(self.queue, block) // FIXME: rdar://problem/21389236 + + static let queueKey = unsafeBitCast(ReentrantDispatcher.self, UnsafePointer.self) + + let queue : dispatch_queue_t + + let queueContext : UnsafeMutablePointer + + public init(_ name: String) { + queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL) + queueContext = unsafeBitCast(queue, UnsafeMutablePointer.self) + dispatch_queue_set_specific(queue, ReentrantDispatcher.queueKey, queueContext, nil) + } + + public func dispatch(block: dispatch_block_t) { + if dispatch_get_specific(ReentrantDispatcher.queueKey) == self.queueContext { + block() + } else { + dispatch_sync(self.queue, block) // FIXME: rdar://problem/21389236 + } } - } - + } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 9f966250..30a57337 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -1051,7 +1051,6 @@ public struct Row { } // FIXME: rdar://problem/18673897 // subscript… - public subscript(column: Expression) -> Blob { return get(column) } From a1121c42101483fd469f393eb63420e083e7cd60 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Mon, 18 Jul 2016 16:06:18 -0600 Subject: [PATCH 0403/1046] Change ConnectionPool API and get rid of Connection protocol. --- SQLite/Core/Connection.swift | 215 ++------------------------ SQLite/Core/ConnectionPool.swift | 114 +++++++------- SQLite/Core/Statement.swift | 4 +- SQLite/Extensions/FTS4.swift | 2 +- SQLite/Typed/CustomFunctions.swift | 2 +- SQLiteTests/ConnectionPoolTests.swift | 131 +++++++++++----- SQLiteTests/ConnectionTests.swift | 12 +- SQLiteTests/TestHelpers.swift | 2 +- 8 files changed, 168 insertions(+), 314 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index f66fb064..d594a4f5 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -46,193 +46,8 @@ public enum TransactionMode : String { } -/// Protocol to an SQLite connection -public protocol Connection { - - /// Whether or not the database was opened in a read-only state. - var readonly: Bool { get } - - /// The last rowid inserted into the database via this connection. - var lastInsertRowid: Int64? { get } - - /// The last number of changes (inserts, updates, or deletes) made to the - /// database via this connection. - var changes: Int { get } - - /// The total number of changes (inserts, updates, or deletes) made to the - /// database via this connection. - var totalChanges : Int { get } - - // MARK: - Execute - - /// Executes a batch of SQL statements. - /// - /// - Parameter SQL: A batch of zero or more semicolon-separated SQL - /// statements. - /// - /// - Throws: `Result.Error` if query execution fails. - func execute(SQL: String) throws - - // MARK: - Prepare - - /// Prepares a single SQL statement (with optional parameter bindings). - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A list of parameters to bind to the statement. - /// - /// - Returns: A prepared statement. - @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement - - /// Prepares a single SQL statement and binds parameters to it. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A list of parameters to bind to the statement. - /// - /// - Returns: A prepared statement. - @warn_unused_result func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement - - /// Prepares a single SQL statement and binds parameters to it. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A dictionary of named parameters to bind to the statement. - /// - /// - Returns: A prepared statement. - @warn_unused_result func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement - - // MARK: - Run - - /// Runs a single SQL statement (with optional parameter bindings). - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A list of parameters to bind to the statement. - /// - /// - Throws: `Result.Error` if query execution fails. - /// - /// - Returns: The statement. - func run(statement: String, _ bindings: Binding?...) throws -> Statement - - /// Prepares, binds, and runs a single SQL statement. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A list of parameters to bind to the statement. - /// - /// - Throws: `Result.Error` if query execution fails. - /// - /// - Returns: The statement. - func run(statement: String, _ bindings: [Binding?]) throws -> Statement - - /// Prepares, binds, and runs a single SQL statement. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A dictionary of named parameters to bind to the statement. - /// - /// - Throws: `Result.Error` if query execution fails. - /// - /// - Returns: The statement. - func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement - - // MARK: - Scalar - - /// Runs a single SQL statement (with optional parameter bindings), - /// returning the first value of the first row. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A list of parameters to bind to the statement. - /// - /// - Returns: The first value of the first row returned. - @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? - - /// Runs a single SQL statement (with optional parameter bindings), - /// returning the first value of the first row. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A list of parameters to bind to the statement. - /// - /// - Returns: The first value of the first row returned. - @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? - - /// Runs a single SQL statement (with optional parameter bindings), - /// returning the first value of the first row. - /// - /// - Parameters: - /// - /// - statement: A single SQL statement. - /// - /// - bindings: A dictionary of named parameters to bind to the statement. - /// - /// - Returns: The first value of the first row returned. - @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? - - // MARK: - Transactions - - // TODO: Consider not requiring a throw to roll back? - /// Runs a transaction with the given mode. - /// - /// - Note: Transactions cannot be nested. To nest transactions, see - /// `savepoint()`, instead. - /// - /// - Parameters: - /// - /// - mode: The mode in which a transaction acquires a lock. - /// - /// Default: `.Deferred` - /// - /// - block: A closure to run SQL statements within the transaction. - /// The transaction will be committed when the block returns. The block - /// must throw to roll the transaction back. - /// - /// - Throws: `Result.Error`, and rethrows. - func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws - - // TODO: Consider not requiring a throw to roll back? - // TODO: Consider removing ability to set a name? - /// Runs a transaction with the given savepoint name (if omitted, it will - /// generate a UUID). - /// - /// - SeeAlso: `transaction()`. - /// - /// - Parameters: - /// - /// - savepointName: A unique identifier for the savepoint (optional). - /// - /// - block: A closure to run SQL statements within the transaction. - /// The savepoint will be released (committed) when the block returns. - /// The block must throw to roll the savepoint back. - /// - /// - Throws: `SQLite.Result.Error`, and rethrows. - func savepoint(name: String, block: (Connection) throws -> Void) throws - - func sync(block: () throws -> T) rethrows -> T - -} - - /// A connection to SQLite. -public final class DirectConnection : Connection, Equatable { +public final class Connection : Equatable { /// The location of a SQLite database. public enum Location { @@ -273,24 +88,12 @@ public final class DirectConnection : Connection, Equatable { /// Default: `false`. /// /// - Returns: A new database connection. - public convenience init(_ location: Location = .InMemory, readonly: Bool = false, vfsName: String? = nil) throws { + public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection"), vfsName: vfsName) + try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection"), vfsName: nil) } - - /// Initializes a new SQLite connection. - /// - /// - Parameters: - /// - /// - location: The location of the database. Creates a new database if it - /// doesn’t already exist (unless in read-only mode). - /// - /// - flags: SQLite open flags - /// - /// - dispatcher: Dispatcher synchronization blocks - /// - /// - Returns: A new database connection. - public init(_ location: Location, flags: Int32, dispatcher: Dispatcher, vfsName: String? = nil) throws { + + init(_ location: Location, flags: Int32, dispatcher: Dispatcher, vfsName: String? = nil) throws { self.dispatcher = dispatcher if let vfsName = vfsName { try check(sqlite3_open_v2(location.description, &_handle, flags, vfsName)) @@ -812,7 +615,7 @@ public final class DirectConnection : Connection, Equatable { } -extension DirectConnection : CustomStringConvertible { +extension Connection : CustomStringConvertible { public var description: String { return String.fromCString(sqlite3_db_filename(handle, nil))! @@ -820,7 +623,7 @@ extension DirectConnection : CustomStringConvertible { } -extension DirectConnection.Location : CustomStringConvertible { +extension Connection.Location : CustomStringConvertible { public var description: String { switch self { @@ -835,7 +638,7 @@ extension DirectConnection.Location : CustomStringConvertible { } -public func ==(lhs: DirectConnection, rhs: DirectConnection) -> Bool { +public func == (lhs: Connection, rhs: Connection) -> Bool { return lhs === rhs } @@ -872,7 +675,7 @@ public enum Result : ErrorType { case Error(message: String, code: Int32, statement: Statement?) - init?(errorCode: Int32, connection: DirectConnection, statement: Statement? = nil) { + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String.fromCString(sqlite3_errmsg(connection.handle))! diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift index 035c706a..91c3e366 100644 --- a/SQLite/Core/ConnectionPool.swift +++ b/SQLite/Core/ConnectionPool.swift @@ -38,11 +38,11 @@ private let vfsName = "unix-excl" // WAL mode. public final class ConnectionPool { - private let location: DirectConnection.Location - private var availableReadConnections = [DirectConnection]() - private var unavailableReadConnections = [DirectConnection]() + private let location: Connection.Location + private var availableReadConnections = [Connection]() + private var unavailableReadConnections = [Connection]() private let lockQueue: dispatch_queue_t - private var writeConnection: DirectConnection! + private var writeConnection: Connection! private let connectionSemaphore = dispatch_semaphore_create(5) public var foreignKeys : Bool { @@ -64,12 +64,38 @@ public final class ConnectionPool { private var internalSetup = [InternalOption: ConnectionProcessor]() - public init(_ location: DirectConnection.Location) throws { + /// Initializes a new SQLite connection pool. + /// + /// - Parameters: + /// + /// - location: The location of the database. Creates a new database if it + /// doesn’t already exist. + /// + /// Default: `.InMemory`. + /// + /// - Throws: `Result.Error` iff a connection cannot be established. + /// + /// - Returns: A new connection pool. + public init(_ location: Connection.Location = .InMemory) throws { self.location = location self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) self.internalSetup[.WriteAheadLogging] = { try $0.execute("PRAGMA journal_mode = WAL;") } } + /// Initializes a new connection to a database. + /// + /// - Parameters: + /// + /// - filename: The location of the database. Creates a new database if + /// it doesn’t already exist (unless in read-only mode). + /// + /// - Throws: `Result.Error` iff a connection cannot be established. + /// + /// - Returns: A new database connection pool. + public convenience init(_ filename: String) throws { + try self.init(.URI(filename)) + } + public var totalReadableConnectionCount : Int { return availableReadConnections.count + unavailableReadConnections.count } @@ -78,65 +104,36 @@ public final class ConnectionPool { return availableReadConnections.count } - // Connection that automatically returns itself - // to the pool when it goes out of scope - private class BorrowedConnection : Connection, Equatable { - - let pool: ConnectionPool - let connection: DirectConnection - - init(pool: ConnectionPool, connection: DirectConnection) { - self.pool = pool - self.connection = connection - } + /// Calls `readBlock` with an available read connection from the connection pool, + /// after which the connection is made available again. + public func read(readBlock: (connection: Connection) -> Void) { + let connection = readable + readBlock(connection: connection) - deinit { - dispatch_sync(pool.lockQueue) { - if let index = self.pool.unavailableReadConnections.indexOf(self.connection) { - self.pool.unavailableReadConnections.removeAtIndex(index) - } - self.pool.availableReadConnections.append(self.connection) - dispatch_semaphore_signal(self.pool.connectionSemaphore) + dispatch_sync(lockQueue) { + if let index = self.unavailableReadConnections.indexOf(connection) { + self.unavailableReadConnections.removeAtIndex(index) } + self.availableReadConnections.append(connection) + dispatch_semaphore_signal(self.connectionSemaphore) } - - var readonly: Bool { return connection.readonly } - var lastInsertRowid: Int64? { return connection.lastInsertRowid } - var changes: Int { return connection.changes } - var totalChanges: Int { return connection.totalChanges } - - func execute(SQL: String) throws { return try connection.execute(SQL) } - @warn_unused_result func prepare(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.prepare(statement, bindings) } - @warn_unused_result func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.prepare(statement, bindings) } - @warn_unused_result func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.prepare(statement, bindings) } - - func run(statement: String, _ bindings: Binding?...) throws -> Statement { return try connection.run(statement, bindings) } - func run(statement: String, _ bindings: [Binding?]) throws -> Statement { return try connection.run(statement, bindings) } - func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try connection.run(statement, bindings) } - - @warn_unused_result func scalar(statement: String, _ bindings: Binding?...) -> Binding? { return connection.scalar(statement, bindings) } - @warn_unused_result func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { return connection.scalar(statement, bindings) } - @warn_unused_result func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { return connection.scalar(statement, bindings) } - - func transaction(mode: TransactionMode, block: (Connection) throws -> Void) throws { return try connection.transaction(mode, block: block) } - func savepoint(name: String, block: (Connection) throws -> Void) throws { return try connection.savepoint(name, block: block) } - - func sync(block: () throws -> T) rethrows -> T { return try connection.sync(block) } - func check(resultCode: Int32, statement: Statement? = nil) throws -> Int32 { return try connection.check(resultCode, statement: statement) } - } + /// Calls `readWriteBlock` with a writeable connection + public func readWrite(readWriteBlock: (connection: Connection) -> Void) { + let connection = writable + readWriteBlock(connection: connection) + } // Acquires a read/write connection to the database - var writeConnectionInit = dispatch_once_t() - public var writable: DirectConnection { + private var writable: Connection { dispatch_once(&writeConnectionInit) { let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - self.writeConnection = try! DirectConnection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) + self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) self.writeConnection.busyTimeout = 2 for setupProcessor in self.internalSetup.values { @@ -153,9 +150,9 @@ public final class ConnectionPool { } // Acquires a read only connection to the database - public var readable: Connection { + private var readable: Connection { - var borrowed: BorrowedConnection! + var borrowed: Connection! dispatch_semaphore_wait(connectionSemaphore, DISPATCH_TIME_FOREVER) dispatch_sync(lockQueue) { @@ -163,7 +160,7 @@ public final class ConnectionPool { // Ensure database is open self.writable - let connection: DirectConnection + let connection: Connection if let availableConnection = self.availableReadConnections.popLast() { connection = availableConnection @@ -172,7 +169,7 @@ public final class ConnectionPool { let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - connection = try! DirectConnection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) + connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) connection.busyTimeout = 2 for (type, setupProcessor) in self.internalSetup { @@ -190,15 +187,10 @@ public final class ConnectionPool { self.unavailableReadConnections.append(connection) - borrowed = BorrowedConnection(pool: self, connection: connection) + borrowed = connection } return borrowed } } - - -private func ==(lhs: ConnectionPool.BorrowedConnection, rhs: ConnectionPool.BorrowedConnection) -> Bool { - return lhs.connection == rhs.connection -} diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 876eab0f..9a4bfa1e 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -33,9 +33,9 @@ public final class Statement { private var handle: COpaquePointer = nil - private let connection: DirectConnection + private let connection: Connection - init(_ connection: DirectConnection, _ SQL: String) throws { + init(_ connection: Connection, _ SQL: String) throws { self.connection = connection try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index 7224cde9..466c42c7 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -138,7 +138,7 @@ extension Tokenizer : CustomStringConvertible { } -extension DirectConnection { +extension Connection { public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index d004f75e..068d0340 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public extension DirectConnection { +public extension Connection { /// Creates or redefines a custom SQL function. /// diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 190b70a0..14632713 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -7,54 +7,55 @@ class ConnectionPoolTests : SQLiteTestCase { override func setUp() { let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - pool = try! ConnectionPool(.URI("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite")) + pool = try! ConnectionPool("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") } func testConnectionSetupClosures() { pool.foreignKeys = true pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } + pool.read { conn in + XCTAssertTrue(conn.scalar("PRAGMA foreign_keys") as! Int64 == 1) + } + + pool.readWrite { conn in + try! conn.execute("INSERT INTO test(value) VALUES (1)") + try! conn.execute("SELECT value FROM test") + } - XCTAssertTrue(pool.readable.scalar("PRAGMA foreign_keys") as! Int64 == 1) - try! pool.writable.execute("INSERT INTO test(value) VALUES (1)") - try! pool.readable.execute("SELECT value FROM test") } func testConcurrentAccess2() { let threadCount = 20 - let conn = pool.writable - try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") - try! conn.execute("DELETE FROM test") - for threadNumber in 0.. 0, "Thread \(threadNumber) did not read.") print("ended at", reads, "reads") ex.fulfill() @@ -67,11 +68,13 @@ class ConnectionPoolTests : SQLiteTestCase { let name = "test" + String(x) let idx = Int(rand()) % threadCount - do { - try conn.run("UPDATE test SET name=? WHERE id=?", name, idx) - } - catch let error { - XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") + pool.readWrite { conn in + do { + try conn.run("UPDATE test SET name=? WHERE id=?", name, idx) + } + catch let error { + XCTFail((error as? CustomStringConvertible)?.description ?? "Unknown") + } } usleep(500) @@ -82,9 +85,10 @@ class ConnectionPoolTests : SQLiteTestCase { } func testConcurrentAccess() throws { - - try! pool.writable.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") - try! pool.writable.run("INSERT INTO test(value) VALUES(?)", 0) + pool.readWrite { conn in + try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try! conn.run("INSERT INTO test(value) VALUES(?)", 0) + } let q = dispatch_queue_create("Readers/Writers", DISPATCH_QUEUE_CONCURRENT); var finished = false @@ -94,10 +98,11 @@ class ConnectionPoolTests : SQLiteTestCase { dispatch_async(q) { while !finished { - - let val = self.pool.readable.scalar("SELECT value FROM test") + var val: Binding? + self.pool.read { conn in + val = conn.scalar("SELECT value FROM test") + } assert(val != nil, "DB query returned nil result set") - } } @@ -105,8 +110,9 @@ class ConnectionPoolTests : SQLiteTestCase { } for c in 0..<5000 { - - try pool.writable.run("INSERT INTO test(value) VALUES(?)", c) + pool.readWrite { conn in + try! conn.run("INSERT INTO test(value) VALUES(?)", c) + } usleep(100); @@ -120,10 +126,63 @@ class ConnectionPoolTests : SQLiteTestCase { } - func testAutoRelease() { + func testMultiplePools() { + let pool2 = try! ConnectionPool("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") + pool.readWrite { conn in + try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try! conn.run("INSERT INTO test(value) VALUES(?)", 0) + } + + let q = dispatch_queue_create("Readers/Writers", DISPATCH_QUEUE_CONCURRENT); + var finished = false - do { - try! pool.readable.execute("SELECT 1") + for _ in 0..<20 { + dispatch_async(q) { + while !finished { + var val: Binding? + self.pool.read { conn in + val = conn.scalar("SELECT value FROM test") + } + assert(val != nil, "DB query returned nil result set") + } + } + } + + for _ in 0..<20 { + dispatch_async(q) { + while !finished { + var val: Binding? + pool2.read { conn in + val = conn.scalar("SELECT value FROM test") + } + assert(val != nil, "DB query returned nil result set") + } + } + } + + for c in 0..<5000 { + pool.readWrite { conn in + try! conn.run("INSERT INTO test(value) VALUES(?)", c) + } + + pool2.readWrite { conn in + try! conn.run("INSERT INTO test(value) VALUES(?)", c + 5000) + } + + usleep(100); + } + + finished = true + + // Wait for readers to finish + dispatch_barrier_sync(q) { + } + + } + + func testAutoRelease() { + pool.read { conn in + try! conn.execute("SELECT 1") } XCTAssertEqual(pool.totalReadableConnectionCount, pool.availableReadableConnectionCount) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index bbad19a8..62203362 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -10,27 +10,27 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! DirectConnection(.InMemory) + let db = try! Connection(.InMemory) XCTAssertEqual("", db.description) } func test_init_returnsInMemoryByDefault() { - let db = try! DirectConnection() + let db = try! Connection() XCTAssertEqual("", db.description) } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! DirectConnection(.Temporary) + let db = try! Connection(.Temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! DirectConnection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + let db = try! Connection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } func test_init_withString_returnsURIConnection() { - let db = try! DirectConnection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } @@ -39,7 +39,7 @@ class ConnectionTests : SQLiteTestCase { } func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! DirectConnection(readonly: true) + let db = try! Connection(readonly: true) XCTAssertTrue(db.readonly) } diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 855bf7bd..464b9c27 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -5,7 +5,7 @@ class SQLiteTestCase : XCTestCase { var trace = [String: Int]() - let db = try! DirectConnection() + let db = try! Connection() let users = Table("users") From 476cdc550430cbda86626d50af1a47c8d678f138 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Mon, 18 Jul 2016 16:52:13 -0600 Subject: [PATCH 0404/1046] Make sure every thread has at least one read. --- SQLiteTests/ConnectionPoolTests.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 14632713..5a20485b 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -48,14 +48,13 @@ class ConnectionPoolTests : SQLiteTestCase { print("started", threadNumber) - while !quit { + while !quit || reads <= 0 { self.pool.read { conn in let _ = try! conn.prepare("SELECT name FROM test WHERE id = ?").scalar(threadNumber) as! String reads += 1 } } - XCTAssertTrue(reads > 0, "Thread \(threadNumber) did not read.") print("ended at", reads, "reads") ex.fulfill() From c7db9198cdd3afbb4801d0652588b8b5e7752f06 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Tue, 19 Jul 2016 12:45:51 -0600 Subject: [PATCH 0405/1046] Add connection pool documentation. --- Documentation/Index.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 0579e6f3..6ea09026 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -11,6 +11,7 @@ - [Read-Only Databases](#read-only-databases) - [In-Memory Databases](#in-memory-databases) - [Thread-Safety](#thread-safety) + - [Connection Pools](#connection-pools) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) - [Compound Expressions](#compound-expressions) @@ -251,7 +252,7 @@ Every Connection comes equipped with its own serial queue for statement executio If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: -```swift +``` swift db.busyTimeout = 5 db.busyHandler({ tries in @@ -265,6 +266,33 @@ db.busyHandler({ tries in > _Note:_ The default timeout is 0, so if you see `database is locked` errors, you may be trying to access the same database simultaneously from multiple connections. +### Connection Pools + +Connection pools use SQLite WAL mode to allow concurrent reads and writes, which can increase performance. Connection pools are created similar to connections: + +``` swift +let pool = try ConnectionPool("path/to/db.sqlite3") +``` + +Writes are done inside of a readWrite block: + +``` swift +pool.readWrite { connection in + try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) +} +``` + +Reads are done inside of a read block: + +``` swift +pool.read { connection in + for user in try db.prepare(users) { + print("id: \(user[id]), email: \(user[email]), name: \(user[name])") + } +} +``` + + ## Building Type-Safe SQL SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). From 66b61ddacc04576744decdafef6485f57f7a0d2e Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Tue, 19 Jul 2016 12:47:36 -0600 Subject: [PATCH 0406/1046] Use the right variable names. --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 6ea09026..a33000e8 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -278,7 +278,7 @@ Writes are done inside of a readWrite block: ``` swift pool.readWrite { connection in - try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) + try connection.run(users.insert(email <- "alice@mac.com", name <- "Alice")) } ``` @@ -286,7 +286,7 @@ Reads are done inside of a read block: ``` swift pool.read { connection in - for user in try db.prepare(users) { + for user in try connection.prepare(users) { print("id: \(user[id]), email: \(user[email]), name: \(user[name])") } } From 20fa6ca68de34d71668c0fe24101af3c5943005d Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Tue, 19 Jul 2016 15:50:14 -0600 Subject: [PATCH 0407/1046] Revert "Implement connection pool." --- Documentation/Index.md | 30 +--- SQLite.xcodeproj/project.pbxproj | 38 +---- SQLite/Core/Connection.swift | 84 +++++------ SQLite/Core/ConnectionPool.swift | 196 -------------------------- SQLite/Core/Dispatcher.swift | 71 ---------- SQLite/Typed/Query.swift | 1 + SQLiteTests/ConnectionPoolTests.swift | 190 ------------------------- SQLiteTests/ConnectionTests.swift | 26 ++-- 8 files changed, 58 insertions(+), 578 deletions(-) delete mode 100644 SQLite/Core/ConnectionPool.swift delete mode 100644 SQLite/Core/Dispatcher.swift delete mode 100644 SQLiteTests/ConnectionPoolTests.swift diff --git a/Documentation/Index.md b/Documentation/Index.md index a33000e8..0579e6f3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -11,7 +11,6 @@ - [Read-Only Databases](#read-only-databases) - [In-Memory Databases](#in-memory-databases) - [Thread-Safety](#thread-safety) - - [Connection Pools](#connection-pools) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) - [Compound Expressions](#compound-expressions) @@ -252,7 +251,7 @@ Every Connection comes equipped with its own serial queue for statement executio If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: -``` swift +```swift db.busyTimeout = 5 db.busyHandler({ tries in @@ -266,33 +265,6 @@ db.busyHandler({ tries in > _Note:_ The default timeout is 0, so if you see `database is locked` errors, you may be trying to access the same database simultaneously from multiple connections. -### Connection Pools - -Connection pools use SQLite WAL mode to allow concurrent reads and writes, which can increase performance. Connection pools are created similar to connections: - -``` swift -let pool = try ConnectionPool("path/to/db.sqlite3") -``` - -Writes are done inside of a readWrite block: - -``` swift -pool.readWrite { connection in - try connection.run(users.insert(email <- "alice@mac.com", name <- "Alice")) -} -``` - -Reads are done inside of a read block: - -``` swift -pool.read { connection in - for user in try connection.prepare(users) { - print("id: \(user[id]), email: \(user[email]), name: \(user[name])") - } -} -``` - - ## Building Type-Safe SQL SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ec4cdfad..88d74dc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,17 +46,6 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; - AA780B3D1CC201A700E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; - AA780B411CC202C800E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; - AA780B421CC202C900E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; - AA780B431CC202CA00E0E95E /* ConnectionPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */; }; - AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; - AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; - AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */; }; - AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA780B3C1CC201A700E0E95E /* Dispatcher.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; @@ -181,28 +170,25 @@ 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionPool.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - AA780B3C1CC201A700E0E95E /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = ""; }; - AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPoolTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247ADD1C3F04ED00AE3E12 /* SQLiteTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; - EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Connection.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; - EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FTS4.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; EE247AF61C3F06E900AE3E12 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; EE247AF71C3F06E900AE3E12 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; EE247AF81C3F06E900AE3E12 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; EE247AFB1C3F06E900AE3E12 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; - EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CustomFunctions.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; EE247AFE1C3F06E900AE3E12 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; EE247AFF1C3F06E900AE3E12 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; EE247B001C3F06E900AE3E12 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; @@ -212,7 +198,7 @@ EE247B181C3F134A00AE3E12 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; EE247B1B1C3F137700AE3E12 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ConnectionTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; EE247B201C3F137700AE3E12 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; @@ -412,7 +398,6 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( - AA780B3F1CC202B000E0E95E /* ConnectionPoolTests.swift */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -437,8 +422,6 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - AA780B3B1CC201A700E0E95E /* ConnectionPool.swift */, - AA780B3C1CC201A700E0E95E /* Dispatcher.swift */, EE91808D1C46E5230038162A /* SQLite-Bridging.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, @@ -809,11 +792,9 @@ 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, - AA780B471CC202F400E0E95E /* Dispatcher.swift in Sources */, 03A65E841C6BB2FB0062603F /* Query.swift in Sources */, 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */, 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, - AA780B461CC202F400E0E95E /* ConnectionPool.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, ); @@ -824,7 +805,6 @@ buildActionMask = 2147483647; files = ( 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, - AA780B431CC202CA00E0E95E /* ConnectionPoolTests.swift in Sources */, 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */, 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, @@ -847,8 +827,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA780B491CC202F500E0E95E /* Dispatcher.swift in Sources */, - AA780B481CC202F500E0E95E /* ConnectionPool.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -870,11 +848,9 @@ EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, - AA780B3E1CC201A700E0E95E /* Dispatcher.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, - AA780B3D1CC201A700E0E95E /* ConnectionPool.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, ); @@ -885,7 +861,6 @@ buildActionMask = 2147483647; files = ( EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, - AA780B411CC202C800E0E95E /* ConnectionPoolTests.swift in Sources */, EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */, @@ -922,11 +897,9 @@ EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, - AA780B451CC202F300E0E95E /* Dispatcher.swift in Sources */, EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */, EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */, EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, - AA780B441CC202F300E0E95E /* ConnectionPool.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, ); @@ -937,7 +910,6 @@ buildActionMask = 2147483647; files = ( EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, - AA780B421CC202C900E0E95E /* ConnectionPoolTests.swift in Sources */, EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */, diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index d594a4f5..866ed1bf 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -30,24 +30,8 @@ import sqlite3 import CSQLite #endif - -/// The mode in which a transaction acquires a lock. -public enum TransactionMode : String { - - /// Defers locking the database till the first read/write executes. - case Deferred = "DEFERRED" - - /// Immediately acquires a reserved lock on the database. - case Immediate = "IMMEDIATE" - - /// Immediately acquires an exclusive lock on all databases. - case Exclusive = "EXCLUSIVE" - -} - - /// A connection to SQLite. -public final class Connection : Equatable { +public final class Connection { /// The location of a SQLite database. public enum Location { @@ -88,20 +72,10 @@ public final class Connection : Equatable { /// Default: `false`. /// /// - Returns: A new database connection. - public convenience init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public init(_ location: Location = .InMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try self.init(location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.Connection"), vfsName: nil) - } - - init(_ location: Location, flags: Int32, dispatcher: Dispatcher, vfsName: String? = nil) throws { - self.dispatcher = dispatcher - if let vfsName = vfsName { - try check(sqlite3_open_v2(location.description, &_handle, flags, vfsName)) - } - else { - try check(sqlite3_open_v2(location.description, &_handle, flags, nil)) - } - try check(sqlite3_extended_result_codes(handle, 1)) + try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) } /// Initializes a new connection to a database. @@ -296,6 +270,20 @@ public final class Connection : Equatable { // MARK: - Transactions + /// The mode in which a transaction acquires a lock. + public enum TransactionMode : String { + + /// Defers locking the database till the first read/write executes. + case Deferred = "DEFERRED" + + /// Immediately acquires a reserved lock on the database. + case Immediate = "IMMEDIATE" + + /// Immediately acquires an exclusive lock on all databases. + case Exclusive = "EXCLUSIVE" + + } + // TODO: Consider not requiring a throw to roll back? /// Runs a transaction with the given mode. /// @@ -313,10 +301,10 @@ public final class Connection : Equatable { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(mode: TransactionMode = .Deferred, block: (Connection) throws -> Void) throws { + public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } - + // TODO: Consider not requiring a throw to roll back? // TODO: Consider removing ability to set a name? /// Runs a transaction with the given savepoint name (if omitted, it will @@ -333,18 +321,18 @@ public final class Connection : Equatable { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(name: String = NSUUID().UUIDString, block: (Connection) throws -> Void) throws { + public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { let name = name.quote("'") let savepoint = "SAVEPOINT \(name)" - + try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") } - - private func transaction(begin: String, _ block: (Connection) throws -> Void, _ commit: String, or rollback: String) throws { + + private func transaction(begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { return try sync { try self.run(begin) do { - try block(self) + try block() } catch { try self.run(rollback) throw error @@ -352,7 +340,7 @@ public final class Connection : Equatable { try self.run(commit) } } - + /// Interrupts any long-running queries. public func interrupt() { sqlite3_interrupt(handle) @@ -582,7 +570,7 @@ public final class Connection : Equatable { // MARK: - Error Handling - public func sync(block: () throws -> T) rethrows -> T { + func sync(block: () throws -> T) rethrows -> T { var success: T? var failure: ErrorType? @@ -594,7 +582,11 @@ public final class Connection : Equatable { } } - dispatcher.dispatch(box) + if dispatch_get_specific(Connection.queueKey) == queueContext { + box() + } else { + dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 + } if let failure = failure { try { () -> Void in throw failure }() @@ -610,8 +602,12 @@ public final class Connection : Equatable { throw error } - - private var dispatcher: Dispatcher + + private var queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) + + private static let queueKey = unsafeBitCast(Connection.self, UnsafePointer.self) + + private lazy var queueContext: UnsafeMutablePointer = unsafeBitCast(self, UnsafeMutablePointer.self) } @@ -638,10 +634,6 @@ extension Connection.Location : CustomStringConvertible { } -public func == (lhs: Connection, rhs: Connection) -> Bool { - return lhs === rhs -} - /// An SQL operation passed to update callbacks. public enum Operation { diff --git a/SQLite/Core/ConnectionPool.swift b/SQLite/Core/ConnectionPool.swift deleted file mode 100644 index 91c3e366..00000000 --- a/SQLite/Core/ConnectionPool.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Dispatch -#if SQLITE_SWIFT_STANDALONE -import sqlite3 -#else -import CSQLite -#endif - - -private let vfsName = "unix-excl" - - -// Connection pool for accessing an SQLite database -// with multiple readers & a single writer. Utilizes -// WAL mode. -public final class ConnectionPool { - - private let location: Connection.Location - private var availableReadConnections = [Connection]() - private var unavailableReadConnections = [Connection]() - private let lockQueue: dispatch_queue_t - private var writeConnection: Connection! - private let connectionSemaphore = dispatch_semaphore_create(5) - - public var foreignKeys : Bool { - get { - return internalSetup[.ForeignKeys] != nil - } - set { - internalSetup[.ForeignKeys] = newValue ? { try $0.execute("PRAGMA foreign_keys = ON;") } : nil - } - } - - public typealias ConnectionProcessor = Connection throws -> Void - public var setup = [ConnectionProcessor]() - - private enum InternalOption { - case WriteAheadLogging - case ForeignKeys - } - - private var internalSetup = [InternalOption: ConnectionProcessor]() - - /// Initializes a new SQLite connection pool. - /// - /// - Parameters: - /// - /// - location: The location of the database. Creates a new database if it - /// doesn’t already exist. - /// - /// Default: `.InMemory`. - /// - /// - Throws: `Result.Error` iff a connection cannot be established. - /// - /// - Returns: A new connection pool. - public init(_ location: Connection.Location = .InMemory) throws { - self.location = location - self.lockQueue = dispatch_queue_create("SQLite.ConnectionPool.Lock", DISPATCH_QUEUE_SERIAL) - self.internalSetup[.WriteAheadLogging] = { try $0.execute("PRAGMA journal_mode = WAL;") } - } - - /// Initializes a new connection to a database. - /// - /// - Parameters: - /// - /// - filename: The location of the database. Creates a new database if - /// it doesn’t already exist (unless in read-only mode). - /// - /// - Throws: `Result.Error` iff a connection cannot be established. - /// - /// - Returns: A new database connection pool. - public convenience init(_ filename: String) throws { - try self.init(.URI(filename)) - } - - public var totalReadableConnectionCount : Int { - return availableReadConnections.count + unavailableReadConnections.count - } - - public var availableReadableConnectionCount : Int { - return availableReadConnections.count - } - - /// Calls `readBlock` with an available read connection from the connection pool, - /// after which the connection is made available again. - public func read(readBlock: (connection: Connection) -> Void) { - let connection = readable - readBlock(connection: connection) - - dispatch_sync(lockQueue) { - if let index = self.unavailableReadConnections.indexOf(connection) { - self.unavailableReadConnections.removeAtIndex(index) - } - self.availableReadConnections.append(connection) - dispatch_semaphore_signal(self.connectionSemaphore) - } - } - - /// Calls `readWriteBlock` with a writeable connection - public func readWrite(readWriteBlock: (connection: Connection) -> Void) { - let connection = writable - readWriteBlock(connection: connection) - } - - // Acquires a read/write connection to the database - var writeConnectionInit = dispatch_once_t() - - private var writable: Connection { - - dispatch_once(&writeConnectionInit) { - - let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - self.writeConnection = try! Connection(self.location, flags: flags, dispatcher: ReentrantDispatcher("SQLite.ConnectionPool.Write"), vfsName: vfsName) - self.writeConnection.busyTimeout = 2 - - for setupProcessor in self.internalSetup.values { - try! setupProcessor(self.writeConnection) - } - - for setupProcessor in self.setup { - try! setupProcessor(self.writeConnection) - } - - } - - return writeConnection - } - - // Acquires a read only connection to the database - private var readable: Connection { - - var borrowed: Connection! - - dispatch_semaphore_wait(connectionSemaphore, DISPATCH_TIME_FOREVER) - dispatch_sync(lockQueue) { - - // Ensure database is open - self.writable - - let connection: Connection - - if let availableConnection = self.availableReadConnections.popLast() { - connection = availableConnection - } - else { - - let flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_WAL | SQLITE_OPEN_NOMUTEX - - connection = try! Connection(self.location, flags: flags, dispatcher: ImmediateDispatcher(), vfsName: vfsName) - connection.busyTimeout = 2 - - for (type, setupProcessor) in self.internalSetup { - if type == .WriteAheadLogging { - continue - } - try! setupProcessor(connection) - } - - for setupProcessor in self.setup { - try! setupProcessor(connection) - } - - } - - self.unavailableReadConnections.append(connection) - - borrowed = connection - } - - return borrowed - } - -} diff --git a/SQLite/Core/Dispatcher.swift b/SQLite/Core/Dispatcher.swift deleted file mode 100644 index f6cff005..00000000 --- a/SQLite/Core/Dispatcher.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Foundation - - -/// Block dispatch method -public protocol Dispatcher { - - /// Dispatches the provided block - func dispatch(block: dispatch_block_t) - -} - - -/// Dispatches block immediately on current thread -public final class ImmediateDispatcher : Dispatcher { - - public func dispatch(block: dispatch_block_t) { - block() - } - -} - - -/// Synchronously dispatches block on a serial -/// queue. Specifically allows reentrant calls -public final class ReentrantDispatcher : Dispatcher { - - static let queueKey = unsafeBitCast(ReentrantDispatcher.self, UnsafePointer.self) - - let queue : dispatch_queue_t - - let queueContext : UnsafeMutablePointer - - public init(_ name: String) { - queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL) - queueContext = unsafeBitCast(queue, UnsafeMutablePointer.self) - dispatch_queue_set_specific(queue, ReentrantDispatcher.queueKey, queueContext, nil) - } - - public func dispatch(block: dispatch_block_t) { - if dispatch_get_specific(ReentrantDispatcher.queueKey) == self.queueContext { - block() - } else { - dispatch_sync(self.queue, block) // FIXME: rdar://problem/21389236 - } - } - -} diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 30a57337..e45034ca 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -1051,6 +1051,7 @@ public struct Row { } // FIXME: rdar://problem/18673897 // subscript… + public subscript(column: Expression) -> Blob { return get(column) } diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift deleted file mode 100644 index 5a20485b..00000000 --- a/SQLiteTests/ConnectionPoolTests.swift +++ /dev/null @@ -1,190 +0,0 @@ -import XCTest -import SQLite - -class ConnectionPoolTests : SQLiteTestCase { - - var pool : ConnectionPool! - - override func setUp() { - let _ = try? NSFileManager.defaultManager().removeItemAtPath("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - pool = try! ConnectionPool("\(NSTemporaryDirectory())/SQLite.swift Pool Tests.sqlite") - } - - func testConnectionSetupClosures() { - - pool.foreignKeys = true - pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } - pool.read { conn in - XCTAssertTrue(conn.scalar("PRAGMA foreign_keys") as! Int64 == 1) - } - - pool.readWrite { conn in - try! conn.execute("INSERT INTO test(value) VALUES (1)") - try! conn.execute("SELECT value FROM test") - } - - } - - func testConcurrentAccess2() { - - let threadCount = 20 - pool.readWrite { conn in - try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(id INTEGER PRIMARY KEY, name TEXT);") - try! conn.execute("DELETE FROM test") - for threadNumber in 0.. Date: Tue, 19 Jul 2016 16:05:15 -0600 Subject: [PATCH 0408/1046] Add test for single connection across multiple threads. --- SQLiteTests/ConnectionTests.swift | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 62203362..4ec50c8a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -313,5 +313,26 @@ class ConnectionTests : SQLiteTestCase { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) AssertThrows(try stmt.run()) } + + func test_concurrent_access_single_connection() { + let conn = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Connection Tests.sqlite") + try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try! conn.run("INSERT INTO test(value) VALUES(?)", 0) + + let q = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT); + var finished = false + + for _ in 0..<100 { + dispatch_async(q) { + while !finished { + _ = try! conn.prepare("SELECT value FROM test") + } + } + } + + // Give the threads some time to conflict + sleep(5) + finished = true + } } From dd97747529cc8bc1940425a63fb240e7cd90b54e Mon Sep 17 00:00:00 2001 From: Will Richardson Date: Wed, 20 Jul 2016 10:11:45 +1200 Subject: [PATCH 0409/1046] Added try! to connection pool tests --- SQLiteTests/ConnectionPoolTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLiteTests/ConnectionPoolTests.swift b/SQLiteTests/ConnectionPoolTests.swift index 5a20485b..9c243379 100644 --- a/SQLiteTests/ConnectionPoolTests.swift +++ b/SQLiteTests/ConnectionPoolTests.swift @@ -15,7 +15,7 @@ class ConnectionPoolTests : SQLiteTestCase { pool.foreignKeys = true pool.setup.append { try $0.execute("CREATE TABLE IF NOT EXISTS test(value INT)") } pool.read { conn in - XCTAssertTrue(conn.scalar("PRAGMA foreign_keys") as! Int64 == 1) + XCTAssertTrue(try! conn.scalar("PRAGMA foreign_keys") as! Int64 == 1) } pool.readWrite { conn in @@ -99,7 +99,7 @@ class ConnectionPoolTests : SQLiteTestCase { while !finished { var val: Binding? self.pool.read { conn in - val = conn.scalar("SELECT value FROM test") + val = try! conn.scalar("SELECT value FROM test") } assert(val != nil, "DB query returned nil result set") } @@ -140,7 +140,7 @@ class ConnectionPoolTests : SQLiteTestCase { while !finished { var val: Binding? self.pool.read { conn in - val = conn.scalar("SELECT value FROM test") + val = try! conn.scalar("SELECT value FROM test") } assert(val != nil, "DB query returned nil result set") } @@ -152,7 +152,7 @@ class ConnectionPoolTests : SQLiteTestCase { while !finished { var val: Binding? pool2.read { conn in - val = conn.scalar("SELECT value FROM test") + val = try! conn.scalar("SELECT value FROM test") } assert(val != nil, "DB query returned nil result set") } From aec65643b92023bc38d8440ba2a5aa38e3f35056 Mon Sep 17 00:00:00 2001 From: tasanobu Date: Wed, 20 Jul 2016 12:06:50 +0900 Subject: [PATCH 0410/1046] Propagate error thrown in Query.pluck() --- SQLite/Typed/Query.swift | 7 ++----- SQLiteTests/QueryTests.swift | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index b81c3372..8eedf2d5 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -954,11 +954,8 @@ extension Connection { return V.fromDatatypeValue(value) } - public func pluck(query: QueryType) -> Row? { - guard let rows = try? prepare(query.limit(1, query.clauses.limit?.offset)) else { - return nil - } - return rows.generate().next() + public func pluck(query: QueryType) throws -> Row? { + return try prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() } /// Runs an `Insert` query. diff --git a/SQLiteTests/QueryTests.swift b/SQLiteTests/QueryTests.swift index b76da61e..e1ef1344 100644 --- a/SQLiteTests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -315,7 +315,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_pluck() { let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, db.pluck(users)![id]) + XCTAssertEqual(rowid, try! db.pluck(users)![id]) } func test_insert() { From ce1d0f1e5c463b63b5d92f97773429a29e117548 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 20 Jul 2016 10:45:25 -0600 Subject: [PATCH 0411/1046] Update test. --- SQLiteTests/ConnectionTests.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 4ec50c8a..a00ab388 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -320,19 +320,22 @@ class ConnectionTests : SQLiteTestCase { try! conn.run("INSERT INTO test(value) VALUES(?)", 0) let q = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT); - var finished = false - for _ in 0..<100 { + var reads = [0, 0, 0, 0, 0] + var finished = false + for index in 0..<5 { dispatch_async(q) { while !finished { _ = try! conn.prepare("SELECT value FROM test") + reads[index] += 1 } } } - // Give the threads some time to conflict - sleep(5) - finished = true + while !finished { + sleep(1) + finished = reads.reduce(true) { $0 && ($1 > 500) } + } } } From 72ee02cd8536c95a7e09604e2a85614a44efb386 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 20 Jul 2016 11:06:46 -0600 Subject: [PATCH 0412/1046] Whitespace change to retrigger build. --- SQLiteTests/ConnectionTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index a00ab388..c49e99e3 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -336,6 +336,6 @@ class ConnectionTests : SQLiteTestCase { sleep(1) finished = reads.reduce(true) { $0 && ($1 > 500) } } - } + } } From 68210d61ed95e8222a8dcef1c2ab3a079722827e Mon Sep 17 00:00:00 2001 From: David Potter Date: Thu, 28 Jul 2016 15:56:01 -0700 Subject: [PATCH 0413/1046] Allow the framework to be included in app extensions for tvOS --- SQLite.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 88d74dc4..b5179c09 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -952,6 +952,7 @@ 03A65E6B1C6BB0F60062603F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -972,6 +973,7 @@ 03A65E6C1C6BB0F60062603F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; From 9279fc6e5e927575af956557dd16477d0f0f7fa9 Mon Sep 17 00:00:00 2001 From: Tim Spears Date: Thu, 11 Aug 2016 12:24:48 -0600 Subject: [PATCH 0414/1046] Renamed R*Tree.swift to RTree.swift and R*TreeTests.swift to RTreeTests.swift. Made the necessary changes to the project.pbxproj file. Verified tests still pass. No new tests since no functionality is changed. --- SQLite.xcodeproj/project.pbxproj | 32 +++++++++---------- .../Extensions/{R*Tree.swift => RTree.swift} | 0 .../{R*TreeTests.swift => RTreeTests.swift} | 0 3 files changed, 16 insertions(+), 16 deletions(-) rename SQLite/Extensions/{R*Tree.swift => RTree.swift} (100%) rename SQLiteTests/{R*TreeTests.swift => RTreeTests.swift} (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 88d74dc4..ec5b5882 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -20,7 +20,7 @@ 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; - 03A65E7D1C6BB2F70062603F /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* R*Tree.swift */; }; + 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* RTree.swift */; }; 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; 03A65E7F1C6BB2FB0062603F /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; 03A65E801C6BB2FB0062603F /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; @@ -39,7 +39,7 @@ 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */; }; + 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; @@ -61,7 +61,7 @@ EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B081C3F06E900AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; - EE247B0A1C3F06E900AE3E12 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* R*Tree.swift */; }; + EE247B0A1C3F06E900AE3E12 /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* RTree.swift */; }; EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; EE247B0C1C3F06E900AE3E12 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; @@ -84,7 +84,7 @@ EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - EE247B301C3F141E00AE3E12 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */; }; + EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; @@ -98,7 +98,7 @@ EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - EE247B5C1C3F3FC700AE3E12 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */; }; + EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; @@ -114,7 +114,7 @@ EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; - EE247B6C1C3F3FEC00AE3E12 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* R*Tree.swift */; }; + EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* RTree.swift */; }; EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; EE247B6E1C3F3FEC00AE3E12 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; EE247B6F1C3F3FEC00AE3E12 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; @@ -182,7 +182,7 @@ EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; - EE247AF61C3F06E900AE3E12 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; + EE247AF61C3F06E900AE3E12 /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RTree.swift"; sourceTree = ""; }; EE247AF71C3F06E900AE3E12 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; EE247AF81C3F06E900AE3E12 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; @@ -205,7 +205,7 @@ EE247B211C3F137700AE3E12 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*TreeTests.swift"; sourceTree = ""; }; + EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RTreeTests.swift"; sourceTree = ""; }; EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; EE247B321C3F142E00AE3E12 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; EE247B331C3F142E00AE3E12 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; @@ -407,7 +407,7 @@ EE247B211C3F137700AE3E12 /* FTS4Tests.swift */, EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */, EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */, - EE247B2C1C3F141E00AE3E12 /* R*TreeTests.swift */, + EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */, EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */, EE247B181C3F134A00AE3E12 /* SetterTests.swift */, EE247B321C3F142E00AE3E12 /* StatementTests.swift */, @@ -437,7 +437,7 @@ isa = PBXGroup; children = ( EE247AF51C3F06E900AE3E12 /* FTS4.swift */, - EE247AF61C3F06E900AE3E12 /* R*Tree.swift */, + EE247AF61C3F06E900AE3E12 /* RTree.swift */, 19A1730E4390C775C25677D1 /* FTS5.swift */, ); path = Extensions; @@ -780,7 +780,7 @@ files = ( 03A65E801C6BB2FB0062603F /* CoreFunctions.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, - 03A65E7D1C6BB2F70062603F /* R*Tree.swift in Sources */, + 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, @@ -805,7 +805,7 @@ buildActionMask = 2147483647; files = ( 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, - 03A65E901C6BB3030062603F /* R*TreeTests.swift in Sources */, + 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */, 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */, @@ -835,7 +835,7 @@ buildActionMask = 2147483647; files = ( EE247B0F1C3F06E900AE3E12 /* CoreFunctions.swift in Sources */, - EE247B0A1C3F06E900AE3E12 /* R*Tree.swift in Sources */, + EE247B0A1C3F06E900AE3E12 /* RTree.swift in Sources */, EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */, EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */, EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */, @@ -868,7 +868,7 @@ EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */, EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */, EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */, - EE247B301C3F141E00AE3E12 /* R*TreeTests.swift in Sources */, + EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */, EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */, EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, @@ -885,7 +885,7 @@ files = ( EE247B6F1C3F3FEC00AE3E12 /* CoreFunctions.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, - EE247B6C1C3F3FEC00AE3E12 /* R*Tree.swift in Sources */, + EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */, EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, @@ -916,7 +916,7 @@ EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */, EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */, EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */, - EE247B5C1C3F3FC700AE3E12 /* R*TreeTests.swift in Sources */, + EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */, EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */, EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */, EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, diff --git a/SQLite/Extensions/R*Tree.swift b/SQLite/Extensions/RTree.swift similarity index 100% rename from SQLite/Extensions/R*Tree.swift rename to SQLite/Extensions/RTree.swift diff --git a/SQLiteTests/R*TreeTests.swift b/SQLiteTests/RTreeTests.swift similarity index 100% rename from SQLiteTests/R*TreeTests.swift rename to SQLiteTests/RTreeTests.swift From d5ede257ecba3281272e32232c4306641b2c3629 Mon Sep 17 00:00:00 2001 From: Daniel Alm Date: Mon, 9 May 2016 16:43:57 +0200 Subject: [PATCH 0415/1046] Fix transactions not being rolled back when the individual statements succeed, but the committing the transaction fails. --- SQLite/Core/Connection.swift | 2 +- SQLiteTests/ConnectionTests.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 04fdd901..62a2b812 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -333,11 +333,11 @@ public final class Connection { try self.run(begin) do { try block() + try self.run(commit) } catch { try self.run(rollback) throw error } - try self.run(commit) } } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 3d7dd3eb..eda4677a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -125,6 +125,33 @@ class ConnectionTests : SQLiteTestCase { AssertSQL("ROLLBACK TRANSACTION", 0) } + func test_transaction_rollsBackTransactionsIfCommitsFail() { + // This test case needs to emulate an environment where the individual statements succeed, but committing the + // transactuin fails. Using deferred foreign keys is one option to achieve this. + try! db.execute("PRAGMA foreign_keys = ON;") + try! db.execute("PRAGMA defer_foreign_keys = ON;") + let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) + + do { + try db.transaction { + try stmt.run() + } + } catch { + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") + AssertSQL("COMMIT TRANSACTION") + AssertSQL("ROLLBACK TRANSACTION") + + // Run another transaction to ensure that a subsequent transaction does not fail with an "cannot start a + // transaction within a transaction" error. + let stmt2 = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + try! db.transaction { + try stmt2.run() + } + } + func test_transaction_beginsAndRollsTransactionsBack() { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") From 39a065ce458ece86c815b9b7b3a30a51bb863ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Hast=C3=BCrk?= Date: Thu, 28 Jul 2016 11:53:24 +0300 Subject: [PATCH 0416/1046] remove --pre argument Cocoapods 1.0.1 is now stable so we dont need to pass --pre argument --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 850d3374..53d654fb 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ SQLite.swift with CocoaPods: ``` sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. - sudo gem install --pre cocoapods + [sudo] gem install cocoapods ``` 2. Update your Podfile to include the following: From 4ff05c6018435aecd238809ddb2f0e36d697002f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 21 Jul 2016 15:12:12 +0100 Subject: [PATCH 0417/1046] Run tests on Xcode8-beta --- .travis.yml | 7 +++++++ CocoaPods/iphoneos-10.0/module.modulemap | 4 ++++ .../iphonesimulator-10.0/module.modulemap | 4 ++++ CocoaPods/macosx-10.12/module.modulemap | 4 ++++ Makefile | 2 +- SQLite.xcodeproj/project.pbxproj | 18 ++++++++++++++++++ 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 CocoaPods/iphoneos-10.0/module.modulemap create mode 100644 CocoaPods/iphonesimulator-10.0/module.modulemap create mode 100644 CocoaPods/macosx-10.12/module.modulemap diff --git a/.travis.yml b/.travis.yml index b33f9af3..e4d9fe7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,13 @@ language: objective-c matrix: + include: + - os: osx + osx_image: xcode8 + env: BUILD_SCHEME="SQLite iOS" + - os: osx + osx_image: xcode8 + env: BUILD_SCHEME="SQLite Mac" - env: BUILD_SCHEME="SQLite iOS" - env: BUILD_SCHEME="SQLite Mac" - env: VALIDATOR_SUBSPEC="none" diff --git a/CocoaPods/iphoneos-10.0/module.modulemap b/CocoaPods/iphoneos-10.0/module.modulemap new file mode 100644 index 00000000..67a6c203 --- /dev/null +++ b/CocoaPods/iphoneos-10.0/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/iphonesimulator-10.0/module.modulemap b/CocoaPods/iphonesimulator-10.0/module.modulemap new file mode 100644 index 00000000..c8b84ab8 --- /dev/null +++ b/CocoaPods/iphonesimulator-10.0/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/macosx-10.12/module.modulemap b/CocoaPods/macosx-10.12/module.modulemap new file mode 100644 index 00000000..8fc958e6 --- /dev/null +++ b/CocoaPods/macosx-10.12/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/sqlite3.h" + export * +} diff --git a/Makefile b/Makefile index 7257b9b1..059a20b1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac ifeq ($(BUILD_SCHEME),SQLite iOS) - BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -sdk iphonesimulator + BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=iPhone 6" else BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" endif diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d431ff3c..59bce055 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -686,15 +686,19 @@ }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; EE247ADC1C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; EE247B441C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; }; }; @@ -1167,8 +1171,11 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/CocoaPods/iphoneos"; + "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; + "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -1189,7 +1196,10 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/CocoaPods/iphoneos"; + "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; + "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; + SWIFT_VERSION = 2.3; }; name = Release; }; @@ -1200,6 +1210,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -1210,6 +1221,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 2.3; }; name = Release; }; @@ -1234,6 +1246,8 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; + "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -1258,6 +1272,8 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; + "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; + SWIFT_VERSION = 2.3; }; name = Release; }; @@ -1272,6 +1288,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -1286,6 +1303,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 2.3; }; name = Release; }; From 4cdfd69bf405bf5eb91bc31203af3fd536097851 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 24 Aug 2016 12:12:51 +0100 Subject: [PATCH 0418/1046] Validate pod on xcode8 --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index e4d9fe7b..c40d8e39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,15 @@ matrix: - os: osx osx_image: xcode8 env: BUILD_SCHEME="SQLite Mac" + - os: osx + osx_image: xcode8 + env: VALIDATOR_SUBSPEC="none" + - os: osx + osx_image: xcode8 + env: VALIDATOR_SUBSPEC="standard" + - os: osx + osx_image: xcode8 + env: VALIDATOR_SUBSPEC="standalone" - env: BUILD_SCHEME="SQLite iOS" - env: BUILD_SCHEME="SQLite Mac" - env: VALIDATOR_SUBSPEC="none" From ed8980cf35b1756a1a4453378bb0bcebddc6890b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 24 Aug 2016 12:57:44 +0100 Subject: [PATCH 0419/1046] Run tests on iPhone 6 --- CocoaPodsTests/test_running_validator.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 10f4db61..ab0e12ed 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -7,6 +7,14 @@ class TestRunningValidator < Pod::Validator TEST_TARGET = 'Tests' attr_accessor :test_files + attr_accessor :iphone_simulator + attr_accessor :tvos_simulator + + def initialize(spec_or_path, source_urls) + super(spec_or_path, source_urls) + self.iphone_simulator = 'iPhone 6' + self.tvos_simulator = 'Apple TV 1080p' + end def create_app_project super.tap do @@ -67,12 +75,12 @@ def run_tests case consumer.platform_name when :ios command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination('iPhone 4s', deployment_target) + command += Fourflusher::SimControl.new.destination(iphone_simulator, deployment_target) when :osx command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks) when :tvos command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) - command += Fourflusher::SimControl.new.destination('Apple TV 1080p', deployment_target) + command += Fourflusher::SimControl.new.destination(tvos_simulator, deployment_target) else return # skip watchos end From 775a26c542cfe21026e9038460669fe6d3df66be Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 24 Aug 2016 13:17:27 +0100 Subject: [PATCH 0420/1046] =?UTF-8?q?Don=E2=80=99t=20hardcode=20simulator?= =?UTF-8?q?=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CocoaPodsTests/Gemfile | 2 +- CocoaPodsTests/Gemfile.lock | 39 +++++++++++++----------- CocoaPodsTests/test_running_validator.rb | 8 ++--- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/CocoaPodsTests/Gemfile b/CocoaPodsTests/Gemfile index 0a6af5c8..995a10e4 100644 --- a/CocoaPodsTests/Gemfile +++ b/CocoaPodsTests/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods' +gem 'cocoapods', '~> 1.1.0.beta1' gem 'minitest' diff --git a/CocoaPodsTests/Gemfile.lock b/CocoaPodsTests/Gemfile.lock index 7173e2c4..36f2a982 100644 --- a/CocoaPodsTests/Gemfile.lock +++ b/CocoaPodsTests/Gemfile.lock @@ -1,36 +1,37 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.6) + activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) claide (1.0.0) - cocoapods (1.0.0) - activesupport (>= 4.0.2) + cocoapods (1.1.0.beta.1) + activesupport (>= 4.0.2, < 5) claide (>= 1.0.0, < 2.0) - cocoapods-core (= 1.0.0) + cocoapods-core (= 1.1.0.beta.1) cocoapods-deintegrate (>= 1.0.0, < 2.0) - cocoapods-downloader (>= 1.0.0, < 2.0) + cocoapods-downloader (>= 1.1.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.0.0, < 2.0) - cocoapods-try (>= 1.0.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) colored (~> 1.2) escape (~> 0.0.4) - fourflusher (~> 0.3.0) - molinillo (~> 0.4.5) + fourflusher (~> 1.0.1) + gh_inspector (~> 1.0) + molinillo (~> 0.5.0) nap (~> 1.0) - xcodeproj (>= 1.0.0, < 2.0) - cocoapods-core (1.0.0) + xcodeproj (>= 1.2.0, < 2.0) + cocoapods-core (1.1.0.beta.1) activesupport (>= 4.0.2) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.0) - cocoapods-downloader (1.0.0) + cocoapods-downloader (1.1.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) @@ -38,21 +39,22 @@ GEM cocoapods-trunk (1.0.0) nap (>= 0.8, < 2.0) netrc (= 0.7.8) - cocoapods-try (1.0.0) + cocoapods-try (1.1.0) colored (1.2) escape (0.0.4) - fourflusher (0.3.0) + fourflusher (1.0.1) fuzzy_match (2.0.4) + gh_inspector (1.0.2) i18n (0.7.0) json (1.8.3) - minitest (5.8.4) - molinillo (0.4.5) + minitest (5.9.0) + molinillo (0.5.0) nap (1.1.0) netrc (0.7.8) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) - xcodeproj (1.0.0) + xcodeproj (1.2.0) activesupport (>= 3) claide (>= 1.0.0, < 2.0) colored (~> 1.2) @@ -61,5 +63,8 @@ PLATFORMS ruby DEPENDENCIES - cocoapods + cocoapods (~> 1.1.0.beta1) minitest + +BUNDLED WITH + 1.10.6 diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index ab0e12ed..e866d873 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -12,8 +12,8 @@ class TestRunningValidator < Pod::Validator def initialize(spec_or_path, source_urls) super(spec_or_path, source_urls) - self.iphone_simulator = 'iPhone 6' - self.tvos_simulator = 'Apple TV 1080p' + self.iphone_simulator = :oldest + self.tvos_simulator = :oldest end def create_app_project @@ -75,12 +75,12 @@ def run_tests case consumer.platform_name when :ios command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination(iphone_simulator, deployment_target) + command += Fourflusher::SimControl.new.destination(iphone_simulator, 'iOS', deployment_target) when :osx command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks) when :tvos command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) - command += Fourflusher::SimControl.new.destination(tvos_simulator, deployment_target) + command += Fourflusher::SimControl.new.destination(tvos_simulator, 'tvOS', deployment_target) else return # skip watchos end From 1cc0a535732cd5000a3d23ae703a25d6b58de8ef Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 13 Sep 2016 14:36:00 +0100 Subject: [PATCH 0421/1046] Bump cocoapods --- CocoaPodsTests/Gemfile | 2 +- CocoaPodsTests/Gemfile.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CocoaPodsTests/Gemfile b/CocoaPodsTests/Gemfile index 995a10e4..86c90369 100644 --- a/CocoaPodsTests/Gemfile +++ b/CocoaPodsTests/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.1.0.beta1' +gem 'cocoapods', '~> 1.1.0.rc.1' gem 'minitest' diff --git a/CocoaPodsTests/Gemfile.lock b/CocoaPodsTests/Gemfile.lock index 36f2a982..4252e155 100644 --- a/CocoaPodsTests/Gemfile.lock +++ b/CocoaPodsTests/Gemfile.lock @@ -8,12 +8,12 @@ GEM thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) claide (1.0.0) - cocoapods (1.1.0.beta.1) + cocoapods (1.1.0.rc.1) activesupport (>= 4.0.2, < 5) claide (>= 1.0.0, < 2.0) - cocoapods-core (= 1.1.0.beta.1) - cocoapods-deintegrate (>= 1.0.0, < 2.0) - cocoapods-downloader (>= 1.1.0, < 2.0) + cocoapods-core (= 1.1.0.rc.1) + cocoapods-deintegrate (>= 1.0.1, < 2.0) + cocoapods-downloader (>= 1.1.1, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) @@ -25,13 +25,13 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.5.0) nap (~> 1.0) - xcodeproj (>= 1.2.0, < 2.0) - cocoapods-core (1.1.0.beta.1) - activesupport (>= 4.0.2) + xcodeproj (>= 1.3.1, < 2.0) + cocoapods-core (1.1.0.rc.1) + activesupport (>= 4.0.2, < 5) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.0) - cocoapods-downloader (1.1.0) + cocoapods-deintegrate (1.0.1) + cocoapods-downloader (1.1.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) @@ -48,13 +48,13 @@ GEM i18n (0.7.0) json (1.8.3) minitest (5.9.0) - molinillo (0.5.0) + molinillo (0.5.1) nap (1.1.0) netrc (0.7.8) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) - xcodeproj (1.2.0) + xcodeproj (1.3.1) activesupport (>= 3) claide (>= 1.0.0, < 2.0) colored (~> 1.2) @@ -63,7 +63,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.1.0.beta1) + cocoapods (~> 1.1.0.rc.1) minitest BUNDLED WITH From 0aaf650f4f1530e6d97e39ed454619ce5e9c8aed Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 13 Sep 2016 15:27:18 +0100 Subject: [PATCH 0422/1046] Set Swift version to 2.3 --- CocoaPodsTests/test_running_validator.rb | 17 ++++++++++++++++- SQLite.xcodeproj/project.pbxproj | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index e866d873..0c21e90c 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -20,6 +20,8 @@ def create_app_project super.tap do project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj") create_test_target(project) + set_swift_version(project, '2.3') + project.save end end @@ -45,6 +47,14 @@ def build_pod end private + def set_swift_version(project, version) + project.targets.each do |target| + target.build_configuration_list.build_configurations.each do |configuration| + configuration.build_settings['SWIFT_VERSION'] = version + end + end + end + def create_test_target(project) test_target = project.new_target(:unit_test_bundle, TEST_TARGET, consumer.platform_name, deployment_target) group = project.new_group(TEST_TARGET) @@ -71,7 +81,12 @@ def add_test_target(pod_file) end def run_tests - command = %W(clean test -workspace #{APP_TARGET}.xcworkspace -scheme #{TEST_TARGET} -configuration Debug) + command = [ + 'clean', 'test', + '-workspace', File.join(validation_dir, "#{APP_TARGET}.xcworkspace"), + '-scheme', TEST_TARGET, + '-configuration', 'Debug' + ] case consumer.platform_name when :ios command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 59bce055..03d27cd8 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -970,6 +970,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; + SWIFT_VERSION = 2.3; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -991,6 +992,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; + SWIFT_VERSION = 2.3; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; From 2588c4440bc9aa36ba52189e5be0fceab46e641f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 13 Sep 2016 17:32:33 +0100 Subject: [PATCH 0423/1046] Xcode8 compat changes * Add modulemap for MacOSX 10.11 * Update modulemap * Use iPhone SE simulator to fix test failures (https://github.com/travis-ci/travis-ci/issues/6422) --- .travis.yml | 21 ++++++++++++++++----- CocoaPods/macosx-10.11/module.modulemap | 4 ++++ CocoaPods/macosx/module.modulemap | 2 +- CocoaPodsTests/integration_test.rb | 7 +++++-- CocoaPodsTests/test_running_validator.rb | 12 +++++++----- Makefile | 3 ++- SQLite.swift.podspec | 18 +++++++++++------- SQLite.xcodeproj/project.pbxproj | 6 ++++-- run-tests.sh | 6 +++++- 9 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 CocoaPods/macosx-10.11/module.modulemap diff --git a/.travis.yml b/.travis.yml index c40d8e39..7deffc04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,30 @@ language: objective-c +rvm: 2.2 matrix: - include: - os: osx osx_image: xcode8 - env: BUILD_SCHEME="SQLite iOS" + env: + - BUILD_SCHEME="SQLite iOS" + - IOS_SIMULATOR="iPhone SE" - os: osx osx_image: xcode8 env: BUILD_SCHEME="SQLite Mac" - os: osx osx_image: xcode8 - env: VALIDATOR_SUBSPEC="none" + env: + - VALIDATOR_SUBSPEC="none" + - IOS_SIMULATOR="iPhone SE" - os: osx osx_image: xcode8 - env: VALIDATOR_SUBSPEC="standard" + env: + - VALIDATOR_SUBSPEC="standard" + - IOS_SIMULATOR="iPhone SE" - os: osx osx_image: xcode8 - env: VALIDATOR_SUBSPEC="standalone" + env: + - VALIDATOR_SUBSPEC="standalone" + - IOS_SIMULATOR="iPhone SE" - env: BUILD_SCHEME="SQLite iOS" - env: BUILD_SCHEME="SQLite Mac" - env: VALIDATOR_SUBSPEC="none" @@ -26,4 +34,7 @@ before_install: - gem install xcpretty --no-document script: - ./run-tests.sh +after_failure: + - find $HOME/Library/Developer/Xcode/DerivedData/ -name '*.log' -print0 | xargs -0 cat + - cat /var/log/system.log osx_image: xcode7.3 diff --git a/CocoaPods/macosx-10.11/module.modulemap b/CocoaPods/macosx-10.11/module.modulemap new file mode 100644 index 00000000..9e091297 --- /dev/null +++ b/CocoaPods/macosx-10.11/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite [system] { + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" + export * +} diff --git a/CocoaPods/macosx/module.modulemap b/CocoaPods/macosx/module.modulemap index 9e091297..cc8370ec 100644 --- a/CocoaPods/macosx/module.modulemap +++ b/CocoaPods/macosx/module.modulemap @@ -1,4 +1,4 @@ module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sqlite3.h" export * } diff --git a/CocoaPodsTests/integration_test.rb b/CocoaPodsTests/integration_test.rb index 429a9c77..c62e066e 100755 --- a/CocoaPodsTests/integration_test.rb +++ b/CocoaPodsTests/integration_test.rb @@ -13,7 +13,6 @@ def test_validate_project def validator @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| - subspec = ENV["VALIDATOR_SUBSPEC"] validator.test_files = Dir["#{project_test_dir}/*.swift"] validator.config.verbose = true validator.no_clean = true @@ -21,11 +20,15 @@ def validator validator.fail_fast = true validator.local = true validator.allow_warnings = true - if subspec == "none" + subspec = ENV['VALIDATOR_SUBSPEC'] + if subspec == 'none' validator.no_subspecs = true else validator.only_subspec = subspec end + if ENV['IOS_SIMULATOR'] + validator.ios_simulator = ENV['IOS_SIMULATOR'] + end end end diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 0c21e90c..17fd88d7 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -7,12 +7,12 @@ class TestRunningValidator < Pod::Validator TEST_TARGET = 'Tests' attr_accessor :test_files - attr_accessor :iphone_simulator + attr_accessor :ios_simulator attr_accessor :tvos_simulator def initialize(spec_or_path, source_urls) super(spec_or_path, source_urls) - self.iphone_simulator = :oldest + self.ios_simulator = :oldest self.tvos_simulator = :oldest end @@ -61,7 +61,7 @@ def create_test_target(project) test_target.add_file_references(test_files.map { |file| group.new_file(file) }) project.save create_test_scheme(project, test_target) - project + test_target end def create_test_scheme(project, test_target) @@ -90,7 +90,7 @@ def run_tests case consumer.platform_name when :ios command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination(iphone_simulator, 'iOS', deployment_target) + command += Fourflusher::SimControl.new.destination(ios_simulator, 'iOS', deployment_target) when :osx command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks) when :tvos @@ -100,7 +100,8 @@ def run_tests return # skip watchos end - output, status = Dir.chdir(validation_dir) { _xcodebuild(command) } + output, status = _xcodebuild(command) + unless status.success? message = 'Returned an unsuccessful exit code.' if config.verbose? @@ -110,5 +111,6 @@ def run_tests end error('xcodebuild', message) end + output end end diff --git a/Makefile b/Makefile index 059a20b1..35edf493 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac +IOS_SIMULATOR = iPhone 6 ifeq ($(BUILD_SCHEME),SQLite iOS) - BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=iPhone 6" + BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR)" else BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" endif diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 6e5e25d5..b0fb7965 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -33,13 +33,17 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.preserve_paths = 'CocoaPods/**/*' ss.pod_target_xcconfig = { - 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', - 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', - 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', - 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', - 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', - 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', - 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' + 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', + 'SWIFT_INCLUDE_PATHS[sdk=macosx10.11]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.11', + 'SWIFT_INCLUDE_PATHS[sdk=macosx10.12]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.12', + 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', + 'SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos-10.0', + 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', + 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator-10.0', + 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', + 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', + 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', + 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' } end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 03d27cd8..f7450bd2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -182,7 +182,7 @@ EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; - EE247AF61C3F06E900AE3E12 /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RTree.swift"; sourceTree = ""; }; + EE247AF61C3F06E900AE3E12 /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; EE247AF71C3F06E900AE3E12 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; EE247AF81C3F06E900AE3E12 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; @@ -205,7 +205,7 @@ EE247B211C3F137700AE3E12 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RTreeTests.swift"; sourceTree = ""; }; + EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; EE247B321C3F142E00AE3E12 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; EE247B331C3F142E00AE3E12 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; @@ -1248,6 +1248,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; + "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; SWIFT_VERSION = 2.3; }; @@ -1274,6 +1275,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; + "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; SWIFT_VERSION = 2.3; }; diff --git a/run-tests.sh b/run-tests.sh index 7a35fb02..bc8cd773 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,7 +1,11 @@ #!/bin/bash set -ev if [ -n "$BUILD_SCHEME" ]; then - make test BUILD_SCHEME="$BUILD_SCHEME" + if [ -n "$IOS_SIMULATOR" ]; then + make test BUILD_SCHEME="$BUILD_SCHEME" IOS_SIMULATOR="$IOS_SIMULATOR" + else + make test BUILD_SCHEME="$BUILD_SCHEME" + fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then cd CocoaPodsTests && make test fi From b62880efe87ccb19eb2f44fc39212179c7b908da Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 14 Sep 2016 22:24:03 +0100 Subject: [PATCH 0424/1046] Update CocoaPods, update repo --- CocoaPodsTests/Gemfile | 2 +- CocoaPodsTests/Gemfile.lock | 10 +++++----- CocoaPodsTests/Makefile | 5 ++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CocoaPodsTests/Gemfile b/CocoaPodsTests/Gemfile index 86c90369..287dcbfc 100644 --- a/CocoaPodsTests/Gemfile +++ b/CocoaPodsTests/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.1.0.rc.1' +gem 'cocoapods', '~> 1.1.0.rc.2' gem 'minitest' diff --git a/CocoaPodsTests/Gemfile.lock b/CocoaPodsTests/Gemfile.lock index 4252e155..93a54bb9 100644 --- a/CocoaPodsTests/Gemfile.lock +++ b/CocoaPodsTests/Gemfile.lock @@ -8,10 +8,10 @@ GEM thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) claide (1.0.0) - cocoapods (1.1.0.rc.1) + cocoapods (1.1.0.rc.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.0, < 2.0) - cocoapods-core (= 1.1.0.rc.1) + cocoapods-core (= 1.1.0.rc.2) cocoapods-deintegrate (>= 1.0.1, < 2.0) cocoapods-downloader (>= 1.1.1, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -23,10 +23,10 @@ GEM escape (~> 0.0.4) fourflusher (~> 1.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.5.0) + molinillo (~> 0.5.1) nap (~> 1.0) xcodeproj (>= 1.3.1, < 2.0) - cocoapods-core (1.1.0.rc.1) + cocoapods-core (1.1.0.rc.2) activesupport (>= 4.0.2, < 5) fuzzy_match (~> 2.0.4) nap (~> 1.0) @@ -63,7 +63,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.1.0.rc.1) + cocoapods (~> 1.1.0.rc.2) minitest BUNDLED WITH diff --git a/CocoaPodsTests/Makefile b/CocoaPodsTests/Makefile index c9a3182e..26163fdb 100644 --- a/CocoaPodsTests/Makefile +++ b/CocoaPodsTests/Makefile @@ -1,9 +1,12 @@ -test: install +test: install repo_update @set -e; \ for test in *_test.rb; do \ bundle exec ./$$test; \ done +repo_update: + @bundle exec pod repo update --silent + install: @bundle install --path gems From 9fa5b908ae67e033a9bc7d498735c237b7c198b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 14 Sep 2016 23:11:26 +0100 Subject: [PATCH 0425/1046] Use recommended Xcode8 project settings --- SQLite.xcodeproj/project.pbxproj | 17 ++++++++++++++--- .../xcshareddata/xcschemes/SQLite Mac.xcscheme | 2 +- .../xcshareddata/xcschemes/SQLite iOS.xcscheme | 2 +- .../xcshareddata/xcschemes/SQLite tvOS.xcscheme | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index f7450bd2..a69730d4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -673,7 +673,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0800; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -957,6 +957,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -979,6 +980,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1026,6 +1028,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1047,6 +1050,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1076,8 +1080,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1126,8 +1132,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1149,6 +1157,7 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -1161,6 +1170,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1186,6 +1196,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1231,7 +1242,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1258,7 +1269,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index fa91858e..606b5a10 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 14 Sep 2016 22:00:53 +0100 Subject: [PATCH 0426/1046] travis: use xcode8 as default image --- .travis.yml | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7deffc04..d855fb3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,35 +1,36 @@ language: objective-c rvm: 2.2 +osx_image: xcode8 matrix: include: - - os: osx - osx_image: xcode8 - env: + - env: - BUILD_SCHEME="SQLite iOS" - IOS_SIMULATOR="iPhone SE" - - os: osx - osx_image: xcode8 - env: BUILD_SCHEME="SQLite Mac" - - os: osx - osx_image: xcode8 - env: + - env: BUILD_SCHEME="SQLite Mac" + - env: - VALIDATOR_SUBSPEC="none" - IOS_SIMULATOR="iPhone SE" - - os: osx - osx_image: xcode8 - env: + - env: - VALIDATOR_SUBSPEC="standard" - IOS_SIMULATOR="iPhone SE" - - os: osx - osx_image: xcode8 - env: + - env: - VALIDATOR_SUBSPEC="standalone" - IOS_SIMULATOR="iPhone SE" - - env: BUILD_SCHEME="SQLite iOS" - - env: BUILD_SCHEME="SQLite Mac" - - env: VALIDATOR_SUBSPEC="none" - - env: VALIDATOR_SUBSPEC="standard" - - env: VALIDATOR_SUBSPEC="standalone" + - os: osx + osx_image: xcode7.3 + env: BUILD_SCHEME="SQLite iOS" + - os: osx + osx_image: xcode7.3 + env: BUILD_SCHEME="SQLite Mac" + - os: osx + osx_image: xcode7.3 + env: VALIDATOR_SUBSPEC="none" + - os: osx + osx_image: xcode7.3 + env: VALIDATOR_SUBSPEC="standard" + - os: osx + osx_image: xcode7.3 + env: VALIDATOR_SUBSPEC="standalone" before_install: - gem install xcpretty --no-document script: @@ -37,4 +38,3 @@ script: after_failure: - find $HOME/Library/Developer/Xcode/DerivedData/ -name '*.log' -print0 | xargs -0 cat - cat /var/log/system.log -osx_image: xcode7.3 From 90e662e1e9e502b66d60d658ab076482d1251e3a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 16 Sep 2016 16:38:55 +0100 Subject: [PATCH 0427/1046] Add Swift version to xcconfig --- SQLite.swift.podspec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b0fb7965..89cffff1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -25,6 +25,9 @@ Pod::Spec.new do |s| s.osx.deployment_target = "10.9" s.watchos.deployment_target = "2.0" s.default_subspec = 'standard' + s.pod_target_xcconfig = { + 'SWIFT_VERSION' => '2.3', + } s.subspec 'standard' do |ss| ss.source_files = 'SQLite/**/*.{c,h,m,swift}' From 494f3c4e8810dc06e9c71b825cc14eb3571d2cc7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 16 Sep 2016 16:39:30 +0100 Subject: [PATCH 0428/1046] use Pod::Validator#add_swift_version --- .swift-version | 1 + CocoaPodsTests/test_running_validator.rb | 39 +++++++++++------------- 2 files changed, 18 insertions(+), 22 deletions(-) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 00000000..bb576dbd --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +2.3 diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 17fd88d7..1eefa4b8 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -17,19 +17,26 @@ def initialize(spec_or_path, source_urls) end def create_app_project - super.tap do - project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj") - create_test_target(project) - set_swift_version(project, '2.3') - project.save - end + super + project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj") + create_test_target(project) + project.save + end + + def add_app_project_import + super + project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj') + group = project.new_group(TEST_TARGET) + test_target = project.targets.last + test_target.add_file_references(test_files.map { |file| group.new_file(file) }) + add_swift_version(test_target) + project.save end def install_pod - super.tap do - if local? - FileUtils.ln_s file.dirname, validation_dir + "Pods/#{spec.name}" - end + super + if local? + FileUtils.ln_s file.dirname, validation_dir + "Pods/#{spec.name}" end end @@ -47,21 +54,9 @@ def build_pod end private - def set_swift_version(project, version) - project.targets.each do |target| - target.build_configuration_list.build_configurations.each do |configuration| - configuration.build_settings['SWIFT_VERSION'] = version - end - end - end - def create_test_target(project) test_target = project.new_target(:unit_test_bundle, TEST_TARGET, consumer.platform_name, deployment_target) - group = project.new_group(TEST_TARGET) - test_target.add_file_references(test_files.map { |file| group.new_file(file) }) - project.save create_test_scheme(project, test_target) - test_target end def create_test_scheme(project, test_target) From 8f9b8a855f9d473c8389d2927b4321217e5622cc Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Fri, 16 Sep 2016 17:45:29 +0800 Subject: [PATCH 0429/1046] migrating to swift 3 --- SQLite.xcodeproj/project.pbxproj | 20 +- SQLite/Core/Blob.swift | 10 +- SQLite/Core/Connection.swift | 198 ++++++++++--------- SQLite/Core/Statement.swift | 60 +++--- SQLite/Core/Value.swift | 14 +- SQLite/Extensions/FTS4.swift | 114 +++++------ SQLite/Extensions/FTS5.swift | 28 +-- SQLite/Extensions/RTree.swift | 4 +- SQLite/Foundation.swift | 46 ++--- SQLite/Helpers.swift | 32 +-- SQLite/Typed/AggregateFunctions.swift | 4 +- SQLite/Typed/Collation.swift | 16 +- SQLite/Typed/CoreFunctions.swift | 68 +++---- SQLite/Typed/CustomFunctions.swift | 32 +-- SQLite/Typed/Expression.swift | 4 +- SQLite/Typed/Operators.swift | 272 +++++++++++++------------- SQLite/Typed/Query.swift | 130 ++++++------ SQLite/Typed/Schema.swift | 164 ++++++++-------- SQLite/Typed/Setter.swift | 128 ++++++------ SQLiteTests/ConnectionTests.swift | 76 +++---- SQLiteTests/CoreFunctionsTests.swift | 18 +- SQLiteTests/FTS4Tests.swift | 26 +-- SQLiteTests/FTS5Tests.swift | 8 +- SQLiteTests/OperatorsTests.swift | 2 +- SQLiteTests/QueryTests.swift | 4 +- SQLiteTests/SchemaTests.swift | 106 +++++----- SQLiteTests/TestHelpers.swift | 30 +-- 27 files changed, 811 insertions(+), 803 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a69730d4..2f13a35f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -971,7 +971,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -994,7 +994,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1187,7 +1187,7 @@ "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -1212,7 +1212,7 @@ "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -1223,7 +1223,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -1234,7 +1234,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -1261,7 +1261,7 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -1288,7 +1288,7 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -1303,7 +1303,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -1318,7 +1318,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/SQLite/Core/Blob.swift b/SQLite/Core/Blob.swift index 1b30ffa1..45d6604a 100644 --- a/SQLite/Core/Blob.swift +++ b/SQLite/Core/Blob.swift @@ -30,16 +30,16 @@ public struct Blob { self.bytes = bytes } - public init(bytes: UnsafePointer, length: Int) { - self.init(bytes: [UInt8](UnsafeBufferPointer( - start: UnsafePointer(bytes), count: length - ))) + public init(bytes: UnsafeRawPointer, length: Int) { + // TODO correct count + let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length) + self.init(bytes: [UInt8](i8bufptr)) } public func toHex() -> String { return bytes.map { ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) - }.joinWithSeparator("") + }.joined(separator: "") } } diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 04fdd901..832910ba 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -39,24 +39,24 @@ public final class Connection { /// An in-memory database (equivalent to `.URI(":memory:")`). /// /// See: - case InMemory + case inMemory /// A temporary, file-backed database (equivalent to `.URI("")`). /// /// See: - case Temporary + case temporary /// A database located at the given URI filename (or path). /// /// See: /// /// - Parameter filename: A URI filename - case URI(String) + case uri(String) } - public var handle: COpaquePointer { return _handle } + public var handle: OpaquePointer { return _handle! } - private var _handle: COpaquePointer = nil + fileprivate var _handle: OpaquePointer? = nil /// Initializes a new SQLite connection. /// @@ -72,10 +72,10 @@ public final class Connection { /// Default: `false`. /// /// - Returns: A new database connection. - public init(_ location: Location = .InMemory, readonly: Bool = false) throws { + public init(_ location: Location = .inMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) - dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) + _ = try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + queue.setSpecific(key: /*Migrator FIXME: Use a variable of type DispatchSpecificKey*/ Connection.queueKey, value: queueContext) } /// Initializes a new connection to a database. @@ -93,7 +93,7 @@ public final class Connection { /// /// - Returns: A new database connection. public convenience init(_ filename: String, readonly: Bool = false) throws { - try self.init(.URI(filename), readonly: readonly) + try self.init(.uri(filename), readonly: readonly) } deinit { @@ -131,8 +131,8 @@ public final class Connection { /// statements. /// /// - Throws: `Result.Error` if query execution fails. - public func execute(SQL: String) throws { - try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) } + public func execute(_ SQL: String) throws { + _ = try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) } } // MARK: - Prepare @@ -146,7 +146,7 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: A prepared statement. - @warn_unused_result public func prepare(statement: String, _ bindings: Binding?...) throws -> Statement { + public func prepare(_ statement: String, _ bindings: Binding?...) throws -> Statement { if !bindings.isEmpty { return try prepare(statement, bindings) } return try Statement(self, statement) } @@ -160,7 +160,7 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: A prepared statement. - @warn_unused_result public func prepare(statement: String, _ bindings: [Binding?]) throws -> Statement { + public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try prepare(statement).bind(bindings) } @@ -173,7 +173,7 @@ public final class Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: A prepared statement. - @warn_unused_result public func prepare(statement: String, _ bindings: [String: Binding?]) throws -> Statement { + public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).bind(bindings) } @@ -190,7 +190,7 @@ public final class Connection { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement. - public func run(statement: String, _ bindings: Binding?...) throws -> Statement { + public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { return try run(statement, bindings) } @@ -205,7 +205,7 @@ public final class Connection { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement. - public func run(statement: String, _ bindings: [Binding?]) throws -> Statement { + public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } @@ -220,7 +220,7 @@ public final class Connection { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement. - public func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { + public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } @@ -236,7 +236,7 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) throws -> Binding? { + public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? { return try scalar(statement, bindings) } @@ -250,7 +250,7 @@ public final class Connection { /// - bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) throws -> Binding? { + public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? { return try prepare(statement).scalar(bindings) } @@ -264,7 +264,7 @@ public final class Connection { /// - bindings: A dictionary of named parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) throws -> Binding? { + public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? { return try prepare(statement).scalar(bindings) } @@ -301,7 +301,7 @@ public final class Connection { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { + public func transaction(_ mode: TransactionMode = .Deferred, block: @escaping () throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } @@ -321,23 +321,23 @@ public final class Connection { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { + public func savepoint(_ name: String = UUID().uuidString, block: @escaping () throws -> Void) throws { let name = name.quote("'") let savepoint = "SAVEPOINT \(name)" try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") } - private func transaction(begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { + fileprivate func transaction(_ begin: String, _ block: @escaping () throws -> Void, _ commit: String, or rollback: String) throws { return try sync { - try self.run(begin) + _ = try self.run(begin) do { try block() } catch { - try self.run(rollback) + _ = try self.run(rollback) throw error } - try self.run(commit) + _ = try self.run(commit) } } @@ -362,21 +362,21 @@ public final class Connection { /// busy error would otherwise be returned. It’s passed the number of /// times it’s been called for this lock. If it returns `true`, it will /// try again. If it returns `false`, no further attempts will be made. - public func busyHandler(callback: ((tries: Int) -> Bool)?) { + public func busyHandler(_ callback: ((_ tries: Int) -> Bool)?) { guard let callback = callback else { sqlite3_busy_handler(handle, nil, nil) busyHandler = nil return } - let box: BusyHandler = { callback(tries: Int($0)) ? 1 : 0 } + let box: BusyHandler = { callback(Int($0)) ? 1 : 0 } sqlite3_busy_handler(handle, { callback, tries in - unsafeBitCast(callback, BusyHandler.self)(tries) - }, unsafeBitCast(box, UnsafeMutablePointer.self)) + unsafeBitCast(callback, to: BusyHandler.self)(tries) + }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self)) busyHandler = box } - private typealias BusyHandler = @convention(block) Int32 -> Int32 - private var busyHandler: BusyHandler? + fileprivate typealias BusyHandler = @convention(block) (Int32) -> Int32 + fileprivate var busyHandler: BusyHandler? /// Sets a handler to call when a statement is executed with the compiled /// SQL. @@ -385,21 +385,21 @@ public final class Connection { /// with the compiled SQL as its argument. /// /// db.trace { SQL in print(SQL) } - public func trace(callback: (String -> Void)?) { + public func trace(_ callback: ((String) -> Void)?) { guard let callback = callback else { sqlite3_trace(handle, nil, nil) trace = nil return } - let box: Trace = { callback(String.fromCString($0)!) } + let box: Trace = { callback(String(cString: $0)) } sqlite3_trace(handle, { callback, SQL in - unsafeBitCast(callback, Trace.self)(SQL) - }, unsafeBitCast(box, UnsafeMutablePointer.self)) + unsafeBitCast(callback, to: Trace.self)(SQL!) + }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self)) trace = box } - private typealias Trace = @convention(block) UnsafePointer -> Void - private var trace: Trace? + fileprivate typealias Trace = @convention(block) (UnsafePointer) -> Void + fileprivate var trace: Trace? /// Registers a callback to be invoked whenever a row is inserted, updated, /// or deleted in a rowid table. @@ -407,7 +407,7 @@ public final class Connection { /// - Parameter callback: A callback invoked with the `Operation` (one of /// `.Insert`, `.Update`, or `.Delete`), database name, table name, and /// rowid. - public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { + public func updateHook(_ callback: ((_ operation: Operation, _ db: String, _ table: String, _ rowid: Int64) -> Void)?) { guard let callback = callback else { sqlite3_update_hook(handle, nil, nil) updateHook = nil @@ -416,26 +416,26 @@ public final class Connection { let box: UpdateHook = { callback( - operation: Operation(rawValue: $0), - db: String.fromCString($1)!, - table: String.fromCString($2)!, - rowid: $3 + Operation(rawValue: $0), + String(cString: $1), + String(cString: $2), + $3 ) } sqlite3_update_hook(handle, { callback, operation, db, table, rowid in - unsafeBitCast(callback, UpdateHook.self)(operation, db, table, rowid) - }, unsafeBitCast(box, UnsafeMutablePointer.self)) + unsafeBitCast(callback, to: UpdateHook.self)(operation, db!, table!, rowid) + }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self)) updateHook = box } - private typealias UpdateHook = @convention(block) (Int32, UnsafePointer, UnsafePointer, Int64) -> Void - private var updateHook: UpdateHook? + fileprivate typealias UpdateHook = @convention(block) (Int32, UnsafePointer, UnsafePointer, Int64) -> Void + fileprivate var updateHook: UpdateHook? /// Registers a callback to be invoked whenever a transaction is committed. /// /// - Parameter callback: A callback invoked whenever a transaction is /// committed. If this callback throws, the transaction will be rolled /// back. - public func commitHook(callback: (() throws -> Void)?) { + public func commitHook(_ callback: (() throws -> Void)?) { guard let callback = callback else { sqlite3_commit_hook(handle, nil, nil) commitHook = nil @@ -451,18 +451,18 @@ public final class Connection { return 0 } sqlite3_commit_hook(handle, { callback in - unsafeBitCast(callback, CommitHook.self)() - }, unsafeBitCast(box, UnsafeMutablePointer.self)) + unsafeBitCast(callback, to: CommitHook.self)() + }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self)) commitHook = box } - private typealias CommitHook = @convention(block) () -> Int32 - private var commitHook: CommitHook? + fileprivate typealias CommitHook = @convention(block) () -> Int32 + fileprivate var commitHook: CommitHook? /// Registers a callback to be invoked whenever a transaction rolls back. /// /// - Parameter callback: A callback invoked when a transaction is rolled /// back. - public func rollbackHook(callback: (() -> Void)?) { + public func rollbackHook(_ callback: (() -> Void)?) { guard let callback = callback else { sqlite3_rollback_hook(handle, nil, nil) rollbackHook = nil @@ -471,12 +471,12 @@ public final class Connection { let box: RollbackHook = { callback() } sqlite3_rollback_hook(handle, { callback in - unsafeBitCast(callback, RollbackHook.self)() - }, unsafeBitCast(box, UnsafeMutablePointer.self)) + unsafeBitCast(callback, to: RollbackHook.self)() + }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self)) rollbackHook = box } - private typealias RollbackHook = @convention(block) () -> Void - private var rollbackHook: RollbackHook? + fileprivate typealias RollbackHook = @convention(block) () -> Void + fileprivate var rollbackHook: RollbackHook? /// Creates or redefines a custom SQL function. /// @@ -497,11 +497,11 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - public func createFunction(function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { + public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 let box: Function = { context, argc, argv in let arguments: [Binding?] = (0...self), { context, argc, value in - unsafeBitCast(sqlite3_user_data(context), Function.self)(context, argc, value) + sqlite3_create_function_v2(handle, function, Int32(argc), flags, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in + let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) + function(context, argc, value) }, nil, nil, nil) if functions[function] == nil { self.functions[function] = [:] } functions[function]?[argc] = box } - private typealias Function = @convention(block) (COpaquePointer, Int32, UnsafeMutablePointer) -> Void - private var functions = [String: [Int: Function]]() + fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + fileprivate var functions = [String: [Int: Function]]() /// The return type of a collation comparison function. - public typealias ComparisonResult = NSComparisonResult + public typealias ComparisonResult = Foundation.ComparisonResult /// Defines a new collating sequence. /// @@ -556,23 +557,26 @@ public final class Connection { /// /// - block: A collation function that takes two strings and returns the /// comparison result. - public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) throws { + public func createCollation(_ collation: String, _ block: @escaping (_ lhs: String, _ rhs: String) -> ComparisonResult) throws { + // TODO correct capacity let box: Collation = { lhs, rhs in - Int32(block(lhs: String.fromCString(UnsafePointer(lhs))!, rhs: String.fromCString(UnsafePointer(rhs))!).rawValue) + let lstr = String(cString: lhs.bindMemory(to: UInt8.self, capacity: 0)) + let rstr = String(cString: rhs.bindMemory(to: UInt8.self, capacity: 0)) + return Int32(Int(block(lstr, rstr).rawValue)) } - try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in - unsafeBitCast(callback, Collation.self)(lhs, rhs) + _ = try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { callback, _, lhs, _, rhs in + unsafeBitCast(callback, to: Collation.self)(lhs!, rhs!) }, nil)) collations[collation] = box } - private typealias Collation = @convention(block) (UnsafePointer, UnsafePointer) -> Int32 - private var collations = [String: Collation]() + fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32 + fileprivate var collations = [String: Collation]() // MARK: - Error Handling - func sync(block: () throws -> T) rethrows -> T { + func sync(_ block: @escaping () throws -> T) rethrows -> T { var success: T? - var failure: ErrorType? + var failure: Error? let box: () -> Void = { do { @@ -582,10 +586,10 @@ public final class Connection { } } - if dispatch_get_specific(Connection.queueKey) == queueContext { + if DispatchQueue.getSpecific(key: Connection.queueKey) == queueContext { box() } else { - dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 + queue.sync(execute: box) // FIXME: rdar://problem/21389236 } if let failure = failure { @@ -595,7 +599,7 @@ public final class Connection { return success! } - func check(resultCode: Int32, statement: Statement? = nil) throws -> Int32 { + func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else { return resultCode } @@ -603,18 +607,18 @@ public final class Connection { throw error } - private var queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) + fileprivate var queue = DispatchQueue(label: "SQLite.Database", attributes: []) - private static let queueKey = unsafeBitCast(Connection.self, UnsafePointer.self) + fileprivate static let queueKey = DispatchSpecificKey() - private lazy var queueContext: UnsafeMutablePointer = unsafeBitCast(self, UnsafeMutablePointer.self) + fileprivate lazy var queueContext: Int = unsafeBitCast(self, to: Int.self) } extension Connection : CustomStringConvertible { public var description: String { - return String.fromCString(sqlite3_db_filename(handle, nil))! + return String(cString: sqlite3_db_filename(handle, nil)) } } @@ -623,11 +627,11 @@ extension Connection.Location : CustomStringConvertible { public var description: String { switch self { - case .InMemory: + case .inMemory: return ":memory:" - case .Temporary: + case .temporary: return "" - case .URI(let URI): + case .uri(let URI): return URI } } @@ -638,22 +642,22 @@ extension Connection.Location : CustomStringConvertible { public enum Operation { /// An INSERT operation. - case Insert + case insert /// An UPDATE operation. - case Update + case update /// A DELETE operation. - case Delete + case delete - private init(rawValue: Int32) { + fileprivate init(rawValue: Int32) { switch rawValue { case SQLITE_INSERT: - self = .Insert + self = .insert case SQLITE_UPDATE: - self = .Update + self = .update case SQLITE_DELETE: - self = .Delete + self = .delete default: fatalError("unhandled operation code: \(rawValue)") } @@ -661,17 +665,17 @@ public enum Operation { } -public enum Result : ErrorType { +public enum Result : Error { - private static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] - case Error(message: String, code: Int32, statement: Statement?) + case error(message: String, code: Int32, statement: Statement?) init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } - let message = String.fromCString(sqlite3_errmsg(connection.handle))! - self = Error(message: message, code: errorCode, statement: statement) + let message = String(cString: sqlite3_errmsg(connection.handle)) + self = .error(message: message, code: errorCode, statement: statement) } } @@ -680,7 +684,7 @@ extension Result : CustomStringConvertible { public var description: String { switch self { - case let .Error(message, _, statement): + case let .error(message, _, statement): guard let statement = statement else { return message } return "\(message) (\(statement))" diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 5e172b68..01720433 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -31,13 +31,13 @@ import CSQLite /// A single SQL statement. public final class Statement { - private var handle: COpaquePointer = nil + fileprivate var handle: OpaquePointer? = nil - private let connection: Connection + fileprivate let connection: Connection init(_ connection: Connection, _ SQL: String) throws { self.connection = connection - try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) + _ = try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } deinit { @@ -47,7 +47,7 @@ public final class Statement { public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) public lazy var columnNames: [String] = (0.. Statement { + public func bind(_ values: Binding?...) -> Statement { return bind(values) } @@ -67,7 +67,7 @@ public final class Statement { /// - Parameter values: A list of parameters to bind to the statement. /// /// - Returns: The statement object (useful for chaining). - public func bind(values: [Binding?]) -> Statement { + public func bind(_ values: [Binding?]) -> Statement { if values.isEmpty { return self } reset() guard values.count == Int(sqlite3_bind_parameter_count(handle)) else { @@ -83,7 +83,7 @@ public final class Statement { /// statement. /// /// - Returns: The statement object (useful for chaining). - public func bind(values: [String: Binding?]) -> Statement { + public func bind(_ values: [String: Binding?]) -> Statement { reset() for (name, value) in values { let idx = sqlite3_bind_parameter_index(handle, name) @@ -95,7 +95,7 @@ public final class Statement { return self } - private func bind(value: Binding?, atIndex idx: Int) { + fileprivate func bind(_ value: Binding?, atIndex idx: Int) { if value == nil { sqlite3_bind_null(handle, Int32(idx)) } else if let value = value as? Blob { @@ -120,7 +120,7 @@ public final class Statement { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement object (useful for chaining). - public func run(bindings: Binding?...) throws -> Statement { + public func run(_ bindings: Binding?...) throws -> Statement { guard bindings.isEmpty else { return try run(bindings) } @@ -135,7 +135,7 @@ public final class Statement { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement object (useful for chaining). - public func run(bindings: [Binding?]) throws -> Statement { + public func run(_ bindings: [Binding?]) throws -> Statement { return try bind(bindings).run() } @@ -145,27 +145,27 @@ public final class Statement { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement object (useful for chaining). - public func run(bindings: [String: Binding?]) throws -> Statement { + public func run(_ bindings: [String: Binding?]) throws -> Statement { return try bind(bindings).run() } /// - Parameter bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: Binding?...) throws -> Binding? { + public func scalar(_ bindings: Binding?...) throws -> Binding? { guard bindings.isEmpty else { return try scalar(bindings) } reset(clearBindings: false) - try step() + _ = try step() return row[0] } /// - Parameter bindings: A list of parameters to bind to the statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: [Binding?]) throws -> Binding? { + public func scalar(_ bindings: [Binding?]) throws -> Binding? { return try bind(bindings).scalar() } @@ -174,7 +174,7 @@ public final class Statement { /// statement. /// /// - Returns: The first value of the first row returned. - @warn_unused_result public func scalar(bindings: [String: Binding?]) throws -> Binding? { + public func scalar(_ bindings: [String: Binding?]) throws -> Binding? { return try bind(bindings).scalar() } @@ -182,23 +182,23 @@ public final class Statement { return try connection.sync { try self.connection.check(sqlite3_step(self.handle)) == SQLITE_ROW } } - private func reset(clearBindings shouldClear: Bool = true) { + fileprivate func reset(clearBindings shouldClear: Bool = true) { sqlite3_reset(handle) if (shouldClear) { sqlite3_clear_bindings(handle) } } } -extension Statement : SequenceType { +extension Statement : Sequence { - public func generate() -> Statement { + public func makeIterator() -> Statement { reset(clearBindings: false) return self } } -extension Statement : GeneratorType { +extension Statement : IteratorProtocol { public func next() -> [Binding?]? { return try! step() ? Array(row) : nil @@ -209,19 +209,19 @@ extension Statement : GeneratorType { extension Statement : CustomStringConvertible { public var description: String { - return String.fromCString(sqlite3_sql(handle))! + return String(cString: sqlite3_sql(handle)) } } public struct Cursor { - private let handle: COpaquePointer + fileprivate let handle: OpaquePointer - private let columnCount: Int + fileprivate let columnCount: Int - private init(_ statement: Statement) { - handle = statement.handle + fileprivate init(_ statement: Statement) { + handle = statement.handle! columnCount = statement.columnCount } @@ -234,13 +234,13 @@ public struct Cursor { } public subscript(idx: Int) -> String { - return String.fromCString(UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) ?? "" + return String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) } public subscript(idx: Int) -> Blob { let bytes = sqlite3_column_blob(handle, Int32(idx)) let length = Int(sqlite3_column_bytes(handle, Int32(idx))) - return Blob(bytes: bytes, length: length) + return Blob(bytes: bytes!, length: length) } // MARK: - @@ -256,7 +256,7 @@ public struct Cursor { } /// Cursors provide direct access to a statement’s current row. -extension Cursor : SequenceType { +extension Cursor : Sequence { public subscript(idx: Int) -> Binding? { switch sqlite3_column_type(handle, Int32(idx)) { @@ -275,11 +275,11 @@ extension Cursor : SequenceType { } } - public func generate() -> AnyGenerator { + public func makeIterator() -> AnyIterator { var idx = 0 - return AnyGenerator { + return AnyIterator { if idx >= self.columnCount { - return Optional.None + return Optional.none } else { idx += 1 return self[idx - 1] diff --git a/SQLite/Core/Value.swift b/SQLite/Core/Value.swift index 2cfb45ed..608f0ce6 100644 --- a/SQLite/Core/Value.swift +++ b/SQLite/Core/Value.swift @@ -39,7 +39,7 @@ public protocol Value : Expressible { // extensions cannot have inheritance clau static var declaredDatatype: String { get } - static func fromDatatypeValue(datatypeValue: Datatype) -> ValueType + static func fromDatatypeValue(_ datatypeValue: Datatype) -> ValueType var datatypeValue: Datatype { get } @@ -49,7 +49,7 @@ extension Double : Number, Value { public static let declaredDatatype = "REAL" - public static func fromDatatypeValue(datatypeValue: Double) -> Double { + public static func fromDatatypeValue(_ datatypeValue: Double) -> Double { return datatypeValue } @@ -63,7 +63,7 @@ extension Int64 : Number, Value { public static let declaredDatatype = "INTEGER" - public static func fromDatatypeValue(datatypeValue: Int64) -> Int64 { + public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int64 { return datatypeValue } @@ -77,7 +77,7 @@ extension String : Binding, Value { public static let declaredDatatype = "TEXT" - public static func fromDatatypeValue(datatypeValue: String) -> String { + public static func fromDatatypeValue(_ datatypeValue: String) -> String { return datatypeValue } @@ -91,7 +91,7 @@ extension Blob : Binding, Value { public static let declaredDatatype = "BLOB" - public static func fromDatatypeValue(datatypeValue: Blob) -> Blob { + public static func fromDatatypeValue(_ datatypeValue: Blob) -> Blob { return datatypeValue } @@ -107,7 +107,7 @@ extension Bool : Binding, Value { public static var declaredDatatype = Int64.declaredDatatype - public static func fromDatatypeValue(datatypeValue: Int64) -> Bool { + public static func fromDatatypeValue(_ datatypeValue: Int64) -> Bool { return datatypeValue != 0 } @@ -121,7 +121,7 @@ extension Int : Number, Value { public static var declaredDatatype = Int64.declaredDatatype - public static func fromDatatypeValue(datatypeValue: Int64) -> Int { + public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int { return Int(datatypeValue) } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index 466c42c7..52de47bf 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -24,15 +24,15 @@ extension Module { - @warn_unused_result public static func FTS4(column: Expressible, _ more: Expressible...) -> Module { + public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module { return FTS4([column] + more) } - @warn_unused_result public static func FTS4(columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { + public static func FTS4(_ columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { return FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) } - @warn_unused_result public static func FTS4(config: FTS4Config) -> Module { + public static func FTS4(_ config: FTS4Config) -> Module { return Module(name: "fts4", arguments: config.arguments()) } } @@ -51,15 +51,15 @@ extension VirtualTable { /// /// - Returns: An expression appended with a `MATCH` query against the given /// pattern. - @warn_unused_result public func match(pattern: String) -> Expression { + public func match(_ pattern: String) -> Expression { return "MATCH".infix(tableName(), pattern) } - @warn_unused_result public func match(pattern: Expression) -> Expression { + public func match(_ pattern: Expression) -> Expression { return "MATCH".infix(tableName(), pattern) } - @warn_unused_result public func match(pattern: Expression) -> Expression { + public func match(_ pattern: Expression) -> Expression { return "MATCH".infix(tableName(), pattern) } @@ -73,15 +73,15 @@ extension VirtualTable { /// - Parameter pattern: A pattern to match. /// /// - Returns: A query with the given `WHERE … MATCH` clause applied. - @warn_unused_result public func match(pattern: String) -> QueryType { + public func match(_ pattern: String) -> QueryType { return filter(match(pattern)) } - @warn_unused_result public func match(pattern: Expression) -> QueryType { + public func match(_ pattern: Expression) -> QueryType { return filter(match(pattern)) } - @warn_unused_result public func match(pattern: Expression) -> QueryType { + public func match(_ pattern: Expression) -> QueryType { return filter(match(pattern)) } @@ -93,7 +93,7 @@ public struct Tokenizer { public static let Porter = Tokenizer("porter") - @warn_unused_result public static func Unicode61(removeDiacritics removeDiacritics: Bool? = nil, tokenchars: Set = [], separators: Set = []) -> Tokenizer { + public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], separators: Set = []) -> Tokenizer { var arguments = [String]() if let removeDiacritics = removeDiacritics { @@ -101,19 +101,19 @@ public struct Tokenizer { } if !tokenchars.isEmpty { - let joined = tokenchars.map { String($0) }.joinWithSeparator("") + let joined = tokenchars.map { String($0) }.joined(separator: "") arguments.append("tokenchars=\(joined)".quote()) } if !separators.isEmpty { - let joined = separators.map { String($0) }.joinWithSeparator("") + let joined = separators.map { String($0) }.joined(separator: "") arguments.append("separators=\(joined)".quote()) } return Tokenizer("unicode61", arguments) } - @warn_unused_result public static func Custom(name: String) -> Tokenizer { + public static func Custom(_ name: String) -> Tokenizer { return Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) } @@ -121,34 +121,34 @@ public struct Tokenizer { public let arguments: [String] - private init(_ name: String, _ arguments: [String] = []) { + fileprivate init(_ name: String, _ arguments: [String] = []) { self.name = name self.arguments = arguments } - private static let moduleName = "SQLite.swift" + fileprivate static let moduleName = "SQLite.swift" } extension Tokenizer : CustomStringConvertible { public var description: String { - return ([name] + arguments).joinWithSeparator(" ") + return ([name] + arguments).joined(separator: " ") } } extension Connection { - public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { - try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in - let string = String.fromCString(input)! + public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range)?) throws { + _ = try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + let string = String(cString: input) guard let (token, range) = next(string) else { return nil } let view = string.utf8 - offset.memory += string.substringToIndex(range.startIndex).utf8.count - length.memory = Int32(range.startIndex.samePositionIn(view).distanceTo(range.endIndex.samePositionIn(view))) + offset.pointee += string.substring(to: range.lowerBound).utf8.count + length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.lowerBound.samePosition(in: view))) return token }) } @@ -157,7 +157,7 @@ extension Connection { /// Configuration options shared between the [FTS4](https://www.sqlite.org/fts3.html) and /// [FTS5](https://www.sqlite.org/fts5.html) extensions. -public class FTSConfig { +open class FTSConfig { public enum ColumnOption { /// [The notindexed= option](https://www.sqlite.org/fts3.html#section_6_5) case unindexed @@ -171,38 +171,38 @@ public class FTSConfig { var isContentless: Bool = false /// Adds a column definition - public func column(column: Expressible, _ options: [ColumnOption] = []) -> Self { + open func column(_ column: Expressible, _ options: [ColumnOption] = []) -> Self { self.columnDefinitions.append((column, options)) return self } - public func columns(columns: [Expressible]) -> Self { + open func columns(_ columns: [Expressible]) -> Self { for column in columns { - self.column(column) + _ = self.column(column) } return self } /// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer) - public func tokenizer(tokenizer: Tokenizer?) -> Self { + open func tokenizer(_ tokenizer: Tokenizer?) -> Self { self.tokenizer = tokenizer return self } /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) - public func prefix(prefix: [Int]) -> Self { + open func prefix(_ prefix: [Int]) -> Self { self.prefixes += prefix return self } /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) - public func externalContent(schema: SchemaType) -> Self { + open func externalContent(_ schema: SchemaType) -> Self { self.externalContentSchema = schema return self } /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) - public func contentless() -> Self { + open func contentless() -> Self { self.isContentless = true return self } @@ -217,15 +217,15 @@ public class FTSConfig { func options() -> Options { var options = Options() - options.append(formatColumnDefinitions()) + _ = options.append(formatColumnDefinitions()) if let tokenizer = tokenizer { - options.append("tokenize", value: Expression(literal: tokenizer.description)) + _ = options.append("tokenize", value: Expression(literal: tokenizer.description)) } - options.appendCommaSeparated("prefix", values:prefixes.sort().map { String($0) }) + _ = options.appendCommaSeparated("prefix", values:prefixes.sorted().map { String($0) }) if isContentless { - options.append("content", value: "") + _ = options.append("content", value: "") } else if let externalContentSchema = externalContentSchema { - options.append("content", value: externalContentSchema.tableName()) + _ = options.append("content", value: externalContentSchema.tableName()) } return options } @@ -233,28 +233,28 @@ public class FTSConfig { struct Options { var arguments = [Expressible]() - mutating func append(columns: [Expressible]) -> Options { - arguments.appendContentsOf(columns) + mutating func append(_ columns: [Expressible]) -> Options { + arguments.append(contentsOf: columns) return self } - mutating func appendCommaSeparated(key: String, values: [String]) -> Options { + mutating func appendCommaSeparated(_ key: String, values: [String]) -> Options { if values.isEmpty { return self } else { - return append(key, value: values.joinWithSeparator(",")) + return append(key, value: values.joined(separator: ",")) } } - mutating func append(key: String, value: CustomStringConvertible?) -> Options { + mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { return append(key, value: value?.description) } - mutating func append(key: String, value: String?) -> Options { + mutating func append(_ key: String, value: String?) -> Options { return append(key, value: value.map { Expression($0) }) } - mutating func append(key: String, value: Expressible?) -> Options { + mutating func append(_ key: String, value: Expressible?) -> Options { if let value = value { arguments.append("=".join([Expression(literal: key), value])) } @@ -264,10 +264,10 @@ public class FTSConfig { } /// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. -public class FTS4Config : FTSConfig { +open class FTS4Config : FTSConfig { /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) public enum MatchInfo : CustomStringConvertible { - case FTS3 + case fts3 public var description: String { return "fts3" } @@ -276,14 +276,14 @@ public class FTS4Config : FTSConfig { /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) public enum Order : CustomStringConvertible { /// Data structures are optimized for returning results in ascending order by docid (default) - case Asc + case asc /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid. - case Desc + case desc public var description: String { switch self { - case Asc: return "asc" - case Desc: return "desc" + case .asc: return "asc" + case .desc: return "desc" } } } @@ -298,31 +298,31 @@ public class FTS4Config : FTSConfig { } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) - public func compress(functionName: String) -> Self { + open func compress(_ functionName: String) -> Self { self.compressFunction = functionName return self } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) - public func uncompress(functionName: String) -> Self { + open func uncompress(_ functionName: String) -> Self { self.uncompressFunction = functionName return self } /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) - public func languageId(columnName: String) -> Self { + open func languageId(_ columnName: String) -> Self { self.languageId = columnName return self } /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - public func matchInfo(matchInfo: MatchInfo) -> Self { + open func matchInfo(_ matchInfo: MatchInfo) -> Self { self.matchInfo = matchInfo return self } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - public func order(order: Order) -> Self { + open func order(_ order: Order) -> Self { self.order = order return self } @@ -332,11 +332,11 @@ public class FTS4Config : FTSConfig { for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { options.append("notindexed", value: column) } - options.append("languageid", value: languageId) - options.append("compress", value: compressFunction) - options.append("uncompress", value: uncompressFunction) - options.append("matchinfo", value: matchInfo) - options.append("order", value: order) + _ = options.append("languageid", value: languageId) + _ = options.append("compress", value: compressFunction) + _ = options.append("uncompress", value: uncompressFunction) + _ = options.append("matchinfo", value: matchInfo) + _ = options.append("order", value: order) return options } } diff --git a/SQLite/Extensions/FTS5.swift b/SQLite/Extensions/FTS5.swift index 97056819..9204d5b2 100644 --- a/SQLite/Extensions/FTS5.swift +++ b/SQLite/Extensions/FTS5.swift @@ -23,7 +23,7 @@ // extension Module { - @warn_unused_result public static func FTS5(config: FTS5Config) -> Module { + public static func FTS5(_ config: FTS5Config) -> Module { return Module(name: "fts5", arguments: config.arguments()) } } @@ -32,20 +32,20 @@ extension Module { /// /// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version /// of SQLite. -public class FTS5Config : FTSConfig { +open class FTS5Config : FTSConfig { public enum Detail : CustomStringConvertible { /// store rowid, column number, term offset - case Full + case full /// store rowid, column number - case Column + case column /// store rowid - case None + case none public var description: String { switch self { - case Full: return "full" - case Column: return "column" - case None: return "none" + case .full: return "full" + case .column: return "column" + case .none: return "none" } } } @@ -58,30 +58,30 @@ public class FTS5Config : FTSConfig { } /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) - public func contentRowId(column: Expressible) -> Self { + open func contentRowId(_ column: Expressible) -> Self { self.contentRowId = column return self } /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) - public func columnSize(size: Int) -> Self { + open func columnSize(_ size: Int) -> Self { self.columnSize = size return self } /// [The Detail Option](https://www.sqlite.org/fts5.html#section_4_6) - public func detail(detail: Detail) -> Self { + open func detail(_ detail: Detail) -> Self { self.detail = detail return self } override func options() -> Options { var options = super.options() - options.append("content_rowid", value: contentRowId) + _ = options.append("content_rowid", value: contentRowId) if let columnSize = columnSize { - options.append("columnsize", value: Expression(value: columnSize)) + _ = options.append("columnsize", value: Expression(value: columnSize)) } - options.append("detail", value: detail) + _ = options.append("detail", value: detail) return options } diff --git a/SQLite/Extensions/RTree.swift b/SQLite/Extensions/RTree.swift index a5571ea6..4fc1a235 100644 --- a/SQLite/Extensions/RTree.swift +++ b/SQLite/Extensions/RTree.swift @@ -24,11 +24,11 @@ extension Module { - @warn_unused_result public static func RTree(primaryKey: Expression, _ pairs: (Expression, Expression)...) -> Module { + public static func RTree(_ primaryKey: Expression, _ pairs: (Expression, Expression)...) -> Module where T.Datatype == Int64, U.Datatype == Double { var arguments: [Expressible] = [primaryKey] for pair in pairs { - arguments.appendContentsOf([pair.0, pair.1] as [Expressible]) + arguments.append(contentsOf: [pair.0, pair.1] as [Expressible]) } return Module(name: "rtree", arguments: arguments) diff --git a/SQLite/Foundation.swift b/SQLite/Foundation.swift index 52b2ea02..5c266a05 100644 --- a/SQLite/Foundation.swift +++ b/SQLite/Foundation.swift @@ -24,34 +24,36 @@ import Foundation -extension NSData : Value { +extension Data : Value { - public class var declaredDatatype: String { + public static var declaredDatatype: String { return Blob.declaredDatatype } - public class func fromDatatypeValue(dataValue: Blob) -> NSData { - return NSData(bytes: dataValue.bytes, length: dataValue.bytes.count) + public static func fromDatatypeValue(_ dataValue: Blob) -> Data { + return Data(bytes: UnsafePointer(dataValue.bytes), count: dataValue.bytes.count) } public var datatypeValue: Blob { - return Blob(bytes: bytes, length: length) + return withUnsafeBytes { ptr -> Blob in + return Blob(bytes: ptr, length: count) + } } } -extension NSDate : Value { +extension Date : Value { - public class var declaredDatatype: String { + public static var declaredDatatype: String { return String.declaredDatatype } - public class func fromDatatypeValue(stringValue: String) -> NSDate { - return dateFormatter.dateFromString(stringValue)! + public static func fromDatatypeValue(_ stringValue: String) -> Date { + return dateFormatter.date(from: stringValue)! } public var datatypeValue: String { - return dateFormatter.stringFromDate(self) + return dateFormatter.string(from: self) } } @@ -59,11 +61,11 @@ extension NSDate : Value { /// A global date formatter used to serialize and deserialize `NSDate` objects. /// If multiple date formats are used in an application’s database(s), use a /// custom `Value` type per additional format. -public var dateFormatter: NSDateFormatter = { - let formatter = NSDateFormatter() +public var dateFormatter: DateFormatter = { + let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" - formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") - formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() @@ -71,17 +73,17 @@ public var dateFormatter: NSDateFormatter = { extension QueryType { - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } @@ -89,17 +91,17 @@ extension QueryType { extension Row { - public subscript(column: Expression) -> NSData { + public subscript(column: Expression) -> Data { return get(column) } - public subscript(column: Expression) -> NSData? { + public subscript(column: Expression) -> Data? { return get(column) } - public subscript(column: Expression) -> NSDate { + public subscript(column: Expression) -> Date { return get(column) } - public subscript(column: Expression) -> NSDate? { + public subscript(column: Expression) -> Date? { return get(column) } diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index cc6da27c..e9a17ce6 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -47,28 +47,28 @@ extension Optional : _OptionalType { } // let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self) -let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self) +let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) extension String { - @warn_unused_result func quote(mark: Character = "\"") -> String { + func quote(_ mark: Character = "\"") -> String { let escaped = characters.reduce("") { string, character in string + (character == mark ? "\(mark)\(mark)" : "\(character)") } return "\(mark)\(escaped)\(mark)" } - @warn_unused_result func join(expressions: [Expressible]) -> Expressible { + func join(_ expressions: [Expressible]) -> Expressible { var (template, bindings) = ([String](), [Binding?]()) for expressible in expressions { let expression = expressible.expression template.append(expression.template) - bindings.appendContentsOf(expression.bindings) + bindings.append(contentsOf: expression.bindings) } - return Expression(template.joinWithSeparator(self), bindings) + return Expression(template.joined(separator: self), bindings) } - @warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { let expression = Expression(" \(self) ".join([lhs, rhs]).expression) guard wrap else { return expression @@ -76,37 +76,37 @@ extension String { return "".wrap(expression) } - @warn_unused_result func prefix(expressions: Expressible) -> Expressible { + func prefix(_ expressions: Expressible) -> Expressible { return "\(self) ".wrap(expressions) as Expression } - @warn_unused_result func prefix(expressions: [Expressible]) -> Expressible { + func prefix(_ expressions: [Expressible]) -> Expressible { return "\(self) ".wrap(expressions) as Expression } - @warn_unused_result func wrap(expression: Expressible) -> Expression { + func wrap(_ expression: Expressible) -> Expression { return Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) } - @warn_unused_result func wrap(expressions: [Expressible]) -> Expression { + func wrap(_ expressions: [Expressible]) -> Expression { return wrap(", ".join(expressions)) } } -@warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = #function) -> Expression { +func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = #function) -> Expression { return function.infix(lhs, rhs, wrap: wrap) } -@warn_unused_result func wrap(expression: Expressible, function: String = #function) -> Expression { +func wrap(_ expression: Expressible, function: String = #function) -> Expression { return function.wrap(expression) } -@warn_unused_result func wrap(expressions: [Expressible], function: String = #function) -> Expression { +func wrap(_ expressions: [Expressible], function: String = #function) -> Expression { return function.wrap(", ".join(expressions)) } -@warn_unused_result func transcode(literal: Binding?) -> String { +func transcode(_ literal: Binding?) -> String { guard let literal = literal else { return "NULL" } switch literal { @@ -119,10 +119,10 @@ extension String { } } -@warn_unused_result func value(v: Binding) -> A { +func value(_ v: Binding) -> A { return A.fromDatatypeValue(v as! A.Datatype) as! A } -@warn_unused_result func value(v: Binding?) -> A { +func value(_ v: Binding?) -> A { return value(v!) } diff --git a/SQLite/Typed/AggregateFunctions.swift b/SQLite/Typed/AggregateFunctions.swift index 5775e0fe..249bbe60 100644 --- a/SQLite/Typed/AggregateFunctions.swift +++ b/SQLite/Typed/AggregateFunctions.swift @@ -232,7 +232,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { - @warn_unused_result static func count(star: Star) -> Expression { + static func count(_ star: Star) -> Expression { return wrap(star(nil, nil)) } @@ -246,6 +246,6 @@ extension ExpressionType where UnderlyingType == Int { /// /// - Returns: An expression returning `count(*)` (when called with the `*` /// function literal). -@warn_unused_result public func count(star: Star) -> Expression { +public func count(_ star: Star) -> Expression { return Expression.count(star) } diff --git a/SQLite/Typed/Collation.swift b/SQLite/Typed/Collation.swift index 5a632055..e2ff9d10 100644 --- a/SQLite/Typed/Collation.swift +++ b/SQLite/Typed/Collation.swift @@ -28,18 +28,18 @@ public enum Collation { /// Compares string by raw data. - case Binary + case binary /// Like binary, but folds uppercase ASCII letters into their lowercase /// equivalents. - case Nocase + case nocase /// Like binary, but strips trailing space. - case Rtrim + case rtrim /// A custom collating sequence identified by the given string, registered /// using `Database.create(collation:…)` - case Custom(String) + case custom(String) } @@ -55,13 +55,13 @@ extension Collation : CustomStringConvertible { public var description : String { switch self { - case Binary: + case .binary: return "BINARY" - case Nocase: + case .nocase: return "NOCASE" - case Rtrim: + case .rtrim: return "RTRIM" - case Custom(let collation): + case .custom(let collation): return collation.quote() } } diff --git a/SQLite/Typed/CoreFunctions.swift b/SQLite/Typed/CoreFunctions.swift index b597f3ba..9d17a326 100644 --- a/SQLite/Typed/CoreFunctions.swift +++ b/SQLite/Typed/CoreFunctions.swift @@ -66,7 +66,7 @@ extension ExpressionType where UnderlyingType == Double { /// // round("salary", 2) /// /// - Returns: A copy of the expression wrapped with the `round` function. - @warn_unused_result public func round(precision: Int? = nil) -> Expression { + public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { return wrap([self]) } @@ -86,7 +86,7 @@ extension ExpressionType where UnderlyingType == Double? { /// // round("salary", 2) /// /// - Returns: A copy of the expression wrapped with the `round` function. - @warn_unused_result public func round(precision: Int? = nil) -> Expression { + public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { return wrap(self) } @@ -103,13 +103,13 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype = /// // random() /// /// - Returns: An expression calling the `random` function. - @warn_unused_result public static func random() -> Expression { + public static func random() -> Expression { return "random".wrap([]) } } -extension ExpressionType where UnderlyingType == NSData { +extension ExpressionType where UnderlyingType == Data { /// Builds an expression representing the `randomblob` function. /// @@ -119,7 +119,7 @@ extension ExpressionType where UnderlyingType == NSData { /// - Parameter length: Length in bytes. /// /// - Returns: An expression calling the `randomblob` function. - @warn_unused_result public static func random(length: Int) -> Expression { + public static func random(_ length: Int) -> Expression { return "randomblob".wrap([]) } @@ -131,7 +131,7 @@ extension ExpressionType where UnderlyingType == NSData { /// - Parameter length: Length in bytes. /// /// - Returns: An expression calling the `zeroblob` function. - @warn_unused_result public static func allZeros(length: Int) -> Expression { + public static func allZeros(_ length: Int) -> Expression { return "zeroblob".wrap([]) } @@ -148,7 +148,7 @@ extension ExpressionType where UnderlyingType == NSData { } -extension ExpressionType where UnderlyingType == NSData? { +extension ExpressionType where UnderlyingType == Data? { /// Builds a copy of the expression wrapped with the `length` function. /// @@ -216,7 +216,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. - @warn_unused_result public func like(pattern: String, escape character: Character? = nil) -> Expression { + public func like(_ pattern: String, escape character: Character? = nil) -> Expression { guard let character = character else { return "LIKE".infix(self, pattern) } @@ -234,7 +234,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. - @warn_unused_result public func glob(pattern: String) -> Expression { + public func glob(_ pattern: String) -> Expression { return "GLOB".infix(self, pattern) } @@ -249,7 +249,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. - @warn_unused_result public func match(pattern: String) -> Expression { + public func match(_ pattern: String) -> Expression { return "MATCH".infix(self, pattern) } @@ -260,7 +260,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. - @warn_unused_result public func regexp(pattern: String) -> Expression { + public func regexp(_ pattern: String) -> Expression { return "REGEXP".infix(self, pattern) } @@ -275,7 +275,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. - @warn_unused_result public func collate(collation: Collation) -> Expression { + public func collate(_ collation: Collation) -> Expression { return "COLLATE".infix(self, collation) } @@ -290,7 +290,7 @@ extension ExpressionType where UnderlyingType == String { /// - Parameter characters: A set of characters to trim. /// /// - Returns: A copy of the expression wrapped with the `ltrim` function. - @warn_unused_result public func ltrim(characters: Set? = nil) -> Expression { + public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { return wrap(self) } @@ -308,7 +308,7 @@ extension ExpressionType where UnderlyingType == String { /// - Parameter characters: A set of characters to trim. /// /// - Returns: A copy of the expression wrapped with the `rtrim` function. - @warn_unused_result public func rtrim(characters: Set? = nil) -> Expression { + public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { return wrap(self) } @@ -326,7 +326,7 @@ extension ExpressionType where UnderlyingType == String { /// - Parameter characters: A set of characters to trim. /// /// - Returns: A copy of the expression wrapped with the `trim` function. - @warn_unused_result public func trim(characters: Set? = nil) -> Expression { + public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { return wrap([self]) } @@ -346,11 +346,11 @@ extension ExpressionType where UnderlyingType == String { /// - replacement: The replacement string. /// /// - Returns: A copy of the expression wrapped with the `replace` function. - @warn_unused_result public func replace(pattern: String, with replacement: String) -> Expression { + public func replace(_ pattern: String, with replacement: String) -> Expression { return "replace".wrap([self, pattern, replacement]) } - @warn_unused_result public func substring(location: Int, length: Int? = nil) -> Expression { + public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { return "substr".wrap([self, location]) } @@ -358,7 +358,7 @@ extension ExpressionType where UnderlyingType == String { } public subscript(range: Range) -> Expression { - return substring(range.startIndex, length: range.endIndex - range.startIndex) + return substring(range.lowerBound, length: range.upperBound - range.lowerBound) } } @@ -416,7 +416,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. - @warn_unused_result public func like(pattern: String, escape character: Character? = nil) -> Expression { + public func like(_ pattern: String, escape character: Character? = nil) -> Expression { guard let character = character else { return "LIKE".infix(self, pattern) } @@ -434,7 +434,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. - @warn_unused_result public func glob(pattern: String) -> Expression { + public func glob(_ pattern: String) -> Expression { return "GLOB".infix(self, pattern) } @@ -449,7 +449,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. - @warn_unused_result public func match(pattern: String) -> Expression { + public func match(_ pattern: String) -> Expression { return "MATCH".infix(self, pattern) } @@ -460,7 +460,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. - @warn_unused_result public func regexp(pattern: String) -> Expression { + public func regexp(_ pattern: String) -> Expression { return "REGEXP".infix(self, pattern) } @@ -475,7 +475,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. - @warn_unused_result public func collate(collation: Collation) -> Expression { + public func collate(_ collation: Collation) -> Expression { return "COLLATE".infix(self, collation) } @@ -490,7 +490,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Parameter characters: A set of characters to trim. /// /// - Returns: A copy of the expression wrapped with the `ltrim` function. - @warn_unused_result public func ltrim(characters: Set? = nil) -> Expression { + public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { return wrap(self) } @@ -508,7 +508,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Parameter characters: A set of characters to trim. /// /// - Returns: A copy of the expression wrapped with the `rtrim` function. - @warn_unused_result public func rtrim(characters: Set? = nil) -> Expression { + public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { return wrap(self) } @@ -526,7 +526,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Parameter characters: A set of characters to trim. /// /// - Returns: A copy of the expression wrapped with the `trim` function. - @warn_unused_result public func trim(characters: Set? = nil) -> Expression { + public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { return wrap(self) } @@ -546,7 +546,7 @@ extension ExpressionType where UnderlyingType == String? { /// - replacement: The replacement string. /// /// - Returns: A copy of the expression wrapped with the `replace` function. - @warn_unused_result public func replace(pattern: String, with replacement: String) -> Expression { + public func replace(_ pattern: String, with replacement: String) -> Expression { return "replace".wrap([self, pattern, replacement]) } @@ -565,7 +565,7 @@ extension ExpressionType where UnderlyingType == String? { /// - length: An optional substring length. /// /// - Returns: A copy of the expression wrapped with the `substr` function. - @warn_unused_result public func substring(location: Int, length: Int? = nil) -> Expression { + public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { return "substr".wrap([self, location]) } @@ -582,12 +582,12 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `substr` function. public subscript(range: Range) -> Expression { - return substring(range.startIndex, length: range.endIndex - range.startIndex) + return substring(range.lowerBound, length: range.upperBound - range.lowerBound) } } -extension CollectionType where Generator.Element : Value, Index.Distance == Int { +extension Collection where Iterator.Element : Value, IndexDistance == Int { /// Builds a copy of the expression prepended with an `IN` check against the /// collection. @@ -600,8 +600,8 @@ extension CollectionType where Generator.Element : Value, Index.Distance == Int /// /// - Returns: A copy of the expression prepended with an `IN` check against /// the collection. - @warn_unused_result public func contains(expression: Expression) -> Expression { - let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") + public func contains(_ expression: Expression) -> Expression { + let templates = [String](repeating: "?", count: count).joined(separator: ", ") return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } @@ -616,8 +616,8 @@ extension CollectionType where Generator.Element : Value, Index.Distance == Int /// /// - Returns: A copy of the expression prepended with an `IN` check against /// the collection. - @warn_unused_result public func contains(expression: Expression) -> Expression { - let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") + public func contains(_ expression: Expression) -> Expression { + let templates = [String](repeating: "?", count: count).joined(separator: ", ") return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } diff --git a/SQLite/Typed/CustomFunctions.swift b/SQLite/Typed/CustomFunctions.swift index 068d0340..64b54edb 100644 --- a/SQLite/Typed/CustomFunctions.swift +++ b/SQLite/Typed/CustomFunctions.swift @@ -39,83 +39,83 @@ public extension Connection { /// The assigned types must be explicit. /// /// - Returns: A closure returning an SQL expression to call the function. - public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z) throws -> (() -> Expression) { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z?) throws -> (() -> Expression) { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } // MARK: - - public func createFunction(function: String, deterministic: Bool = false, _ block: A -> Z) throws -> (Expression -> Expression) { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z) throws -> (Expression -> Expression) { + public func createFunction(function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } - public func createFunction(function function: String, deterministic: Bool = false, _ block: A -> Z?) throws -> (Expression -> Expression) { + public func createFunction(function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z?) throws -> (Expression -> Expression) { + public func createFunction(function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } // MARK: - - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } // MARK: - - private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z) throws -> ([Expressible] -> Expression) { + fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z) throws -> (([Expressible]) -> Expression) { createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in block(arguments).datatypeValue } @@ -124,7 +124,7 @@ public extension Connection { } } - private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z?) throws -> ([Expressible] -> Expression) { + fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z?) throws -> (([Expressible]) -> Expression) { createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in block(arguments)?.datatypeValue } diff --git a/SQLite/Typed/Expression.swift b/SQLite/Typed/Expression.swift index 2e71b970..3198901c 100644 --- a/SQLite/Typed/Expression.swift +++ b/SQLite/Typed/Expression.swift @@ -138,10 +138,10 @@ extension Value { public let rowid = Expression("ROWID") -public func cast(expression: Expression) -> Expression { +public func cast(_ expression: Expression) -> Expression { return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) } -public func cast(expression: Expression) -> Expression { +public func cast(_ expression: Expression) -> Expression { return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) } diff --git a/SQLite/Typed/Operators.swift b/SQLite/Typed/Operators.swift index 816560a5..616015dc 100644 --- a/SQLite/Typed/Operators.swift +++ b/SQLite/Typed/Operators.swift @@ -52,433 +52,433 @@ public func +(lhs: String, rhs: Expression) -> Expression { // MARK: - -public func +(lhs: Expression, rhs: Expression) -> Expression { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression { +public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression { +public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: V, rhs: Expression) -> Expression { +public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func +(lhs: V, rhs: Expression) -> Expression { +public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression { +public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression { +public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: V, rhs: Expression) -> Expression { +public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func -(lhs: V, rhs: Expression) -> Expression { +public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression { +public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression { +public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: V, rhs: Expression) -> Expression { +public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func *(lhs: V, rhs: Expression) -> Expression { +public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression { +public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression { +public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: V, rhs: Expression) -> Expression { +public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public func /(lhs: V, rhs: Expression) -> Expression { +public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { return infix(lhs, rhs) } -public prefix func -(rhs: Expression) -> Expression { +public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { return wrap(rhs) } -public prefix func -(rhs: Expression) -> Expression { +public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { return wrap(rhs) } // MARK: - -public func %(lhs: Expression, rhs: Expression) -> Expression { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: Expression, rhs: V) -> Expression { +public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: Expression, rhs: V) -> Expression { +public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: V, rhs: Expression) -> Expression { +public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func %(lhs: V, rhs: Expression) -> Expression { +public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: V) -> Expression { +public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: V) -> Expression { +public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: V, rhs: Expression) -> Expression { +public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func <<(lhs: V, rhs: Expression) -> Expression { +public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: V) -> Expression { +public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: V) -> Expression { +public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: V, rhs: Expression) -> Expression { +public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func >>(lhs: V, rhs: Expression) -> Expression { +public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: Expression, rhs: V) -> Expression { +public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: Expression, rhs: V) -> Expression { +public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: V, rhs: Expression) -> Expression { +public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func &(lhs: V, rhs: Expression) -> Expression { +public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: Expression, rhs: V) -> Expression { +public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: Expression, rhs: V) -> Expression { +public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: V, rhs: Expression) -> Expression { +public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func |(lhs: V, rhs: Expression) -> Expression { +public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return infix(lhs, rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: V) -> Expression { +public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: V) -> Expression { +public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: V, rhs: Expression) -> Expression { +public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: V, rhs: Expression) -> Expression { +public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public prefix func ~(rhs: Expression) -> Expression { +public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { return wrap(rhs) } -public prefix func ~(rhs: Expression) -> Expression { +public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { return wrap(rhs) } // MARK: - -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V) -> Expression { +public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { return "=".infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V?) -> Expression { +public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } return "=".infix(lhs, rhs) } -public func ==(lhs: V, rhs: Expression) -> Expression { +public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { return "=".infix(lhs, rhs) } -public func ==(lhs: V?, rhs: Expression) -> Expression { +public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } return "=".infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V) -> Expression { +public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { return infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V?) -> Expression { +public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return infix(lhs, rhs) } -public func !=(lhs: V, rhs: Expression) -> Expression { +public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { return infix(lhs, rhs) } -public func !=(lhs: V?, rhs: Expression) -> Expression { +public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression { +public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression { +public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: V, rhs: Expression) -> Expression { +public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >(lhs: V, rhs: Expression) -> Expression { +public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression { +public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression { +public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: V, rhs: Expression) -> Expression { +public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func >=(lhs: V, rhs: Expression) -> Expression { +public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression { +public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression { +public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: V, rhs: Expression) -> Expression { +public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <(lhs: V, rhs: Expression) -> Expression { +public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression { +public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression { +public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: V, rhs: Expression) -> Expression { +public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func <=(lhs: V, rhs: Expression) -> Expression { +public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { return infix(lhs, rhs) } -public func ~=, V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +public func ~=(lhs: CountableClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } -public func ~=, V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +public func ~=(lhs: CountableClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } // MARK: - diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 36b3b2e1..d0e4572d 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -50,7 +50,7 @@ extension SchemaType { /// - Parameter all: A list of expressions to select. /// /// - Returns: A query with the given `SELECT` clause applied. - public func select(column1: Expressible, _ more: Expressible...) -> Self { + public func select(_ column1: Expressible, _ more: Expressible...) -> Self { return select(false, [column1] + more) } @@ -81,7 +81,7 @@ extension SchemaType { /// - Parameter all: A list of expressions to select. /// /// - Returns: A query with the given `SELECT` clause applied. - public func select(all: [Expressible]) -> Self { + public func select(_ all: [Expressible]) -> Self { return select(false, all) } @@ -110,7 +110,7 @@ extension SchemaType { /// - Parameter star: A star literal. /// /// - Returns: A query with the given `SELECT *` clause applied. - public func select(star: Star) -> Self { + public func select(_ star: Star) -> Self { return select([star(nil, nil)]) } @@ -139,10 +139,10 @@ extension SchemaType { /// - Parameter all: A list of expressions to select. /// /// - Returns: A query with the given `SELECT` clause applied. - public func select(column: Expression) -> ScalarQuery { + public func select(_ column: Expression) -> ScalarQuery { return select(false, [column]) } - public func select(column: Expression) -> ScalarQuery { + public func select(_ column: Expression) -> ScalarQuery { return select(false, [column]) } @@ -173,7 +173,7 @@ extension SchemaType { extension QueryType { - private func select(distinct: Bool, _ columns: [Expressible]) -> Q { + fileprivate func select(_ distinct: Bool, _ columns: [Expressible]) -> Q { var query = Q.init(clauses.from.name, database: clauses.from.database) query.clauses = clauses query.clauses.select = (distinct, columns) @@ -199,7 +199,7 @@ extension QueryType { /// - condition: A boolean expression describing the join condition. /// /// - Returns: A query with the given `JOIN` clause applied. - public func join(table: QueryType, on condition: Expression) -> Self { + public func join(_ table: QueryType, on condition: Expression) -> Self { return join(table, on: Expression(condition)) } @@ -220,7 +220,7 @@ extension QueryType { /// - condition: A boolean expression describing the join condition. /// /// - Returns: A query with the given `JOIN` clause applied. - public func join(table: QueryType, on condition: Expression) -> Self { + public func join(_ table: QueryType, on condition: Expression) -> Self { return join(.Inner, table, on: condition) } @@ -243,7 +243,7 @@ extension QueryType { /// - condition: A boolean expression describing the join condition. /// /// - Returns: A query with the given `JOIN` clause applied. - public func join(type: JoinType, _ table: QueryType, on condition: Expression) -> Self { + public func join(_ type: JoinType, _ table: QueryType, on condition: Expression) -> Self { return join(type, table, on: Expression(condition)) } @@ -266,7 +266,7 @@ extension QueryType { /// - condition: A boolean expression describing the join condition. /// /// - Returns: A query with the given `JOIN` clause applied. - public func join(type: JoinType, _ table: QueryType, on condition: Expression) -> Self { + public func join(_ type: JoinType, _ table: QueryType, on condition: Expression) -> Self { var query = self query.clauses.join.append((type: type, query: table, condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) return query @@ -285,7 +285,7 @@ extension QueryType { /// - Parameter condition: A boolean expression to filter on. /// /// - Returns: A query with the given `WHERE` clause applied. - public func filter(predicate: Expression) -> Self { + public func filter(_ predicate: Expression) -> Self { return filter(Expression(predicate)) } @@ -300,7 +300,7 @@ extension QueryType { /// - Parameter condition: A boolean expression to filter on. /// /// - Returns: A query with the given `WHERE` clause applied. - public func filter(predicate: Expression) -> Self { + public func filter(_ predicate: Expression) -> Self { var query = self query.clauses.filters = query.clauses.filters.map { $0 && predicate } ?? predicate return query @@ -313,7 +313,7 @@ extension QueryType { /// - Parameter by: A list of columns to group by. /// /// - Returns: A query with the given `GROUP BY` clause applied. - public func group(by: Expressible...) -> Self { + public func group(_ by: Expressible...) -> Self { return group(by) } @@ -322,7 +322,7 @@ extension QueryType { /// - Parameter by: A list of columns to group by. /// /// - Returns: A query with the given `GROUP BY` clause applied. - public func group(by: [Expressible]) -> Self { + public func group(_ by: [Expressible]) -> Self { return group(by, nil) } @@ -335,7 +335,7 @@ extension QueryType { /// - having: A condition determining which groups are returned. /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. - public func group(by: Expressible, having: Expression) -> Self { + public func group(_ by: Expressible, having: Expression) -> Self { return group([by], having: having) } @@ -348,7 +348,7 @@ extension QueryType { /// - having: A condition determining which groups are returned. /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. - public func group(by: Expressible, having: Expression) -> Self { + public func group(_ by: Expressible, having: Expression) -> Self { return group([by], having: having) } @@ -361,7 +361,7 @@ extension QueryType { /// - having: A condition determining which groups are returned. /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. - public func group(by: [Expressible], having: Expression) -> Self { + public func group(_ by: [Expressible], having: Expression) -> Self { return group(by, Expression(having)) } @@ -374,11 +374,11 @@ extension QueryType { /// - having: A condition determining which groups are returned. /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. - public func group(by: [Expressible], having: Expression) -> Self { + public func group(_ by: [Expressible], having: Expression) -> Self { return group(by, having) } - private func group(by: [Expressible], _ having: Expression?) -> Self { + fileprivate func group(_ by: [Expressible], _ having: Expression?) -> Self { var query = self query.clauses.group = (by, having) return query @@ -398,7 +398,7 @@ extension QueryType { /// - Parameter by: An ordered list of columns and directions to sort by. /// /// - Returns: A query with the given `ORDER BY` clause applied. - public func order(by: Expressible...) -> Self { + public func order(_ by: Expressible...) -> Self { return order(by) } @@ -414,7 +414,7 @@ extension QueryType { /// - Parameter by: An ordered list of columns and directions to sort by. /// /// - Returns: A query with the given `ORDER BY` clause applied. - public func order(by: [Expressible]) -> Self { + public func order(_ by: [Expressible]) -> Self { var query = self query.clauses.order = by return query @@ -433,7 +433,7 @@ extension QueryType { /// return unlimited rows). /// /// - Returns: A query with the given LIMIT clause applied. - public func limit(length: Int?) -> Self { + public func limit(_ length: Int?) -> Self { return limit(length, nil) } @@ -451,12 +451,12 @@ extension QueryType { /// - offset: The number of rows to skip. /// /// - Returns: A query with the given LIMIT and OFFSET clauses applied. - public func limit(length: Int, offset: Int) -> Self { + public func limit(_ length: Int, offset: Int) -> Self { return limit(length, offset) } // prevents limit(nil, offset: 5) - private func limit(length: Int?, _ offset: Int?) -> Self { + fileprivate func limit(_ length: Int?, _ offset: Int?) -> Self { var query = self query.clauses.limit = length.map { ($0, offset) } return query @@ -468,7 +468,7 @@ extension QueryType { // MARK: - - private var selectClause: Expressible { + fileprivate var selectClause: Expressible { return " ".join([ Expression(literal: clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), ", ".join(clauses.select.columns), @@ -477,7 +477,7 @@ extension QueryType { ]) } - private var joinClause: Expressible? { + fileprivate var joinClause: Expressible? { guard !clauses.join.isEmpty else { return nil } @@ -492,7 +492,7 @@ extension QueryType { }) } - private var whereClause: Expressible? { + fileprivate var whereClause: Expressible? { guard let filters = clauses.filters else { return nil } @@ -503,7 +503,7 @@ extension QueryType { ]) } - private var groupByClause: Expressible? { + fileprivate var groupByClause: Expressible? { guard let group = clauses.group else { return nil } @@ -526,7 +526,7 @@ extension QueryType { ]) } - private var orderClause: Expressible? { + fileprivate var orderClause: Expressible? { guard !clauses.order.isEmpty else { return nil } @@ -537,7 +537,7 @@ extension QueryType { ]) } - private var limitOffsetClause: Expressible? { + fileprivate var limitOffsetClause: Expressible? { guard let limit = clauses.limit else { return nil } @@ -556,7 +556,7 @@ extension QueryType { // MARK: - - public func alias(aliasName: String) -> Self { + public func alias(_ aliasName: String) -> Self { var query = self query.clauses.from = (clauses.from.name, aliasName, clauses.from.database) return query @@ -566,11 +566,11 @@ extension QueryType { // // MARK: INSERT - public func insert(value: Setter, _ more: Setter...) -> Insert { + public func insert(_ value: Setter, _ more: Setter...) -> Insert { return insert([value] + more) } - public func insert(values: [Setter]) -> Insert { + public func insert(_ values: [Setter]) -> Insert { return insert(nil, values) } @@ -582,7 +582,7 @@ extension QueryType { return insert(onConflict, values) } - private func insert(or: OnConflict?, _ values: [Setter]) -> Insert { + fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert { let insert = values.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in (insert.columns + [setter.column], insert.values + [setter.value]) } @@ -616,7 +616,7 @@ extension QueryType { /// - Parameter query: A query to `SELECT` results from. /// /// - Returns: The number of updated rows and statement. - public func insert(query: QueryType) -> Update { + public func insert(_ query: QueryType) -> Update { return Update(" ".join([ Expression(literal: "INSERT INTO"), tableName(), @@ -626,11 +626,11 @@ extension QueryType { // MARK: UPDATE - public func update(values: Setter...) -> Update { + public func update(_ values: Setter...) -> Update { return update(values) } - public func update(values: [Setter]) -> Update { + public func update(_ values: [Setter]) -> Update { let clauses: [Expressible?] = [ Expression(literal: "UPDATE"), tableName(), @@ -671,7 +671,7 @@ extension QueryType { /// /// - Returns: A column expression namespaced with the query’s table name or /// alias. - public func namespace(column: Expression) -> Expression { + public func namespace(_ column: Expression) -> Expression { return Expression(".".join([tableName(), column]).expression) } @@ -733,7 +733,7 @@ extension QueryType { // TODO: alias support func tableName(alias aliased: Bool = false) -> Expressible { - guard let alias = clauses.from.alias where aliased else { + guard let alias = clauses.from.alias , aliased else { return database(namespace: clauses.from.alias ?? clauses.from.name) } @@ -744,7 +744,7 @@ extension QueryType { ]) } - func tableName(qualified qualified: Bool) -> Expressible { + func tableName(qualified: Bool) -> Expressible { if qualified { return tableName() } @@ -880,7 +880,7 @@ public struct Delete : ExpressionType { extension Connection { - public func prepare(query: QueryType) throws -> AnySequence { + public func prepare(_ query: QueryType) throws -> AnySequence { let expression = query.expression let statement = try prepare(expression.template, expression.bindings) @@ -889,11 +889,11 @@ extension Connection { column: for each in query.clauses.select.columns ?? [Expression(literal: "*")] { var names = each.expression.template.characters.split { $0 == "." }.map(String.init) let column = names.removeLast() - let namespace = names.joinWithSeparator(".") + let namespace = names.joined(separator: ".") - func expandGlob(namespace: Bool) -> (QueryType throws -> Void) { + func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { return { (query: QueryType) throws -> (Void) in - var q = query.dynamicType.init(query.clauses.from.name, database: query.clauses.from.database) + var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) q.clauses.select = query.clauses.select let e = q.expression var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } @@ -928,34 +928,34 @@ extension Connection { }() return AnySequence { - AnyGenerator { statement.next().map { Row(columnNames, $0) } } + AnyIterator { statement.next().map { Row(columnNames, $0) } } } } - public func scalar(query: ScalarQuery) throws -> V { + public func scalar(_ query: ScalarQuery) throws -> V { let expression = query.expression return value(try scalar(expression.template, expression.bindings)) } - public func scalar(query: ScalarQuery) throws -> V.ValueType? { + public func scalar(_ query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func scalar(query: Select) throws -> V { + public func scalar(_ query: Select) throws -> V { let expression = query.expression return value(try scalar(expression.template, expression.bindings)) } - public func scalar(query: Select) throws -> V.ValueType? { + public func scalar(_ query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func pluck(query: QueryType) throws -> Row? { - return try prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + public func pluck(_ query: QueryType) throws -> Row? { + return try prepare(query.limit(1, query.clauses.limit?.offset)).makeIterator().next() } /// Runs an `Insert` query. @@ -968,10 +968,10 @@ extension Connection { /// - Parameter query: An insert query. /// /// - Returns: The insert’s rowid. - public func run(query: Insert) throws -> Int64 { + public func run(_ query: Insert) throws -> Int64 { let expression = query.expression return try sync { - try self.run(expression.template, expression.bindings) + _ = try self.run(expression.template, expression.bindings) return self.lastInsertRowid! } } @@ -984,10 +984,10 @@ extension Connection { /// - Parameter query: An update query. /// /// - Returns: The number of updated rows. - public func run(query: Update) throws -> Int { + public func run(_ query: Update) throws -> Int { let expression = query.expression return try sync { - try self.run(expression.template, expression.bindings) + _ = try self.run(expression.template, expression.bindings) return self.changes } } @@ -999,10 +999,10 @@ extension Connection { /// - Parameter query: A delete query. /// /// - Returns: The number of deleted rows. - public func run(query: Delete) throws -> Int { + public func run(_ query: Delete) throws -> Int { let expression = query.expression return try sync { - try self.run(expression.template, expression.bindings) + _ = try self.run(expression.template, expression.bindings) return self.changes } } @@ -1011,11 +1011,11 @@ extension Connection { public struct Row { - private let columnNames: [String: Int] + fileprivate let columnNames: [String: Int] - private let values: [Binding?] + fileprivate let values: [Binding?] - private init(_ columnNames: [String: Int], _ values: [Binding?]) { + fileprivate init(_ columnNames: [String: Int], _ values: [Binding?]) { self.columnNames = columnNames self.values = values } @@ -1025,11 +1025,11 @@ public struct Row { /// - Parameter column: An expression representing a column selected in a Query. /// /// - Returns: The value for the given column. - public func get(column: Expression) -> V { + public func get(_ column: Expression) -> V { return get(Expression(column))! } - public func get(column: Expression) -> V? { - func valueAtIndex(idx: Int) -> V? { + public func get(_ column: Expression) -> V? { + func valueAtIndex(_ idx: Int) -> V? { guard let value = values[idx] as? V.Datatype else { return nil } return (V.fromDatatypeValue(value) as? V)! } @@ -1039,7 +1039,7 @@ public struct Row { switch similar.count { case 0: - fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sort())") + fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sorted())") case 1: return valueAtIndex(columnNames[similar[0]]!) default: @@ -1143,7 +1143,7 @@ public struct QueryClauses { var limit: (length: Int, offset: Int?)? - private init(_ name: String, alias: String?, database: String?) { + fileprivate init(_ name: String, alias: String?, database: String?) { self.from = (name, alias, database) } diff --git a/SQLite/Typed/Schema.swift b/SQLite/Typed/Schema.swift index 16e11c16..4c6862c0 100644 --- a/SQLite/Typed/Schema.swift +++ b/SQLite/Typed/Schema.swift @@ -26,7 +26,7 @@ extension SchemaType { // MARK: - DROP TABLE / VIEW / VIRTUAL TABLE - public func drop(ifExists ifExists: Bool = false) -> String { + public func drop(ifExists: Bool = false) -> String { return drop("TABLE", tableName(), ifExists) } @@ -36,7 +36,7 @@ extension Table { // MARK: - CREATE TABLE - public func create(temporary temporary: Bool = false, ifNotExists: Bool = false, @noescape block: TableBuilder -> Void) -> String { + public func create(temporary: Bool = false, ifNotExists: Bool = false, block: (TableBuilder) -> Void) -> String { let builder = TableBuilder() block(builder) @@ -49,7 +49,7 @@ extension Table { return " ".join(clauses.flatMap { $0 }).asSQL() } - public func create(query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { + public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), Expression(literal: "AS"), @@ -61,55 +61,55 @@ extension Table { // MARK: - ALTER TABLE … ADD COLUMN - public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } - public func addColumn(name: Expression, check: Expression, defaultValue: V) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } - public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } - public func addColumn(name: Expression, check: Expression, defaultValue: V? = nil) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } - public func addColumn(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } - public func addColumn(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } - public func addColumn(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } - public func addColumn(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } - public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } - public func addColumn(name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } - public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } - public func addColumn(name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } - private func addColumn(expression: Expressible) -> String { + fileprivate func addColumn(_ expression: Expressible) -> String { return " ".join([ Expression(literal: "ALTER TABLE"), tableName(), @@ -120,17 +120,17 @@ extension Table { // MARK: - ALTER TABLE … RENAME TO - public func rename(to: Table) -> String { + public func rename(_ to: Table) -> String { return rename(to: to) } // MARK: - CREATE INDEX - public func createIndex(columns: Expressible...) -> String { + public func createIndex(_ columns: Expressible...) -> String { return createIndex(columns) } - public func createIndex(columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { + public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .Unique : nil, ifNotExists), Expression(literal: "ON"), @@ -143,16 +143,16 @@ extension Table { // MARK: - DROP INDEX - public func dropIndex(columns: Expressible...) -> String { + public func dropIndex(_ columns: Expressible...) -> String { return dropIndex(columns) } - public func dropIndex(columns: [Expressible], ifExists: Bool = false) -> String { + public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { return drop("INDEX", indexName(columns), ifExists) } - private func indexName(columns: [Expressible]) -> Expressible { - let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joinWithSeparator(" ").lowercaseString + fileprivate func indexName(_ columns: [Expressible]) -> Expressible { + let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joined(separator: " ").lowercased() let index = string.characters.reduce("") { underscored, character in guard character != "\"" else { @@ -173,7 +173,7 @@ extension View { // MARK: - CREATE VIEW - public func create(query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { + public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create(View.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), Expression(literal: "AS"), @@ -185,7 +185,7 @@ extension View { // MARK: - DROP VIEW - public func drop(ifExists ifExists: Bool = false) -> String { + public func drop(ifExists: Bool = false) -> String { return drop("VIEW", tableName(), ifExists) } @@ -195,7 +195,7 @@ extension VirtualTable { // MARK: - CREATE VIRTUAL TABLE - public func create(using: Module, ifNotExists: Bool = false) -> String { + public func create(_ using: Module, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create(VirtualTable.identifier, tableName(), nil, ifNotExists), Expression(literal: "USING"), @@ -207,7 +207,7 @@ extension VirtualTable { // MARK: - ALTER TABLE … RENAME TO - public func rename(to: VirtualTable) -> String { + public func rename(_ to: VirtualTable) -> String { return rename(to: to) } @@ -215,155 +215,155 @@ extension VirtualTable { public final class TableBuilder { - private var definitions = [Expressible]() + fileprivate var definitions = [Expressible]() - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(name: Expression, primaryKey: Bool, check: Expression? = nil, defaultValue: Expression? = nil) { - column(name, V.declaredDatatype, primaryKey ? .Default : nil, false, false, check, defaultValue, nil, nil) + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil) } - public func column(name: Expression, primaryKey: Bool, check: Expression, defaultValue: Expression? = nil) { - column(name, V.declaredDatatype, primaryKey ? .Default : nil, false, false, check, defaultValue, nil, nil) + public func column(_ name: Expression, primaryKey: Bool, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil) } - public func column(name: Expression, primaryKey: PrimaryKey, check: Expression? = nil) { + public func column(_ name: Expression, primaryKey: PrimaryKey, check: Expression? = nil) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) } - public func column(name: Expression, primaryKey: PrimaryKey, check: Expression) { + public func column(_ name: Expression, primaryKey: PrimaryKey, check: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } - public func column(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - private func column(name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { + fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { definitions.append(definition(name, datatype, primaryKey, null, unique, check, defaultValue, references, collate)) } // MARK: - - public func primaryKey(column: Expression) { + public func primaryKey(_ column: Expression) { primaryKey([column]) } - public func primaryKey(compositeA: Expression, _ b: Expression) { + public func primaryKey(_ compositeA: Expression, _ b: Expression) { primaryKey([compositeA, b]) } - public func primaryKey(compositeA: Expression, _ b: Expression, _ c: Expression) { + public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression) { primaryKey([compositeA, b, c]) } - private func primaryKey(composite: [Expressible]) { + fileprivate func primaryKey(_ composite: [Expressible]) { definitions.append("PRIMARY KEY".prefix(composite)) } - public func unique(columns: Expressible...) { + public func unique(_ columns: Expressible...) { unique(columns) } - public func unique(columns: [Expressible]) { + public func unique(_ columns: [Expressible]) { definitions.append("UNIQUE".prefix(columns)) } - public func check(condition: Expression) { + public func check(_ condition: Expression) { check(Expression(condition)) } - public func check(condition: Expression) { + public func check(_ condition: Expression) { definitions.append("CHECK".prefix(condition)) } @@ -381,29 +381,29 @@ public final class TableBuilder { } - public func foreignKey(column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { foreignKey(column, (table, other), update, delete) } - public func foreignKey(column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { foreignKey(column, (table, other), update, delete) } - public func foreignKey(composite: (Expression, Expression), references table: QueryType, _ other: (Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ composite: (Expression, Expression), references table: QueryType, _ other: (Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { let composite = ", ".join([composite.0, composite.1]) let references = (table, ", ".join([other.0, other.1])) foreignKey(composite, references, update, delete) } - public func foreignKey(composite: (Expression, Expression, Expression), references table: QueryType, _ other: (Expression, Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ composite: (Expression, Expression, Expression), references table: QueryType, _ other: (Expression, Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { let composite = ", ".join([composite.0, composite.1, composite.2]) let references = (table, ", ".join([other.0, other.1, other.2])) foreignKey(composite, references, update, delete) } - private func foreignKey(column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) { + fileprivate func foreignKey(_ column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) { let clauses: [Expressible?] = [ "FOREIGN KEY".prefix(column), reference(references), @@ -418,17 +418,17 @@ public final class TableBuilder { public enum PrimaryKey { - case Default + case `default` - case Autoincrement + case autoincrement } public struct Module { - private let name: String + fileprivate let name: String - private let arguments: [Expressible] + fileprivate let arguments: [Expressible] public init(_ name: String, _ arguments: [Expressible]) { self.init(name: name.quote(), arguments: arguments) @@ -453,7 +453,7 @@ extension Module : Expressible { private extension QueryType { - func create(identifier: String, _ name: Expressible, _ modifier: Modifier?, _ ifNotExists: Bool) -> Expressible { + func create(_ identifier: String, _ name: Expressible, _ modifier: Modifier?, _ ifNotExists: Bool) -> Expressible { let clauses: [Expressible?] = [ Expression(literal: "CREATE"), modifier.map { Expression(literal: $0.rawValue) }, @@ -465,7 +465,7 @@ private extension QueryType { return " ".join(clauses.flatMap { $0 }) } - func rename(to to: Self) -> String { + func rename(to: Self) -> String { return " ".join([ Expression(literal: "ALTER TABLE"), tableName(), @@ -474,7 +474,7 @@ private extension QueryType { ]).asSQL() } - func drop(identifier: String, _ name: Expressible, _ ifExists: Bool) -> String { + func drop(_ identifier: String, _ name: Expressible, _ ifExists: Bool) -> String { let clauses: [Expressible?] = [ Expression(literal: "DROP \(identifier)"), ifExists ? Expression(literal: "IF EXISTS") : nil, @@ -486,11 +486,11 @@ private extension QueryType { } -private func definition(column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { +private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { let clauses: [Expressible?] = [ column, Expression(literal: datatype), - primaryKey.map { Expression(literal: $0 == .Autoincrement ? "PRIMARY KEY AUTOINCREMENT" : "PRIMARY KEY") }, + primaryKey.map { Expression(literal: $0 == .autoincrement ? "PRIMARY KEY AUTOINCREMENT" : "PRIMARY KEY") }, null ? nil : Expression(literal: "NOT NULL"), unique ? Expression(literal: "UNIQUE") : nil, check.map { " ".join([Expression(literal: "CHECK"), $0]) }, @@ -502,7 +502,7 @@ private func definition(column: Expressible, _ datatype: String, _ primaryKey: P return " ".join(clauses.flatMap { $0 }) } -private func reference(primary: (QueryType, Expressible)) -> Expressible { +private func reference(_ primary: (QueryType, Expressible)) -> Expressible { return " ".join([ Expression(literal: "REFERENCES"), primary.0.tableName(qualified: false), diff --git a/SQLite/Typed/Setter.swift b/SQLite/Typed/Setter.swift index d8bf775a..86f16fca 100644 --- a/SQLite/Typed/Setter.swift +++ b/SQLite/Typed/Setter.swift @@ -22,38 +22,40 @@ // THE SOFTWARE. // -infix operator <- { - associativity left - precedence 135 - assignment +precedencegroup ColumnAssignment { + associativity: left + assignment: true + lowerThan: AssignmentPrecedence } +infix operator <- : ColumnAssignment + public struct Setter { let column: Expressible let value: Expressible - private init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - private init(column: Expression, value: V) { + fileprivate init(column: Expression, value: V) { self.column = column self.value = value } - private init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - private init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - private init(column: Expression, value: V?) { + fileprivate init(column: Expression, value: V?) { self.column = column self.value = Expression(value: value) } @@ -100,176 +102,176 @@ public func +=(column: Expression, value: String) -> Setter { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column + value } -public func +=(column: Expression, value: V) -> Setter { +public func +=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column + value } -public func +=(column: Expression, value: V) -> Setter { +public func +=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column + value } -public func -=(column: Expression, value: Expression) -> Setter { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column - value } -public func -=(column: Expression, value: V) -> Setter { +public func -=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column - value } -public func -=(column: Expression, value: Expression) -> Setter { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column - value } -public func -=(column: Expression, value: Expression) -> Setter { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column - value } -public func -=(column: Expression, value: V) -> Setter { +public func -=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column - value } -public func *=(column: Expression, value: Expression) -> Setter { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column * value } -public func *=(column: Expression, value: V) -> Setter { +public func *=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column * value } -public func *=(column: Expression, value: Expression) -> Setter { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column * value } -public func *=(column: Expression, value: Expression) -> Setter { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column * value } -public func *=(column: Expression, value: V) -> Setter { +public func *=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column * value } -public func /=(column: Expression, value: Expression) -> Setter { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column / value } -public func /=(column: Expression, value: V) -> Setter { +public func /=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column / value } -public func /=(column: Expression, value: Expression) -> Setter { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column / value } -public func /=(column: Expression, value: Expression) -> Setter { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { return column <- column / value } -public func /=(column: Expression, value: V) -> Setter { +public func /=(column: Expression, value: V) -> Setter where V.Datatype : Number { return column <- column / value } -public func %=(column: Expression, value: Expression) -> Setter { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: V) -> Setter { +public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: Expression) -> Setter { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: Expression) -> Setter { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: V) -> Setter { +public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func <<=(column: Expression, value: Expression) -> Setter { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: V) -> Setter { +public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: Expression) -> Setter { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: Expression) -> Setter { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: V) -> Setter { +public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func >>=(column: Expression, value: Expression) -> Setter { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: V) -> Setter { +public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: Expression) -> Setter { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: Expression) -> Setter { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: V) -> Setter { +public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func &=(column: Expression, value: Expression) -> Setter { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: V) -> Setter { +public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: Expression) -> Setter { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: Expression) -> Setter { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: V) -> Setter { +public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func |=(column: Expression, value: Expression) -> Setter { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: V) -> Setter { +public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: Expression) -> Setter { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: Expression) -> Setter { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: V) -> Setter { +public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func ^=(column: Expression, value: Expression) -> Setter { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: V) -> Setter { +public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: Expression) -> Setter { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: Expression) -> Setter { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: V) -> Setter { +public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public postfix func ++(column: Expression) -> Setter { +public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) += 1 } -public postfix func ++(column: Expression) -> Setter { +public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) += 1 } -public postfix func --(column: Expression) -> Setter { +public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) -= 1 } -public postfix func --(column: Expression) -> Setter { +public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) -= 1 } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 3d7dd3eb..0e8ebea6 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -10,7 +10,7 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! Connection(.InMemory) + let db = try! Connection(.inMemory) XCTAssertEqual("", db.description) } @@ -20,12 +20,12 @@ class ConnectionTests : SQLiteTestCase { } func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! Connection(.Temporary) + let db = try! Connection(.temporary) XCTAssertEqual("", db.description) } func test_init_withURI_returnsURIConnection() { - let db = try! Connection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + let db = try! Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) } @@ -48,7 +48,7 @@ class ConnectionTests : SQLiteTestCase { } func test_lastInsertRowid_returnsLastIdAfterInserts() { - try! InsertUser("alice") + _ = try! InsertUser("alice") XCTAssertEqual(1, db.lastInsertRowid!) } @@ -57,17 +57,17 @@ class ConnectionTests : SQLiteTestCase { } func test_changes_returnsNumberOfChanges() { - try! InsertUser("alice") + _ = try! InsertUser("alice") XCTAssertEqual(1, db.changes) - try! InsertUser("betsy") + _ = try! InsertUser("betsy") XCTAssertEqual(1, db.changes) } func test_totalChanges_returnsTotalNumberOfChanges() { XCTAssertEqual(0, db.totalChanges) - try! InsertUser("alice") + _ = try! InsertUser("alice") XCTAssertEqual(1, db.totalChanges) - try! InsertUser("betsy") + _ = try! InsertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -79,10 +79,10 @@ class ConnectionTests : SQLiteTestCase { } func test_run_preparesRunsAndReturnsStatements() { - try! db.run("SELECT * FROM users WHERE admin = 0") - try! db.run("SELECT * FROM users WHERE admin = ?", 0) - try! db.run("SELECT * FROM users WHERE admin = ?", [0]) - try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + _ = try! db.run("SELECT * FROM users WHERE admin = 0") + _ = try! db.run("SELECT * FROM users WHERE admin = ?", 0) + _ = try! db.run("SELECT * FROM users WHERE admin = ?", [0]) + _ = try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) AssertSQL("SELECT * FROM users WHERE admin = 0", 4) } @@ -116,7 +116,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") try! db.transaction { - try stmt.run() + _ = try stmt.run() } AssertSQL("BEGIN DEFERRED TRANSACTION") @@ -130,8 +130,8 @@ class ConnectionTests : SQLiteTestCase { do { try db.transaction { - try stmt.run() - try stmt.run() + _ = try stmt.run() + _ = try stmt.run() } } catch { } @@ -147,7 +147,7 @@ class ConnectionTests : SQLiteTestCase { try! db.savepoint("1") { try db.savepoint("2") { - try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") + _ = try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") } } @@ -167,14 +167,14 @@ class ConnectionTests : SQLiteTestCase { do { try db.savepoint("1") { try db.savepoint("2") { - try stmt.run() - try stmt.run() - try stmt.run() + _ = try stmt.run() + _ = try stmt.run() + _ = try stmt.run() } try db.savepoint("2") { - try stmt.run() - try stmt.run() - try stmt.run() + _ = try stmt.run() + _ = try stmt.run() + _ = try stmt.run() } } } catch { @@ -192,41 +192,41 @@ class ConnectionTests : SQLiteTestCase { func test_updateHook_setsUpdateHook_withInsert() { async { done in db.updateHook { operation, db, table, rowid in - XCTAssertEqual(Operation.Insert, operation) + XCTAssertEqual(Operation.insert, operation) XCTAssertEqual("main", db) XCTAssertEqual("users", table) XCTAssertEqual(1, rowid) done() } - try! InsertUser("alice") + _ = try! InsertUser("alice") } } func test_updateHook_setsUpdateHook_withUpdate() { - try! InsertUser("alice") + _ = try! InsertUser("alice") async { done in db.updateHook { operation, db, table, rowid in - XCTAssertEqual(Operation.Update, operation) + XCTAssertEqual(Operation.update, operation) XCTAssertEqual("main", db) XCTAssertEqual("users", table) XCTAssertEqual(1, rowid) done() } - try! db.run("UPDATE users SET email = 'alice@example.com'") + _ = try! db.run("UPDATE users SET email = 'alice@example.com'") } } func test_updateHook_setsUpdateHook_withDelete() { - try! InsertUser("alice") + _ = try! InsertUser("alice") async { done in db.updateHook { operation, db, table, rowid in - XCTAssertEqual(Operation.Delete, operation) + XCTAssertEqual(Operation.delete, operation) XCTAssertEqual("main", db) XCTAssertEqual("users", table) XCTAssertEqual(1, rowid) done() } - try! db.run("DELETE FROM users WHERE id = 1") + _ = try! db.run("DELETE FROM users WHERE id = 1") } } @@ -236,7 +236,7 @@ class ConnectionTests : SQLiteTestCase { done() } try! db.transaction { - try self.InsertUser("alice") + _ = try self.InsertUser("alice") } XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } @@ -247,8 +247,8 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.InsertUser("alice") - try self.InsertUser("alice") // throw + _ = try self.InsertUser("alice") + _ = try self.InsertUser("alice") // throw } } catch { } @@ -264,7 +264,7 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.InsertUser("alice") + _ = try self.InsertUser("alice") } } catch { } @@ -288,14 +288,14 @@ class ConnectionTests : SQLiteTestCase { func test_createCollation_createsCollation() { try! db.createCollation("NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + return lhs.compare(rhs, options: .diacriticInsensitive) } XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { try! db.createCollation("NO DIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + return lhs.compare(rhs, options: .diacriticInsensitive) } XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } @@ -308,9 +308,9 @@ class ConnectionTests : SQLiteTestCase { } let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) - try! stmt.run() + _ = try! stmt.run() - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) + _ = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).asyncAfter(deadline: DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC), execute: db.interrupt) AssertThrows(try stmt.run()) } diff --git a/SQLiteTests/CoreFunctionsTests.swift b/SQLiteTests/CoreFunctionsTests.swift index 8f7460d5..db37ff7f 100644 --- a/SQLiteTests/CoreFunctionsTests.swift +++ b/SQLiteTests/CoreFunctionsTests.swift @@ -55,15 +55,15 @@ class CoreFunctionsTests : XCTestCase { } func test_collate_buildsExpressionWithCollateOperator() { - AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.Binary)) - AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.Nocase)) - AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.Rtrim)) - AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.Custom("CUSTOM"))) - - AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.Binary)) - AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.Nocase)) - AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.Rtrim)) - AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.Custom("CUSTOM"))) + AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.binary)) + AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.nocase)) + AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.rtrim)) + AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.custom("CUSTOM"))) + + AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.binary)) + AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.nocase)) + AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.rtrim)) + AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.custom("CUSTOM"))) } func test_ltrim_wrapsStringWithLtrimFunction() { diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index 8b4e4f96..37a773a6 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -121,19 +121,19 @@ class FTS4ConfigTests : XCTestCase { func test_config_matchinfo() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(matchinfo=\"fts3\")", - sql(config.matchInfo(.FTS3))) + sql(config.matchInfo(.fts3))) } func test_config_order_asc() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"asc\")", - sql(config.order(.Asc))) + sql(config.order(.asc))) } func test_config_order_desc() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(order=\"desc\")", - sql(config.order(.Desc))) + sql(config.order(.desc))) } func test_config_compress() { @@ -163,14 +163,14 @@ class FTS4ConfigTests : XCTestCase { .column(string, [.unindexed]) .column(date, [.unindexed]) .externalContent(table) - .matchInfo(.FTS3) + .matchInfo(.fts3) .languageId("lid") - .order(.Desc) + .order(.desc) .prefix([2, 4])) ) } - func sql(config: FTS4Config) -> String { + func sql(_ config: FTS4Config) -> String { return virtualTable.create(.FTS4(config)) } } @@ -184,24 +184,24 @@ class FTS4IntegrationTests : SQLiteTestCase { let locale = CFLocaleCopyCurrent() let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "", CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + let tokenizer = CFStringTokenizerCreate(nil, "" as CFString!, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) try! db.registerTokenizer(tokenizerName) { string in - CFStringTokenizerSetString(tokenizer, string, CFRangeMake(0, CFStringGetLength(string))) - if CFStringTokenizerAdvanceToNextToken(tokenizer) == .None { + CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString))) + if CFStringTokenizerAdvanceToNextToken(tokenizer) == .none { return nil } let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) - let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string, range) + let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string as CFString, range) let token = CFStringCreateMutableCopy(nil, range.length, input) CFStringLowercase(token, locale) CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false) - return (token as String, string.rangeOfString(input as String)!) + return ((token as String?)!, string.range(of: (input as String?)!)!) } - try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) + _ = try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") - try! db.run(emails.insert(subject <- "Aún más cáfe!")) + _ = try! db.run(emails.insert(subject <- "Aún más cáfe!")) XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) } #endif diff --git a/SQLiteTests/FTS5Tests.swift b/SQLiteTests/FTS5Tests.swift index aa3965bd..63d8dc40 100644 --- a/SQLiteTests/FTS5Tests.swift +++ b/SQLiteTests/FTS5Tests.swift @@ -90,19 +90,19 @@ class FTS5Tests: XCTestCase { func test_detail_full() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"full\")", - sql(config.detail(.Full))) + sql(config.detail(.full))) } func test_detail_column() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"column\")", - sql(config.detail(.Column))) + sql(config.detail(.column))) } func test_detail_none() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(detail=\"none\")", - sql(config.detail(.None))) + sql(config.detail(.none))) } func test_fts5_config_all() { @@ -118,7 +118,7 @@ class FTS5Tests: XCTestCase { ) } - func sql(config: FTS5Config) -> String { + func sql(_ config: FTS5Config) -> String { return virtualTable.create(.FTS5(config)) } } diff --git a/SQLiteTests/OperatorsTests.swift b/SQLiteTests/OperatorsTests.swift index 86aa34ed..819cf292 100644 --- a/SQLiteTests/OperatorsTests.swift +++ b/SQLiteTests/OperatorsTests.swift @@ -283,4 +283,4 @@ class OperatorsTests : XCTestCase { AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } -} \ No newline at end of file +} diff --git a/SQLiteTests/QueryTests.swift b/SQLiteTests/QueryTests.swift index f584bfcf..df2ff073 100644 --- a/SQLiteTests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -298,10 +298,10 @@ class QueryIntegrationTests : SQLiteTestCase { let managers = users.alias("managers") let alice = try! db.run(users.insert(email <- "alice@example.com")) - try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { - user[users[managerId]] + _ = user[users[managerId]] } } diff --git a/SQLiteTests/SchemaTests.swift b/SQLiteTests/SchemaTests.swift index 416b3646..01bedda0 100644 --- a/SQLiteTests/SchemaTests.swift +++ b/SQLiteTests/SchemaTests.swift @@ -283,15 +283,15 @@ class SchemaTests : XCTestCase { func test_column_withIntegerExpression_compilesPrimaryKeyAutoincrementColumnDefinitionExpression() { XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", - table.create { t in t.column(int64, primaryKey: .Autoincrement) } + table.create { t in t.column(int64, primaryKey: .autoincrement) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64\" > 0))", - table.create { t in t.column(int64, primaryKey: .Autoincrement, check: int64 > 0) } + table.create { t in t.column(int64, primaryKey: .autoincrement, check: int64 > 0) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64Optional\" > 0))", - table.create { t in t.column(int64, primaryKey: .Autoincrement, check: int64Optional > 0) } + table.create { t in t.column(int64, primaryKey: .autoincrement, check: int64Optional > 0) } ) } @@ -354,172 +354,172 @@ class SchemaTests : XCTestCase { func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL COLLATE RTRIM)", - table.create { t in t.column(string, collate: .Rtrim) } + table.create { t in t.column(string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, collate: .Rtrim) } + table.create { t in t.column(string, unique: true, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", - table.create { t in t.column(string, check: string != "", collate: .Rtrim) } + table.create { t in t.column(string, check: string != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", - table.create { t in t.column(string, check: stringOptional != "", collate: .Rtrim) } + table.create { t in t.column(string, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(string, defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(string, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(string, defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(string, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, check: string != "", collate: .Rtrim) } + table.create { t in t.column(string, unique: true, check: string != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, check: stringOptional != "", collate: .Rtrim) } + table.create { t in t.column(string, unique: true, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(string, unique: true, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(string, unique: true, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, check: string != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(string, unique: true, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, check: string != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(string, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(string, check: string != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(string, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(string, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(string, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(string, check: string != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(string, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(string, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(string, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL COLLATE RTRIM)", - table.create { t in t.column(stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: string != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(stringOptional, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(stringOptional, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) } @@ -676,36 +676,36 @@ class SchemaTests : XCTestCase { func test_addColumn_withStringExpression_compilesCollatedAlterTableExpression() { XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM", - table.addColumn(string, defaultValue: "string", collate: .Rtrim) + table.addColumn(string, defaultValue: "string", collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM", - table.addColumn(string, check: string != "", defaultValue: "string", collate: .Rtrim) + table.addColumn(string, check: string != "", defaultValue: "string", collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM", - table.addColumn(string, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) + table.addColumn(string, check: stringOptional != "", defaultValue: "string", collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT COLLATE RTRIM", - table.addColumn(stringOptional, collate: .Rtrim) + table.addColumn(stringOptional, collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') COLLATE RTRIM", - table.addColumn(stringOptional, check: string != "", collate: .Rtrim) + table.addColumn(stringOptional, check: string != "", collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') COLLATE RTRIM", - table.addColumn(stringOptional, check: stringOptional != "", collate: .Rtrim) + table.addColumn(stringOptional, check: stringOptional != "", collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM", - table.addColumn(stringOptional, check: string != "", defaultValue: "string", collate: .Rtrim) + table.addColumn(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) ) XCTAssertEqual( "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM", - table.addColumn(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) + table.addColumn(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) ) } diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 464b9c27..7b7715b1 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -32,22 +32,22 @@ class SQLiteTestCase : XCTestCase { ) } - func InsertUsers(names: String...) throws { + func InsertUsers(_ names: String...) throws { try InsertUsers(names) } - func InsertUsers(names: [String]) throws { - for name in names { try InsertUser(name) } + func InsertUsers(_ names: [String]) throws { + for name in names { _ = try InsertUser(name) } } - func InsertUser(name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { + func InsertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { return try db.run( "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", "\(name)@example.com", age?.datatypeValue, admin.datatypeValue ) } - func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { + func AssertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual( executions, trace[SQL] ?? 0, message ?? SQL, @@ -55,8 +55,8 @@ class SQLiteTestCase : XCTestCase { ) } - func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { - try! statement.run() + func AssertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { + _ = try! statement.run() AssertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } } @@ -67,10 +67,10 @@ class SQLiteTestCase : XCTestCase { // if let count = trace[SQL] { trace[SQL] = count - 1 } // } - func async(expect description: String = "async", timeout: Double = 5, @noescape block: (() -> Void) -> Void) { - let expectation = expectationWithDescription(description) + func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) -> Void) { + let expectation = self.expectation(description: description) block(expectation.fulfill) - waitForExpectationsWithTimeout(timeout, handler: nil) + waitForExpectations(timeout: timeout, handler: nil) } } @@ -81,8 +81,8 @@ let boolOptional = Expression("boolOptional") let data = Expression("blob") let dataOptional = Expression("blobOptional") -let date = Expression("date") -let dateOptional = Expression("dateOptional") +let date = Expression("date") +let dateOptional = Expression("dateOptional") let double = Expression("double") let doubleOptional = Expression("doubleOptional") @@ -96,13 +96,13 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") -func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () -> Expressible, file: StaticString = #file, line: UInt = #line) { +func AssertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } -func AssertThrows(@autoclosure expression: () throws -> T, file: StaticString = #file, line: UInt = #line) { +func AssertThrows(_ expression: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line) { do { - try expression() + _ = try expression() XCTFail("expression expected to throw", file: file, line: line) } catch { XCTAssert(true, file: file, line: line) From 78c7f6ae01fc06d86b24f9a595c03eb85d0bde8f Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Fri, 16 Sep 2016 18:47:54 +0800 Subject: [PATCH 0430/1046] all tests passed --- SQLite.xcodeproj/project.pbxproj | 4 ++++ SQLite/Extensions/FTS4.swift | 2 +- SQLiteTests/FTS4Tests.swift | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2f13a35f..aa951fd8 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1007,6 +1007,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1019,6 +1020,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; + SWIFT_VERSION = 3.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1040,6 +1042,7 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1062,6 +1065,7 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index 52de47bf..bd8c7b2d 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -148,7 +148,7 @@ extension Connection { let view = string.utf8 offset.pointee += string.substring(to: range.lowerBound).utf8.count - length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.lowerBound.samePosition(in: view))) + length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.upperBound.samePosition(in: view))) return token }) } diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index 37a773a6..92f197a2 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -187,15 +187,15 @@ class FTS4IntegrationTests : SQLiteTestCase { let tokenizer = CFStringTokenizerCreate(nil, "" as CFString!, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) try! db.registerTokenizer(tokenizerName) { string in CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString))) - if CFStringTokenizerAdvanceToNextToken(tokenizer) == .none { + if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { return nil } let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) - let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string as CFString, range) - let token = CFStringCreateMutableCopy(nil, range.length, input) + let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string as CFString, range)! + let token = CFStringCreateMutableCopy(nil, range.length, input)! CFStringLowercase(token, locale) CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false) - return ((token as String?)!, string.range(of: (input as String?)!)!) + return (token as String, string.range(of: input as String)!) } _ = try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) From aaa07c70e1952ce6aadccc0b79e7e0a1ce05d2ba Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Sat, 17 Sep 2016 09:37:27 +0800 Subject: [PATCH 0431/1046] added .swift-version for pods --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index bb576dbd..9f55b2cc 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -2.3 +3.0 From e9abf032f461bf2a6d84751d13e37998a94a2dfa Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 17 Sep 2016 17:32:30 +0100 Subject: [PATCH 0432/1046] Remove Xcode 7.3 tests --- .travis.yml | 31 ++++--------------------------- Makefile | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index d855fb3c..ada13822 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,34 +3,11 @@ rvm: 2.2 osx_image: xcode8 matrix: include: - - env: - - BUILD_SCHEME="SQLite iOS" - - IOS_SIMULATOR="iPhone SE" + - env: BUILD_SCHEME="SQLite iOS" - env: BUILD_SCHEME="SQLite Mac" - - env: - - VALIDATOR_SUBSPEC="none" - - IOS_SIMULATOR="iPhone SE" - - env: - - VALIDATOR_SUBSPEC="standard" - - IOS_SIMULATOR="iPhone SE" - - env: - - VALIDATOR_SUBSPEC="standalone" - - IOS_SIMULATOR="iPhone SE" - - os: osx - osx_image: xcode7.3 - env: BUILD_SCHEME="SQLite iOS" - - os: osx - osx_image: xcode7.3 - env: BUILD_SCHEME="SQLite Mac" - - os: osx - osx_image: xcode7.3 - env: VALIDATOR_SUBSPEC="none" - - os: osx - osx_image: xcode7.3 - env: VALIDATOR_SUBSPEC="standard" - - os: osx - osx_image: xcode7.3 - env: VALIDATOR_SUBSPEC="standalone" + - env: VALIDATOR_SUBSPEC="none" + - env: VALIDATOR_SUBSPEC="standard" + - env: VALIDATOR_SUBSPEC="standalone" before_install: - gem install xcpretty --no-document script: diff --git a/Makefile b/Makefile index 35edf493..2f085b6c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone 6 +IOS_SIMULATOR = iPhone SE ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR)" else From f8209be2fad7c88e41210e742a57a80338ff8a17 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 17 Sep 2016 17:37:07 +0100 Subject: [PATCH 0433/1046] Set swift version in podspec --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 89cffff1..b7a09bc1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.0" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '2.3', + 'SWIFT_VERSION' => '3.0', } s.subspec 'standard' do |ss| From 33f712dc30571fd631175675a8aecf604f612eaa Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 17 Sep 2016 17:57:51 +0100 Subject: [PATCH 0434/1046] Set iOS simulator --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index ada13822..d280766b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: objective-c rvm: 2.2 osx_image: xcode8 +env: + global: + - IOS_SIMULATOR="iPhone SE" matrix: include: - env: BUILD_SCHEME="SQLite iOS" From 183726692204ba437541ea378ad90c99644472db Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Sep 2016 10:48:58 +0100 Subject: [PATCH 0435/1046] Swift3 enum casing --- Documentation/Index.md | 34 +++++++++++++++---------------- SQLite/Core/Connection.swift | 8 ++++---- SQLite/Typed/Query.swift | 20 +++++++++--------- SQLite/Typed/Schema.swift | 12 +++++------ SQLiteTests/ConnectionTests.swift | 6 +++--- SQLiteTests/QueryTests.swift | 10 ++++----- SQLiteTests/SchemaTests.swift | 2 +- 7 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 0579e6f3..3063d095 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -192,7 +192,7 @@ On iOS, you can create a writable database in your app’s **Documents** directo ``` swift let path = NSSearchPathForDirectoriesInDomains( - .DocumentDirectory, .UserDomainMask, true + .documentDirectory, .userDomainMask, true ).first! let db = try Connection("\(path)/db.sqlite3") @@ -202,7 +202,7 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( - .ApplicationSupportDirectory, .UserDomainMask, true + .applicationSupportDirectory, .userDomainMask, true ).first! + NSBundle.mainBundle().bundleIdentifier! // create parent directory iff it doesn’t exist @@ -225,7 +225,7 @@ let db = try Connection(path, readonly: true) ``` > _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). -> +> > See these two Stack Overflow questions for more information about iOS apps with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). We welcome sample code to show how to successfully copy and use a bundled "seed" database for writing in an app. #### In-Memory Databases @@ -233,13 +233,13 @@ let db = try Connection(path, readonly: true) If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). ``` swift -let db = try Connection() // equivalent to `Connection(.InMemory)` +let db = try Connection() // equivalent to `Connection(.inMemory)` ``` To create a temporary, disk-backed database, pass an empty file name. ``` swift -let db = try Connection(.Temporary) +let db = try Connection(.temporary) ``` In-memory databases are automatically deleted when the database connection is closed. @@ -367,7 +367,7 @@ The `column` function is used for a single column definition. It takes an [expre t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL - t.column(id, primaryKey: .Autoincrement) + t.column(id, primaryKey: .autoincrement) // "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ``` @@ -375,7 +375,7 @@ The `column` function is used for a single column definition. It takes an [expre > > Primary keys cannot be optional (_e.g._, `Expression`). > - > Only an `INTEGER PRIMARY KEY` can take `.Autoincrement`. + > Only an `INTEGER PRIMARY KEY` can take `.autoincrement`. - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns). @@ -403,10 +403,10 @@ The `column` function is used for a single column definition. It takes an [expre - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - t.column(email, collate: .Nocase) + t.column(email, collate: .nocase) // "email" TEXT NOT NULL COLLATE "NOCASE" - t.column(name, collate: .Rtrim) + t.column(name, collate: .rtrim) // "name" TEXT COLLATE "RTRIM" ``` @@ -454,7 +454,7 @@ Additional constraints may be provided outside the scope of a single column usin - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. ``` swift - t.foreignKey(user_id, references: users, id, delete: .SetNull) + t.foreignKey(user_id, references: users, id, delete: .setNull) // FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL ``` @@ -471,7 +471,7 @@ We can insert rows into a table by calling a [query’s](#queries) `insert` func try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') -try db.run(users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.")) +try db.run(users.insert(or: .replace, email <- "alice@mac.com", name <- "Alice B.")) // INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` @@ -637,7 +637,7 @@ users.join(posts, on: user_id == users[id]) // SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` -The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.Inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing). +The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing). ##### Column Namespacing @@ -996,10 +996,10 @@ The `addColumn` function shares several of the same [`column` function parameter - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - try db.run(users.addColumn(email, collate: .Nocase)) + try db.run(users.addColumn(email, collate: .nocase)) // ALTER TABLE "users" ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" - try db.run(users.addColumn(name, collate: .Rtrim)) + try db.run(users.addColumn(name, collate: .rtrim)) // ALTER TABLE "users" ADD COLUMN "name" TEXT COLLATE "RTRIM" ``` @@ -1367,14 +1367,14 @@ We can create custom collating sequences by calling `createCollation` on a datab ``` swift try db.createCollation("NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + return lhs.compare(rhs, options: .diacriticInsensitiveSearch) } ``` We can reference a custom collation using the `Custom` member of the `Collation` enumeration. ``` swift -restaurants.order(collate(.Custom("NODIACRITIC"), name)) +restaurants.order(collate(.custom("NODIACRITIC"), name)) // SELECT * FROM "restaurants" ORDER BY "name" COLLATE "NODIACRITIC" ``` @@ -1409,7 +1409,7 @@ let config = FTS4Config() .column(subject) .column(body, [.unindexed]) .languageId("lid") - .order(.Desc) + .order(.desc) try db.run(emails.create(.FTS4(config)) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc") diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 832910ba..5acc44d2 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -274,13 +274,13 @@ public final class Connection { public enum TransactionMode : String { /// Defers locking the database till the first read/write executes. - case Deferred = "DEFERRED" + case deferred = "DEFERRED" /// Immediately acquires a reserved lock on the database. - case Immediate = "IMMEDIATE" + case immediate = "IMMEDIATE" /// Immediately acquires an exclusive lock on all databases. - case Exclusive = "EXCLUSIVE" + case exclusive = "EXCLUSIVE" } @@ -301,7 +301,7 @@ public final class Connection { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(_ mode: TransactionMode = .Deferred, block: @escaping () throws -> Void) throws { + public func transaction(_ mode: TransactionMode = .deferred, block: @escaping () throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index d0e4572d..0e54392b 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -221,7 +221,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ table: QueryType, on condition: Expression) -> Self { - return join(.Inner, table, on: condition) + return join(.inner, table, on: condition) } /// Adds a `JOIN` clause to the query. @@ -401,7 +401,7 @@ extension QueryType { public func order(_ by: Expressible...) -> Self { return order(by) } - + /// Sets an `ORDER BY` clause on the query. /// /// let users = Table("users") @@ -1100,28 +1100,28 @@ public struct Row { public enum JoinType : String { /// A `CROSS` join. - case Cross = "CROSS" + case cross = "CROSS" /// An `INNER` join. - case Inner = "INNER" + case inner = "INNER" /// A `LEFT OUTER` join. - case LeftOuter = "LEFT OUTER" + case leftOuter = "LEFT OUTER" } /// ON CONFLICT resolutions. public enum OnConflict: String { - case Replace = "REPLACE" + case replace = "REPLACE" - case Rollback = "ROLLBACK" + case rollback = "ROLLBACK" - case Abort = "ABORT" + case abort = "ABORT" - case Fail = "FAIL" + case fail = "FAIL" - case Ignore = "IGNORE" + case ignore = "IGNORE" } diff --git a/SQLite/Typed/Schema.swift b/SQLite/Typed/Schema.swift index 4c6862c0..1388170e 100644 --- a/SQLite/Typed/Schema.swift +++ b/SQLite/Typed/Schema.swift @@ -369,16 +369,16 @@ public final class TableBuilder { public enum Dependency: String { - case NoAction = "NO ACTION" + case noAction = "NO ACTION" - case Restrict = "RESTRICT" + case restrict = "RESTRICT" - case SetNull = "SET NULL" + case setNull = "SET NULL" - case SetDefault = "SET DEFAULT" + case setDefault = "SET DEFAULT" + + case cascade = "CASCADE" - case Cascade = "CASCADE" - } public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 0e8ebea6..c217eac7 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -95,19 +95,19 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_executesBeginDeferred() { - try! db.transaction(.Deferred) {} + try! db.transaction(.deferred) {} AssertSQL("BEGIN DEFERRED TRANSACTION") } func test_transaction_executesBeginImmediate() { - try! db.transaction(.Immediate) {} + try! db.transaction(.immediate) {} AssertSQL("BEGIN IMMEDIATE TRANSACTION") } func test_transaction_executesBeginExclusive() { - try! db.transaction(.Exclusive) {} + try! db.transaction(.exclusive) {} AssertSQL("BEGIN EXCLUSIVE TRANSACTION") } diff --git a/SQLiteTests/QueryTests.swift b/SQLiteTests/QueryTests.swift index df2ff073..ed34921e 100644 --- a/SQLiteTests/QueryTests.swift +++ b/SQLiteTests/QueryTests.swift @@ -20,11 +20,11 @@ class QueryTests : XCTestCase { func test_select_withExpression_compilesSelectClause() { AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) } - + func test_select_withStarExpression_compilesSelectClause() { AssertSQL("SELECT * FROM \"users\"", users.select(*)) } - + func test_select_withNamespacedStarExpression_compilesSelectClause() { AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) } @@ -59,12 +59,12 @@ class QueryTests : XCTestCase { func test_join_withExplicitType_compilesJoinClauseWithType() { AssertSQL( "SELECT * FROM \"users\" LEFT OUTER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", - users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + users.join(.leftOuter, posts, on: posts[userId] == users[id]) ) AssertSQL( "SELECT * FROM \"users\" CROSS JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", - users.join(.Cross, posts, on: posts[userId] == users[id]) + users.join(.cross, posts, on: posts[userId] == users[id]) ) } @@ -192,7 +192,7 @@ class QueryTests : XCTestCase { func test_insert_withOnConflict_compilesInsertOrOnConflictExpression() { AssertSQL( "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", - users.insert(or: .Replace, email <- "alice@example.com", age <- 30) + users.insert(or: .replace, email <- "alice@example.com", age <- 30) ) } diff --git a/SQLiteTests/SchemaTests.swift b/SQLiteTests/SchemaTests.swift index 01bedda0..371459c7 100644 --- a/SQLiteTests/SchemaTests.swift +++ b/SQLiteTests/SchemaTests.swift @@ -568,7 +568,7 @@ class SchemaTests : XCTestCase { XCTAssertEqual( "CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\") ON UPDATE CASCADE ON DELETE SET NULL)", - table.create { t in t.foreignKey(string, references: table, string, update: .Cascade, delete: .SetNull) } + table.create { t in t.foreignKey(string, references: table, string, update: .cascade, delete: .setNull) } ) XCTAssertEqual( From 4ed14adaefa1ed308ff949ae5e2cff0dc2b8d4b2 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Sep 2016 11:59:50 +0100 Subject: [PATCH 0436/1046] Nest Operation inside Connection --- SQLite/Core/Connection.swift | 53 +++++++++++++++---------------- SQLiteTests/ConnectionTests.swift | 6 ++-- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 5acc44d2..2a7ba3b0 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -54,6 +54,32 @@ public final class Connection { case uri(String) } + /// An SQL operation passed to update callbacks. + public enum Operation { + + /// An INSERT operation. + case insert + + /// An UPDATE operation. + case update + + /// A DELETE operation. + case delete + + fileprivate init(rawValue:Int32) { + switch rawValue { + case SQLITE_INSERT: + self = .insert + case SQLITE_UPDATE: + self = .update + case SQLITE_DELETE: + self = .delete + default: + fatalError("unhandled operation code: \(rawValue)") + } + } + } + public var handle: OpaquePointer { return _handle! } fileprivate var _handle: OpaquePointer? = nil @@ -638,33 +664,6 @@ extension Connection.Location : CustomStringConvertible { } -/// An SQL operation passed to update callbacks. -public enum Operation { - - /// An INSERT operation. - case insert - - /// An UPDATE operation. - case update - - /// A DELETE operation. - case delete - - fileprivate init(rawValue: Int32) { - switch rawValue { - case SQLITE_INSERT: - self = .insert - case SQLITE_UPDATE: - self = .update - case SQLITE_DELETE: - self = .delete - default: - fatalError("unhandled operation code: \(rawValue)") - } - } - -} - public enum Result : Error { fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index c217eac7..4db671ad 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -192,7 +192,7 @@ class ConnectionTests : SQLiteTestCase { func test_updateHook_setsUpdateHook_withInsert() { async { done in db.updateHook { operation, db, table, rowid in - XCTAssertEqual(Operation.insert, operation) + XCTAssertEqual(Connection.Operation.insert, operation) XCTAssertEqual("main", db) XCTAssertEqual("users", table) XCTAssertEqual(1, rowid) @@ -206,7 +206,7 @@ class ConnectionTests : SQLiteTestCase { _ = try! InsertUser("alice") async { done in db.updateHook { operation, db, table, rowid in - XCTAssertEqual(Operation.update, operation) + XCTAssertEqual(Connection.Operation.update, operation) XCTAssertEqual("main", db) XCTAssertEqual("users", table) XCTAssertEqual(1, rowid) @@ -220,7 +220,7 @@ class ConnectionTests : SQLiteTestCase { _ = try! InsertUser("alice") async { done in db.updateHook { operation, db, table, rowid in - XCTAssertEqual(Operation.delete, operation) + XCTAssertEqual(Connection.Operation.delete, operation) XCTAssertEqual("main", db) XCTAssertEqual("users", table) XCTAssertEqual(1, rowid) From 2c9fd0c1417bec5878226c045ae7ac86b7644bc0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 6 Oct 2016 22:22:29 +0200 Subject: [PATCH 0437/1046] Flag methods executed for side-effects to silence warnings --- SQLite/Core/Connection.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 2a7ba3b0..04b515c3 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -216,7 +216,7 @@ public final class Connection { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement. - public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { + @discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { return try run(statement, bindings) } @@ -231,7 +231,7 @@ public final class Connection { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement. - public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { + @discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } @@ -246,7 +246,7 @@ public final class Connection { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement. - public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { + @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } From 8f8eec7b4c81b7547ee90bb2e51a235c9b02e222 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 7 Oct 2016 12:48:29 +0200 Subject: [PATCH 0438/1046] Add tests for Blob initializer --- SQLiteTests/BlobTests.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/SQLiteTests/BlobTests.swift b/SQLiteTests/BlobTests.swift index ba0a7c54..fbcca9bc 100644 --- a/SQLiteTests/BlobTests.swift +++ b/SQLiteTests/BlobTests.swift @@ -9,4 +9,15 @@ class BlobTests : XCTestCase { XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") } + func test_init_array() { + let blob = Blob(bytes: [42, 42, 42]) + XCTAssertEqual(blob.bytes, [42, 42, 42]) + } + + func test_init_unsafeRawPointer() { + let pointer = UnsafeMutablePointer.allocate(capacity: 3) + pointer.initialize(to: 42, count: 3) + let blob = Blob(bytes: pointer, length: 3) + XCTAssertEqual(blob.bytes, [42, 42, 42]) + } } From b6941cda744c6cb163180eb5c8dead1fa76d6c77 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 7 Oct 2016 15:03:53 +0200 Subject: [PATCH 0439/1046] Add more tests for blob handling --- SQLite.xcodeproj/project.pbxproj | 8 ++++++++ SQLite/Core/Blob.swift | 1 - SQLite/Core/Statement.swift | 9 ++++++--- SQLite/Foundation.swift | 6 +++--- SQLiteTests/FoundationTests.swift | 16 ++++++++++++++++ SQLiteTests/StatementTests.swift | 14 +++++++++++++- 6 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 SQLiteTests/FoundationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index aa951fd8..4dddeaaf 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -51,7 +51,10 @@ 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -162,6 +165,7 @@ 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; + 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -415,6 +419,7 @@ EE247B161C3F127200AE3E12 /* TestHelpers.swift */, EE247AE41C3F04ED00AE3E12 /* Info.plist */, 19A1721B8984686B9963B45D /* FTS5Tests.swift */, + 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, ); path = SQLiteTests; sourceTree = ""; @@ -824,6 +829,7 @@ 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, + 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -880,6 +886,7 @@ EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, + 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -929,6 +936,7 @@ EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, + 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite/Core/Blob.swift b/SQLite/Core/Blob.swift index 45d6604a..2f5d2a14 100644 --- a/SQLite/Core/Blob.swift +++ b/SQLite/Core/Blob.swift @@ -31,7 +31,6 @@ public struct Blob { } public init(bytes: UnsafeRawPointer, length: Int) { - // TODO correct count let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length) self.init(bytes: [UInt8](i8bufptr)) } diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 01720433..f0c05bb8 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -238,9 +238,12 @@ public struct Cursor { } public subscript(idx: Int) -> Blob { - let bytes = sqlite3_column_blob(handle, Int32(idx)) - let length = Int(sqlite3_column_bytes(handle, Int32(idx))) - return Blob(bytes: bytes!, length: length) + if let pointer = sqlite3_column_blob(handle, Int32(idx)) { + let length = Int(sqlite3_column_bytes(handle, Int32(idx))) + return Blob(bytes: pointer, length: length) + } else { + fatalError("sqlite3_column_blob returned NULL") + } } // MARK: - diff --git a/SQLite/Foundation.swift b/SQLite/Foundation.swift index 5c266a05..5e102297 100644 --- a/SQLite/Foundation.swift +++ b/SQLite/Foundation.swift @@ -31,12 +31,12 @@ extension Data : Value { } public static func fromDatatypeValue(_ dataValue: Blob) -> Data { - return Data(bytes: UnsafePointer(dataValue.bytes), count: dataValue.bytes.count) + return Data(bytes: dataValue.bytes) } public var datatypeValue: Blob { - return withUnsafeBytes { ptr -> Blob in - return Blob(bytes: ptr, length: count) + return withUnsafeBytes { (pointer: UnsafePointer) -> Blob in + return Blob(bytes: pointer, length: count) } } diff --git a/SQLiteTests/FoundationTests.swift b/SQLiteTests/FoundationTests.swift new file mode 100644 index 00000000..0df746d9 --- /dev/null +++ b/SQLiteTests/FoundationTests.swift @@ -0,0 +1,16 @@ +import XCTest +import SQLite + +class FoundationTests : XCTestCase { + func testDataFromBlob() { + let data = Data(bytes: [1, 2, 3]) + let blob = data.datatypeValue + XCTAssertEqual([1, 2, 3], blob.bytes) + } + + func testBlobToData() { + let blob = Blob(bytes: [1, 2, 3]) + let data = Data.fromDatatypeValue(blob) + XCTAssertEqual(Data(bytes: [1, 2, 3]), data) + } +} diff --git a/SQLiteTests/StatementTests.swift b/SQLiteTests/StatementTests.swift index 5965929c..fce3585c 100644 --- a/SQLiteTests/StatementTests.swift +++ b/SQLiteTests/StatementTests.swift @@ -1,5 +1,17 @@ import XCTest import SQLite -class StatementTests : XCTestCase { +class StatementTests : SQLiteTestCase { + override func setUp() { + super.setUp() + CreateUsersTable() + } + + func test_cursor_to_blob() { + try! InsertUsers("alice") + let statement = try! db.prepare("SELECT email FROM users") + XCTAssert(try! statement.step()) + let blob = statement.row[0] as Blob + XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + } } From f7d017d858a6e0dc5b5bbc81974d3caca04b619c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 8 Oct 2016 13:44:47 +0200 Subject: [PATCH 0440/1046] use @discardableResult --- SQLite/Core/Connection.swift | 12 +++---- SQLite/Core/Statement.swift | 8 ++--- SQLite/Extensions/FTS4.swift | 28 +++++++-------- SQLite/Extensions/FTS5.swift | 6 ++-- SQLite/Typed/Query.swift | 6 ++-- SQLiteTests/ConnectionTests.swift | 58 +++++++++++++++---------------- SQLiteTests/FTS4Tests.swift | 4 +-- SQLiteTests/TestHelpers.swift | 6 ++-- 8 files changed, 64 insertions(+), 64 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 04b515c3..bd4cafb3 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -100,7 +100,7 @@ public final class Connection { /// - Returns: A new database connection. public init(_ location: Location = .inMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - _ = try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) queue.setSpecific(key: /*Migrator FIXME: Use a variable of type DispatchSpecificKey*/ Connection.queueKey, value: queueContext) } @@ -356,14 +356,14 @@ public final class Connection { fileprivate func transaction(_ begin: String, _ block: @escaping () throws -> Void, _ commit: String, or rollback: String) throws { return try sync { - _ = try self.run(begin) + try self.run(begin) do { try block() } catch { - _ = try self.run(rollback) + try self.run(rollback) throw error } - _ = try self.run(commit) + try self.run(commit) } } @@ -590,7 +590,7 @@ public final class Connection { let rstr = String(cString: rhs.bindMemory(to: UInt8.self, capacity: 0)) return Int32(Int(block(lstr, rstr).rawValue)) } - _ = try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { callback, _, lhs, _, rhs in + try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { callback, _, lhs, _, rhs in unsafeBitCast(callback, to: Collation.self)(lhs!, rhs!) }, nil)) collations[collation] = box @@ -625,7 +625,7 @@ public final class Connection { return success! } - func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { + @discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else { return resultCode } diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index f0c05bb8..a7664993 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -37,7 +37,7 @@ public final class Statement { init(_ connection: Connection, _ SQL: String) throws { self.connection = connection - _ = try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) + try connection.check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) } deinit { @@ -120,7 +120,7 @@ public final class Statement { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement object (useful for chaining). - public func run(_ bindings: Binding?...) throws -> Statement { + @discardableResult public func run(_ bindings: Binding?...) throws -> Statement { guard bindings.isEmpty else { return try run(bindings) } @@ -135,7 +135,7 @@ public final class Statement { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement object (useful for chaining). - public func run(_ bindings: [Binding?]) throws -> Statement { + @discardableResult public func run(_ bindings: [Binding?]) throws -> Statement { return try bind(bindings).run() } @@ -145,7 +145,7 @@ public final class Statement { /// - Throws: `Result.Error` if query execution fails. /// /// - Returns: The statement object (useful for chaining). - public func run(_ bindings: [String: Binding?]) throws -> Statement { + @discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement { return try bind(bindings).run() } diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index bd8c7b2d..ecaf2507 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -141,7 +141,7 @@ extension Tokenizer : CustomStringConvertible { extension Connection { public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range)?) throws { - _ = try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in let string = String(cString: input) guard let (token, range) = next(string) else { return nil } @@ -178,7 +178,7 @@ open class FTSConfig { open func columns(_ columns: [Expressible]) -> Self { for column in columns { - _ = self.column(column) + self.column(column) } return self } @@ -217,15 +217,15 @@ open class FTSConfig { func options() -> Options { var options = Options() - _ = options.append(formatColumnDefinitions()) + options.append(formatColumnDefinitions()) if let tokenizer = tokenizer { - _ = options.append("tokenize", value: Expression(literal: tokenizer.description)) + options.append("tokenize", value: Expression(literal: tokenizer.description)) } - _ = options.appendCommaSeparated("prefix", values:prefixes.sorted().map { String($0) }) + options.appendCommaSeparated("prefix", values:prefixes.sorted().map { String($0) }) if isContentless { - _ = options.append("content", value: "") + options.append("content", value: "") } else if let externalContentSchema = externalContentSchema { - _ = options.append("content", value: externalContentSchema.tableName()) + options.append("content", value: externalContentSchema.tableName()) } return options } @@ -238,7 +238,7 @@ open class FTSConfig { return self } - mutating func appendCommaSeparated(_ key: String, values: [String]) -> Options { + @discardableResult mutating func appendCommaSeparated(_ key: String, values: [String]) -> Options { if values.isEmpty { return self } else { @@ -246,7 +246,7 @@ open class FTSConfig { } } - mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { + @discardableResult mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { return append(key, value: value?.description) } @@ -332,11 +332,11 @@ open class FTS4Config : FTSConfig { for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { options.append("notindexed", value: column) } - _ = options.append("languageid", value: languageId) - _ = options.append("compress", value: compressFunction) - _ = options.append("uncompress", value: uncompressFunction) - _ = options.append("matchinfo", value: matchInfo) - _ = options.append("order", value: order) + options.append("languageid", value: languageId) + options.append("compress", value: compressFunction) + options.append("uncompress", value: uncompressFunction) + options.append("matchinfo", value: matchInfo) + options.append("order", value: order) return options } } diff --git a/SQLite/Extensions/FTS5.swift b/SQLite/Extensions/FTS5.swift index 9204d5b2..763927ff 100644 --- a/SQLite/Extensions/FTS5.swift +++ b/SQLite/Extensions/FTS5.swift @@ -77,11 +77,11 @@ open class FTS5Config : FTSConfig { override func options() -> Options { var options = super.options() - _ = options.append("content_rowid", value: contentRowId) + options.append("content_rowid", value: contentRowId) if let columnSize = columnSize { - _ = options.append("columnsize", value: Expression(value: columnSize)) + options.append("columnsize", value: Expression(value: columnSize)) } - _ = options.append("detail", value: detail) + options.append("detail", value: detail) return options } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 0e54392b..af6549de 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -971,7 +971,7 @@ extension Connection { public func run(_ query: Insert) throws -> Int64 { let expression = query.expression return try sync { - _ = try self.run(expression.template, expression.bindings) + try self.run(expression.template, expression.bindings) return self.lastInsertRowid! } } @@ -987,7 +987,7 @@ extension Connection { public func run(_ query: Update) throws -> Int { let expression = query.expression return try sync { - _ = try self.run(expression.template, expression.bindings) + try self.run(expression.template, expression.bindings) return self.changes } } @@ -1002,7 +1002,7 @@ extension Connection { public func run(_ query: Delete) throws -> Int { let expression = query.expression return try sync { - _ = try self.run(expression.template, expression.bindings) + try self.run(expression.template, expression.bindings) return self.changes } } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 4db671ad..3eb2b915 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -48,7 +48,7 @@ class ConnectionTests : SQLiteTestCase { } func test_lastInsertRowid_returnsLastIdAfterInserts() { - _ = try! InsertUser("alice") + try! InsertUser("alice") XCTAssertEqual(1, db.lastInsertRowid!) } @@ -57,17 +57,17 @@ class ConnectionTests : SQLiteTestCase { } func test_changes_returnsNumberOfChanges() { - _ = try! InsertUser("alice") + try! InsertUser("alice") XCTAssertEqual(1, db.changes) - _ = try! InsertUser("betsy") + try! InsertUser("betsy") XCTAssertEqual(1, db.changes) } func test_totalChanges_returnsTotalNumberOfChanges() { XCTAssertEqual(0, db.totalChanges) - _ = try! InsertUser("alice") + try! InsertUser("alice") XCTAssertEqual(1, db.totalChanges) - _ = try! InsertUser("betsy") + try! InsertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -79,10 +79,10 @@ class ConnectionTests : SQLiteTestCase { } func test_run_preparesRunsAndReturnsStatements() { - _ = try! db.run("SELECT * FROM users WHERE admin = 0") - _ = try! db.run("SELECT * FROM users WHERE admin = ?", 0) - _ = try! db.run("SELECT * FROM users WHERE admin = ?", [0]) - _ = try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + try! db.run("SELECT * FROM users WHERE admin = 0") + try! db.run("SELECT * FROM users WHERE admin = ?", 0) + try! db.run("SELECT * FROM users WHERE admin = ?", [0]) + try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) AssertSQL("SELECT * FROM users WHERE admin = 0", 4) } @@ -116,7 +116,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") try! db.transaction { - _ = try stmt.run() + try stmt.run() } AssertSQL("BEGIN DEFERRED TRANSACTION") @@ -130,8 +130,8 @@ class ConnectionTests : SQLiteTestCase { do { try db.transaction { - _ = try stmt.run() - _ = try stmt.run() + try stmt.run() + try stmt.run() } } catch { } @@ -147,7 +147,7 @@ class ConnectionTests : SQLiteTestCase { try! db.savepoint("1") { try db.savepoint("2") { - _ = try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") + try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") } } @@ -167,14 +167,14 @@ class ConnectionTests : SQLiteTestCase { do { try db.savepoint("1") { try db.savepoint("2") { - _ = try stmt.run() - _ = try stmt.run() - _ = try stmt.run() + try stmt.run() + try stmt.run() + try stmt.run() } try db.savepoint("2") { - _ = try stmt.run() - _ = try stmt.run() - _ = try stmt.run() + try stmt.run() + try stmt.run() + try stmt.run() } } } catch { @@ -198,12 +198,12 @@ class ConnectionTests : SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - _ = try! InsertUser("alice") + try! InsertUser("alice") } } func test_updateHook_setsUpdateHook_withUpdate() { - _ = try! InsertUser("alice") + try! InsertUser("alice") async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.update, operation) @@ -212,12 +212,12 @@ class ConnectionTests : SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - _ = try! db.run("UPDATE users SET email = 'alice@example.com'") + try! db.run("UPDATE users SET email = 'alice@example.com'") } } func test_updateHook_setsUpdateHook_withDelete() { - _ = try! InsertUser("alice") + try! InsertUser("alice") async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.delete, operation) @@ -226,7 +226,7 @@ class ConnectionTests : SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - _ = try! db.run("DELETE FROM users WHERE id = 1") + try! db.run("DELETE FROM users WHERE id = 1") } } @@ -236,7 +236,7 @@ class ConnectionTests : SQLiteTestCase { done() } try! db.transaction { - _ = try self.InsertUser("alice") + try self.InsertUser("alice") } XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } @@ -247,8 +247,8 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - _ = try self.InsertUser("alice") - _ = try self.InsertUser("alice") // throw + try self.InsertUser("alice") + try self.InsertUser("alice") // throw } } catch { } @@ -264,7 +264,7 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - _ = try self.InsertUser("alice") + try self.InsertUser("alice") } } catch { } @@ -308,7 +308,7 @@ class ConnectionTests : SQLiteTestCase { } let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) - _ = try! stmt.run() + try! stmt.run() _ = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).asyncAfter(deadline: DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC), execute: db.interrupt) AssertThrows(try stmt.run()) diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index 92f197a2..0148efc3 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -198,10 +198,10 @@ class FTS4IntegrationTests : SQLiteTestCase { return (token as String, string.range(of: input as String)!) } - _ = try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) + try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") - _ = try! db.run(emails.insert(subject <- "Aún más cáfe!")) + try! db.run(emails.insert(subject <- "Aún más cáfe!")) XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) } #endif diff --git a/SQLiteTests/TestHelpers.swift b/SQLiteTests/TestHelpers.swift index 7b7715b1..8c33bf6a 100644 --- a/SQLiteTests/TestHelpers.swift +++ b/SQLiteTests/TestHelpers.swift @@ -37,10 +37,10 @@ class SQLiteTestCase : XCTestCase { } func InsertUsers(_ names: [String]) throws { - for name in names { _ = try InsertUser(name) } + for name in names { try InsertUser(name) } } - func InsertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { + @discardableResult func InsertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { return try db.run( "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", "\(name)@example.com", age?.datatypeValue, admin.datatypeValue @@ -56,7 +56,7 @@ class SQLiteTestCase : XCTestCase { } func AssertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { - _ = try! statement.run() + try! statement.run() AssertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } } From 6bf844a1c6c2c6bbeadaab594124fc9874026ded Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 8 Oct 2016 13:59:55 +0200 Subject: [PATCH 0441/1046] Fix warnings --- SQLite/Core/Trace.swift | 2 ++ SQLite/Extensions/FTS4.swift | 10 +++++----- SQLite/Typed/Query.swift | 2 +- SQLiteTests/ConnectionTests.swift | 2 +- SQLiteTests/FTS4Tests.swift | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 SQLite/Core/Trace.swift diff --git a/SQLite/Core/Trace.swift b/SQLite/Core/Trace.swift new file mode 100644 index 00000000..350c82fa --- /dev/null +++ b/SQLite/Core/Trace.swift @@ -0,0 +1,2 @@ + +import Foundation diff --git a/SQLite/Extensions/FTS4.swift b/SQLite/Extensions/FTS4.swift index ecaf2507..7bbca7a5 100644 --- a/SQLite/Extensions/FTS4.swift +++ b/SQLite/Extensions/FTS4.swift @@ -171,12 +171,12 @@ open class FTSConfig { var isContentless: Bool = false /// Adds a column definition - open func column(_ column: Expressible, _ options: [ColumnOption] = []) -> Self { + @discardableResult open func column(_ column: Expressible, _ options: [ColumnOption] = []) -> Self { self.columnDefinitions.append((column, options)) return self } - open func columns(_ columns: [Expressible]) -> Self { + @discardableResult open func columns(_ columns: [Expressible]) -> Self { for column in columns { self.column(column) } @@ -233,7 +233,7 @@ open class FTSConfig { struct Options { var arguments = [Expressible]() - mutating func append(_ columns: [Expressible]) -> Options { + @discardableResult mutating func append(_ columns: [Expressible]) -> Options { arguments.append(contentsOf: columns) return self } @@ -250,11 +250,11 @@ open class FTSConfig { return append(key, value: value?.description) } - mutating func append(_ key: String, value: String?) -> Options { + @discardableResult mutating func append(_ key: String, value: String?) -> Options { return append(key, value: value.map { Expression($0) }) } - mutating func append(_ key: String, value: Expressible?) -> Options { + @discardableResult mutating func append(_ key: String, value: Expressible?) -> Options { if let value = value { arguments.append("=".join([Expression(literal: key), value])) } diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index af6549de..75f29afd 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -886,7 +886,7 @@ extension Connection { let columnNames: [String: Int] = try { var (columnNames, idx) = ([String: Int](), 0) - column: for each in query.clauses.select.columns ?? [Expression(literal: "*")] { + column: for each in query.clauses.select.columns { var names = each.expression.template.characters.split { $0 == "." }.map(String.init) let column = names.removeLast() let namespace = names.joined(separator: ".") diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 3eb2b915..1d18bdca 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -310,7 +310,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) try! stmt.run() - _ = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).asyncAfter(deadline: DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC), execute: db.interrupt) + _ = DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC), execute: db.interrupt) AssertThrows(try stmt.run()) } diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index 0148efc3..b2ccc86c 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -201,7 +201,7 @@ class FTS4IntegrationTests : SQLiteTestCase { try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") - try! db.run(emails.insert(subject <- "Aún más cáfe!")) + try! _ = db.run(emails.insert(subject <- "Aún más cáfe!")) XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) } #endif From 15490620c798e7715b43acec889af80230deedd6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 8 Oct 2016 14:01:06 +0200 Subject: [PATCH 0442/1046] Accidental commit --- SQLite/Core/Trace.swift | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 SQLite/Core/Trace.swift diff --git a/SQLite/Core/Trace.swift b/SQLite/Core/Trace.swift deleted file mode 100644 index 350c82fa..00000000 --- a/SQLite/Core/Trace.swift +++ /dev/null @@ -1,2 +0,0 @@ - -import Foundation From 5bc521eb8abd7f125a0fab8338f61064e19811d7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 8 Oct 2016 14:39:51 +0200 Subject: [PATCH 0443/1046] Only available on OS X 10.10 or newer --- SQLiteTests/ConnectionTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 1d18bdca..33fa113d 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -310,7 +310,8 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) try! stmt.run() - _ = DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC), execute: db.interrupt) + let deadline = DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC) + _ = DispatchQueue.global(priority: .background).asyncAfter(deadline: deadline, execute: db.interrupt) AssertThrows(try stmt.run()) } From f11f6b8985bc5da530022a047f51b6d5585a72a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 9 Oct 2016 08:52:59 +0200 Subject: [PATCH 0444/1046] cleanup --- SQLite/Core/Connection.swift | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index bd4cafb3..ad97466b 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -584,15 +584,20 @@ public final class Connection { /// - block: A collation function that takes two strings and returns the /// comparison result. public func createCollation(_ collation: String, _ block: @escaping (_ lhs: String, _ rhs: String) -> ComparisonResult) throws { - // TODO correct capacity - let box: Collation = { lhs, rhs in - let lstr = String(cString: lhs.bindMemory(to: UInt8.self, capacity: 0)) - let rstr = String(cString: rhs.bindMemory(to: UInt8.self, capacity: 0)) - return Int32(Int(block(lstr, rstr).rawValue)) + let box: Collation = { (lhs:UnsafeRawPointer, rhs:UnsafeRawPointer) in + let lstr = String(cString: lhs.assumingMemoryBound(to: UInt8.self)) + let rstr = String(cString: rhs.assumingMemoryBound(to: UInt8.self)) + return Int32(block(lstr, rstr).rawValue) } - try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { callback, _, lhs, _, rhs in - unsafeBitCast(callback, to: Collation.self)(lhs!, rhs!) - }, nil)) + try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self), + { (callback:UnsafeMutableRawPointer?, _, lhs:UnsafeRawPointer?, _, rhs:UnsafeRawPointer?) in /* xCompare */ + if let lhs = lhs, let rhs = rhs { + return unsafeBitCast(callback, to: Collation.self)(lhs, rhs) + } else { + fatalError("sqlite3_create_collation_v2 callback called with NULL pointer") + } + }, nil /* xDestroy */)) collations[collation] = box } fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32 From 6152ee3c17abe952ebea291f8d04557086b2ab07 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 9 Oct 2016 16:20:19 +0200 Subject: [PATCH 0445/1046] Documentation updates --- Documentation/Index.md | 42 +++++++++++++++++++++--------------------- README.md | 4 ++-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3063d095..8b136246 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -65,7 +65,7 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 2 (and [Xcode 7](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 3 (and [Xcode 8](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -203,10 +203,10 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true -).first! + NSBundle.mainBundle().bundleIdentifier! +).first! + Bundle.main.bundleIdentifier! // create parent directory iff it doesn’t exist -try NSFileManager.defaultManager().createDirectoryAtPath( +try FileManager.default.createDirectoryAtPath( path, withIntermediateDirectories: true, attributes: nil ) @@ -219,7 +219,7 @@ let db = try Connection("\(path)/db.sqlite3") If you bundle a database with your app (_i.e._, you’ve copied a database file into your Xcode project and added it to your application target), you can establish a _read-only_ connection to it. ``` swift -let path = NSBundle.mainBundle().pathForResource("db", ofType: "sqlite3")! +let path = Bundle.main.pathForResource("db", ofType: "sqlite3")! let db = try Connection(path, readonly: true) ``` @@ -1114,16 +1114,16 @@ Once extended, the type can be used [_almost_](#custom-type-caveats) wherever ty ### Date-Time Values -In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can transparently bridge `NSDate` objects through Swift’s `String` or `Int` types. +In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can transparently bridge `Date` objects through Swift’s `String` or `Int` types. -To serialize `NSDate` objects as `TEXT` values (in ISO 8601), we’ll use `String`. +To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use `String`. ``` swift -extension NSDate: Value { +extension Date: Value { class var declaredDatatype: String { return String.declaredDatatype } - class func fromDatatypeValue(stringValue: String) -> NSDate { + class func fromDatatypeValue(stringValue: String) -> Date { return SQLDateFormatter.dateFromString(stringValue)! } var datatypeValue: String { @@ -1131,11 +1131,11 @@ extension NSDate: Value { } } -let SQLDateFormatter: NSDateFormatter = { - let formatter = NSDateFormatter() +let SQLDateFormatter: DateFormatter = { + let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" - formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") - formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) + formatter.locale = Locale(localeIdentifier: "en_US_POSIX") + formatter.timeZone = TimeZone(forSecondsFromGMT: 0) return formatter }() ``` @@ -1143,12 +1143,12 @@ let SQLDateFormatter: NSDateFormatter = { We can also treat them as `INTEGER` values using `Int`. ``` swift -extension NSDate: Value { +extension Date: Value { class var declaredDatatype: String { return Int.declaredDatatype } class func fromDatatypeValue(intValue: Int) -> Self { - return self(timeIntervalSince1970: NSTimeInterval(intValue)) + return self(timeIntervalSince1970: TimeInterval(intValue)) } var datatypeValue: Int { return Int(timeIntervalSince1970) @@ -1161,9 +1161,9 @@ extension NSDate: Value { Once defined, we can use these types directly in SQLite statements. ``` swift -let published_at = Expression("published_at") +let published_at = Expression("published_at") -let published = posts.filter(published_at <= NSDate()) +let published = posts.filter(published_at <= Date()) // extension where Datatype == String: // SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18 12:45:30' // extension where Datatype == Int: @@ -1175,10 +1175,10 @@ let published = posts.filter(published_at <= NSDate()) Any object that can be encoded and decoded can be stored as a blob of data in SQL. -We can create an `NSData` bridge rather trivially. +We can create an `Data` bridge rather trivially. ``` swift -extension NSData: Value { +extension Data: Value { class var declaredDatatype: String { return Blob.declaredDatatype } @@ -1191,16 +1191,16 @@ extension NSData: Value { } ``` -We can bridge any type that can be initialized from and encoded to `NSData`. +We can bridge any type that can be initialized from and encoded to `Data`. ``` swift -// assumes NSData conformance, above +// assumes Data conformance, above extension UIImage: Value { public class var declaredDatatype: String { return Blob.declaredDatatype } public class func fromDatatypeValue(blobValue: Blob) -> UIImage { - return UIImage(data: NSData.fromDatatypeValue(blobValue))! + return UIImage(data: Data.fromDatatypeValue(blobValue))! } public var datatypeValue: Blob { return UIImagePNGRepresentation(self)!.datatypeValue diff --git a/README.md b/README.md index 53d654fb..bdd652f2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) +[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-3-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -109,7 +109,7 @@ For a more comprehensive example, see [this article](http://masteringswift.blogs ## Installation -> _Note:_ SQLite.swift requires Swift 2 (and [Xcode][] 7) or greater. +> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8) or greater. > > The following instructions apply to targets that support embedded > Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line From bbab40147c17ab1c68f5a57d68b564d97c8a4f1a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 9 Oct 2016 16:24:47 +0200 Subject: [PATCH 0446/1046] Remove FIXME --- SQLite/Core/Connection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index ad97466b..f95246fd 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -101,7 +101,7 @@ public final class Connection { public init(_ location: Location = .inMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) - queue.setSpecific(key: /*Migrator FIXME: Use a variable of type DispatchSpecificKey*/ Connection.queueKey, value: queueContext) + queue.setSpecific(key: Connection.queueKey, value: queueContext) } /// Initializes a new connection to a database. From 96238f558d776354ceeafe52d04bc17df280bb34 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 9 Oct 2016 16:36:41 +0200 Subject: [PATCH 0447/1046] sqlite3_trace --- SQLite/Core/Connection.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index f95246fd..d836f1d4 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -419,9 +419,13 @@ public final class Connection { } let box: Trace = { callback(String(cString: $0)) } - sqlite3_trace(handle, { callback, SQL in - unsafeBitCast(callback, to: Trace.self)(SQL!) - }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self)) + sqlite3_trace(handle, { + (callback: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in + guard let callback = callback, let SQL = SQL else { return } + unsafeBitCast(callback, to: Trace.self)(SQL) + }, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self) + ) trace = box } fileprivate typealias Trace = @convention(block) (UnsafePointer) -> Void @@ -584,14 +588,14 @@ public final class Connection { /// - block: A collation function that takes two strings and returns the /// comparison result. public func createCollation(_ collation: String, _ block: @escaping (_ lhs: String, _ rhs: String) -> ComparisonResult) throws { - let box: Collation = { (lhs:UnsafeRawPointer, rhs:UnsafeRawPointer) in + let box: Collation = { (lhs: UnsafeRawPointer, rhs: UnsafeRawPointer) in let lstr = String(cString: lhs.assumingMemoryBound(to: UInt8.self)) let rstr = String(cString: rhs.assumingMemoryBound(to: UInt8.self)) return Int32(block(lstr, rstr).rawValue) } try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), - { (callback:UnsafeMutableRawPointer?, _, lhs:UnsafeRawPointer?, _, rhs:UnsafeRawPointer?) in /* xCompare */ + { (callback: UnsafeMutableRawPointer?, _, lhs: UnsafeRawPointer?, _, rhs: UnsafeRawPointer?) in /* xCompare */ if let lhs = lhs, let rhs = rhs { return unsafeBitCast(callback, to: Collation.self)(lhs, rhs) } else { From b392eaf6620a662e477597518e240760f48a04cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 9 Oct 2016 16:42:54 +0200 Subject: [PATCH 0448/1046] Remove unneeded typealias --- SQLite/Core/Connection.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index d836f1d4..6aa27dc6 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -576,9 +576,6 @@ public final class Connection { fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate var functions = [String: [Int: Function]]() - /// The return type of a collation comparison function. - public typealias ComparisonResult = Foundation.ComparisonResult - /// Defines a new collating sequence. /// /// - Parameters: From 336faa1726887a90745e2949241394e41dd59be7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 8 Oct 2016 11:01:54 +0200 Subject: [PATCH 0449/1046] Use sqlite3_trace_v2 where available --- SQLite/Core/Connection.swift | 60 ++++++++++++++++++++++++++----- SQLiteTests/ConnectionTests.swift | 6 ++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 6aa27dc6..ff284ada 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -412,23 +412,67 @@ public final class Connection { /// /// db.trace { SQL in print(SQL) } public func trace(_ callback: ((String) -> Void)?) { + if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { + trace_v2(callback) + } else { + trace_v1(callback) + } + } + + fileprivate func trace_v1(_ callback: ((String) -> Void)?) { guard let callback = callback else { - sqlite3_trace(handle, nil, nil) + sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */) trace = nil return } - - let box: Trace = { callback(String(cString: $0)) } - sqlite3_trace(handle, { - (callback: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in - guard let callback = callback, let SQL = SQL else { return } - unsafeBitCast(callback, to: Trace.self)(SQL) + let box: Trace = { (pointer: UnsafeRawPointer) in + callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) + } + sqlite3_trace(handle, + { + (C: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in + if let C = C, let SQL = SQL { + unsafeBitCast(C, to: Trace.self)(SQL) + } }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self) ) trace = box } - fileprivate typealias Trace = @convention(block) (UnsafePointer) -> Void + + @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) + fileprivate func trace_v2(_ callback: ((String) -> Void)?) { + guard let callback = callback else { + // If the X callback is NULL or if the M mask is zero, then tracing is disabled. + sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) + trace = nil + return + } + + let box: Trace = { (pointer: UnsafeRawPointer) in + callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) + } + sqlite3_trace_v2(handle, + UInt32(SQLITE_TRACE_STMT) /* mask */, + { + // A trace callback is invoked with four arguments: callback(T,C,P,X). + // The T argument is one of the SQLITE_TRACE constants to indicate why the + // callback was invoked. The C argument is a copy of the context pointer. + // The P and X arguments are pointers whose meanings depend on T. + (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in + if let P = P, + let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { + unsafeBitCast(C, to: Trace.self)(expandedSQL) + sqlite3_free(expandedSQL) + } + return Int32(0) // currently ignored + }, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ + ) + trace = box + } + + fileprivate typealias Trace = @convention(block) (UnsafeRawPointer) -> Void fileprivate var trace: Trace? /// Registers a callback to be invoked whenever a row is inserted, updated, diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 33fa113d..99b9d8a1 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -94,6 +94,12 @@ class ConnectionTests : SQLiteTestCase { AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } + func test_execute_comment() { + try! db.run("-- this is a comment\nSELECT 1") + AssertSQL("-- this is a comment", 0) + AssertSQL("SELECT 1", 0) + } + func test_transaction_executesBeginDeferred() { try! db.transaction(.deferred) {} From 8ba973e2fed5e008b7129596107a215578090c54 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 10 Oct 2016 15:13:41 +0200 Subject: [PATCH 0450/1046] Remove outdated sample code (#516) --- Documentation/Index.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 8b136246..91f631ef 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1173,28 +1173,9 @@ let published = posts.filter(published_at <= Date()) ### Binary Data -Any object that can be encoded and decoded can be stored as a blob of data in SQL. - -We can create an `Data` bridge rather trivially. - -``` swift -extension Data: Value { - class var declaredDatatype: String { - return Blob.declaredDatatype - } - class func fromDatatypeValue(blobValue: Blob) -> Self { - return self(bytes: blobValue.bytes, length: blobValue.length) - } - var datatypeValue: Blob { - return Blob(bytes: bytes, length: length) - } -} -``` - We can bridge any type that can be initialized from and encoded to `Data`. ``` swift -// assumes Data conformance, above extension UIImage: Value { public class var declaredDatatype: String { return Blob.declaredDatatype From 681e21926f3b8bb813c91319d63e04cab4eb3f76 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 10 Oct 2016 19:33:51 +0200 Subject: [PATCH 0451/1046] Update bundler --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d280766b..925ceee3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ matrix: - env: VALIDATOR_SUBSPEC="standard" - env: VALIDATOR_SUBSPEC="standalone" before_install: + - gem update bundler - gem install xcpretty --no-document script: - ./run-tests.sh From db8f39b0a1cc845f0158f70a27c6e29e5d8d9e28 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 11 Oct 2016 15:08:03 +0200 Subject: [PATCH 0452/1046] Only import module when building with CocoaPods --- SQLite/Core/Connection.swift | 2 +- SQLite/Core/Statement.swift | 2 +- SQLite/Helpers.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 6aa27dc6..22f09c27 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -26,7 +26,7 @@ import Foundation.NSUUID import Dispatch #if SQLITE_SWIFT_STANDALONE import sqlite3 -#else +#elseif COCOAPODS import CSQLite #endif diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index a7664993..64b9017f 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -24,7 +24,7 @@ #if SQLITE_SWIFT_STANDALONE import sqlite3 -#else +#elseif COCOAPODS import CSQLite #endif diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index e9a17ce6..febdc949 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -24,7 +24,7 @@ #if SQLITE_SWIFT_STANDALONE import sqlite3 -#else +#elseif COCOAPODS import CSQLite #endif From 6b4cb81e78852c203397bf9132f75d78bab92b1c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 11 Oct 2016 15:51:13 +0200 Subject: [PATCH 0453/1046] Mention swift-2.3 branch in documentation --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bdd652f2..87d283d6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SQLite.swift +# SQLite.swift [![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-3-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) @@ -109,7 +109,9 @@ For a more comprehensive example, see [this article](http://masteringswift.blogs ## Installation -> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8) or greater. +> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8) or greater. If you absolutely +> need compatibility with Swift 2.3 you can use the [swift-2.3][] branch or older +> released versions. New development will happen exclusively on the master/Swift 3 branch. > > The following instructions apply to targets that support embedded > Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line @@ -199,7 +201,7 @@ To install SQLite.swift as an Xcode sub-project: - Found a **bug** or have a **feature request**? [Open an issue][]. - Want to **contribute**? [Submit a pull request][]. -[See the planning document]: /Documentation/Planning.md +[See the planning document]: /Documentation/Planning.md [Read the contributing guidelines]: ./CONTRIBUTING.md#contributing [Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new @@ -237,3 +239,4 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) [FMDB]: https://github.com/ccgus/fmdb +[swift-2.3]: https://github.com/stephencelis/SQLite.swift/tree/swift-2.3 From 3dacf3b00d9e5ed41cd1dd13a0279b62ed109128 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 12:12:58 +0200 Subject: [PATCH 0454/1046] Prepare 0.11.0 --- Documentation/Index.md | 8 ++++---- README.md | 4 ++-- SQLite.swift.podspec | 2 +- SQLite/Info.plist | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 91f631ef..3ecbbf77 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.10.1 + github "stephencelis/SQLite.swift" ~> 0.11.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -106,7 +106,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.10.1' + pod 'SQLite.swift', '~> 0.11.0' ``` 3. Run `pod install`. @@ -116,13 +116,13 @@ install SQLite.swift with Carthage: If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: ``` ruby - pod 'SQLite.swift/standalone', '~> 0.10.1' + pod 'SQLite.swift/standalone', '~> 0.11.0' ``` By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: ``` ruby - pod 'SQLite.swift/standalone', '~> 0.10.1' + pod 'SQLite.swift/standalone', '~> 0.11.0' pod 'sqlite3/fts5', '= 3.11.1' # SQLite 3.11.1 with FTS5 enabled ``` diff --git a/README.md b/README.md index 87d283d6..6e9857bd 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.10.1 + github "stephencelis/SQLite.swift" ~> 0.11.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.10.1' + pod 'SQLite.swift', '~> 0.11.0' ``` 3. Run `pod install`. diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b7a09bc1..a6efaf2a 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.10.1" + s.version = "0.11.0" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/SQLite/Info.plist b/SQLite/Info.plist index d93473a7..03d53553 100644 --- a/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.10.1 + 0.11.0 CFBundleSignature ???? CFBundleVersion From f160b86be47735b69262a00d713b796a29cf2f89 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 12:25:47 +0200 Subject: [PATCH 0455/1046] Docs --- Documentation/Index.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3ecbbf77..ac8bfa3e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -93,15 +93,17 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure the latest CocoaPods beta is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0.beta.6 or greater.) + 1. Verify that your copy of Xcode is installed in the default location (`/Application/Xcode.app`). + + 2. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). ``` sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. - sudo gem install --pre cocoapods + sudo gem install cocoapods ``` - 2. Update your Podfile to include the following: + 3. Update your Podfile to include the following: ``` ruby use_frameworks! @@ -109,7 +111,8 @@ install SQLite.swift with Carthage: pod 'SQLite.swift', '~> 0.11.0' ``` - 3. Run `pod install`. + 4. Run `pod install`. + #### Requiring a specific version of SQLite From 89102d79a39e68dee15b5e79554baecd0f823396 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 12:30:07 +0200 Subject: [PATCH 0456/1046] Update SQLite version --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index ac8bfa3e..3940541b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -126,7 +126,7 @@ By default this will use the most recent version of SQLite without any extras. I ``` ruby pod 'SQLite.swift/standalone', '~> 0.11.0' - pod 'sqlite3/fts5', '= 3.11.1' # SQLite 3.11.1 with FTS5 enabled + pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled ``` See the [sqlite3 podspec][sqlite3pod] for more details. From d3d2e4e72b15624d0e0ca1e3814d5e4279daa335 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 12:31:38 +0200 Subject: [PATCH 0457/1046] Fix screenshot path --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3940541b..6107969a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -142,7 +142,7 @@ To install SQLite.swift as an Xcode sub-project: 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.) - ![Installation Screen Shot](Documentation/Resources/installation@2x.png) + ![Installation Screen Shot](Resources/installation@2x.png) 2. In your target’s **General** tab, click the **+** button under **Linked Frameworks and Libraries**. From 0a89b1b7dcdcd5a22830a966a025b6c9c451852f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 12:45:34 +0200 Subject: [PATCH 0458/1046] Spec lints OK now --- SQLite.swift.podspec | 5 ----- 1 file changed, 5 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index a6efaf2a..9c3a35e0 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,8 +1,3 @@ -# -# `pod lib lint SQLite.swift.podspec' fails - see -# https://github.com/CocoaPods/CocoaPods/issues/4607 -# - Pod::Spec.new do |s| s.name = "SQLite.swift" s.version = "0.11.0" From 18aad56f33410ae295eb45459b0d84bc1b4d76ce Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 13:07:35 +0200 Subject: [PATCH 0459/1046] Add watchOS scheme --- CocoaPodsTests/test_running_validator.rb | 7 +- SQLite.xcodeproj/project.pbxproj | 4 + .../xcschemes/SQLite watchOS.xcscheme | 80 +++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLite watchOS.xcscheme diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 1eefa4b8..1a06e48a 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -9,11 +9,13 @@ class TestRunningValidator < Pod::Validator attr_accessor :test_files attr_accessor :ios_simulator attr_accessor :tvos_simulator + attr_accessor :watchos_simulator def initialize(spec_or_path, source_urls) super(spec_or_path, source_urls) self.ios_simulator = :oldest self.tvos_simulator = :oldest + self.watchos_simulator = :oldest end def create_app_project @@ -91,8 +93,11 @@ def run_tests when :tvos command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) command += Fourflusher::SimControl.new.destination(tvos_simulator, 'tvOS', deployment_target) + when :watchos + command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator) + command += Fourflusher::SimControl.new.destination(watchos_simulator, 'watchOS', deployment_target) else - return # skip watchos + return end output, status = _xcodebuild(command) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 4dddeaaf..690a1bea 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1043,6 +1043,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; + "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1066,6 +1068,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; + "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite watchOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite watchOS.xcscheme new file mode 100644 index 00000000..d2088e81 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite watchOS.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 15887ae7730e76b76c94757498f3a2c6ea408cd8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 13:11:06 +0200 Subject: [PATCH 0460/1046] Swift incude paths --- SQLite.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 690a1bea..f0b18841 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1043,8 +1043,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; - "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; + "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; + "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1068,8 +1068,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; - "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; + "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; + "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; From dd2568fa23377bd281c270937977a5efc5b82919 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 13:15:44 +0200 Subject: [PATCH 0461/1046] Add files to watchOS target --- SQLite.xcodeproj/project.pbxproj | 66 ++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index f0b18841..7b33eac6 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -55,6 +55,30 @@ 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; + 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; + 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; + 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; + 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; + 3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; + 3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* RTree.swift */; }; + 3D67B3ED1DB246D100A4F4C6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 3D67B3EE1DB246D100A4F4C6 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; + 3D67B3EF1DB246D100A4F4C6 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; + 3D67B3F01DB246D100A4F4C6 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; + 3D67B3F11DB246D100A4F4C6 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */; }; + 3D67B3F21DB246D100A4F4C6 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFE1C3F06E900AE3E12 /* Expression.swift */; }; + 3D67B3F31DB246D100A4F4C6 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFF1C3F06E900AE3E12 /* Operators.swift */; }; + 3D67B3F41DB246D100A4F4C6 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; + 3D67B3F51DB246D100A4F4C6 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; + 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; + 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; + 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; + 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 3D67B3FA1DB2470600A4F4C6 /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -173,6 +197,7 @@ 39548A6B1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; + 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -250,6 +275,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -358,6 +384,14 @@ path = watchsimulator; sourceTree = ""; }; + 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */, + ); + name = Frameworks; + sourceTree = ""; + }; EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( @@ -365,6 +399,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, + 3D67B3E41DB2469200A4F4C6 /* Frameworks */, ); indentWidth = 4; sourceTree = ""; @@ -517,6 +552,10 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 3D67B3FA1DB2470600A4F4C6 /* usr/include/sqlite3.h in Headers */, + 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */, + 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, + 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -837,6 +876,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */, + 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, + 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, + 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, + 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, + 3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */, + 3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */, + 3D67B3ED1DB246D100A4F4C6 /* FTS5.swift in Sources */, + 3D67B3EE1DB246D100A4F4C6 /* AggregateFunctions.swift in Sources */, + 3D67B3EF1DB246D100A4F4C6 /* Collation.swift in Sources */, + 3D67B3F01DB246D100A4F4C6 /* CoreFunctions.swift in Sources */, + 3D67B3F11DB246D100A4F4C6 /* CustomFunctions.swift in Sources */, + 3D67B3F21DB246D100A4F4C6 /* Expression.swift in Sources */, + 3D67B3F31DB246D100A4F4C6 /* Operators.swift in Sources */, + 3D67B3F41DB246D100A4F4C6 /* Query.swift in Sources */, + 3D67B3F51DB246D100A4F4C6 /* Schema.swift in Sources */, + 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */, + 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, + 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1043,8 +1101,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; - "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1052,6 +1108,8 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; + "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; @@ -1068,8 +1126,6 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; - "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1077,6 +1133,8 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; + "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; From 9bed4fcb6db633a40996037f0ca8cfab325c5f42 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 13:28:29 +0200 Subject: [PATCH 0462/1046] Update bundle --- CocoaPodsTests/Gemfile | 2 +- CocoaPodsTests/Gemfile.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CocoaPodsTests/Gemfile b/CocoaPodsTests/Gemfile index 287dcbfc..29e72332 100644 --- a/CocoaPodsTests/Gemfile +++ b/CocoaPodsTests/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.1.0.rc.2' +gem 'cocoapods', '~> 1.1.0.rc.3' gem 'minitest' diff --git a/CocoaPodsTests/Gemfile.lock b/CocoaPodsTests/Gemfile.lock index 93a54bb9..f8cc1118 100644 --- a/CocoaPodsTests/Gemfile.lock +++ b/CocoaPodsTests/Gemfile.lock @@ -7,26 +7,26 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - claide (1.0.0) - cocoapods (1.1.0.rc.2) + claide (1.0.1) + cocoapods (1.1.0.rc.3) activesupport (>= 4.0.2, < 5) - claide (>= 1.0.0, < 2.0) - cocoapods-core (= 1.1.0.rc.2) + claide (>= 1.0.1, < 2.0) + cocoapods-core (= 1.1.0.rc.3) cocoapods-deintegrate (>= 1.0.1, < 2.0) cocoapods-downloader (>= 1.1.1, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.0.0, < 2.0) + cocoapods-trunk (= 1.1.0.beta.1) cocoapods-try (>= 1.1.0, < 2.0) colored (~> 1.2) escape (~> 0.0.4) - fourflusher (~> 1.0.1) + fourflusher (~> 2.0) gh_inspector (~> 1.0) molinillo (~> 0.5.1) nap (~> 1.0) - xcodeproj (>= 1.3.1, < 2.0) - cocoapods-core (1.1.0.rc.2) + xcodeproj (>= 1.3.2, < 2.0) + cocoapods-core (1.1.0.rc.3) activesupport (>= 4.0.2, < 5) fuzzy_match (~> 2.0.4) nap (~> 1.0) @@ -36,35 +36,35 @@ GEM nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.0.0) + cocoapods-trunk (1.1.0.beta.1) nap (>= 0.8, < 2.0) netrc (= 0.7.8) cocoapods-try (1.1.0) colored (1.2) escape (0.0.4) - fourflusher (1.0.1) + fourflusher (2.0.0) fuzzy_match (2.0.4) gh_inspector (1.0.2) i18n (0.7.0) json (1.8.3) - minitest (5.9.0) + minitest (5.9.1) molinillo (0.5.1) nap (1.1.0) netrc (0.7.8) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) - xcodeproj (1.3.1) + xcodeproj (1.3.2) activesupport (>= 3) - claide (>= 1.0.0, < 2.0) + claide (>= 1.0.1, < 2.0) colored (~> 1.2) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.1.0.rc.2) + cocoapods (~> 1.1.0.rc.3) minitest BUNDLED WITH - 1.10.6 + 1.13.1 From e68f8da46e3025e8e281fb3eb1061073e75bcc9f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 13:55:08 +0200 Subject: [PATCH 0463/1046] Don't print logs after failure --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 925ceee3..d28be8de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,3 @@ before_install: - gem install xcpretty --no-document script: - ./run-tests.sh -after_failure: - - find $HOME/Library/Developer/Xcode/DerivedData/ -name '*.log' -print0 | xargs -0 cat - - cat /var/log/system.log From 7d63509c5007550ea71b03cf1117bce0fba3bfbf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 15 Oct 2016 14:07:31 +0200 Subject: [PATCH 0464/1046] skip watchOS --- CocoaPodsTests/test_running_validator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 1a06e48a..949e8489 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -94,8 +94,8 @@ def run_tests command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) command += Fourflusher::SimControl.new.destination(tvos_simulator, 'tvOS', deployment_target) when :watchos - command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator) - command += Fourflusher::SimControl.new.destination(watchos_simulator, 'watchOS', deployment_target) + # there's no XCTest on watchOS (https://openradar.appspot.com/21760513) + return else return end From 517c2b624d6bed5e0d653974d0cb45bca33c821f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 20 Oct 2016 08:26:26 +0200 Subject: [PATCH 0465/1046] Connection#pluck can throw --- SQLite.playground/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 2015531a..11e13139 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -35,7 +35,7 @@ try! db.run(emails.insert( body <- "This is a hello world message." )) -let row = db.pluck(emails.match("hello")) +let row = try! db.pluck(emails.match("hello")) let query = try! db.prepare(emails.match("hello")) for row in query { From 61bb2dd588751b97195aba4e40b1d3cb0e4dc13f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 20 Oct 2016 10:43:44 +0200 Subject: [PATCH 0466/1046] Fix for ~= operator used with Double ranges --- SQLite/Typed/Operators.swift | 4 ++-- SQLiteTests/OperatorsTests.swift | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SQLite/Typed/Operators.swift b/SQLite/Typed/Operators.swift index 616015dc..fdd293be 100644 --- a/SQLite/Typed/Operators.swift +++ b/SQLite/Typed/Operators.swift @@ -474,10 +474,10 @@ public func <=(lhs: V, rhs: Expression) -> Expression wher return infix(lhs, rhs) } -public func ~=(lhs: CountableClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } -public func ~=(lhs: CountableClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } diff --git a/SQLiteTests/OperatorsTests.swift b/SQLiteTests/OperatorsTests.swift index 819cf292..d2ce17ef 100644 --- a/SQLiteTests/OperatorsTests.swift +++ b/SQLiteTests/OperatorsTests.swift @@ -245,11 +245,16 @@ class OperatorsTests : XCTestCase { AssertSQL("(1 <= \"boolOptional\")", true <= boolOptional) } - func test_patternMatchingOperator_withComparableInterval_buildsBetweenBooleanExpression() { + func test_patternMatchingOperator_withComparableCountableClosedRange_buildsBetweenBooleanExpression() { AssertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) AssertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) } + func test_patternMatchingOperator_withComparableClosedRange_buildsBetweenBooleanExpression() { + AssertSQL("\"double\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= double) + AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) + } + func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { AssertSQL("(\"bool\" AND \"bool\")", bool && bool) AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) From 14c0a48d075906efdc3366ad7a42cdf4ba741169 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 20 Oct 2016 10:48:27 +0200 Subject: [PATCH 0467/1046] Tag as discardable --- SQLite/Typed/Query.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 75f29afd..375c1d0a 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -968,7 +968,7 @@ extension Connection { /// - Parameter query: An insert query. /// /// - Returns: The insert’s rowid. - public func run(_ query: Insert) throws -> Int64 { + @discardableResult public func run(_ query: Insert) throws -> Int64 { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) @@ -984,7 +984,7 @@ extension Connection { /// - Parameter query: An update query. /// /// - Returns: The number of updated rows. - public func run(_ query: Update) throws -> Int { + @discardableResult public func run(_ query: Update) throws -> Int { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) @@ -999,7 +999,7 @@ extension Connection { /// - Parameter query: A delete query. /// /// - Returns: The number of deleted rows. - public func run(_ query: Delete) throws -> Int { + @discardableResult public func run(_ query: Delete) throws -> Int { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) From 26ef2597ad40bd42f8a40616c34d857d3f69b40e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 20 Oct 2016 10:58:35 +0200 Subject: [PATCH 0468/1046] Test String ranges --- SQLiteTests/OperatorsTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SQLiteTests/OperatorsTests.swift b/SQLiteTests/OperatorsTests.swift index d2ce17ef..f0e585cd 100644 --- a/SQLiteTests/OperatorsTests.swift +++ b/SQLiteTests/OperatorsTests.swift @@ -255,6 +255,11 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) } + func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { + AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) + AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) + } + func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { AssertSQL("(\"bool\" AND \"bool\")", bool && bool) AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) From d95a518178570c805bb18b971d264b8a48edbcf5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 21 Oct 2016 08:56:21 +0200 Subject: [PATCH 0469/1046] Bump version --- Documentation/Index.md | 8 ++++---- README.md | 4 ++-- SQLite.swift.podspec | 2 +- SQLite/Info.plist | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 6107969a..50d7d840 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.0 + github "stephencelis/SQLite.swift" ~> 0.11.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -108,7 +108,7 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.11.0' + pod 'SQLite.swift', '~> 0.11.1' ``` 4. Run `pod install`. @@ -119,13 +119,13 @@ install SQLite.swift with Carthage: If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: ``` ruby - pod 'SQLite.swift/standalone', '~> 0.11.0' + pod 'SQLite.swift/standalone', '~> 0.11.1' ``` By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: ``` ruby - pod 'SQLite.swift/standalone', '~> 0.11.0' + pod 'SQLite.swift/standalone', '~> 0.11.1' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled ``` diff --git a/README.md b/README.md index 6e9857bd..85767b38 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.0 + github "stephencelis/SQLite.swift" ~> 0.11.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.11.0' + pod 'SQLite.swift', '~> 0.11.1' ``` 3. Run `pod install`. diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 9c3a35e0..ac7ed8bb 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.0" + s.version = "0.11.1" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/SQLite/Info.plist b/SQLite/Info.plist index 03d53553..21f24d9d 100644 --- a/SQLite/Info.plist +++ b/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.0 + 0.11.1 CFBundleSignature ???? CFBundleVersion From f58ebbf2850693d262af7df4521716bd53c74d12 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 8 Nov 2016 12:45:29 +0000 Subject: [PATCH 0470/1046] Update CocoaPods doc --- Documentation/Index.md | 2 +- README.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 50d7d840..367236ae 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -100,7 +100,7 @@ install SQLite.swift with Carthage: ``` sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. - sudo gem install cocoapods + [sudo] gem install cocoapods ``` 3. Update your Podfile to include the following: diff --git a/README.md b/README.md index 85767b38..e3c766a1 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,9 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure the latest CocoaPods beta is [installed][CocoaPods - Installation]. (SQLite.swift requires version 1.0.0.beta.6 or greater.) + 1. Verify that your copy of Xcode is installed in the default location (`/Application/Xcode.app`). + + 2. Make sure the latest CocoaPods beta is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) ``` sh # Using the default Ruby install will require you to use sudo when @@ -154,7 +155,7 @@ SQLite.swift with CocoaPods: [sudo] gem install cocoapods ``` - 2. Update your Podfile to include the following: + 3. Update your Podfile to include the following: ``` ruby use_frameworks! @@ -162,7 +163,7 @@ SQLite.swift with CocoaPods: pod 'SQLite.swift', '~> 0.11.1' ``` - 3. Run `pod install`. + 4. Run `pod install`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started From 2d059b0f4a8759d224e0b78f34b26cfd4f6c5f6d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 8 Nov 2016 13:06:04 +0000 Subject: [PATCH 0471/1046] Docs: restore migration information --- Documentation/Index.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 367236ae..77f9d8e6 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1079,11 +1079,22 @@ try db.run(users.drop(ifExists: true)) ``` - +``` ## Custom Types From bf6a722fc35aef5661edfa165ae43cefbfecd160 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 8 Nov 2016 13:09:05 +0000 Subject: [PATCH 0472/1046] Add missing boilerplate --- Documentation/Index.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 77f9d8e6..7fabe836 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1086,9 +1086,11 @@ You can add a convenience property on `Connection` to query and set the [`PRAGMA This is a great way to manage your schema’s version over migrations. ``` swift -public var userVersion: Int32 { - get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} - set { try! run("PRAGMA user_version = \(newValue)") } +extension Connection { + public var userVersion: Int32 { + get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} + set { try! run("PRAGMA user_version = \(newValue)") } + } } ``` From d784548f25a2989cda873f158aec63f2d9f9eedd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 8 Nov 2016 13:10:29 +0000 Subject: [PATCH 0473/1046] Fix link target --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7fabe836..a8aa0747 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1081,7 +1081,7 @@ try db.run(users.drop(ifExists: true)) ### Migrations and Schema Versioning -You can add a convenience property on `Connection` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_schema_version). +You can add a convenience property on `Connection` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). This is a great way to manage your schema’s version over migrations. From 8b960d443b9475f5c7866e22a5eb0183bcd9618a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 10 Nov 2016 15:17:19 +0000 Subject: [PATCH 0474/1046] Tell users to perform a repo update when installing pods #540 --- Documentation/Index.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index a8aa0747..e5eda36a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -111,7 +111,7 @@ install SQLite.swift with Carthage: pod 'SQLite.swift', '~> 0.11.1' ``` - 4. Run `pod install`. + 4. Run `pod install --repo-update`. #### Requiring a specific version of SQLite diff --git a/README.md b/README.md index e3c766a1..14c0f6f6 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ SQLite.swift with CocoaPods: pod 'SQLite.swift', '~> 0.11.1' ``` - 4. Run `pod install`. + 4. Run `pod install --repo-update`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started From c2f3fc8b0f2ed5ea413e692952f44b13efe237db Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 12 Nov 2016 16:11:27 +0000 Subject: [PATCH 0475/1046] Link to SQLiteMigrationManager.swift now that it is Swift3 compatible --- Documentation/Index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index e5eda36a..3678368b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1107,6 +1107,8 @@ if db.userVersion == 1 { } ``` +For more complex migration requirements check out the schema management system +[SQLiteMigrationManager.swift][]. ## Custom Types @@ -1526,3 +1528,4 @@ We can log SQL using the database’s `trace` function. [ROWID]: https://sqlite.org/lang_createtable.html#rowid +[SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift From 6e30a3d40cdecf94124c43729c7566dd03f0d8d8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 12 Nov 2016 19:40:24 +0000 Subject: [PATCH 0476/1046] Add config file for CocoaDocs --- .cocoadocs.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cocoadocs.yml diff --git a/.cocoadocs.yml b/.cocoadocs.yml new file mode 100644 index 00000000..27840032 --- /dev/null +++ b/.cocoadocs.yml @@ -0,0 +1,2 @@ +additional_guides: + - Documentation/Index.md From 6b784093b8661cd81b2f4a075a8e5880cca2da47 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 15 Nov 2016 08:04:45 +0000 Subject: [PATCH 0477/1046] Mention FTS5 query differences in docs Closes #544 --- Documentation/Index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3678368b..190c1d2d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1444,6 +1444,13 @@ let config = FTS5Config() try db.run(emails.create(.FTS5(config)) // CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED) + +// Note that FTS5 uses a different syntax to select columns, so we need to rewrite +// the last FTS4 query above as: +let replies = emails.filter(emails.match("subject:\"Re:\"*)) +// SELECT * FROM "emails" WHERE "emails" MATCH 'subject:"Re:"*' + +// https://www.sqlite.org/fts5.html#_changes_to_select_statements_ ``` ## Executing Arbitrary SQL From 75394dab16991f64672dbf8a11fbb8e80c17a2e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 15 Nov 2016 11:27:09 +0000 Subject: [PATCH 0478/1046] Make lastInsertRowid consistent with other SQLite wrappers closes #532 --- SQLite/Core/Connection.swift | 5 ++--- SQLite/Typed/Query.swift | 2 +- SQLiteTests/ConnectionTests.swift | 22 +++++++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 5d6ebcdc..e96a84b5 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -132,9 +132,8 @@ public final class Connection { public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } /// The last rowid inserted into the database via this connection. - public var lastInsertRowid: Int64? { - let rowid = sqlite3_last_insert_rowid(handle) - return rowid != 0 ? rowid : nil + public var lastInsertRowid: Int64 { + return sqlite3_last_insert_rowid(handle) } /// The last number of changes (inserts, updates, or deletes) made to the diff --git a/SQLite/Typed/Query.swift b/SQLite/Typed/Query.swift index 375c1d0a..288768c1 100644 --- a/SQLite/Typed/Query.swift +++ b/SQLite/Typed/Query.swift @@ -972,7 +972,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.lastInsertRowid! + return self.lastInsertRowid } } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 99b9d8a1..ab0e957c 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -43,17 +43,29 @@ class ConnectionTests : SQLiteTestCase { XCTAssertTrue(db.readonly) } - func test_lastInsertRowid_returnsNilOnNewConnections() { - XCTAssert(db.lastInsertRowid == nil) + func test_changes_returnsZeroOnNewConnections() { + XCTAssertEqual(0, db.changes) } func test_lastInsertRowid_returnsLastIdAfterInserts() { try! InsertUser("alice") - XCTAssertEqual(1, db.lastInsertRowid!) + XCTAssertEqual(1, db.lastInsertRowid) } - func test_changes_returnsZeroOnNewConnections() { - XCTAssertEqual(0, db.changes) + func test_lastInsertRowid_doesNotResetAfterError() { + XCTAssert(db.lastInsertRowid == 0) + try! InsertUser("alice") + XCTAssertEqual(1, db.lastInsertRowid) + XCTAssertThrowsError( + try db.run("INSERT INTO \"users\" (email, age, admin) values ('invalid@example.com', 12, 'invalid')") + ) { error in + if case SQLite.Result.error(_, let code, _) = error { + XCTAssertEqual(SQLITE_CONSTRAINT, code) + } else { + XCTFail("expected error") + } + } + XCTAssertEqual(1, db.lastInsertRowid) } func test_changes_returnsNumberOfChanges() { From c7480062ee75bea70ba3f4be157c4ffa0bb293ee Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 15 Nov 2016 11:28:22 +0000 Subject: [PATCH 0479/1046] Add changelog --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ca8da53e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +0.11.1 (unreleased, [diff][diff-0.11.1]) +======================================== + +* Made lastInsertRowid consistent with other SQLite wrappers (#532) +* Fix for ~= operator used with Double ranges +* Various documentation updates + +0.11.0 (19-10-2016) +=================== + +* Swift3 migration ([diff][diff-0.11.0]) + + +[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.0 +[diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0 From fbea9bf6593100b10f4473a9c241b841b5b84b36 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 15 Nov 2016 13:03:05 +0000 Subject: [PATCH 0480/1046] Retry failed builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d28be8de..192c9979 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,4 @@ before_install: - gem update bundler - gem install xcpretty --no-document script: - - ./run-tests.sh + - travis_retry ./run-tests.sh From 095026d1b1ec8f249a51a624ff44e200251d2407 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 15 Nov 2016 13:13:00 +0000 Subject: [PATCH 0481/1046] Add missing imports --- SQLiteTests/ConnectionTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index ab0e957c..20d4215a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -1,5 +1,10 @@ import XCTest import SQLite +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif COCOAPODS +import CSQLite +#endif class ConnectionTests : SQLiteTestCase { From e177a1d6f3bb85161e326c20423a0ca014b56b4a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 15 Nov 2016 14:26:10 +0000 Subject: [PATCH 0482/1046] Switch to released CocoaPods version --- CocoaPodsTests/Gemfile | 2 +- CocoaPodsTests/Gemfile.lock | 32 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CocoaPodsTests/Gemfile b/CocoaPodsTests/Gemfile index 29e72332..94018f18 100644 --- a/CocoaPodsTests/Gemfile +++ b/CocoaPodsTests/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.1.0.rc.3' +gem 'cocoapods', '~> 1.1.0' gem 'minitest' diff --git a/CocoaPodsTests/Gemfile.lock b/CocoaPodsTests/Gemfile.lock index f8cc1118..e8879f13 100644 --- a/CocoaPodsTests/Gemfile.lock +++ b/CocoaPodsTests/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: https://rubygems.org/ specs: + CFPropertyList (2.3.3) activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) @@ -8,63 +9,66 @@ GEM thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) claide (1.0.1) - cocoapods (1.1.0.rc.3) + cocoapods (1.1.1) activesupport (>= 4.0.2, < 5) claide (>= 1.0.1, < 2.0) - cocoapods-core (= 1.1.0.rc.3) + cocoapods-core (= 1.1.1) cocoapods-deintegrate (>= 1.0.1, < 2.0) - cocoapods-downloader (>= 1.1.1, < 2.0) + cocoapods-downloader (>= 1.1.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (= 1.1.0.beta.1) + cocoapods-trunk (>= 1.1.1, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored (~> 1.2) escape (~> 0.0.4) - fourflusher (~> 2.0) + fourflusher (~> 2.0.1) gh_inspector (~> 1.0) molinillo (~> 0.5.1) nap (~> 1.0) - xcodeproj (>= 1.3.2, < 2.0) - cocoapods-core (1.1.0.rc.3) + xcodeproj (>= 1.3.3, < 2.0) + cocoapods-core (1.1.1) activesupport (>= 4.0.2, < 5) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.1) - cocoapods-downloader (1.1.1) + cocoapods-downloader (1.1.2) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.1.0.beta.1) + cocoapods-trunk (1.1.1) nap (>= 0.8, < 2.0) netrc (= 0.7.8) cocoapods-try (1.1.0) colored (1.2) escape (0.0.4) - fourflusher (2.0.0) + fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.0.2) i18n (0.7.0) json (1.8.3) minitest (5.9.1) - molinillo (0.5.1) + molinillo (0.5.4) + nanaimo (0.2.2) nap (1.1.0) netrc (0.7.8) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) - xcodeproj (1.3.2) + xcodeproj (1.4.1) + CFPropertyList (~> 2.3.3) activesupport (>= 3) claide (>= 1.0.1, < 2.0) colored (~> 1.2) + nanaimo (~> 0.2.0) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.1.0.rc.3) + cocoapods (~> 1.1.0) minitest BUNDLED WITH - 1.13.1 + 1.13.3 From bb3b7059a324651e89357da0e6edeaa2c5d06d36 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 16 Nov 2016 15:09:42 +0000 Subject: [PATCH 0483/1046] public check method, needed for #389 --- SQLite/Core/Connection.swift | 2 +- SQLiteTests/ConnectionTests.swift | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index e96a84b5..6b447305 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -674,7 +674,7 @@ public final class Connection { return success! } - @discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { + @discardableResult public func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else { return resultCode } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 20d4215a..6455d30a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -338,4 +338,11 @@ class ConnectionTests : SQLiteTestCase { AssertThrows(try stmt.run()) } + func test_check_with_successCode_returnsCode() { + try! XCTAssertEqual(SQLITE_OK, db.check(SQLITE_OK)) + } + + func test_check_with_errorCode_throws() { + XCTAssertThrowsError(try db.check(SQLITE_CONSTRAINT)) + } } From 6b8661afdf4128379f6382ee55e8eea2b444e5ea Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 16 Nov 2016 15:11:09 +0000 Subject: [PATCH 0484/1046] Try out xcode 8.1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 192c9979..b27d7061 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.2 -osx_image: xcode8 +osx_image: xcode8.1 env: global: - IOS_SIMULATOR="iPhone SE" From 433d2479939ba583e3d15939c081126cbd027c0b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Nov 2016 09:43:21 +0000 Subject: [PATCH 0485/1046] Xcode 8.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b27d7061..9138ddb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.2 -osx_image: xcode8.1 +osx_image: xcode8.2 env: global: - IOS_SIMULATOR="iPhone SE" From fbf994a331c3f5a2fc1729be5e35605c1943c31b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Nov 2016 09:51:04 +0000 Subject: [PATCH 0486/1046] Specify destination --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2f085b6c..bb459c2c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone SE +IOS_SIMULATOR = iPhone 6s ifeq ($(BUILD_SCHEME),SQLite iOS) - BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR)" + BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=9.3" else BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" endif From 6e53f365d7d3d871f95b1705e8af8e0fb9e4e293 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Nov 2016 10:56:08 +0000 Subject: [PATCH 0487/1046] Use 6s --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9138ddb7..9e06164f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ rvm: 2.2 osx_image: xcode8.2 env: global: - - IOS_SIMULATOR="iPhone SE" + - IOS_SIMULATOR="iPhone 6s" matrix: include: - env: BUILD_SCHEME="SQLite iOS" From cbf5e0e92103180e539f4e883b3f32ff28ecf0d1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Nov 2016 11:01:06 +0000 Subject: [PATCH 0488/1046] Remove travis_retry workaround --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e06164f..833c67d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,4 @@ before_install: - gem update bundler - gem install xcpretty --no-document script: - - travis_retry ./run-tests.sh + - ./run-tests.sh From f23969f589e84d7f542dbe83db7a7bd391f363dd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Nov 2016 11:14:04 +0000 Subject: [PATCH 0489/1046] Specify iOS version --- .travis.yml | 1 + Makefile | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 833c67d5..818639c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ osx_image: xcode8.2 env: global: - IOS_SIMULATOR="iPhone 6s" + - IOS_VERSION="9.3" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Makefile b/Makefile index bb459c2c..537fb1ff 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s +IOS_VERSION = 9.3 ifeq ($(BUILD_SCHEME),SQLite iOS) - BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=9.3" + BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" endif From 2e6e3c93b926f8e31d1a52f84c3790826302c1eb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Nov 2016 12:49:21 +0000 Subject: [PATCH 0490/1046] Remove env --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 818639c4..833c67d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ osx_image: xcode8.2 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="9.3" matrix: include: - env: BUILD_SCHEME="SQLite iOS" From 3472f4e47988223047c6ae0768c6c0ce6c584480 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 4 Dec 2016 20:52:47 +0000 Subject: [PATCH 0491/1046] Split testing into separate commands --- CocoaPodsTests/test_running_validator.rb | 2 +- Makefile | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 949e8489..410f9465 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -79,7 +79,7 @@ def add_test_target(pod_file) def run_tests command = [ - 'clean', 'test', + 'clean', 'build', 'build-for-testing', 'test-without-building', '-workspace', File.join(validation_dir, "#{APP_TARGET}.xcworkspace"), '-scheme', TEST_TARGET, '-configuration', 'Debug' diff --git a/Makefile b/Makefile index 537fb1ff..d4f51d53 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ endif XCPRETTY := $(shell command -v xcpretty) SWIFTCOV := $(shell command -v swiftcov) GCOVR := $(shell command -v gcovr) +TEST_ACTIONS := clean build build-for-testing test-without-building default: test @@ -19,9 +20,9 @@ build: test: ifdef XCPRETTY - @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) test | $(XCPRETTY) -c + @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) | $(XCPRETTY) -c else - $(BUILD_TOOL) $(BUILD_ARGUMENTS) test + $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) endif coverage: From 0613cf53f68aea306463d3c4322ba15e5230e320 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 00:30:55 +0000 Subject: [PATCH 0492/1046] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca8da53e..6cfd1fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ -0.11.1 (unreleased, [diff][diff-0.11.1]) +0.11.1 (05-12-2016), [diff][diff-0.11.1]) ======================================== * Made lastInsertRowid consistent with other SQLite wrappers (#532) * Fix for ~= operator used with Double ranges * Various documentation updates -0.11.0 (19-10-2016) +0.11.0 (10-19-2016) =================== * Swift3 migration ([diff][diff-0.11.0]) From e2eb4fd6556d852045af96abc35c3c85175418ad Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 14:03:58 +0000 Subject: [PATCH 0493/1046] Integrate SQLCipher --- CHANGELOG.md | 1 + Documentation/Index.md | 22 ++++++++- README.md | 3 +- SQLite.swift.podspec | 11 +++++ SQLite.xcodeproj/project.pbxproj | 18 +++++++ SQLite/Core/Cipher.swift | 29 +++++++++++ SQLite/Core/Connection.swift | 81 ++++++++++++++++++------------- SQLite/Core/Statement.swift | 2 + SQLite/Helpers.swift | 2 + SQLiteTests/CipherTests.swift | 74 ++++++++++++++++++++++++++++ SQLiteTests/ConnectionTests.swift | 2 + SQLiteTests/FTS4Tests.swift | 2 +- 12 files changed, 209 insertions(+), 38 deletions(-) create mode 100644 SQLite/Core/Cipher.swift create mode 100644 SQLiteTests/CipherTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cfd1fe6..34cd0bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.1 (05-12-2016), [diff][diff-0.11.1]) ======================================== +* Integrate SQLCipher via CocoaPods (#546) * Made lastInsertRowid consistent with other SQLite wrappers (#532) * Fix for ~= operator used with Double ranges * Various documentation updates diff --git a/Documentation/Index.md b/Documentation/Index.md index 190c1d2d..82e9f43f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -131,10 +131,30 @@ By default this will use the most recent version of SQLite without any extras. I See the [sqlite3 podspec][sqlite3pod] for more details. +#### Using SQLite.swift with SQLCipher + +If you want to use [SQLCipher][] with SQLite.swift you can require the `sqlcipher` +subspec in your Podfile: + +``` ruby + pod 'SQLite.swift/sqlcipher', '~> 0.11.1' +``` + +This will automatically add a dependency to the SQLCipher pod as well as extend +`Connection` with methods to change the database key: + +``` swift +import SQLite + +let db = try Connection("path/to/db.sqlite3") +try db.key("secret") +try db.rekey("another secret") +``` + [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started [sqlite3pod]: https://github.com/clemensg/sqlite3pod - +[SQLCipher]: https://www.zetetic.net/sqlcipher/ ### Manual diff --git a/README.md b/README.md index 14c0f6f6..a9b44abe 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ syntax _and_ intent. - [Full-text search][] support - [Well-documented][See Documentation] - Extensively tested - - Companion project has [SQLCipher support](https://github.com/stephencelis/SQLiteCipher.swift) + - SQLCipher support via CocoaPods - Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) [Full-text search]: Documentation/Index.md#full-text-search @@ -224,7 +224,6 @@ file](./LICENSE.txt) for more information. These projects enhance or use SQLite.swift: - - [SQLiteCipher.swift](https://github.com/stephencelis/SQLiteCipher.swift) - [SQLiteMigrationManager.swift](https://github.com/garriguv/SQLiteMigrationManager.swift) (inspired by [FMDBMigrationManager](https://github.com/layerhq/FMDBMigrationManager)) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index ac7ed8bb..fb17adda 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -52,4 +52,15 @@ Pod::Spec.new do |s| ss.dependency 'sqlite3' end + + s.subspec 'sqlcipher' do |ss| + ss.source_files = 'SQLite/**/*.{c,h,m,swift}' + ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + ss.xcconfig = { + 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', + 'GCC_PREPROCESSOR_DEFINITIONS' => 'SQLITE_HAS_CODEC=1', + 'OTHER_CFLAGS' => '-DSQLITE_HAS_CODEC' + } + ss.dependency 'SQLCipher' + end end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 7b33eac6..69cd87fb 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -51,8 +51,15 @@ 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A179C2C078DDD29E052DA9 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; + 19A17BF0509EA4A026530E23 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; + 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17E24574F51C37C04C758 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; + 19A17E7E3DA4966FA2921857 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; @@ -187,8 +194,10 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A171CACA25EB58FE045146 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; + 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -455,6 +464,7 @@ EE247AE41C3F04ED00AE3E12 /* Info.plist */, 19A1721B8984686B9963B45D /* FTS5Tests.swift */, 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, + 19A17399EA9E61235D5D77BF /* CipherTests.swift */, ); path = SQLiteTests; sourceTree = ""; @@ -469,6 +479,7 @@ EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, + 19A171CACA25EB58FE045146 /* Cipher.swift */, ); path = Core; sourceTree = ""; @@ -845,6 +856,7 @@ 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, + 19A179C2C078DDD29E052DA9 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -869,6 +881,7 @@ 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, + 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -895,6 +908,7 @@ 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */, 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, + 19A17E7E3DA4966FA2921857 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -921,6 +935,7 @@ EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, + 19A17E24574F51C37C04C758 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -945,6 +960,7 @@ EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, + 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -971,6 +987,7 @@ EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, + 19A17BF0509EA4A026530E23 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -995,6 +1012,7 @@ EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, + 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite/Core/Cipher.swift b/SQLite/Core/Cipher.swift new file mode 100644 index 00000000..3533854e --- /dev/null +++ b/SQLite/Core/Cipher.swift @@ -0,0 +1,29 @@ +#if SQLITE_SWIFT_SQLCIPHER +import SQLCipher + +extension Connection { + public func key(_ key: String) throws { + try check(sqlite3_key(handle, key, Int32(key.utf8.count))) + try execute( + "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + + "DROP TABLE \"__SQLCipher.swift__\";" + ) + } + + public func rekey(_ key: String) throws { + try check(sqlite3_rekey(handle, key, Int32(key.utf8.count))) + } + + public func key(_ key: Blob) throws { + try check(sqlite3_key(handle, key.bytes, Int32(key.bytes.count))) + try execute( + "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + + "DROP TABLE \"__SQLCipher.swift__\";" + ) + } + + public func rekey(_ key: Blob) throws { + try check(sqlite3_rekey(handle, key.bytes, Int32(key.bytes.count))) + } +} +#endif diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 6b447305..42cfc2a5 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -26,6 +26,8 @@ import Foundation.NSUUID import Dispatch #if SQLITE_SWIFT_STANDALONE import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher #elseif COCOAPODS import CSQLite #endif @@ -411,11 +413,15 @@ public final class Connection { /// /// db.trace { SQL in print(SQL) } public func trace(_ callback: ((String) -> Void)?) { - if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { - trace_v2(callback) - } else { + #if SQLITE_SWIFT_SQLCIPHER trace_v1(callback) - } + #else + if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { + trace_v2(callback) + } else { + trace_v1(callback) + } + #endif } fileprivate func trace_v1(_ callback: ((String) -> Void)?) { @@ -439,37 +445,8 @@ public final class Connection { trace = box } - @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) - fileprivate func trace_v2(_ callback: ((String) -> Void)?) { - guard let callback = callback else { - // If the X callback is NULL or if the M mask is zero, then tracing is disabled. - sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) - trace = nil - return - } - let box: Trace = { (pointer: UnsafeRawPointer) in - callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) - } - sqlite3_trace_v2(handle, - UInt32(SQLITE_TRACE_STMT) /* mask */, - { - // A trace callback is invoked with four arguments: callback(T,C,P,X). - // The T argument is one of the SQLITE_TRACE constants to indicate why the - // callback was invoked. The C argument is a copy of the context pointer. - // The P and X arguments are pointers whose meanings depend on T. - (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in - if let P = P, - let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { - unsafeBitCast(C, to: Trace.self)(expandedSQL) - sqlite3_free(expandedSQL) - } - return Int32(0) // currently ignored - }, - unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ - ) - trace = box - } + fileprivate typealias Trace = @convention(block) (UnsafeRawPointer) -> Void fileprivate var trace: Trace? @@ -740,3 +717,39 @@ extension Result : CustomStringConvertible { } } + +#if !SQLITE_SWIFT_SQLCIPHER +@available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) +extension Connection { + fileprivate func trace_v2(_ callback: ((String) -> Void)?) { + guard let callback = callback else { + // If the X callback is NULL or if the M mask is zero, then tracing is disabled. + sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) + trace = nil + return + } + + let box: Trace = { (pointer: UnsafeRawPointer) in + callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) + } + sqlite3_trace_v2(handle, + UInt32(SQLITE_TRACE_STMT) /* mask */, + { + // A trace callback is invoked with four arguments: callback(T,C,P,X). + // The T argument is one of the SQLITE_TRACE constants to indicate why the + // callback was invoked. The C argument is a copy of the context pointer. + // The P and X arguments are pointers whose meanings depend on T. + (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in + if let P = P, + let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { + unsafeBitCast(C, to: Trace.self)(expandedSQL) + sqlite3_free(expandedSQL) + } + return Int32(0) // currently ignored + }, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ + ) + trace = box + } +} +#endif diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 64b9017f..1dc3e93d 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -24,6 +24,8 @@ #if SQLITE_SWIFT_STANDALONE import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher #elseif COCOAPODS import CSQLite #endif diff --git a/SQLite/Helpers.swift b/SQLite/Helpers.swift index febdc949..08c9a144 100644 --- a/SQLite/Helpers.swift +++ b/SQLite/Helpers.swift @@ -24,6 +24,8 @@ #if SQLITE_SWIFT_STANDALONE import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher #elseif COCOAPODS import CSQLite #endif diff --git a/SQLiteTests/CipherTests.swift b/SQLiteTests/CipherTests.swift new file mode 100644 index 00000000..89b67f76 --- /dev/null +++ b/SQLiteTests/CipherTests.swift @@ -0,0 +1,74 @@ +#if SQLITE_SWIFT_SQLCIPHER +import XCTest +import SQLite +import SQLCipher + +class CipherTests: XCTestCase { + + let db = try! Connection() + let db2 = try! Connection() + + override func setUp() { + // db + try! db.key("hello") + + try! db.run("CREATE TABLE foo (bar TEXT)") + try! db.run("INSERT INTO foo (bar) VALUES ('world')") + + // db2 + let keyData = NSMutableData(length: 64)! + let _ = SecRandomCopyBytes(kSecRandomDefault, 64, + keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) + try! db2.key(Blob(bytes: keyData.bytes, length: keyData.length)) + + try! db2.run("CREATE TABLE foo (bar TEXT)") + try! db2.run("INSERT INTO foo (bar) VALUES ('world')") + + super.setUp() + } + + func test_key() { + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as? Int64) + } + + func test_rekey() { + try! db.rekey("goodbye") + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as? Int64) + } + + func test_data_key() { + XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) + } + + func test_data_rekey() { + let keyData = NSMutableData(length: 64)! + let _ = SecRandomCopyBytes(kSecRandomDefault, 64, + keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) + + try! db2.rekey(Blob(bytes: keyData.bytes, length: keyData.length)) + XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) + } + + func test_keyFailure() { + let path = "\(NSTemporaryDirectory())/db.sqlite3" + _ = try? FileManager.default.removeItem(atPath: path) + + let connA = try! Connection(path) + defer { try! FileManager.default.removeItem(atPath: path) } + + try! connA.key("hello") + + let connB = try! Connection(path) + + var rc: Int32? + do { + try connB.key("world") + } catch Result.error(_, let code, _) { + rc = code + } catch { + XCTFail() + } + XCTAssertEqual(SQLITE_NOTADB, rc) + } +} +#endif diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 6455d30a..e5e690f7 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -2,6 +2,8 @@ import XCTest import SQLite #if SQLITE_SWIFT_STANDALONE import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher #elseif COCOAPODS import CSQLite #endif diff --git a/SQLiteTests/FTS4Tests.swift b/SQLiteTests/FTS4Tests.swift index b2ccc86c..4373bf8b 100644 --- a/SQLiteTests/FTS4Tests.swift +++ b/SQLiteTests/FTS4Tests.swift @@ -176,7 +176,7 @@ class FTS4ConfigTests : XCTestCase { } class FTS4IntegrationTests : SQLiteTestCase { -#if !SQLITE_SWIFT_STANDALONE +#if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER func test_registerTokenizer_registersTokenizer() { let emails = VirtualTable("emails") let subject = Expression("subject") From 3d85793876d65198b17749db784ac4081030cba3 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 14:55:15 +0000 Subject: [PATCH 0494/1046] Add sqlcipher subspec --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 833c67d5..3de39c11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ matrix: - env: VALIDATOR_SUBSPEC="none" - env: VALIDATOR_SUBSPEC="standard" - env: VALIDATOR_SUBSPEC="standalone" + - env: VALIDATOR_SUBSPEC="sqlcipher" before_install: - gem update bundler - gem install xcpretty --no-document From 20a10a3492e688df7b246ae99beecce83a664b35 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 19:19:39 +0000 Subject: [PATCH 0495/1046] WS --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 82e9f43f..49acfc55 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -114,7 +114,7 @@ install SQLite.swift with Carthage: 4. Run `pod install --repo-update`. - #### Requiring a specific version of SQLite +#### Requiring a specific version of SQLite If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: From ffd079317fea9b703322ce8a35831974bf60ae7a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 19:27:56 +0000 Subject: [PATCH 0496/1046] Revert "public check method, needed for #389" This reverts commit bb3b7059a324651e89357da0e6edeaa2c5d06d36. --- SQLite/Core/Connection.swift | 2 +- SQLiteTests/ConnectionTests.swift | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 42cfc2a5..cad25996 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -651,7 +651,7 @@ public final class Connection { return success! } - @discardableResult public func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { + @discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { guard let error = Result(errorCode: resultCode, connection: self, statement: statement) else { return resultCode } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index e5e690f7..3b255fd0 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -340,11 +340,4 @@ class ConnectionTests : SQLiteTestCase { AssertThrows(try stmt.run()) } - func test_check_with_successCode_returnsCode() { - try! XCTAssertEqual(SQLITE_OK, db.check(SQLITE_OK)) - } - - func test_check_with_errorCode_throws() { - XCTAssertThrowsError(try db.check(SQLITE_CONSTRAINT)) - } } From c86db3d67337cd9bb3bb5be2c6b29f06e28826ae Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 20:15:39 +0000 Subject: [PATCH 0497/1046] Naming; paths --- .travis.yml | 2 +- Documentation/Index.md | 4 ++-- SQLite.swift.podspec | 12 ++++++++---- SQLite.xcodeproj/project.pbxproj | 20 ++++++++++---------- SQLite/{Core => Extensions}/Cipher.swift | 0 5 files changed, 21 insertions(+), 17 deletions(-) rename SQLite/{Core => Extensions}/Cipher.swift (100%) diff --git a/.travis.yml b/.travis.yml index 3de39c11..98975aba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: - env: VALIDATOR_SUBSPEC="none" - env: VALIDATOR_SUBSPEC="standard" - env: VALIDATOR_SUBSPEC="standalone" - - env: VALIDATOR_SUBSPEC="sqlcipher" + - env: VALIDATOR_SUBSPEC="SQLCipher" before_install: - gem update bundler - gem install xcpretty --no-document diff --git a/Documentation/Index.md b/Documentation/Index.md index 49acfc55..ae3a8a0b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -133,11 +133,11 @@ See the [sqlite3 podspec][sqlite3pod] for more details. #### Using SQLite.swift with SQLCipher -If you want to use [SQLCipher][] with SQLite.swift you can require the `sqlcipher` +If you want to use [SQLCipher][] with SQLite.swift you can require the `SQLCipher` subspec in your Podfile: ``` ruby - pod 'SQLite.swift/sqlcipher', '~> 0.11.1' + pod 'SQLite.swift/SQLCipher', '~> 0.11.1' ``` This will automatically add a dependency to the SQLCipher pod as well as extend diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index fb17adda..9df09b47 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -26,6 +26,7 @@ Pod::Spec.new do |s| s.subspec 'standard' do |ss| ss.source_files = 'SQLite/**/*.{c,h,m,swift}' + ss.exclude_files = 'SQLite/Extensions/Cipher.swift' ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' ss.library = 'sqlite3' @@ -47,20 +48,23 @@ Pod::Spec.new do |s| s.subspec 'standalone' do |ss| ss.source_files = 'SQLite/**/*.{c,h,m,swift}' + ss.exclude_files = 'SQLite/Extensions/Cipher.swift' ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' - ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' } + ss.xcconfig = { + 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' + } ss.dependency 'sqlite3' end - s.subspec 'sqlcipher' do |ss| + s.subspec 'SQLCipher' do |ss| ss.source_files = 'SQLite/**/*.{c,h,m,swift}' ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', - 'GCC_PREPROCESSOR_DEFINITIONS' => 'SQLITE_HAS_CODEC=1', - 'OTHER_CFLAGS' => '-DSQLITE_HAS_CODEC' + 'GCC_PREPROCESSOR_DEFINITIONS' => 'SQLITE_HAS_CODEC=1' } + ss.dependency 'SQLCipher' end end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 69cd87fb..531d5713 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -48,18 +48,18 @@ 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; - 19A179C2C078DDD29E052DA9 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; - 19A17BF0509EA4A026530E23 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; + 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; - 19A17E24574F51C37C04C758 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; - 19A17E7E3DA4966FA2921857 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171CACA25EB58FE045146 /* Cipher.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; @@ -194,10 +194,10 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; - 19A171CACA25EB58FE045146 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -479,7 +479,6 @@ EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, - 19A171CACA25EB58FE045146 /* Cipher.swift */, ); path = Core; sourceTree = ""; @@ -490,6 +489,7 @@ EE247AF51C3F06E900AE3E12 /* FTS4.swift */, EE247AF61C3F06E900AE3E12 /* RTree.swift */, 19A1730E4390C775C25677D1 /* FTS5.swift */, + 19A178A39ACA9667A62663CC /* Cipher.swift */, ); path = Extensions; sourceTree = ""; @@ -856,7 +856,7 @@ 03A65E771C6BB2E60062603F /* Connection.swift in Sources */, 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, - 19A179C2C078DDD29E052DA9 /* Cipher.swift in Sources */, + 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -908,7 +908,7 @@ 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */, 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, - 19A17E7E3DA4966FA2921857 /* Cipher.swift in Sources */, + 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -935,7 +935,7 @@ EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, - 19A17E24574F51C37C04C758 /* Cipher.swift in Sources */, + 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -987,7 +987,7 @@ EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */, EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, - 19A17BF0509EA4A026530E23 /* Cipher.swift in Sources */, + 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite/Core/Cipher.swift b/SQLite/Extensions/Cipher.swift similarity index 100% rename from SQLite/Core/Cipher.swift rename to SQLite/Extensions/Cipher.swift From 47dfa59d96e130d5444ce99c537002e3159175f6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 5 Dec 2016 20:48:37 +0000 Subject: [PATCH 0498/1046] Remove some duplication in key/rekey --- SQLite/Extensions/Cipher.swift | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/SQLite/Extensions/Cipher.swift b/SQLite/Extensions/Cipher.swift index 3533854e..e0b53390 100644 --- a/SQLite/Extensions/Cipher.swift +++ b/SQLite/Extensions/Cipher.swift @@ -3,27 +3,32 @@ import SQLCipher extension Connection { public func key(_ key: String) throws { - try check(sqlite3_key(handle, key, Int32(key.utf8.count))) - try execute( - "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + - "DROP TABLE \"__SQLCipher.swift__\";" - ) + try _key(keyPointer: key, keySize: key.utf8.count) + } + + public func key(_ key: Blob) throws { + try _key(keyPointer: key.bytes, keySize: key.bytes.count) } public func rekey(_ key: String) throws { - try check(sqlite3_rekey(handle, key, Int32(key.utf8.count))) + try _rekey(keyPointer: key, keySize: key.utf8.count) } - public func key(_ key: Blob) throws { - try check(sqlite3_key(handle, key.bytes, Int32(key.bytes.count))) + public func rekey(_ key: Blob) throws { + try _rekey(keyPointer: key.bytes, keySize: key.bytes.count) + } + + // MARK: - private + private func _key(keyPointer: UnsafePointer, keySize: Int) throws { + try check(sqlite3_key(handle, keyPointer, Int32(keySize))) try execute( "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + "DROP TABLE \"__SQLCipher.swift__\";" ) } - public func rekey(_ key: Blob) throws { - try check(sqlite3_rekey(handle, key.bytes, Int32(key.bytes.count))) + private func _rekey(keyPointer: UnsafePointer, keySize: Int) throws { + try check(sqlite3_rekey(handle, keyPointer, Int32(keySize))) } } #endif From 9c37e8730720ff0dbdf720eea999dec8d3158367 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 6 Dec 2016 06:43:19 +0000 Subject: [PATCH 0499/1046] =?UTF-8?q?Don=E2=80=99t=20overwrite=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 9df09b47..589d73c0 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -62,7 +62,7 @@ Pod::Spec.new do |s| ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', - 'GCC_PREPROCESSOR_DEFINITIONS' => 'SQLITE_HAS_CODEC=1' + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' } ss.dependency 'SQLCipher' From 1a76ab1cbbe7d6b9120535b28d380adb4ea9898b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 6 Dec 2016 06:58:45 +0000 Subject: [PATCH 0500/1046] Explicitly set required versions --- SQLite.swift.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 589d73c0..ad423298 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -54,7 +54,7 @@ Pod::Spec.new do |s| 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' } - ss.dependency 'sqlite3' + ss.dependency 'sqlite3', '>= 3.14.0' end s.subspec 'SQLCipher' do |ss| @@ -65,6 +65,6 @@ Pod::Spec.new do |s| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' } - ss.dependency 'SQLCipher' + ss.dependency 'SQLCipher', '>= 3.4.0' end end From a5184bed7c694ce6b9835a6593b9568a902de575 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 6 Dec 2016 07:04:16 +0000 Subject: [PATCH 0501/1046] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34cd0bc4..cdf98550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.11.1 (05-12-2016), [diff][diff-0.11.1]) +0.11.1 (06-12-2016), [diff][diff-0.11.1]) ======================================== * Integrate SQLCipher via CocoaPods (#546) From f8be0b2f512015c0ce8b84f24e12dfd1987a7532 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 6 Dec 2016 07:09:10 +0000 Subject: [PATCH 0502/1046] Update planning --- Documentation/Planning.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 12555703..a0c6d049 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -1,24 +1,19 @@ # SQLite.swift Planning -This document captures both near term steps (aka Roadmap) and feature requests. +This document captures both near term steps (aka Roadmap) and feature requests. The goal is to add some visibility and guidance for future additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. ## Roadmap _Lists agreed upon next steps in approximate priority order._ - * ~~publish to the CocoaPods directory at https://cocoapods.org/, per [#257](https://github.com/stephencelis/SQLite.swift/issues/257)~~ _jan 7, 2016_) - * add SQLCipher back into the product as a separate repo, per [#311](https://github.com/stephencelis/SQLite.swift/issues/311), _in progress jan 6, 2016_ - - ## Feature Requests _A gathering point for ideas for new features. In general, the corresponding issue will be closed once it is added here, with the assumption that it will be referred to when it comes time to add the corresponding feature._ ### Packaging - * add TV OS support, per [#272](https://github.com/stephencelis/SQLite.swift/issues/272) - currently pending SQLiteCipher merge and/or adding ability for user to pick their preferred [SQLCipher](https://github.com/sqlcipher/sqlcipher) branch - * linux support via Swift Package Manager, per [#315](https://github.com/stephencelis/SQLite.swift/issues/315) + * linux support via Swift Package Manager, per [#315](https://github.com/stephencelis/SQLite.swift/issues/315), _in progress_: [#548](https://github.com/stephencelis/SQLite.swift/pull/548) ### Features From 2ecb8d9b41cd8a1b26a68b9f0570f8c2d491b7bd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 6 Dec 2016 07:09:51 +0000 Subject: [PATCH 0503/1046] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf98550..27036a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,5 +12,5 @@ * Swift3 migration ([diff][diff-0.11.0]) -[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.0 +[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 [diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0 From f8bdf2aef5ba0b624c23f0b4f9023c728df04f6d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 6 Dec 2016 07:21:32 +0000 Subject: [PATCH 0504/1046] Link to issues/PRs in changelog --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27036a7f..394f651f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -0.11.1 (06-12-2016), [diff][diff-0.11.1]) + +0.11.1 (06-12-2016), [diff][diff-0.11.1] ======================================== -* Integrate SQLCipher via CocoaPods (#546) -* Made lastInsertRowid consistent with other SQLite wrappers (#532) +* Integrate SQLCipher via CocoaPods ([#546][], [#553][]) +* Made lastInsertRowid consistent with other SQLite wrappers ([#532][]) * Fix for ~= operator used with Double ranges * Various documentation updates @@ -14,3 +15,7 @@ [diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 [diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0 + +[#532]: https://github.com/stephencelis/SQLite.swift/issues/532 +[#546]: https://github.com/stephencelis/SQLite.swift/issues/546 +[#553]: https://github.com/stephencelis/SQLite.swift/pull/553 From 59fd5e7f3c0fa23af1080bef2c6d15476ba78d9e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 7 Dec 2016 20:04:08 +0000 Subject: [PATCH 0505/1046] Update instructions for manual install Closes #536 --- Documentation/Index.md | 7 +++++++ README.md | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index ae3a8a0b..c70ba2b9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -172,6 +172,13 @@ To install SQLite.swift as an Xcode sub-project: You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift. +Some additional steps are required to install the application on an actual device: + + 5. In the **General** tab, click the **+** button under **Embedded Binaries**. + + 6. Select the appropriate **SQLite.framework** for your platform. + + 7. **Add**. ### Frameworkless Targets diff --git a/README.md b/README.md index a9b44abe..4d28f3b5 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,15 @@ To install SQLite.swift as an Xcode sub-project: 4. **Add**. +Some additional steps are required to install the application on an actual device: + + 5. In the **General** tab, click the **+** button under **Embedded Binaries**. + + 6. Select the appropriate **SQLite.framework** for your platform. + + 7. **Add**. + + [Frameworkless Targets]: Documentation/Index.md#frameworkless-targets [Xcode]: https://developer.apple.com/xcode/downloads/ [Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules From f32a43c316df8008aacb5fdc53e99924532064f5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 7 Dec 2016 21:18:38 +0000 Subject: [PATCH 0506/1046] Updated for CocoaPods 1.x --- Documentation/Index.md | 4 +++- README.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index c70ba2b9..9f52724f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,9 @@ install SQLite.swift with Carthage: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.11.1' + target 'YourAppTargetName' do + pod 'SQLite.swift', '~> 0.11.1' + end ``` 4. Run `pod install --repo-update`. diff --git a/README.md b/README.md index 4d28f3b5..f75aaa31 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,9 @@ SQLite.swift with CocoaPods: ``` ruby use_frameworks! - pod 'SQLite.swift', '~> 0.11.1' + target 'YourAppTargetName' do + pod 'SQLite.swift', '~> 0.11.1' + end ``` 4. Run `pod install --repo-update`. From edf1112b98295f5dfd10f9c422554b47875d9c45 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 8 Dec 2016 02:14:04 +0000 Subject: [PATCH 0507/1046] Use sqlcipher v2 methods, refactor, add docs --- SQLite/Extensions/Cipher.swift | 57 +++++++++++++++++++++++++--------- SQLiteTests/CipherTests.swift | 21 +++++++------ 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/SQLite/Extensions/Cipher.swift b/SQLite/Extensions/Cipher.swift index e0b53390..5b7074bb 100644 --- a/SQLite/Extensions/Cipher.swift +++ b/SQLite/Extensions/Cipher.swift @@ -1,34 +1,63 @@ #if SQLITE_SWIFT_SQLCIPHER import SQLCipher + +/// Extension methods for [SQLCipher](https://www.zetetic.net/sqlcipher/). extension Connection { - public func key(_ key: String) throws { - try _key(keyPointer: key, keySize: key.utf8.count) + + /// Specify the key for an encrypted database. This routine should be + /// called right after sqlite3_open(). + /// + /// @param key The key to use.The key itself can be a passphrase, which is converted to a key + /// using [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) key derivation. The result + /// is used as the encryption key for the database. + /// + /// Alternatively, it is possible to specify an exact byte sequence using a blob literal. + /// With this method, it is the calling application's responsibility to ensure that the data + /// provided is a 64 character hex string, which will be converted directly to 32 bytes (256 bits) + /// of key data. + /// e.g. x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99' + /// @param db name of the database, defaults to 'main' + public func key(_ key: String, db: String = "main") throws { + try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count) } - public func key(_ key: Blob) throws { - try _key(keyPointer: key.bytes, keySize: key.bytes.count) + public func key(_ key: Blob, db: String = "main") throws { + try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } - public func rekey(_ key: String) throws { - try _rekey(keyPointer: key, keySize: key.utf8.count) + + /// Change the key on an open database. If the current database is not encrypted, this routine + /// will encrypt it. + /// To change the key on an existing encrypted database, it must first be unlocked with the + /// current encryption key. Once the database is readable and writeable, rekey can be used + /// to re-encrypt every page in the database with a new key. + public func rekey(_ key: String, db: String = "main") throws { + try _rekey_v2(db: db, keyPointer: key, keySize: key.utf8.count) } - public func rekey(_ key: Blob) throws { - try _rekey(keyPointer: key.bytes, keySize: key.bytes.count) + public func rekey(_ key: Blob, db: String = "main") throws { + try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } // MARK: - private - private func _key(keyPointer: UnsafePointer, keySize: Int) throws { - try check(sqlite3_key(handle, keyPointer, Int32(keySize))) + private func _key_v2(db: String, keyPointer: UnsafePointer, keySize: Int) throws { + try check(sqlite3_key_v2(handle, db, keyPointer, Int32(keySize))) + try cipher_key_check() + } + + private func _rekey_v2(db: String, keyPointer: UnsafePointer, keySize: Int) throws { + try check(sqlite3_rekey_v2(handle, db, keyPointer, Int32(keySize))) + } + + // When opening an existing database, sqlite3_key_v2 will not immediately throw an error if + // the key provided is incorrect. To test that the database can be successfully opened with the + // provided key, it is necessary to perform some operation on the database (i.e. read from it). + private func cipher_key_check() throws { try execute( "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + "DROP TABLE \"__SQLCipher.swift__\";" ) } - - private func _rekey(keyPointer: UnsafePointer, keySize: Int) throws { - try check(sqlite3_rekey(handle, keyPointer, Int32(keySize))) - } } #endif diff --git a/SQLiteTests/CipherTests.swift b/SQLiteTests/CipherTests.swift index 89b67f76..e7eb099a 100644 --- a/SQLiteTests/CipherTests.swift +++ b/SQLiteTests/CipherTests.swift @@ -16,10 +16,8 @@ class CipherTests: XCTestCase { try! db.run("INSERT INTO foo (bar) VALUES ('world')") // db2 - let keyData = NSMutableData(length: 64)! - let _ = SecRandomCopyBytes(kSecRandomDefault, 64, - keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) - try! db2.key(Blob(bytes: keyData.bytes, length: keyData.length)) + let key = keyData() + try! db2.key(Blob(bytes: key.bytes, length: key.length)) try! db2.run("CREATE TABLE foo (bar TEXT)") try! db2.run("INSERT INTO foo (bar) VALUES ('world')") @@ -41,11 +39,8 @@ class CipherTests: XCTestCase { } func test_data_rekey() { - let keyData = NSMutableData(length: 64)! - let _ = SecRandomCopyBytes(kSecRandomDefault, 64, - keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) - - try! db2.rekey(Blob(bytes: keyData.bytes, length: keyData.length)) + let key = keyData() + try! db2.rekey(Blob(bytes: key.bytes, length: key.length)) XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) } @@ -70,5 +65,13 @@ class CipherTests: XCTestCase { } XCTAssertEqual(SQLITE_NOTADB, rc) } + + private func keyData(length: Int = 64) -> NSMutableData { + let keyData = NSMutableData(length: length)! + let result = SecRandomCopyBytes(kSecRandomDefault, length, + keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) + XCTAssertEqual(0, result) + return keyData + } } #endif From 4747d9e2891135e03e8d305446976e830649f378 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 8 Dec 2016 09:25:26 +0000 Subject: [PATCH 0508/1046] Test naming --- SQLite/Extensions/Cipher.swift | 1 + SQLiteTests/CipherTests.swift | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/SQLite/Extensions/Cipher.swift b/SQLite/Extensions/Cipher.swift index 5b7074bb..9be3fa91 100644 --- a/SQLite/Extensions/Cipher.swift +++ b/SQLite/Extensions/Cipher.swift @@ -3,6 +3,7 @@ import SQLCipher /// Extension methods for [SQLCipher](https://www.zetetic.net/sqlcipher/). +/// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/) extension Connection { /// Specify the key for an encrypted database. This routine should be diff --git a/SQLiteTests/CipherTests.swift b/SQLiteTests/CipherTests.swift index e7eb099a..6989e112 100644 --- a/SQLiteTests/CipherTests.swift +++ b/SQLiteTests/CipherTests.swift @@ -5,19 +5,20 @@ import SQLCipher class CipherTests: XCTestCase { - let db = try! Connection() + let db1 = try! Connection() let db2 = try! Connection() override func setUp() { // db - try! db.key("hello") - try! db.run("CREATE TABLE foo (bar TEXT)") - try! db.run("INSERT INTO foo (bar) VALUES ('world')") + try! db1.key("hello") + + try! db1.run("CREATE TABLE foo (bar TEXT)") + try! db1.run("INSERT INTO foo (bar) VALUES ('world')") // db2 - let key = keyData() - try! db2.key(Blob(bytes: key.bytes, length: key.length)) + let key2 = keyData() + try! db2.key(Blob(bytes: key2.bytes, length: key2.length)) try! db2.run("CREATE TABLE foo (bar TEXT)") try! db2.run("INSERT INTO foo (bar) VALUES ('world')") @@ -26,12 +27,12 @@ class CipherTests: XCTestCase { } func test_key() { - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as? Int64) + XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) } func test_rekey() { - try! db.rekey("goodbye") - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as? Int64) + try! db1.rekey("goodbye") + XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) } func test_data_key() { @@ -39,8 +40,8 @@ class CipherTests: XCTestCase { } func test_data_rekey() { - let key = keyData() - try! db2.rekey(Blob(bytes: key.bytes, length: key.length)) + let newKey = keyData() + try! db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) } From 53dda835477415709d37997442d60773a433ffda Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 8 Dec 2016 10:02:13 +0000 Subject: [PATCH 0509/1046] Add test to read pre-encrypted file --- SQLite.xcodeproj/project.pbxproj | 16 ++++++++++++++++ SQLiteTests/CipherTests.swift | 16 ++++++++++++++++ SQLiteTests/Fixtures.swift | 8 ++++++++ SQLiteTests/fixtures/encrypted.sqlite | Bin 0 -> 2048 bytes 4 files changed, 40 insertions(+) create mode 100644 SQLiteTests/Fixtures.swift create mode 100644 SQLiteTests/fixtures/encrypted.sqlite diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 531d5713..0a242fbc 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,12 +46,15 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -61,7 +64,10 @@ 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17FDA323BAFDEC627E76F /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; @@ -199,6 +205,8 @@ 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; + 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; + 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; @@ -465,6 +473,8 @@ 19A1721B8984686B9963B45D /* FTS5Tests.swift */, 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, 19A17399EA9E61235D5D77BF /* CipherTests.swift */, + 19A17E2695737FAB5D6086E3 /* fixtures */, + 19A17B93B48B5560E6E51791 /* Fixtures.swift */, ); path = SQLiteTests; sourceTree = ""; @@ -792,6 +802,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -813,6 +824,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 19A17FDA323BAFDEC627E76F /* fixtures in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -827,6 +839,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 19A175DFF47B84757E547C62 /* fixtures in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -882,6 +895,7 @@ 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, + 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -961,6 +975,7 @@ 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, + 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1013,6 +1028,7 @@ 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, + 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLiteTests/CipherTests.swift b/SQLiteTests/CipherTests.swift index 6989e112..b3ff2d33 100644 --- a/SQLiteTests/CipherTests.swift +++ b/SQLiteTests/CipherTests.swift @@ -30,6 +30,11 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) } + func test_key_blob_literal() { + let db = try! Connection() + try! db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") + } + func test_rekey() { try! db1.rekey("goodbye") XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) @@ -67,6 +72,17 @@ class CipherTests: XCTestCase { XCTAssertEqual(SQLITE_NOTADB, rc) } + func test_open_db_encrypted_with_sqlcipher() { + // $ sqlcipher SQLiteTests/fixtures/encrypted.sqlite + // sqlite> pragma key = 'sqlcipher-test'; + // sqlite> CREATE TABLE foo (bar TEXT); + // sqlite> INSERT INTO foo (bar) VALUES ('world'); + let encryptedFile = fixture("encrypted", withExtension: "sqlite") + let conn = try! Connection(encryptedFile) + try! conn.key("sqlcipher-test") + XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64) + } + private func keyData(length: Int = 64) -> NSMutableData { let keyData = NSMutableData(length: length)! let result = SecRandomCopyBytes(kSecRandomDefault, length, diff --git a/SQLiteTests/Fixtures.swift b/SQLiteTests/Fixtures.swift new file mode 100644 index 00000000..13f83f77 --- /dev/null +++ b/SQLiteTests/Fixtures.swift @@ -0,0 +1,8 @@ +import Foundation + +func fixture(_ name: String, withExtension: String?) -> String { + let testBundle = Bundle(for: SQLiteTestCase.self) + return testBundle.url( + forResource: URL(string: "fixtures")?.appendingPathComponent(name).path, + withExtension: withExtension)!.path +} diff --git a/SQLiteTests/fixtures/encrypted.sqlite b/SQLiteTests/fixtures/encrypted.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..4b3c4d0ec06c7224bbe23ca0e8bf3c5538a66ae9 GIT binary patch literal 2048 zcmV+b2>3)08a>;W-J5(;;@D*~Bq9FW90iNe-OU^w&-Fcb$F#j?3aO_f4=8pqB zwUCPy(GDu&{$YUF5!X#`@>&eL5Em+_AEBZn8MZu@THAj{8Bv!*evFKqdG{YKs^8#T zCpmB+qU86jKR*e(Y>tA*FEg$$J|-8+pg&N&8o;RU8`DsWc#tGE$f5ywo9kBc@QhiHX%2s8Quttn2V zuGP*8u0`77#)W$on++;OqGNNqzogAI?GX)H;H_#6^&_K^!wl`xO)Ih9&ymiuN4PcU zt&VfsB*ds2Vnzrm!$C#3&;=p#{3 zrr8mr`4MBBojX@#pxxmE%B{n8MdtZy-h_#mQ2k%uyFPR1{JfQ)0kAC{P-Ebs2tDq( zjPnghm6-cX?zC=zlxrpaU;H@eW4x~N=Oiwu29xA|y+f4@TDvQOM&;U7tN(l#eDucI zDa#1T?{pn)V=;L{nNvz7?GND8N`9Srs{URN;Jj{j{#nl}qrVi}5+wb#ncUl&RiVOQQ95zzU@4a#jWNnrZo5HlLAWh0 z_sW}l4KZD#Uyzy4xJ2G2WIpg5N*q$x`drk|^-<;W5$_PMhXz3{o+>X_OsN!Ei;d7d z$Wm=XO};b66?p);>yJa|$VMDf^2gn-!SqbLPm9G}XB?%<@Dm2SF^CnJubCMU!+62& z<6JqBo`kMu?=AVrJ9hSBuroR3ZAO^n82#F)IREa{RNaq8xtWqCL5rRO4rn1++A;T- zH|wXI{~2a3e_c{8?;lGUo#GaDY(ZT$7PzhbC~erD8UeZ>kbz;z#d#6wacoA|n|_cX zSRnz&3qql~%jFQ`->|)Z2u}zajj{Peubri}rg+)kAO==0#2J zt>7uOLj4yB^C;y}%`U+c{PM%I_Rr>nUr=Mbtw2ky-gayM)7tgQ(J$KJ%yNbw>C_P2 z@PxB}H>HPB-kS)kB)x|u_}grUH|bSTzi70Ifz^+Wo&Ok$phkHBS_M|*YGz>kH<^+% zx-ZFmvoy+=5@Qt898FL49?%td>4!+=>!ueg^$68NXD8gaNTCesCKyzD?22s(Dw0wO z131^t6@R1LR4IXd!cK|y9s{Y9(L7!VIO*oMa0i6M{Q_5-s~!tV#J$){OZ|vu#$c0k z<8lcG?@1BFroP|He3B|;P%W1^!JtSl+cdI|a@obN`_0ywi1IzfP)a6MtPdA7K4A#8 zB6cuIWSff|DQzNO*tyB#JhA8?I8{HJY+Fv?9>my=c@g?Gi4R#?5Ja27&diEbN}GeX zWgDS*ti#>3*OLoeHgn_|n_SW`rsh4luOh#lZ6eXmKMiU7qOhP1sZ_`}B>Fu4HAwl$ z&&+LFFHOBz2!Vw-sJei$;m@otCD5wYf!Gt3Rji(lK&1$#LTfAFZjBcR(ifwCZA+_q z%QA?z-s3I&?J5;rtYpsIp0UegCO^+prph0M*w9{@xHuo|ENOFE^a~&!y&!?@Qzy1(qN2noyyx? zqXw(T+H|8K@GXJ)|!Q%`oU#ql2yWN7hR(=l(1NRDA%uC7vS!T=Z0H zUv93_!4dz2nUOF^xy~i%Ui+`x`oHAOE$LrK5F4>0t%l~Ux=K3QJTYl7jYG&vDWUXh e@S*KLmc9i7F)WxTL6K?@Y7};KXset%z6Wp5kNblF literal 0 HcmV?d00001 From 555a62054f3c0bfc73040da3852b581c4d6619cb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 8 Dec 2016 11:02:49 +0000 Subject: [PATCH 0510/1046] Add resources to test target --- CocoaPodsTests/integration_test.rb | 1 + CocoaPodsTests/test_running_validator.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CocoaPodsTests/integration_test.rb b/CocoaPodsTests/integration_test.rb index c62e066e..7f3e8132 100755 --- a/CocoaPodsTests/integration_test.rb +++ b/CocoaPodsTests/integration_test.rb @@ -14,6 +14,7 @@ def test_validate_project def validator @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| validator.test_files = Dir["#{project_test_dir}/*.swift"] + validator.test_resources = Dir["#{project_test_dir}/fixtures"] validator.config.verbose = true validator.no_clean = true validator.use_frameworks = true diff --git a/CocoaPodsTests/test_running_validator.rb b/CocoaPodsTests/test_running_validator.rb index 410f9465..fcfd3c28 100644 --- a/CocoaPodsTests/test_running_validator.rb +++ b/CocoaPodsTests/test_running_validator.rb @@ -7,12 +7,15 @@ class TestRunningValidator < Pod::Validator TEST_TARGET = 'Tests' attr_accessor :test_files + attr_accessor :test_resources attr_accessor :ios_simulator attr_accessor :tvos_simulator attr_accessor :watchos_simulator def initialize(spec_or_path, source_urls) super(spec_or_path, source_urls) + self.test_files = [] + self.test_resources = [] self.ios_simulator = :oldest self.tvos_simulator = :oldest self.watchos_simulator = :oldest @@ -30,6 +33,7 @@ def add_app_project_import project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj') group = project.new_group(TEST_TARGET) test_target = project.targets.last + test_target.add_resources(test_resources.map { |resource| group.new_file(resource) }) test_target.add_file_references(test_files.map { |file| group.new_file(file) }) add_swift_version(test_target) project.save From f7e9fe9c2c1ff48a85616dac1808b4d927eef9d4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 8 Dec 2016 19:09:17 +0000 Subject: [PATCH 0511/1046] Include function return code in error message --- SQLite/Core/Connection.swift | 11 ++++---- SQLiteTests/ConnectionTests.swift | 43 ++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index cad25996..87ea3a22 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -709,13 +709,14 @@ extension Result : CustomStringConvertible { public var description: String { switch self { - case let .error(message, _, statement): - guard let statement = statement else { return message } - - return "\(message) (\(statement))" + case let .error(message, errorCode, statement): + if let statement = statement { + return "\(message) (\(statement)) (code: \(errorCode))" + } else { + return "\(message) (code: \(errorCode))" + } } } - } #if !SQLITE_SWIFT_SQLCIPHER diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 3b255fd0..33c7610e 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -1,5 +1,6 @@ import XCTest -import SQLite +@testable import SQLite + #if SQLITE_SWIFT_STANDALONE import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER @@ -341,3 +342,43 @@ class ConnectionTests : SQLiteTestCase { } } + + +class ResultTests : XCTestCase { + let connection = try! Connection(.inMemory) + + func test_init_with_ok_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) + } + + func test_init_with_row_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_ROW, connection: connection, statement: nil) as Result?) + } + + func test_init_with_done_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_DONE, connection: connection, statement: nil) as Result?) + } + + func test_init_with_other_code_returns_error() { + if case .some(.error(let message, let code, let statement)) = + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + XCTAssertEqual("not an error", message) + XCTAssertEqual(SQLITE_MISUSE, code) + XCTAssertNil(statement) + XCTAssert(self.connection === connection) + } else { + XCTFail() + } + } + + func test_description_contains_error_code() { + XCTAssertEqual("not an error (code: 21)", + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) + } + + func test_description_contains_statement_and_error_code() { + let statement = try! Statement(connection, "SELECT 1") + XCTAssertEqual("not an error (SELECT 1) (code: 21)", + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) + } +} From 1ed7070ade294051a3b11bf40cd345d7920258c8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 9 Dec 2016 17:25:56 +0000 Subject: [PATCH 0512/1046] Make sure the valid key check works with read-only dbs --- SQLite/Extensions/Cipher.swift | 5 +---- SQLiteTests/CipherTests.swift | 7 ++++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SQLite/Extensions/Cipher.swift b/SQLite/Extensions/Cipher.swift index 9be3fa91..6c0d4657 100644 --- a/SQLite/Extensions/Cipher.swift +++ b/SQLite/Extensions/Cipher.swift @@ -55,10 +55,7 @@ extension Connection { // the key provided is incorrect. To test that the database can be successfully opened with the // provided key, it is necessary to perform some operation on the database (i.e. read from it). private func cipher_key_check() throws { - try execute( - "CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" + - "DROP TABLE \"__SQLCipher.swift__\";" - ) + try scalar("SELECT count(*) FROM sqlite_master;") } } #endif diff --git a/SQLiteTests/CipherTests.swift b/SQLiteTests/CipherTests.swift index b3ff2d33..46e7024e 100644 --- a/SQLiteTests/CipherTests.swift +++ b/SQLiteTests/CipherTests.swift @@ -59,11 +59,12 @@ class CipherTests: XCTestCase { try! connA.key("hello") - let connB = try! Connection(path) + let connB = try! Connection(path, readonly: true) var rc: Int32? do { try connB.key("world") + XCTFail("expected exception") } catch Result.error(_, let code, _) { rc = code } catch { @@ -78,6 +79,10 @@ class CipherTests: XCTestCase { // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); let encryptedFile = fixture("encrypted", withExtension: "sqlite") + + try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile) + XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) + let conn = try! Connection(encryptedFile) try! conn.key("sqlcipher-test") XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64) From be3dc0b02ca1d3e42ff327818f4befe07ac8152d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 9 Dec 2016 17:39:50 +0000 Subject: [PATCH 0513/1046] Update changelog --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 394f651f..56b84ce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.11.2 (unreleased), [diff][diff-0.11.2] +======================================== + +* Fixed SQLCipher integration with read-only databases ([#559][]) 0.11.1 (06-12-2016), [diff][diff-0.11.1] ======================================== @@ -7,15 +11,17 @@ * Fix for ~= operator used with Double ranges * Various documentation updates -0.11.0 (10-19-2016) +0.11.0 (19-10-2016) =================== * Swift3 migration ([diff][diff-0.11.0]) -[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 [diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0 +[diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 +[diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [#532]: https://github.com/stephencelis/SQLite.swift/issues/532 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 [#553]: https://github.com/stephencelis/SQLite.swift/pull/553 +[#559]: https://github.com/stephencelis/SQLite.swift/pull/559 From 7b724b05f11d17d278a32c2ad83b08c28ab2a331 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 9 Dec 2016 19:24:23 +0000 Subject: [PATCH 0514/1046] Fix tests --- SQLiteTests/CipherTests.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SQLiteTests/CipherTests.swift b/SQLiteTests/CipherTests.swift index 46e7024e..3ee0b135 100644 --- a/SQLiteTests/CipherTests.swift +++ b/SQLiteTests/CipherTests.swift @@ -58,19 +58,18 @@ class CipherTests: XCTestCase { defer { try! FileManager.default.removeItem(atPath: path) } try! connA.key("hello") + try! connA.run("CREATE TABLE foo (bar TEXT)") let connB = try! Connection(path, readonly: true) - var rc: Int32? do { try connB.key("world") XCTFail("expected exception") } catch Result.error(_, let code, _) { - rc = code + XCTAssertEqual(SQLITE_NOTADB, code) } catch { - XCTFail() + XCTFail("unexpected error: \(error)") } - XCTAssertEqual(SQLITE_NOTADB, rc) } func test_open_db_encrypted_with_sqlcipher() { From c29247f2147a3595847a592d99ffd11f95b88827 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 13 Dec 2016 12:53:13 +0000 Subject: [PATCH 0515/1046] Fix null pointer when fetching an empty BLOB Closes #561 --- SQLite/Core/Statement.swift | 4 +++- SQLiteTests/StatementTests.swift | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/Statement.swift b/SQLite/Core/Statement.swift index 1dc3e93d..7e584ef9 100644 --- a/SQLite/Core/Statement.swift +++ b/SQLite/Core/Statement.swift @@ -244,7 +244,9 @@ public struct Cursor { let length = Int(sqlite3_column_bytes(handle, Int32(idx))) return Blob(bytes: pointer, length: length) } else { - fatalError("sqlite3_column_blob returned NULL") + // The return value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. + // https://www.sqlite.org/c3ref/column_blob.html + return Blob(bytes: []) } } diff --git a/SQLiteTests/StatementTests.swift b/SQLiteTests/StatementTests.swift index fce3585c..326259b2 100644 --- a/SQLiteTests/StatementTests.swift +++ b/SQLiteTests/StatementTests.swift @@ -14,4 +14,13 @@ class StatementTests : SQLiteTestCase { let blob = statement.row[0] as Blob XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) } + + func test_zero_sized_blob_returns_null() { + let blobs = Table("blobs") + let blobColumn = Expression("blob_column") + try! db.run(blobs.create { $0.column(blobColumn) }) + try! db.run(blobs.insert(blobColumn <- Blob(bytes: []))) + let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) + XCTAssertEqual([], blobValue.bytes) + } } From dd0aa4bdf1a19af6d274f256918b853f1d8724e5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 13 Dec 2016 12:56:05 +0000 Subject: [PATCH 0516/1046] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b84ce4..185e9a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * Fixed SQLCipher integration with read-only databases ([#559][]) +* Fixed null pointer when fetching an empty BLOB ([#561][]) 0.11.1 (06-12-2016), [diff][diff-0.11.1] ======================================== @@ -25,3 +26,4 @@ [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 [#553]: https://github.com/stephencelis/SQLite.swift/pull/553 [#559]: https://github.com/stephencelis/SQLite.swift/pull/559 +[#561]: https://github.com/stephencelis/SQLite.swift/issues/561 From cbcef0264d8f4889dd4d028813de003546445708 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 19 Dec 2016 09:39:11 +0000 Subject: [PATCH 0517/1046] Add note about active Xcode dev directory #120 --- Documentation/Index.md | 6 +++++- README.md | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9f52724f..51348dc3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -93,7 +93,11 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed in the default location (`/Application/Xcode.app`). + 1. Verify that your copy of Xcode is installed and active in the default location (`/Application/Xcode.app`). + + ```sh + sudo xcode-select --switch /Applications/Xcode.app + ``` 2. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). diff --git a/README.md b/README.md index f75aaa31..eb5635ca 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,13 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed in the default location (`/Application/Xcode.app`). + 1. Verify that your copy of Xcode is installed and active in the default location (`/Application/Xcode.app`). - 2. Make sure the latest CocoaPods beta is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) + ```sh + sudo xcode-select --switch /Applications/Xcode.app + ``` + + 2. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) ``` sh # Using the default Ruby install will require you to use sudo when From 7f42a6b58c958d7aa755170ad19b0bb1e7a7d819 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 19 Dec 2016 09:40:54 +0000 Subject: [PATCH 0518/1046] =?UTF-8?q?Missing=20`s=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Documentation/Index.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 51348dc3..40e9f503 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -93,7 +93,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed and active in the default location (`/Application/Xcode.app`). + 1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`). ```sh sudo xcode-select --switch /Applications/Xcode.app diff --git a/README.md b/README.md index eb5635ca..c0b778df 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed and active in the default location (`/Application/Xcode.app`). + 1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`). ```sh sudo xcode-select --switch /Applications/Xcode.app From 42e0ae83f5896f31cd1f183cd2fd635c08204770 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:20:22 -0800 Subject: [PATCH 0519/1046] Moved things around a bit to suit Swift packages. --- {SQLite => Sources/SQLite}/Core/Blob.swift | 0 {SQLite => Sources/SQLite}/Core/Connection.swift | 0 {SQLite => Sources/SQLite}/Core/SQLite-Bridging.h | 0 .../SQLite/Core/SQLite-Bridging.m.hidden | 0 {SQLite => Sources/SQLite}/Core/Statement.swift | 0 {SQLite => Sources/SQLite}/Core/Value.swift | 0 {SQLite => Sources/SQLite}/Core/fts3_tokenizer.h | 0 {SQLite => Sources/SQLite}/Extensions/FTS4.swift | 0 {SQLite => Sources/SQLite}/Extensions/FTS5.swift | 0 {SQLite => Sources/SQLite}/Extensions/RTree.swift | 0 {SQLite => Sources/SQLite}/Foundation.swift | 0 {SQLite => Sources/SQLite}/Helpers.swift | 0 {SQLite => Sources/SQLite}/Info.plist | 0 {SQLite => Sources/SQLite}/SQLite.h | 0 {SQLite => Sources/SQLite}/Typed/AggregateFunctions.swift | 0 {SQLite => Sources/SQLite}/Typed/Collation.swift | 0 {SQLite => Sources/SQLite}/Typed/CoreFunctions.swift | 0 {SQLite => Sources/SQLite}/Typed/CustomFunctions.swift | 0 {SQLite => Sources/SQLite}/Typed/Expression.swift | 0 {SQLite => Sources/SQLite}/Typed/Operators.swift | 0 {SQLite => Sources/SQLite}/Typed/Query.swift | 0 {SQLite => Sources/SQLite}/Typed/Schema.swift | 0 {SQLite => Sources/SQLite}/Typed/Setter.swift | 0 {SQLiteTests => Tests/SQLiteTests}/AggregateFunctionsTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/BlobTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/ConnectionTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/CoreFunctionsTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/CustomFunctionsTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/ExpressionTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/FTS4Tests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/FTS5Tests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/FoundationTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/Info.plist | 0 {SQLiteTests => Tests/SQLiteTests}/OperatorsTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/QueryTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/RTreeTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/SchemaTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/SetterTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/StatementTests.swift | 0 {SQLiteTests => Tests/SQLiteTests}/TestHelpers.swift | 0 {SQLiteTests => Tests/SQLiteTests}/ValueTests.swift | 0 41 files changed, 0 insertions(+), 0 deletions(-) rename {SQLite => Sources/SQLite}/Core/Blob.swift (100%) rename {SQLite => Sources/SQLite}/Core/Connection.swift (100%) rename {SQLite => Sources/SQLite}/Core/SQLite-Bridging.h (100%) rename SQLite/Core/SQLite-Bridging.m => Sources/SQLite/Core/SQLite-Bridging.m.hidden (100%) rename {SQLite => Sources/SQLite}/Core/Statement.swift (100%) rename {SQLite => Sources/SQLite}/Core/Value.swift (100%) rename {SQLite => Sources/SQLite}/Core/fts3_tokenizer.h (100%) rename {SQLite => Sources/SQLite}/Extensions/FTS4.swift (100%) rename {SQLite => Sources/SQLite}/Extensions/FTS5.swift (100%) rename {SQLite => Sources/SQLite}/Extensions/RTree.swift (100%) rename {SQLite => Sources/SQLite}/Foundation.swift (100%) rename {SQLite => Sources/SQLite}/Helpers.swift (100%) rename {SQLite => Sources/SQLite}/Info.plist (100%) rename {SQLite => Sources/SQLite}/SQLite.h (100%) rename {SQLite => Sources/SQLite}/Typed/AggregateFunctions.swift (100%) rename {SQLite => Sources/SQLite}/Typed/Collation.swift (100%) rename {SQLite => Sources/SQLite}/Typed/CoreFunctions.swift (100%) rename {SQLite => Sources/SQLite}/Typed/CustomFunctions.swift (100%) rename {SQLite => Sources/SQLite}/Typed/Expression.swift (100%) rename {SQLite => Sources/SQLite}/Typed/Operators.swift (100%) rename {SQLite => Sources/SQLite}/Typed/Query.swift (100%) rename {SQLite => Sources/SQLite}/Typed/Schema.swift (100%) rename {SQLite => Sources/SQLite}/Typed/Setter.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/AggregateFunctionsTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/BlobTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/ConnectionTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/CoreFunctionsTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/CustomFunctionsTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/ExpressionTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/FTS4Tests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/FTS5Tests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/FoundationTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/Info.plist (100%) rename {SQLiteTests => Tests/SQLiteTests}/OperatorsTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/QueryTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/RTreeTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/SchemaTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/SetterTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/StatementTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/TestHelpers.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/ValueTests.swift (100%) diff --git a/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift similarity index 100% rename from SQLite/Core/Blob.swift rename to Sources/SQLite/Core/Blob.swift diff --git a/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift similarity index 100% rename from SQLite/Core/Connection.swift rename to Sources/SQLite/Core/Connection.swift diff --git a/SQLite/Core/SQLite-Bridging.h b/Sources/SQLite/Core/SQLite-Bridging.h similarity index 100% rename from SQLite/Core/SQLite-Bridging.h rename to Sources/SQLite/Core/SQLite-Bridging.h diff --git a/SQLite/Core/SQLite-Bridging.m b/Sources/SQLite/Core/SQLite-Bridging.m.hidden similarity index 100% rename from SQLite/Core/SQLite-Bridging.m rename to Sources/SQLite/Core/SQLite-Bridging.m.hidden diff --git a/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift similarity index 100% rename from SQLite/Core/Statement.swift rename to Sources/SQLite/Core/Statement.swift diff --git a/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift similarity index 100% rename from SQLite/Core/Value.swift rename to Sources/SQLite/Core/Value.swift diff --git a/SQLite/Core/fts3_tokenizer.h b/Sources/SQLite/Core/fts3_tokenizer.h similarity index 100% rename from SQLite/Core/fts3_tokenizer.h rename to Sources/SQLite/Core/fts3_tokenizer.h diff --git a/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift similarity index 100% rename from SQLite/Extensions/FTS4.swift rename to Sources/SQLite/Extensions/FTS4.swift diff --git a/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift similarity index 100% rename from SQLite/Extensions/FTS5.swift rename to Sources/SQLite/Extensions/FTS5.swift diff --git a/SQLite/Extensions/RTree.swift b/Sources/SQLite/Extensions/RTree.swift similarity index 100% rename from SQLite/Extensions/RTree.swift rename to Sources/SQLite/Extensions/RTree.swift diff --git a/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift similarity index 100% rename from SQLite/Foundation.swift rename to Sources/SQLite/Foundation.swift diff --git a/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift similarity index 100% rename from SQLite/Helpers.swift rename to Sources/SQLite/Helpers.swift diff --git a/SQLite/Info.plist b/Sources/SQLite/Info.plist similarity index 100% rename from SQLite/Info.plist rename to Sources/SQLite/Info.plist diff --git a/SQLite/SQLite.h b/Sources/SQLite/SQLite.h similarity index 100% rename from SQLite/SQLite.h rename to Sources/SQLite/SQLite.h diff --git a/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift similarity index 100% rename from SQLite/Typed/AggregateFunctions.swift rename to Sources/SQLite/Typed/AggregateFunctions.swift diff --git a/SQLite/Typed/Collation.swift b/Sources/SQLite/Typed/Collation.swift similarity index 100% rename from SQLite/Typed/Collation.swift rename to Sources/SQLite/Typed/Collation.swift diff --git a/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift similarity index 100% rename from SQLite/Typed/CoreFunctions.swift rename to Sources/SQLite/Typed/CoreFunctions.swift diff --git a/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift similarity index 100% rename from SQLite/Typed/CustomFunctions.swift rename to Sources/SQLite/Typed/CustomFunctions.swift diff --git a/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift similarity index 100% rename from SQLite/Typed/Expression.swift rename to Sources/SQLite/Typed/Expression.swift diff --git a/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift similarity index 100% rename from SQLite/Typed/Operators.swift rename to Sources/SQLite/Typed/Operators.swift diff --git a/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift similarity index 100% rename from SQLite/Typed/Query.swift rename to Sources/SQLite/Typed/Query.swift diff --git a/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift similarity index 100% rename from SQLite/Typed/Schema.swift rename to Sources/SQLite/Typed/Schema.swift diff --git a/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift similarity index 100% rename from SQLite/Typed/Setter.swift rename to Sources/SQLite/Typed/Setter.swift diff --git a/SQLiteTests/AggregateFunctionsTests.swift b/Tests/SQLiteTests/AggregateFunctionsTests.swift similarity index 100% rename from SQLiteTests/AggregateFunctionsTests.swift rename to Tests/SQLiteTests/AggregateFunctionsTests.swift diff --git a/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/BlobTests.swift similarity index 100% rename from SQLiteTests/BlobTests.swift rename to Tests/SQLiteTests/BlobTests.swift diff --git a/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift similarity index 100% rename from SQLiteTests/ConnectionTests.swift rename to Tests/SQLiteTests/ConnectionTests.swift diff --git a/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift similarity index 100% rename from SQLiteTests/CoreFunctionsTests.swift rename to Tests/SQLiteTests/CoreFunctionsTests.swift diff --git a/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift similarity index 100% rename from SQLiteTests/CustomFunctionsTests.swift rename to Tests/SQLiteTests/CustomFunctionsTests.swift diff --git a/SQLiteTests/ExpressionTests.swift b/Tests/SQLiteTests/ExpressionTests.swift similarity index 100% rename from SQLiteTests/ExpressionTests.swift rename to Tests/SQLiteTests/ExpressionTests.swift diff --git a/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift similarity index 100% rename from SQLiteTests/FTS4Tests.swift rename to Tests/SQLiteTests/FTS4Tests.swift diff --git a/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift similarity index 100% rename from SQLiteTests/FTS5Tests.swift rename to Tests/SQLiteTests/FTS5Tests.swift diff --git a/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift similarity index 100% rename from SQLiteTests/FoundationTests.swift rename to Tests/SQLiteTests/FoundationTests.swift diff --git a/SQLiteTests/Info.plist b/Tests/SQLiteTests/Info.plist similarity index 100% rename from SQLiteTests/Info.plist rename to Tests/SQLiteTests/Info.plist diff --git a/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift similarity index 100% rename from SQLiteTests/OperatorsTests.swift rename to Tests/SQLiteTests/OperatorsTests.swift diff --git a/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift similarity index 100% rename from SQLiteTests/QueryTests.swift rename to Tests/SQLiteTests/QueryTests.swift diff --git a/SQLiteTests/RTreeTests.swift b/Tests/SQLiteTests/RTreeTests.swift similarity index 100% rename from SQLiteTests/RTreeTests.swift rename to Tests/SQLiteTests/RTreeTests.swift diff --git a/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift similarity index 100% rename from SQLiteTests/SchemaTests.swift rename to Tests/SQLiteTests/SchemaTests.swift diff --git a/SQLiteTests/SetterTests.swift b/Tests/SQLiteTests/SetterTests.swift similarity index 100% rename from SQLiteTests/SetterTests.swift rename to Tests/SQLiteTests/SetterTests.swift diff --git a/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift similarity index 100% rename from SQLiteTests/StatementTests.swift rename to Tests/SQLiteTests/StatementTests.swift diff --git a/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift similarity index 100% rename from SQLiteTests/TestHelpers.swift rename to Tests/SQLiteTests/TestHelpers.swift diff --git a/SQLiteTests/ValueTests.swift b/Tests/SQLiteTests/ValueTests.swift similarity index 100% rename from SQLiteTests/ValueTests.swift rename to Tests/SQLiteTests/ValueTests.swift From 9b1bef9e9ca1d8ac3c91bbdcb18388ddbff3970c Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:21:05 -0800 Subject: [PATCH 0520/1046] Ignore '.build' folder created by the `swift` command line tool. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 99e4f5e2..b617bcef 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ DerivedData # Carthage Carthage + +# Swift Package Manager +.build From 20bda8f652085dd6d0abf091d0fcffd04b19b052 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:21:46 -0800 Subject: [PATCH 0521/1046] Added the CSQLite submodule. --- Sources/CSQLite/module.modulemap | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Sources/CSQLite/module.modulemap diff --git a/Sources/CSQLite/module.modulemap b/Sources/CSQLite/module.modulemap new file mode 100644 index 00000000..f4127dce --- /dev/null +++ b/Sources/CSQLite/module.modulemap @@ -0,0 +1,4 @@ +module CSQLite { + umbrella header "include/sqlite3.h" + export * +} From 0d408b6164b9a43db8566d82ff2b123cbf054cc8 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:21:58 -0800 Subject: [PATCH 0522/1046] Added an initial Package.swift file. --- Package.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Package.swift diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..372e9441 --- /dev/null +++ b/Package.swift @@ -0,0 +1,14 @@ +import PackageDescription + +let package = Package( + name: "SQLite.swift", + targets: [ + Target( + name: "SQLite", + dependencies: [ + .Target(name: "CSQLite") + ]), + Target( + name: "CSQLite") + ] +) From 4e6b1e00466299136f826ccb7f2c6995eb81b8b2 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:25:54 -0800 Subject: [PATCH 0523/1046] Import CSQLite by default, not just when using CocoaPods. --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 87ea3a22..06611756 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif COCOAPODS +#else import CSQLite #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 7e584ef9..766bd15b 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif COCOAPODS +#else import CSQLite #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 08c9a144..81a7507e 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif COCOAPODS +#else import CSQLite #endif From 0db23c9e73f68ce8e082f410c7d0acd3e489e892 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:44:54 -0800 Subject: [PATCH 0524/1046] Broke the ObjC pieces out into a separate module, so that they can actually be built and used correctly. --- Package.swift | 5 +++++ Sources/SQLite/Extensions/FTS4.swift | 3 +++ .../SQLite-Bridging.m} | 0 Sources/{SQLite/Core => SQLiteObjc}/fts3_tokenizer.h | 0 .../{SQLite/Core => SQLiteObjc/include}/SQLite-Bridging.h | 0 5 files changed, 8 insertions(+) rename Sources/{SQLite/Core/SQLite-Bridging.m.hidden => SQLiteObjc/SQLite-Bridging.m} (100%) rename Sources/{SQLite/Core => SQLiteObjc}/fts3_tokenizer.h (100%) rename Sources/{SQLite/Core => SQLiteObjc/include}/SQLite-Bridging.h (100%) diff --git a/Package.swift b/Package.swift index 372e9441..8cbc2f4d 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,11 @@ let package = Package( targets: [ Target( name: "SQLite", + dependencies: [ + .Target(name: "SQLiteObjc") + ]), + Target( + name: "SQLiteObjc", dependencies: [ .Target(name: "CSQLite") ]), diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 7bbca7a5..6aec27e1 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -22,6 +22,9 @@ // THE SOFTWARE. // +import SQLiteObjc + + extension Module { public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module { diff --git a/Sources/SQLite/Core/SQLite-Bridging.m.hidden b/Sources/SQLiteObjc/SQLite-Bridging.m similarity index 100% rename from Sources/SQLite/Core/SQLite-Bridging.m.hidden rename to Sources/SQLiteObjc/SQLite-Bridging.m diff --git a/Sources/SQLite/Core/fts3_tokenizer.h b/Sources/SQLiteObjc/fts3_tokenizer.h similarity index 100% rename from Sources/SQLite/Core/fts3_tokenizer.h rename to Sources/SQLiteObjc/fts3_tokenizer.h diff --git a/Sources/SQLite/Core/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLite-Bridging.h similarity index 100% rename from Sources/SQLite/Core/SQLite-Bridging.h rename to Sources/SQLiteObjc/include/SQLite-Bridging.h From c7ab2629761d020b09a5dbc4879177e901b86fb3 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:46:17 -0800 Subject: [PATCH 0525/1046] Corrected dependencies - SQLite also uses CSQLite directly, not just indirectly via SQLiteObjc. --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 8cbc2f4d..ae5d09c0 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,8 @@ let package = Package( Target( name: "SQLite", dependencies: [ - .Target(name: "SQLiteObjc") + .Target(name: "SQLiteObjc"), + .Target(name: "CSQLite") ]), Target( name: "SQLiteObjc", From c6b167773f4889af1cc0e26ef3bb0360c3f1e35a Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 22:48:31 -0800 Subject: [PATCH 0526/1046] Renamed the package to just "SQLite", rather than "SQLite.swift", to if nothing else match the existing Xcode project name. --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index ae5d09c0..f5c5b54f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,7 +1,7 @@ import PackageDescription let package = Package( - name: "SQLite.swift", + name: "SQLite", targets: [ Target( name: "SQLite", From 441d592320bff3325e028e9e980806d467d95646 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 23:02:27 -0800 Subject: [PATCH 0527/1046] Fixed up file references, given all the moves made to support Swift packages. --- SQLite.xcodeproj/project.pbxproj | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 0a242fbc..40837d37 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -223,8 +223,8 @@ EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = Sources/SQLiteObjc/fts3_tokenizer.h; sourceTree = ""; }; + EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SQLite-Bridging.m"; path = "Sources/SQLiteObjc/SQLite-Bridging.m"; sourceTree = ""; }; EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; @@ -266,7 +266,7 @@ EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; - EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SQLite-Bridging.h"; path = "../../SQLiteObjc/include/SQLite-Bridging.h"; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -448,7 +448,8 @@ EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, ); - path = SQLite; + name = SQLite; + path = Sources/SQLite; sourceTree = ""; }; EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { @@ -476,7 +477,8 @@ 19A17E2695737FAB5D6086E3 /* fixtures */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, ); - path = SQLiteTests; + name = SQLiteTests; + path = Tests/SQLiteTests; sourceTree = ""; }; EE247AED1C3F06E900AE3E12 /* Core */ = { @@ -1062,7 +1064,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1085,7 +1087,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1135,7 +1137,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1160,7 +1162,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1283,7 +1285,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1309,7 +1311,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1357,7 +1359,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -1384,7 +1386,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; From 28dfcc792112609a661e23360e01d5b197886a14 Mon Sep 17 00:00:00 2001 From: Wade Tregaskis Date: Fri, 18 Nov 2016 23:17:23 -0800 Subject: [PATCH 0528/1046] =?UTF-8?q?=E2=80=A2=C2=A0Fixed=20two=20remainin?= =?UTF-8?q?g=20file=20references=20that=20I=20missed=20in=20the=20first=20?= =?UTF-8?q?pass.=20=E2=80=A2=C2=A0Added=20a=20[hopefully]=20temporary=20wo?= =?UTF-8?q?rkaround=20to=20allow=20building=20within=20the=20existing=20Xc?= =?UTF-8?q?ode=20project=20to=20work=20as=20it=20did=20before,=20in=20addi?= =?UTF-8?q?tion=20to=20command-line=20builds=20via=20`swift`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SQLite.xcodeproj/project.pbxproj | 6 ++++-- Sources/SQLite/Extensions/FTS4.swift | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 40837d37..b2711828 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -223,8 +223,8 @@ EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = Sources/SQLiteObjc/fts3_tokenizer.h; sourceTree = ""; }; - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SQLite-Bridging.m"; path = "Sources/SQLiteObjc/SQLite-Bridging.m"; sourceTree = ""; }; + EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../../SQLiteObjc/fts3_tokenizer.h; sourceTree = ""; }; + EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SQLite-Bridging.m"; path = "../../SQLiteObjc/SQLite-Bridging.m"; sourceTree = ""; }; EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; @@ -1222,6 +1222,7 @@ ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALL_IN_ONE_BUILD; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; @@ -1267,6 +1268,7 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALL_IN_ONE_BUILD; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 6aec27e1..fe9ee6b8 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -22,8 +22,11 @@ // THE SOFTWARE. // +#if ALL_IN_ONE_BUILD +// Nothing required +#else import SQLiteObjc - +#endif extension Module { From c354a7fa71de0f145229fd3fd36703ff15f93cd0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 10 Dec 2016 09:20:02 +0000 Subject: [PATCH 0529/1046] Update docs, add tests, fix podspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix paths Fix up podspec Run tests with package manager missing space Update changelog Fix variable expansion Fix quoting More quoting Don’t use env variable Rename Update doc Try build only Link ref Add to index Sans quotes Doc Simplify podspec Update docs Update planning doc Missing closing quote yml doc Capitalization Remove ALL_IN_ONE_BUILD Use system sqlite3 Fix import Fix bridging header Fixing paths --- .travis.yml | 1 + CHANGELOG.md | 3 ++ CocoaPodsTests/integration_test.rb | 4 +- Documentation/Index.md | 38 +++++++++--------- Documentation/Planning.md | 5 --- Package.swift | 14 +++---- README.md | 20 +++++---- SQLite.swift.podspec | 16 ++++---- SQLite.xcodeproj/project.pbxproj | 16 ++++---- Sources/CSQLite/module.modulemap | 4 -- .../SQLite}/Extensions/Cipher.swift | 0 Sources/SQLite/Extensions/FTS4.swift | 4 +- Sources/SQLiteObjc/SQLite-Bridging.m | 1 - .../SQLiteTests}/CipherTests.swift | 0 Tests/SQLiteTests/ConnectionTests.swift | 2 +- .../SQLiteTests}/Fixtures.swift | 0 .../SQLiteTests}/fixtures/encrypted.sqlite | Bin run-tests.sh | 2 + 18 files changed, 62 insertions(+), 68 deletions(-) delete mode 100644 Sources/CSQLite/module.modulemap rename {SQLite => Sources/SQLite}/Extensions/Cipher.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/CipherTests.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/Fixtures.swift (100%) rename {SQLiteTests => Tests/SQLiteTests}/fixtures/encrypted.sqlite (100%) diff --git a/.travis.yml b/.travis.yml index 98975aba..e114b573 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ matrix: - env: VALIDATOR_SUBSPEC="standard" - env: VALIDATOR_SUBSPEC="standalone" - env: VALIDATOR_SUBSPEC="SQLCipher" + - env: PACKAGE_MANAGER_COMMAND="test -Xlinker -lsqlite3" before_install: - gem update bundler - gem install xcpretty --no-document diff --git a/CHANGELOG.md b/CHANGELOG.md index 185e9a63..c23af3cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * Fixed SQLCipher integration with read-only databases ([#559][]) +* Preliminary Swift Package Manager support ([#548][], [#560][]) * Fixed null pointer when fetching an empty BLOB ([#561][]) 0.11.1 (06-12-2016), [diff][diff-0.11.1] @@ -24,6 +25,8 @@ [#532]: https://github.com/stephencelis/SQLite.swift/issues/532 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 +[#548]: https://github.com/stephencelis/SQLite.swift/pull/548 [#553]: https://github.com/stephencelis/SQLite.swift/pull/553 [#559]: https://github.com/stephencelis/SQLite.swift/pull/559 +[#560]: https://github.com/stephencelis/SQLite.swift/pull/560 [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 diff --git a/CocoaPodsTests/integration_test.rb b/CocoaPodsTests/integration_test.rb index 7f3e8132..d296c28e 100755 --- a/CocoaPodsTests/integration_test.rb +++ b/CocoaPodsTests/integration_test.rb @@ -13,7 +13,7 @@ def test_validate_project def validator @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| - validator.test_files = Dir["#{project_test_dir}/*.swift"] + validator.test_files = Dir["#{project_test_dir}/**/*.swift"] validator.test_resources = Dir["#{project_test_dir}/fixtures"] validator.config.verbose = true validator.no_clean = true @@ -38,6 +38,6 @@ def podspec end def project_test_dir - File.expand_path(File.dirname(__FILE__) + '/../SQLiteTests') + File.expand_path(File.dirname(__FILE__) + '/../Tests/SQLiteTests') end end diff --git a/Documentation/Index.md b/Documentation/Index.md index 40e9f503..63c8d903 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -3,8 +3,8 @@ - [Installation](#installation) - [Carthage](#carthage) - [CocoaPods](#cocoapods) + - [Swift Package Manager](#swift-package-manager) - [Manual](#manual) - - [Frameworkless Targets](#frameworkless-targets) - [Getting Started](#getting-started) - [Connecting to a Database](#connecting-to-a-database) - [Read-Write Databases](#read-write-databases) @@ -162,6 +162,24 @@ try db.rekey("another secret") [sqlite3pod]: https://github.com/clemensg/sqlite3pod [SQLCipher]: https://www.zetetic.net/sqlcipher/ +### Swift Package Manager + +The [Swift Package Manager][] is a tool for managing the distribution of Swift code. +It’s integrated with the Swift build system to automate the process of +downloading, compiling, and linking dependencies. + +It is the recommended approach for using SQLite.swift in OSX CLI applications. + +1. Add the following to your `Package.swift` file: + +```swift +dependencies: [ + .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) +] +``` + +[Swift Package Manager]: https://swift.org/package-manager + ### Manual To install SQLite.swift as an Xcode sub-project: @@ -186,24 +204,6 @@ Some additional steps are required to install the application on an actual devic 7. **Add**. -### Frameworkless Targets - -It’s possible to use SQLite.swift in a target that doesn’t support frameworks, including iOS 7 apps and OS X command line tools, though it takes a little extra work. - - 1. In your target’s **Build Phases**, add **libsqlite3.dylib** to the **Link Binary With Libraries** build phase. - - 2. Copy the SQLite.swift source files (from its **SQLite** directory) into your Xcode project. - - 3. Add the following lines to your project’s [bridging header](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_79) (a file usually in the form of `$(TARGET_NAME)-Bridging-Header.h`). - - ``` swift - #import - #import "SQLite-Bridging.h" - ``` - -> _Note:_ Adding SQLite.swift source files directly to your application will both remove the `SQLite` module namespace (no need—or ability—to `import SQLite`) and expose internal functions and variables. You will need to rename anything that conflicts with code of your own. Please [report any bugs](https://github.com/stephencelis/SQLite.swift/issues/new) (_e.g._, segfaults) you encounter. - - ## Getting Started To use SQLite.swift classes or structures in your target’s source file, first import the `SQLite` module. diff --git a/Documentation/Planning.md b/Documentation/Planning.md index a0c6d049..d814d26b 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -11,15 +11,10 @@ _Lists agreed upon next steps in approximate priority order._ _A gathering point for ideas for new features. In general, the corresponding issue will be closed once it is added here, with the assumption that it will be referred to when it comes time to add the corresponding feature._ -### Packaging - - * linux support via Swift Package Manager, per [#315](https://github.com/stephencelis/SQLite.swift/issues/315), _in progress_: [#548](https://github.com/stephencelis/SQLite.swift/pull/548) - ### Features * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per [#30](https://github.com/stephencelis/SQLite.swift/issues/30) * provide separate threads for update vs read, so updates don't block reads, per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) - * expose more FTS4 options, e.g. virtual table support per [#164](https://github.com/stephencelis/SQLite.swift/issues/164) * expose triggers, per [#164](https://github.com/stephencelis/SQLite.swift/issues/164) ## Suspended Feature Requests diff --git a/Package.swift b/Package.swift index f5c5b54f..966ebfde 100644 --- a/Package.swift +++ b/Package.swift @@ -6,15 +6,11 @@ let package = Package( Target( name: "SQLite", dependencies: [ - .Target(name: "SQLiteObjc"), - .Target(name: "CSQLite") + .Target(name: "SQLiteObjc") ]), - Target( - name: "SQLiteObjc", - dependencies: [ - .Target(name: "CSQLite") - ]), - Target( - name: "CSQLite") + Target(name: "SQLiteObjc") + ], + dependencies: [ + .Package(url: "https://github.com/jberkel/CSQLite.git", majorVersion: 0) ] ) diff --git a/README.md b/README.md index c0b778df..488e964b 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,6 @@ For a more comprehensive example, see [this article](http://masteringswift.blogs > _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8) or greater. If you absolutely > need compatibility with Swift 2.3 you can use the [swift-2.3][] branch or older > released versions. New development will happen exclusively on the master/Swift 3 branch. -> -> The following instructions apply to targets that support embedded -> Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line -> tool, please read the [Frameworkless Targets][] section of the -> documentation. - ### Carthage @@ -174,6 +168,19 @@ SQLite.swift with CocoaPods: [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started +### Swift Package Manager + +The [Swift Package Manager][] is a tool for managing the distribution of Swift code. + +1. Add the following to your `Package.swift` file: + +```swift +dependencies: [ + .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) +] +``` + +[Swift Package Manager]: https://swift.org/package-manager ### Manual @@ -200,7 +207,6 @@ Some additional steps are required to install the application on an actual devic 7. **Add**. -[Frameworkless Targets]: Documentation/Index.md#frameworkless-targets [Xcode]: https://developer.apple.com/xcode/downloads/ [Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules [download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index ad423298..b5ed4559 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -25,9 +25,9 @@ Pod::Spec.new do |s| } s.subspec 'standard' do |ss| - ss.source_files = 'SQLite/**/*.{c,h,m,swift}' - ss.exclude_files = 'SQLite/Extensions/Cipher.swift' - ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.exclude_files = 'Sources/**/Cipher.swift' + ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.library = 'sqlite3' ss.preserve_paths = 'CocoaPods/**/*' @@ -47,9 +47,9 @@ Pod::Spec.new do |s| end s.subspec 'standalone' do |ss| - ss.source_files = 'SQLite/**/*.{c,h,m,swift}' - ss.exclude_files = 'SQLite/Extensions/Cipher.swift' - ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.exclude_files = 'Sources/**/Cipher.swift' + ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' } @@ -58,8 +58,8 @@ Pod::Spec.new do |s| end s.subspec 'SQLCipher' do |ss| - ss.source_files = 'SQLite/**/*.{c,h,m,swift}' - ss.private_header_files = 'SQLite/Core/fts3_tokenizer.h' + ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index b2711828..153295cc 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -455,6 +455,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( + 19A17E2695737FAB5D6086E3 /* fixtures */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -474,7 +475,6 @@ 19A1721B8984686B9963B45D /* FTS5Tests.swift */, 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, 19A17399EA9E61235D5D77BF /* CipherTests.swift */, - 19A17E2695737FAB5D6086E3 /* fixtures */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, ); name = SQLiteTests; @@ -1104,7 +1104,7 @@ 03A65E6D1C6BB0F60062603F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = Tests/SQLite/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1117,7 +1117,7 @@ 03A65E6E1C6BB0F60062603F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = SQLite/Info.plist; + INFOPLIST_FILE = Tests/SQLite/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1222,7 +1222,6 @@ ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALL_IN_ONE_BUILD; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; @@ -1268,7 +1267,6 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ALL_IN_ONE_BUILD; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; @@ -1331,7 +1329,7 @@ EE247AEB1C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = SQLiteTests/Info.plist; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1342,7 +1340,7 @@ EE247AEC1C3F04ED00AE3E12 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = SQLiteTests/Info.plist; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1409,7 +1407,7 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = SQLiteTests/Info.plist; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; @@ -1424,7 +1422,7 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = SQLiteTests/Info.plist; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; diff --git a/Sources/CSQLite/module.modulemap b/Sources/CSQLite/module.modulemap deleted file mode 100644 index f4127dce..00000000 --- a/Sources/CSQLite/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite { - umbrella header "include/sqlite3.h" - export * -} diff --git a/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift similarity index 100% rename from SQLite/Extensions/Cipher.swift rename to Sources/SQLite/Extensions/Cipher.swift diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index fe9ee6b8..152a2921 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -22,9 +22,7 @@ // THE SOFTWARE. // -#if ALL_IN_ONE_BUILD -// Nothing required -#else +#if SWIFT_PACKAGE import SQLiteObjc #endif diff --git a/Sources/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLite-Bridging.m index a5702cc7..d8fe6b68 100644 --- a/Sources/SQLiteObjc/SQLite-Bridging.m +++ b/Sources/SQLiteObjc/SQLite-Bridging.m @@ -23,7 +23,6 @@ // #import "SQLite-Bridging.h" -#import "sqlite3.h" #import "fts3_tokenizer.h" #pragma mark - FTS diff --git a/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift similarity index 100% rename from SQLiteTests/CipherTests.swift rename to Tests/SQLiteTests/CipherTests.swift diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 33c7610e..59514a25 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -5,7 +5,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif COCOAPODS +#else import CSQLite #endif diff --git a/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift similarity index 100% rename from SQLiteTests/Fixtures.swift rename to Tests/SQLiteTests/Fixtures.swift diff --git a/SQLiteTests/fixtures/encrypted.sqlite b/Tests/SQLiteTests/fixtures/encrypted.sqlite similarity index 100% rename from SQLiteTests/fixtures/encrypted.sqlite rename to Tests/SQLiteTests/fixtures/encrypted.sqlite diff --git a/run-tests.sh b/run-tests.sh index bc8cd773..571bb6e2 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,4 +8,6 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then cd CocoaPodsTests && make test +elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then + swift ${PACKAGE_MANAGER_COMMAND} fi From a58eac718a3ecd17690302d018d355cd163f5c94 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 11 Dec 2016 01:44:59 +0000 Subject: [PATCH 0530/1046] Add build step to docs --- Documentation/Index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 63c8d903..540b7167 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -178,6 +178,12 @@ dependencies: [ ] ``` +2. Build your project: + +```shell +$ swift build -Xlinker -lsqlite3 +``` + [Swift Package Manager]: https://swift.org/package-manager ### Manual From ddace3e66fa554ea32c6c9fe3f2e943481006f0d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 11 Dec 2016 01:45:24 +0000 Subject: [PATCH 0531/1046] Ignore package dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b617bcef..25ac055b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ Carthage # Swift Package Manager .build +Packages/ From 3656d9ef1d3fdf34de19cbbce00bad59e85c1818 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 11 Dec 2016 01:50:12 +0000 Subject: [PATCH 0532/1046] Formatting --- Documentation/Index.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 540b7167..050db1a4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -170,19 +170,19 @@ downloading, compiling, and linking dependencies. It is the recommended approach for using SQLite.swift in OSX CLI applications. -1. Add the following to your `Package.swift` file: + 1. Add the following to your `Package.swift` file: -```swift -dependencies: [ + ``` swift + dependencies: [ .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) -] -``` + ] + ``` -2. Build your project: + 2. Build your project: -```shell -$ swift build -Xlinker -lsqlite3 -``` + ``` sh + $ swift build -Xlinker -lsqlite3 + ``` [Swift Package Manager]: https://swift.org/package-manager From 66f199281549902961d5b9c0dbbddc3e1d2587de Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 19 Dec 2016 19:04:53 +0000 Subject: [PATCH 0533/1046] Move CocoaPods test --- Package.swift | 3 ++- {CocoaPodsTests => Tests/CocoaPods}/.gitignore | 0 {CocoaPodsTests => Tests/CocoaPods}/Gemfile | 0 {CocoaPodsTests => Tests/CocoaPods}/Gemfile.lock | 0 {CocoaPodsTests => Tests/CocoaPods}/Makefile | 0 {CocoaPodsTests => Tests/CocoaPods}/integration_test.rb | 0 {CocoaPodsTests => Tests/CocoaPods}/test_running_validator.rb | 0 run-tests.sh | 2 +- 8 files changed, 3 insertions(+), 2 deletions(-) rename {CocoaPodsTests => Tests/CocoaPods}/.gitignore (100%) rename {CocoaPodsTests => Tests/CocoaPods}/Gemfile (100%) rename {CocoaPodsTests => Tests/CocoaPods}/Gemfile.lock (100%) rename {CocoaPodsTests => Tests/CocoaPods}/Makefile (100%) rename {CocoaPodsTests => Tests/CocoaPods}/integration_test.rb (100%) rename {CocoaPodsTests => Tests/CocoaPods}/test_running_validator.rb (100%) diff --git a/Package.swift b/Package.swift index 966ebfde..7d698370 100644 --- a/Package.swift +++ b/Package.swift @@ -12,5 +12,6 @@ let package = Package( ], dependencies: [ .Package(url: "https://github.com/jberkel/CSQLite.git", majorVersion: 0) - ] + ], + exclude: ["Tests/CocoaPods"] ) diff --git a/CocoaPodsTests/.gitignore b/Tests/CocoaPods/.gitignore similarity index 100% rename from CocoaPodsTests/.gitignore rename to Tests/CocoaPods/.gitignore diff --git a/CocoaPodsTests/Gemfile b/Tests/CocoaPods/Gemfile similarity index 100% rename from CocoaPodsTests/Gemfile rename to Tests/CocoaPods/Gemfile diff --git a/CocoaPodsTests/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock similarity index 100% rename from CocoaPodsTests/Gemfile.lock rename to Tests/CocoaPods/Gemfile.lock diff --git a/CocoaPodsTests/Makefile b/Tests/CocoaPods/Makefile similarity index 100% rename from CocoaPodsTests/Makefile rename to Tests/CocoaPods/Makefile diff --git a/CocoaPodsTests/integration_test.rb b/Tests/CocoaPods/integration_test.rb similarity index 100% rename from CocoaPodsTests/integration_test.rb rename to Tests/CocoaPods/integration_test.rb diff --git a/CocoaPodsTests/test_running_validator.rb b/Tests/CocoaPods/test_running_validator.rb similarity index 100% rename from CocoaPodsTests/test_running_validator.rb rename to Tests/CocoaPods/test_running_validator.rb diff --git a/run-tests.sh b/run-tests.sh index 571bb6e2..6ec6bacf 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,7 +7,7 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - cd CocoaPodsTests && make test + cd Tests/CocoaPods && make test elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then swift ${PACKAGE_MANAGER_COMMAND} fi From 98be8593a9ec2387f27375b8486e72e58d356619 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 19 Dec 2016 20:04:38 +0000 Subject: [PATCH 0534/1046] Absolute path --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 25ac055b..5882e0cb 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ DerivedData *.xcuserstate # Carthage -Carthage +/Carthage/ # Swift Package Manager .build From 4066660ffaab86ee070a4388efbfee44070d312d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 19 Dec 2016 20:09:16 +0000 Subject: [PATCH 0535/1046] Add tests for Carthage --- .travis.yml | 2 ++ Package.swift | 2 +- Tests/Carthage/.gitignore | 2 ++ Tests/Carthage/Cartfile | 1 + Tests/Carthage/Makefile | 10 ++++++++++ run-tests.sh | 2 ++ 6 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Tests/Carthage/.gitignore create mode 100644 Tests/Carthage/Cartfile create mode 100644 Tests/Carthage/Makefile diff --git a/.travis.yml b/.travis.yml index e114b573..f9618e2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ matrix: - env: VALIDATOR_SUBSPEC="standard" - env: VALIDATOR_SUBSPEC="standalone" - env: VALIDATOR_SUBSPEC="SQLCipher" + - env: CARTHAGE_PLATFORM="iOS" + - env: CARTHAGE_PLATFORM="Mac" - env: PACKAGE_MANAGER_COMMAND="test -Xlinker -lsqlite3" before_install: - gem update bundler diff --git a/Package.swift b/Package.swift index 7d698370..4d4c1504 100644 --- a/Package.swift +++ b/Package.swift @@ -13,5 +13,5 @@ let package = Package( dependencies: [ .Package(url: "https://github.com/jberkel/CSQLite.git", majorVersion: 0) ], - exclude: ["Tests/CocoaPods"] + exclude: ["Tests/CocoaPods", "Tests/Carthage"] ) diff --git a/Tests/Carthage/.gitignore b/Tests/Carthage/.gitignore new file mode 100644 index 00000000..3b8ec70c --- /dev/null +++ b/Tests/Carthage/.gitignore @@ -0,0 +1,2 @@ +Carthage/ +Cartfile.resolved diff --git a/Tests/Carthage/Cartfile b/Tests/Carthage/Cartfile new file mode 100644 index 00000000..a750e057 --- /dev/null +++ b/Tests/Carthage/Cartfile @@ -0,0 +1 @@ +github "stephencelis/SQLite.swift" "master" diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile new file mode 100644 index 00000000..04df397a --- /dev/null +++ b/Tests/Carthage/Makefile @@ -0,0 +1,10 @@ +CARTHAGE := /usr/local/bin/carthage +CARTHAGE_PLATFORM := iOS +CARTHAGE_CONFIGURATION := Release +CARTHAGE_DIR := Carthage +CARTHAGE_ARGS := --no-use-binaries +CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.Swift_3_0 +CARTHAGE_CMDLINE := --configuration $(CARTHAGE_CONFIGURATION) --platform $(CARTHAGE_PLATFORM) --toolchain $(CARTHAGE_TOOLCHAIN) $(CARTHAGE_ARGS) + +test: $(CARTHAGE) Cartfile + $< bootstrap $(CARTHAGE_CMDLINE) diff --git a/run-tests.sh b/run-tests.sh index 6ec6bacf..ddd7d678 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,6 +8,8 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then cd Tests/CocoaPods && make test +elif [ -n "$CARTHAGE_PLATFORM" ]; then + cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then swift ${PACKAGE_MANAGER_COMMAND} fi From 0b29b2691b7576ba63e1dffdb12a9624e54af38d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Dec 2016 00:37:31 +0000 Subject: [PATCH 0536/1046] Fix paths --- Tests/CocoaPods/integration_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index d296c28e..192aff90 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -34,10 +34,10 @@ def validator end def podspec - File.expand_path(File.dirname(__FILE__) + '/../SQLite.swift.podspec') + File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') end def project_test_dir - File.expand_path(File.dirname(__FILE__) + '/../Tests/SQLiteTests') + File.expand_path(File.dirname(__FILE__) + '/../SQLiteTests') end end From 8bf692bf2d16639fba3e7276ccc23c3ac358cf75 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Dec 2016 01:28:48 +0000 Subject: [PATCH 0537/1046] Update carthage; use correct git sha to test --- .travis.yml | 2 ++ Tests/Carthage/Cartfile | 1 - Tests/Carthage/Makefile | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) delete mode 100644 Tests/Carthage/Cartfile diff --git a/.travis.yml b/.travis.yml index f9618e2e..30617d47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,5 +18,7 @@ matrix: before_install: - gem update bundler - gem install xcpretty --no-document + - brew update + - brew outdated carthage || brew upgrade carthage script: - ./run-tests.sh diff --git a/Tests/Carthage/Cartfile b/Tests/Carthage/Cartfile deleted file mode 100644 index a750e057..00000000 --- a/Tests/Carthage/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "stephencelis/SQLite.swift" "master" diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index 04df397a..57644f8e 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -5,6 +5,11 @@ CARTHAGE_DIR := Carthage CARTHAGE_ARGS := --no-use-binaries CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.Swift_3_0 CARTHAGE_CMDLINE := --configuration $(CARTHAGE_CONFIGURATION) --platform $(CARTHAGE_PLATFORM) --toolchain $(CARTHAGE_TOOLCHAIN) $(CARTHAGE_ARGS) +TRAVIS_COMMIT ?= master +TRAVIS_REPO_SLUG ?= stephencelis/SQLite.swift test: $(CARTHAGE) Cartfile $< bootstrap $(CARTHAGE_CMDLINE) + +Cartfile: + echo 'github "$(TRAVIS_REPO_SLUG)" "$(TRAVIS_COMMIT)"' > $@ From 8a1aa20aa112df6394f8727dc41184ccb35a08c2 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Dec 2016 02:00:55 +0000 Subject: [PATCH 0538/1046] Reference build dir to get correct sha --- Tests/Carthage/.gitignore | 1 + Tests/Carthage/Makefile | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/Carthage/.gitignore b/Tests/Carthage/.gitignore index 3b8ec70c..2d43454a 100644 --- a/Tests/Carthage/.gitignore +++ b/Tests/Carthage/.gitignore @@ -1,2 +1,3 @@ Carthage/ +Cartfile Cartfile.resolved diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index 57644f8e..a4a14925 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -6,10 +6,13 @@ CARTHAGE_ARGS := --no-use-binaries CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.Swift_3_0 CARTHAGE_CMDLINE := --configuration $(CARTHAGE_CONFIGURATION) --platform $(CARTHAGE_PLATFORM) --toolchain $(CARTHAGE_TOOLCHAIN) $(CARTHAGE_ARGS) TRAVIS_COMMIT ?= master -TRAVIS_REPO_SLUG ?= stephencelis/SQLite.swift +TRAVIS_BUILD_DIR ?= https://github.com/stephencelis/SQLite.swift.git test: $(CARTHAGE) Cartfile $< bootstrap $(CARTHAGE_CMDLINE) Cartfile: - echo 'github "$(TRAVIS_REPO_SLUG)" "$(TRAVIS_COMMIT)"' > $@ + echo 'git "$(TRAVIS_BUILD_DIR)" "$(TRAVIS_COMMIT)"' > $@ + +clean: + @rm -f Cartfile Cartfile.resolved From bcd4be7e4bb939c85f7a6f9a9f6cbb407b51ae4b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Dec 2016 02:29:54 +0000 Subject: [PATCH 0539/1046] Carthage: test all platforms --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 30617d47..3747ac7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,8 @@ matrix: - env: VALIDATOR_SUBSPEC="SQLCipher" - env: CARTHAGE_PLATFORM="iOS" - env: CARTHAGE_PLATFORM="Mac" + - env: CARTHAGE_PLATFORM="watchOS" + - env: CARTHAGE_PLATFORM="tvOS" - env: PACKAGE_MANAGER_COMMAND="test -Xlinker -lsqlite3" before_install: - gem update bundler From 845ad5af63faa83a14558cd69f228f30877936ea Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 Dec 2016 04:15:53 +0000 Subject: [PATCH 0540/1046] Use head commit --- Tests/Carthage/Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index a4a14925..f28eb25b 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -5,14 +5,12 @@ CARTHAGE_DIR := Carthage CARTHAGE_ARGS := --no-use-binaries CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.Swift_3_0 CARTHAGE_CMDLINE := --configuration $(CARTHAGE_CONFIGURATION) --platform $(CARTHAGE_PLATFORM) --toolchain $(CARTHAGE_TOOLCHAIN) $(CARTHAGE_ARGS) -TRAVIS_COMMIT ?= master -TRAVIS_BUILD_DIR ?= https://github.com/stephencelis/SQLite.swift.git test: $(CARTHAGE) Cartfile $< bootstrap $(CARTHAGE_CMDLINE) Cartfile: - echo 'git "$(TRAVIS_BUILD_DIR)" "$(TRAVIS_COMMIT)"' > $@ + echo 'git "$(TRAVIS_BUILD_DIR)" "HEAD"' > $@ clean: @rm -f Cartfile Cartfile.resolved From bd80bb2208e3ba37c97a802f1f84266dc74eca77 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 Dec 2016 10:19:42 +0000 Subject: [PATCH 0541/1046] Allow `where` as alias for `filter` --- CHANGELOG.md | 2 ++ Documentation/Index.md | 6 +++++- Sources/SQLite/Typed/Query.swift | 12 ++++++++++++ Tests/SQLiteTests/QueryTests.swift | 29 +++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c23af3cc..5a11ea00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Fixed SQLCipher integration with read-only databases ([#559][]) * Preliminary Swift Package Manager support ([#548][], [#560][]) * Fixed null pointer when fetching an empty BLOB ([#561][]) +* Allow `where` as alias for `filter` ([#571][]) 0.11.1 (06-12-2016), [diff][diff-0.11.1] ======================================== @@ -30,3 +31,4 @@ [#559]: https://github.com/stephencelis/SQLite.swift/pull/559 [#560]: https://github.com/stephencelis/SQLite.swift/pull/560 [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 +[#571]: https://github.com/stephencelis/SQLite.swift/issues/571 diff --git a/Documentation/Index.md b/Documentation/Index.md index 050db1a4..3ab27a29 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -755,8 +755,12 @@ users.filter(verified || balance >= 10_000) We can build our own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). -> _Note:_ SQLite.swift defines `filter` instead of `where` because `where` is [a reserved keyword](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413). +Instead of `filter` we can also use the `where` function which is an alias: +``` swift +users.where(id == 1) +// SELECT * FROM "users" WHERE ("id" = 1) +``` ##### Filter Operators and Functions diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 288768c1..c9d2ea9c 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -306,6 +306,18 @@ extension QueryType { return query } + /// Adds a condition to the query’s `WHERE` clause. + /// This is an alias for `filter(predicate)` + public func `where`(_ predicate: Expression) -> Self { + return `where`(Expression(predicate)) + } + + /// Adds a condition to the query’s `WHERE` clause. + /// This is an alias for `filter(predicate)` + public func `where`(_ predicate: Expression) -> Self { + return filter(predicate) + } + // MARK: GROUP BY /// Sets a `GROUP BY` clause on the query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index ed34921e..2cf164c6 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -8,6 +8,7 @@ class QueryTests : XCTestCase { let email = Expression("email") let age = Expression("age") let admin = Expression("admin") + let optionalAdmin = Expression("admin") let posts = Table("posts") let userId = Expression("user_id") @@ -88,6 +89,34 @@ class QueryTests : XCTestCase { AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) } + func test_filter_compilesWhereClause_false() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(admin == false)) + } + + func test_filter_compilesWhereClause_optional() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(optionalAdmin == true)) + } + + func test_filter_compilesWhereClause_optional_false() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(optionalAdmin == false)) + } + + func test_where_compilesWhereClause() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(admin == true)) + } + + func test_where_compilesWhereClause_false() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(admin == false)) + } + + func test_where_compilesWhereClause_optional() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(optionalAdmin == true)) + } + + func test_where_compilesWhereClause_optional_false() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(optionalAdmin == false)) + } + func test_filter_whenChained_compilesAggregateWhereClause() { AssertSQL( "SELECT * FROM \"users\" WHERE ((\"age\" >= 35) AND \"admin\")", From a19b29b9e0202cfef340928ac01407260af537d1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 Dec 2016 14:47:37 +0000 Subject: [PATCH 0542/1046] Make it clear that pod needs to be included once --- Documentation/Index.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3ab27a29..4bb51eed 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -125,14 +125,18 @@ install SQLite.swift with Carthage: If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: ``` ruby - pod 'SQLite.swift/standalone', '~> 0.11.1' + target 'YourAppTargetName' do + pod 'SQLite.swift/standalone', '~> 0.11.1' + end ``` By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: ``` ruby - pod 'SQLite.swift/standalone', '~> 0.11.1' - pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled + target 'YourAppTargetName' do + pod 'SQLite.swift/standalone', '~> 0.11.1' + pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled + end ``` See the [sqlite3 podspec][sqlite3pod] for more details. @@ -143,7 +147,9 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the `SQLCiphe subspec in your Podfile: ``` ruby - pod 'SQLite.swift/SQLCipher', '~> 0.11.1' + target 'YourAppTargetName' do + pod 'SQLite.swift/SQLCipher', '~> 0.11.1' + end ``` This will automatically add a dependency to the SQLCipher pod as well as extend From 9953dea614b701cdb5a873eceea28b55f776bfc6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 Dec 2016 15:13:37 +0000 Subject: [PATCH 0543/1046] Indent --- Documentation/Index.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4bb51eed..c407f06f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -125,18 +125,18 @@ install SQLite.swift with Carthage: If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: ``` ruby - target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.1' - end +target 'YourAppTargetName' do + pod 'SQLite.swift/standalone', '~> 0.11.1' +end ``` By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: ``` ruby - target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.1' - pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled - end +target 'YourAppTargetName' do + pod 'SQLite.swift/standalone', '~> 0.11.1' + pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled +end ``` See the [sqlite3 podspec][sqlite3pod] for more details. @@ -147,9 +147,9 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the `SQLCiphe subspec in your Podfile: ``` ruby - target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.1' - end +target 'YourAppTargetName' do + pod 'SQLite.swift/SQLCipher', '~> 0.11.1' +end ``` This will automatically add a dependency to the SQLCipher pod as well as extend From e3816fd44ac53c0fd72329a75357878f3717b6eb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 24 Dec 2016 22:47:42 +0000 Subject: [PATCH 0544/1046] Switch repo --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 4d4c1504..fc7c5a0e 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( Target(name: "SQLiteObjc") ], dependencies: [ - .Package(url: "https://github.com/jberkel/CSQLite.git", majorVersion: 0) + .Package(url: "https://github.com/stephencelis/CSQLite.git", majorVersion: 0) ], exclude: ["Tests/CocoaPods", "Tests/Carthage"] ) From 1b09ca46b0c75c839994cb0225d227aaaa6ef234 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 25 Dec 2016 19:15:32 +0100 Subject: [PATCH 0545/1046] Prepare release --- CHANGELOG.md | 2 +- Documentation/Index.md | 10 +++++----- README.md | 4 ++-- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a11ea00..f3298d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.11.2 (unreleased), [diff][diff-0.11.2] +0.11.2 (25-12-2016), [diff][diff-0.11.2] ======================================== * Fixed SQLCipher integration with read-only databases ([#559][]) diff --git a/Documentation/Index.md b/Documentation/Index.md index c407f06f..3e13ad4e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.1 + github "stephencelis/SQLite.swift" ~> 0.11.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -113,7 +113,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.1' + pod 'SQLite.swift', '~> 0.11.2' end ``` @@ -126,7 +126,7 @@ install SQLite.swift with Carthage: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.1' + pod 'SQLite.swift/standalone', '~> 0.11.2' end ``` @@ -134,7 +134,7 @@ By default this will use the most recent version of SQLite without any extras. I ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.1' + pod 'SQLite.swift/standalone', '~> 0.11.2' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ subspec in your Podfile: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.1' + pod 'SQLite.swift/SQLCipher', '~> 0.11.2' end ``` diff --git a/README.md b/README.md index 488e964b..89917b36 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.1 + github "stephencelis/SQLite.swift" ~> 0.11.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.1' + pod 'SQLite.swift', '~> 0.11.2' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b5ed4559..3c61aa1f 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.1" + s.version = "0.11.2" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 21f24d9d..588139c4 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.1 + 0.11.2 CFBundleSignature ???? CFBundleVersion From 97e6a660e3a41ef4db43a3a33249d2c10f083fc8 Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Mon, 16 Jan 2017 11:28:59 +0200 Subject: [PATCH 0546/1046] Add possibility to have expression on other side of like --- Sources/SQLite/Typed/CoreFunctions.swift | 50 ++++++++++++++++++++++ Tests/SQLiteTests/CoreFunctionsTests.swift | 6 +++ 2 files changed, 56 insertions(+) diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 9d17a326..dc450abd 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -223,6 +223,31 @@ extension ExpressionType where UnderlyingType == String { return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// let pattern = Expression("pattern") + /// email.like(pattern) + /// // "email" LIKE "pattern" + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + let like: Expression = "LIKE".infix(self, pattern, wrap: false) + return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) + } + /// Builds a copy of the expression appended with a `GLOB` query against the /// given pattern. /// @@ -422,6 +447,31 @@ extension ExpressionType where UnderlyingType == String? { } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// let pattern = Expression("pattern") + /// email.like(pattern) + /// // "email" LIKE "pattern" + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + let like: Expression = "LIKE".infix(self, pattern, wrap: false) + return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) + } /// Builds a copy of the expression appended with a `GLOB` query against the /// given pattern. diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index db37ff7f..82c59c61 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -37,6 +37,12 @@ class CoreFunctionsTests : XCTestCase { AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) + + AssertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) + AssertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) + + AssertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) + AssertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { From 5800d139597b5ffa8ba747d059fbeb9f6dd425eb Mon Sep 17 00:00:00 2001 From: Fred Cox Date: Mon, 16 Jan 2017 12:14:25 +0200 Subject: [PATCH 0547/1046] Allow use of string on left hand side of like expresssion --- Sources/SQLite/Typed/CoreFunctions.swift | 29 ++++++++++++++++++++++ Tests/SQLiteTests/CoreFunctionsTests.swift | 3 +++ 2 files changed, 32 insertions(+) diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index dc450abd..2e00c44a 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -673,6 +673,35 @@ extension Collection where Iterator.Element : Value, IndexDistance == Int { } +extension String { + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = "some@thing.com" + /// let pattern = Expression("pattern") + /// email.like(pattern) + /// // 'some@thing.com' LIKE "pattern" + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + let like: Expression = "LIKE".infix(self, pattern, wrap: false) + return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) + } + +} + /// Builds a copy of the given expressions wrapped with the `ifnull` function. /// /// let name = Expression("name") diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index 82c59c61..7cf046a3 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -43,6 +43,9 @@ class CoreFunctionsTests : XCTestCase { AssertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) AssertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) + + AssertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) + AssertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { From 9843f5e7a1e72deec830fbb696127f9f92226455 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 17 Jan 2017 00:16:18 +0100 Subject: [PATCH 0548/1046] Mention linker flag in READNE --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 89917b36..e0cbcf60 100644 --- a/README.md +++ b/README.md @@ -174,11 +174,17 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift c 1. Add the following to your `Package.swift` file: -```swift -dependencies: [ - .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) -] -``` + ```swift + dependencies: [ + .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) + ] + ``` + +2. Build your project: + + ``` sh + $ swift build -Xlinker -lsqlite3 + ``` [Swift Package Manager]: https://swift.org/package-manager From f3da195d1a7701785509124b98ef8ed8b32eda25 Mon Sep 17 00:00:00 2001 From: Warif Akhand Rishi Date: Sat, 28 Jan 2017 12:48:15 +0600 Subject: [PATCH 0549/1046] Update Index.md let wonderfulEmails = emails.match("wonder*") // doesn't compile in Swift 3 changed to let wonderfulEmails: QueryType = emails.match("wonder*") // compiles ok --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3e13ad4e..fa3dfeb7 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1471,7 +1471,7 @@ try db.run(emails.insert( body <- "Hey, I was just wondering...did you get my last email?" )) -let wonderfulEmails = emails.match("wonder*") +let wonderfulEmails: QueryType = emails.match("wonder*") // SELECT * FROM "emails" WHERE "emails" MATCH 'wonder*' let replies = emails.filter(subject.match("Re:*")) From 61f805126a4dd93f89b678895c40f5af313a0b4e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 14 Mar 2017 11:48:07 +0100 Subject: [PATCH 0550/1046] Fix regression introduced in 4e6b1e00466299136f826ccb7f2c6995eb81b8b2 --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 06611756..c0678387 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#else +#elseif SWIFT_PACKAGE || COCOAPODS import CSQLite #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 766bd15b..a9232bba 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#else +#elseif SWIFT_PACKAGE || COCOAPODS import CSQLite #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 81a7507e..50b21d0d 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#else +#elseif SWIFT_PACKAGE || COCOAPODS import CSQLite #endif diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 59514a25..2f033bad 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -5,7 +5,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#else +#elseif SWIFT_PACKAGE || COCOAPODS import CSQLite #endif From a51a6babb99a5ab9f32ab74eb350ffa3cdeac3df Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 14 Mar 2017 13:14:34 +0100 Subject: [PATCH 0551/1046] update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3298d68..da5e6a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.11.3 (tba), [diff][diff-0.11.3] +======================================== + +* Fix compilation problems when using Carthage ([#615][]) +* Documentation updates + 0.11.2 (25-12-2016), [diff][diff-0.11.2] ======================================== @@ -23,6 +29,7 @@ [diff-0.11.0]: https://github.com/stephencelis/SQLite.swift/compare/0.10.1...0.11.0 [diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 +[diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [#532]: https://github.com/stephencelis/SQLite.swift/issues/532 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 @@ -32,3 +39,4 @@ [#560]: https://github.com/stephencelis/SQLite.swift/pull/560 [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 +[#615]: https://github.com/stephencelis/SQLite.swift/pull/615 From 9a9a05a7b3d905f84621e7a591fa2b6b5f12d2a7 Mon Sep 17 00:00:00 2001 From: Kevin Koltzau Date: Thu, 10 Nov 2016 13:58:15 -0500 Subject: [PATCH 0552/1046] Add "WITHOUT ROWID" table option. See: https://www.sqlite.org/withoutrowid.html --- Sources/SQLite/Typed/Schema.swift | 5 +++-- Tests/SQLiteTests/SchemaTests.swift | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 1388170e..65509292 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -36,14 +36,15 @@ extension Table { // MARK: - CREATE TABLE - public func create(temporary: Bool = false, ifNotExists: Bool = false, block: (TableBuilder) -> Void) -> String { + public func create(temporary: Bool = false, ifNotExists: Bool = false, withoutRowid: Bool = false, block: (TableBuilder) -> Void) -> String { let builder = TableBuilder() block(builder) let clauses: [Expressible?] = [ create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), - "".wrap(builder.definitions) as Expression + "".wrap(builder.definitions) as Expression, + withoutRowid ? Expression(literal: "WITHOUT ROWID") : nil ] return " ".join(clauses.flatMap { $0 }).asSQL() diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 371459c7..e59b569b 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -53,6 +53,15 @@ class SchemaTests : XCTestCase { "CREATE TEMPORARY TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)", table.create(temporary: true, ifNotExists: true) { $0.column(int64) } ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL) WITHOUT ROWID", + table.create(withoutRowid: true) { $0.column(int64) } + ) + XCTAssertEqual( + "CREATE TEMPORARY TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL) WITHOUT ROWID", + table.create(temporary: true, ifNotExists: true, withoutRowid: true) { $0.column(int64) } + ) } func test_create_withQuery_compilesCreateTableExpression() { From 82451a29326bc5db23987c696002e6e9fbe48a91 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 15 Mar 2017 12:55:25 +0100 Subject: [PATCH 0553/1046] Changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da5e6a50..4b8f1785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * Fix compilation problems when using Carthage ([#615][]) +* Add "WITHOUT ROWID" table option ([#541][]) * Documentation updates 0.11.2 (25-12-2016), [diff][diff-0.11.2] @@ -31,7 +32,8 @@ [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 -[#532]: https://github.com/stephencelis/SQLite.swift/issues/532 +[#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 +[#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 [#548]: https://github.com/stephencelis/SQLite.swift/pull/548 [#553]: https://github.com/stephencelis/SQLite.swift/pull/553 From 47e109aab50308e71504aa4b1ea2d0cdf527b983 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 15 Mar 2017 22:06:59 +0100 Subject: [PATCH 0554/1046] Argument count fixed for binary custom functions Closes #481 --- CHANGELOG.md | 2 + Sources/SQLite/Typed/CustomFunctions.swift | 22 +-- Tests/SQLiteTests/CustomFunctionsTests.swift | 133 ++++++++++++++++++- 3 files changed, 145 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b8f1785..9d23ddc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Fix compilation problems when using Carthage ([#615][]) * Add "WITHOUT ROWID" table option ([#541][]) +* Argument count fixed for binary custom functions ([#481][]) * Documentation updates 0.11.2 (25-12-2016), [diff][diff-0.11.2] @@ -32,6 +33,7 @@ [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 +[#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 [#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 64b54edb..2389901f 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -56,17 +56,17 @@ public extension Connection { return { arg in fn([arg]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { + public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } @@ -74,42 +74,42 @@ public extension Connection { // MARK: - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { - let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 67150ccf..919986b6 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,6 +1,137 @@ import XCTest import SQLite -class CustomFunctionsTests : XCTestCase { +class CustomFunctionNoArgsTests : SQLiteTestCase { + typealias FunctionNoOptional = () -> Expression + typealias FunctionResultOptional = () -> Expression + func testFunctionNoOptional() { + let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { + return "a" + } + let result = try! db.prepare("SELECT test()").scalar() as! String + XCTAssertEqual("a", result) + } + + func testFunctionResultOptional() { + let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { + return "a" + } + let result = try! db.prepare("SELECT test()").scalar() as! String? + XCTAssertEqual("a", result) + } +} + +class CustomFunctionWithOneArgTests : SQLiteTestCase { + typealias FunctionNoOptional = (Expression) -> Expression + typealias FunctionLeftOptional = (Expression) -> Expression + typealias FunctionResultOptional = (Expression) -> Expression + typealias FunctionLeftResultOptional = (Expression) -> Expression + + func testFunctionNoOptional() { + let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a in + return "b"+a + } + let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + XCTAssertEqual("ba", result) + } + + func testFunctionLeftOptional() { + let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a in + return "b"+a! + } + let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + XCTAssertEqual("ba", result) + } + + func testFunctionResultOptional() { + let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a in + return "b"+a + } + let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + XCTAssertEqual("ba", result) + } + + func testFunctionLeftResultOptional() { + let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a:String?) -> String? in + return "b"+a! + } + let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + XCTAssertEqual("ba", result) + } +} + +class CustomFunctionWithTwoArgsTests : SQLiteTestCase { + typealias FunctionNoOptional = (Expression, Expression) -> Expression + typealias FunctionLeftOptional = (Expression, Expression) -> Expression + typealias FunctionRightOptional = (Expression, Expression) -> Expression + typealias FunctionResultOptional = (Expression, Expression) -> Expression + typealias FunctionLeftRightOptional = (Expression, Expression) -> Expression + typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression + typealias FunctionRightResultOptional = (Expression, Expression) -> Expression + typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression + + func testNoOptional() { + let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a+b + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + XCTAssertEqual("ab", result) + } + + func testLeftOptional() { + let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a!+b + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + XCTAssertEqual("ab", result) + } + + func testRightOptional() { + let _: FunctionRightOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a+b! + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + XCTAssertEqual("ab", result) + } + + func testResultOptional() { + let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a+b + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + XCTAssertEqual("ab", result) + } + + func testFunctionLeftRightOptional() { + let _: FunctionLeftRightOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a!+b! + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + XCTAssertEqual("ab", result) + } + + func testFunctionLeftResultOptional() { + let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a!+b + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + XCTAssertEqual("ab", result) + } + + func testFunctionRightResultOptional() { + let _: FunctionRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a+b! + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + XCTAssertEqual("ab", result) + } + + func testFunctionLeftRightResultOptional() { + let _: FunctionLeftRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + return a!+b! + } + let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + XCTAssertEqual("ab", result) + } } From c0dbbfc122cf7dd036ad842d46fa075e3e8c9d57 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 15 Mar 2017 22:12:27 +0100 Subject: [PATCH 0555/1046] Fix documentation Closes #266 --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index fa3dfeb7..bf8fa796 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1359,7 +1359,7 @@ For example, to give queries access to [`MobileCoreServices.UTTypeConformsTo`](h ``` swift import MobileCoreServices -let typeConformsTo: (Expression, String) -> Expression = ( +let typeConformsTo: (Expression, Expression) -> Expression = ( try db.createFunction("typeConformsTo", deterministic: true) { UTI, conformsToUTI in return UTTypeConformsTo(UTI, conformsToUTI) } @@ -1371,7 +1371,7 @@ let typeConformsTo: (Expression, String) -> Expression = ( Note `typeConformsTo`’s signature: ``` swift -(Expression, String) -> Expression +(Expression, Expression) -> Expression ``` Because of this, `createFunction` expects a block with the following signature: From 7af6e8ae7f73e2b7f285589ce8938d100711c1f7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 29 Mar 2017 22:13:55 +0200 Subject: [PATCH 0556/1046] Fix warnings --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Extensions/FTS4.swift | 5 +++-- Tests/SQLiteTests/ConnectionTests.swift | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index c0678387..490a1592 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -579,7 +579,7 @@ public final class Connection { } else if result == nil { sqlite3_result_null(context) } else { - fatalError("unsupported result type: \(result)") + fatalError("unsupported result type: \(String(describing: result))") } } var flags = SQLITE_UTF8 diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 152a2921..3f28d33b 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -145,13 +145,14 @@ extension Tokenizer : CustomStringConvertible { extension Connection { public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range)?) throws { - try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { ( + input: UnsafePointer, offset: UnsafeMutablePointer, length: UnsafeMutablePointer) in let string = String(cString: input) guard let (token, range) = next(string) else { return nil } let view = string.utf8 - offset.pointee += string.substring(to: range.lowerBound).utf8.count + offset.pointee += Int32(string.substring(to: range.lowerBound).utf8.count) length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.upperBound.samePosition(in: view))) return token }) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 2f033bad..d05c5ece 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -337,7 +337,7 @@ class ConnectionTests : SQLiteTestCase { try! stmt.run() let deadline = DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC) - _ = DispatchQueue.global(priority: .background).asyncAfter(deadline: deadline, execute: db.interrupt) + _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) AssertThrows(try stmt.run()) } From 05ca19433efa68e1e93041ed400ae70b62071382 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 29 Mar 2017 22:17:31 +0200 Subject: [PATCH 0557/1046] Switch to 8.3 image --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3747ac7b..88e75fa4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.2 -osx_image: xcode8.2 +osx_image: xcode8.3 env: global: - IOS_SIMULATOR="iPhone 6s" From bd9413ad43024f365d87c5665041c08f77cc95cf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 30 Mar 2017 07:55:58 +0200 Subject: [PATCH 0558/1046] Adjust deployment targets --- SQLite.swift.podspec | 8 ++++---- SQLite.xcodeproj/project.pbxproj | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 3c61aa1f..3e43d196 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -15,10 +15,10 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/stephencelis' s.module_name = 'SQLite' - s.ios.deployment_target = "8.0" - s.tvos.deployment_target = "9.0" - s.osx.deployment_target = "10.9" - s.watchos.deployment_target = "2.0" + s.ios.deployment_target = "9.0" + s.tvos.deployment_target = "9.1" + s.osx.deployment_target = "10.10" + s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.0', diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 153295cc..13146ba0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1287,7 +1287,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1313,7 +1313,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1362,7 +1362,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1389,7 +1389,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1409,7 +1409,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1424,7 +1424,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; From 104e2f11ad49c3840ecc582b40a83417d8628abd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 30 Mar 2017 08:05:11 +0200 Subject: [PATCH 0559/1046] Bump version number --- Documentation/Index.md | 10 +++++----- README.md | 4 ++-- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bf8fa796..ddd20b42 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.2 + github "stephencelis/SQLite.swift" ~> 0.11.3 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -113,7 +113,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.2' + pod 'SQLite.swift', '~> 0.11.3' end ``` @@ -126,7 +126,7 @@ install SQLite.swift with Carthage: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.2' + pod 'SQLite.swift/standalone', '~> 0.11.3' end ``` @@ -134,7 +134,7 @@ By default this will use the most recent version of SQLite without any extras. I ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.2' + pod 'SQLite.swift/standalone', '~> 0.11.3' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ subspec in your Podfile: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.2' + pod 'SQLite.swift/SQLCipher', '~> 0.11.3' end ``` diff --git a/README.md b/README.md index e0cbcf60..a7fe6080 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.2 + github "stephencelis/SQLite.swift" ~> 0.11.3 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.2' + pod 'SQLite.swift', '~> 0.11.3' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 3e43d196..06aa043b 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.2" + s.version = "0.11.3" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 588139c4..8c55becd 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.2 + 0.11.3 CFBundleSignature ???? CFBundleVersion From 4229b04fce8e0db440cafbc077837efde359ad04 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 30 Mar 2017 08:06:33 +0200 Subject: [PATCH 0560/1046] Bump CocoaPods --- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 48 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 94018f18..e40770e8 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.1.0' +gem 'cocoapods', '~> 1.2.0' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index e8879f13..869ae3dc 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,43 +1,43 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.3) - activesupport (4.2.7.1) + CFPropertyList (2.3.5) + activesupport (4.2.8) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) claide (1.0.1) - cocoapods (1.1.1) + cocoapods (1.2.0) activesupport (>= 4.0.2, < 5) claide (>= 1.0.1, < 2.0) - cocoapods-core (= 1.1.1) + cocoapods-core (= 1.2.0) cocoapods-deintegrate (>= 1.0.1, < 2.0) - cocoapods-downloader (>= 1.1.2, < 2.0) + cocoapods-downloader (>= 1.1.3, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.1.1, < 2.0) + cocoapods-trunk (>= 1.1.2, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored (~> 1.2) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.5.1) + molinillo (~> 0.5.5) nap (~> 1.0) - xcodeproj (>= 1.3.3, < 2.0) - cocoapods-core (1.1.1) + ruby-macho (~> 0.2.5) + xcodeproj (>= 1.4.1, < 2.0) + cocoapods-core (1.2.0) activesupport (>= 4.0.2, < 5) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.1) - cocoapods-downloader (1.1.2) + cocoapods-downloader (1.1.3) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.1.1) + cocoapods-trunk (1.1.2) nap (>= 0.8, < 2.0) netrc (= 0.7.8) cocoapods-try (1.1.0) @@ -45,30 +45,30 @@ GEM escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) - gh_inspector (1.0.2) - i18n (0.7.0) - json (1.8.3) - minitest (5.9.1) - molinillo (0.5.4) - nanaimo (0.2.2) + gh_inspector (1.0.3) + i18n (0.8.1) + minitest (5.10.1) + molinillo (0.5.7) + nanaimo (0.2.3) nap (1.1.0) netrc (0.7.8) - thread_safe (0.3.5) - tzinfo (1.2.2) + ruby-macho (0.2.6) + thread_safe (0.3.6) + tzinfo (1.2.3) thread_safe (~> 0.1) - xcodeproj (1.4.1) + xcodeproj (1.4.2) CFPropertyList (~> 2.3.3) activesupport (>= 3) claide (>= 1.0.1, < 2.0) colored (~> 1.2) - nanaimo (~> 0.2.0) + nanaimo (~> 0.2.3) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.1.0) + cocoapods (~> 1.2.0) minitest BUNDLED WITH - 1.13.3 + 1.13.6 From 1fc4548708e661bffa9ebf62bc1f88f2494045e5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 30 Mar 2017 08:20:29 +0200 Subject: [PATCH 0561/1046] Set test deployment target --- SQLite.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 13146ba0..78b43249 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1330,6 +1330,7 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1341,6 +1342,7 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; From a7e325f91cd6b7e989dc1140fb19987b10f59698 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 30 Mar 2017 08:24:01 +0200 Subject: [PATCH 0562/1046] Bump iOS version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d4f51d53..adab1d81 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 9.3 +IOS_VERSION = 10.3 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else From f389b6a36e8385c6d651827ab6893eb0f8de2a44 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 30 Mar 2017 10:56:41 +0200 Subject: [PATCH 0563/1046] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d23ddc5..d6387a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ -0.11.3 (tba), [diff][diff-0.11.3] +0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== * Fix compilation problems when using Carthage ([#615][]) * Add "WITHOUT ROWID" table option ([#541][]) * Argument count fixed for binary custom functions ([#481][]) * Documentation updates +* Tested with Xcode 8.3 / iOS 10.3 0.11.2 (25-12-2016), [diff][diff-0.11.2] ======================================== From 426e7d606f8011574d46fe21c7d27351dff90031 Mon Sep 17 00:00:00 2001 From: Josh Friend Date: Sun, 21 May 2017 09:57:42 -0400 Subject: [PATCH 0564/1046] Fix usages of old Swift 2 enum case names --- Sources/SQLite/Core/Connection.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 490a1592..bba9a358 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -38,12 +38,12 @@ public final class Connection { /// The location of a SQLite database. public enum Location { - /// An in-memory database (equivalent to `.URI(":memory:")`). + /// An in-memory database (equivalent to `.uri(":memory:")`). /// /// See: case inMemory - /// A temporary, file-backed database (equivalent to `.URI("")`). + /// A temporary, file-backed database (equivalent to `.uri("")`). /// /// See: case temporary @@ -93,7 +93,7 @@ public final class Connection { /// - location: The location of the database. Creates a new database if it /// doesn’t already exist (unless in read-only mode). /// - /// Default: `.InMemory`. + /// Default: `.inMemory`. /// /// - readonly: Whether or not to open the database in a read-only state. /// @@ -321,7 +321,7 @@ public final class Connection { /// /// - mode: The mode in which a transaction acquires a lock. /// - /// Default: `.Deferred` + /// Default: `.deferred` /// /// - block: A closure to run SQL statements within the transaction. /// The transaction will be committed when the block returns. The block From 89630b2de78d9dfec31b6258f23917f8f4352317 Mon Sep 17 00:00:00 2001 From: Josh Friend Date: Sun, 21 May 2017 10:11:55 -0400 Subject: [PATCH 0565/1046] Apply Swift3 case convention to internal enum `Modifier` --- Sources/SQLite/Typed/Schema.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 65509292..7bf70fe4 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -42,7 +42,7 @@ extension Table { block(builder) let clauses: [Expressible?] = [ - create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + create(Table.identifier, tableName(), temporary ? .temporary : nil, ifNotExists), "".wrap(builder.definitions) as Expression, withoutRowid ? Expression(literal: "WITHOUT ROWID") : nil ] @@ -52,7 +52,7 @@ extension Table { public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ - create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + create(Table.identifier, tableName(), temporary ? .temporary : nil, ifNotExists), Expression(literal: "AS"), query ] @@ -133,7 +133,7 @@ extension Table { public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ - create("INDEX", indexName(columns), unique ? .Unique : nil, ifNotExists), + create("INDEX", indexName(columns), unique ? .unique : nil, ifNotExists), Expression(literal: "ON"), tableName(qualified: false), "".wrap(columns) as Expression @@ -176,7 +176,7 @@ extension View { public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ - create(View.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + create(View.identifier, tableName(), temporary ? .temporary : nil, ifNotExists), Expression(literal: "AS"), query ] @@ -513,8 +513,8 @@ private func reference(_ primary: (QueryType, Expressible)) -> Expressible { private enum Modifier : String { - case Unique = "UNIQUE" + case unique = "UNIQUE" - case Temporary = "TEMPORARY" + case temporary = "TEMPORARY" } From 2b067d4ee162c137f52da306824df7cc232c9435 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 16 Jun 2017 07:45:08 +0200 Subject: [PATCH 0566/1046] Travis sneakily replaced the Xcode 8.3 image. https://blog.travis-ci.com/2017-06-06-xcode-833-is-here --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 88e75fa4..2eb85311 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ osx_image: xcode8.3 env: global: - IOS_SIMULATOR="iPhone 6s" + - IOS_VERSION="10.3.1" matrix: include: - env: BUILD_SCHEME="SQLite iOS" From ca627720edb6aae00200c17024e998f21da690a5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 16 Jun 2017 08:08:49 +0200 Subject: [PATCH 0567/1046] Set iOS version --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index ddd7d678..0a105c41 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -2,7 +2,7 @@ set -ev if [ -n "$BUILD_SCHEME" ]; then if [ -n "$IOS_SIMULATOR" ]; then - make test BUILD_SCHEME="$BUILD_SCHEME" IOS_SIMULATOR="$IOS_SIMULATOR" + make test BUILD_SCHEME="$BUILD_SCHEME" IOS_SIMULATOR="$IOS_SIMULATOR" IOS_VERSION="$IOS_VERSION" else make test BUILD_SCHEME="$BUILD_SCHEME" fi From f24bc5db861c61f1d9a808ee24f4755852651ac1 Mon Sep 17 00:00:00 2001 From: Eoin Kelly Date: Fri, 16 Jun 2017 10:43:53 +1200 Subject: [PATCH 0568/1046] Fix demonstration of how to trace SQL statements in docs The old way shown causes a build error in swift 3 (XCode8): Ambiguous reference to member 'print(_:separator:terminator:)' --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index ddd20b42..07bb5f33 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1578,7 +1578,7 @@ We can log SQL using the database’s `trace` function. ``` swift #if DEBUG - db.trace(print) + db.trace { print($0) } #endif ``` From 1848964a0a5d31945045c6fe294d3a7961cad6f7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 1 Jul 2017 22:29:26 +0200 Subject: [PATCH 0569/1046] Mention swift-4 branch --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a7fe6080..5a424689 100644 --- a/README.md +++ b/README.md @@ -109,9 +109,7 @@ For a more comprehensive example, see [this article](http://masteringswift.blogs ## Installation -> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8) or greater. If you absolutely -> need compatibility with Swift 2.3 you can use the [swift-2.3][] branch or older -> released versions. New development will happen exclusively on the master/Swift 3 branch. +> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8). Use the [swift-4][] branch for Xcode 9 Beta. ### Carthage @@ -266,4 +264,4 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) [FMDB]: https://github.com/ccgus/fmdb -[swift-2.3]: https://github.com/stephencelis/SQLite.swift/tree/swift-2.3 +[swift-4]: https://github.com/stephencelis/SQLite.swift/tree/swift-4 From 8a8da02a15366f5c6c2ff3d2384881f49cb7ab9f Mon Sep 17 00:00:00 2001 From: avinassh Date: Fri, 21 Jul 2017 17:52:08 +0530 Subject: [PATCH 0570/1046] Fix OS X database path --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 07bb5f33..491a5938 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -251,7 +251,7 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true -).first! + Bundle.main.bundleIdentifier! +).first! + "/" + Bundle.main.bundleIdentifier! // create parent directory iff it doesn’t exist try FileManager.default.createDirectoryAtPath( From 2b3f71c861c8d34c5058a6443cd45afc7a9c563c Mon Sep 17 00:00:00 2001 From: Igor de Oliveira Sa Date: Fri, 28 Jul 2017 15:24:44 -0300 Subject: [PATCH 0571/1046] missing try clause on db.scalar line of example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a424689..e435bfed 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ for row in try db.prepare("SELECT id, email FROM users") { // id: Optional(3), email: Optional("cathy@icloud.com") } -db.scalar("SELECT count(*) FROM users") // 2 +try db.scalar("SELECT count(*) FROM users") // 2 ``` [Read the documentation][See Documentation] or explore more, From 01de138c85b4b2ee7ea6e3ac087cd9827f8fded8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20de=20Oliveira=20S=C3=A1?= Date: Thu, 3 Aug 2017 16:16:29 -0300 Subject: [PATCH 0572/1046] try was missing before db.scalar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e435bfed..8c3affd9 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) -db.scalar(users.count) // 0 +try db.scalar(users.count) // 0 // SELECT count(*) FROM "users" ``` From 8e3fd559622f96b59f1cc9882dcfd6361635f3de Mon Sep 17 00:00:00 2001 From: Ross MacLeod Date: Thu, 7 Sep 2017 12:06:49 -0400 Subject: [PATCH 0573/1046] add support for ORDER and LIMIT on UPDATE and DELETE --- Sources/SQLite/Typed/Query.swift | 8 ++++++-- Tests/SQLiteTests/QueryTests.swift | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c9d2ea9c..2ab51e45 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -648,7 +648,9 @@ extension QueryType { tableName(), Expression(literal: "SET"), ", ".join(values.map { " = ".join([$0.column, $0.value]) }), - whereClause + whereClause, + orderClause, + limitOffsetClause ] return Update(" ".join(clauses.flatMap { $0 }).expression) @@ -660,7 +662,9 @@ extension QueryType { let clauses: [Expressible?] = [ Expression(literal: "DELETE FROM"), tableName(), - whereClause + whereClause, + orderClause, + limitOffsetClause ] return Delete(" ".join(clauses.flatMap { $0 }).expression) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2cf164c6..e725105b 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -245,6 +245,13 @@ class QueryTests : XCTestCase { ) } + func test_update_compilesUpdateLimitOrderExpression() { + AssertSQL( + "UPDATE \"users\" SET \"age\" = 30 ORDER BY \"id\" LIMIT 1", + users.order(id).limit(1).update(age <- 30) + ) + } + func test_delete_compilesDeleteExpression() { AssertSQL( "DELETE FROM \"users\" WHERE (\"id\" = 1)", @@ -252,6 +259,13 @@ class QueryTests : XCTestCase { ) } + func test_delete_compilesDeleteLimitOrderExpression() { + AssertSQL( + "DELETE FROM \"users\" ORDER BY \"id\" LIMIT 1", + users.order(id).limit(1).delete() + ) + } + func test_delete_compilesExistsExpression() { AssertSQL( "SELECT EXISTS (SELECT * FROM \"users\")", From 59b8dd7a0ef4d5db96607dcd3a4a11fe05624c45 Mon Sep 17 00:00:00 2001 From: Stephan Heilner Date: Thu, 7 Sep 2017 13:30:03 -0600 Subject: [PATCH 0574/1046] Added support for the union query clause --- Sources/SQLite/Typed/Query.swift | 37 ++++++++++++++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 22 +++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c9d2ea9c..e13d054d 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -180,6 +180,27 @@ extension QueryType { return query } + // MARK: UNION + + /// Adds a `UNION` clause to the query. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.filter(email == "alice@example.com").union(users.filter(email == "sally@example.com")) + /// // SELECT * FROM "users" WHERE email = 'alice@example.com' UNION SELECT * FROM "users" WHERE email = 'sally@example.com' + /// + /// - Parameters: + /// + /// - table: A query representing the other table. + /// + /// - Returns: A query with the given `UNION` clause applied. + public func union(_ table: QueryType) -> Self { + var query = self + query.clauses.union.append(table) + return query + } + // MARK: JOIN /// Adds a `JOIN` clause to the query. @@ -565,6 +586,19 @@ extension QueryType { Expression(literal: "OFFSET \(offset)") ]) } + + fileprivate var unionClause: Expressible? { + guard !clauses.union.isEmpty else { + return nil + } + + return " ".join(clauses.union.map { query in + " ".join([ + Expression(literal: "UNION"), + query + ]) + }) + } // MARK: - @@ -779,6 +813,7 @@ extension QueryType { joinClause, whereClause, groupByClause, + unionClause, orderClause, limitOffsetClause ] @@ -1154,6 +1189,8 @@ public struct QueryClauses { var order = [Expressible]() var limit: (length: Int, offset: Int?)? + + var union = [QueryType]() fileprivate init(_ name: String, alias: String?, database: String?) { self.from = (name, alias, database) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2cf164c6..cac32b28 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -361,5 +361,25 @@ class QueryIntegrationTests : SQLiteTestCase { let changes = try! db.run(users.delete()) XCTAssertEqual(0, changes) } - + + func test_union() throws { + let expectedIDs = [ + try db.run(users.insert(email <- "alice@example.com")), + try db.run(users.insert(email <- "sally@example.com")) + ] + + let query1 = users.filter(email == "alice@example.com") + let query2 = users.filter(email == "sally@example.com") + + let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } + XCTAssertEqual(expectedIDs, actualIDs) + + let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") + let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") + + print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) + + let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } + XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) + } } From 5dbe70f5003691f5c0a081cfd49ccf7b4acacc77 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:06:10 +0200 Subject: [PATCH 0575/1046] Update for Swift 4, removing module maps and fixing the map issue --- CocoaPods/appletvos/module.modulemap | 4 --- CocoaPods/appletvsimulator/module.modulemap | 4 --- CocoaPods/iphoneos-10.0/module.modulemap | 4 --- CocoaPods/iphoneos/module.modulemap | 4 --- .../iphonesimulator-10.0/module.modulemap | 4 --- CocoaPods/iphonesimulator/module.modulemap | 4 --- CocoaPods/macosx-10.11/module.modulemap | 4 --- CocoaPods/macosx-10.12/module.modulemap | 4 --- CocoaPods/macosx/module.modulemap | 4 --- CocoaPods/watchos/module.modulemap | 4 --- CocoaPods/watchsimulator/module.modulemap | 4 --- SQLite.xcodeproj/project.pbxproj | 30 ++++++++++++++----- .../xcschemes/SQLite Mac.xcscheme | 2 +- .../xcschemes/SQLite iOS.xcscheme | 2 +- .../xcschemes/SQLite tvOS.xcscheme | 2 +- .../xcschemes/SQLite watchOS.xcscheme | 2 +- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Sources/SQLite/Typed/Query.swift | 5 ++-- Tests/SQLiteTests/ConnectionTests.swift | 2 +- 21 files changed, 34 insertions(+), 61 deletions(-) delete mode 100644 CocoaPods/appletvos/module.modulemap delete mode 100644 CocoaPods/appletvsimulator/module.modulemap delete mode 100644 CocoaPods/iphoneos-10.0/module.modulemap delete mode 100644 CocoaPods/iphoneos/module.modulemap delete mode 100644 CocoaPods/iphonesimulator-10.0/module.modulemap delete mode 100644 CocoaPods/iphonesimulator/module.modulemap delete mode 100644 CocoaPods/macosx-10.11/module.modulemap delete mode 100644 CocoaPods/macosx-10.12/module.modulemap delete mode 100644 CocoaPods/macosx/module.modulemap delete mode 100644 CocoaPods/watchos/module.modulemap delete mode 100644 CocoaPods/watchsimulator/module.modulemap diff --git a/CocoaPods/appletvos/module.modulemap b/CocoaPods/appletvos/module.modulemap deleted file mode 100644 index 637d9935..00000000 --- a/CocoaPods/appletvos/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/appletvsimulator/module.modulemap b/CocoaPods/appletvsimulator/module.modulemap deleted file mode 100644 index f8b9b671..00000000 --- a/CocoaPods/appletvsimulator/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphoneos-10.0/module.modulemap b/CocoaPods/iphoneos-10.0/module.modulemap deleted file mode 100644 index 67a6c203..00000000 --- a/CocoaPods/iphoneos-10.0/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphoneos/module.modulemap b/CocoaPods/iphoneos/module.modulemap deleted file mode 100644 index 043db6c4..00000000 --- a/CocoaPods/iphoneos/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphonesimulator-10.0/module.modulemap b/CocoaPods/iphonesimulator-10.0/module.modulemap deleted file mode 100644 index c8b84ab8..00000000 --- a/CocoaPods/iphonesimulator-10.0/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/iphonesimulator/module.modulemap b/CocoaPods/iphonesimulator/module.modulemap deleted file mode 100644 index a7b14cbb..00000000 --- a/CocoaPods/iphonesimulator/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/macosx-10.11/module.modulemap b/CocoaPods/macosx-10.11/module.modulemap deleted file mode 100644 index 9e091297..00000000 --- a/CocoaPods/macosx-10.11/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/macosx-10.12/module.modulemap b/CocoaPods/macosx-10.12/module.modulemap deleted file mode 100644 index 8fc958e6..00000000 --- a/CocoaPods/macosx-10.12/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/macosx/module.modulemap b/CocoaPods/macosx/module.modulemap deleted file mode 100644 index cc8370ec..00000000 --- a/CocoaPods/macosx/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/watchos/module.modulemap b/CocoaPods/watchos/module.modulemap deleted file mode 100644 index 62a6c4ee..00000000 --- a/CocoaPods/watchos/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/usr/include/sqlite3.h" - export * -} diff --git a/CocoaPods/watchsimulator/module.modulemap b/CocoaPods/watchsimulator/module.modulemap deleted file mode 100644 index 086fbab2..00000000 --- a/CocoaPods/watchsimulator/module.modulemap +++ /dev/null @@ -1,4 +0,0 @@ -module CSQLite [system] { - header "/Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk/usr/include/sqlite3.h" - export * -} diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 78b43249..c4d702c7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -740,7 +740,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0900; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -753,11 +753,11 @@ }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; EE247ADC1C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; @@ -1185,14 +1185,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1237,14 +1243,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1297,7 +1309,8 @@ "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1322,7 +1335,8 @@ "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1334,7 +1348,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1346,7 +1361,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 606b5a10..2c3c431f 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ ?, Expression?) -> Expression diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c9d2ea9c..6a8d885b 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -494,8 +494,9 @@ extension QueryType { return nil } - return " ".join(clauses.join.map { type, query, condition in - " ".join([ + return " ".join(clauses.join.map { arg in + let (type, query, condition) = arg + return " ".join([ Expression(literal: "\(type.rawValue) JOIN"), query.tableName(alias: true), Expression(literal: "ON"), diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index d05c5ece..373cba0a 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -6,7 +6,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif SWIFT_PACKAGE || COCOAPODS -import CSQLite +import SQLite3 #endif class ConnectionTests : SQLiteTestCase { From 53f18574ca6500d1ad44a7f083aa179daf9559d4 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:11:16 +0200 Subject: [PATCH 0576/1046] Remove reference to deleted files --- SQLite.swift.podspec | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 06aa043b..6e9145c8 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -31,19 +31,6 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.preserve_paths = 'CocoaPods/**/*' - ss.pod_target_xcconfig = { - 'SWIFT_INCLUDE_PATHS[sdk=macosx*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx', - 'SWIFT_INCLUDE_PATHS[sdk=macosx10.11]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.11', - 'SWIFT_INCLUDE_PATHS[sdk=macosx10.12]' => '$(SRCROOT)/SQLite.swift/CocoaPods/macosx-10.12', - 'SWIFT_INCLUDE_PATHS[sdk=iphoneos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos', - 'SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphoneos-10.0', - 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator', - 'SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]' => '$(SRCROOT)/SQLite.swift/CocoaPods/iphonesimulator-10.0', - 'SWIFT_INCLUDE_PATHS[sdk=appletvos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvos', - 'SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/appletvsimulator', - 'SWIFT_INCLUDE_PATHS[sdk=watchos*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchos', - 'SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]' => '$(SRCROOT)/SQLite.swift/CocoaPods/watchsimulator' - } end s.subspec 'standalone' do |ss| From 53e768ab0db413a5f0dbbb1166736eb9e89802fa Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:21:17 +0200 Subject: [PATCH 0577/1046] Remove deleted paths --- SQLite.swift.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 6e9145c8..808e8a46 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -30,7 +30,6 @@ Pod::Spec.new do |s| ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.library = 'sqlite3' - ss.preserve_paths = 'CocoaPods/**/*' end s.subspec 'standalone' do |ss| From 034093fc45c638525163c3bbfb0076ca698f73d7 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:27:27 +0200 Subject: [PATCH 0578/1046] Remove dependency --- SQLite.swift.podspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 808e8a46..ce31843e 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -28,8 +28,6 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' - - ss.library = 'sqlite3' end s.subspec 'standalone' do |ss| From 3033a4ddc6a8ed2d98b7488a51bbb553a7287036 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Thu, 15 Jun 2017 11:32:24 +0200 Subject: [PATCH 0579/1046] Undo --- SQLite.swift.podspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index ce31843e..808e8a46 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -28,6 +28,8 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' + + ss.library = 'sqlite3' end s.subspec 'standalone' do |ss| From 2211bce8dea454b48b9df94715f1526d3b9276ed Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Fri, 16 Jun 2017 23:52:47 +0200 Subject: [PATCH 0580/1046] More cleanup --- .swift-version | 2 +- SQLite.xcodeproj/project.pbxproj | 125 +++++++------------------------ 2 files changed, 26 insertions(+), 101 deletions(-) diff --git a/.swift-version b/.swift-version index 9f55b2cc..5186d070 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +4.0 diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index c4d702c7..30295edb 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; - 03A65E711C6BB2CD0062603F /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; @@ -88,7 +87,6 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; - 3D67B3FA1DB2470600A4F4C6 /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; @@ -164,10 +162,8 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - EE91808C1C46E34A0038162A /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE9180901C46E8980038162A /* usr/include/sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ @@ -207,13 +203,6 @@ 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; - 39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A691CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A6B1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A6D1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - 39548A6F1CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -265,7 +254,6 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SQLite-Bridging.h"; path = "../../SQLiteObjc/include/SQLite-Bridging.h"; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; @@ -331,76 +319,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 39548A611CA63C740003E3B5 /* CocoaPods */ = { - isa = PBXGroup; - children = ( - 39548A621CA63C740003E3B5 /* appletvos */, - 39548A641CA63C740003E3B5 /* appletvsimulator */, - 39548A661CA63C740003E3B5 /* iphoneos */, - 39548A681CA63C740003E3B5 /* iphonesimulator */, - 39548A6A1CA63C740003E3B5 /* macosx */, - 39548A6C1CA63C740003E3B5 /* watchos */, - 39548A6E1CA63C740003E3B5 /* watchsimulator */, - ); - path = CocoaPods; - sourceTree = ""; - }; - 39548A621CA63C740003E3B5 /* appletvos */ = { - isa = PBXGroup; - children = ( - 39548A631CA63C740003E3B5 /* module.modulemap */, - ); - path = appletvos; - sourceTree = ""; - }; - 39548A641CA63C740003E3B5 /* appletvsimulator */ = { - isa = PBXGroup; - children = ( - 39548A651CA63C740003E3B5 /* module.modulemap */, - ); - path = appletvsimulator; - sourceTree = ""; - }; - 39548A661CA63C740003E3B5 /* iphoneos */ = { - isa = PBXGroup; - children = ( - 39548A671CA63C740003E3B5 /* module.modulemap */, - ); - path = iphoneos; - sourceTree = ""; - }; - 39548A681CA63C740003E3B5 /* iphonesimulator */ = { - isa = PBXGroup; - children = ( - 39548A691CA63C740003E3B5 /* module.modulemap */, - ); - path = iphonesimulator; - sourceTree = ""; - }; - 39548A6A1CA63C740003E3B5 /* macosx */ = { - isa = PBXGroup; - children = ( - 39548A6B1CA63C740003E3B5 /* module.modulemap */, - ); - path = macosx; - sourceTree = ""; - }; - 39548A6C1CA63C740003E3B5 /* watchos */ = { - isa = PBXGroup; - children = ( - 39548A6D1CA63C740003E3B5 /* module.modulemap */, - ); - path = watchos; - sourceTree = ""; - }; - 39548A6E1CA63C740003E3B5 /* watchsimulator */ = { - isa = PBXGroup; - children = ( - 39548A6F1CA63C740003E3B5 /* module.modulemap */, - ); - path = watchsimulator; - sourceTree = ""; - }; 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -439,7 +357,6 @@ EE247AD51C3F04ED00AE3E12 /* SQLite */ = { isa = PBXGroup; children = ( - EE91808B1C46E34A0038162A /* usr/include/sqlite3.h */, EE247AD61C3F04ED00AE3E12 /* SQLite.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, @@ -525,7 +442,6 @@ EE247B8A1C3F81D000AE3E12 /* Metadata */ = { isa = PBXGroup; children = ( - 39548A611CA63C740003E3B5 /* CocoaPods */, EE247B771C3F40D700AE3E12 /* README.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, @@ -566,7 +482,6 @@ files = ( 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */, - 03A65E711C6BB2CD0062603F /* usr/include/sqlite3.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -575,7 +490,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3FA1DB2470600A4F4C6 /* usr/include/sqlite3.h in Headers */, 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */, 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, @@ -588,7 +502,6 @@ files = ( EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, - EE91808C1C46E34A0038162A /* usr/include/sqlite3.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -597,7 +510,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE9180901C46E8980038162A /* usr/include/sqlite3.h in Headers */, EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */, @@ -744,12 +656,15 @@ TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0900; }; 03A65E621C6BB0F60062603F = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0900; }; A121AC441CA35C79005A31D1 = { CreatedOnToolsVersion = 7.3; + LastSwiftMigration = 0900; }; EE247AD21C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; @@ -761,11 +676,11 @@ }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; EE247B441C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; }; }; @@ -1073,7 +988,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1096,7 +1012,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1109,7 +1026,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1122,7 +1040,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1146,7 +1065,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1171,7 +1091,8 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1389,7 +1310,8 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1416,7 +1338,8 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1431,7 +1354,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1446,7 +1370,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; From 52e02ae7c3e7ab7be0404c3739ec925def785ab0 Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Fri, 30 Jun 2017 19:17:46 +0200 Subject: [PATCH 0581/1046] Update Travis CI to use Xcode 9 beta 2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2eb85311..5dba809f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.2 -osx_image: xcode8.3 +osx_image: xcode9 env: global: - IOS_SIMULATOR="iPhone 6s" From f780ba16a2511760b694d7c8a179610e4198614f Mon Sep 17 00:00:00 2001 From: Jonas Zaugg Date: Fri, 30 Jun 2017 23:00:59 +0200 Subject: [PATCH 0582/1046] Fix for iOS version not found on Travis --- Makefile | 2 +- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index adab1d81..ebd38494 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 10.3 +IOS_VERSION = 11.0 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme index e6cb416a..fb00f5bb 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES" codeCoverageEnabled = "YES"> @@ -56,6 +57,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" From ede1af7baad71344dc323e3d54b0e22995992694 Mon Sep 17 00:00:00 2001 From: thebluepotato Date: Sat, 1 Jul 2017 01:33:14 +0200 Subject: [PATCH 0583/1046] Enforce iOS 11.0 after update from master --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5dba809f..62f36185 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ osx_image: xcode9 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="10.3.1" + - IOS_VERSION="11.0" matrix: include: - env: BUILD_SCHEME="SQLite iOS" From 0bbb4ca9bcc091a42bc6a60ff6c0410a70b75d18 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 22 Aug 2017 08:45:27 +0200 Subject: [PATCH 0584/1046] Fix compiler warnings - objc inference - substring(to:) deprecation - redundant conformance constraint --- SQLite.xcodeproj/project.pbxproj | 28 ++++++++++++++-------------- Sources/SQLite/Extensions/FTS4.swift | 15 ++++++++++----- Sources/SQLite/Typed/Operators.swift | 4 ++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30295edb..8e3162d9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -988,7 +988,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1012,7 +1012,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1026,7 +1026,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1040,7 +1040,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; }; @@ -1065,7 +1065,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; @@ -1091,7 +1091,7 @@ SKIP_INSTALL = YES; "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; @@ -1230,7 +1230,7 @@ "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1256,7 +1256,7 @@ "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; @@ -1269,7 +1269,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1282,7 +1282,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; @@ -1310,7 +1310,7 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1338,7 +1338,7 @@ "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; @@ -1354,7 +1354,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Debug; @@ -1370,7 +1370,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; name = Release; diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 3f28d33b..5ef84dd7 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -151,13 +151,18 @@ extension Connection { guard let (token, range) = next(string) else { return nil } - let view = string.utf8 - offset.pointee += Int32(string.substring(to: range.lowerBound).utf8.count) - length.pointee = Int32(view.distance(from: range.lowerBound.samePosition(in: view), to: range.upperBound.samePosition(in: view))) - return token + let view:String.UTF8View = string.utf8 + + if let from = range.lowerBound.samePosition(in: view), + let to = range.upperBound.samePosition(in: view) { + offset.pointee += Int32(string[string.startIndex..(lhs: V, rhs: Expression) -> Expression wher return infix(lhs, rhs) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Binding & Comparable { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) } From f898813ccf9f4966e6889ae74bb4a67104df7d69 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 14 Sep 2017 20:55:01 +0200 Subject: [PATCH 0585/1046] replace custom validator w/ new test_specs --- SQLite.swift.podspec | 23 ++++- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 41 ++++---- Tests/CocoaPods/integration_test.rb | 18 ++-- Tests/CocoaPods/test_running_validator.rb | 120 ---------------------- Tests/SQLiteTests/Fixtures.swift | 2 +- 6 files changed, 49 insertions(+), 157 deletions(-) delete mode 100644 Tests/CocoaPods/test_running_validator.rb diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 808e8a46..9ffb885f 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.3" + s.version = "0.11.4" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,26 +21,35 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '3.0', + 'SWIFT_VERSION' => '4.0', } s.subspec 'standard' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' - ss.library = 'sqlite3' + + ss.test_spec 'tests' do |test_spec| + test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.source_files = 'Tests/SQLiteTests/*.swift' + end end s.subspec 'standalone' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' } + ss.dependency 'sqlite3' - ss.dependency 'sqlite3', '>= 3.14.0' + ss.test_spec 'tests' do |test_spec| + test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.source_files = 'Tests/SQLiteTests/*.swift' + end end s.subspec 'SQLCipher' do |ss| @@ -50,7 +59,11 @@ Pod::Spec.new do |s| 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' } - ss.dependency 'SQLCipher', '>= 3.4.0' + + ss.test_spec 'tests' do |test_spec| + test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.source_files = 'Tests/SQLiteTests/*.swift' + end end end diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index e40770e8..04f0155a 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.2.0' +gem 'cocoapods', '~> 1.3.1' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index 869ae3dc..47a2db58 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,33 +2,33 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (2.3.5) - activesupport (4.2.8) + activesupport (4.2.9) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - claide (1.0.1) - cocoapods (1.2.0) + claide (1.0.2) + cocoapods (1.3.1) activesupport (>= 4.0.2, < 5) - claide (>= 1.0.1, < 2.0) - cocoapods-core (= 1.2.0) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.3.1) cocoapods-deintegrate (>= 1.0.1, < 2.0) cocoapods-downloader (>= 1.1.3, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.1.2, < 2.0) + cocoapods-trunk (>= 1.2.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) - colored (~> 1.2) + colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.5.5) + molinillo (~> 0.5.7) nap (~> 1.0) - ruby-macho (~> 0.2.5) - xcodeproj (>= 1.4.1, < 2.0) - cocoapods-core (1.2.0) - activesupport (>= 4.0.2, < 5) + ruby-macho (~> 1.1) + xcodeproj (>= 1.5.1, < 2.0) + cocoapods-core (1.3.1) + activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.1) @@ -37,37 +37,36 @@ GEM nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.1.2) + cocoapods-trunk (1.2.0) nap (>= 0.8, < 2.0) netrc (= 0.7.8) cocoapods-try (1.1.0) - colored (1.2) + colored2 (3.1.2) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.0.3) - i18n (0.8.1) + i18n (0.8.6) minitest (5.10.1) molinillo (0.5.7) nanaimo (0.2.3) nap (1.1.0) netrc (0.7.8) - ruby-macho (0.2.6) + ruby-macho (1.1.0) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) - xcodeproj (1.4.2) + xcodeproj (1.5.1) CFPropertyList (~> 2.3.3) - activesupport (>= 3) - claide (>= 1.0.1, < 2.0) - colored (~> 1.2) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) nanaimo (~> 0.2.3) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.2.0) + cocoapods (~> 1.3.1) minitest BUNDLED WITH diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 192aff90..9792b570 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -1,7 +1,8 @@ #!/usr/bin/env ruby +require 'cocoapods' +require 'cocoapods/validator' require 'minitest/autorun' -require_relative 'test_running_validator' class IntegrationTest < Minitest::Test @@ -12,9 +13,7 @@ def test_validate_project private def validator - @validator ||= TestRunningValidator.new(podspec, []).tap do |validator| - validator.test_files = Dir["#{project_test_dir}/**/*.swift"] - validator.test_resources = Dir["#{project_test_dir}/fixtures"] + @validator ||= CustomValidator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| validator.config.verbose = true validator.no_clean = true validator.use_frameworks = true @@ -27,9 +26,6 @@ def validator else validator.only_subspec = subspec end - if ENV['IOS_SIMULATOR'] - validator.ios_simulator = ENV['IOS_SIMULATOR'] - end end end @@ -37,7 +33,11 @@ def podspec File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') end - def project_test_dir - File.expand_path(File.dirname(__FILE__) + '/../SQLiteTests') + + class CustomValidator < Pod::Validator + def test_pod + # https://github.com/CocoaPods/CocoaPods/issues/7009 + super unless consumer.platform_name == :watchos + end end end diff --git a/Tests/CocoaPods/test_running_validator.rb b/Tests/CocoaPods/test_running_validator.rb deleted file mode 100644 index fcfd3c28..00000000 --- a/Tests/CocoaPods/test_running_validator.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'cocoapods' -require 'cocoapods/validator' -require 'fileutils' - -class TestRunningValidator < Pod::Validator - APP_TARGET = 'App' - TEST_TARGET = 'Tests' - - attr_accessor :test_files - attr_accessor :test_resources - attr_accessor :ios_simulator - attr_accessor :tvos_simulator - attr_accessor :watchos_simulator - - def initialize(spec_or_path, source_urls) - super(spec_or_path, source_urls) - self.test_files = [] - self.test_resources = [] - self.ios_simulator = :oldest - self.tvos_simulator = :oldest - self.watchos_simulator = :oldest - end - - def create_app_project - super - project = Xcodeproj::Project.open(validation_dir + "#{APP_TARGET}.xcodeproj") - create_test_target(project) - project.save - end - - def add_app_project_import - super - project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj') - group = project.new_group(TEST_TARGET) - test_target = project.targets.last - test_target.add_resources(test_resources.map { |resource| group.new_file(resource) }) - test_target.add_file_references(test_files.map { |file| group.new_file(file) }) - add_swift_version(test_target) - project.save - end - - def install_pod - super - if local? - FileUtils.ln_s file.dirname, validation_dir + "Pods/#{spec.name}" - end - end - - def podfile_from_spec(*args) - super(*args).tap do |pod_file| - add_test_target(pod_file) - end - end - - def build_pod - super - Pod::UI.message "\Testing with xcodebuild.\n".yellow do - run_tests - end - end - - private - def create_test_target(project) - test_target = project.new_target(:unit_test_bundle, TEST_TARGET, consumer.platform_name, deployment_target) - create_test_scheme(project, test_target) - end - - def create_test_scheme(project, test_target) - project.recreate_user_schemes - test_scheme = Xcodeproj::XCScheme.new(test_scheme_path(project)) - test_scheme.add_test_target(test_target) - test_scheme.save! - end - - def test_scheme_path(project) - Xcodeproj::XCScheme.user_data_dir(project.path) + "#{TEST_TARGET}.xcscheme" - end - - def add_test_target(pod_file) - app_target = pod_file.target_definitions[APP_TARGET] - Pod::Podfile::TargetDefinition.new(TEST_TARGET, app_target) - end - - def run_tests - command = [ - 'clean', 'build', 'build-for-testing', 'test-without-building', - '-workspace', File.join(validation_dir, "#{APP_TARGET}.xcworkspace"), - '-scheme', TEST_TARGET, - '-configuration', 'Debug' - ] - case consumer.platform_name - when :ios - command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination(ios_simulator, 'iOS', deployment_target) - when :osx - command += %w(LD_RUNPATH_SEARCH_PATHS=@loader_path/../Frameworks) - when :tvos - command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) - command += Fourflusher::SimControl.new.destination(tvos_simulator, 'tvOS', deployment_target) - when :watchos - # there's no XCTest on watchOS (https://openradar.appspot.com/21760513) - return - else - return - end - - output, status = _xcodebuild(command) - - unless status.success? - message = 'Returned an unsuccessful exit code.' - if config.verbose? - message += "\nXcode output: \n#{output}\n" - else - message += ' You can use `--verbose` for more information.' - end - error('xcodebuild', message) - end - output - end -end diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index 13f83f77..d0683130 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -3,6 +3,6 @@ import Foundation func fixture(_ name: String, withExtension: String?) -> String { let testBundle = Bundle(for: SQLiteTestCase.self) return testBundle.url( - forResource: URL(string: "fixtures")?.appendingPathComponent(name).path, + forResource: name, withExtension: withExtension)!.path } From c7656a6d90caffb92717da735182daa51dabfabd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 15 Sep 2017 17:59:39 +0200 Subject: [PATCH 0586/1046] Lower deployment target #624, #671, #717 --- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 9ffb885f..4a329338 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/stephencelis' s.module_name = 'SQLite' - s.ios.deployment_target = "9.0" + s.ios.deployment_target = "8.0" s.tvos.deployment_target = "9.1" s.osx.deployment_target = "10.10" s.watchos.deployment_target = "2.2" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 8e3162d9..fc92eeff 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1220,7 +1220,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1247,7 +1247,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; From ac52569861e6ced88f168be1fca2e20bcd1118cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 09:58:25 +0200 Subject: [PATCH 0587/1046] fix create/drop index functions --- Sources/SQLite/Typed/Schema.swift | 11 ++--------- Tests/SQLiteTests/SchemaTests.swift | 10 +++++----- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 7bf70fe4..690a26a5 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -127,11 +127,7 @@ extension Table { // MARK: - CREATE INDEX - public func createIndex(_ columns: Expressible...) -> String { - return createIndex(columns) - } - - public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { + public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .unique : nil, ifNotExists), Expression(literal: "ON"), @@ -144,11 +140,8 @@ extension Table { // MARK: - DROP INDEX - public func dropIndex(_ columns: Expressible...) -> String { - return dropIndex(columns) - } - public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { + public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { return drop("INDEX", indexName(columns), ifExists) } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index e59b569b..34226b3c 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -727,25 +727,25 @@ class SchemaTests : XCTestCase { XCTAssertEqual( "CREATE UNIQUE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", - table.createIndex([int64], unique: true) + table.createIndex(int64, unique: true) ) XCTAssertEqual( "CREATE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", - table.createIndex([int64], ifNotExists: true) + table.createIndex(int64, ifNotExists: true) ) XCTAssertEqual( "CREATE UNIQUE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", - table.createIndex([int64], unique: true, ifNotExists: true) + table.createIndex(int64, unique: true, ifNotExists: true) ) XCTAssertEqual( "CREATE UNIQUE INDEX IF NOT EXISTS \"main\".\"index_table_on_int64\" ON \"table\" (\"int64\")", - qualifiedTable.createIndex([int64], unique: true, ifNotExists: true) + qualifiedTable.createIndex(int64, unique: true, ifNotExists: true) ) } func test_dropIndex_compilesCreateIndexExpression() { XCTAssertEqual("DROP INDEX \"index_table_on_int64\"", table.dropIndex(int64)) - XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex([int64], ifExists: true)) + XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex(int64, ifExists: true)) } func test_create_onView_compilesCreateViewExpression() { From 0bb468359a9e8e709741c85ca4daa85067a7a6a6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 09:59:44 +0200 Subject: [PATCH 0588/1046] Changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6387a56..495034a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +0.11.4 (xx-09-2017), [diff][diff-0.11.4] +======================================== + +* Fix create/drop index functions ([#666][]) +* Set deployment target to 8.0 (#624, #671, #717) +* Added support for the union query clause ([#723][]) +* Add support for ORDER and LIMIT on UPDATE and DELETE ([#722][]) +* Swift 4 support [(#668][]) + 0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== @@ -33,6 +42,7 @@ [diff-0.11.1]: https://github.com/stephencelis/SQLite.swift/compare/0.11.0...0.11.1 [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 +[diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 @@ -45,3 +55,7 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#666]: https://github.com/stephencelis/SQLite.swift/pull/666 +[#668]: https://github.com/stephencelis/SQLite.swift/pull/668 +[#722]: https://github.com/stephencelis/SQLite.swift/pull/722 +[#723]: https://github.com/stephencelis/SQLite.swift/pull/723 From 3708a962dadcd46dfc9d47f44c826029d4c0c4c1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 12:01:01 +0200 Subject: [PATCH 0589/1046] Bump version number --- Documentation/Index.md | 10 +++++----- README.md | 8 ++++---- Sources/SQLite/Info.plist | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 491a5938..2255373c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -78,7 +78,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.3 + github "stephencelis/SQLite.swift" ~> 0.11.4 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -113,7 +113,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.3' + pod 'SQLite.swift', '~> 0.11.4' end ``` @@ -126,7 +126,7 @@ install SQLite.swift with Carthage: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.3' + pod 'SQLite.swift/standalone', '~> 0.11.4' end ``` @@ -134,7 +134,7 @@ By default this will use the most recent version of SQLite without any extras. I ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.3' + pod 'SQLite.swift/standalone', '~> 0.11.4' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ subspec in your Podfile: ``` ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.3' + pod 'SQLite.swift/SQLCipher', '~> 0.11.4' end ``` diff --git a/README.md b/README.md index 8c3affd9..420da81d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-3-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) +[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-4-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -109,7 +109,7 @@ For a more comprehensive example, see [this article](http://masteringswift.blogs ## Installation -> _Note:_ SQLite.swift requires Swift 3 (and [Xcode][] 8). Use the [swift-4][] branch for Xcode 9 Beta. +> _Note:_ SQLite.swift requires Swift 4 (and [Xcode][] 9). ### Carthage @@ -121,7 +121,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ``` - github "stephencelis/SQLite.swift" ~> 0.11.3 + github "stephencelis/SQLite.swift" ~> 0.11.4 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -157,7 +157,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.3' + pod 'SQLite.swift', '~> 0.11.4' end ``` diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 8c55becd..7347d842 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.3 + 0.11.4 CFBundleSignature ???? CFBundleVersion From 70c06c6624599a9bb78197e100d03a62cafd46cb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 12:08:31 +0200 Subject: [PATCH 0590/1046] Fix link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 495034a5..2473b6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Set deployment target to 8.0 (#624, #671, #717) * Added support for the union query clause ([#723][]) * Add support for ORDER and LIMIT on UPDATE and DELETE ([#722][]) -* Swift 4 support [(#668][]) +* Swift 4 support ([#668][]) 0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== From 9d42763509956cea05959f7b16c96bf4428a5202 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 18:16:26 +0200 Subject: [PATCH 0591/1046] Add ticket --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2473b6ba..b70b1403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) * Added support for the union query clause ([#723][]) -* Add support for ORDER and LIMIT on UPDATE and DELETE ([#722][]) +* Add support for ORDER and LIMIT on UPDATE and DELETE ([#657][], [#722][]) * Swift 4 support ([#668][]) 0.11.3 (30-03-2017), [diff][diff-0.11.3] @@ -55,6 +55,7 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 From b8d6a9cf9495358dd917d5270a1efef345ea0796 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 19:42:31 +0200 Subject: [PATCH 0592/1046] Generics work as expected now --- Sources/SQLite/Foundation.swift | 38 -------------- Sources/SQLite/Typed/Query.swift | 81 ++---------------------------- Tests/SQLiteTests/QueryTests.swift | 16 ++++++ 3 files changed, 20 insertions(+), 115 deletions(-) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 5e102297..5638bc5b 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -68,41 +68,3 @@ public var dateFormatter: DateFormatter = { formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() - -// FIXME: rdar://problem/18673897 // subscript… - -extension QueryType { - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - -} - -extension Row { - - public subscript(column: Expression) -> Data { - return get(column) - } - public subscript(column: Expression) -> Data? { - return get(column) - } - - public subscript(column: Expression) -> Date { - return get(column) - } - public subscript(column: Expression) -> Date? { - return get(column) - } - -} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 79fdbc66..7c0a055f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -726,47 +726,11 @@ extension QueryType { return Expression(".".join([tableName(), column]).expression) } - // FIXME: rdar://problem/18673897 // subscript… - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { - return namespace(column) - } - - public subscript(column: Expression) -> Expression { - return namespace(column) - } - public subscript(column: Expression) -> Expression { + public subscript(column: Expression) -> Expression { return namespace(column) } @@ -1102,50 +1066,13 @@ public struct Row { return valueAtIndex(idx) } - // FIXME: rdar://problem/18673897 // subscript… - - public subscript(column: Expression) -> Blob { - return get(column) - } - public subscript(column: Expression) -> Blob? { - return get(column) - } - - public subscript(column: Expression) -> Bool { - return get(column) - } - public subscript(column: Expression) -> Bool? { + public subscript(column: Expression) -> T { return get(column) } - public subscript(column: Expression) -> Double { + public subscript(column: Expression) -> T? { return get(column) } - public subscript(column: Expression) -> Double? { - return get(column) - } - - public subscript(column: Expression) -> Int { - return get(column) - } - public subscript(column: Expression) -> Int? { - return get(column) - } - - public subscript(column: Expression) -> Int64 { - return get(column) - } - public subscript(column: Expression) -> Int64? { - return get(column) - } - - public subscript(column: Expression) -> String { - return get(column) - } - public subscript(column: Expression) -> String? { - return get(column) - } - } /// Determines the join operator for a query’s `JOIN` clause. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 6bb109bb..813173ad 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -348,6 +348,22 @@ class QueryIntegrationTests : SQLiteTestCase { } } + func test_select_optional() { + for _ in try! db.prepare(users) { + // FIXME + } + + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + _ = user[users[managerId]] + } + } + func test_scalar() { XCTAssertEqual(0, try! db.scalar(users.count)) XCTAssertEqual(false, try! db.scalar(users.exists)) From bd1fc80414c89b8d7b756899cff2a153d5fd04f1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 20:43:28 +0200 Subject: [PATCH 0593/1046] Row.get should throw, not crash #649, #413 --- SQLite.xcodeproj/project.pbxproj | 10 ++++++++++ Sources/SQLite/Core/Errors.swift | 18 ++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 19 ++++++++++--------- Tests/SQLiteTests/QueryTests.swift | 14 ++++++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 Sources/SQLite/Core/Errors.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fc92eeff..e246e76d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -51,22 +51,26 @@ 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17FDA323BAFDEC627E76F /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; @@ -196,6 +200,7 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; @@ -408,6 +413,7 @@ EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, + 19A1710E73A46D5AC721CDA9 /* Errors.swift */, ); path = Core; sourceTree = ""; @@ -787,6 +793,7 @@ 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, + 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -840,6 +847,7 @@ 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, + 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -867,6 +875,7 @@ EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, + 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -920,6 +929,7 @@ EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, + 19A17490543609FCED53CACC /* Errors.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Errors.swift b/Sources/SQLite/Core/Errors.swift new file mode 100644 index 00000000..e069f50e --- /dev/null +++ b/Sources/SQLite/Core/Errors.swift @@ -0,0 +1,18 @@ +import Foundation + +public enum QueryError: Error, CustomStringConvertible { + case noSuchTable(name: String) + case noSuchColumn(name: String, columns: [String]) + case ambiguousColumn(name: String, similar: [String]) + + public var description: String { + switch self { + case .noSuchTable(let name): + return "No such table: \(name)" + case .noSuchColumn(let name, let columns): + return "No such column `\(name)` in columns \(columns)" + case .ambiguousColumn(let name, let similar): + return "Ambiguous column `\(name)` (please disambiguate: \(similar))" + } + } +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 7c0a055f..7bcda40b 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -929,7 +929,7 @@ extension Connection { continue column } } - fatalError("no such table: \(namespace)") + throw QueryError.noSuchTable(name: namespace) } for q in queries { try expandGlob(query.clauses.join.count > 0)(q) @@ -1041,13 +1041,14 @@ public struct Row { /// - Parameter column: An expression representing a column selected in a Query. /// /// - Returns: The value for the given column. - public func get(_ column: Expression) -> V { - return get(Expression(column))! + public func get(_ column: Expression) throws -> V { + return try get(Expression(column))! } - public func get(_ column: Expression) -> V? { + + public func get(_ column: Expression) throws -> V? { func valueAtIndex(_ idx: Int) -> V? { guard let value = values[idx] as? V.Datatype else { return nil } - return (V.fromDatatypeValue(value) as? V)! + return V.fromDatatypeValue(value) as? V } guard let idx = columnNames[column.template] else { @@ -1055,11 +1056,11 @@ public struct Row { switch similar.count { case 0: - fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sorted())") + throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) case 1: return valueAtIndex(columnNames[similar[0]]!) default: - fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + throw QueryError.ambiguousColumn(name: column.template, similar: similar) } } @@ -1067,11 +1068,11 @@ public struct Row { } public subscript(column: Expression) -> T { - return get(column) + return try! get(column) } public subscript(column: Expression) -> T? { - return get(column) + return try! get(column) } } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 813173ad..3ab0419a 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -412,4 +412,18 @@ class QueryIntegrationTests : SQLiteTestCase { let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) } + + func test_no_such_column() throws { + let doesNotExist = Expression("doesNotExist") + try! InsertUser("alice") + let row = try! db.pluck(users.filter(email == "alice@example.com"))! + + XCTAssertThrowsError(try row.get(doesNotExist)) { error in + if case QueryError.noSuchColumn(let name, _) = error { + XCTAssertEqual("\"doesNotExist\"", name) + } else { + XCTFail("unexpected error: \(error)") + } + } + } } From b286edee9b1244094b130d39e8b634b70d966b08 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 20:50:19 +0200 Subject: [PATCH 0594/1046] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b70b1403..e59e221c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) * Added support for the union query clause ([#723][]) @@ -55,6 +56,7 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#649]: https://github.com/stephencelis/SQLite.swift/pull/649 [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 From 5d1c7936ba63d68e5aeb3560590d124a6911ee7d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Sep 2017 21:09:11 +0200 Subject: [PATCH 0595/1046] No longer applies --- Documentation/Index.md | 12 +++--------- README.md | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2255373c..7d13b5a7 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -93,13 +93,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`). - - ```sh - sudo xcode-select --switch /Applications/Xcode.app - ``` - - 2. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). ``` sh # Using the default Ruby install will require you to use sudo when @@ -107,7 +101,7 @@ install SQLite.swift with Carthage: [sudo] gem install cocoapods ``` - 3. Update your Podfile to include the following: + 2. Update your Podfile to include the following: ``` ruby use_frameworks! @@ -117,7 +111,7 @@ install SQLite.swift with Carthage: end ``` - 4. Run `pod install --repo-update`. + 3. Run `pod install --repo-update`. #### Requiring a specific version of SQLite diff --git a/README.md b/README.md index 420da81d..fe06c6f7 100644 --- a/README.md +++ b/README.md @@ -137,13 +137,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Verify that your copy of Xcode is installed and active in the default location (`/Applications/Xcode.app`). - - ```sh - sudo xcode-select --switch /Applications/Xcode.app - ``` - - 2. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) ``` sh # Using the default Ruby install will require you to use sudo when @@ -151,7 +145,7 @@ SQLite.swift with CocoaPods: [sudo] gem install cocoapods ``` - 3. Update your Podfile to include the following: + 2. Update your Podfile to include the following: ``` ruby use_frameworks! @@ -161,7 +155,7 @@ SQLite.swift with CocoaPods: end ``` - 4. Run `pod install --repo-update`. + 3. Run `pod install --repo-update`. [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started From dc1b643b509605e90a37ebf687d3905fd06b026b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 08:51:08 +0200 Subject: [PATCH 0596/1046] Handle null value case --- SQLite.xcodeproj/project.pbxproj | 8 +++ Sources/SQLite/Core/Errors.swift | 3 ++ Sources/SQLite/Typed/Query.swift | 8 ++- Tests/SQLiteTests/RowTests.swift | 88 ++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 Tests/SQLiteTests/RowTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index e246e76d..c033cf3a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -47,8 +47,10 @@ 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; @@ -58,6 +60,7 @@ 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -204,6 +207,7 @@ 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; @@ -398,6 +402,7 @@ 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, 19A17399EA9E61235D5D77BF /* CipherTests.swift */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, + 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -820,6 +825,7 @@ 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, + 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -902,6 +908,7 @@ 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, + 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -956,6 +963,7 @@ 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, + 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Errors.swift b/Sources/SQLite/Core/Errors.swift index e069f50e..3cd7ae9f 100644 --- a/Sources/SQLite/Core/Errors.swift +++ b/Sources/SQLite/Core/Errors.swift @@ -4,6 +4,7 @@ public enum QueryError: Error, CustomStringConvertible { case noSuchTable(name: String) case noSuchColumn(name: String, columns: [String]) case ambiguousColumn(name: String, similar: [String]) + case unexpectedNullValue(name: String) public var description: String { switch self { @@ -13,6 +14,8 @@ public enum QueryError: Error, CustomStringConvertible { return "No such column `\(name)` in columns \(columns)" case .ambiguousColumn(let name, let similar): return "Ambiguous column `\(name)` (please disambiguate: \(similar))" + case .unexpectedNullValue(let name): + return "Unexpected null value for column `\(name)`" } } } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 7bcda40b..8fa2c539 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1031,7 +1031,7 @@ public struct Row { fileprivate let values: [Binding?] - fileprivate init(_ columnNames: [String: Int], _ values: [Binding?]) { + internal init(_ columnNames: [String: Int], _ values: [Binding?]) { self.columnNames = columnNames self.values = values } @@ -1042,7 +1042,11 @@ public struct Row { /// /// - Returns: The value for the given column. public func get(_ column: Expression) throws -> V { - return try get(Expression(column))! + if let value = try get(Expression(column)) { + return value + } else { + throw QueryError.unexpectedNullValue(name: column.template) + } } public func get(_ column: Expression) throws -> V? { diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift new file mode 100644 index 00000000..4ef5dc2d --- /dev/null +++ b/Tests/SQLiteTests/RowTests.swift @@ -0,0 +1,88 @@ +import XCTest +@testable import SQLite + +class RowTests : XCTestCase { + + public func test_get_value() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = try! row.get(Expression("foo")) + + XCTAssertEqual("value", result) + } + + public func test_get_value_subscript() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = row[Expression("foo")] + + XCTAssertEqual("value", result) + } + + public func test_get_value_optional() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = try! row.get(Expression("foo")) + + XCTAssertEqual("value", result) + } + + public func test_get_value_optional_subscript() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = row[Expression("foo")] + + XCTAssertEqual("value", result) + } + + public func test_get_value_optional_nil() { + let row = Row(["\"foo\"": 0], [nil]) + let result = try! row.get(Expression("foo")) + + XCTAssertNil(result) + } + + public func test_get_value_optional_nil_subscript() { + let row = Row(["\"foo\"": 0], [nil]) + let result = row[Expression("foo")] + + XCTAssertNil(result) + } + + public func test_get_type_mismatch_throws_unexpected_null_value() { + let row = Row(["\"foo\"": 0], ["value"]) + XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + if case QueryError.unexpectedNullValue(let name) = error { + XCTAssertEqual("\"foo\"", name) + } else { + XCTFail("unexpected error: \(error)") + } + } + } + + public func test_get_type_mismatch_optional_returns_nil() { + let row = Row(["\"foo\"": 0], ["value"]) + let result = try! row.get(Expression("foo")) + XCTAssertNil(result) + } + + public func test_get_non_existent_column_throws_no_such_column() { + let row = Row(["\"foo\"": 0], ["value"]) + XCTAssertThrowsError(try row.get(Expression("bar"))) { error in + if case QueryError.noSuchColumn(let name, let columns) = error { + XCTAssertEqual("\"bar\"", name) + XCTAssertEqual(["\"foo\""], columns) + } else { + XCTFail("unexpected error: \(error)") + } + } + } + + public func test_get_ambiguous_column_throws() { + let row = Row(["table1.\"foo\"": 0, "table2.\"foo\"": 1], ["value"]) + XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + if case QueryError.ambiguousColumn(let name, let columns) = error { + XCTAssertEqual("\"foo\"", name) + XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns) + } else { + XCTFail("unexpected error: \(error)") + } + } + } +} From d79d52c74ed138280c36314f0e1e42757679fc6d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:04:20 +0200 Subject: [PATCH 0597/1046] Simplify `sync` #640 --- Sources/SQLite/Core/Connection.swift | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 51b68ed8..a2e7dcf2 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -626,29 +626,12 @@ public final class Connection { // MARK: - Error Handling - func sync(_ block: @escaping () throws -> T) rethrows -> T { - var success: T? - var failure: Error? - - let box: () -> Void = { - do { - success = try block() - } catch { - failure = error - } - } - + func sync(_ block: () throws -> T) rethrows -> T { if DispatchQueue.getSpecific(key: Connection.queueKey) == queueContext { - box() + return try block() } else { - queue.sync(execute: box) // FIXME: rdar://problem/21389236 - } - - if let failure = failure { - try { () -> Void in throw failure }() + return try queue.sync(execute: block) } - - return success! } @discardableResult func check(_ resultCode: Int32, statement: Statement? = nil) throws -> Int32 { From a1dc335fee02985a4af08e2e32421b28e9cfac7b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:08:17 +0200 Subject: [PATCH 0598/1046] Fix FIXMEs --- Sources/SQLite/Typed/Expression.swift | 3 +-- Tests/SQLiteTests/CoreFunctionsTests.swift | 2 +- Tests/SQLiteTests/QueryTests.swift | 10 +--------- Tests/SQLiteTests/TestHelpers.swift | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 3198901c..d26b6692 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,8 +73,7 @@ public protocol Expressible { extension Expressible { // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - // FIXME: use @testable and make internal - public func asSQL() -> String { + internal func asSQL() -> String { let expressed = expression var idx = 0 return expressed.template.characters.reduce("") { template, character in diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index db37ff7f..4a2b511e 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class CoreFunctionsTests : XCTestCase { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 3ab0419a..7dde0c53 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class QueryTests : XCTestCase { @@ -333,10 +333,6 @@ class QueryIntegrationTests : SQLiteTestCase { // MARK: - func test_select() { - for _ in try! db.prepare(users) { - // FIXME - } - let managerId = Expression("manager_id") let managers = users.alias("managers") @@ -349,10 +345,6 @@ class QueryIntegrationTests : SQLiteTestCase { } func test_select_optional() { - for _ in try! db.prepare(users) { - // FIXME - } - let managerId = Expression("manager_id") let managers = users.alias("managers") diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 8c33bf6a..b66144b3 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite class SQLiteTestCase : XCTestCase { From 9b8cc16114f25a8b8e8541f3524a38434cbdf554 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:12:20 +0200 Subject: [PATCH 0599/1046] Drop `@escaping` --- Sources/SQLite/Core/Connection.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index a2e7dcf2..0189d300 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -328,7 +328,7 @@ public final class Connection { /// must throw to roll the transaction back. /// /// - Throws: `Result.Error`, and rethrows. - public func transaction(_ mode: TransactionMode = .deferred, block: @escaping () throws -> Void) throws { + public func transaction(_ mode: TransactionMode = .deferred, block: () throws -> Void) throws { try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") } @@ -348,14 +348,14 @@ public final class Connection { /// The block must throw to roll the savepoint back. /// /// - Throws: `SQLite.Result.Error`, and rethrows. - public func savepoint(_ name: String = UUID().uuidString, block: @escaping () throws -> Void) throws { + public func savepoint(_ name: String = UUID().uuidString, block: () throws -> Void) throws { let name = name.quote("'") let savepoint = "SAVEPOINT \(name)" try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") } - fileprivate func transaction(_ begin: String, _ block: @escaping () throws -> Void, _ commit: String, or rollback: String) throws { + fileprivate func transaction(_ begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { return try sync { try self.run(begin) do { From 5850d8397d846814e58d74cdab5710ebfa639b8a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 09:41:28 +0200 Subject: [PATCH 0600/1046] Fix test --- Tests/SQLiteTests/RowTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift index 4ef5dc2d..17873e71 100644 --- a/Tests/SQLiteTests/RowTests.swift +++ b/Tests/SQLiteTests/RowTests.swift @@ -79,7 +79,7 @@ class RowTests : XCTestCase { XCTAssertThrowsError(try row.get(Expression("foo"))) { error in if case QueryError.ambiguousColumn(let name, let columns) = error { XCTAssertEqual("\"foo\"", name) - XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns) + XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns.sorted()) } else { XCTFail("unexpected error: \(error)") } From 9f1735a52898a3cae23020a76e2003357712c38c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Sep 2017 10:31:56 +0200 Subject: [PATCH 0601/1046] Keep API compatibility --- Sources/SQLite/Typed/Expression.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index d26b6692..33329b73 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,7 +73,8 @@ public protocol Expressible { extension Expressible { // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - internal func asSQL() -> String { + // FIXME: make internal (0.12.0) + public func asSQL() -> String { let expressed = expression var idx = 0 return expressed.template.characters.reduce("") { template, character in From feb26b06cd29584bba59c5db5f84b958a476d9bc Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Fri, 5 May 2017 14:48:18 -0600 Subject: [PATCH 0602/1046] Add cursor type for more safety. --- Sources/SQLite/Core/Statement.swift | 8 ++ Sources/SQLite/Typed/Query.swift | 111 ++++++++++++++++++---------- Tests/SQLiteTests/QueryTests.swift | 10 +++ 3 files changed, 89 insertions(+), 40 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 8c13ff6c..c817e887 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -191,6 +191,14 @@ public final class Statement { } +extension Statement { + + func rowCursorNext() throws -> [Binding?]? { + return try step() ? Array(row) : nil + } + +} + extension Statement : Sequence { public func makeIterator() -> Statement { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 8fa2c539..199e5ef8 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -894,58 +894,89 @@ public struct Delete : ExpressionType { } +public struct RowCursor { + let statement: Statement + let columnNames: [String: Int] + + public func next() throws -> Row? { + return try statement.rowCursorNext().flatMap { Row(columnNames, $0) } + } + + public func map(_ transform: (Row) throws -> T) throws -> [T] { + var elements = [T]() + while true { + if let row = try next() { + elements.append(try transform(row)) + } else { + break + } + } + + return elements + } +} + extension Connection { + + public func prepareCursor(_ query: QueryType) throws -> RowCursor { + let expression = query.expression + let statement = try prepare(expression.template, expression.bindings) + return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) + } public func prepare(_ query: QueryType) throws -> AnySequence { let expression = query.expression let statement = try prepare(expression.template, expression.bindings) - let columnNames: [String: Int] = try { - var (columnNames, idx) = ([String: Int](), 0) - column: for each in query.clauses.select.columns { - var names = each.expression.template.characters.split { $0 == "." }.map(String.init) - let column = names.removeLast() - let namespace = names.joined(separator: ".") - - func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { - return { (query: QueryType) throws -> (Void) in - var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) - q.clauses.select = query.clauses.select - let e = q.expression - var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } - if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } - for name in names { columnNames[name] = idx; idx += 1 } - } - } + let columnNames = try columnNamesForQuery(query) - if column == "*" { - var select = query - select.clauses.select = (false, [Expression(literal: "*") as Expressible]) - let queries = [select] + query.clauses.join.map { $0.query } - if !namespace.isEmpty { - for q in queries { - if q.tableName().expression.template == namespace { - try expandGlob(true)(q) - continue column - } + return AnySequence { + AnyIterator { statement.next().map { Row(columnNames, $0) } } + } + } + + private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { + var (columnNames, idx) = ([String: Int](), 0) + column: for each in query.clauses.select.columns { + var names = each.expression.template.characters.split { $0 == "." }.map(String.init) + let column = names.removeLast() + let namespace = names.joined(separator: ".") + + func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { + return { (query: QueryType) throws -> (Void) in + var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) + q.clauses.select = query.clauses.select + let e = q.expression + var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } + if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } + for name in names { columnNames[name] = idx; idx += 1 } + } + } + + if column == "*" { + var select = query + select.clauses.select = (false, [Expression(literal: "*") as Expressible]) + let queries = [select] + query.clauses.join.map { $0.query } + if !namespace.isEmpty { + for q in queries { + if q.tableName().expression.template == namespace { + try expandGlob(true)(q) + continue column } throw QueryError.noSuchTable(name: namespace) } - for q in queries { - try expandGlob(query.clauses.join.count > 0)(q) - } - continue + fatalError("no such table: \(namespace)") } - - columnNames[each.expression.template] = idx - idx += 1 + for q in queries { + try expandGlob(query.clauses.join.count > 0)(q) + } + continue } - return columnNames - }() - - return AnySequence { - AnyIterator { statement.next().map { Row(columnNames, $0) } } + + columnNames[each.expression.template] = idx + idx += 1 } + return columnNames } public func scalar(_ query: ScalarQuery) throws -> V { @@ -971,7 +1002,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepare(query.limit(1, query.clauses.limit?.offset)).makeIterator().next() + return try prepareCursor(query.limit(1, query.clauses.limit?.offset)).next() } /// Runs an `Insert` query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 7dde0c53..feb0596d 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -343,6 +343,16 @@ class QueryIntegrationTests : SQLiteTestCase { _ = user[users[managerId]] } } + + func test_prepareCursor() { + let names = ["a", "b", "c"] + try! InsertUsers(names) + + let emailColumn = Expression("email") + let emails = try! db.prepareCursor(users).map { $0[emailColumn] } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } func test_select_optional() { let managerId = Expression("manager_id") From 28e2cfa1cc9a0251abdbd707bec11f265a62c0a9 Mon Sep 17 00:00:00 2001 From: Nick Shelley Date: Wed, 20 Sep 2017 07:53:39 -0600 Subject: [PATCH 0603/1046] Change fatalError to throw. --- Sources/SQLite/Typed/Query.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 199e5ef8..e9fa941c 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -965,7 +965,7 @@ extension Connection { } throw QueryError.noSuchTable(name: namespace) } - fatalError("no such table: \(namespace)") + throw QueryError.noSuchTable(name: namespace) } for q in queries { try expandGlob(query.clauses.join.count > 0)(q) From 5a9b583d2a28d12bc8523b204e75a0da6c8b04ff Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 24 Sep 2017 22:53:16 +0200 Subject: [PATCH 0604/1046] Introduce FailableIterator --- Sources/SQLite/Core/Statement.swift | 40 +++++++++++++++++++++-------- Sources/SQLite/Typed/Query.swift | 38 ++++++++++----------------- Tests/SQLiteTests/QueryTests.swift | 10 ++++---- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index c817e887..5df00791 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -191,14 +191,6 @@ public final class Statement { } -extension Statement { - - func rowCursorNext() throws -> [Binding?]? { - return try step() ? Array(row) : nil - } - -} - extension Statement : Sequence { public func makeIterator() -> Statement { @@ -208,12 +200,38 @@ extension Statement : Sequence { } -extension Statement : IteratorProtocol { +public protocol FailableIterator : IteratorProtocol { + func failableNext() throws -> Self.Element? +} - public func next() -> [Binding?]? { - return try! step() ? Array(row) : nil +extension FailableIterator { + public func next() -> Element? { + return try! failableNext() } + public func map(_ transform: (Element) throws -> T) throws -> [T] { + var elements = [T]() + while let row = try failableNext() { + elements.append(try transform(row)) + } + return elements + } +} + +extension Array { + public init(_ failableIterator: I) throws where I.Element == Element { + self.init() + while let row = try failableIterator.failableNext() { + append(row) + } + } +} + +extension Statement : FailableIterator { + public typealias Element = [Binding?] + public func failableNext() throws -> [Binding?]? { + return try step() ? Array(row) : nil + } } extension Statement : CustomStringConvertible { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index e9fa941c..cc54649e 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -894,35 +894,19 @@ public struct Delete : ExpressionType { } -public struct RowCursor { + +public struct RowCursor: FailableIterator { + public typealias Element = Row let statement: Statement let columnNames: [String: Int] - - public func next() throws -> Row? { - return try statement.rowCursorNext().flatMap { Row(columnNames, $0) } - } - - public func map(_ transform: (Row) throws -> T) throws -> [T] { - var elements = [T]() - while true { - if let row = try next() { - elements.append(try transform(row)) - } else { - break - } - } - - return elements + + public func failableNext() throws -> Row? { + return try statement.failableNext().flatMap { Row(columnNames, $0) } } } + extension Connection { - - public func prepareCursor(_ query: QueryType) throws -> RowCursor { - let expression = query.expression - let statement = try prepare(expression.template, expression.bindings) - return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) - } public func prepare(_ query: QueryType) throws -> AnySequence { let expression = query.expression @@ -935,6 +919,12 @@ extension Connection { } } + public func prepareRowCursor(_ query: QueryType) throws -> RowCursor { + let expression = query.expression + let statement = try prepare(expression.template, expression.bindings) + return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) + } + private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { var (columnNames, idx) = ([String: Int](), 0) column: for each in query.clauses.select.columns { @@ -1002,7 +992,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepareCursor(query.limit(1, query.clauses.limit?.offset)).next() + return try prepareRowCursor(query.limit(1, query.clauses.limit?.offset)).failableNext() } /// Runs an `Insert` query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index feb0596d..2c026b9b 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -343,14 +343,14 @@ class QueryIntegrationTests : SQLiteTestCase { _ = user[users[managerId]] } } - - func test_prepareCursor() { + + func test_prepareRowCursor() { let names = ["a", "b", "c"] try! InsertUsers(names) - + let emailColumn = Expression("email") - let emails = try! db.prepareCursor(users).map { $0[emailColumn] } - + let emails = try! db.prepareRowCursor(users).map { $0[emailColumn] } + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } From 30b78fdcf995cbe75dd3469188de6b326cfb2f08 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 25 Sep 2017 17:53:18 +0200 Subject: [PATCH 0605/1046] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e59e221c..2963d8d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Add cursor type for more safety ([#647](), [#726][]) * Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) @@ -56,9 +57,11 @@ [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#647]: https://github.com/stephencelis/SQLite.swift/pull/647 [#649]: https://github.com/stephencelis/SQLite.swift/pull/649 [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 +[#726]: https://github.com/stephencelis/SQLite.swift/pull/726 From 2ebdf7802bbea4b009c6d8493842d5eeccf3113d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 25 Sep 2017 18:18:17 +0200 Subject: [PATCH 0606/1046] RowCursor -> RowIterator --- CHANGELOG.md | 2 +- Sources/SQLite/Typed/Query.swift | 9 +++++---- Tests/SQLiteTests/QueryTests.swift | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2963d8d4..3fa68dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== -* Add cursor type for more safety ([#647](), [#726][]) +* Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) * Set deployment target to 8.0 (#624, #671, #717) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index cc54649e..a08561cb 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -895,7 +895,7 @@ public struct Delete : ExpressionType { } -public struct RowCursor: FailableIterator { +public struct RowIterator: FailableIterator { public typealias Element = Row let statement: Statement let columnNames: [String: Int] @@ -919,10 +919,11 @@ extension Connection { } } - public func prepareRowCursor(_ query: QueryType) throws -> RowCursor { + + public func prepareRowIterator(_ query: QueryType) throws -> RowIterator { let expression = query.expression let statement = try prepare(expression.template, expression.bindings) - return RowCursor(statement: statement, columnNames: try columnNamesForQuery(query)) + return RowIterator(statement: statement, columnNames: try columnNamesForQuery(query)) } private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { @@ -992,7 +993,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepareRowCursor(query.limit(1, query.clauses.limit?.offset)).failableNext() + return try prepareRowIterator(query.limit(1, query.clauses.limit?.offset)).failableNext() } /// Runs an `Insert` query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2c026b9b..df0e7bec 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -344,12 +344,12 @@ class QueryIntegrationTests : SQLiteTestCase { } } - func test_prepareRowCursor() { + func test_prepareRowIterator() { let names = ["a", "b", "c"] try! InsertUsers(names) let emailColumn = Expression("email") - let emails = try! db.prepareRowCursor(users).map { $0[emailColumn] } + let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } From c3c8360e89cd47a500622ded3268c77ca77f839f Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 19 Sep 2017 22:33:38 -0600 Subject: [PATCH 0607/1046] Add Coding support - Insert or update from an Encodable type - Decode a row into a Decodable type Closes stephencelis/SQLite.swift#727 --- Sources/SQLite/Typed/Query.swift | 321 ++++++++++++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 77 +++++++ Tests/SQLiteTests/TestHelpers.swift | 20 ++ 3 files changed, 418 insertions(+) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 8fa2c539..45d15c79 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import Foundation + public protocol QueryType : Expressible { var clauses: QueryClauses { get set } @@ -671,6 +673,26 @@ extension QueryType { ]).expression) } + /// Creates an `INSERT` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.insert(encoder.setters + otherSetters) + } + // MARK: UPDATE public func update(_ values: Setter...) -> Update { @@ -691,6 +713,26 @@ extension QueryType { return Update(" ".join(clauses.flatMap { $0 }).expression) } + /// Creates an `UPDATE` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `UPDATE` statement fort the encodable object + public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.update(encoder.setters + otherSetters) + } + // MARK: DELETE public func delete() -> Delete { @@ -1036,6 +1078,13 @@ public struct Row { self.values = values } + fileprivate func hasValue(for column: String) -> Bool { + guard let idx = columnNames[column.quote()] else { + return false + } + return values[idx] != nil + } + /// Returns a row’s value for the given column. /// /// - Parameter column: An expression representing a column selected in a Query. @@ -1071,6 +1120,22 @@ public struct Row { return valueAtIndex(idx) } + /// Decode an object from this row + /// This method expects any custom nested types to be in the form of JSON data and does not handle + /// any sort of object relationships. If you want to support relationships between objects you will + /// have to provide your own Decodable implementations that decodes the correct columns. + /// + /// - Parameter: userInfo + /// + /// - Returns: a decoded object from this row + public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { + return try V(from: self.decoder(userInfo: userInfo)) + } + + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { + return SQLiteDecoder(row: self, userInfo: userInfo) + } + public subscript(column: Expression) -> T { return try! get(column) } @@ -1134,3 +1199,259 @@ public struct QueryClauses { } } + +/// Generates a list of settings for an Encodable object +fileprivate class SQLiteEncoder: Encoder { + struct EncodingError: Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { + typealias Key = MyKey + + let encoder: SQLiteEncoder + let codingPath: [CodingKey] = [] + + init(encoder: SQLiteEncoder) { + self.encoder = encoder + } + + func superEncoder() -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func superEncoder(forKey key: Key) -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- nil) + } + + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Bool, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Float, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) + } + + func encode(_ value: Double, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: String, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { + if let data = value as? Data { + self.encoder.setters.append(Expression(key.stringValue) <- data) + } + else { + let encoded = try JSONEncoder().encode(value) + self.encoder.setters.append(Expression(key.stringValue) <- encoded) + } + } + + func encode(_ value: Int8, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int8 is not supported") + } + + func encode(_ value: Int16, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int16 is not supported") + } + + func encode(_ value: Int32, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int32 is not supported") + } + + func encode(_ value: Int64, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int64 is not supported") + } + + func encode(_ value: UInt, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt is not supported") + } + + func encode(_ value: UInt8, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt8 is not supported") + } + + func encode(_ value: UInt16, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt16 is not supported") + } + + func encode(_ value: UInt32, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt32 is not supported") + } + + func encode(_ value: UInt64, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt64 is not supported") + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + fatalError("encoding a nested container is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + fatalError("encoding nested values is not supported") + } + } + + fileprivate var setters: [SQLite.Setter] = [] + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(userInfo: [CodingUserInfoKey: Any]) { + self.userInfo = userInfo + } + + func singleValueContainer() -> SingleValueEncodingContainer { + fatalError("not supported") + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("not supported") + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + } +} + +class SQLiteDecoder : Decoder { + struct DecodingError : Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { + typealias Key = MyKey + + let codingPath: [CodingKey] = [] + let row: Row + + init(row: Row) { + self.row = row + } + + var allKeys: [Key] { + return self.row.columnNames.keys.flatMap({Key(stringValue: $0)}) + } + + func contains(_ key: Key) -> Bool { + return self.row.hasValue(for: key.stringValue) + } + + func decodeNil(forKey key: Key) throws -> Bool { + return !self.contains(key) + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw DecodingError(description: "decoding an Int8 is not supported") + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw DecodingError(description: "decoding an Int16 is not supported") + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw DecodingError(description: "decoding an Int32 is not supported") + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw DecodingError(description: "decoding an Int64 is not supported") + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw DecodingError(description: "decoding an UInt is not supported") + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw DecodingError(description: "decoding an UInt8 is not supported") + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw DecodingError(description: "decoding an UInt16 is not supported") + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw DecodingError(description: "decoding an UInt32 is not supported") + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw DecodingError(description: "decoding an UInt64 is not supported") + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + return Float(try self.row.get(Expression(key.stringValue))) + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { + guard let data = try self.row.get(Expression(key.stringValue)) else { + throw DecodingError(description: "an unsupported type was found") + } + if type == Data.self { + return data as! T + } + return try JSONDecoder().decode(type, from: data) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + throw DecodingError(description: "decoding nested containers is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding unkeyed containers is not supported") + } + + func superDecoder() throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + + func superDecoder(forKey key: Key) throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + } + + let row: Row + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(row: Row, userInfo: [CodingUserInfoKey: Any]) { + self.row = row + self.userInfo = userInfo + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") + } +} diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 7dde0c53..be414735 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -238,6 +238,27 @@ class QueryTests : XCTestCase { ) } + func test_insert_encodable() throws { + let emails = Table("emails") + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let insert = try emails.insert(value) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0)", + insert + ) + } + + func test_insert_encodable_with_nested_encodable() throws { + let emails = Table("emails") + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: "optional", sub: value1) + let insert = try emails.insert(value) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d')", + insert + ) + } + func test_update_compilesUpdateExpression() { AssertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", @@ -252,6 +273,27 @@ class QueryTests : XCTestCase { ) } + func test_update_encodable() throws { + let emails = Table("emails") + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let update = try emails.update(value) + AssertSQL( + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0", + update + ) + } + + func test_update_encodable_with_nested_encodable() throws { + let emails = Table("emails") + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: value1) + let update = try emails.update(value) + AssertSQL( + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d'", + update + ) + } + func test_delete_compilesDeleteExpression() { AssertSQL( "DELETE FROM \"users\" WHERE (\"id\" = 1)", @@ -356,6 +398,41 @@ class QueryIntegrationTests : SQLiteTestCase { } } + func test_select_codable() throws { + let table = Table("codable") + try db.run(table.create { builder in + builder.column(Expression("int")) + builder.column(Expression("string")) + builder.column(Expression("bool")) + builder.column(Expression("float")) + builder.column(Expression("double")) + builder.column(Expression("optional")) + builder.column(Expression("sub")) + }) + + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, optional: "optional", sub: value1) + + try db.run(table.insert(value)) + + let rows = try db.prepare(table) + let values: [TestCodable] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0].int, 5) + XCTAssertEqual(values[0].string, "6") + XCTAssertEqual(values[0].bool, true) + XCTAssertEqual(values[0].float, 7) + XCTAssertEqual(values[0].double, 8) + XCTAssertEqual(values[0].optional, "optional") + XCTAssertEqual(values[0].sub?.int, 1) + XCTAssertEqual(values[0].sub?.string, "2") + XCTAssertEqual(values[0].sub?.bool, true) + XCTAssertEqual(values[0].sub?.float, 3) + XCTAssertEqual(values[0].sub?.double, 4) + XCTAssertNil(values[0].sub?.optional) + XCTAssertNil(values[0].sub?.sub) + } + func test_scalar() { XCTAssertEqual(0, try! db.scalar(users.count)) XCTAssertEqual(false, try! db.scalar(users.exists)) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index b66144b3..4572f6fe 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -113,3 +113,23 @@ let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") let _view = View("view") // avoid Mac XCTestCase collision + +class TestCodable: Codable { + let int: Int + let string: String + let bool: Bool + let float: Float + let double: Double + let optional: String? + let sub: TestCodable? + + init(int: Int, string: String, bool: Bool, float: Float, double: Double, optional: String?, sub: TestCodable?) { + self.int = int + self.string = string + self.bool = bool + self.float = float + self.double = double + self.optional = optional + self.sub = sub + } +} From 7312423d83bb713591a0d827a89fe664eba14451 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 26 Sep 2017 01:28:01 +0200 Subject: [PATCH 0608/1046] Avoid ambiguity --- Sources/SQLite/Core/Statement.swift | 8 -------- Sources/SQLite/Typed/Query.swift | 9 ++++++++- Tests/SQLiteTests/QueryTests.swift | 9 +++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 5df00791..1e48d3e8 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -208,14 +208,6 @@ extension FailableIterator { public func next() -> Element? { return try! failableNext() } - - public func map(_ transform: (Element) throws -> T) throws -> [T] { - var elements = [T]() - while let row = try failableNext() { - elements.append(try transform(row)) - } - return elements - } } extension Array { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index a08561cb..d5680cd0 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -903,8 +903,15 @@ public struct RowIterator: FailableIterator { public func failableNext() throws -> Row? { return try statement.failableNext().flatMap { Row(columnNames, $0) } } -} + public func map(_ transform: (Element) throws -> T) throws -> [T] { + var elements = [T]() + while let row = try failableNext() { + elements.append(try transform(row)) + } + return elements + } +} extension Connection { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index df0e7bec..0ba469a9 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -354,6 +354,15 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } + func test_ambiguousMap() { + let names = ["a", "b", "c"] + try! InsertUsers(names) + + let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } + func test_select_optional() { let managerId = Expression("manager_id") let managers = users.alias("managers") From 87b16d77db5df72d4c414658cc76d85f41914510 Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Mon, 25 Sep 2017 19:29:35 -0600 Subject: [PATCH 0609/1046] Store embedded Codable objects as JSON text instead of data --- Sources/SQLite/Typed/Query.swift | 13 +++++++++---- Tests/SQLiteTests/QueryTests.swift | 8 ++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 45d15c79..0b257fbe 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1254,7 +1254,8 @@ fileprivate class SQLiteEncoder: Encoder { } else { let encoded = try JSONEncoder().encode(value) - self.encoder.setters.append(Expression(key.stringValue) <- encoded) + let string = String(data: encoded, encoding: .utf8) + self.encoder.setters.append(Expression(key.stringValue) <- string) } } @@ -1408,12 +1409,16 @@ class SQLiteDecoder : Decoder { } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { - guard let data = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError(description: "an unsupported type was found") - } if type == Data.self { + let data = try self.row.get(Expression(key.stringValue)) return data as! T } + guard let JSONString = try self.row.get(Expression(key.stringValue)) else { + throw DecodingError(description: "an unsupported type was found") + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError(description: "invalid utf8 data found") + } return try JSONDecoder().decode(type, from: data) } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index be414735..6ba55d16 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -253,8 +253,10 @@ class QueryTests : XCTestCase { let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: "optional", sub: value1) let insert = try emails.insert(value) + let encodedJSON = try JSONEncoder().encode(value1) + let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d')", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', '\(encodedJSONString)')", insert ) } @@ -288,8 +290,10 @@ class QueryTests : XCTestCase { let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: value1) let update = try emails.update(value) + let encodedJSON = try JSONEncoder().encode(value1) + let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = x'7b22626f6f6c223a747275652c22737472696e67223a2232222c22666c6f6174223a332c22696e74223a312c22646f75626c65223a347d'", + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = '\(encodedJSONString)'", update ) } From 96e55b19d31e30e4f3369d2ba077494c2c3d20b3 Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 26 Sep 2017 20:01:00 -0600 Subject: [PATCH 0610/1046] Extract Coding code to a separate file --- SQLite.xcodeproj/project.pbxproj | 10 + Sources/SQLite/Typed/Coding.swift | 345 ++++++++++++++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 320 +-------------------------- 3 files changed, 357 insertions(+), 318 deletions(-) create mode 100644 Sources/SQLite/Typed/Coding.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index c033cf3a..3093b11d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -97,6 +97,10 @@ 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -213,6 +217,7 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -446,6 +451,7 @@ EE247B001C3F06E900AE3E12 /* Query.swift */, EE247B011C3F06E900AE3E12 /* Schema.swift */, EE247B021C3F06E900AE3E12 /* Setter.swift */, + 49EB68C31F7B3CB400D89D40 /* Coding.swift */, ); path = Typed; sourceTree = ""; @@ -779,6 +785,7 @@ buildActionMask = 2147483647; files = ( 03A65E801C6BB2FB0062603F /* CoreFunctions.swift in Sources */, + 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */, @@ -834,6 +841,7 @@ buildActionMask = 2147483647; files = ( 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */, + 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, @@ -862,6 +870,7 @@ buildActionMask = 2147483647; files = ( EE247B0F1C3F06E900AE3E12 /* CoreFunctions.swift in Sources */, + 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B0A1C3F06E900AE3E12 /* RTree.swift in Sources */, EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */, EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */, @@ -917,6 +926,7 @@ buildActionMask = 2147483647; files = ( EE247B6F1C3F3FEC00AE3E12 /* CoreFunctions.swift in Sources */, + 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */, diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift new file mode 100644 index 00000000..68f0a998 --- /dev/null +++ b/Sources/SQLite/Typed/Coding.swift @@ -0,0 +1,345 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension QueryType { + /// Creates an `INSERT` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.insert(encoder.setters + otherSetters) + } + + /// Creates an `UPDATE` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `UPDATE` statement fort the encodable object + public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.update(encoder.setters + otherSetters) + } +} + +extension Row { + /// Decode an object from this row + /// This method expects any custom nested types to be in the form of JSON data and does not handle + /// any sort of object relationships. If you want to support relationships between objects you will + /// have to provide your own Decodable implementations that decodes the correct columns. + /// + /// - Parameter: userInfo + /// + /// - Returns: a decoded object from this row + public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { + return try V(from: self.decoder(userInfo: userInfo)) + } + + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { + return SQLiteDecoder(row: self, userInfo: userInfo) + } +} + +/// Generates a list of settings for an Encodable object +fileprivate class SQLiteEncoder: Encoder { + struct EncodingError: Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { + typealias Key = MyKey + + let encoder: SQLiteEncoder + let codingPath: [CodingKey] = [] + + init(encoder: SQLiteEncoder) { + self.encoder = encoder + } + + func superEncoder() -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func superEncoder(forKey key: Key) -> Swift.Encoder { + fatalError("SQLiteEncoding does not support super encoders") + } + + func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- nil) + } + + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Bool, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: Float, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) + } + + func encode(_ value: Double, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: String, forKey key: Key) throws { + self.encoder.setters.append(Expression(key.stringValue) <- value) + } + + func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { + if let data = value as? Data { + self.encoder.setters.append(Expression(key.stringValue) <- data) + } + else { + let encoded = try JSONEncoder().encode(value) + let string = String(data: encoded, encoding: .utf8) + self.encoder.setters.append(Expression(key.stringValue) <- string) + } + } + + func encode(_ value: Int8, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int8 is not supported") + } + + func encode(_ value: Int16, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int16 is not supported") + } + + func encode(_ value: Int32, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int32 is not supported") + } + + func encode(_ value: Int64, forKey key: Key) throws { + throw EncodingError(description: "encoding an Int64 is not supported") + } + + func encode(_ value: UInt, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt is not supported") + } + + func encode(_ value: UInt8, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt8 is not supported") + } + + func encode(_ value: UInt16, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt16 is not supported") + } + + func encode(_ value: UInt32, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt32 is not supported") + } + + func encode(_ value: UInt64, forKey key: Key) throws { + throw EncodingError(description: "encoding an UInt64 is not supported") + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + fatalError("encoding a nested container is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + fatalError("encoding nested values is not supported") + } + } + + fileprivate var setters: [SQLite.Setter] = [] + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(userInfo: [CodingUserInfoKey: Any]) { + self.userInfo = userInfo + } + + func singleValueContainer() -> SingleValueEncodingContainer { + fatalError("not supported") + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + fatalError("not supported") + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + } +} + +fileprivate class SQLiteDecoder : Decoder { + struct DecodingError : Error, CustomStringConvertible { + let description: String + } + + class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { + typealias Key = MyKey + + let codingPath: [CodingKey] = [] + let row: Row + + init(row: Row) { + self.row = row + } + + var allKeys: [Key] { + return self.row.columnNames.keys.flatMap({Key(stringValue: $0)}) + } + + func contains(_ key: Key) -> Bool { + return self.row.hasValue(for: key.stringValue) + } + + func decodeNil(forKey key: Key) throws -> Bool { + return !self.contains(key) + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { + throw DecodingError(description: "decoding an Int8 is not supported") + } + + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { + throw DecodingError(description: "decoding an Int16 is not supported") + } + + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { + throw DecodingError(description: "decoding an Int32 is not supported") + } + + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { + throw DecodingError(description: "decoding an Int64 is not supported") + } + + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { + throw DecodingError(description: "decoding an UInt is not supported") + } + + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { + throw DecodingError(description: "decoding an UInt8 is not supported") + } + + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { + throw DecodingError(description: "decoding an UInt16 is not supported") + } + + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { + throw DecodingError(description: "decoding an UInt32 is not supported") + } + + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { + throw DecodingError(description: "decoding an UInt64 is not supported") + } + + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { + return Float(try self.row.get(Expression(key.stringValue))) + } + + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: String.Type, forKey key: Key) throws -> String { + return try self.row.get(Expression(key.stringValue)) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { + if type == Data.self { + let data = try self.row.get(Expression(key.stringValue)) + return data as! T + } + guard let JSONString = try self.row.get(Expression(key.stringValue)) else { + throw DecodingError(description: "an unsupported type was found") + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError(description: "invalid utf8 data found") + } + return try JSONDecoder().decode(type, from: data) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + throw DecodingError(description: "decoding nested containers is not supported") + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding unkeyed containers is not supported") + } + + func superDecoder() throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + + func superDecoder(forKey key: Key) throws -> Swift.Decoder { + throw DecodingError(description: "decoding super encoders is not supported") + } + } + + let row: Row + let codingPath: [CodingKey] = [] + let userInfo: [CodingUserInfoKey: Any] + + init(row: Row, userInfo: [CodingUserInfoKey: Any]) { + self.row = row + self.userInfo = userInfo + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") + } +} + diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 0b257fbe..5b778431 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -673,26 +673,6 @@ extension QueryType { ]).expression) } - /// Creates an `INSERT` statement by encoding the given object - /// This method converts any custom nested types to JSON data and does not handle any sort - /// of object relationships. If you want to support relationships between objects you will - /// have to provide your own Encodable implementations that encode the correct ids. - /// - /// - Parameters: - /// - /// - encodable: An encodable object to insert - /// - /// - userInfo: User info to be passed to encoder - /// - /// - otherSetters: Any other setters to include in the insert - /// - /// - Returns: An `INSERT` statement fort the encodable object - public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { - let encoder = SQLiteEncoder(userInfo: userInfo) - try encodable.encode(to: encoder) - return self.insert(encoder.setters + otherSetters) - } - // MARK: UPDATE public func update(_ values: Setter...) -> Update { @@ -713,26 +693,6 @@ extension QueryType { return Update(" ".join(clauses.flatMap { $0 }).expression) } - /// Creates an `UPDATE` statement by encoding the given object - /// This method converts any custom nested types to JSON data and does not handle any sort - /// of object relationships. If you want to support relationships between objects you will - /// have to provide your own Encodable implementations that encode the correct ids. - /// - /// - Parameters: - /// - /// - encodable: An encodable object to insert - /// - /// - userInfo: User info to be passed to encoder - /// - /// - otherSetters: Any other setters to include in the insert - /// - /// - Returns: An `UPDATE` statement fort the encodable object - public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { - let encoder = SQLiteEncoder(userInfo: userInfo) - try encodable.encode(to: encoder) - return self.update(encoder.setters + otherSetters) - } - // MARK: DELETE public func delete() -> Delete { @@ -1069,7 +1029,7 @@ extension Connection { public struct Row { - fileprivate let columnNames: [String: Int] + let columnNames: [String: Int] fileprivate let values: [Binding?] @@ -1078,7 +1038,7 @@ public struct Row { self.values = values } - fileprivate func hasValue(for column: String) -> Bool { + func hasValue(for column: String) -> Bool { guard let idx = columnNames[column.quote()] else { return false } @@ -1120,22 +1080,6 @@ public struct Row { return valueAtIndex(idx) } - /// Decode an object from this row - /// This method expects any custom nested types to be in the form of JSON data and does not handle - /// any sort of object relationships. If you want to support relationships between objects you will - /// have to provide your own Decodable implementations that decodes the correct columns. - /// - /// - Parameter: userInfo - /// - /// - Returns: a decoded object from this row - public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { - return try V(from: self.decoder(userInfo: userInfo)) - } - - public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { - return SQLiteDecoder(row: self, userInfo: userInfo) - } - public subscript(column: Expression) -> T { return try! get(column) } @@ -1200,263 +1144,3 @@ public struct QueryClauses { } -/// Generates a list of settings for an Encodable object -fileprivate class SQLiteEncoder: Encoder { - struct EncodingError: Error, CustomStringConvertible { - let description: String - } - - class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { - typealias Key = MyKey - - let encoder: SQLiteEncoder - let codingPath: [CodingKey] = [] - - init(encoder: SQLiteEncoder) { - self.encoder = encoder - } - - func superEncoder() -> Swift.Encoder { - fatalError("SQLiteEncoding does not support super encoders") - } - - func superEncoder(forKey key: Key) -> Swift.Encoder { - fatalError("SQLiteEncoding does not support super encoders") - } - - func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- nil) - } - - func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: Bool, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: Float, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) - } - - func encode(_ value: Double, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: String, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) - } - - func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { - if let data = value as? Data { - self.encoder.setters.append(Expression(key.stringValue) <- data) - } - else { - let encoded = try JSONEncoder().encode(value) - let string = String(data: encoded, encoding: .utf8) - self.encoder.setters.append(Expression(key.stringValue) <- string) - } - } - - func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int8 is not supported") - } - - func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int16 is not supported") - } - - func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int32 is not supported") - } - - func encode(_ value: Int64, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int64 is not supported") - } - - func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt is not supported") - } - - func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt8 is not supported") - } - - func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt16 is not supported") - } - - func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt32 is not supported") - } - - func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt64 is not supported") - } - - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { - fatalError("encoding a nested container is not supported") - } - - func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - fatalError("encoding nested values is not supported") - } - } - - fileprivate var setters: [SQLite.Setter] = [] - let codingPath: [CodingKey] = [] - let userInfo: [CodingUserInfoKey: Any] - - init(userInfo: [CodingUserInfoKey: Any]) { - self.userInfo = userInfo - } - - func singleValueContainer() -> SingleValueEncodingContainer { - fatalError("not supported") - } - - func unkeyedContainer() -> UnkeyedEncodingContainer { - fatalError("not supported") - } - - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { - return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) - } -} - -class SQLiteDecoder : Decoder { - struct DecodingError : Error, CustomStringConvertible { - let description: String - } - - class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { - typealias Key = MyKey - - let codingPath: [CodingKey] = [] - let row: Row - - init(row: Row) { - self.row = row - } - - var allKeys: [Key] { - return self.row.columnNames.keys.flatMap({Key(stringValue: $0)}) - } - - func contains(_ key: Key) -> Bool { - return self.row.hasValue(for: key.stringValue) - } - - func decodeNil(forKey key: Key) throws -> Bool { - return !self.contains(key) - } - - func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError(description: "decoding an Int8 is not supported") - } - - func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError(description: "decoding an Int16 is not supported") - } - - func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError(description: "decoding an Int32 is not supported") - } - - func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw DecodingError(description: "decoding an Int64 is not supported") - } - - func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError(description: "decoding an UInt is not supported") - } - - func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError(description: "decoding an UInt8 is not supported") - } - - func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError(description: "decoding an UInt16 is not supported") - } - - func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError(description: "decoding an UInt32 is not supported") - } - - func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError(description: "decoding an UInt64 is not supported") - } - - func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - return Float(try self.row.get(Expression(key.stringValue))) - } - - func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try self.row.get(Expression(key.stringValue)) - } - - func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { - if type == Data.self { - let data = try self.row.get(Expression(key.stringValue)) - return data as! T - } - guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError(description: "an unsupported type was found") - } - guard let data = JSONString.data(using: .utf8) else { - throw DecodingError(description: "invalid utf8 data found") - } - return try JSONDecoder().decode(type, from: data) - } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw DecodingError(description: "decoding nested containers is not supported") - } - - func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding unkeyed containers is not supported") - } - - func superDecoder() throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") - } - - func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") - } - } - - let row: Row - let codingPath: [CodingKey] = [] - let userInfo: [CodingUserInfoKey: Any] - - init(row: Row, userInfo: [CodingUserInfoKey: Any]) { - self.row = row - self.userInfo = userInfo - } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { - return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) - } - - func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") - } - - func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") - } -} From 6751ed293efb3b92e5f0e9369c440ffe55e40a34 Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 26 Sep 2017 20:34:13 -0600 Subject: [PATCH 0611/1046] Add documentation for coding functionality --- Documentation/Index.md | 90 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7d13b5a7..cf673d05 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -50,6 +50,7 @@ - [Date-Time Values](#date-time-values) - [Binary Data](#binary-data) - [Custom Type Caveats](#custom-type-caveats) + - [Codable Types](#codable-types) - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) @@ -72,7 +73,7 @@ [Carthage][] is a simple, decentralized dependency manager for Cocoa. To install SQLite.swift with Carthage: - +## Custom Types 1. Make sure Carthage is [installed][Carthage Installation]. 2. Update your Cartfile to include the following: @@ -1296,6 +1297,93 @@ extension Row { } ``` +## Codable Types + +Codable types were introduced as a part of Swift 4 to allow serializing and deserializing types. SQLite.swift +supports the insertion, updating, and retrieval of basic Codable types. + +### Inserting Codable Types + +Queries have a method to allow inserting an Encodable type. + +``` swift +try db.run(users.insert(user)) + +``` + +There are two other parameters also available to this method: + +- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. +- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. + +### Updating Codable Types + +Queries have a method to allow updating an Encodable type. + +``` swift +try db.run(users.update(user)) + +``` + +There are two other parameters also available to this method: + +- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. +- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. + +### Retrieving Codable Types + +Rows have a method to decode a Decodable type. + +``` swift +let loadedUsers: [User] = try db.prepare(users).map { row in + return try row.decode() +} +``` + +You can also create a decoder to use manually yourself. This can be useful for example if you are using +the [Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide subclasses behind a super class. +For example, you may want to encode an Image type that can be multiple different formats such as PNGImage, JPGImage, +or HEIFIamge. You will need to determine the correct subclass before you know which type to decode. + +``` swift +enum ImageCodingKeys: String, CodingKey { + case kind +} + +enum ImageKind: Int, Codable { + case png, jpg, heif +} + +let loadedImages: [Image] = try db.prepare(images).map { row in + let decoder = row.decoder() + let container = try decoder.container(keyedBy: ImageCodingKeys.self) + switch try container.decode(ImageKind.self, forKey: .kind) { + case .png: + return try PNGImage(from: decoder) + case .jpg: + return try JPGImage(from: decoder) + case .heif: + return try HEIFImage(from: decoder) + } +} +``` + +Both of the above methods also have the following optional parameter: + +- `userInfo` is a dictionary that is passed to the decoder and made available to decodable types to allow customizing their behavior. + +### Restrictions + +There are a few restrictions on using Codable types: + +- The encodable and decodable objects can only use the following types: + - Int, Bool, Float, Double, String + - Nested Codable types that will be encoded as JSON to a single column +- These methods will not handle object relationships for you. You must write your own Codable and Decodable +implementations if you wish to support this. +- The Codable types may not try to access nested containers or nested unkeyed containers +- The Codable types may not access single value containers or unkeyed containers +- The Codable types may not access super decoders or encoders ## Other Operators From c614d80b5eca8c29b64f8e64405397361b17f08a Mon Sep 17 00:00:00 2001 From: Andrew J Wagner Date: Tue, 26 Sep 2017 22:36:58 -0600 Subject: [PATCH 0612/1046] Fix CI --- Sources/SQLite/Typed/Coding.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 68f0a998..1e049666 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -22,6 +22,8 @@ // THE SOFTWARE. // +import Foundation + extension QueryType { /// Creates an `INSERT` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort From 7cb407d43d7446740ef2cad8716c74d8525f1594 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Sep 2017 12:45:11 +0200 Subject: [PATCH 0613/1046] use PackageDescription API Version 4 --- .travis.yml | 2 +- Documentation/Index.md | 6 +++--- Package.swift | 18 +++++++----------- README.md | 4 ++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62f36185..7ff85f9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: - env: CARTHAGE_PLATFORM="Mac" - env: CARTHAGE_PLATFORM="watchOS" - env: CARTHAGE_PLATFORM="tvOS" - - env: PACKAGE_MANAGER_COMMAND="test -Xlinker -lsqlite3" + - env: PACKAGE_MANAGER_COMMAND="test" before_install: - gem update bundler - gem install xcpretty --no-document diff --git a/Documentation/Index.md b/Documentation/Index.md index 7d13b5a7..42c247b5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -172,16 +172,16 @@ It is the recommended approach for using SQLite.swift in OSX CLI applications. 1. Add the following to your `Package.swift` file: - ``` swift + ```swift dependencies: [ - .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") ] ``` 2. Build your project: ``` sh - $ swift build -Xlinker -lsqlite3 + $ swift build ``` [Swift Package Manager]: https://swift.org/package-manager diff --git a/Package.swift b/Package.swift index fc7c5a0e..bab00031 100644 --- a/Package.swift +++ b/Package.swift @@ -1,17 +1,13 @@ +// swift-tools-version:4.0 import PackageDescription let package = Package( - name: "SQLite", + name: "SQLite.swift", + products: [.library(name: "SQLite", targets: ["SQLite"])], targets: [ - Target( - name: "SQLite", - dependencies: [ - .Target(name: "SQLiteObjc") - ]), - Target(name: "SQLiteObjc") + .target(name: "SQLite", dependencies: ["SQLiteObjc"]), + .target(name: "SQLiteObjc"), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - dependencies: [ - .Package(url: "https://github.com/stephencelis/CSQLite.git", majorVersion: 0) - ], - exclude: ["Tests/CocoaPods", "Tests/Carthage"] + swiftLanguageVersions: [4] ) diff --git a/README.md b/README.md index fe06c6f7..a10f2c09 100644 --- a/README.md +++ b/README.md @@ -168,14 +168,14 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift c ```swift dependencies: [ - .Package(url: "https://github.com/stephencelis/SQLite.swift.git", majorVersion: 0, minor: 11) + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") ] ``` 2. Build your project: ``` sh - $ swift build -Xlinker -lsqlite3 + $ swift build ``` [Swift Package Manager]: https://swift.org/package-manager From 7d3714deb0dbaed58ca718d7d1d8dc137f725045 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Sep 2017 14:47:52 +0200 Subject: [PATCH 0614/1046] Workaround no longer needed --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Sources/SQLiteObjc/SQLite-Bridging.m | 4 ++-- Sources/SQLiteObjc/include/SQLite-Bridging.h | 8 ++------ Tests/SQLiteTests/ConnectionTests.swift | 2 +- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 0189d300..13e62fa1 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1e48d3e8..0ad5cd45 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 52b158b1..4748ee53 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif diff --git a/Sources/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLite-Bridging.m index d8fe6b68..e00a7315 100644 --- a/Sources/SQLiteObjc/SQLite-Bridging.m +++ b/Sources/SQLiteObjc/SQLite-Bridging.m @@ -112,14 +112,14 @@ static int __SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char __SQLiteTokenizerNext }; -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) { +int _SQLiteRegisterTokenizer(sqlite3 *db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ __SQLiteTokenizerMap = [NSMutableDictionary new]; }); sqlite3_stmt * stmt; - int status = sqlite3_prepare_v2((sqlite3 *)db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); + int status = sqlite3_prepare_v2(db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); if (status != SQLITE_OK ){ return status; } diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLite-Bridging.h index d15e8d56..5b356593 100644 --- a/Sources/SQLiteObjc/include/SQLite-Bridging.h +++ b/Sources/SQLiteObjc/include/SQLite-Bridging.h @@ -24,14 +24,10 @@ @import Foundation; -#ifndef COCOAPODS #import "sqlite3.h" -#endif - -typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround NS_ASSUME_NONNULL_BEGIN -typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); +typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); +int _SQLiteRegisterTokenizer(sqlite3 *db, const char *module, const char *tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); NS_ASSUME_NONNULL_END diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 373cba0a..84620f4e 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -5,7 +5,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE || COCOAPODS +#elseif SWIFT_PACKAGE import SQLite3 #endif From bc005192d93eb92dc5ff6a7fd6cccc7ff025c2a6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Sep 2017 15:36:50 +0200 Subject: [PATCH 0615/1046] Linux support --- CHANGELOG.md | 3 +++ Package.swift | 11 +++++++++++ Sources/SQLite/Core/Connection.swift | 12 ++++++++---- Sources/SQLite/Core/Statement.swift | 4 +++- Sources/SQLite/Helpers.swift | 4 +++- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- Tests/LinuxMain.swift | 6 ++++++ Tests/SQLiteTests/ConnectionTests.swift | 8 ++++++-- Tests/SQLiteTests/TestHelpers.swift | 2 +- 9 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 Tests/LinuxMain.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fa68dfe..bbb5d671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) @@ -46,6 +47,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 +[#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 [#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 @@ -62,6 +64,7 @@ [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 +[#681]: https://github.com/stephencelis/SQLite.swift/issues/681 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 diff --git a/Package.swift b/Package.swift index bab00031..c008e482 100644 --- a/Package.swift +++ b/Package.swift @@ -11,3 +11,14 @@ let package = Package( ], swiftLanguageVersions: [4] ) + +#if os(Linux) + package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] + package.targets = [ + .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ + "FTS4Tests.swift", + "FTS5Tests.swift" + ]) + ] +#endif diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 13e62fa1..a3b2f362 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -22,13 +22,15 @@ // THE SOFTWARE. // -import Foundation.NSUUID +import Foundation import Dispatch #if SQLITE_SWIFT_STANDALONE import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif @@ -413,7 +415,7 @@ public final class Connection { /// /// db.trace { SQL in print(SQL) } public func trace(_ callback: ((String) -> Void)?) { - #if SQLITE_SWIFT_SQLCIPHER + #if SQLITE_SWIFT_SQLCIPHER || os(Linux) trace_v1(callback) #else if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { @@ -583,9 +585,11 @@ public final class Connection { } } var flags = SQLITE_UTF8 + #if !os(Linux) if deterministic { flags |= SQLITE_DETERMINISTIC } + #endif sqlite3_create_function_v2(handle, function, Int32(argc), flags, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) function(context, argc, value) @@ -702,7 +706,7 @@ extension Result : CustomStringConvertible { } } -#if !SQLITE_SWIFT_SQLCIPHER +#if !SQLITE_SWIFT_SQLCIPHER && !os(Linux) @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) extension Connection { fileprivate func trace_v2(_ callback: ((String) -> Void)?) { diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 0ad5cd45..dc91d3d8 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,9 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 4748ee53..64e5eca8 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,9 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 9d17a326..be260ec8 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -import Foundation.NSData +import Foundation extension ExpressionType where UnderlyingType : Number { diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 00000000..59796fde --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,6 @@ +import XCTest +@testable import SQLiteTests + +XCTMain([ +testCase([ +])]) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 84620f4e..157fbe9f 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -1,11 +1,15 @@ import XCTest +import Foundation +import Dispatch @testable import SQLite #if SQLITE_SWIFT_STANDALONE import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif SWIFT_PACKAGE +#elseif os(Linux) +import CSQLite +#else import SQLite3 #endif @@ -336,7 +340,7 @@ class ConnectionTests : SQLiteTestCase { let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) try! stmt.run() - let deadline = DispatchTime.now() + Double(Int64(10 * NSEC_PER_MSEC)) / Double(NSEC_PER_SEC) + let deadline = DispatchTime.now() + 0.01 _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) AssertThrows(try stmt.run()) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index b66144b3..2ebbc56e 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -69,7 +69,7 @@ class SQLiteTestCase : XCTestCase { func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) -> Void) { let expectation = self.expectation(description: description) - block(expectation.fulfill) + block({ expectation.fulfill() }) waitForExpectations(timeout: timeout, handler: nil) } From 112bc6fbc28e4db26afcd63b1c36e979d8f9e198 Mon Sep 17 00:00:00 2001 From: Andrew Wagner Date: Wed, 27 Sep 2017 14:05:47 -0600 Subject: [PATCH 0616/1046] Use native decoding and encoding errors --- Documentation/Index.md | 1 - Sources/SQLite/Typed/Coding.swift | 61 ++++++++++++++----------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index cf673d05..a1370cd5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -73,7 +73,6 @@ [Carthage][] is a simple, decentralized dependency manager for Cocoa. To install SQLite.swift with Carthage: -## Custom Types 1. Make sure Carthage is [installed][Carthage Installation]. 2. Update your Cartfile to include the following: diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 1e049666..dd6a4ec2 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -86,10 +86,6 @@ extension Row { /// Generates a list of settings for an Encodable object fileprivate class SQLiteEncoder: Encoder { - struct EncodingError: Error, CustomStringConvertible { - let description: String - } - class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { typealias Key = MyKey @@ -144,39 +140,39 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int8 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int8 is not supported")) } func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int16 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int16 is not supported")) } func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int32 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int32 is not supported")) } func encode(_ value: Int64, forKey key: Key) throws { - throw EncodingError(description: "encoding an Int64 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int64 is not supported")) } func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt is not supported")) } func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt8 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt8 is not supported")) } func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt16 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt16 is not supported")) } func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt32 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt32 is not supported")) } func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError(description: "encoding an UInt64 is not supported") + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt64 is not supported")) } func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { @@ -210,10 +206,6 @@ fileprivate class SQLiteEncoder: Encoder { } fileprivate class SQLiteDecoder : Decoder { - struct DecodingError : Error, CustomStringConvertible { - let description: String - } - class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { typealias Key = MyKey @@ -245,39 +237,40 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError(description: "decoding an Int8 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int8 is not supported")) } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError(description: "decoding an Int16 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int16 is not supported")) } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError(description: "decoding an Int32 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int32 is not supported")) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw DecodingError(description: "decoding an Int64 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError(description: "decoding an UInt is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt is not supported")) + } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError(description: "decoding an UInt8 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt8 is not supported")) } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError(description: "decoding an UInt16 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt16 is not supported")) } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError(description: "decoding an UInt32 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt32 is not supported")) } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError(description: "decoding an UInt64 is not supported") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { @@ -298,28 +291,28 @@ fileprivate class SQLiteDecoder : Decoder { return data as! T } guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError(description: "an unsupported type was found") + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "an unsupported type was found")) } guard let data = JSONString.data(using: .utf8) else { - throw DecodingError(description: "invalid utf8 data found") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "invalid utf8 data found")) } return try JSONDecoder().decode(type, from: data) } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw DecodingError(description: "decoding nested containers is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding nested containers is not supported")) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding unkeyed containers is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding unkeyed containers is not supported")) } func superDecoder() throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super encoders containers is not supported")) } func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError(description: "decoding super encoders is not supported") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super decoders is not supported")) } } @@ -337,11 +330,11 @@ fileprivate class SQLiteDecoder : Decoder { } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an unkeyed container is not supported")) } func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder") + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding a single value container is not supported")) } } From 364a240a6217d4d1b6fd7c237a3cf9039cbad6ec Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 02:09:47 +0200 Subject: [PATCH 0617/1046] Add date and time functions Closes #142 --- CHANGELOG.md | 2 + Documentation/Index.md | 13 +++ SQLite.xcodeproj/project.pbxproj | 18 +++ .../SQLite/Typed/DateAndTimeFunctions.swift | 106 ++++++++++++++++++ Sources/SQLite/Typed/Operators.swift | 17 ++- .../DateAndTimeFunctionTests.swift | 66 +++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 31 +++++ 7 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 Sources/SQLite/Typed/DateAndTimeFunctions.swift create mode 100644 Tests/SQLiteTests/DateAndTimeFunctionTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb5d671..e36e40d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Added Date and Time functions ([#142][]) * Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) @@ -47,6 +48,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 +[#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142 [#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 diff --git a/Documentation/Index.md b/Documentation/Index.md index eb5cbc53..9a6d07ad 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -54,6 +54,7 @@ - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) + - [Date and Time Functions](#date-and-time-functions) - [Custom SQL Functions](#custom-sql-functions) - [Custom Collations](#custom-collations) - [Full-text Search](#full-text-search) @@ -1430,6 +1431,18 @@ Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) h Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. +## Date and Time functions + +SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) are available: + +```swift +DateFunctions.date("now") +// date('now') +Date().date +// date('2007-01-09T09:41:00.000') +Expression("date").date +// date("date") +``` ## Custom SQL Functions diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3093b11d..cb776608 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,17 +46,22 @@ 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -66,8 +71,10 @@ 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; @@ -209,12 +216,14 @@ 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; + 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; @@ -408,6 +417,7 @@ 19A17399EA9E61235D5D77BF /* CipherTests.swift */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, + 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -452,6 +462,7 @@ EE247B011C3F06E900AE3E12 /* Schema.swift */, EE247B021C3F06E900AE3E12 /* Setter.swift */, 49EB68C31F7B3CB400D89D40 /* Coding.swift */, + 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */, ); path = Typed; sourceTree = ""; @@ -806,6 +817,7 @@ 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, + 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -833,6 +845,7 @@ 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, + 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -862,6 +875,7 @@ 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, + 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -891,6 +905,7 @@ 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, + 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -918,6 +933,7 @@ 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, + 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -947,6 +963,7 @@ 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, + 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -974,6 +991,7 @@ 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, + 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Typed/DateAndTimeFunctions.swift b/Sources/SQLite/Typed/DateAndTimeFunctions.swift new file mode 100644 index 00000000..0b9a497f --- /dev/null +++ b/Sources/SQLite/Typed/DateAndTimeFunctions.swift @@ -0,0 +1,106 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +/// All five date and time functions take a time string as an argument. +/// The time string is followed by zero or more modifiers. +/// The strftime() function also takes a format string as its first argument. +/// +/// https://www.sqlite.org/lang_datefunc.html +public class DateFunctions { + /// The date() function returns the date in this format: YYYY-MM-DD. + public static func date(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("date", timestring: timestring, modifiers: modifiers) + } + + /// The time() function returns the time as HH:MM:SS. + public static func time(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("time", timestring: timestring, modifiers: modifiers) + } + + /// The datetime() function returns "YYYY-MM-DD HH:MM:SS". + public static func datetime(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("datetime", timestring: timestring, modifiers: modifiers) + } + + /// The julianday() function returns the Julian day - + /// the number of days since noon in Greenwich on November 24, 4714 B.C. + public static func julianday(_ timestring: String, _ modifiers: String...) -> Expression { + return timefunction("julianday", timestring: timestring, modifiers: modifiers) + } + + /// The strftime() routine returns the date formatted according to the format string specified as the first argument. + public static func strftime(_ format: String, _ timestring: String, _ modifiers: String...) -> Expression { + if !modifiers.isEmpty { + let templates = [String](repeating: "?", count: modifiers.count).joined(separator: ", ") + return Expression("strftime(?, ?, \(templates))", [format, timestring] + modifiers) + } + return Expression("strftime(?, ?)", [format, timestring]) + } + + private static func timefunction(_ name: String, timestring: String, modifiers: [String]) -> Expression { + if !modifiers.isEmpty { + let templates = [String](repeating: "?", count: modifiers.count).joined(separator: ", ") + return Expression("\(name)(?, \(templates))", [timestring] + modifiers) + } + return Expression("\(name)(?)", [timestring]) + } +} + +extension Date { + public var date: Expression { + return DateFunctions.date(dateFormatter.string(from: self)) + } + + public var time: Expression { + return DateFunctions.time(dateFormatter.string(from: self)) + } + + public var datetime: Expression { + return DateFunctions.datetime(dateFormatter.string(from: self)) + } + + public var julianday: Expression { + return DateFunctions.julianday(dateFormatter.string(from: self)) + } +} + +extension Expression where UnderlyingType == Date { + public var date: Expression { + return Expression("date(\(template))", bindings) + } + + public var time: Expression { + return Expression("time(\(template))", bindings) + } + + public var datetime: Expression { + return Expression("datetime(\(template))", bindings) + } + + public var julianday: Expression { + return Expression("julianday(\(template))", bindings) + } +} diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 01381bb8..5f95993f 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -474,11 +474,20 @@ public func <=(lhs: V, rhs: Expression) -> Expression wher return infix(lhs, rhs) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound as? Binding, lhs.upperBound as? Binding]) + +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } // MARK: - diff --git a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift new file mode 100644 index 00000000..628b5910 --- /dev/null +++ b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift @@ -0,0 +1,66 @@ +import XCTest +@testable import SQLite + +class DateAndTimeFunctionsTests : XCTestCase { + + func test_date() { + AssertSQL("date('now')", DateFunctions.date("now")) + AssertSQL("date('now', 'localtime')", DateFunctions.date("now", "localtime")) + } + + func test_time() { + AssertSQL("time('now')", DateFunctions.time("now")) + AssertSQL("time('now', 'localtime')", DateFunctions.time("now", "localtime")) + } + + func test_datetime() { + AssertSQL("datetime('now')", DateFunctions.datetime("now")) + AssertSQL("datetime('now', 'localtime')", DateFunctions.datetime("now", "localtime")) + } + + func test_julianday() { + AssertSQL("julianday('now')", DateFunctions.julianday("now")) + AssertSQL("julianday('now', 'localtime')", DateFunctions.julianday("now", "localtime")) + } + + func test_strftime() { + AssertSQL("strftime('%Y-%m-%d', 'now')", DateFunctions.strftime("%Y-%m-%d", "now")) + AssertSQL("strftime('%Y-%m-%d', 'now', 'localtime')", DateFunctions.strftime("%Y-%m-%d", "now", "localtime")) + } +} + +class DateExtensionTests : XCTestCase { + func test_time() { + AssertSQL("time('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).time) + } + + func test_date() { + AssertSQL("date('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).date) + } + + func test_datetime() { + AssertSQL("datetime('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).datetime) + } + + func test_julianday() { + AssertSQL("julianday('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).julianday) + } +} + +class DateExpressionTests : XCTestCase { + func test_date() { + AssertSQL("date(\"date\")", date.date) + } + + func test_time() { + AssertSQL("time(\"date\")", date.time) + } + + func test_datetime() { + AssertSQL("datetime(\"date\")", date.datetime) + } + + func test_julianday() { + AssertSQL("julianday(\"date\")", date.julianday) + } +} diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index f0e585cd..08e679c1 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -255,6 +255,11 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) } + func test_patternMatchingOperator_withComparableRange_buildsBooleanExpression() { + AssertSQL("\"double\" >= 1.2 AND \"double\" < 4.5", 1.2..<4.5 ~= double) + AssertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) + } + func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) @@ -293,4 +298,30 @@ class OperatorsTests : XCTestCase { AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } + func test_dateExpressionLessGreater() { + let begin = Date(timeIntervalSince1970: 0) + AssertSQL("(\"date\" < '1970-01-01T00:00:00.000')", date < begin) + AssertSQL("(\"date\" > '1970-01-01T00:00:00.000')", date > begin) + AssertSQL("(\"date\" >= '1970-01-01T00:00:00.000')", date >= begin) + AssertSQL("(\"date\" <= '1970-01-01T00:00:00.000')", date <= begin) + } + + func test_dateExpressionRange() { + let begin = Date(timeIntervalSince1970: 0) + let end = Date(timeIntervalSince1970: 5000) + AssertSQL( + "\"date\" >= '1970-01-01T00:00:00.000' AND \"date\" < '1970-01-01T01:23:20.000'", + (begin.. Date: Thu, 28 Sep 2017 08:36:31 +0200 Subject: [PATCH 0618/1046] Reflow documentation --- Documentation/Index.md | 849 +++++++++++++++++++++++++++-------------- README.md | 47 ++- 2 files changed, 590 insertions(+), 306 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9a6d07ad..9657a94a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,7 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 3 (and [Xcode 8](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 4 (and +> [Xcode 9](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -78,7 +79,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: - ``` + ```ruby github "stephencelis/SQLite.swift" ~> 0.11.4 ``` @@ -94,9 +95,10 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift requires version 1.0.0 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift + requires version 1.0.0 or greater). - ``` sh + ```sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. [sudo] gem install cocoapods @@ -104,7 +106,7 @@ install SQLite.swift with Carthage: 2. Update your Podfile to include the following: - ``` ruby + ```ruby use_frameworks! target 'YourAppTargetName' do @@ -117,17 +119,20 @@ install SQLite.swift with Carthage: #### Requiring a specific version of SQLite - If you want to use a more recent version of SQLite than what is provided with the OS you can require the `standalone` subspec: +If you want to use a more recent version of SQLite than what is provided +with the OS you can require the `standalone` subspec: -``` ruby +```ruby target 'YourAppTargetName' do pod 'SQLite.swift/standalone', '~> 0.11.4' end ``` -By default this will use the most recent version of SQLite without any extras. If you want you can further customize this by adding another dependency to sqlite3 or one of its subspecs: +By default this will use the most recent version of SQLite without any +extras. If you want you can further customize this by adding another +dependency to sqlite3 or one of its subspecs: -``` ruby +```ruby target 'YourAppTargetName' do pod 'SQLite.swift/standalone', '~> 0.11.4' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled @@ -138,19 +143,19 @@ See the [sqlite3 podspec][sqlite3pod] for more details. #### Using SQLite.swift with SQLCipher -If you want to use [SQLCipher][] with SQLite.swift you can require the `SQLCipher` -subspec in your Podfile: +If you want to use [SQLCipher][] with SQLite.swift you can require the +`SQLCipher` subspec in your Podfile: -``` ruby +```ruby target 'YourAppTargetName' do pod 'SQLite.swift/SQLCipher', '~> 0.11.4' end ``` -This will automatically add a dependency to the SQLCipher pod as well as extend -`Connection` with methods to change the database key: +This will automatically add a dependency to the SQLCipher pod as well as +extend `Connection` with methods to change the database key: -``` swift +```swift import SQLite let db = try Connection("path/to/db.sqlite3") @@ -165,11 +170,12 @@ try db.rekey("another secret") ### Swift Package Manager -The [Swift Package Manager][] is a tool for managing the distribution of Swift code. -It’s integrated with the Swift build system to automate the process of -downloading, compiling, and linking dependencies. +The [Swift Package Manager][] is a tool for managing the distribution of +Swift code. It’s integrated with the Swift build system to automate the +process of downloading, compiling, and linking dependencies. -It is the recommended approach for using SQLite.swift in OSX CLI applications. +It is the recommended approach for using SQLite.swift in OSX CLI +applications. 1. Add the following to your `Package.swift` file: @@ -181,7 +187,7 @@ It is the recommended approach for using SQLite.swift in OSX CLI applications. 2. Build your project: - ``` sh + ```sh $ swift build ``` @@ -191,21 +197,28 @@ It is the recommended approach for using SQLite.swift in OSX CLI applications. To install SQLite.swift as an Xcode sub-project: - 1. Drag the **SQLite.xcodeproj** file into your own project. ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) the project first.) + 1. Drag the **SQLite.xcodeproj** file into your own project. + ([Submodule](http://git-scm.com/book/en/Git-Tools-Submodules), clone, or + [download](https://github.com/stephencelis/SQLite.swift/archive/master.zip) + the project first.) ![Installation Screen Shot](Resources/installation@2x.png) - 2. In your target’s **General** tab, click the **+** button under **Linked Frameworks and Libraries**. + 2. In your target’s **General** tab, click the **+** button under **Linked + Frameworks and Libraries**. 3. Select the appropriate **SQLite.framework** for your platform. 4. **Add**. -You should now be able to `import SQLite` from any of your target’s source files and begin using SQLite.swift. +You should now be able to `import SQLite` from any of your target’s source +files and begin using SQLite.swift. -Some additional steps are required to install the application on an actual device: +Some additional steps are required to install the application on an actual +device: - 5. In the **General** tab, click the **+** button under **Embedded Binaries**. + 5. In the **General** tab, click the **+** button under **Embedded + Binaries**. 6. Select the appropriate **SQLite.framework** for your platform. @@ -213,27 +226,31 @@ Some additional steps are required to install the application on an actual devic ## Getting Started -To use SQLite.swift classes or structures in your target’s source file, first import the `SQLite` module. +To use SQLite.swift classes or structures in your target’s source file, first +import the `SQLite` module. -``` swift +```swift import SQLite ``` ### Connecting to a Database -Database connections are established using the `Connection` class. A connection is initialized with a path to a database. SQLite will attempt to create the database file if it does not already exist. +Database connections are established using the `Connection` class. A +connection is initialized with a path to a database. SQLite will attempt to +create the database file if it does not already exist. -``` swift +```swift let db = try Connection("path/to/db.sqlite3") ``` #### Read-Write Databases -On iOS, you can create a writable database in your app’s **Documents** directory. +On iOS, you can create a writable database in your app’s **Documents** +directory. -``` swift +```swift let path = NSSearchPathForDirectoriesInDomains( .documentDirectory, .userDomainMask, true ).first! @@ -243,7 +260,7 @@ let db = try Connection("\(path)/db.sqlite3") On OS X, you can use your app’s **Application Support** directory: -``` swift +```swift var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true ).first! + "/" + Bundle.main.bundleIdentifier! @@ -259,38 +276,53 @@ let db = try Connection("\(path)/db.sqlite3") #### Read-Only Databases -If you bundle a database with your app (_i.e._, you’ve copied a database file into your Xcode project and added it to your application target), you can establish a _read-only_ connection to it. +If you bundle a database with your app (_i.e._, you’ve copied a database file +into your Xcode project and added it to your application target), you can +establish a _read-only_ connection to it. -``` swift +```swift let path = Bundle.main.pathForResource("db", ofType: "sqlite3")! let db = try Connection(path, readonly: true) ``` -> _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). +> _Note:_ Signed applications cannot modify their bundle resources. If you +> bundle a database file with your app for the purpose of bootstrapping, copy +> it to a writable location _before_ establishing a connection (see [Read- +> Write Databases](#read-write-databases), above, for typical, writable +> locations). > -> See these two Stack Overflow questions for more information about iOS apps with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). We welcome sample code to show how to successfully copy and use a bundled "seed" database for writing in an app. +> See these two Stack Overflow questions for more information about iOS apps +> with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), +> [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). +> We welcome sample code to show how to successfully copy and use a bundled "seed" +> database for writing in an app. #### In-Memory Databases -If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). +If you omit the path, SQLite.swift will provision an [in-memory +database](https://www.sqlite.org/inmemorydb.html). -``` swift +```swift let db = try Connection() // equivalent to `Connection(.inMemory)` ``` To create a temporary, disk-backed database, pass an empty file name. -``` swift +```swift let db = try Connection(.temporary) ``` -In-memory databases are automatically deleted when the database connection is closed. +In-memory databases are automatically deleted when the database connection is +closed. #### Thread-Safety -Every Connection comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. +Every Connection comes equipped with its own serial queue for statement +execution and can be safely accessed across threads. Threads that open +transactions and savepoints will block other threads from executing +statements while the transaction is open. If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: @@ -305,12 +337,16 @@ db.busyHandler({ tries in }) ``` -> _Note:_ The default timeout is 0, so if you see `database is locked` errors, you may be trying to access the same database simultaneously from multiple connections. +> _Note:_ The default timeout is 0, so if you see `database is locked` +> errors, you may be trying to access the same database simultaneously from +> multiple connections. ## Building Type-Safe SQL -SQLite.swift comes with a typed expression layer that directly maps [Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). +SQLite.swift comes with a typed expression layer that directly maps +[Swift types](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/) +to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). | Swift Type | SQLite Type | | --------------- | ----------- | @@ -320,22 +356,32 @@ SQLite.swift comes with a typed expression layer that directly maps [Swift types | `nil` | `NULL` | | `SQLite.Blob`† | `BLOB` | -> *While `Int64` is the basic, raw type (to preserve 64-bit integers on 32-bit platforms), `Int` and `Bool` work transparently. +> *While `Int64` is the basic, raw type (to preserve 64-bit integers on +> 32-bit platforms), `Int` and `Bool` work transparently. > -> †SQLite.swift defines its own `Blob` structure, which safely wraps the underlying bytes. +> †SQLite.swift defines its own `Blob` structure, which safely wraps the +> underlying bytes. > -> See [Custom Types](#custom-types) for more information about extending other classes and structures to work with SQLite.swift. +> See [Custom Types](#custom-types) for more information about extending +> other classes and structures to work with SQLite.swift. > -> See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed layer and execute raw SQL, instead. +> See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed +> layer and execute raw SQL, instead. -These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`QueryType`](#queries)), can create and execute SQL statements. +These expressions (in the form of the structure, +[`Expression`](#expressions)) build on one another and, with a query +([`QueryType`](#queries)), can create and execute SQL statements. ### Expressions -Expressions are generic structures associated with a type ([built-in](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and (optionally) values to bind to that SQL. Typically, you will only explicitly create expressions to describe your columns, and typically only once per column. +Expressions are generic structures associated with a type ([built-in +](#building-type-safe-sql) or [custom](#custom-types)), raw SQL, and +(optionally) values to bind to that SQL. Typically, you will only explicitly +create expressions to describe your columns, and typically only once per +column. -``` swift +```swift let id = Expression("id") let email = Expression("email") let balance = Expression("balance") @@ -344,34 +390,48 @@ let verified = Expression("verified") Use optional generics for expressions that can evaluate to `NULL`. -``` swift +```swift let name = Expression("name") ``` -> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. +> _Note:_ The default `Expression` initializer is for [quoted +> identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column +> names). To build a literal SQL expression, use `init(literal:)`. +> ### Compound Expressions -Expressions can be combined with other expressions and types using [filter operators and functions](#filter-operators-and-functions) (as well as other [non-filter operators](#other-operators) and [functions](#core-sqlite-functions)). These building blocks can create complex SQLite statements. +Expressions can be combined with other expressions and types using +[filter operators and functions](#filter-operators-and-functions) +(as well as other [non-filter operators](#other-operators) and +[functions](#core-sqlite-functions)). These building blocks can create complex SQLite statements. ### Queries -Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a query by initializing a `Table`, `View`, or `VirtualTable`. +Queries are structures that reference a database and table name, and can be +used to build a variety of statements using expressions. We can create a +query by initializing a `Table`, `View`, or `VirtualTable`. -``` swift +```swift let users = Table("users") ``` -Assuming [the table exists](#creating-a-table), we can immediately [insert](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and [delete](#deleting-rows) rows. +Assuming [the table exists](#creating-a-table), we can immediately [insert +](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and +[delete](#deleting-rows) rows. ## Creating a Table -We can build [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create` function on a `Table`. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. +We can build [`CREATE TABLE` +statements](https://www.sqlite.org/lang_createtable.html) by calling the +`create` function on a `Table`. The following is a basic example of +SQLite.swift code (using the [expressions](#expressions) and +[query](#queries) above) and the corresponding SQL it generates. -``` swift +```swift try db.run(users.create { t in // CREATE TABLE "users" ( t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL, t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL, @@ -379,34 +439,42 @@ try db.run(users.create { t in // CREATE TABLE "users" ( }) // ) ``` -> _Note:_ `Expression` structures (in this case, the `id` and `email` columns), generate `NOT NULL` constraints automatically, while `Expression` structures (`name`) do not. +> _Note:_ `Expression` structures (in this case, the `id` and `email` +> columns), generate `NOT NULL` constraints automatically, while +> `Expression` structures (`name`) do not. ### Create Table Options The `Table.create` function has several default parameters we can override. - - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to create a temporary table that will automatically drop when the database connection closes). Default: `false`. + - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to + create a temporary table that will automatically drop when the database + connection closes). Default: `false`. - ``` swift + ```swift try db.run(users.create(temporary: true) { t in /* ... */ }) // CREATE TEMPORARY TABLE "users" -- ... ``` - - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. + - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` + statement (which will bail out gracefully if the table already exists). + Default: `false`. - ``` swift + ```swift try db.run(users.create(ifNotExists: true) { t in /* ... */ }) // CREATE TABLE "users" IF NOT EXISTS -- ... ``` ### Column Constraints -The `column` function is used for a single column definition. It takes an [expression](#expressions) describing the column name and type, and accepts several parameters that map to various column constraints and clauses. +The `column` function is used for a single column definition. It takes an +[expression](#expressions) describing the column name and type, and accepts +several parameters that map to various column constraints and clauses. - `primaryKey` adds a `PRIMARY KEY` constraint to a single column. - ``` swift + ```swift t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL @@ -414,38 +482,59 @@ The `column` function is used for a single column definition. It takes an [expre // "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ``` - > _Note:_ The `primaryKey` parameter cannot be used alongside `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `primaryKey` parameter cannot be used alongside + > `references`. If you need to create a column that has a default value + > and is also a primary and/or foreign key, use the `primaryKey` and + > `foreignKey` functions mentioned under + > [Table Constraints](#table-constraints). > > Primary keys cannot be optional (_e.g._, `Expression`). > > Only an `INTEGER PRIMARY KEY` can take `.autoincrement`. - - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` function under [Table Constraints](#table-constraints) for uniqueness over multiple columns). + - `unique` adds a `UNIQUE` constraint to the column. (See the `unique` + function under [Table Constraints](#table-constraints) for uniqueness + over multiple columns). - ``` swift + ```swift t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL ``` - - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).) + - `check` attaches a `CHECK` constraint to a column definition in the form + of a boolean expression (`Expression`). Boolean expressions can be + easily built using + [filter operators and functions](#filter-operators-and-functions). + (See also the `check` function under + [Table Constraints](#table-constraints).) - ``` swift + ```swift t.column(email, check: email.like("%@%")) // "email" TEXT NOT NULL CHECK ("email" LIKE '%@%') ``` - - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). + - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ + accepts a value (or expression) matching the column’s type. This value is + used if none is explicitly provided during [an `INSERT`](#inserting- + rows). - ``` swift + ```swift t.column(name, defaultValue: "Anonymous") // "name" TEXT DEFAULT 'Anonymous' ``` - > _Note:_ The `defaultValue` parameter cannot be used alongside `primaryKey` and `references`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `defaultValue` parameter cannot be used alongside + > `primaryKey` and `references`. If you need to create a column that has + > a default value and is also a primary and/or foreign key, use the + > `primaryKey` and `foreignKey` functions mentioned under + > [Table Constraints](#table-constraints). - - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. + - `collate` adds a `COLLATE` clause to `Expression` (and + `Expression`) column definitions with + [a collating sequence](https://www.sqlite.org/datatype3.html#collation) + defined in the `Collation` enumeration. - ``` swift + ```swift t.column(email, collate: .nocase) // "email" TEXT NOT NULL COLLATE "NOCASE" @@ -453,50 +542,66 @@ The `column` function is used for a single column definition. It takes an [expre // "name" TEXT COLLATE "RTRIM" ``` - - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`SchemaType`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Expression` (and + `Expression`) column definitions and accepts a table + (`SchemaType`) or namespaced column expression. (See the `foreignKey` + function under [Table Constraints](#table-constraints) for non-integer + foreign key support.) - ``` swift + ```swift t.column(user_id, references: users, id) // "user_id" INTEGER REFERENCES "users" ("id") - - - > _Note:_ The `references` parameter cannot be used alongside `primaryKey` and `defaultValue`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). + > _Note:_ The `references` parameter cannot be used alongside + > `primaryKey` and `defaultValue`. If you need to create a column that + > has a default value and is also a primary and/or foreign key, use the + > `primaryKey` and `foreignKey` functions mentioned under + > [Table Constraints](#table-constraints). ### Table Constraints -Additional constraints may be provided outside the scope of a single column using the following functions. +Additional constraints may be provided outside the scope of a single column +using the following functions. - - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports all SQLite types, [ascending and descending orders](#sorting-rows), and composite (multiple column) keys. + - `primaryKey` adds a `PRIMARY KEY` constraint to the table. Unlike [the + column constraint, above](#column-constraints), it supports all SQLite + types, [ascending and descending orders](#sorting-rows), and composite + (multiple column) keys. - ``` swift + ```swift t.primaryKey(email.asc, name) // PRIMARY KEY("email" ASC, "name") ``` - - `unique` adds a `UNIQUE` constraint to the table. Unlike [the column constraint, above](#column-constraints), it supports composite (multiple column) constraints. + - `unique` adds a `UNIQUE` constraint to the table. Unlike + [the column constraint, above](#column-constraints), it + supports composite (multiplecolumn) constraints. - ``` swift + ```swift t.unique(local, domain) // UNIQUE("local", "domain") ``` - - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` parameter under [Column Constraints](#column-constraints).) + - `check` adds a `CHECK` constraint to the table in the form of a boolean + expression (`Expression`). Boolean expressions can be easily built + using [filter operators and functions](#filter-operators-and-functions). + (See also the `check` parameter under [Column Constraints](#column- + constraints).) - ``` swift + ```swift t.check(balance >= 0) // CHECK ("balance" >= 0.0) ``` - - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. + - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the + `references` constraint, above](#column-constraints), it supports all + SQLite types, both [`ON UPDATE` and `ON DELETE` + actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and + composite (multiple column) keys. - ``` swift + ```swift t.foreignKey(user_id, references: users, id, delete: .setNull) // FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL ``` @@ -508,9 +613,12 @@ Additional constraints may be provided outside the scope of a single column usin ## Inserting Rows -We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. +We can insert rows into a table by calling a [query’s](#queries) `insert` +function with a list of [setters](#setters)—typically [typed column +expressions](#expressions) and values (which can also be expressions)—each +joined by the `<-` operator. -``` swift +```swift try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') @@ -518,9 +626,10 @@ try db.run(users.insert(or: .replace, email <- "alice@mac.com", name <- "Alice B // INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` -The `insert` function, when run successfully, returns an `Int64` representing the inserted row’s [`ROWID`][ROWID]. +The `insert` function, when run successfully, returns an `Int64` representing +the inserted row’s [`ROWID`][ROWID]. -``` swift +```swift do { let rowid = try db.run(users.insert(email <- "alice@mac.com")) print("inserted id: \(rowid)") @@ -529,11 +638,14 @@ do { } ``` -The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns. +The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions +follow similar patterns. -> _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values. +> _Note:_ If `insert` is called without any arguments, the statement will run +> with a `DEFAULT VALUES` clause. The table must not have any constraints +> that aren’t fulfilled by default values. > -> ``` swift +> ```swift > try db.run(timestamps.insert()) > // INSERT INTO "timestamps" DEFAULT VALUES > ``` @@ -541,25 +653,27 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s ### Setters -SQLite.swift typically uses the `<-` operator to set values during [inserts](#inserting-rows) and [updates](#updating-rows). +SQLite.swift typically uses the `<-` operator to set values during [inserts +](#inserting-rows) and [updates](#updating-rows). -``` swift +```swift try db.run(counter.update(count <- 0)) // UPDATE "counters" SET "count" = 0 WHERE ("id" = 1) ``` -There are also a number of convenience setters that take the existing value into account using native Swift operators. +There are also a number of convenience setters that take the existing value +into account using native Swift operators. For example, to atomically increment a column, we can use `++`: -``` swift +```swift try db.run(counter.update(count++)) // equivalent to `counter.update(count -> count + 1)` // UPDATE "counters" SET "count" = "count" + 1 WHERE ("id" = 1) ``` To take an amount and “move” it via transaction, we can use `-=` and `+=`: -``` swift +```swift let amount = 100.0 try db.transaction { try db.run(alice.update(balance -= amount)) @@ -600,14 +714,18 @@ try db.transaction { ## Selecting Rows -[Query structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. +[Query structures](#queries) are `SELECT` statements waiting to happen. They +execute via [iteration](#iterating-and-accessing-values) and [other means +](#plucking-values) of sequence access. ### Iterating and Accessing Values -Prepared [queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. +Prepared [queries](#queries) execute lazily upon iteration. Each row is +returned as a `Row` object, which can be subscripted with a [column +expression](#expressions) matching one of the columns returned. -``` swift +```swift for user in try db.prepare(users) { print("id: \(user[id]), email: \(user[email]), name: \(user[name])") // id: 1, email: alice@mac.com, name: Optional("Alice") @@ -615,21 +733,25 @@ for user in try db.prepare(users) { // SELECT * FROM "users" ``` -`Expression` column values are _automatically unwrapped_ (we’ve made a promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. +`Expression` column values are _automatically unwrapped_ (we’ve made a +promise to the compiler that they’ll never be `NULL`), while `Expression` +values remain wrapped. ### Plucking Rows -We can pluck the first row by passing a query to the `pluck` function on a database connection. +We can pluck the first row by passing a query to the `pluck` function on a +database connection. -``` swift +```swift if let user = try db.pluck(users) { /* ... */ } // Row // SELECT * FROM "users" LIMIT 1 ``` -To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea). +To collect all rows into an array, we can simply wrap the sequence (though +this is not always the most memory-efficient idea). -``` swift +```swift let all = Array(try db.prepare(users)) // SELECT * FROM "users" ``` @@ -637,9 +759,12 @@ let all = Array(try db.prepare(users)) ### Building Complex Queries -[Queries](#queries) have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. +[Queries](#queries) have a number of chainable functions that can be used +(with [expressions](#expressions)) to add and modify [a number of +clauses](https://www.sqlite.org/lang_select.html) to the underlying +statement. -``` swift +```swift let query = users.select(email) // SELECT "email" FROM "users" .filter(name != nil) // WHERE "name" IS NOT NULL .order(email.desc, name) // ORDER BY "email" DESC, "name" @@ -649,9 +774,11 @@ let query = users.select(email) // SELECT "email" FROM "users" #### Selecting Columns -By default, [queries](#queries) select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. +By default, [queries](#queries) select every column of the result set (using +`SELECT *`). We can use the `select` function with a list of +[expressions](#expressions) to return specific columns instead. -``` swift +```swift for user in try db.prepare(users.select(id, email)) { print("id: \(user[id]), email: \(user[email])") // id: 1, email: alice@mac.com @@ -659,9 +786,10 @@ for user in try db.prepare(users.select(id, email)) { // SELECT "id", "email" FROM "users" ``` -We can access the results of more complex expressions by holding onto a reference of the expression itself. +We can access the results of more complex expressions by holding onto a +reference of the expression itself. -``` swift +```swift let sentence = name + " is " + cast(age) as Expression + " years old!" for user in users.select(sentence) { print(user[sentence]) @@ -675,35 +803,42 @@ for user in users.select(sentence) { We can join tables using a [query’s](#queries) `join` function. -``` swift +```swift users.join(posts, on: user_id == users[id]) // SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` -The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require [namespacing](#column-namespacing), and sometimes require [aliasing](#table-aliasing). +The `join` function takes a [query](#queries) object (for the table being +joined on), a join condition (`on`), and is prefixed with an optional join +type (default: `.inner`). Join conditions can be built using [filter +operators and functions](#filter-operators-and-functions), generally require +[namespacing](#column-namespacing), and sometimes require [aliasing](#table- +aliasing). ##### Column Namespacing -When joining tables, column names can become ambiguous. _E.g._, both tables may have an `id` column. +When joining tables, column names can become ambiguous. _E.g._, both tables +may have an `id` column. -``` swift +```swift let query = users.join(posts, on: user_id == id) // assertion failure: ambiguous column 'id' ``` We can disambiguate by namespacing `id`. -``` swift +```swift let query = users.join(posts, on: user_id == users[id]) // SELECT * FROM "users" INNER JOIN "posts" ON ("user_id" = "users"."id") ``` -Namespacing is achieved by subscripting a [query](#queries) with a [column expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`). +Namespacing is achieved by subscripting a [query](#queries) with a [column +expression](#expressions) (_e.g._, `users[id]` above becomes `users.id`). > _Note:_ We can namespace all of a table’s columns using `*`. > -> ``` swift +> ```swift > let query = users.select(users[*]) > // SELECT "users".* FROM "users" > ``` @@ -711,9 +846,11 @@ Namespacing is achieved by subscripting a [query](#queries) with a [column expre ##### Table Aliasing -Occasionally, we need to join a table to itself, in which case we must alias the table with another name. We can achieve this using the [query’s](#queries) `alias` function. +Occasionally, we need to join a table to itself, in which case we must alias +the table with another name. We can achieve this using the +[query’s](#queries) `alias` function. -``` swift +```swift let managers = users.alias("managers") let query = users.join(managers, on: managers[id] == users[managerId]) @@ -721,9 +858,11 @@ let query = users.join(managers, on: managers[id] == users[managerId]) // INNER JOIN ("users") AS "managers" ON ("managers"."id" = "users"."manager_id") ``` -If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. +If query results can have ambiguous column names, row values should be +accessed with namespaced [column expressions](#expressions). In the above +case, `SELECT *` immediately namespaces all columns of the result set. -``` swift +```swift let user = try db.pluck(query) user[id] // fatal error: ambiguous column 'id' // (please disambiguate: ["users"."id", "managers"."id"]) @@ -735,9 +874,10 @@ user[managers[id]] // returns "managers"."id" #### Filtering Rows -SQLite.swift filters rows using a [query’s](#queries) `filter` function with a boolean [expression](#expressions) (`Expression`). +SQLite.swift filters rows using a [query’s](#queries) `filter` function with +a boolean [expression](#expressions) (`Expression`). -``` swift +```swift users.filter(id == 1) // SELECT * FROM "users" WHERE ("id" = 1) @@ -754,18 +894,21 @@ users.filter(verified || balance >= 10_000) // SELECT * FROM "users" WHERE ("verified" OR ("balance" >= 10000.0)) ``` -We can build our own boolean expressions by using one of the many [filter operators and functions](#filter-operators-and-functions). +We can build our own boolean expressions by using one of the many [filter +operators and functions](#filter-operators-and-functions). Instead of `filter` we can also use the `where` function which is an alias: -``` swift +```swift users.where(id == 1) // SELECT * FROM "users" WHERE ("id" = 1) ``` ##### Filter Operators and Functions -SQLite.swift defines a number of operators for building filtering predicates. Operators and functions work together in a type-safe manner, so attempting to equate or compare different types will prevent compilation. +SQLite.swift defines a number of operators for building filtering predicates. +Operators and functions work together in a type-safe manner, so attempting to +equate or compare different types will prevent compilation. ###### Infix Filter Operators @@ -782,7 +925,8 @@ SQLite.swift defines a number of operators for building filtering predicates. Op | `&&` | `Bool -> Bool` | `AND` | | `||` | `Bool -> Bool` | `OR` | -> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` accordingly. +> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` +> accordingly. ###### Prefix Filter Operators @@ -813,16 +957,18 @@ We can pre-sort returned rows using the [query’s](#queries) `order` function. _E.g._, to return users sorted by `email`, then `name`, in ascending order: -``` swift +```swift users.order(email, name) // SELECT * FROM "users" ORDER BY "email", "name" ``` The `order` function takes a list of [column expressions](#expressions). -`Expression` objects have two computed properties to assist sorting: `asc` and `desc`. These properties append the expression with `ASC` and `DESC` to mark ascending and descending order respectively. +`Expression` objects have two computed properties to assist sorting: `asc` +and `desc`. These properties append the expression with `ASC` and `DESC` to +mark ascending and descending order respectively. -``` swift +```swift users.order(email.desc, name.asc) // SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC ``` @@ -830,9 +976,10 @@ users.order(email.desc, name.asc) #### Limiting and Paging Results -We can limit and skip returned rows using a [query’s](#queries) `limit` function (and its optional `offset` parameter). +We can limit and skip returned rows using a [query’s](#queries) `limit` +function (and its optional `offset` parameter). -``` swift +```swift users.limit(5) // SELECT * FROM "users" LIMIT 5 @@ -843,67 +990,79 @@ users.limit(5, offset: 5) #### Aggregation -[Queries](#queries) come with a number of functions that quickly return aggregate scalar values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. +[Queries](#queries) come with a number of functions that quickly return +aggregate scalar values from the table. These mirror the [core aggregate +functions](#aggregate-sqlite-functions) and are executed immediately against +the query. -``` swift +```swift let count = try db.scalar(users.count) // SELECT count(*) FROM "users" ``` Filtered queries will appropriately filter aggregate values. -``` swift +```swift let count = try db.scalar(users.filter(name != nil).count) // SELECT count(*) FROM "users" WHERE "name" IS NOT NULL ``` - - `count` as a computed property on a query (see examples above) returns the total number of rows matching the query. + - `count` as a computed property on a query (see examples above) returns + the total number of rows matching the query. - `count` as a computed property on a column expression returns the total number of rows where that column is not `NULL`. + `count` as a computed property on a column expression returns the total + number of rows where that column is not `NULL`. - ``` swift + ```swift let count = try db.scalar(users.select(name.count)) // -> Int // SELECT count("name") FROM "users" ``` - - `max` takes a comparable column expression and returns the largest value if any exists. + - `max` takes a comparable column expression and returns the largest value + if any exists. - ``` swift + ```swift let max = try db.scalar(users.select(id.max)) // -> Int64? // SELECT max("id") FROM "users" ``` - - `min` takes a comparable column expression and returns the smallest value if any exists. + - `min` takes a comparable column expression and returns the smallest value + if any exists. - ``` swift + ```swift let min = try db.scalar(users.select(id.min)) // -> Int64? // SELECT min("id") FROM "users" ``` - - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists. + - `average` takes a numeric column expression and returns the average row + value (as a `Double`) if any exists. - ``` swift + ```swift let average = try db.scalar(users.select(balance.average)) // -> Double? // SELECT avg("balance") FROM "users" ``` - - `sum` takes a numeric column expression and returns the sum total of all rows if any exist. + - `sum` takes a numeric column expression and returns the sum total of all + rows if any exist. - ``` swift + ```swift let sum = try db.scalar(users.select(balance.sum)) // -> Double? // SELECT sum("balance") FROM "users" ``` - - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query. + - `total`, like `sum`, takes a numeric column expression and returns the + sum total of all rows, but in this case always returns a `Double`, and + returns `0.0` for an empty query. - ``` swift + ```swift let total = try db.scalar(users.select(balance.total)) // -> Double // SELECT total("balance") FROM "users" ``` -> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the `distinct` computed property. +> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the +> `distinct` computed property. > -> ``` swift +> ```swift > let count = try db.scalar(users.select(name.distinct.count) // -> Int > // SELECT count(DISTINCT "name") FROM "users" > ``` @@ -911,26 +1070,32 @@ let count = try db.scalar(users.filter(name != nil).count) ## Updating Rows -We can update a table’s rows by calling a [query’s](#queries) `update` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. +We can update a table’s rows by calling a [query’s](#queries) `update` +function with a list of [setters](#setters)—typically [typed column +expressions](#expressions) and values (which can also be expressions)—each +joined by the `<-` operator. -When an unscoped query calls `update`, it will update _every_ row in the table. +When an unscoped query calls `update`, it will update _every_ row in the +table. -``` swift +```swift try db.run(users.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' ``` -Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#filtering-rows). +Be sure to scope `UPDATE` statements beforehand using [the `filter` function +](#filtering-rows). -``` swift +```swift let alice = users.filter(id == 1) try db.run(alice.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1) ``` -The `update` function returns an `Int` representing the number of updated rows. +The `update` function returns an `Int` representing the number of updated +rows. -``` swift +```swift do { if try db.run(alice.update(email <- "alice@me.com")) > 0 { print("updated alice") @@ -945,26 +1110,30 @@ do { ## Deleting Rows -We can delete rows from a table by calling a [query’s](#queries) `delete` function. +We can delete rows from a table by calling a [query’s](#queries) `delete` +function. -When an unscoped query calls `delete`, it will delete _every_ row in the table. +When an unscoped query calls `delete`, it will delete _every_ row in the +table. -``` swift +```swift try db.run(users.delete()) // DELETE FROM "users" ``` -Be sure to scope `DELETE` statements beforehand using [the `filter` function](#filtering-rows). +Be sure to scope `DELETE` statements beforehand using +[the `filter` function](#filtering-rows). -``` swift +```swift let alice = users.filter(id == 1) try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) ``` -The `delete` function returns an `Int` representing the number of deleted rows. +The `delete` function returns an `Int` representing the number of deleted +rows. -``` swift +```swift do { if try db.run(alice.delete()) > 0 { print("deleted alice") @@ -979,9 +1148,11 @@ do { ## Transactions and Savepoints -Using the `transaction` and `savepoint` functions, we can run a series of statements in a transaction. If a single statement fails or the block throws an error, the changes will be rolled back. +Using the `transaction` and `savepoint` functions, we can run a series of +statements in a transaction. If a single statement fails or the block throws +an error, the changes will be rolled back. -``` swift +```swift try db.transaction { let rowid = try db.run(users.insert(email <- "betty@icloud.com")) try db.run(users.insert(email <- "cathy@icloud.com", managerId <- rowid)) @@ -997,14 +1168,16 @@ try db.transaction { ## Altering the Schema -SQLite.swift comes with several functions (in addition to `Table.create`) for altering a database schema in a type-safe manner. +SQLite.swift comes with several functions (in addition to `Table.create`) for +altering a database schema in a type-safe manner. ### Renaming Tables -We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` function on a `Table` or `VirtualTable`. +We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` +function on a `Table` or `VirtualTable`. -``` swift +```swift try db.run(users.rename(Table("users_old")) // ALTER TABLE "users" RENAME TO "users_old" ``` @@ -1012,9 +1185,12 @@ try db.run(users.rename(Table("users_old")) ### Adding Columns -We can add columns to a table by calling `addColumn` function on a `Table`. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. +We can add columns to a table by calling `addColumn` function on a `Table`. +SQLite.swift enforces +[the same limited subset](https://www.sqlite.org/lang_altertable.html) of +`ALTER TABLE` that SQLite supports. -``` swift +```swift try db.run(users.addColumn(suffix)) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` @@ -1022,27 +1198,38 @@ try db.run(users.addColumn(suffix)) #### Added Column Constraints -The `addColumn` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). +The `addColumn` function shares several of the same [`column` function +parameters](#column-constraints) used when [creating +tables](#creating-a-table). - - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). (See also the `check` function under [Table Constraints](#table-constraints).) + - `check` attaches a `CHECK` constraint to a column definition in the form + of a boolean expression (`Expression`). (See also the `check` + function under [Table Constraints](#table-constraints).) - ``` swift + ```swift try db.run(users.addColumn(suffix, check: ["JR", "SR"].contains(suffix))) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) ``` - - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). + - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ + accepts a value matching the column’s type. This value is used if none is + explicitly provided during [an `INSERT`](#inserting-rows). - ``` swift + ```swift try db.run(users.addColumn(suffix, defaultValue: "SR")) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT DEFAULT 'SR' ``` - > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), default values may not be expression structures (including `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). + > _Note:_ Unlike the [`CREATE TABLE` constraint](#table-constraints), + > default values may not be expression structures (including + > `CURRENT_TIME`, `CURRENT_DATE`, or `CURRENT_TIMESTAMP`). - - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. + - `collate` adds a `COLLATE` clause to `Expression` (and + `Expression`) column definitions with [a collating + sequence](https://www.sqlite.org/datatype3.html#collation) defined in the + `Collation` enumeration. - ``` swift + ```swift try db.run(users.addColumn(email, collate: .nocase)) // ALTER TABLE "users" ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" @@ -1050,9 +1237,12 @@ The `addColumn` function shares several of the same [`column` function parameter // ALTER TABLE "users" ADD COLUMN "name" TEXT COLLATE "RTRIM" ``` - - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column + definitions and accepts a table or namespaced column expression. (See the + `foreignKey` function under [Table Constraints](#table-constraints) for + non-integer foreign key support.) - ``` swift + ```swift try db.run(posts.addColumn(userId, references: users, id) // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` @@ -1063,27 +1253,32 @@ The `addColumn` function shares several of the same [`column` function parameter #### Creating Indexes -We can build [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `createIndex` function on a `SchemaType`. +We can build +[`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) +by calling the `createIndex` function on a `SchemaType`. -``` swift +```swift try db.run(users.createIndex(email)) // CREATE INDEX "index_users_on_email" ON "users" ("email") ``` -The index name is generated automatically based on the table and column names. +The index name is generated automatically based on the table and column +names. The `createIndex` function has a couple default parameters we can override. - `unique` adds a `UNIQUE` constraint to the index. Default: `false`. - ``` swift + ```swift try db.run(users.createIndex(email, unique: true)) // CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email") ``` - - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. + - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` + statement (which will bail out gracefully if the table already exists). + Default: `false`. - ``` swift + ```swift try db.run(users.createIndex(email, ifNotExists: true)) // CREATE INDEX IF NOT EXISTS "index_users_on_email" ON "users" ("email") ``` @@ -1091,16 +1286,19 @@ The `createIndex` function has a couple default parameters we can override. #### Dropping Indexes -We can build [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `dropIndex` function on a `SchemaType`. +We can build +[`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by +calling the `dropIndex` function on a `SchemaType`. -``` swift +```swift try db.run(users.dropIndex(email)) // DROP INDEX "index_users_on_email" ``` -The `dropIndex` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `dropIndex` function has one additional parameter, `ifExists`, which +(when `true`) adds an `IF EXISTS` clause to the statement. -``` swift +```swift try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` @@ -1108,16 +1306,19 @@ try db.run(users.dropIndex(email, ifExists: true)) ### Dropping Tables -We can build [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `dropTable` function on a `SchemaType`. +We can build +[`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) +by calling the `dropTable` function on a `SchemaType`. -``` swift +```swift try db.run(users.drop()) // DROP TABLE "users" ``` -The `drop` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `drop` function has one additional parameter, `ifExists`, which (when +`true`) adds an `IF EXISTS` clause to the statement. -``` swift +```swift try db.run(users.drop(ifExists: true)) // DROP TABLE IF EXISTS "users" ``` @@ -1125,11 +1326,12 @@ try db.run(users.drop(ifExists: true)) ### Migrations and Schema Versioning -You can add a convenience property on `Connection` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). +You can add a convenience property on `Connection` to query and set the +[`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). This is a great way to manage your schema’s version over migrations. -``` swift +```swift extension Connection { public var userVersion: Int32 { get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} @@ -1151,14 +1353,15 @@ if db.userVersion == 1 { } ``` -For more complex migration requirements check out the schema management system -[SQLiteMigrationManager.swift][]. +For more complex migration requirements check out the schema management +system [SQLiteMigrationManager.swift][]. ## Custom Types -SQLite.swift supports serializing and deserializing any custom type as long as it conforms to the `Value` protocol. +SQLite.swift supports serializing and deserializing any custom type as long +as it conforms to the `Value` protocol. -> ``` swift +> ```swift > protocol Value { > typealias Datatype: Binding > class var declaredDatatype: String { get } @@ -1167,20 +1370,27 @@ SQLite.swift supports serializing and deserializing any custom type as long as i > } > ``` -The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL](#building-type-safe-sql) for a list of types). +The `Datatype` must be one of the basic Swift types that values are bridged +through before serialization and deserialization (see [Building Type-Safe SQL +](#building-type-safe-sql) for a list of types). -> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to directly map SQLite types to Swift types. **Do _not_** conform custom types to the `Binding` protocol. +> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to +> directly map SQLite types to Swift types. **Do _not_** conform custom types +> to the `Binding` protocol. -Once extended, the type can be used [_almost_](#custom-type-caveats) wherever typed expressions can be. +Once extended, the type can be used [_almost_](#custom-type-caveats) wherever +typed expressions can be. ### Date-Time Values -In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can transparently bridge `Date` objects through Swift’s `String` or `Int` types. +In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can +transparently bridge `Date` objects through Swift’s `String` or `Int` types. -To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use `String`. +To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use +`String`. -``` swift +```swift extension Date: Value { class var declaredDatatype: String { return String.declaredDatatype @@ -1204,7 +1414,7 @@ let SQLDateFormatter: DateFormatter = { We can also treat them as `INTEGER` values using `Int`. -``` swift +```swift extension Date: Value { class var declaredDatatype: String { return Int.declaredDatatype @@ -1218,11 +1428,14 @@ extension Date: Value { } ``` -> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` helpers return `TEXT` values. Because of this (and the fact that Unix time is far less human-readable when we’re faced with the raw data), we recommend using the `TEXT` extension. +> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` +> helpers return `TEXT` values. Because of this (and the fact that Unix time +> is far less human-readable when we’re faced with the raw data), we +> recommend using the `TEXT` extension. Once defined, we can use these types directly in SQLite statements. -``` swift +```swift let published_at = Expression("published_at") let published = posts.filter(published_at <= Date()) @@ -1237,7 +1450,7 @@ let published = posts.filter(published_at <= Date()) We can bridge any type that can be initialized from and encoded to `Data`. -``` swift +```swift extension UIImage: Value { public class var declaredDatatype: String { return Blob.declaredDatatype @@ -1252,16 +1465,20 @@ extension UIImage: Value { } ``` -> _Note:_ See the [Archives and Serializations Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i) for more information on encoding and decoding custom types. +> _Note:_ See the [Archives and Serializations Programming Guide][] for more +> information on encoding and decoding custom types. +[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i + ### Custom Type Caveats -Swift does _not_ currently support generic subscripting, which means we cannot, by default, subscript Expressions with custom types to: +Swift does _not_ currently support generic subscripting, which means we +cannot, by default, subscript Expressions with custom types to: 1. **Namespace expressions**. Use the `namespace` function, instead: - ``` swift + ```swift let avatar = Expression("avatar") users[avatar] // fails to compile users.namespace(avatar) // "users"."avatar" @@ -1269,7 +1486,7 @@ Swift does _not_ currently support generic subscripting, which means we cannot, 2. **Access column data**. Use the `get` function, instead: - ``` swift + ```swift let user = users.first! user[avatar] // fails to compile user.get(avatar) // UIImage? @@ -1277,7 +1494,7 @@ Swift does _not_ currently support generic subscripting, which means we cannot, We can, of course, write extensions, but they’re rather wordy. -``` swift +```swift extension Query { subscript(column: Expression) -> Expression { return namespace(column) @@ -1299,53 +1516,63 @@ extension Row { ## Codable Types -Codable types were introduced as a part of Swift 4 to allow serializing and deserializing types. SQLite.swift -supports the insertion, updating, and retrieval of basic Codable types. +Codable types were introduced as a part of Swift 4 to allow serializing and +deserializing types. SQLite.swift supports the insertion, updating, and +retrieval of basic Codable types. ### Inserting Codable Types Queries have a method to allow inserting an Encodable type. -``` swift +```swift try db.run(users.insert(user)) ``` There are two other parameters also available to this method: -- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. -- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. +- `userInfo` is a dictionary that is passed to the encoder and made available + to encodable types to allow customizing their behavior. + +- `otherSetters` allows you to specify additional setters on top of those + that are generated from the encodable types themselves. ### Updating Codable Types Queries have a method to allow updating an Encodable type. -``` swift +```swift try db.run(users.update(user)) ``` There are two other parameters also available to this method: -- `userInfo` is a dictionary that is passed to the encoder and made available to encodable types to allow customizing their behavior. -- `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. +- `userInfo` is a dictionary that is passed to the encoder and made available + to encodable types to allow customizing their behavior. + +- `otherSetters` allows you to specify additional setters on top of those + that are generated from the encodable types themselves. ### Retrieving Codable Types Rows have a method to decode a Decodable type. -``` swift +```swift let loadedUsers: [User] = try db.prepare(users).map { row in return try row.decode() } ``` -You can also create a decoder to use manually yourself. This can be useful for example if you are using -the [Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide subclasses behind a super class. -For example, you may want to encode an Image type that can be multiple different formats such as PNGImage, JPGImage, -or HEIFIamge. You will need to determine the correct subclass before you know which type to decode. +You can also create a decoder to use manually yourself. This can be useful +for example if you are using the +[Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide +subclasses behind a super class. For example, you may want to encode an Image +type that can be multiple different formats such as PNGImage, JPGImage, or +HEIFIamge. You will need to determine the correct subclass before you know +which type to decode. -``` swift +```swift enum ImageCodingKeys: String, CodingKey { case kind } @@ -1370,7 +1597,8 @@ let loadedImages: [Image] = try db.prepare(images).map { row in Both of the above methods also have the following optional parameter: -- `userInfo` is a dictionary that is passed to the decoder and made available to decodable types to allow customizing their behavior. +- `userInfo` is a dictionary that is passed to the decoder and made available + to decodable types to allow customizing their behavior. ### Restrictions @@ -1379,15 +1607,19 @@ There are a few restrictions on using Codable types: - The encodable and decodable objects can only use the following types: - Int, Bool, Float, Double, String - Nested Codable types that will be encoded as JSON to a single column -- These methods will not handle object relationships for you. You must write your own Codable and Decodable -implementations if you wish to support this. -- The Codable types may not try to access nested containers or nested unkeyed containers -- The Codable types may not access single value containers or unkeyed containers +- These methods will not handle object relationships for you. You must write + your own Codable and Decodable implementations if you wish to support this. +- The Codable types may not try to access nested containers or nested unkeyed + containers +- The Codable types may not access single value containers or unkeyed + containers - The Codable types may not access super decoders or encoders ## Other Operators -In addition to [filter operators](#filtering-infix-operators), SQLite.swift defines a number of operators that can modify expression values with arithmetic, bitwise operations, and concatenation. +In addition to [filter operators](#filtering-infix-operators), SQLite.swift +defines a number of operators that can modify expression values with +arithmetic, bitwise operations, and concatenation. ###### Other Infix Operators @@ -1405,7 +1637,8 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi | `|` | `Int -> Int` | `|` | | `+` | `String -> String` | `||` | -> _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. +> _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which +> expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. ###### Other Prefix Operators @@ -1418,22 +1651,26 @@ In addition to [filter operators](#filtering-infix-operators), SQLite.swift defi ## Core SQLite Functions -Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) have been surfaced in and type-audited for SQLite.swift. +Many of SQLite’s [core functions](https://www.sqlite.org/lang_corefunc.html) +have been surfaced in and type-audited for SQLite.swift. > _Note:_ SQLite.swift aliases the `??` operator to the `ifnull` function. > -> ``` swift +> ```swift > name ?? email // ifnull("name", "email") > ``` ## Aggregate SQLite Functions -Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. +Most of SQLite’s +[aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been +surfaced in and type-audited for SQLite.swift. ## Date and Time functions -SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) are available: +SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) +functions are available: ```swift DateFunctions.date("now") @@ -1446,11 +1683,14 @@ Expression("date").date ## Custom SQL Functions -We can create custom SQL functions by calling `createFunction` on a database connection. +We can create custom SQL functions by calling `createFunction` on a database +connection. -For example, to give queries access to [`MobileCoreServices.UTTypeConformsTo`](https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo), we can write the following: +For example, to give queries access to +[`MobileCoreServices.UTTypeConformsTo`][UTTypeConformsTo], we can +write the following: -``` swift +```swift import MobileCoreServices let typeConformsTo: (Expression, Expression) -> Expression = ( @@ -1460,23 +1700,27 @@ let typeConformsTo: (Expression, Expression) -> Expression ) ``` -> _Note:_ The optional `deterministic` parameter is an optimization that causes the function to be created with [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/create_function.html). +> _Note:_ The optional `deterministic` parameter is an optimization that +> causes the function to be created with +> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/create_function.html). Note `typeConformsTo`’s signature: -``` swift +```swift (Expression, Expression) -> Expression ``` -Because of this, `createFunction` expects a block with the following signature: +Because of this, `createFunction` expects a block with the following +signature: -``` swift +```swift (String, String) -> Bool ``` -Once assigned, the closure can be called wherever boolean expressions are accepted. +Once assigned, the closure can be called wherever boolean expressions are +accepted. -``` swift +```swift let attachments = Table("attachments") let UTI = Expression("UTI") @@ -1484,38 +1728,44 @@ let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage)) // SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` -> _Note:_ The return type of a function must be [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types). +> _Note:_ The return type of a function must be [a core SQL type](#building- +> type-safe-sql) or [conform to `Value`](#custom-types). -We can create loosely-typed functions by handling an array of raw arguments, instead. +We can create loosely-typed functions by handling an array of raw arguments, +instead. -``` swift +```swift db.createFunction("typeConformsTo", deterministic: true) { args in guard let UTI = args[0] as? String, conformsToUTI = args[1] as? String else { return nil } return UTTypeConformsTo(UTI, conformsToUTI) } ``` -Creating a loosely-typed function cannot return a closure and instead must be wrapped manually or executed [using raw SQL](#executing-arbitrary-sql). +Creating a loosely-typed function cannot return a closure and instead must be +wrapped manually or executed [using raw SQL](#executing-arbitrary-sql). -``` swift +```swift let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` +[UTTypeConformsTo]: https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo ## Custom Collations -We can create custom collating sequences by calling `createCollation` on a database connection. +We can create custom collating sequences by calling `createCollation` on a +database connection. -``` swift +```swift try db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .diacriticInsensitiveSearch) } ``` -We can reference a custom collation using the `Custom` member of the `Collation` enumeration. +We can reference a custom collation using the `Custom` member of the +`Collation` enumeration. -``` swift +```swift restaurants.order(collate(.custom("NODIACRITIC"), name)) // SELECT * FROM "restaurants" ORDER BY "name" COLLATE "NODIACRITIC" ``` @@ -1523,9 +1773,11 @@ restaurants.order(collate(.custom("NODIACRITIC"), name)) ## Full-text Search -We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create` on a `VirtualTable`. +We can create a virtual table using the [FTS4 +module](http://www.sqlite.org/fts3.html) by calling `create` on a +`VirtualTable`. -``` swift +```swift let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") @@ -1536,14 +1788,14 @@ try db.run(emails.create(.FTS4(subject, body))) We can specify a [tokenizer](http://www.sqlite.org/fts3.html#tokenizer) using the `tokenize` parameter. -``` swift +```swift try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) ``` We can set the full range of parameters by creating a `FTS4Config` object. -``` swift +```swift let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") @@ -1557,9 +1809,11 @@ try db.run(emails.create(.FTS4(config)) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", notindexed="body", languageid="lid", order="desc") ``` -Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. +Once we insert a few rows, we can search using the `match` function, which +takes a table or column as its first argument and a query string as its +second. -``` swift +```swift try db.run(emails.insert( subject <- "Just Checking In", body <- "Hey, I was just wondering...did you get my last email?" @@ -1574,8 +1828,9 @@ let replies = emails.filter(subject.match("Re:*")) ### FTS5 -When linking against a version of SQLite with [FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual table -in a similar fashion. +When linking against a version of SQLite with +[FTS5](http://www.sqlite.org/fts5.html) enabled we can create the virtual +table in a similar fashion. ```swift let emails = VirtualTable("emails") @@ -1598,11 +1853,14 @@ let replies = emails.filter(emails.match("subject:\"Re:\"*)) ## Executing Arbitrary SQL -Though we recommend you stick with SQLite.swift’s [type-safe system](#building-type-safe-sql) whenever possible, it is possible to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. +Though we recommend you stick with SQLite.swift’s [type-safe system +](#building-type-safe-sql) whenever possible, it is possible to simply and +safely prepare and execute raw SQL statements via a `Database` connection +using the following functions. - `execute` runs an arbitrary number of SQL statements as a convenience. - ``` swift + ```swift try db.execute( "BEGIN TRANSACTION;" + "CREATE TABLE users (" + @@ -1621,22 +1879,26 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building ) ``` - - `prepare` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), and returns the statement for deferred execution. + - `prepare` prepares a single `Statement` object from a SQL string, + optionally binds values to it (using the statement’s `bind` function), + and returns the statement for deferred execution. - ``` swift + ```swift let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") ``` - Once prepared, statements may be executed using `run`, binding any unbound parameters. + Once prepared, statements may be executed using `run`, binding any + unbound parameters. - ``` swift + ```swift try stmt.run("alice@mac.com") db.changes // -> {Some 1} ``` - Statements with results may be iterated over, using the columnNames if useful. + Statements with results may be iterated over, using the columnNames if + useful. - ``` swift + ```swift let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { for (index, name) in stmt.columnNames.enumerate() { @@ -1646,21 +1908,26 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building } ``` - - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. + - `run` prepares a single `Statement` object from a SQL string, optionally + binds values to it (using the statement’s `bind` function), executes, + and returns the statement. - ``` swift + ```swift try db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") ``` - - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. + - `scalar` prepares a single `Statement` object from a SQL string, + optionally binds values to it (using the statement’s `bind` function), + executes, and returns the first value of the first row. - ``` swift + ```swift let count = try db.scalar("SELECT count(*) FROM users") as! Int64 ``` - Statements also have a `scalar` function, which can optionally re-bind values at execution. + Statements also have a `scalar` function, which can optionally re-bind + values at execution. - ``` swift + ```swift let stmt = try db.prepare("SELECT count (*) FROM users") let count = try stmt.scalar() as! Int64 ``` @@ -1670,7 +1937,7 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building We can log SQL using the database’s `trace` function. -``` swift +```swift #if DEBUG db.trace { print($0) } #endif diff --git a/README.md b/README.md index a10f2c09..07802fa8 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,10 @@ syntax _and_ intent. - [Well-documented][See Documentation] - Extensively tested - SQLCipher support via CocoaPods - - Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) + - Active support at + [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), + and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) + (_experimental_) [Full-text search]: Documentation/Index.md#full-text-search [See Documentation]: Documentation/Index.md#sqliteswift-documentation @@ -34,7 +37,7 @@ syntax _and_ intent. ## Usage -``` swift +```swift import SQLite let db = try Connection("path/to/db.sqlite3") @@ -81,7 +84,7 @@ try db.scalar(users.count) // 0 SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. -``` swift +```swift let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") for email in ["betty@icloud.com", "cathy@icloud.com"] { try stmt.run(email) @@ -105,7 +108,13 @@ interactively, from the Xcode project’s playground. ![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) -For a more comprehensive example, see [this article](http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html) and the [companion repository](https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master). +For a more comprehensive example, see +[this article][Create a Data Access Layer with SQLite.swift and Swift 2] +and the [companion repository][SQLiteDataAccessLayer2]. + + +[Create a Data Access Layer with SQLite.swift and Swift 2]: http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html +[SQLiteDataAccessLayer2]: https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master ## Installation @@ -120,11 +129,12 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: - ``` + ```ruby github "stephencelis/SQLite.swift" ~> 0.11.4 ``` - 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. + 3. Run `carthage update` and + [add the appropriate framework][Carthage Usage]. [Carthage]: https://github.com/Carthage/Carthage @@ -137,9 +147,10 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift requires version 1.0.0 or greater.) + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift + requires version 1.0.0 or greater.) - ``` sh + ```sh # Using the default Ruby install will require you to use sudo when # installing and updating gems. [sudo] gem install cocoapods @@ -147,7 +158,7 @@ SQLite.swift with CocoaPods: 2. Update your Podfile to include the following: - ``` ruby + ```ruby use_frameworks! target 'YourAppTargetName' do @@ -162,7 +173,8 @@ SQLite.swift with CocoaPods: ### Swift Package Manager -The [Swift Package Manager][] is a tool for managing the distribution of Swift code. +The [Swift Package Manager][] is a tool for managing the distribution of +Swift code. 1. Add the following to your `Package.swift` file: @@ -174,7 +186,7 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift c 2. Build your project: - ``` sh + ```sh $ swift build ``` @@ -196,9 +208,11 @@ To install SQLite.swift as an Xcode sub-project: 4. **Add**. -Some additional steps are required to install the application on an actual device: +Some additional steps are required to install the application on an actual +device: - 5. In the **General** tab, click the **+** button under **Embedded Binaries**. + 5. In the **General** tab, click the **+** button under **Embedded + Binaries**. 6. Select the appropriate **SQLite.framework** for your platform. @@ -243,7 +257,8 @@ file](./LICENSE.txt) for more information. These projects enhance or use SQLite.swift: - - [SQLiteMigrationManager.swift](https://github.com/garriguv/SQLiteMigrationManager.swift) (inspired by [FMDBMigrationManager](https://github.com/layerhq/FMDBMigrationManager)) + - [SQLiteMigrationManager.swift][] (inspired by + [FMDBMigrationManager][]) ## Alternatives @@ -257,5 +272,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftData](https://github.com/ryanfowler/SwiftData) - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) -[FMDB]: https://github.com/ccgus/fmdb [swift-4]: https://github.com/stephencelis/SQLite.swift/tree/swift-4 +[SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift +[FMDB]: https://github.com/ccgus/fmdb +[FMDBMigrationManager]: https://github.com/layerhq/FMDBMigrationManager From f7dfd88229c5e882dc84fa916b224f30dcab39cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 08:56:29 +0200 Subject: [PATCH 0619/1046] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e36e40d7..09c5c33f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * Added Date and Time functions ([#142][]) +* Add Swift4 Coding support ([#733][]) * Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) * Make Row.get throw instead of crash ([#649][]) @@ -69,4 +70,5 @@ [#681]: https://github.com/stephencelis/SQLite.swift/issues/681 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 +[#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 From 25b2ae6e3eddfce2d0c18b69bb997316bfac4b25 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 09:02:34 +0200 Subject: [PATCH 0620/1046] Add link --- Documentation/Index.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9657a94a..74837148 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1516,9 +1516,11 @@ extension Row { ## Codable Types -Codable types were introduced as a part of Swift 4 to allow serializing and -deserializing types. SQLite.swift supports the insertion, updating, and -retrieval of basic Codable types. +[Codable types][Encoding and Decoding Custom Types] were introduced as a part +of Swift 4 to allow serializing and deserializing types. SQLite.swift supports +the insertion, updating, and retrieval of basic Codable types. + +[Encoding and Decoding Custom Types]: https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types ### Inserting Codable Types From 93f2f63f38f021583ac7a31c5c5b07a1e9fd7069 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 09:06:46 +0200 Subject: [PATCH 0621/1046] clarify --- Documentation/Index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 74837148..4b5c0025 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1527,7 +1527,10 @@ the insertion, updating, and retrieval of basic Codable types. Queries have a method to allow inserting an Encodable type. ```swift -try db.run(users.insert(user)) +struct User: Codable { + let name: String +} +try db.run(users.insert(User(name: "test"))) ``` From 865f4ad448b746b7ce82d55e0fee46478f87f38b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 09:27:42 +0200 Subject: [PATCH 0622/1046] Doc fixes --- Documentation/Index.md | 138 ++++++++++++++--------------------------- README.md | 32 +++++++--- 2 files changed, 70 insertions(+), 100 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 4b5c0025..f3569374 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -288,8 +288,8 @@ let db = try Connection(path, readonly: true) > _Note:_ Signed applications cannot modify their bundle resources. If you > bundle a database file with your app for the purpose of bootstrapping, copy -> it to a writable location _before_ establishing a connection (see [Read- -> Write Databases](#read-write-databases), above, for typical, writable +> it to a writable location _before_ establishing a connection (see +> [Read-Write Databases](#read-write-databases), above, for typical, writable > locations). > > See these two Stack Overflow questions for more information about iOS apps @@ -515,8 +515,8 @@ several parameters that map to various column constraints and clauses. - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value (or expression) matching the column’s type. This value is - used if none is explicitly provided during [an `INSERT`](#inserting- - rows). + used if none is explicitly provided during + [an `INSERT`](#inserting-rows). ```swift t.column(name, defaultValue: "Anonymous") @@ -587,8 +587,8 @@ using the following functions. - `check` adds a `CHECK` constraint to the table in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). - (See also the `check` parameter under [Column Constraints](#column- - constraints).) + (See also the `check` parameter under + [Column Constraints](#column-constraints).) ```swift t.check(balance >= 0) @@ -812,8 +812,8 @@ The `join` function takes a [query](#queries) object (for the table being joined on), a join condition (`on`), and is prefixed with an optional join type (default: `.inner`). Join conditions can be built using [filter operators and functions](#filter-operators-and-functions), generally require -[namespacing](#column-namespacing), and sometimes require [aliasing](#table- -aliasing). +[namespacing](#column-namespacing), and sometimes require +[aliasing](#table-aliasing). ##### Column Namespacing @@ -1361,14 +1361,14 @@ system [SQLiteMigrationManager.swift][]. SQLite.swift supports serializing and deserializing any custom type as long as it conforms to the `Value` protocol. -> ```swift -> protocol Value { -> typealias Datatype: Binding -> class var declaredDatatype: String { get } -> class func fromDatatypeValue(datatypeValue: Datatype) -> Self -> var datatypeValue: Datatype { get } -> } -> ``` +```swift +protocol Value { + typealias Datatype: Binding + class var declaredDatatype: String { get } + class func fromDatatypeValue(datatypeValue: Datatype) -> Self + var datatypeValue: Datatype { get } +} +``` The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL @@ -1385,64 +1385,19 @@ typed expressions can be. ### Date-Time Values In SQLite, `DATETIME` columns can be treated as strings or numbers, so we can -transparently bridge `Date` objects through Swift’s `String` or `Int` types. - -To serialize `Date` objects as `TEXT` values (in ISO 8601), we’ll use -`String`. - -```swift -extension Date: Value { - class var declaredDatatype: String { - return String.declaredDatatype - } - class func fromDatatypeValue(stringValue: String) -> Date { - return SQLDateFormatter.dateFromString(stringValue)! - } - var datatypeValue: String { - return SQLDateFormatter.stringFromDate(self) - } -} +transparently bridge `Date` objects through Swift’s `String` types. -let SQLDateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" - formatter.locale = Locale(localeIdentifier: "en_US_POSIX") - formatter.timeZone = TimeZone(forSecondsFromGMT: 0) - return formatter -}() -``` - -We can also treat them as `INTEGER` values using `Int`. - -```swift -extension Date: Value { - class var declaredDatatype: String { - return Int.declaredDatatype - } - class func fromDatatypeValue(intValue: Int) -> Self { - return self(timeIntervalSince1970: TimeInterval(intValue)) - } - var datatypeValue: Int { - return Int(timeIntervalSince1970) - } -} -``` - -> _Note:_ SQLite’s `CURRENT_DATE`, `CURRENT_TIME`, and `CURRENT_TIMESTAMP` -> helpers return `TEXT` values. Because of this (and the fact that Unix time -> is far less human-readable when we’re faced with the raw data), we -> recommend using the `TEXT` extension. - -Once defined, we can use these types directly in SQLite statements. +We can use these types directly in SQLite statements. ```swift let published_at = Expression("published_at") let published = posts.filter(published_at <= Date()) -// extension where Datatype == String: -// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18 12:45:30' -// extension where Datatype == Int: -// SELECT * FROM "posts" WHERE "published_at" <= 1416314730 +// SELECT * FROM "posts" WHERE "published_at" <= '2014-11-18T12:45:30.000' + +let startDate = Date(timeIntervalSince1970: 0) +let published = posts.filter(startDate...Date() ~= published_at) +// SELECT * FROM "posts" WHERE "published_at" BETWEEN '1970-01-01T00:00:00.000' AND '2014-11-18T12:45:30.000' ``` @@ -1469,7 +1424,7 @@ extension UIImage: Value { > information on encoding and decoding custom types. -[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html#//apple_ref/doc/uid/10000047i +[Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html ### Custom Type Caveats @@ -1707,7 +1662,7 @@ let typeConformsTo: (Expression, Expression) -> Expression > _Note:_ The optional `deterministic` parameter is an optimization that > causes the function to be created with -> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/create_function.html). +> [`SQLITE_DETERMINISTIC`](https://www.sqlite.org/c3ref/c_deterministic.html). Note `typeConformsTo`’s signature: @@ -1733,8 +1688,8 @@ let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage)) // SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` -> _Note:_ The return type of a function must be [a core SQL type](#building- -> type-safe-sql) or [conform to `Value`](#custom-types). +> _Note:_ The return type of a function must be +> [a core SQL type](#building-type-safe-sql) or [conform to `Value`](#custom-types). We can create loosely-typed functions by handling an array of raw arguments, instead. @@ -1754,7 +1709,7 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ? for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` -[UTTypeConformsTo]: https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo +[UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto ## Custom Collations @@ -1858,29 +1813,30 @@ let replies = emails.filter(emails.match("subject:\"Re:\"*)) ## Executing Arbitrary SQL -Though we recommend you stick with SQLite.swift’s [type-safe system -](#building-type-safe-sql) whenever possible, it is possible to simply and -safely prepare and execute raw SQL statements via a `Database` connection +Though we recommend you stick with SQLite.swift’s +[type-safe system](#building-type-safe-sql) whenever possible, it is possible +to simply and safely prepare and execute raw SQL statements via a `Database` connection using the following functions. - `execute` runs an arbitrary number of SQL statements as a convenience. ```swift - try db.execute( - "BEGIN TRANSACTION;" + - "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY NOT NULL," + - "email TEXT UNIQUE NOT NULL," + - "name TEXT" + - ");" + - "CREATE TABLE posts (" + - "id INTEGER PRIMARY KEY NOT NULL," + - "title TEXT NOT NULL," + - "body TEXT NOT NULL," + - "published_at DATETIME" + - ");" + - "PRAGMA user_version = 1;" + - "COMMIT TRANSACTION;" + try db.execute(""" + BEGIN TRANSACTION; + CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL, + email TEXT UNIQUE NOT NULL, + name TEXT + ); + CREATE TABLE posts ( + id INTEGER PRIMARY KEY NOT NULL, + title TEXT NOT NULL, + body TEXT NOT NULL, + published_at DATETIME + ); + PRAGMA user_version = 1; + COMMIT TRANSACTION; + """ ) ``` diff --git a/README.md b/README.md index 07802fa8..3a9ceecb 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,12 @@ # SQLite.swift -[![Build Status][Badge]][Travis] [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Swift](https://img.shields.io/badge/swift-4-orange.svg?style=flat)](https://developer.apple.com/swift/) [![Platform](https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png)](http://cocoadocs.org/docsets/SQLite.swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Join the chat at https://gitter.im/stephencelis/SQLite.swift](https://badges.gitter.im/stephencelis/SQLite.swift.svg)](https://gitter.im/stephencelis/SQLite.swift) +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. [SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. -[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat -[Travis]: https://travis-ci.org/stephencelis/SQLite.swift -[Swift]: https://developer.apple.com/swift/ -[SQLite3]: http://www.sqlite.org -[SQLite.swift]: https://github.com/stephencelis/SQLite.swift - - ## Features - A pure-Swift interface @@ -272,7 +265,28 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftData](https://github.com/ryanfowler/SwiftData) - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) -[swift-4]: https://github.com/stephencelis/SQLite.swift/tree/swift-4 +[Swift]: https://swift.org/ +[SQLite3]: http://www.sqlite.org +[SQLite.swift]: https://github.com/stephencelis/SQLite.swift + +[TravisBadge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat +[TravisLink]: https://travis-ci.org/stephencelis/SQLite.swift + +[CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png +[CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift + +[PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png +[PlatformLink]: http://cocoadocs.org/docsets/SQLite.swift + +[CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat +[CarthageLink]: https://github.com/Carthage/Carthage + +[GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg +[GitterLink]: https://gitter.im/stephencelis/SQLite.swift + +[Swift4Badge]: https://img.shields.io/badge/swift-4-orange.svg?style=flat +[Swift4Link]: https://developer.apple.com/swift/ + [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift [FMDB]: https://github.com/ccgus/fmdb [FMDBMigrationManager]: https://github.com/layerhq/FMDBMigrationManager From 5df5620a218d560fe727648b579cd988da13537b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 10:17:12 +0200 Subject: [PATCH 0623/1046] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c5c33f..3f0e9f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) * Add Swift4 Coding support ([#733][]) * Preliminary Linux support ([#315][], [#681][]) @@ -61,6 +62,7 @@ [#560]: https://github.com/stephencelis/SQLite.swift/pull/560 [#561]: https://github.com/stephencelis/SQLite.swift/issues/561 [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 +[#591]: https://github.com/stephencelis/SQLite.swift/pull/591 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 [#647]: https://github.com/stephencelis/SQLite.swift/pull/647 [#649]: https://github.com/stephencelis/SQLite.swift/pull/649 From bc60dcdec186040c77df5f164ec1909ba803955a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 10:24:14 +0200 Subject: [PATCH 0624/1046] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0e9f0d..7f3db59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Fix transactions not being rolled back when committing fails ([#426][]) * Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) * Add Swift4 Coding support ([#733][]) @@ -52,6 +53,7 @@ [#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142 [#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 +[#426]: https://github.com/stephencelis/SQLit1e.swift/pull/426 [#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 [#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 [#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 From 0fa5e5e00a3e0456ba292b502013ed3708bab756 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 11:09:38 +0200 Subject: [PATCH 0625/1046] Optional types should not enforce NOT NULL Closes #697 --- CHANGELOG.md | 2 ++ Sources/SQLite/Typed/Schema.swift | 12 ++++---- Tests/SQLiteTests/SchemaTests.swift | 48 ++++++++++++++--------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3db59e..f9a920d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.11.4 (xx-09-2017), [diff][diff-0.11.4] ======================================== +* Collate .nocase strictly enforces NOT NULL even when using Optional ([#697][]) * Fix transactions not being rolled back when committing fails ([#426][]) * Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) @@ -72,6 +73,7 @@ [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 [#681]: https://github.com/stephencelis/SQLite.swift/issues/681 +[#697]: https://github.com/stephencelis/SQLite.swift/issues/697 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 690a26a5..46a1f87a 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -300,27 +300,27 @@ public final class TableBuilder { } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { - column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 34226b3c..b9a08881 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -435,99 +435,99 @@ class SchemaTests : XCTestCase { ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT COLLATE RTRIM)", table.create { t in t.column(stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } ) } From 44452ad553e72e76d567ccefa1208317f60c8626 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 12:09:04 +0200 Subject: [PATCH 0626/1046] Document error handling Closes #700 --- Documentation/Index.md | 14 ++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index f3569374..aa219633 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -650,6 +650,20 @@ follow similar patterns. > // INSERT INTO "timestamps" DEFAULT VALUES > ``` +### Handling specific SQLite errors + +You can pattern match on the error to selectively catch SQLite errors: + +```swift +do { + try db.run(users.insert(email <- "alice@mac.com")) + try db.run(users.insert(email <- "alice@mac.com")) +} catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { + print("constraint failed") +} catch let error { + print("insertion failed: \(error)") +} +``` ### Setters diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index bb99d016..cd609708 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -518,4 +518,16 @@ class QueryIntegrationTests : SQLiteTestCase { } } } + + func test_catchConstraintError() { + try! db.run(users.insert(email <- "alice@example.com")) + do { + try db.run(users.insert(email <- "alice@example.com")) + XCTFail("expected error") + } catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { + // expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } } From 1602cc044ece758cd1a80b0c403aea9b21452bb7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 12:32:01 +0200 Subject: [PATCH 0627/1046] Link milestone --- Documentation/Planning.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index d814d26b..8a3d5a11 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -1,7 +1,13 @@ # SQLite.swift Planning -This document captures both near term steps (aka Roadmap) and feature requests. -The goal is to add some visibility and guidance for future additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. +This document captures both near term steps (aka Roadmap) and feature +requests. The goal is to add some visibility and guidance for future +additions and Pull Requests, as well as to keep the Issues list clear of +enhancement requests so that bugs are more visible. + +> ⚠ This document is currently not actively maintained. See +> the [0.12.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.12.0) +> on Github for additional information about planned features for the next release. ## Roadmap @@ -9,16 +15,24 @@ _Lists agreed upon next steps in approximate priority order._ ## Feature Requests -_A gathering point for ideas for new features. In general, the corresponding issue will be closed once it is added here, with the assumption that it will be referred to when it comes time to add the corresponding feature._ +_A gathering point for ideas for new features. In general, the corresponding +issue will be closed once it is added here, with the assumption that it will +be referred to when it comes time to add the corresponding feature._ ### Features - * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per [#30](https://github.com/stephencelis/SQLite.swift/issues/30) - * provide separate threads for update vs read, so updates don't block reads, per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) - * expose triggers, per [#164](https://github.com/stephencelis/SQLite.swift/issues/164) + * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per + [#30](https://github.com/stephencelis/SQLite.swift/issues/30) + * provide separate threads for update vs read, so updates don't block reads, + per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) + * expose triggers, per + [#164](https://github.com/stephencelis/SQLite.swift/issues/164) ## Suspended Feature Requests -_Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._ +_Features that are not actively being considered, perhaps because of no clean +type-safe way to implement them with the current Swift, or bugs, or just +general uncertainty._ - * provide a mechanism for INSERT INTO multiple values, per [#168](https://github.com/stephencelis/SQLite.swift/issues/168) + * provide a mechanism for INSERT INTO multiple values, per + [#168](https://github.com/stephencelis/SQLite.swift/issues/168) From 1bdd7f3855a5e5f5afc41c5fa07e0126cf8f10dd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 12:54:08 +0200 Subject: [PATCH 0628/1046] Docs: how to get error message Closes #366 --- Documentation/Index.md | 13 +++++++++---- Sources/SQLite/Core/Connection.swift | 7 +++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index aa219633..8ac564a8 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -650,21 +650,26 @@ follow similar patterns. > // INSERT INTO "timestamps" DEFAULT VALUES > ``` -### Handling specific SQLite errors +### Handling SQLite errors -You can pattern match on the error to selectively catch SQLite errors: +You can pattern match on the error to selectively catch SQLite errors. For example, to +specifically handle constraint errors ([SQLITE_CONSTRAINT](https://sqlite.org/rescode.html#constraint)): ```swift do { try db.run(users.insert(email <- "alice@mac.com")) try db.run(users.insert(email <- "alice@mac.com")) -} catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { - print("constraint failed") +} catch let Result.error(message, code, statement) where code == SQLITE_CONSTRAINT { + print("constraint failed: \(message), in \(statement)") } catch let error { print("insertion failed: \(error)") } ``` +The `Result.error` type contains the English-language text that describes the error (`message`), +the error `code` (see [SQLite result code list](https://sqlite.org/rescode.html#primary_result_code_list) +for details) and a optional reference to the `statement` which produced the error. + ### Setters SQLite.swift typically uses the `<-` operator to set values during [inserts diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index d2f5f523..f7513115 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -681,6 +681,13 @@ public enum Result : Error { fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + /// Represents a SQLite specific [error code](https://sqlite.org/rescode.html) + /// + /// - message: English-language text that describes the error + /// + /// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list) + /// + /// - statement: the statement which produced the error case error(message: String, code: Int32, statement: Statement?) init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { From e9d93b5913e3e1326daee7a6bb9acabb874956de Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 14:02:48 +0200 Subject: [PATCH 0629/1046] Missing import --- Tests/SQLiteTests/QueryTests.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index cd609708..2a9e4ecb 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -1,4 +1,13 @@ import XCTest +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif @testable import SQLite class QueryTests : XCTestCase { From 56d3a65e99cba3af5ee07795822dff1dfb3436d6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 14:53:07 +0200 Subject: [PATCH 0630/1046] Clear out trace --- Tests/SQLiteTests/ConnectionTests.swift | 2 +- Tests/SQLiteTests/TestHelpers.swift | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ac738961..ad902ed3 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -157,7 +157,7 @@ class ConnectionTests : SQLiteTestCase { func test_transaction_rollsBackTransactionsIfCommitsFail() { // This test case needs to emulate an environment where the individual statements succeed, but committing the - // transactuin fails. Using deferred foreign keys is one option to achieve this. + // transaction fails. Using deferred foreign keys is one option to achieve this. try! db.execute("PRAGMA foreign_keys = ON;") try! db.execute("PRAGMA defer_foreign_keys = ON;") let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index dc32bb83..bec6f751 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -2,15 +2,13 @@ import XCTest @testable import SQLite class SQLiteTestCase : XCTestCase { - - var trace = [String: Int]() - - let db = try! Connection() - + private var trace:[String: Int]! let users = Table("users") + let db = try! Connection() override func setUp() { super.setUp() + trace = [String:Int]() db.trace { SQL in print(SQL) From 7cc98a8d006caa35475f744944ad2b30b97f8e4e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 16:10:36 +0200 Subject: [PATCH 0631/1046] Recreate connection --- Tests/SQLiteTests/ConnectionTests.swift | 4 ++-- Tests/SQLiteTests/TestHelpers.swift | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ad902ed3..b0be9a72 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -200,7 +200,7 @@ class ConnectionTests : SQLiteTestCase { } func test_savepoint_beginsAndCommitsSavepoints() { - let db = self.db + let db:Connection = self.db try! db.savepoint("1") { try db.savepoint("2") { @@ -218,7 +218,7 @@ class ConnectionTests : SQLiteTestCase { } func test_savepoint_beginsAndRollsSavepointsBack() { - let db = self.db + let db:Connection = self.db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index bec6f751..7f790ef7 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -3,11 +3,12 @@ import XCTest class SQLiteTestCase : XCTestCase { private var trace:[String: Int]! + var db:Connection! let users = Table("users") - let db = try! Connection() override func setUp() { super.setUp() + db = try! Connection() trace = [String:Int]() db.trace { SQL in From 616e5bde44191e40f4722eada2e924216861c5cd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 17:33:36 +0200 Subject: [PATCH 0632/1046] https://sqlite.org/pragma.html#pragma_defer_foreign_keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “The defer_foreign_keys pragma is automatically switched off at each COMMIT or ROLLBACK. Hence, the defer_foreign_keys pragma must be separately enabled for each transaction. This pragma is only meaningful if foreign key constraints are enabled, of course.” --- Tests/SQLiteTests/ConnectionTests.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index b0be9a72..f1af1fd0 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -159,14 +159,18 @@ class ConnectionTests : SQLiteTestCase { // This test case needs to emulate an environment where the individual statements succeed, but committing the // transaction fails. Using deferred foreign keys is one option to achieve this. try! db.execute("PRAGMA foreign_keys = ON;") - try! db.execute("PRAGMA defer_foreign_keys = ON;") let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) do { try db.transaction { + try db.execute("PRAGMA defer_foreign_keys = ON;") try stmt.run() } - } catch { + XCTFail("expected error") + } catch let Result.error(_, code, _) { + XCTAssertEqual(SQLITE_CONSTRAINT, code) + } catch let error { + XCTFail("unexpected error: \(error)") } AssertSQL("BEGIN DEFERRED TRANSACTION") From 1d6167fe877c327a29bbf2c4b7343f99d36bedae Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 22:31:33 +0200 Subject: [PATCH 0633/1046] Swift 4 --- Tests/SQLiteTests/TestHelpers.swift | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 7f790ef7..271f4166 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -13,21 +13,22 @@ class SQLiteTestCase : XCTestCase { db.trace { SQL in print(SQL) - self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 + self.trace[SQL, default: 0] += 1 } } func CreateUsersTable() { - try! db.execute( - "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "salary REAL, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" + try! db.execute(""" + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + age INTEGER, + salary REAL, + admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), + manager_id INTEGER, + FOREIGN KEY(manager_id) REFERENCES users(id) + ) + """ ) } From 2e59493201d19cd6be0f53a080e26729b686d5c4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 28 Sep 2017 22:32:41 +0200 Subject: [PATCH 0634/1046] Skip tests for older versions of SQLite --- Tests/SQLiteTests/ConnectionTests.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index f1af1fd0..d74a50c1 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -156,14 +156,21 @@ class ConnectionTests : SQLiteTestCase { } func test_transaction_rollsBackTransactionsIfCommitsFail() { + let sqliteVersion = String(describing: try! db.scalar("SELECT sqlite_version()")!) + .split(separator: ".").flatMap { Int($0) } + // PRAGMA defer_foreign_keys only supported in SQLite >= 3.8.0 + guard sqliteVersion[0] == 3 && sqliteVersion[1] >= 8 else { + NSLog("skipping test for SQLite version \(sqliteVersion)") + return + } // This test case needs to emulate an environment where the individual statements succeed, but committing the // transaction fails. Using deferred foreign keys is one option to achieve this. try! db.execute("PRAGMA foreign_keys = ON;") + try! db.execute("PRAGMA defer_foreign_keys = ON;") let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) do { try db.transaction { - try db.execute("PRAGMA defer_foreign_keys = ON;") try stmt.run() } XCTFail("expected error") From 42727c2a3d8024bc081db0f0d16d3d46b64afec2 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:21:48 +0200 Subject: [PATCH 0635/1046] Add issue template --- .github/ISSUE_TEMPLATE.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..61152bad --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ +> Issues are used to track bugs and feature requests. +> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). + +## Build Information + +- Include the SQLite.swift version, commit or branch experiencing the issue. +- Mention Xcode and OS X versions affected. +- How do do you integrate SQLite.swift in your project? + - manual + - CocoaPods + - Carthage + - Swift Package manager + +## General guidelines + +- Be as descriptive as possible. +- Provide as much information needed to _reliably reproduce_ the issue. +- Attach screenshots if possible. +- Better yet: attach GIFs or link to video. +- Even better: link to a sample project exhibiting the issue. From fd9b42e5d8657680bd2073c0c1cde139cb8120a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:21:54 +0200 Subject: [PATCH 0636/1046] Bump ruby version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ff85f9a..4f17b26f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -rvm: 2.2 +rvm: 2.3 osx_image: xcode9 env: global: From 2e8611fe06f8125668e346dad8a51218d901083d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:21:58 +0200 Subject: [PATCH 0637/1046] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a920d5..41006b82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.11.4 (xx-09-2017), [diff][diff-0.11.4] +0.11.4 (30-09-2017), [diff][diff-0.11.4] ======================================== * Collate .nocase strictly enforces NOT NULL even when using Optional ([#697][]) From ddb8049a58822768b3c1208ea58c8ba18a99952a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:28:28 +0200 Subject: [PATCH 0638/1046] Warn about `update()` (#733) --- Documentation/Index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 8ac564a8..a20087ea 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1521,10 +1521,13 @@ There are two other parameters also available to this method: Queries have a method to allow updating an Encodable type. ```swift -try db.run(users.update(user)) +try db.run(users.filter(id == userId).update(user)) ``` +> ⚠ Unless filtered, using the update method on an instance of a Codable +> type updates all table rows. + There are two other parameters also available to this method: - `userInfo` is a dictionary that is passed to the encoder and made available From ae406afd75e72a0122e76b44c9abb5f6a2432cb1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 07:28:54 +0200 Subject: [PATCH 0639/1046] WS --- .github/ISSUE_TEMPLATE.md | 4 ++-- Tests/SQLiteTests/TestHelpers.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 61152bad..9304a672 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,11 +1,11 @@ -> Issues are used to track bugs and feature requests. +> Issues are used to track bugs and feature requests. > Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). ## Build Information - Include the SQLite.swift version, commit or branch experiencing the issue. - Mention Xcode and OS X versions affected. -- How do do you integrate SQLite.swift in your project? +- How do do you integrate SQLite.swift in your project? - manual - CocoaPods - Carthage diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 271f4166..8d60362c 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -20,9 +20,9 @@ class SQLiteTestCase : XCTestCase { func CreateUsersTable() { try! db.execute(""" CREATE TABLE users ( - id INTEGER PRIMARY KEY, - email TEXT NOT NULL UNIQUE, - age INTEGER, + id INTEGER PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + age INTEGER, salary REAL, admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), manager_id INTEGER, From e12bd38d6f1d6de67efa7ce7894c663e15a232c9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 12:27:57 +0200 Subject: [PATCH 0640/1046] Modernize --- Tests/SQLiteTests/ConnectionTests.swift | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 05d2676b..03f00719 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -382,31 +382,28 @@ class ConnectionTests : SQLiteTestCase { _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) AssertThrows(try stmt.run()) } - + func test_concurrent_access_single_connection() { - let conn = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Connection Tests.sqlite") + let conn = try! Connection("\(NSTemporaryDirectory())/\(UUID().uuidString)") try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! conn.run("INSERT INTO test(value) VALUES(?)", 0) - - let q = dispatch_queue_create("Readers", DISPATCH_QUEUE_CONCURRENT); - - var reads = [0, 0, 0, 0, 0] + let queue = DispatchQueue(label: "Readers", attributes: [.concurrent]) + let nReaders = 5 + var reads = Array(repeating: 0, count: nReaders) var finished = false - for index in 0..<5 { - dispatch_async(q) { + for index in 0.. 500) } } - } - + } } From 73f4472097a1acdbba42d5ab8805d50a181589e3 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 16:49:22 +0200 Subject: [PATCH 0641/1046] Changelog --- CHANGELOG.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41006b82..514de7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,25 @@ 0.11.4 (30-09-2017), [diff][diff-0.11.4] ======================================== -* Collate .nocase strictly enforces NOT NULL even when using Optional ([#697][]) +* Collate `.nocase` strictly enforces `NOT NULL` even when using Optional ([#697][]) * Fix transactions not being rolled back when committing fails ([#426][]) * Add possibility to have expression on right hand side of like ([#591][]) * Added Date and Time functions ([#142][]) * Add Swift4 Coding support ([#733][]) * Preliminary Linux support ([#315][], [#681][]) * Add `RowIterator` for more safety ([#647][], [#726][]) -* Make Row.get throw instead of crash ([#649][]) +* Make `Row.get` throw instead of crash ([#649][]) * Fix create/drop index functions ([#666][]) -* Set deployment target to 8.0 (#624, #671, #717) +* Revert deployment target to 8.0 ([#625][], [#671][], [#717][]) * Added support for the union query clause ([#723][]) -* Add support for ORDER and LIMIT on UPDATE and DELETE ([#657][], [#722][]) +* Add support for `ORDER` and `LIMIT` on `UPDATE` and `DELETE` ([#657][], [#722][]) * Swift 4 support ([#668][]) 0.11.3 (30-03-2017), [diff][diff-0.11.3] ======================================== * Fix compilation problems when using Carthage ([#615][]) -* Add "WITHOUT ROWID" table option ([#541][]) +* Add `WITHOUT ROWID` table option ([#541][]) * Argument count fixed for binary custom functions ([#481][]) * Documentation updates * Tested with Xcode 8.3 / iOS 10.3 @@ -37,7 +37,7 @@ * Integrate SQLCipher via CocoaPods ([#546][], [#553][]) * Made lastInsertRowid consistent with other SQLite wrappers ([#532][]) -* Fix for ~= operator used with Double ranges +* Fix for `~=` operator used with Double ranges * Various documentation updates 0.11.0 (19-10-2016) @@ -67,13 +67,16 @@ [#571]: https://github.com/stephencelis/SQLite.swift/issues/571 [#591]: https://github.com/stephencelis/SQLite.swift/pull/591 [#615]: https://github.com/stephencelis/SQLite.swift/pull/615 +[#625]: https://github.com/stephencelis/SQLite.swift/issues/625 [#647]: https://github.com/stephencelis/SQLite.swift/pull/647 [#649]: https://github.com/stephencelis/SQLite.swift/pull/649 [#657]: https://github.com/stephencelis/SQLite.swift/issues/657 [#666]: https://github.com/stephencelis/SQLite.swift/pull/666 [#668]: https://github.com/stephencelis/SQLite.swift/pull/668 +[#671]: https://github.com/stephencelis/SQLite.swift/issues/671 [#681]: https://github.com/stephencelis/SQLite.swift/issues/681 [#697]: https://github.com/stephencelis/SQLite.swift/issues/697 +[#717]: https://github.com/stephencelis/SQLite.swift/issues/717 [#722]: https://github.com/stephencelis/SQLite.swift/pull/722 [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 From 1c929b9e8071a6e144cc928c8077f765a6c7424c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 16:51:41 +0200 Subject: [PATCH 0642/1046] Link --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a9ceecb..3900c0a3 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,13 @@ syntax _and_ intent. - [Full-text search][] support - [Well-documented][See Documentation] - Extensively tested - - SQLCipher support via CocoaPods + - [SQLCipher][] support via CocoaPods - Active support at [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) +[SQLCipher]: https://www.zetetic.net/sqlcipher/ [Full-text search]: Documentation/Index.md#full-text-search [See Documentation]: Documentation/Index.md#sqliteswift-documentation From 172b3e55284de4f05c13b8d0e45702b3a2c270ca Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 16:53:53 +0200 Subject: [PATCH 0643/1046] TOC --- Documentation/Index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index a20087ea..3ccc6e21 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -20,6 +20,7 @@ - [Column Constraints](#column-constraints) - [Table Constraints](#table-constraints) - [Inserting Rows](#inserting-rows) + - [Handling SQLite errors](#handling-sqlite-errors) - [Setters](#setters) - [Selecting Rows](#selecting-rows) - [Iterating and Accessing Values](#iterating-and-accessing-values) From d68e0a761e6fafd40030887c24f54ee08f3f03a8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:05:51 +0200 Subject: [PATCH 0644/1046] Document throwing `Row.get()` --- Documentation/Index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3ccc6e21..be3d32a9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -757,6 +757,18 @@ for user in try db.prepare(users) { promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. +⚠ Column subscripts on `Row` will force try and abort execution in error cases +If you want to handle this yourself, use `Row.get(_ column: Expression)`: + +```swift +for user in try db.prepare(users) { + do { + print("name: \(try user.get(name))") + } catch { + // handle + } +} +``` ### Plucking Rows From e0eb663c114c579226422ccb41324c32d2841381 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:16:56 +0200 Subject: [PATCH 0645/1046] Doc --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index be3d32a9..88ed24a4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1406,7 +1406,7 @@ The `Datatype` must be one of the basic Swift types that values are bridged through before serialization and deserialization (see [Building Type-Safe SQL ](#building-type-safe-sql) for a list of types). -> _Note:_ `Binding` is a protocol that SQLite.swift uses internally to +> ⚠ _Note:_ `Binding` is a protocol that SQLite.swift uses internally to > directly map SQLite types to Swift types. **Do _not_** conform custom types > to the `Binding` protocol. From d80a5ef128338a1bfea2ed809c4ed5623a01bb44 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:17:27 +0200 Subject: [PATCH 0646/1046] Support partial ranges for completeness --- Sources/SQLite/Typed/Operators.swift | 24 ++++++++++++++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 15 +++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 5f95993f..d97e52b9 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -490,6 +490,30 @@ public func ~=(lhs: Range, rhs: Expression) -> Expression= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) +} + +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { + return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) +} + // MARK: - public func &&(lhs: Expression, rhs: Expression) -> Expression { diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 08e679c1..4dcb6717 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -260,6 +260,21 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) } + func test_patternMatchingOperator_withComparablePartialRangeThrough_buildsBooleanExpression() { + AssertSQL("\"double\" <= 4.5", ...4.5 ~= double) + AssertSQL("\"doubleOptional\" <= 4.5", ...4.5 ~= doubleOptional) + } + + func test_patternMatchingOperator_withComparablePartialRangeUpTo_buildsBooleanExpression() { + AssertSQL("\"double\" < 4.5", ..<4.5 ~= double) + AssertSQL("\"doubleOptional\" < 4.5", ..<4.5 ~= doubleOptional) + } + + func test_patternMatchingOperator_withComparablePartialRangeFrom_buildsBooleanExpression() { + AssertSQL("\"double\" >= 4.5", 4.5... ~= double) + AssertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) + } + func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) From 07df3355f45c443704fc973899fabc6a37f9e506 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:21:33 +0200 Subject: [PATCH 0647/1046] No longer applies --- Documentation/Index.md | 47 ------------------------------------------ 1 file changed, 47 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 88ed24a4..476bc816 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -50,7 +50,6 @@ - [Custom Types](#custom-types) - [Date-Time Values](#date-time-values) - [Binary Data](#binary-data) - - [Custom Type Caveats](#custom-type-caveats) - [Codable Types](#codable-types) - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) @@ -1410,9 +1409,6 @@ through before serialization and deserialization (see [Building Type-Safe SQL > directly map SQLite types to Swift types. **Do _not_** conform custom types > to the `Binding` protocol. -Once extended, the type can be used [_almost_](#custom-type-caveats) wherever -typed expressions can be. - ### Date-Time Values @@ -1458,49 +1454,6 @@ extension UIImage: Value { [Archives and Serializations Programming Guide]: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Archiving/Archiving.html -### Custom Type Caveats - -Swift does _not_ currently support generic subscripting, which means we -cannot, by default, subscript Expressions with custom types to: - - 1. **Namespace expressions**. Use the `namespace` function, instead: - - ```swift - let avatar = Expression("avatar") - users[avatar] // fails to compile - users.namespace(avatar) // "users"."avatar" - ``` - - 2. **Access column data**. Use the `get` function, instead: - - ```swift - let user = users.first! - user[avatar] // fails to compile - user.get(avatar) // UIImage? - ``` - -We can, of course, write extensions, but they’re rather wordy. - -```swift -extension Query { - subscript(column: Expression) -> Expression { - return namespace(column) - } - subscript(column: Expression) -> Expression { - return namespace(column) - } -} - -extension Row { - subscript(column: Expression) -> UIImage { - return get(column) - } - subscript(column: Expression) -> UIImage? { - return get(column) - } -} -``` - ## Codable Types [Codable types][Encoding and Decoding Custom Types] were introduced as a part From fa041f8f0b5949ca110e049949d0c470f92b811a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:25:28 +0200 Subject: [PATCH 0648/1046] Docs --- Documentation/Index.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 476bc816..c97fe8ca 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -756,7 +756,7 @@ for user in try db.prepare(users) { promise to the compiler that they’ll never be `NULL`), while `Expression` values remain wrapped. -⚠ Column subscripts on `Row` will force try and abort execution in error cases +⚠ Column subscripts on `Row` will force try and abort execution in error cases. If you want to handle this yourself, use `Row.get(_ column: Expression)`: ```swift @@ -1464,10 +1464,10 @@ the insertion, updating, and retrieval of basic Codable types. ### Inserting Codable Types -Queries have a method to allow inserting an Encodable type. +Queries have a method to allow inserting an [Encodable][] type. ```swift -struct User: Codable { +struct User: Encodable { let name: String } try db.run(users.insert(User(name: "test"))) @@ -1482,6 +1482,8 @@ There are two other parameters also available to this method: - `otherSetters` allows you to specify additional setters on top of those that are generated from the encodable types themselves. +[Encodable]: https://developer.apple.com/documentation/swift/encodable + ### Updating Codable Types Queries have a method to allow updating an Encodable type. @@ -1504,7 +1506,7 @@ There are two other parameters also available to this method: ### Retrieving Codable Types -Rows have a method to decode a Decodable type. +Rows have a method to decode a [Decodable][] type. ```swift let loadedUsers: [User] = try db.prepare(users).map { row in @@ -1548,6 +1550,8 @@ Both of the above methods also have the following optional parameter: - `userInfo` is a dictionary that is passed to the decoder and made available to decodable types to allow customizing their behavior. +[Decodable]: https://developer.apple.com/documentation/swift/decodable + ### Restrictions There are a few restrictions on using Codable types: From 4ea2f62c453a7a82a1d9ddbd85d28a321d767ec4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:32:21 +0200 Subject: [PATCH 0649/1046] Typos --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 514de7be..578b59c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,12 +52,12 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 -[#142]: https://github.com/stephencelis/SQLit1e.swift/issues/142 -[#315]: https://github.com/stephencelis/SQLit1e.swift/issues/315 -[#426]: https://github.com/stephencelis/SQLit1e.swift/pull/426 -[#481]: https://github.com/stephencelis/SQLit1e.swift/pull/481 -[#532]: https://github.com/stephencelis/SQLit1e.swift/issues/532 -[#541]: https://github.com/stephencelis/SQLit1e.swift/issues/541 +[#142]: https://github.com/stephencelis/SQLite.swift/issues/142 +[#315]: https://github.com/stephencelis/SQLite.swift/issues/315 +[#426]: https://github.com/stephencelis/SQLite.swift/pull/426 +[#481]: https://github.com/stephencelis/SQLite.swift/pull/481 +[#532]: https://github.com/stephencelis/SQLite.swift/issues/532 +[#541]: https://github.com/stephencelis/SQLite.swift/issues/541 [#546]: https://github.com/stephencelis/SQLite.swift/issues/546 [#548]: https://github.com/stephencelis/SQLite.swift/pull/548 [#553]: https://github.com/stephencelis/SQLite.swift/pull/553 From 9d8a8e5a7fff77486726f47f0dfc2c6994a57a31 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 29 Sep 2017 17:43:31 +0200 Subject: [PATCH 0650/1046] Typo --- Tests/SQLiteTests/OperatorsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 4dcb6717..948eb0a4 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -275,7 +275,7 @@ class OperatorsTests : XCTestCase { AssertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) } - func test_patternMatchingOperator_withomparableClosedRangeString_buildsBetweenBooleanExpression() { + func test_patternMatchingOperator_withComparableClosedRangeString_buildsBetweenBooleanExpression() { AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) } From f0e3442e98862c16a129d1ab00ccd3a82555b6c1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 2 Oct 2017 16:25:40 +0200 Subject: [PATCH 0651/1046] No longer needed --- SQLite.xcodeproj/project.pbxproj | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index cb776608..df603862 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1032,8 +1032,6 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; - "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; @@ -1056,8 +1054,6 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - "SWIFT_INCLUDE_PATHS[sdk=appletvos*]" = "$(SRCROOT)/CocoaPods/appletvos"; - "SWIFT_INCLUDE_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/CocoaPods/appletvsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TVOS_DEPLOYMENT_TARGET = 9.1; @@ -1109,8 +1105,6 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; - "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; - "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; @@ -1135,8 +1129,6 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; - "SWIFT_INCLUDE_PATHS[sdk=watchos*]" = "$(SRCROOT)/CocoaPods/watchos"; - "SWIFT_INCLUDE_PATHS[sdk=watchsimulator*]" = "$(SRCROOT)/CocoaPods/watchsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 4; @@ -1271,10 +1263,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/CocoaPods/iphoneos"; - "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; - "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; - "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; @@ -1298,10 +1286,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - "SWIFT_INCLUDE_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/CocoaPods/iphoneos"; - "SWIFT_INCLUDE_PATHS[sdk=iphoneos10.0]" = "$(SRCROOT)/CocoaPods/iphoneos-10.0"; - "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/CocoaPods/iphonesimulator"; - "SWIFT_INCLUDE_PATHS[sdk=iphonesimulator10.0]" = "$(SRCROOT)/CocoaPods/iphonesimulator-10.0"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; @@ -1353,9 +1337,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; - "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; - "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; - "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; @@ -1381,9 +1362,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; - "SWIFT_INCLUDE_PATHS[sdk=macosx*]" = "$(SRCROOT)/CocoaPods/macosx"; - "SWIFT_INCLUDE_PATHS[sdk=macosx10.11]" = "$(SRCROOT)/CocoaPods/macosx-10.11"; - "SWIFT_INCLUDE_PATHS[sdk=macosx10.12]" = "$(SRCROOT)/CocoaPods/macosx-10.12"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; }; From dde0af2e26f6f50ca9119f43f3b87c9aeea938fe Mon Sep 17 00:00:00 2001 From: Camilleri Alexandre Date: Mon, 2 Oct 2017 16:56:57 +0200 Subject: [PATCH 0652/1046] Corected HEIFIamge to HEIFImage Typo --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index c97fe8ca..efb90ae6 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1519,7 +1519,7 @@ for example if you are using the [Facade pattern](https://en.wikipedia.org/wiki/Facade_pattern) to hide subclasses behind a super class. For example, you may want to encode an Image type that can be multiple different formats such as PNGImage, JPGImage, or -HEIFIamge. You will need to determine the correct subclass before you know +HEIFImage. You will need to determine the correct subclass before you know which type to decode. ```swift From 35bb87c04c0152c7ac857da277742661f9d6c55b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 9 Oct 2017 16:55:06 +0200 Subject: [PATCH 0653/1046] See #742 --- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme | 5 +++-- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme | 3 +-- SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 2c3c431f..f1fa216c 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -26,8 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + language = "" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -56,6 +56,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme index fb00f5bb..dca3ccea 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -27,8 +27,7 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" language = "" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme index cfa4e0fa..be753151 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite tvOS.xcscheme @@ -26,8 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + language = "" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -56,6 +56,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" From 83e0ad85c5b100a06467edd68e71fe3fe7dc1e37 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 17 Oct 2017 10:57:52 -0700 Subject: [PATCH 0654/1046] Do not use .characters on Strings. Prevents warnings in Xcode 9.1 by adopting proper Swift 4 syntax --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Typed/Expression.swift | 2 +- Sources/SQLite/Typed/Query.swift | 2 +- Sources/SQLite/Typed/Schema.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f7513115..1bbf7f73 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -577,7 +577,7 @@ public final class Connection { } else if let result = result as? Int64 { sqlite3_result_int64(context, result) } else if let result = result as? String { - sqlite3_result_text(context, result, Int32(result.characters.count), SQLITE_TRANSIENT) + sqlite3_result_text(context, result, Int32(result.count), SQLITE_TRANSIENT) } else if result == nil { sqlite3_result_null(context) } else { diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 33329b73..d89ee6cc 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -77,7 +77,7 @@ extension Expressible { public func asSQL() -> String { let expressed = expression var idx = 0 - return expressed.template.characters.reduce("") { template, character in + return expressed.template.reduce("") { template, character in let transcoded: String if character == "?" { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 17ec715a..1d04b797 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -938,7 +938,7 @@ extension Connection { private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { var (columnNames, idx) = ([String: Int](), 0) column: for each in query.clauses.select.columns { - var names = each.expression.template.characters.split { $0 == "." }.map(String.init) + var names = each.expression.template.split { $0 == "." }.map(String.init) let column = names.removeLast() let namespace = names.joined(separator: ".") diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 46a1f87a..60b7be8a 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -148,7 +148,7 @@ extension Table { fileprivate func indexName(_ columns: [Expressible]) -> Expressible { let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joined(separator: " ").lowercased() - let index = string.characters.reduce("") { underscored, character in + let index = string.reduce("") { underscored, character in guard character != "\"" else { return underscored } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 03f00719..7bb86e41 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -369,7 +369,7 @@ class ConnectionTests : SQLiteTestCase { } func test_interrupt_interruptsLongRunningQuery() { - try! InsertUsers("abcdefghijklmnopqrstuvwxyz".characters.map { String($0) }) + try! InsertUsers("abcdefghijklmnopqrstuvwxyz".map { String($0) }) db.createFunction("sleep") { args in usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) return nil From 5dceef2028991e7984ec35630dae802335381085 Mon Sep 17 00:00:00 2001 From: Vinay Ganesh Date: Mon, 6 Nov 2017 22:10:46 -0800 Subject: [PATCH 0655/1046] Resolved the warning that was raised because "characters" of a "String" has been deprecated. --- Sources/SQLite/Helpers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 64e5eca8..ac831667 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -56,7 +56,7 @@ let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) extension String { func quote(_ mark: Character = "\"") -> String { - let escaped = characters.reduce("") { string, character in + let escaped = reduce("") { string, character in string + (character == mark ? "\(mark)\(mark)" : "\(character)") } return "\(mark)\(escaped)\(mark)" From f40ca4da2f02204fdc5e3d317014062fac55326a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Apr 2018 18:04:41 +0200 Subject: [PATCH 0656/1046] Fix Swift 4.1 warnings --- .travis.yml | 2 +- SQLite.xcodeproj/project.pbxproj | 6 +++++- .../xcshareddata/xcschemes/SQLite Mac.xcscheme | 4 +--- .../xcshareddata/xcschemes/SQLite iOS.xcscheme | 4 +--- .../xcschemes/SQLite tvOS.xcscheme | 4 +--- .../xcschemes/SQLite watchOS.xcscheme | 2 +- Sources/SQLite/Typed/Coding.swift | 2 +- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- Sources/SQLite/Typed/Query.swift | 8 ++++---- Sources/SQLite/Typed/Schema.swift | 18 +++++++++--------- Tests/SQLiteTests/BlobTests.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 2 +- Tests/SQLiteTests/FTS4Tests.swift | 2 +- 13 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4f17b26f..41389849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c rvm: 2.3 -osx_image: xcode9 +osx_image: xcode9.3 env: global: - IOS_SIMULATOR="iPhone 6s" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index df603862..2015b8ff 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -680,7 +680,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 0930; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -1148,12 +1148,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -1206,12 +1208,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index f1fa216c..2691862e 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ Bool { diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 1afe4e87..d7995b95 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -637,7 +637,7 @@ extension ExpressionType where UnderlyingType == String? { } -extension Collection where Iterator.Element : Value, IndexDistance == Int { +extension Collection where Iterator.Element : Value { /// Builds a copy of the expression prepended with an `IN` check against the /// collection. diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 1d04b797..f6ef6df8 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -647,7 +647,7 @@ extension QueryType { whereClause ] - return Insert(" ".join(clauses.flatMap { $0 }).expression) + return Insert(" ".join(clauses.compactMap { $0 }).expression) } /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. @@ -690,7 +690,7 @@ extension QueryType { limitOffsetClause ] - return Update(" ".join(clauses.flatMap { $0 }).expression) + return Update(" ".join(clauses.compactMap { $0 }).expression) } // MARK: DELETE @@ -704,7 +704,7 @@ extension QueryType { limitOffsetClause ] - return Delete(" ".join(clauses.flatMap { $0 }).expression) + return Delete(" ".join(clauses.compactMap { $0 }).expression) } // MARK: EXISTS @@ -789,7 +789,7 @@ extension QueryType { limitOffsetClause ] - return " ".join(clauses.flatMap { $0 }).expression + return " ".join(clauses.compactMap { $0 }).expression } } diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 60b7be8a..62c90702 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -47,7 +47,7 @@ extension Table { withoutRowid ? Expression(literal: "WITHOUT ROWID") : nil ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } public func create(_ query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { @@ -57,7 +57,7 @@ extension Table { query ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - ALTER TABLE … ADD COLUMN @@ -135,7 +135,7 @@ extension Table { "".wrap(columns) as Expression ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - DROP INDEX @@ -174,7 +174,7 @@ extension View { query ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - DROP VIEW @@ -196,7 +196,7 @@ extension VirtualTable { using ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } // MARK: - ALTER TABLE … RENAME TO @@ -405,7 +405,7 @@ public final class TableBuilder { delete.map { Expression(literal: "ON DELETE \($0.rawValue)") } ] - definitions.append(" ".join(clauses.flatMap { $0 })) + definitions.append(" ".join(clauses.compactMap { $0 })) } } @@ -456,7 +456,7 @@ private extension QueryType { name ] - return " ".join(clauses.flatMap { $0 }) + return " ".join(clauses.compactMap { $0 }) } func rename(to: Self) -> String { @@ -475,7 +475,7 @@ private extension QueryType { name ] - return " ".join(clauses.flatMap { $0 }).asSQL() + return " ".join(clauses.compactMap { $0 }).asSQL() } } @@ -493,7 +493,7 @@ private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: collate.map { " ".join([Expression(literal: "COLLATE"), $0]) } ] - return " ".join(clauses.flatMap { $0 }) + return " ".join(clauses.compactMap { $0 }) } private func reference(_ primary: (QueryType, Expressible)) -> Expressible { diff --git a/Tests/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/BlobTests.swift index fbcca9bc..817205d6 100644 --- a/Tests/SQLiteTests/BlobTests.swift +++ b/Tests/SQLiteTests/BlobTests.swift @@ -16,7 +16,7 @@ class BlobTests : XCTestCase { func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) - pointer.initialize(to: 42, count: 3) + pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) XCTAssertEqual(blob.bytes, [42, 42, 42]) } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 7bb86e41..7fc26110 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -157,7 +157,7 @@ class ConnectionTests : SQLiteTestCase { func test_transaction_rollsBackTransactionsIfCommitsFail() { let sqliteVersion = String(describing: try! db.scalar("SELECT sqlite_version()")!) - .split(separator: ".").flatMap { Int($0) } + .split(separator: ".").compactMap { Int($0) } // PRAGMA defer_foreign_keys only supported in SQLite >= 3.8.0 guard sqliteVersion[0] == 3 && sqliteVersion[1] >= 8 else { NSLog("skipping test for SQLite version \(sqliteVersion)") diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index 4373bf8b..79f0a8e2 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -184,7 +184,7 @@ class FTS4IntegrationTests : SQLiteTestCase { let locale = CFLocaleCopyCurrent() let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "" as CFString!, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) try! db.registerTokenizer(tokenizerName) { string in CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString))) if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { From 1ed747ec930889de2b6e79451a8c37eea3109326 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Apr 2018 18:43:32 +0200 Subject: [PATCH 0657/1046] Fix iOS version --- .travis.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 41389849..f278301d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ osx_image: xcode9.3 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="11.0" + - IOS_VERSION="11.3" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Makefile b/Makefile index ebd38494..cd4afb8d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 11.0 +IOS_VERSION = 11.3 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else From be9fc789c3772edaef83000deda20ab777f607f7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Apr 2018 23:10:41 +0200 Subject: [PATCH 0658/1046] Update bundle --- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 54 +++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 04f0155a..3b9d6ba5 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.3.1' +gem 'cocoapods', '~> 1.5.0' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index 47a2db58..de03030a 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,73 +1,77 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.5) - activesupport (4.2.9) + CFPropertyList (3.0.0) + activesupport (4.2.10) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + atomos (0.1.2) claide (1.0.2) - cocoapods (1.3.1) + cocoapods (1.5.0) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.3.1) - cocoapods-deintegrate (>= 1.0.1, < 2.0) - cocoapods-downloader (>= 1.1.3, < 2.0) + cocoapods-core (= 1.5.0) + cocoapods-deintegrate (>= 1.0.2, < 2.0) + cocoapods-downloader (>= 1.2.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.2.0, < 2.0) + cocoapods-trunk (>= 1.3.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.5.7) + molinillo (~> 0.6.5) nap (~> 1.0) ruby-macho (~> 1.1) - xcodeproj (>= 1.5.1, < 2.0) - cocoapods-core (1.3.1) + xcodeproj (>= 1.5.7, < 2.0) + cocoapods-core (1.5.0) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.1) - cocoapods-downloader (1.1.3) + cocoapods-deintegrate (1.0.2) + cocoapods-downloader (1.2.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.2.0) + cocoapods-trunk (1.3.0) nap (>= 0.8, < 2.0) - netrc (= 0.7.8) + netrc (~> 0.11) cocoapods-try (1.1.0) colored2 (3.1.2) + concurrent-ruby (1.0.5) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) - gh_inspector (1.0.3) - i18n (0.8.6) + gh_inspector (1.1.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) minitest (5.10.1) - molinillo (0.5.7) - nanaimo (0.2.3) + molinillo (0.6.5) + nanaimo (0.2.5) nap (1.1.0) - netrc (0.7.8) + netrc (0.11.0) ruby-macho (1.1.0) thread_safe (0.3.6) - tzinfo (1.2.3) + tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.5.1) - CFPropertyList (~> 2.3.3) + xcodeproj (1.5.7) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.2) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.3) + nanaimo (~> 0.2.4) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.3.1) + cocoapods (~> 1.5.0) minitest BUNDLED WITH - 1.13.6 + 1.16.1 From afd58d3ba205c4b0065528f5c1180e0685ee1388 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Apr 2018 01:21:48 +0200 Subject: [PATCH 0659/1046] =?UTF-8?q?Don=E2=80=99t=20run=20iOS=20tests=20o?= =?UTF-8?q?n=20oldest=20sim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “Early unexpected exit, operation never finished bootstrapping” --- Tests/CocoaPods/integration_test.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 9792b570..4bec8d40 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -39,5 +39,32 @@ def test_pod # https://github.com/CocoaPods/CocoaPods/issues/7009 super unless consumer.platform_name == :watchos end + + def xcodebuild(action, scheme, configuration) + require 'fourflusher' + command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) + case consumer.platform_name + when :osx, :macos + command += %w(CODE_SIGN_IDENTITY=) + when :ios + command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) + command += Fourflusher::SimControl.new.destination(nil, 'iOS', deployment_target) + when :watchos + command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator) + command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target) + when :tvos + command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) + command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target) + end + + begin + _xcodebuild(command, true) + rescue => e + message = 'Returned an unsuccessful exit code.' + message += ' You can use `--verbose` for more information.' unless config.verbose? + error('xcodebuild', message) + e.message + end + end end end From a1d478eb4325a8c980b99afdfc8e4a29dab0c3e0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Apr 2018 23:05:19 +0200 Subject: [PATCH 0660/1046] Exclude test on iOS/tvOS 9.x --- SQLite.xcodeproj/project.pbxproj | 4 ++-- Tests/SQLiteTests/ConnectionTests.swift | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2015b8ff..75b38526 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1063,7 +1063,7 @@ 03A65E6D1C6BB0F60062603F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = Tests/SQLite/Info.plist; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1077,7 +1077,7 @@ 03A65E6E1C6BB0F60062603F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - INFOPLIST_FILE = Tests/SQLite/Info.plist; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 7fc26110..9480e3bb 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -384,6 +384,9 @@ class ConnectionTests : SQLiteTestCase { } func test_concurrent_access_single_connection() { + // test can fail on iOS/tvOS 9.x: SQLite compile-time differences? + guard #available(iOS 10.0, OSX 10.10, tvOS 10.0, watchOS 2.2, *) else { return } + let conn = try! Connection("\(NSTemporaryDirectory())/\(UUID().uuidString)") try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! conn.run("INSERT INTO test(value) VALUES(?)", 0) From d732db052d98d3ac2ef577c0331e6fb93a3a170f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 14 Apr 2018 00:43:48 +0200 Subject: [PATCH 0661/1046] Docs --- .gitmodules | 0 .swift-version | 2 +- CHANGELOG.md | 7 +++++++ Documentation/Index.md | 16 ++++++++-------- README.md | 10 +++++----- SQLite.swift.podspec | 4 ++-- 6 files changed, 23 insertions(+), 16 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/.swift-version b/.swift-version index 5186d070..7d5c902e 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0 +4.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 578b59c2..648cdc07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.11.5 (04-14-2018), [diff][diff-0.11.5] +======================================== + +* Swift 4.1 ([#797][]) + 0.11.4 (30-09-2017), [diff][diff-0.11.4] ======================================== @@ -51,6 +56,7 @@ [diff-0.11.2]: https://github.com/stephencelis/SQLite.swift/compare/0.11.1...0.11.2 [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 +[diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -81,3 +87,4 @@ [#723]: https://github.com/stephencelis/SQLite.swift/pull/723 [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 +[#797]: https://github.com/stephencelis/SQLite.swift/pull/797 diff --git a/Documentation/Index.md b/Documentation/Index.md index efb90ae6..56c8c429 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,8 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 4 (and -> [Xcode 9](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 4.1 (and +> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.4 + github "stephencelis/SQLite.swift" ~> 0.11.5 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.4' + pod 'SQLite.swift', '~> 0.11.5' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.4' + pod 'SQLite.swift/standalone', '~> 0.11.5' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.4' + pod 'SQLite.swift/standalone', '~> 0.11.5' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.4' + pod 'SQLite.swift/SQLCipher', '~> 0.11.5' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") ] ``` diff --git a/README.md b/README.md index 3900c0a3..bc43b6ec 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4 (and [Xcode][] 9). +> _Note:_ SQLite.swift requires Swift 4.1 (and [Xcode][] 9.3). ### Carthage @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.4 + github "stephencelis/SQLite.swift" ~> 0.11.5 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.4' + pod 'SQLite.swift', '~> 0.11.5' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.4") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") ] ``` @@ -285,7 +285,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-4-orange.svg?style=flat +[Swift4Badge]: https://img.shields.io/badge/swift-4.1-orange.svg?style=flat [Swift4Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4a329338..b03a9ce4 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.4" + s.version = "0.11.5" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.0', + 'SWIFT_VERSION' => '4.1', } s.subspec 'standard' do |ss| From 44097d32651d093e826e16ce5d2fa13d4c774c3a Mon Sep 17 00:00:00 2001 From: Dave DeLong Date: Mon, 16 Apr 2018 16:13:04 +0200 Subject: [PATCH 0662/1046] Add `and` and `or` functions When you're programmatically building a query to execute, you have times where you build a variable number of clauses by which you filter a table. There isn't a great way to turn an `Array>` into an `Expression`, short of making a large `(a AND (b AND (c AND (d AND e))))` expression. `and` and `or` fix this by allowing you to turn an `Array>` directly into a lower-complexity clause: `(a AND b AND c AND d AND e)` --- Sources/SQLite/Helpers.swift | 6 +++++- Sources/SQLite/Typed/Operators.swift | 12 ++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index ac831667..b4ff360e 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -73,7 +73,11 @@ extension String { } func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - let expression = Expression(" \(self) ".join([lhs, rhs]).expression) + return infix([lhs, rhs]) + } + + func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { + let expression = Expression(" \(self) ".join(terms).expression) guard wrap else { return expression } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index d97e52b9..a6df717f 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -516,6 +516,12 @@ public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expr // MARK: - +public func and(_ terms: Expression...) -> Expression { + return "AND".infix(terms) +} +public func and(_ terms: [Expression]) -> Expression { + return "AND".infix(terms) +} public func &&(lhs: Expression, rhs: Expression) -> Expression { return "AND".infix(lhs, rhs) } @@ -541,6 +547,12 @@ public func &&(lhs: Bool, rhs: Expression) -> Expression { return "AND".infix(lhs, rhs) } +public func or(_ terms: Expression...) -> Expression { + return "OR".infix(terms) +} +public func or(_ terms: [Expression]) -> Expression { + return "OR".infix(terms) +} public func ||(lhs: Expression, rhs: Expression) -> Expression { return "OR".infix(lhs, rhs) } diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 948eb0a4..050b1e39 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -290,6 +290,16 @@ class OperatorsTests : XCTestCase { AssertSQL("(1 AND \"bool\")", true && bool) AssertSQL("(1 AND \"boolOptional\")", true && boolOptional) } + + func test_andFunction_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and([bool, bool, bool])) + AssertSQL("(\"bool\" AND \"bool\")", and([bool, bool])) + AssertSQL("(\"bool\")", and([bool])) + + AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and(bool, bool, bool)) + AssertSQL("(\"bool\" AND \"bool\")", and(bool, bool)) + AssertSQL("(\"bool\")", and(bool)) + } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { AssertSQL("(\"bool\" OR \"bool\")", bool || bool) @@ -301,6 +311,16 @@ class OperatorsTests : XCTestCase { AssertSQL("(1 OR \"bool\")", true || bool) AssertSQL("(1 OR \"boolOptional\")", true || boolOptional) } + + func test_orFunction_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or([bool, bool, bool])) + AssertSQL("(\"bool\" OR \"bool\")", or([bool, bool])) + AssertSQL("(\"bool\")", or([bool])) + + AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or(bool, bool, bool)) + AssertSQL("(\"bool\" OR \"bool\")", or(bool, bool)) + AssertSQL("(\"bool\")", or(bool)) + } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { AssertSQL("NOT (\"bool\")", !bool) From e080a3ac0d2dc8fea15dde5681939bbbc0848b3e Mon Sep 17 00:00:00 2001 From: Dave DeLong Date: Mon, 16 Apr 2018 16:15:09 +0200 Subject: [PATCH 0663/1046] Forgot to pass on a parameter --- Sources/SQLite/Helpers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index b4ff360e..4b755078 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -73,7 +73,7 @@ extension String { } func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return infix([lhs, rhs]) + return infix([lhs, rhs], wrap: wrap) } func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { From 10d5e85a99e7f0daedccd5a5099bc457f58ea4e4 Mon Sep 17 00:00:00 2001 From: Madalin Mamuleanu Date: Tue, 19 Jun 2018 17:12:28 +0300 Subject: [PATCH 0664/1046] Fixed one of Executing Arbitrary SQL examples: - corrected method name - added : instead of = --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 56c8c429..9aa84f4a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1854,8 +1854,8 @@ using the following functions. ```swift let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { - for (index, name) in stmt.columnNames.enumerate() { - print ("\(name)=\(row[index]!)") + for (index, name) in stmt.columnNames.enumerated() { + print ("\(name):\(row[index]!)") // id: Optional(1), email: Optional("alice@mac.com") } } From 1f84745513aa56bbd38e428e0a5db5d55de3381e Mon Sep 17 00:00:00 2001 From: Serg Date: Sun, 15 Jul 2018 23:36:52 +0700 Subject: [PATCH 0665/1046] Support Int64 coding --- Sources/SQLite/Typed/Coding.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 7c70db33..3e4b5226 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -152,7 +152,7 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: Int64, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int64 is not supported")) + self.encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: UInt, forKey key: Key) throws { @@ -249,7 +249,7 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) + return try self.row.get(Expression(key.stringValue)) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { From 01e152a0672f3934867b86017c290e1f0a62e24a Mon Sep 17 00:00:00 2001 From: Victor Kononov Date: Mon, 6 Aug 2018 17:09:57 +0300 Subject: [PATCH 0666/1046] non_framewrok_fix: fixed a mistype that prevented building SQLite.swift as non-framework sources. --- Sources/SQLite/Typed/Coding.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 7c70db33..c3fb931b 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -184,7 +184,7 @@ fileprivate class SQLiteEncoder: Encoder { } } - fileprivate var setters: [SQLite.Setter] = [] + fileprivate var setters: [Setter] = [] let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] From 0aaddb3b97d8d62d46567e16c305beb34312bad2 Mon Sep 17 00:00:00 2001 From: Otto Suess <37940680+ottosuess@users.noreply.github.com> Date: Thu, 13 Sep 2018 18:33:51 +0200 Subject: [PATCH 0667/1046] fix markdown table formatting tables containing `|` are breaking github markdown formatter when they are not escaped. --- Documentation/Index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9aa84f4a..628e7464 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -718,7 +718,7 @@ try db.transaction { | `<<=` | `Int -> Int` | | `>>=` | `Int -> Int` | | `&=` | `Int -> Int` | -| `||=` | `Int -> Int` | +| `\|\|=` | `Int -> Int` | | `^=` | `Int -> Int` | | `+=` | `String -> String` | @@ -954,7 +954,7 @@ equate or compare different types will prevent compilation. | `<=` | `Comparable -> Bool` | `<=` | | `~=` | `(Interval, Comparable) -> Bool` | `BETWEEN` | | `&&` | `Bool -> Bool` | `AND` | -| `||` | `Bool -> Bool` | `OR` | +| `\|\|`| `Bool -> Bool` | `OR` | > *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` > accordingly. @@ -1586,8 +1586,8 @@ arithmetic, bitwise operations, and concatenation. | `<<` | `Int -> Int` | `<<` | | `>>` | `Int -> Int` | `>>` | | `&` | `Int -> Int` | `&` | -| `|` | `Int -> Int` | `|` | -| `+` | `String -> String` | `||` | +| `\|` | `Int -> Int` | `\|` | +| `+` | `String -> String` | `\|\|` | > _Note:_ SQLite.swift also defines a bitwise XOR operator, `^`, which > expands the expression `lhs ^ rhs` to `~(lhs & rhs) & (lhs | rhs)`. From 304c39d42f45c07eece380f1d64e4ba40f3fadb1 Mon Sep 17 00:00:00 2001 From: Jason Peterson Date: Sat, 29 Sep 2018 21:27:34 -0500 Subject: [PATCH 0668/1046] added sample code for copying a 'seed' database --- Documentation/Index.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..12810f94 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -258,6 +258,28 @@ let path = NSSearchPathForDirectoriesInDomains( let db = try Connection("\(path)/db.sqlite3") ``` +If you have bundled it your application, you can use FileManager to copy it to the Documents directory: + +```swift +func copyDatabaseIfNeeded(sourcePath : String) -> Bool { + var destinationPath = NSSearchPathForDirectoriesInDomains( + .documentDirectory, .userDomainMask, true + ).first! + destinationPath = destinationPath + "/db.sqlite3" + let databaseExistsWhereNeeded = FileManager.default.fileExists(atPath: destinationPath) + if (!databaseExistsWhereNeeded) { + do { + try FileManager.default.copyItem(atPath: sourcePath, toPath: destinationPath) + print("db copied") + } + catch { + print("error during file copy: \(error)") + } + } + return true +} +``` + On OS X, you can use your app’s **Application Support** directory: ```swift @@ -295,7 +317,7 @@ let db = try Connection(path, readonly: true) > See these two Stack Overflow questions for more information about iOS apps > with SQLite databases: [1](https://stackoverflow.com/questions/34609746/what-different-between-store-database-in-different-locations-in-ios), > [2](https://stackoverflow.com/questions/34614968/ios-how-to-copy-pre-seeded-database-at-the-first-running-app-with-sqlite-swift). -> We welcome sample code to show how to successfully copy and use a bundled "seed" +> We welcome changes to the above sample code to show how to successfully copy and use a bundled "seed" > database for writing in an app. #### In-Memory Databases From ad63c37cc044af3d4bc1588faa6c41c5600b71b0 Mon Sep 17 00:00:00 2001 From: Madalin Mamuleanu Date: Tue, 2 Oct 2018 00:21:03 +0300 Subject: [PATCH 0669/1046] Documentation update. Fixes #841 --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..f214b50a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -266,8 +266,8 @@ var path = NSSearchPathForDirectoriesInDomains( ).first! + "/" + Bundle.main.bundleIdentifier! // create parent directory iff it doesn’t exist -try FileManager.default.createDirectoryAtPath( - path, withIntermediateDirectories: true, attributes: nil +try FileManager.default.createDirectory( +atPath: path, withIntermediateDirectories: true, attributes: nil ) let db = try Connection("\(path)/db.sqlite3") From ab53696ce1aca28a292ecb1b20967a1f687cf7a1 Mon Sep 17 00:00:00 2001 From: Huan Lin Date: Mon, 15 Oct 2018 21:52:10 -0700 Subject: [PATCH 0670/1046] add primary key with 4 columns. --- Sources/SQLite/Typed/Schema.swift | 4 ++++ Tests/SQLiteTests/SchemaTests.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 62c90702..febfe68c 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -341,6 +341,10 @@ public final class TableBuilder { primaryKey([compositeA, b, c]) } + public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression, _ d: Expression) { + primaryKey([compositeA, b, c, d]) + } + fileprivate func primaryKey(_ composite: [Expressible]) { definitions.append("PRIMARY KEY".prefix(composite)) } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index b9a08881..30646b98 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -545,6 +545,10 @@ class SchemaTests : XCTestCase { "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\"))", table.create { t in t.primaryKey(int64, string, double) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\", \"date\"))", + table.create { t in t.primaryKey(int64, string, double, date) } + ) } func test_unique_compilesUniqueExpression() { From cf4183f4ef54c5c9e82176cc46451934c8fdec2a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 19 Dec 2018 12:35:50 +0100 Subject: [PATCH 0671/1046] Swift 4.2, Xcode 10.1 - fix tests running agains SQLCipher 4.x --- .swift-version | 2 +- .travis.yml | 5 +- CHANGELOG.md | 7 +++ Makefile | 2 +- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 28 +++++------ Sources/SQLite/Extensions/Cipher.swift | 5 ++ Sources/SQLiteObjc/include/SQLite-Bridging.h | 3 +- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 44 +++++++++--------- Tests/CocoaPods/integration_test.rb | 2 +- Tests/SQLiteTests/CipherTests.swift | 10 +++- Tests/SQLiteTests/ConnectionTests.swift | 35 +++++++------- Tests/SQLiteTests/TestHelpers.swift | 9 ---- ...{encrypted.sqlite => encrypted-3.x.sqlite} | Bin .../SQLiteTests/fixtures/encrypted-4.x.sqlite | Bin 0 -> 8192 bytes 16 files changed, 83 insertions(+), 73 deletions(-) rename Tests/SQLiteTests/fixtures/{encrypted.sqlite => encrypted-3.x.sqlite} (100%) create mode 100644 Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite diff --git a/.swift-version b/.swift-version index 7d5c902e..bf77d549 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.1 +4.2 diff --git a/.travis.yml b/.travis.yml index f278301d..909e5672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: objective-c rvm: 2.3 -osx_image: xcode9.3 +# https://docs.travis-ci.com/user/reference/osx +osx_image: xcode10.1 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="11.3" + - IOS_VERSION="12.1" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/CHANGELOG.md b/CHANGELOG.md index 648cdc07..027611ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.11.6 (xxx), [diff][diff-0.11.6] +======================================== + +* Swift 4.2, SQLCipher 4.x ([#866][]) + 0.11.5 (04-14-2018), [diff][diff-0.11.5] ======================================== @@ -57,6 +62,7 @@ [diff-0.11.3]: https://github.com/stephencelis/SQLite.swift/compare/0.11.2...0.11.3 [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 +[diff-0.11.6]: https://github.com/stephencelis/SQLite.swift/compare/0.11.5...0.11.6 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -88,3 +94,4 @@ [#733]: https://github.com/stephencelis/SQLite.swift/pull/733 [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 [#797]: https://github.com/stephencelis/SQLite.swift/pull/797 +[#866]: https://github.com/stephencelis/SQLite.swift/pull/866 diff --git a/Makefile b/Makefile index cd4afb8d..d98c8deb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 6s -IOS_VERSION = 11.3 +IOS_VERSION = 12.1 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b03a9ce4..c2235aca 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.1', + 'SWIFT_VERSION' => '4.2', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 75b38526..a155a115 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1033,7 +1033,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1055,7 +1055,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1069,7 +1069,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1083,7 +1083,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1106,7 +1106,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1130,7 +1130,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1269,7 +1269,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1291,7 +1291,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1304,7 +1304,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1317,7 +1317,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1342,7 +1342,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1367,7 +1367,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1383,7 +1383,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1399,7 +1399,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 6c0d4657..71ef1765 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -6,6 +6,11 @@ import SQLCipher /// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/) extension Connection { + /// - Returns: the SQLCipher version + public var cipherVersion: String? { + return (try? scalar("PRAGMA cipher_version")) as? String + } + /// Specify the key for an encrypted database. This routine should be /// called right after sqlite3_open(). /// diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLite-Bridging.h index 5b356593..f8c2a3b3 100644 --- a/Sources/SQLiteObjc/include/SQLite-Bridging.h +++ b/Sources/SQLiteObjc/include/SQLite-Bridging.h @@ -23,8 +23,7 @@ // @import Foundation; - -#import "sqlite3.h" +@import SQLite3; NS_ASSUME_NONNULL_BEGIN typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 3b9d6ba5..fd1c8c2e 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.5.0' +gem 'cocoapods', '~> 1.6.0beta2' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index de03030a..b5172144 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,76 +2,76 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - activesupport (4.2.10) + activesupport (4.2.11) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - atomos (0.1.2) + atomos (0.1.3) claide (1.0.2) - cocoapods (1.5.0) + cocoapods (1.6.0.beta.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.5.0) + cocoapods-core (= 1.6.0.beta.2) cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.0, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) fourflusher (~> 2.0.1) gh_inspector (~> 1.0) - molinillo (~> 0.6.5) + molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.1) - xcodeproj (>= 1.5.7, < 2.0) - cocoapods-core (1.5.0) + ruby-macho (~> 1.3, >= 1.3.1) + xcodeproj (>= 1.7.0, < 2.0) + cocoapods-core (1.6.0.beta.2) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.0) + cocoapods-downloader (1.2.2) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.0) + cocoapods-trunk (1.3.1) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.1.0) colored2 (3.1.2) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.4) escape (0.0.4) fourflusher (2.0.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) i18n (0.9.5) concurrent-ruby (~> 1.0) - minitest (5.10.1) - molinillo (0.6.5) - nanaimo (0.2.5) + minitest (5.11.3) + molinillo (0.6.6) + nanaimo (0.2.6) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.1.0) + ruby-macho (1.3.1) thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - xcodeproj (1.5.7) + xcodeproj (1.7.0) CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.2) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.4) + nanaimo (~> 0.2.6) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.5.0) + cocoapods (~> 1.6.0beta2) minitest BUNDLED WITH - 1.16.1 + 1.17.1 diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 4bec8d40..98a539b5 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -42,7 +42,7 @@ def test_pod def xcodebuild(action, scheme, configuration) require 'fourflusher' - command = %W(clean #{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) + command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) case consumer.platform_name when :osx, :macos command += %w(CODE_SIGN_IDENTITY=) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 3ee0b135..abecffba 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -73,11 +73,17 @@ class CipherTests: XCTestCase { } func test_open_db_encrypted_with_sqlcipher() { - // $ sqlcipher SQLiteTests/fixtures/encrypted.sqlite + // $ sqlcipher Tests/SQLiteTests/fixtures/encrypted-[version].x.sqlite // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); - let encryptedFile = fixture("encrypted", withExtension: "sqlite") + guard let cipherVersion:String = db1.cipherVersion, + cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") + else { return } + + let encryptedFile = cipherVersion.starts(with: "3.") ? + fixture("encrypted-3.x", withExtension: "sqlite") : + fixture("encrypted-4.x", withExtension: "sqlite") try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 9480e3bb..eab3cf00 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -369,18 +369,23 @@ class ConnectionTests : SQLiteTestCase { } func test_interrupt_interruptsLongRunningQuery() { - try! InsertUsers("abcdefghijklmnopqrstuvwxyz".map { String($0) }) + let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { args in - usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) + DispatchQueue.global(qos: .background).async { + self.db.interrupt() + semaphore.signal() + } + semaphore.wait() return nil } - - let stmt = try! db.prepare("SELECT *, sleep(?) FROM users", 0.1) - try! stmt.run() - - let deadline = DispatchTime.now() + 0.01 - _ = DispatchQueue(label: "queue", qos: .background).asyncAfter(deadline: deadline, execute: db.interrupt) - AssertThrows(try stmt.run()) + let stmt = try! db.prepare("SELECT sleep()") + XCTAssertThrowsError(try stmt.run()) { error in + if case Result.error(_, let code, _) = error { + XCTAssertEqual(code, SQLITE_INTERRUPT) + } else { + XCTFail("unexpected error: \(error)") + } + } } func test_concurrent_access_single_connection() { @@ -391,21 +396,17 @@ class ConnectionTests : SQLiteTestCase { try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") try! conn.run("INSERT INTO test(value) VALUES(?)", 0) let queue = DispatchQueue(label: "Readers", attributes: [.concurrent]) + let nReaders = 5 - var reads = Array(repeating: 0, count: nReaders) - var finished = false + let semaphores = Array(repeating: DispatchSemaphore(value: 100), count: nReaders) for index in 0.. 500) } - } + semaphores.forEach { $0.wait() } } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 8d60362c..2e491b01 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -100,15 +100,6 @@ func AssertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclo XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } -func AssertThrows(_ expression: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line) { - do { - _ = try expression() - XCTFail("expression expected to throw", file: file, line: line) - } catch { - XCTAssert(true, file: file, line: line) - } -} - let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") diff --git a/Tests/SQLiteTests/fixtures/encrypted.sqlite b/Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted.sqlite rename to Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite b/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..36890365583fc0fa1383f4377d7c9b844059872c GIT binary patch literal 8192 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 Date: Fri, 11 Jan 2019 09:05:41 -0600 Subject: [PATCH 0672/1046] Codable - Add Date support --- Documentation/Index.md | 2 +- Sources/SQLite/Typed/Coding.swift | 7 +++++++ Tests/SQLiteTests/QueryTests.swift | 27 +++++++++++++++------------ Tests/SQLiteTests/TestHelpers.swift | 5 ++++- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..7fc74ea0 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1557,7 +1557,7 @@ Both of the above methods also have the following optional parameter: There are a few restrictions on using Codable types: - The encodable and decodable objects can only use the following types: - - Int, Bool, Float, Double, String + - Int, Bool, Float, Double, String, Date - Nested Codable types that will be encoded as JSON to a single column - These methods will not handle object relationships for you. You must write your own Codable and Decodable implementations if you wish to support this. diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..0eb02a55 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -132,6 +132,9 @@ fileprivate class SQLiteEncoder: Encoder { if let data = value as? Data { self.encoder.setters.append(Expression(key.stringValue) <- data) } + else if let date = value as? Date { + self.encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) @@ -290,6 +293,10 @@ fileprivate class SQLiteDecoder : Decoder { let data = try self.row.get(Expression(key.stringValue)) return data as! T } + else if type == Date.self { + let date = try self.row.get(Expression(key.stringValue)) + return date as! T + } guard let JSONString = try self.row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "an unsupported type was found")) } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2a9e4ecb..8f48264f 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -249,23 +249,23 @@ class QueryTests : XCTestCase { func test_insert_encodable() throws { let emails = Table("emails") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.insert(value) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0)", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000')", insert ) } func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: "optional", sub: value1) let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, 'optional', '\(encodedJSONString)')", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'optional', '\(encodedJSONString)')", insert ) } @@ -286,23 +286,23 @@ class QueryTests : XCTestCase { func test_update_encodable() throws { let emails = Table("emails") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let update = try emails.update(value) AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0", + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000'", update ) } func test_update_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) let update = try emails.update(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"sub\" = '\(encodedJSONString)'", + "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)'", update ) } @@ -438,12 +438,13 @@ class QueryIntegrationTests : SQLiteTestCase { builder.column(Expression("bool")) builder.column(Expression("float")) builder.column(Expression("double")) + builder.column(Expression("date")) builder.column(Expression("optional")) builder.column(Expression("sub")) }) - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) try db.run(table.insert(value)) @@ -455,12 +456,14 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(values[0].bool, true) XCTAssertEqual(values[0].float, 7) XCTAssertEqual(values[0].double, 8) + XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) XCTAssertEqual(values[0].optional, "optional") XCTAssertEqual(values[0].sub?.int, 1) XCTAssertEqual(values[0].sub?.string, "2") XCTAssertEqual(values[0].sub?.bool, true) XCTAssertEqual(values[0].sub?.float, 3) XCTAssertEqual(values[0].sub?.double, 4) + XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0)) XCTAssertNil(values[0].sub?.optional) XCTAssertNil(values[0].sub?.sub) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 2e491b01..247013b4 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -26,6 +26,7 @@ class SQLiteTestCase : XCTestCase { salary REAL, admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), manager_id INTEGER, + created_at DATETIME, FOREIGN KEY(manager_id) REFERENCES users(id) ) """ @@ -111,15 +112,17 @@ class TestCodable: Codable { let bool: Bool let float: Float let double: Double + let date: Date let optional: String? let sub: TestCodable? - init(int: Int, string: String, bool: Bool, float: Float, double: Double, optional: String?, sub: TestCodable?) { + init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, optional: String?, sub: TestCodable?) { self.int = int self.string = string self.bool = bool self.float = float self.double = double + self.date = date self.optional = optional self.sub = sub } From 2b82d152e1870d353cedae0bcbfce0b31a511275 Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Thu, 21 Feb 2019 21:08:29 -0800 Subject: [PATCH 0673/1046] Add prototype implementation for Connection.createAggregation. --- SQLite.xcodeproj/project.pbxproj | 8 ++ Sources/SQLite/Core/Connection.swift | 106 ++++++++++++++++++ .../SQLiteTests/CustomAggregationTests.swift | 73 ++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 Tests/SQLiteTests/CustomAggregationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a155a115..1635f651 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -81,6 +81,9 @@ 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17FDA323BAFDEC627E76F /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; + 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; + 3717F90A221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; @@ -225,6 +228,7 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; + 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -401,6 +405,7 @@ EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, + 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */, EE247B201C3F137700AE3E12 /* ExpressionTests.swift */, EE247B211C3F137700AE3E12 /* FTS4Tests.swift */, EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */, @@ -834,6 +839,7 @@ 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */, 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */, 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */, + 3717F90A221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */, 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */, 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, @@ -922,6 +928,7 @@ EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */, EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */, EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */, + 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */, EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */, EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, @@ -980,6 +987,7 @@ EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */, EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */, EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */, + 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */, EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */, EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..1fce790f 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -597,8 +597,114 @@ public final class Connection { if functions[function] == nil { self.functions[function] = [:] } functions[function]?[argc] = box } + + /// Creates or redefines a custom SQL aggregate. + /// + /// - Parameters: + /// + /// - aggregate: The name of the aggregate to create or redefine. + /// + /// - argumentCount: The number of arguments that the aggregate takes. If + /// `nil`, the aggregate may take any number of arguments. + /// + /// Default: `nil` + /// + /// - deterministic: Whether or not the aggregate is deterministic (_i.e._ + /// the aggregate always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - step: A block of code to run for each row of an aggregation group. + /// The block is called with an array of raw SQL values mapped to the + /// aggregate’s parameters, and an UnsafeMutablePointer to a state + /// variable. + /// + /// - final: A block of code to run after each row of an aggregation group + /// is processed. The block is called with an UnsafeMutablePointer to a + /// state variable, and should return a raw SQL value (or nil). + /// + /// - state: A block of code to run to produce a fresh state variable for + /// each aggregation group. The block should return an + /// UnsafeMutablePointer to the fresh state variable. + public func createAggregation(_ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, step: @escaping ([Binding?], UnsafeMutablePointer) -> (), final: @escaping (UnsafeMutablePointer) -> Binding?, state: @escaping () -> UnsafeMutablePointer) { + let argc = argumentCount.map { Int($0) } ?? -1 + let box : Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? + let p = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) + if stepFlag > 0 { + let arguments: [Binding?] = (0..?) -> Void fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate var functions = [String: [Int: Function]]() + fileprivate var aggregations = [String: [Int: Aggregate]]() /// Defines a new collating sequence. /// diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift new file mode 100644 index 00000000..56c3c011 --- /dev/null +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -0,0 +1,73 @@ +import XCTest +import Foundation +import Dispatch +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class CustomAggregationTests : SQLiteTestCase { + override func setUp() { + super.setUp() + CreateUsersTable() + try! InsertUser("Alice", age: 30, admin: true) + try! InsertUser("Bob", age: 25, admin: true) + try! InsertUser("Eve", age: 28, admin: false) + } + + func testCustomSum() { + let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in + if let v = bindings[0] as? Int64 { + state.pointee += v + } + } + + let final = { (state: UnsafeMutablePointer) -> Binding? in + let v = state.pointee + let p = UnsafeMutableBufferPointer(start: state, count: 1) + p.deallocate() + return v + } + let _ = db.createAggregation("mySUM", step: step, final: final) { + let v = UnsafeMutableBufferPointer.allocate(capacity: 1) + v[0] = 0 + return v.baseAddress! + } + let result = try! db.prepare("SELECT mySUM(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(83, value) + } + } + + func testCustomSumGrouping() { + let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in + if let v = bindings[0] as? Int64 { + state.pointee += v + } + } + let final = { (state: UnsafeMutablePointer) -> Binding? in + let v = state.pointee + let p = UnsafeMutableBufferPointer(start: state, count: 1) + p.deallocate() + return v + } + let _ = db.createAggregation("mySUM", step: step, final: final) { + let v = UnsafeMutableBufferPointer.allocate(capacity: 1) + v[0] = 0 + return v.baseAddress! + } + let result = try! db.prepare("SELECT mySUM(age) AS s FROM users GROUP BY admin ORDER BY s") + let i = result.columnNames.index(of: "s")! + let values = result.compactMap { $0[i] as? Int64 } + XCTAssertTrue(values.elementsEqual([28, 55])) + } +} From 1e6bfbf2c046606adc08c9ca2ee4d7bc17f7cb2a Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Thu, 21 Feb 2019 21:58:34 -0800 Subject: [PATCH 0674/1046] Added convenience overloads for Connection.createAggregation. --- Sources/SQLite/Core/Connection.swift | 16 +++-- Sources/SQLite/Typed/CustomFunctions.swift | 65 +++++++++++++++++ .../SQLiteTests/CustomAggregationTests.swift | 71 +++++++++++++++++-- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1fce790f..349c18b6 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -626,7 +626,15 @@ public final class Connection { /// - state: A block of code to run to produce a fresh state variable for /// each aggregation group. The block should return an /// UnsafeMutablePointer to the fresh state variable. - public func createAggregation(_ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, step: @escaping ([Binding?], UnsafeMutablePointer) -> (), final: @escaping (UnsafeMutablePointer) -> Binding?, state: @escaping () -> UnsafeMutablePointer) { + public func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + step: @escaping ([Binding?], UnsafeMutablePointer) -> (), + final: @escaping (UnsafeMutablePointer) -> Binding?, + state: @escaping () -> UnsafeMutablePointer) { + + let argc = argumentCount.map { Int($0) } ?? -1 let box : Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? @@ -690,17 +698,17 @@ public final class Connection { { context, argc, value in let function = unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self) function(1, context, argc, value) - }, + }, { context in let function = unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self) function(0, context, 0, nil) - }, + }, nil ) if aggregations[aggregate] == nil { self.aggregations[aggregate] = [:] } aggregations[aggregate]?[argc] = box } - + fileprivate typealias Aggregate = @convention(block) (Int, OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void fileprivate var functions = [String: [Int: Function]]() diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 2389901f..375a6b99 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -133,4 +133,69 @@ public extension Connection { } } + // MARK: - + + public func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, ptr) in + let p = ptr.pointee.assumingMemoryBound(to: T.self) + let current = Unmanaged.fromOpaque(p).takeRetainedValue() + let next = reduce(current, bindings) + ptr.pointee = Unmanaged.passRetained(next).toOpaque() + } + + let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in + let p = ptr.pointee.assumingMemoryBound(to: T.self) + let obj = Unmanaged.fromOpaque(p).takeRetainedValue() + let value = result(obj) + ptr.deallocate() + return value + } + + let state: () -> UnsafeMutablePointer = { + let p = UnsafeMutablePointer.allocate(capacity: 1) + p.pointee = Unmanaged.passRetained(initialValue).toOpaque() + return p + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + + public func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, p) in + let current = p.pointee + let next = reduce(current, bindings) + p.pointee = next + } + + let final: (UnsafeMutablePointer) -> Binding? = { (p) in + let v = result(p.pointee) + p.deallocate() + return v + } + + let state: () -> UnsafeMutablePointer = { + let p = UnsafeMutablePointer.allocate(capacity: 1) + p.pointee = initialValue + return p + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 56c3c011..1035f67c 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -22,25 +22,25 @@ class CustomAggregationTests : SQLiteTestCase { try! InsertUser("Eve", age: 28, admin: false) } - func testCustomSum() { + func testUnsafeCustomSum() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v } } - + let final = { (state: UnsafeMutablePointer) -> Binding? in let v = state.pointee let p = UnsafeMutableBufferPointer(start: state, count: 1) p.deallocate() return v } - let _ = db.createAggregation("mySUM", step: step, final: final) { + let _ = db.createAggregation("mySUM1", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM(age) AS s FROM users") + let result = try! db.prepare("SELECT mySUM1(age) AS s FROM users") let i = result.columnNames.index(of: "s")! for row in result { let value = row[i] as? Int64 @@ -48,7 +48,7 @@ class CustomAggregationTests : SQLiteTestCase { } } - func testCustomSumGrouping() { + func testUnsafeCustomSumGrouping() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v @@ -60,14 +60,71 @@ class CustomAggregationTests : SQLiteTestCase { p.deallocate() return v } - let _ = db.createAggregation("mySUM", step: step, final: final) { + let _ = db.createAggregation("mySUM2", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM(age) AS s FROM users GROUP BY admin ORDER BY s") + let result = try! db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.index(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([28, 55])) } + + func testCustomSum() { + let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let v = (bindings[0] as? Int64) ?? 0 + return last + v + } + let _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) + let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(2083, value) + } + } + + func testCustomSumGrouping() { + let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let v = (bindings[0] as? Int64) ?? 0 + return last + v + } + let _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) + let result = try! db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") + let i = result.columnNames.index(of: "s")! + let values = result.compactMap { $0[i] as? Int64 } + XCTAssertTrue(values.elementsEqual([3028, 3055])) + } + + func testCustomObjectSum() { + { + let initial = TestObject(value: 1000) + let reduce : (TestObject, [Binding?]) -> TestObject = { (last, bindings) in + let v = (bindings[0] as? Int64) ?? 0 + return TestObject(value: last.value + v) + } + let _ = db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) + // end this scope to ensure that the initial value is retained + // by the createAggregation call. + }() + let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(1083, value) + } + } +} + +/// This class is used to test that aggregation state variables +/// can be reference types and are properly memory managed when +/// crossing the Swift<->C boundary multiple times. +class TestObject { + var value: Int64 + init(value: Int64) { + self.value = value + } + deinit { + } } From b1d3a4036deddac396b46d725866192eb29df0ff Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Fri, 22 Feb 2019 08:25:08 -0800 Subject: [PATCH 0675/1046] Add extra test for custom aggregation. --- Tests/SQLiteTests/CustomAggregationTests.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 1035f67c..74a19df0 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -97,6 +97,21 @@ class CustomAggregationTests : SQLiteTestCase { XCTAssertTrue(values.elementsEqual([3028, 3055])) } + func testCustomStringAgg() { + let initial = String(repeating: " ", count: 64) + let reduce : (String, [Binding?]) -> String = { (last, bindings) in + let v = (bindings[0] as? String) ?? "" + return last + v + } + let _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) + let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? String + XCTAssertEqual("\(initial)Alice@example.comBob@example.comEve@example.com", value) + } + } + func testCustomObjectSum() { { let initial = TestObject(value: 1000) From 4a28df38c187a428561c12ee9d0a3e3099ba336b Mon Sep 17 00:00:00 2001 From: Gregory Todd Williams Date: Fri, 22 Feb 2019 09:05:24 -0800 Subject: [PATCH 0676/1046] Track retains/releases that occur during custom aggregation testing. --- .../SQLiteTests/CustomAggregationTests.swift | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 74a19df0..f8efc7e4 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -122,13 +122,18 @@ class CustomAggregationTests : SQLiteTestCase { let _ = db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) // end this scope to ensure that the initial value is retained // by the createAggregation call. + }(); + { + XCTAssertEqual(TestObject.inits, 1) + let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") + let i = result.columnNames.index(of: "s")! + for row in result { + let value = row[i] as? Int64 + XCTAssertEqual(1083, value) + } }() - let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! - for row in result { - let value = row[i] as? Int64 - XCTAssertEqual(1083, value) - } + XCTAssertEqual(TestObject.inits, 4) + XCTAssertEqual(TestObject.deinits, 3) // the initial value is still retained by the aggregate's state block, so deinits is one less than inits } } @@ -136,10 +141,15 @@ class CustomAggregationTests : SQLiteTestCase { /// can be reference types and are properly memory managed when /// crossing the Swift<->C boundary multiple times. class TestObject { + static var inits = 0 + static var deinits = 0 + var value: Int64 init(value: Int64) { self.value = value + TestObject.inits += 1 } deinit { + TestObject.deinits += 1 } } From 0fa531d1118bf88e83ffb893d89b8898bfdd85b8 Mon Sep 17 00:00:00 2001 From: Andrew Finnell Date: Sun, 24 Feb 2019 13:54:35 -0500 Subject: [PATCH 0677/1046] Implement Upsert [#482] # Problem Apps need the ability to insert or, if the row already exists, update the existing row. This is commonly called "upsert" and is [documented for SQLite here](https://www.sqlite.org/lang_UPSERT.html). The existing "replace" on conflict clause for `insert()` often does not have the desired behavior. It will cascade (with deletes, if specified) along foreign keys if the value exists and has to be replaced ([see Issue 842](https://github.com/stephencelis/SQLite.swift/issues/842)). # Solution Create an `upsert()` method for queries. The basic form allows for the fallback setters to be manually specified. A slightly more ergonomic version will compute the setters from the inserted values by removing the "conflicting" column, and then using the inserted values via the "excluded" qualifier. Additionally, an `Encodable` version of `upsert` is provided. --- Documentation/Index.md | 28 +++++++++++++++++++++ Sources/SQLite/Typed/Coding.swift | 23 ++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 39 ++++++++++++++++++++++++++++++ Sources/SQLite/Typed/Setter.swift | 5 ++++ Tests/SQLiteTests/QueryTests.swift | 35 ++++++++++++++++++++++++++- 5 files changed, 129 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..61c287d6 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -35,6 +35,7 @@ - [Sorting Rows](#sorting-rows) - [Limiting and Paging Results](#limiting-and-paging-results) - [Aggregation](#aggregation) + - [Upserting Rows](#upserting-rows) - [Updating Rows](#updating-rows) - [Deleting Rows](#deleting-rows) - [Transactions and Savepoints](#transactions-and-savepoints) @@ -1098,6 +1099,33 @@ let count = try db.scalar(users.filter(name != nil).count) > // SELECT count(DISTINCT "name") FROM "users" > ``` +## Upserting Rows + +We can upsert rows into a table by calling a [query’s](#queries) `upsert` +function with a list of [setters](#setters)—typically [typed column +expressions](#expressions) and values (which can also be expressions)—each +joined by the `<-` operator. Upserting is like inserting, except if there is a +conflict on the specified column value, SQLite will perform an update on the row instead. + +```swift +try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice"), onConflictOf: email) +// INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') ON CONFLICT (\"email\") DO UPDATE SET \"name\" = \"excluded\".\"name\" +``` + +The `upsert` function, when run successfully, returns an `Int64` representing +the inserted row’s [`ROWID`][ROWID]. + +```swift +do { + let rowid = try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice", onConflictOf: email)) + print("inserted id: \(rowid)") +} catch { + print("insertion failed: \(error)") +} +``` + +The [`insert`](#inserting-rows), [`update`](#updating-rows), and [`delete`](#deleting-rows) functions +follow similar patterns. ## Updating Rows diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..6ed8c987 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -45,6 +45,29 @@ extension QueryType { return self.insert(encoder.setters + otherSetters) } + + /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - onConflictOf: The column that if conflicts should trigger an update instead of insert. + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func upsert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = [], onConflictOf conflicting: Expressible) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) + } + /// Creates an `UPDATE` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f6ef6df8..8793acae 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -672,6 +672,45 @@ extension QueryType { query.expression ]).expression) } + + // MARK: UPSERT + + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible) -> Insert { + return upsert(insertValues, onConflictOf: conflicting) + } + + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible) -> Insert { + let setValues = insertValues.filter { $0.column.asSQL() != conflicting.asSQL() } + .map { Setter(excluded: $0.column) } + return upsert(insertValues, onConflictOf: conflicting, set: setValues) + } + + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { + return upsert(insertValues, onConflictOf: conflicting, set: setValues) + } + + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { + let insert = insertValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in + (insert.columns + [setter.column], insert.values + [setter.value]) + } + + let clauses: [Expressible?] = [ + Expression(literal: "INSERT"), + Expression(literal: "INTO"), + tableName(), + "".wrap(insert.columns) as Expression, + Expression(literal: "VALUES"), + "".wrap(insert.values) as Expression, + whereClause, + Expression(literal: "ON CONFLICT"), + "".wrap(conflicting) as Expression, + Expression(literal: "DO UPDATE SET"), + ", ".join(setValues.map { $0.expression }) + ] + + return Insert(" ".join(clauses.compactMap { $0 }).expression) + } + // MARK: UPDATE diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 86f16fca..3d3170f6 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -60,6 +60,11 @@ public struct Setter { self.value = Expression(value: value) } + init(excluded column: Expressible) { + let excluded = Expression("excluded") + self.column = column + self.value = ".".join([excluded, column.expression]) + } } extension Setter : Expressible { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2a9e4ecb..694e7f26 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -270,6 +270,24 @@ class QueryTests : XCTestCase { ) } + func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { + AssertSQL( + "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30) ON CONFLICT (\"email\") DO UPDATE SET \"age\" = \"excluded\".\"age\"", + users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email) + ) + } + + func test_upsert_encodable() throws { + let emails = Table("emails") + let string = Expression("string") + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let insert = try emails.upsert(value, onConflictOf: string) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0) ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", + insert + ) + } + func test_update_compilesUpdateExpression() { AssertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", @@ -378,7 +396,8 @@ class QueryIntegrationTests : SQLiteTestCase { let id = Expression("id") let email = Expression("email") - + let age = Expression("age") + override func setUp() { super.setUp() @@ -483,6 +502,20 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(1, id) } + func test_upsert() throws { + let fetchAge = { () throws -> Int? in + return try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } + } + + let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) + XCTAssertEqual(1, id) + XCTAssertEqual(30, try fetchAge()) + + let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) + XCTAssertEqual(1, nextId) + XCTAssertEqual(42, try fetchAge()) + } + func test_update() { let changes = try! db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) From c96a5da4ef8c9ccec7bfac254b56ae17199097f3 Mon Sep 17 00:00:00 2001 From: Otto Suess Date: Tue, 26 Mar 2019 12:53:22 +0100 Subject: [PATCH 0678/1046] fix xcode-10.2 issues --- Sources/SQLite/Helpers.swift | 12 - Sources/SQLite/Typed/AggregateFunctions.swift | 22 +- Sources/SQLite/Typed/CoreFunctions.swift | 40 +-- Sources/SQLite/Typed/Operators.swift | 232 +++++++++--------- 4 files changed, 147 insertions(+), 159 deletions(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index ac831667..115ea5c6 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -98,18 +98,6 @@ extension String { } -func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = #function) -> Expression { - return function.infix(lhs, rhs, wrap: wrap) -} - -func wrap(_ expression: Expressible, function: String = #function) -> Expression { - return function.wrap(expression) -} - -func wrap(_ expressions: [Expressible], function: String = #function) -> Expression { - return function.wrap(", ".join(expressions)) -} - func transcode(_ literal: Binding?) -> String { guard let literal = literal else { return "NULL" } diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 249bbe60..bb1157fe 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -48,7 +48,7 @@ extension ExpressionType where UnderlyingType : Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return wrap(self) + return "count".wrap(self) } } @@ -79,7 +79,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return wrap(self) + return "count".wrap(self) } } @@ -96,7 +96,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return wrap(self) + return "max".wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -109,7 +109,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return wrap(self) + return "min".wrap(self) } } @@ -126,7 +126,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return wrap(self) + return "max".wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -139,7 +139,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return wrap(self) + return "min".wrap(self) } } @@ -169,7 +169,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return wrap(self) + return "sum".wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -182,7 +182,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return wrap(self) + return "total".wrap(self) } } @@ -212,7 +212,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return wrap(self) + return "sum".wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -225,7 +225,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return wrap(self) + return "total".wrap(self) } } @@ -233,7 +233,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return wrap(star(nil, nil)) + return "count".wrap(star(nil, nil)) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index d7995b95..b7b53c71 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -68,9 +68,9 @@ extension ExpressionType where UnderlyingType == Double { /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { - return wrap([self]) + return "round".wrap([self]) } - return wrap([self, Int(precision)]) + return "round".wrap([self, Int(precision)]) } } @@ -88,9 +88,9 @@ extension ExpressionType where UnderlyingType == Double? { /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { - return wrap(self) + return "round".wrap(self) } - return wrap([self, Int(precision)]) + return "round".wrap([self, Int(precision)]) } } @@ -143,7 +143,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } } @@ -158,7 +158,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } } @@ -173,7 +173,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -317,9 +317,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return wrap(self) + return "ltrim".wrap(self) } - return wrap([self, String(characters)]) + return "ltrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -335,9 +335,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return wrap(self) + return "rtrim".wrap(self) } - return wrap([self, String(characters)]) + return "rtrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -353,9 +353,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return wrap([self]) + return "trim".wrap([self]) } - return wrap([self, String(characters)]) + return "trim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -398,7 +398,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return wrap(self) + return "length".wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -542,9 +542,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return wrap(self) + return "ltrim".wrap(self) } - return wrap([self, String(characters)]) + return "ltrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -560,9 +560,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return wrap(self) + return "rtrim".wrap(self) } - return wrap([self, String(characters)]) + return "rtrim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -578,9 +578,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return wrap(self) + return "trim".wrap(self) } - return wrap([self, String(characters)]) + return "trim".wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index d97e52b9..5f8be14b 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -53,237 +53,237 @@ public func +(lhs: String, rhs: Expression) -> Expression { // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "+".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "-".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "*".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return infix(lhs, rhs) + return "/".infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return wrap(rhs) + return "-".wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return wrap(rhs) + return "-".wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "%".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "<<".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return ">>".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "&".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return infix(lhs, rhs) + return "|".infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { @@ -312,10 +312,10 @@ public func ^(lhs: V, rhs: Expression) -> Expression where V. } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return wrap(rhs) + return "~".wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return wrap(rhs) + return "~".wrap(rhs) } // MARK: - @@ -348,130 +348,130 @@ public func ==(lhs: V?, rhs: Expression) -> Expression whe } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } - return infix(lhs, rhs) + return "!=".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return ">=".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return infix(lhs, rhs) + return "<=".infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { From c5be72bac4a022448b92d75a8970e41c6254b21f Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Tue, 26 Mar 2019 17:54:28 +0200 Subject: [PATCH 0679/1046] Strings to enum constants --- Sources/SQLite/Typed/AggregateFunctions.swift | 39 ++- Sources/SQLite/Typed/CoreFunctions.swift | 140 +++++--- Sources/SQLite/Typed/Operators.swift | 331 ++++++++++-------- 3 files changed, 294 insertions(+), 216 deletions(-) diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index bb1157fe..056d617e 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -22,6 +22,19 @@ // THE SOFTWARE. // +private enum Functions: String { + case count + case max + case min + case avg + case sum + case total + + func wrap(_ expression: Expressible) -> Expression { + return self.rawValue.wrap(expression) + } +} + extension ExpressionType where UnderlyingType : Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. @@ -48,7 +61,7 @@ extension ExpressionType where UnderlyingType : Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return "count".wrap(self) + return Functions.count.wrap(self) } } @@ -79,7 +92,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return "count".wrap(self) + return Functions.count.wrap(self) } } @@ -96,7 +109,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return "max".wrap(self) + return Functions.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -109,7 +122,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return "min".wrap(self) + return Functions.min.wrap(self) } } @@ -126,7 +139,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return "max".wrap(self) + return Functions.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -139,7 +152,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return "min".wrap(self) + return Functions.min.wrap(self) } } @@ -156,7 +169,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return "avg".wrap(self) + return Functions.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -169,7 +182,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return "sum".wrap(self) + return Functions.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -182,7 +195,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return "total".wrap(self) + return Functions.total.wrap(self) } } @@ -199,7 +212,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return "avg".wrap(self) + return Functions.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -212,7 +225,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return "sum".wrap(self) + return Functions.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -225,7 +238,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return "total".wrap(self) + return Functions.total.wrap(self) } } @@ -233,7 +246,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return "count".wrap(star(nil, nil)) + return Functions.count.wrap(star(nil, nil)) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index b7b53c71..420110ae 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -24,6 +24,40 @@ import Foundation +private enum Functions: String { + case abs + case round + case random + case randomblob + case zeroblob + case length + case lower + case upper + case ltrim + case rtrim + case trim + case replace + case substr + case LIKE + case IN + case GLOB + case MATCH + case REGEXP + case COLLATE + case ifnull + + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + return self.rawValue.infix(lhs, rhs, wrap: wrap) + } + + func wrap(_ expression: Expressible) -> Expression { + return self.rawValue.wrap(expression) + } + + func wrap(_ expressions: [Expressible]) -> Expression { + return self.rawValue.wrap(", ".join(expressions)) + } +} extension ExpressionType where UnderlyingType : Number { @@ -35,7 +69,7 @@ extension ExpressionType where UnderlyingType : Number { /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return "abs".wrap(self) + return Functions.abs.wrap(self) } } @@ -50,7 +84,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return "abs".wrap(self) + return Functions.abs.wrap(self) } } @@ -68,9 +102,9 @@ extension ExpressionType where UnderlyingType == Double { /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { - return "round".wrap([self]) + return Functions.round.wrap([self]) } - return "round".wrap([self, Int(precision)]) + return Functions.round.wrap([self, Int(precision)]) } } @@ -88,9 +122,9 @@ extension ExpressionType where UnderlyingType == Double? { /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { - return "round".wrap(self) + return Functions.round.wrap(self) } - return "round".wrap([self, Int(precision)]) + return Functions.round.wrap([self, Int(precision)]) } } @@ -104,7 +138,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype = /// /// - Returns: An expression calling the `random` function. public static func random() -> Expression { - return "random".wrap([]) + return Functions.random.wrap([]) } } @@ -120,7 +154,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `randomblob` function. public static func random(_ length: Int) -> Expression { - return "randomblob".wrap([]) + return Functions.randomblob.wrap([]) } /// Builds an expression representing the `zeroblob` function. @@ -132,7 +166,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `zeroblob` function. public static func allZeros(_ length: Int) -> Expression { - return "zeroblob".wrap([]) + return Functions.zeroblob.wrap([]) } /// Builds a copy of the expression wrapped with the `length` function. @@ -143,7 +177,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } } @@ -158,7 +192,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } } @@ -173,7 +207,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -184,7 +218,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return "lower".wrap(self) + return Functions.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -195,7 +229,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return "upper".wrap(self) + return Functions.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -242,9 +276,9 @@ extension ExpressionType where UnderlyingType == String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } - let like: Expression = "LIKE".infix(self, pattern, wrap: false) + let like: Expression = Functions.LIKE.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -260,7 +294,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return "GLOB".infix(self, pattern) + return Functions.GLOB.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -275,7 +309,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return "MATCH".infix(self, pattern) + return Functions.MATCH.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -286,7 +320,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return "REGEXP".infix(self, pattern) + return Functions.REGEXP.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -301,7 +335,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return "COLLATE".infix(self, collation) + return Functions.COLLATE.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -317,9 +351,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return "ltrim".wrap(self) + return Functions.ltrim.wrap(self) } - return "ltrim".wrap([self, String(characters)]) + return Functions.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -335,9 +369,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return "rtrim".wrap(self) + return Functions.rtrim.wrap(self) } - return "rtrim".wrap([self, String(characters)]) + return Functions.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -353,9 +387,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return "trim".wrap([self]) + return Functions.trim.wrap([self]) } - return "trim".wrap([self, String(characters)]) + return Functions.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -372,14 +406,14 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return "replace".wrap([self, pattern, replacement]) + return Functions.replace.wrap([self, pattern, replacement]) } public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { - return "substr".wrap([self, location]) + return Functions.substr.wrap([self, location]) } - return "substr".wrap([self, location, length]) + return Functions.substr.wrap([self, location, length]) } public subscript(range: Range) -> Expression { @@ -398,7 +432,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return "length".wrap(self) + return Functions.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -409,7 +443,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return "lower".wrap(self) + return Functions.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -420,7 +454,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return "upper".wrap(self) + return Functions.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -443,7 +477,7 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } @@ -467,7 +501,7 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } let like: Expression = "LIKE".infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) @@ -485,7 +519,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return "GLOB".infix(self, pattern) + return Functions.GLOB.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -500,7 +534,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return "MATCH".infix(self, pattern) + return Functions.MATCH.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -511,7 +545,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return "REGEXP".infix(self, pattern) + return Functions.REGEXP.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -526,7 +560,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return "COLLATE".infix(self, collation) + return Functions.COLLATE.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -542,9 +576,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return "ltrim".wrap(self) + return Functions.ltrim.wrap(self) } - return "ltrim".wrap([self, String(characters)]) + return Functions.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -560,9 +594,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return "rtrim".wrap(self) + return Functions.rtrim.wrap(self) } - return "rtrim".wrap([self, String(characters)]) + return Functions.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -578,9 +612,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return "trim".wrap(self) + return Functions.trim.wrap(self) } - return "trim".wrap([self, String(characters)]) + return Functions.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -597,7 +631,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return "replace".wrap([self, pattern, replacement]) + return Functions.replace.wrap([self, pattern, replacement]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -617,9 +651,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `substr` function. public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { - return "substr".wrap([self, location]) + return Functions.substr.wrap([self, location]) } - return "substr".wrap([self, location, length]) + return Functions.substr.wrap([self, location, length]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -652,7 +686,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } /// Builds a copy of the expression prepended with an `IN` check against the @@ -668,7 +702,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } } @@ -694,7 +728,7 @@ extension String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return "LIKE".infix(self, pattern) + return Functions.LIKE.infix(self, pattern) } let like: Expression = "LIKE".infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) @@ -718,7 +752,7 @@ extension String { /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: V) -> Expression { - return "ifnull".wrap([optional, defaultValue]) + return Functions.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -738,7 +772,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressi /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return "ifnull".wrap([optional, defaultValue]) + return Functions.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -758,5 +792,5 @@ public func ??(optional: Expression, defaultValue: Expression) /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return "ifnull".wrap([optional, defaultValue]) + return Functions.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 5f8be14b..751215e3 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -24,266 +24,297 @@ // TODO: use `@warn_unused_result` by the time operator functions support it +private enum Operators: String { + case plus = "+" + case minus = "-" + case or = "OR" + case and = "AND" + case not = "NOT " + case mul = "*" + case div = "/" + case mod = "%" + case bitwiseLeft = "<<" + case bitwiseRight = ">>" + case bitwiseAnd = "&" + case bitwiseOr = "|" + case bitwiseXor = "~" + case eq = "=" + case neq = "!=" + case gt = ">" + case lt = "<" + case gte = ">=" + case lte = "<=" + case concatenate = "||" + + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + return self.rawValue.infix(lhs, rhs, wrap: wrap) + } + + func wrap(_ expression: Expressible) -> Expression { + return self.rawValue.wrap(expression) + } +} + public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return "||".infix(lhs, rhs) + return Operators.concatenate.infix(lhs, rhs) } // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "+".infix(lhs, rhs) + return Operators.plus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "-".infix(lhs, rhs) + return Operators.minus.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "*".infix(lhs, rhs) + return Operators.mul.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return "/".infix(lhs, rhs) + return Operators.div.infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return "-".wrap(rhs) + return Operators.minus.wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return "-".wrap(rhs) + return Operators.minus.wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "%".infix(lhs, rhs) + return Operators.mod.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "<<".infix(lhs, rhs) + return Operators.bitwiseLeft.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return ">>".infix(lhs, rhs) + return Operators.bitwiseRight.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "&".infix(lhs, rhs) + return Operators.bitwiseAnd.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return "|".infix(lhs, rhs) + return Operators.bitwiseOr.infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { @@ -312,166 +343,166 @@ public func ^(lhs: V, rhs: Expression) -> Expression where V. } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return "~".wrap(rhs) + return Operators.bitwiseXor.wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return "~".wrap(rhs) + return Operators.bitwiseXor.wrap(rhs) } // MARK: - public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } - return "=".infix(lhs, rhs) + return Operators.eq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } - return "!=".infix(lhs, rhs) + return Operators.neq.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">".infix(lhs, rhs) + return Operators.gt.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return ">=".infix(lhs, rhs) + return Operators.gte.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<".infix(lhs, rhs) + return Operators.lt.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return "<=".infix(lhs, rhs) + return Operators.lte.infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { @@ -517,58 +548,58 @@ public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expr // MARK: - public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return "AND".infix(lhs, rhs) + return Operators.and.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return "OR".infix(lhs, rhs) + return Operators.or.infix(lhs, rhs) } public prefix func !(rhs: Expression) -> Expression { - return "NOT ".wrap(rhs) + return Operators.not.wrap(rhs) } public prefix func !(rhs: Expression) -> Expression { - return "NOT ".wrap(rhs) + return Operators.not.wrap(rhs) } From bc909add007da81916c8b9118df081d746aea4c4 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 27 Mar 2019 13:48:58 +0200 Subject: [PATCH 0680/1046] Fixed enums naming --- Sources/SQLite/Typed/AggregateFunctions.swift | 28 +- Sources/SQLite/Typed/CoreFunctions.swift | 124 +++---- Sources/SQLite/Typed/Operators.swift | 302 +++++++++--------- 3 files changed, 227 insertions(+), 227 deletions(-) diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 056d617e..2ec28288 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -private enum Functions: String { +private enum Function: String { case count case max case min @@ -61,7 +61,7 @@ extension ExpressionType where UnderlyingType : Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Functions.count.wrap(self) + return Function.count.wrap(self) } } @@ -92,7 +92,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Functions.count.wrap(self) + return Function.count.wrap(self) } } @@ -109,7 +109,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Functions.max.wrap(self) + return Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -122,7 +122,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Functions.min.wrap(self) + return Function.min.wrap(self) } } @@ -139,7 +139,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Functions.max.wrap(self) + return Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -152,7 +152,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Functions.min.wrap(self) + return Function.min.wrap(self) } } @@ -169,7 +169,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Functions.avg.wrap(self) + return Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -182,7 +182,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Functions.sum.wrap(self) + return Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -195,7 +195,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Functions.total.wrap(self) + return Function.total.wrap(self) } } @@ -212,7 +212,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Functions.avg.wrap(self) + return Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -225,7 +225,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Functions.sum.wrap(self) + return Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -238,7 +238,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Functions.total.wrap(self) + return Function.total.wrap(self) } } @@ -246,7 +246,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return Functions.count.wrap(star(nil, nil)) + return Function.count.wrap(star(nil, nil)) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 420110ae..068dcf02 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -24,7 +24,7 @@ import Foundation -private enum Functions: String { +private enum Function: String { case abs case round case random @@ -38,12 +38,12 @@ private enum Functions: String { case trim case replace case substr - case LIKE - case IN - case GLOB - case MATCH - case REGEXP - case COLLATE + case like = "LIKE" + case `in` = "IN" + case glob = "GLOB" + case match = "MATCH" + case regexp = "REGEXP" + case collate = "COLLATE" case ifnull func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { @@ -69,7 +69,7 @@ extension ExpressionType where UnderlyingType : Number { /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return Functions.abs.wrap(self) + return Function.abs.wrap(self) } } @@ -84,7 +84,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue : Expression { - return Functions.abs.wrap(self) + return Function.abs.wrap(self) } } @@ -102,9 +102,9 @@ extension ExpressionType where UnderlyingType == Double { /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { - return Functions.round.wrap([self]) + return Function.round.wrap([self]) } - return Functions.round.wrap([self, Int(precision)]) + return Function.round.wrap([self, Int(precision)]) } } @@ -122,9 +122,9 @@ extension ExpressionType where UnderlyingType == Double? { /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { guard let precision = precision else { - return Functions.round.wrap(self) + return Function.round.wrap(self) } - return Functions.round.wrap([self, Int(precision)]) + return Function.round.wrap([self, Int(precision)]) } } @@ -138,7 +138,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype = /// /// - Returns: An expression calling the `random` function. public static func random() -> Expression { - return Functions.random.wrap([]) + return Function.random.wrap([]) } } @@ -154,7 +154,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `randomblob` function. public static func random(_ length: Int) -> Expression { - return Functions.randomblob.wrap([]) + return Function.randomblob.wrap([]) } /// Builds an expression representing the `zeroblob` function. @@ -166,7 +166,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `zeroblob` function. public static func allZeros(_ length: Int) -> Expression { - return Functions.zeroblob.wrap([]) + return Function.zeroblob.wrap([]) } /// Builds a copy of the expression wrapped with the `length` function. @@ -177,7 +177,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } } @@ -192,7 +192,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } } @@ -207,7 +207,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -218,7 +218,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Functions.lower.wrap(self) + return Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -229,7 +229,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Functions.upper.wrap(self) + return Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -276,9 +276,9 @@ extension ExpressionType where UnderlyingType == String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } - let like: Expression = Functions.LIKE.infix(self, pattern, wrap: false) + let like: Expression = Function.like.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -294,7 +294,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Functions.GLOB.infix(self, pattern) + return Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -309,7 +309,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Functions.MATCH.infix(self, pattern) + return Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -320,7 +320,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Functions.REGEXP.infix(self, pattern) + return Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -335,7 +335,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Functions.COLLATE.infix(self, collation) + return Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -351,9 +351,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return Functions.ltrim.wrap(self) + return Function.ltrim.wrap(self) } - return Functions.ltrim.wrap([self, String(characters)]) + return Function.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -369,9 +369,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return Functions.rtrim.wrap(self) + return Function.rtrim.wrap(self) } - return Functions.rtrim.wrap([self, String(characters)]) + return Function.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -387,9 +387,9 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return Functions.trim.wrap([self]) + return Function.trim.wrap([self]) } - return Functions.trim.wrap([self, String(characters)]) + return Function.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -406,14 +406,14 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Functions.replace.wrap([self, pattern, replacement]) + return Function.replace.wrap([self, pattern, replacement]) } public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { - return Functions.substr.wrap([self, location]) + return Function.substr.wrap([self, location]) } - return Functions.substr.wrap([self, location, length]) + return Function.substr.wrap([self, location, length]) } public subscript(range: Range) -> Expression { @@ -432,7 +432,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Functions.length.wrap(self) + return Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -443,7 +443,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Functions.lower.wrap(self) + return Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -454,7 +454,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Functions.upper.wrap(self) + return Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -477,7 +477,7 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } @@ -501,9 +501,9 @@ extension ExpressionType where UnderlyingType == String? { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } - let like: Expression = "LIKE".infix(self, pattern, wrap: false) + let like: Expression = Function.like.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -519,7 +519,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Functions.GLOB.infix(self, pattern) + return Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -534,7 +534,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Functions.MATCH.infix(self, pattern) + return Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -545,7 +545,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Functions.REGEXP.infix(self, pattern) + return Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -560,7 +560,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Functions.COLLATE.infix(self, collation) + return Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -576,9 +576,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return Functions.ltrim.wrap(self) + return Function.ltrim.wrap(self) } - return Functions.ltrim.wrap([self, String(characters)]) + return Function.ltrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `rtrim` function. @@ -594,9 +594,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return Functions.rtrim.wrap(self) + return Function.rtrim.wrap(self) } - return Functions.rtrim.wrap([self, String(characters)]) + return Function.rtrim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `trim` function. @@ -612,9 +612,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { guard let characters = characters else { - return Functions.trim.wrap(self) + return Function.trim.wrap(self) } - return Functions.trim.wrap([self, String(characters)]) + return Function.trim.wrap([self, String(characters)]) } /// Builds a copy of the expression wrapped with the `replace` function. @@ -631,7 +631,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Functions.replace.wrap([self, pattern, replacement]) + return Function.replace.wrap([self, pattern, replacement]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -651,9 +651,9 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression wrapped with the `substr` function. public func substring(_ location: Int, length: Int? = nil) -> Expression { guard let length = length else { - return Functions.substr.wrap([self, location]) + return Function.substr.wrap([self, location]) } - return Functions.substr.wrap([self, location, length]) + return Function.substr.wrap([self, location, length]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -686,7 +686,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Function.in.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } /// Builds a copy of the expression prepended with an `IN` check against the @@ -702,7 +702,7 @@ extension Collection where Iterator.Element : Value { /// the collection. public func contains(_ expression: Expression) -> Expression { let templates = [String](repeating: "?", count: count).joined(separator: ", ") - return Functions.IN.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + return Function.in.infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) } } @@ -728,9 +728,9 @@ extension String { /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { guard let character = character else { - return Functions.LIKE.infix(self, pattern) + return Function.like.infix(self, pattern) } - let like: Expression = "LIKE".infix(self, pattern, wrap: false) + let like: Expression = Function.like.infix(self, pattern, wrap: false) return Expression("(\(like.template) ESCAPE ?)", like.bindings + [String(character)]) } @@ -752,7 +752,7 @@ extension String { /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: V) -> Expression { - return Functions.ifnull.wrap([optional, defaultValue]) + return Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -772,7 +772,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressi /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Functions.ifnull.wrap([optional, defaultValue]) + return Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -792,5 +792,5 @@ public func ??(optional: Expression, defaultValue: Expression) /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Functions.ifnull.wrap([optional, defaultValue]) + return Function.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 751215e3..b5637ea3 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -24,7 +24,7 @@ // TODO: use `@warn_unused_result` by the time operator functions support it -private enum Operators: String { +private enum Operator: String { case plus = "+" case minus = "-" case or = "OR" @@ -56,265 +56,265 @@ private enum Operators: String { } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operators.concatenate.infix(lhs, rhs) + return Operator.concatenate.infix(lhs, rhs) } // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.plus.infix(lhs, rhs) + return Operator.plus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.infix(lhs, rhs) + return Operator.minus.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.mul.infix(lhs, rhs) + return Operator.mul.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.div.infix(lhs, rhs) + return Operator.div.infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.wrap(rhs) + return Operator.minus.wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { - return Operators.minus.wrap(rhs) + return Operator.minus.wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.mod.infix(lhs, rhs) + return Operator.mod.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseLeft.infix(lhs, rhs) + return Operator.bitwiseLeft.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseRight.infix(lhs, rhs) + return Operator.bitwiseRight.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseAnd.infix(lhs, rhs) + return Operator.bitwiseAnd.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseOr.infix(lhs, rhs) + return Operator.bitwiseOr.infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { @@ -343,166 +343,166 @@ public func ^(lhs: V, rhs: Expression) -> Expression where V. } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseXor.wrap(rhs) + return Operator.bitwiseXor.wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operators.bitwiseXor.wrap(rhs) + return Operator.bitwiseXor.wrap(rhs) } // MARK: - public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } - return Operators.eq.infix(lhs, rhs) + return Operator.eq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } - return Operators.neq.infix(lhs, rhs) + return Operator.neq.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gt.infix(lhs, rhs) + return Operator.gt.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.gte.infix(lhs, rhs) + return Operator.gte.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lt.infix(lhs, rhs) + return Operator.lt.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { - return Operators.lte.infix(lhs, rhs) + return Operator.lte.infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { @@ -548,58 +548,58 @@ public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expr // MARK: - public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operators.and.infix(lhs, rhs) + return Operator.and.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operators.or.infix(lhs, rhs) + return Operator.or.infix(lhs, rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operators.not.wrap(rhs) + return Operator.not.wrap(rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operators.not.wrap(rhs) + return Operator.not.wrap(rhs) } From 93f55680604889fea660f191fb9b757d43d961b6 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sat, 30 Mar 2019 16:28:40 -0600 Subject: [PATCH 0681/1046] Updated project to Swift 5, fixed build warnings --- SQLite.xcodeproj/project.pbxproj | 11 +++++--- .../xcschemes/SQLite Mac.xcscheme | 2 +- .../xcschemes/SQLite iOS.xcscheme | 2 +- .../xcschemes/SQLite tvOS.xcscheme | 2 +- .../xcschemes/SQLite watchOS.xcscheme | 2 +- Sources/SQLite/Foundation.swift | 6 ++-- Sources/SQLite/Typed/CustomFunctions.swift | 28 +++++++++---------- 7 files changed, 28 insertions(+), 25 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a155a115..2294065f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -680,7 +680,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -714,10 +714,11 @@ }; buildConfigurationList = EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = EE247AC91C3F04ED00AE3E12; productRefGroup = EE247AD41C3F04ED00AE3E12 /* Products */; @@ -1140,6 +1141,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -1200,6 +1202,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -1269,7 +1272,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1291,7 +1294,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 2691862e..4e94e80a 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ Data { - return Data(bytes: dataValue.bytes) + return Data(dataValue.bytes) } public var datatypeValue: Blob { - return withUnsafeBytes { (pointer: UnsafePointer) -> Blob in - return Blob(bytes: pointer, length: count) + return withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in + return Blob(bytes: pointer.baseAddress!, length: count) } } diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 2389901f..8910a24b 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -39,76 +39,76 @@ public extension Connection { /// The assigned types must be explicit. /// /// - Returns: A closure returning an SQL expression to call the function. - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } // MARK: - - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } // MARK: - - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - public func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } From e28f30c0f861b1e35eae17e9f04e1d0b8c1206d6 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Wed, 3 Apr 2019 11:28:18 -0600 Subject: [PATCH 0682/1046] Swift 5 - Passed through project language version setting to framework & unit test targets - Bumped version to 0.11.6 in documentation & pod spec - Fixed build warnings in unit test code for Swift 5 --- Documentation/Index.md | 16 ++++++------- README.md | 10 ++++---- SQLite.swift.podspec | 4 ++-- SQLite.xcodeproj/project.pbxproj | 32 +++---------------------- Tests/SQLiteTests/FoundationTests.swift | 4 ++-- 5 files changed, 20 insertions(+), 46 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 628e7464..9d40bfdd 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,8 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 4.1 (and -> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 5 (and +> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.5 + github "stephencelis/SQLite.swift" ~> 0.11.6 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.5' + pod 'SQLite.swift', '~> 0.11.6' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.5' + pod 'SQLite.swift/standalone', '~> 0.11.6' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.5' + pod 'SQLite.swift/standalone', '~> 0.11.6' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.5' + pod 'SQLite.swift/SQLCipher', '~> 0.11.6' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") ] ``` diff --git a/README.md b/README.md index bc43b6ec..33598c69 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.1 (and [Xcode][] 9.3). +> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). ### Carthage @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.5 + github "stephencelis/SQLite.swift" ~> 0.11.6 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.5' + pod 'SQLite.swift', '~> 0.11.6' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.5") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") ] ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index c2235aca..63987b4b 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.5" + s.version = "0.11.6" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.2', + 'SWIFT_VERSION' => '5', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2294065f..9d38dbc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -700,7 +700,7 @@ }; EE247ADC1C3F04ED00AE3E12 = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; }; EE247B3B1C3F3ED000AE3E12 = { CreatedOnToolsVersion = 7.2; @@ -1033,8 +1033,6 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1055,8 +1053,6 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1069,8 +1065,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Debug; @@ -1083,8 +1077,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TVOS_DEPLOYMENT_TARGET = 9.1; }; name = Release; @@ -1106,8 +1098,6 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1130,8 +1120,6 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.2; }; @@ -1192,6 +1180,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1246,6 +1235,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -1271,8 +1261,6 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1293,8 +1281,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 5.0; }; name = Release; }; @@ -1306,8 +1292,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1319,8 +1303,6 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1344,8 +1326,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1369,8 +1349,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Release; }; @@ -1385,8 +1363,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -1401,8 +1377,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 0df746d9..dd80afc1 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -3,7 +3,7 @@ import SQLite class FoundationTests : XCTestCase { func testDataFromBlob() { - let data = Data(bytes: [1, 2, 3]) + let data = Data([1, 2, 3]) let blob = data.datatypeValue XCTAssertEqual([1, 2, 3], blob.bytes) } @@ -11,6 +11,6 @@ class FoundationTests : XCTestCase { func testBlobToData() { let blob = Blob(bytes: [1, 2, 3]) let data = Data.fromDatatypeValue(blob) - XCTAssertEqual(Data(bytes: [1, 2, 3]), data) + XCTAssertEqual(Data([1, 2, 3]), data) } } From 7a98a95ebc0a01729a490e6684e8221fd7adf5d8 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Thu, 4 Apr 2019 09:16:01 -0600 Subject: [PATCH 0683/1046] - Reverted to swift 4.2, will go to 5 in another version update - Updated CI config --- .travis.yml | 4 ++-- SQLite.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 909e5672..f1212412 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c rvm: 2.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.1 +osx_image: xcode10.2 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="12.1" + - IOS_VERSION="12.2" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 9d38dbc4..2eb11fd9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1180,7 +1180,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1235,7 +1235,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; From 291f1842138b034d48b65df7a5b4972cbee920c3 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Thu, 4 Apr 2019 09:24:15 -0600 Subject: [PATCH 0684/1046] Reverted other instances of swift 5 / Xcode 10.2 in documentation --- Documentation/Index.md | 4 ++-- README.md | 4 ++-- SQLite.swift.podspec | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 9d40bfdd..cf4815b9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,8 +67,8 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and -> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 4.2 (and +> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage diff --git a/README.md b/README.md index 33598c69..9192fe21 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). +> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 9.3). ### Carthage diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 63987b4b..fcdf88c1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '5', + 'SWIFT_VERSION' => '4.2', } s.subspec 'standard' do |ss| From 7de9ee55694ae2b7df964c27724bb2811fb1ed9f Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 5 Apr 2019 08:53:37 -0600 Subject: [PATCH 0685/1046] Changed CI config back to Xcode 10.1, updated documents --- .travis.yml | 4 ++-- Documentation/Index.md | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1212412..909e5672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c rvm: 2.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.2 +osx_image: xcode10.1 env: global: - IOS_SIMULATOR="iPhone 6s" - - IOS_VERSION="12.2" + - IOS_VERSION="12.1" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Documentation/Index.md b/Documentation/Index.md index cf4815b9..4925ca77 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -68,7 +68,7 @@ ## Installation > _Note:_ SQLite.swift requires Swift 4.2 (and -> [Xcode 9.3](https://developer.apple.com/xcode/downloads/)) or greater. +> [Xcode 10](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage diff --git a/README.md b/README.md index 9192fe21..bdb7aeaa 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 9.3). +> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10). ### Carthage From a3ad1f85a5fb8b31b026a73aa17e3925b618ac06 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 5 Apr 2019 08:54:37 -0600 Subject: [PATCH 0686/1046] Updated Swift badge to 4.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdb7aeaa..d7c87acc 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-4.1-orange.svg?style=flat +[Swift4Badge]: https://img.shields.io/badge/swift-4.2-orange.svg?style=flat [Swift4Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift From 3b4b93f17b90da96b99e636113eb68c71eabfb41 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 5 Apr 2019 09:08:50 -0600 Subject: [PATCH 0687/1046] Changed Xcode back to 10.2 for CI config and documentation (apparently `withUnsafeBytes` doesn't use UnsafeRawBufferPointer prior to 10.2) --- .travis.yml | 2 +- Documentation/Index.md | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 909e5672..2ce89b05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: objective-c rvm: 2.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.1 +osx_image: xcode10.2 env: global: - IOS_SIMULATOR="iPhone 6s" diff --git a/Documentation/Index.md b/Documentation/Index.md index 4925ca77..651aa991 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -68,7 +68,7 @@ ## Installation > _Note:_ SQLite.swift requires Swift 4.2 (and -> [Xcode 10](https://developer.apple.com/xcode/downloads/)) or greater. +> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. ### Carthage diff --git a/README.md b/README.md index d7c87acc..a2f090e0 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10). +> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10.2). ### Carthage From c31b43fee598eef452083bce3878d8f4bc328bd1 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Tue, 9 Apr 2019 13:02:02 -0600 Subject: [PATCH 0688/1046] Changed travis CI device name to iPhone X --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2ce89b05..2c474b79 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rvm: 2.3 osx_image: xcode10.2 env: global: - - IOS_SIMULATOR="iPhone 6s" + - IOS_SIMULATOR="iPhone X" - IOS_VERSION="12.1" matrix: include: From e926eb1120a8d4ac8cee0a5ee6f84bd664cc5376 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Tue, 9 Apr 2019 14:24:08 -0600 Subject: [PATCH 0689/1046] - Updated makefile iOS simulator to iPhone X - Updated cocoapods version in gem file --- Makefile | 2 +- Tests/CocoaPods/Gemfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d98c8deb..7792ecdb 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone 6s +IOS_SIMULATOR = iPhone X IOS_VERSION = 12.1 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index fd1c8c2e..77d90eec 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.0beta2' +gem 'cocoapods', '~> 1.6.1' gem 'minitest' From c2487732f539fe2c0217a014206036a037a89c8d Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 09:03:51 -0600 Subject: [PATCH 0690/1046] Implemented fixes provided by @timshadel --- .travis.yml | 3 +++ Sources/SQLite/Info.plist | 2 +- Tests/CocoaPods/Makefile | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c474b79..d54827b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,4 +25,7 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: +# Workaround for Xcode 10.2/tvOS 9.1 bug +# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 + - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 7347d842..c3f9414b 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.4 + 0.11.6 CFBundleSignature ???? CFBundleVersion diff --git a/Tests/CocoaPods/Makefile b/Tests/CocoaPods/Makefile index 26163fdb..fa87e245 100644 --- a/Tests/CocoaPods/Makefile +++ b/Tests/CocoaPods/Makefile @@ -1,7 +1,9 @@ +XCPRETTY := $(shell command -v xcpretty) + test: install repo_update @set -e; \ for test in *_test.rb; do \ - bundle exec ./$$test; \ + bundle exec ./$$test | $(XCPRETTY) -C; \ done repo_update: From d89c0e895c3a1f956bb024a36a299719bf5f209e Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 09:25:43 -0600 Subject: [PATCH 0691/1046] Fixed option typo --- Tests/CocoaPods/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CocoaPods/Makefile b/Tests/CocoaPods/Makefile index fa87e245..532dcbff 100644 --- a/Tests/CocoaPods/Makefile +++ b/Tests/CocoaPods/Makefile @@ -3,7 +3,7 @@ XCPRETTY := $(shell command -v xcpretty) test: install repo_update @set -e; \ for test in *_test.rb; do \ - bundle exec ./$$test | $(XCPRETTY) -C; \ + bundle exec ./$$test | $(XCPRETTY) -c; \ done repo_update: From 9ae1a0bfe1f950099042964b0b4e6cb696681075 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 11:27:07 -0600 Subject: [PATCH 0692/1046] Updated to Swift 5 --- .travis.yml | 4 ++-- Documentation/Index.md | 14 +++++++------- Makefile | 2 +- README.md | 10 +++++----- SQLite.swift.podspec | 4 ++-- SQLite.xcodeproj/project.pbxproj | 4 ++-- Sources/SQLite/Info.plist | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index d54827b2..c8fc5feb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ rvm: 2.3 osx_image: xcode10.2 env: global: - - IOS_SIMULATOR="iPhone X" - - IOS_VERSION="12.1" + - IOS_SIMULATOR="iPhone XS" + - IOS_VERSION="12.2" matrix: include: - env: BUILD_SCHEME="SQLite iOS" diff --git a/Documentation/Index.md b/Documentation/Index.md index 651aa991..cbb5d49a 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -67,7 +67,7 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and +> _Note:_ SQLite.swift requires Swift 5 (and > [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.6 + github "stephencelis/SQLite.swift" ~> 0.12 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.6' + pod 'SQLite.swift', '~> 0.12' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.6' + pod 'SQLite.swift/standalone', '~> 0.12' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.11.6' + pod 'SQLite.swift/standalone', '~> 0.12' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.11.6' + pod 'SQLite.swift/SQLCipher', '~> 0.12' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") ] ``` diff --git a/Makefile b/Makefile index 7792ecdb..5ea45d5d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone X -IOS_VERSION = 12.1 +IOS_VERSION = 12.2 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/README.md b/README.md index a2f090e0..ce45ef4e 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 4.2 (and [Xcode][] 10.2). +> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). ### Carthage @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.11.6 + github "stephencelis/SQLite.swift" ~> 0.12 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.11.6' + pod 'SQLite.swift', '~> 0.12' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.11.6") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") ] ``` @@ -285,7 +285,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-4.2-orange.svg?style=flat +[Swift4Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat [Swift4Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index fcdf88c1..8b202745 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.11.6" + s.version = "0.12" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." s.description = <<-DESC @@ -21,7 +21,7 @@ Pod::Spec.new do |s| s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '4.2', + 'SWIFT_VERSION' => '5', } s.subspec 'standard' do |ss| diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2eb11fd9..9d38dbc4 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1180,7 +1180,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1235,7 +1235,7 @@ PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index c3f9414b..3a5fc4f6 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.11.6 + 0.12 CFBundleSignature ???? CFBundleVersion From c0b9eb0dfd9faa6176a1ddbec87d98c076a4f62c Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Fri, 19 Apr 2019 11:29:40 -0600 Subject: [PATCH 0693/1046] Updated iOS simulator to iPhone XS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5ea45d5d..50d07148 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone X +IOS_SIMULATOR = iPhone XS IOS_VERSION = 12.2 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" From aa9821896a17878c0bca089d2e61415a2a806282 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sun, 21 Apr 2019 10:59:15 -0600 Subject: [PATCH 0694/1046] Documentation - Updated README to clarify Swift 5 vs Swift 4.2 supported versions - Updated minimum CocoaPods version - Updated SPM language versions --- Documentation/Index.md | 2 +- Package.swift | 2 +- README.md | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index cbb5d49a..94ad580b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -96,7 +96,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift - requires version 1.0.0 or greater). + requires version 1.6.1 or greater). ```sh # Using the default Ruby install will require you to use sudo when diff --git a/Package.swift b/Package.swift index c008e482..2d4325f5 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .target(name: "SQLiteObjc"), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - swiftLanguageVersions: [4] + swiftLanguageVersions: [5] ) #if os(Linux) diff --git a/README.md b/README.md index ce45ef4e..6eaa69bb 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and [Xcode][] 10.2). +> _Note:_ Version 0.12 requires Swift 5 (and [Xcode][] 10.2). \nVersion 0.11.6 requires Swift 4.2 (and [Xcode][] 10.1). ### Carthage @@ -142,7 +142,7 @@ install SQLite.swift with Carthage: SQLite.swift with CocoaPods: 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift - requires version 1.0.0 or greater.) + requires version 1.6.1 or greater.) ```sh # Using the default Ruby install will require you to use sudo when @@ -285,8 +285,8 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitterBadge]: https://badges.gitter.im/stephencelis/SQLite.swift.svg [GitterLink]: https://gitter.im/stephencelis/SQLite.swift -[Swift4Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat -[Swift4Link]: https://developer.apple.com/swift/ +[Swift5Badge]: https://img.shields.io/badge/swift-5-orange.svg?style=flat +[Swift5Link]: https://developer.apple.com/swift/ [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift [FMDB]: https://github.com/ccgus/fmdb From 3afd756e04b90ca40f26c864f7168fd2335c591b Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sun, 21 Apr 2019 11:04:11 -0600 Subject: [PATCH 0695/1046] Fixed Swift badges and installation note formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6eaa69bb..8efc53d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift4 compatible][Swift4Badge]][Swift4Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ Version 0.12 requires Swift 5 (and [Xcode][] 10.2). \nVersion 0.11.6 requires Swift 4.2 (and [Xcode][] 10.1). +> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/)) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1 or greater). ### Carthage From 71dfa68f001c47046958001f4ec8b07ca3a7c7aa Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Sun, 21 Apr 2019 11:05:38 -0600 Subject: [PATCH 0696/1046] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8efc53d8..41c7ffdc 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/)) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1 or greater). +> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. ### Carthage From f9cb6bdab2aeae1dc119d0177c90ac93ae710fc1 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Mon, 22 Apr 2019 11:15:15 -0600 Subject: [PATCH 0697/1046] Documentation - Updated swift tools version in Package.swift - Updated "OS X" references to "macOS" --- CONTRIBUTING.md | 2 +- Documentation/Index.md | 2 +- Package.swift | 2 +- SQLite.swift.podspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60c18370..6c367b95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Made it through everything above and still having trouble? Sorry! - Even better: link to a sample project exhibiting the issue. - Include the SQLite.swift commit or branch experiencing the issue. - Include devices and operating systems affected. - - Include build information: the Xcode and OS X versions affected. + - Include build information: the Xcode and macOS versions affected. [installation instructions]: Documentation/Index.md#installation [See Documentation]: Documentation/Index.md#sqliteswift-documentation diff --git a/Documentation/Index.md b/Documentation/Index.md index 94ad580b..211c01a9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -258,7 +258,7 @@ let path = NSSearchPathForDirectoriesInDomains( let db = try Connection("\(path)/db.sqlite3") ``` -On OS X, you can use your app’s **Application Support** directory: +On macOS, you can use your app’s **Application Support** directory: ```swift var path = NSSearchPathForDirectoriesInDomains( diff --git a/Package.swift b/Package.swift index 2d4325f5..6644cd73 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.0 import PackageDescription let package = Package( diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 8b202745..9405c903 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" s.version = "0.12" - s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and OS X." + s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and From 78089e2bdebbf1f634eaf85876d719981ed3c147 Mon Sep 17 00:00:00 2001 From: Stephan Heilner Date: Thu, 31 May 2018 15:14:02 -0600 Subject: [PATCH 0698/1046] Fixed bug not allowing columns from multiple tables in the .select --- SQLite.xcodeproj/project.pbxproj | 8 +++++ Sources/SQLite/Typed/Query.swift | 1 - Tests/SQLiteTests/SelectTests.swift | 45 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 Tests/SQLiteTests/SelectTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2eb11fd9..9143d1b2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -108,6 +108,9 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; + D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; + D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -228,6 +231,7 @@ 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D4DB368A20C09C9B00D5A58E /* SelectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -418,6 +422,7 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */, 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, + D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -847,6 +852,7 @@ 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, + D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -935,6 +941,7 @@ 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, + D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -993,6 +1000,7 @@ 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, + D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f6ef6df8..7ec5e581 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -963,7 +963,6 @@ extension Connection { try expandGlob(true)(q) continue column } - throw QueryError.noSuchTable(name: namespace) } throw QueryError.noSuchTable(name: namespace) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift new file mode 100644 index 00000000..bca01092 --- /dev/null +++ b/Tests/SQLiteTests/SelectTests.swift @@ -0,0 +1,45 @@ +import XCTest +@testable import SQLite + +class SelectTests: SQLiteTestCase { + + override func setUp() { + super.setUp() + CreateUsersTable() + CreateUsersDataTable() + } + + func CreateUsersDataTable() { + try! db.execute(""" + CREATE TABLE users_name ( + id INTEGER, + user_id INTEGER REFERENCES users(id), + name TEXT + ) + """ + ) + } + + func test_select_columns_from_multiple_tables() { + let usersData = Table("users_name") + let users = Table("users") + + let name = Expression("name") + let id = Expression("id") + let userID = Expression("user_id") + let email = Expression("email") + + try! InsertUser("Joey") + try! db.run(usersData.insert( + id <- 1, + userID <- 1, + name <- "Joey" + )) + + try! db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { + XCTAssertEqual($0[name], "Joey") + XCTAssertEqual($0[email], "Joey@example.com") + } + } + +} From e8ca9c0d138dca152df122053a8a2e181e6c3c7d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Apr 2019 12:29:15 -0600 Subject: [PATCH 0699/1046] Reverted swift tools version to 4.0 for backwards compatibility Co-Authored-By: sburlewapg <49003548+sburlewapg@users.noreply.github.com> --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 6644cd73..2d4325f5 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:4.0 import PackageDescription let package = Package( From 3e27caa3d898ce366fc29c8a8af9ccb105bd21d1 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 22 Apr 2019 12:38:02 -0600 Subject: [PATCH 0700/1046] Added Swift 4 and 4.2 for CI compatibility Co-Authored-By: sburlewapg <49003548+sburlewapg@users.noreply.github.com> --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 2d4325f5..96266a2c 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .target(name: "SQLiteObjc"), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - swiftLanguageVersions: [5] + swiftLanguageVersions: [.version("5"), .v4_2, .v4] ) #if os(Linux) From a93b4956946f122a7f2202297f2f284da829e476 Mon Sep 17 00:00:00 2001 From: Shawn Burlew Date: Mon, 22 Apr 2019 13:34:16 -0600 Subject: [PATCH 0701/1046] Changed language versions array to contain Ints to try and get CI working --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 96266a2c..430ae6f2 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( .target(name: "SQLiteObjc"), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") ], - swiftLanguageVersions: [.version("5"), .v4_2, .v4] + swiftLanguageVersions: [4, 5] ) #if os(Linux) From 08a1696765940e4fdc6e7fd33ab76d92a87239c7 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 24 Apr 2019 20:52:22 +0300 Subject: [PATCH 0702/1046] Fixed version --- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 9405c903..57eda40a 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.12" + s.version = "0.12.0" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 3a5fc4f6..db84c711 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12 + 0.12.0 CFBundleSignature ???? CFBundleVersion From 9b2d3e231feeb25f960e36915656b42719435bea Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 24 Apr 2019 21:44:45 +0300 Subject: [PATCH 0703/1046] Updated version in README and Documentation --- Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- Sources/SQLite/Typed/Expression.swift | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 211c01a9..05967ff4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -80,7 +80,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12 + github "stephencelis/SQLite.swift" ~> 0.12.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -110,7 +110,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12' + pod 'SQLite.swift', '~> 0.12.0' end ``` @@ -124,7 +124,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12' + pod 'SQLite.swift/standalone', '~> 0.12.0' end ``` @@ -134,7 +134,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12' + pod 'SQLite.swift/standalone', '~> 0.12.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -148,7 +148,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.12' + pod 'SQLite.swift/SQLCipher', '~> 0.12.0' end ``` @@ -181,7 +181,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") ] ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 8a3d5a11..5f885de8 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. > ⚠ This document is currently not actively maintained. See -> the [0.12.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.12.0) +> the [0.13.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.0) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index 41c7ffdc..b7a18e0b 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12 + github "stephencelis/SQLite.swift" ~> 0.12.0 ``` 3. Run `carthage update` and @@ -156,7 +156,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12' + pod 'SQLite.swift', '~> 0.12.0' end ``` @@ -174,7 +174,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") ] ``` diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index d89ee6cc..76ba04c4 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,7 +73,7 @@ public protocol Expressible { extension Expressible { // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - // FIXME: make internal (0.12.0) + // FIXME: make internal (0.13.0) public func asSQL() -> String { let expressed = expression var idx = 0 From 9a0170ed7a7a9b8a0bcb976783f849025e7f35e9 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Thu, 25 Apr 2019 01:13:04 +0300 Subject: [PATCH 0704/1046] Removed warning in the Cipher extension --- Sources/SQLite/Extensions/Cipher.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 71ef1765..44919aab 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -60,7 +60,7 @@ extension Connection { // the key provided is incorrect. To test that the database can be successfully opened with the // provided key, it is necessary to perform some operation on the database (i.e. read from it). private func cipher_key_check() throws { - try scalar("SELECT count(*) FROM sqlite_master;") + let _ = try scalar("SELECT count(*) FROM sqlite_master;") } } #endif From 026c5c264f925c0796d1cb72ee4927019c1dec4e Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Fri, 26 Apr 2019 22:27:16 +0200 Subject: [PATCH 0705/1046] Introduce support for backup --- SQLite.xcodeproj/project.pbxproj | 18 +++ Sources/SQLite/Core/Backup.swift | 176 ++++++++++++++++++++++++++++ Tests/SQLiteTests/BackupTests.swift | 36 ++++++ 3 files changed, 230 insertions(+) create mode 100644 Sources/SQLite/Core/Backup.swift create mode 100644 Tests/SQLiteTests/BackupTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 9d38dbc4..aec22743 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 02A43A9822738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9922738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; + 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; + 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; @@ -211,6 +218,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 02A43A9722738CF100FEC494 /* Backup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backup.swift; sourceTree = ""; }; + 02A43A9C22738E2900FEC494 /* BackupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupTests.swift; sourceTree = ""; }; 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; @@ -398,6 +407,7 @@ 19A17E2695737FAB5D6086E3 /* fixtures */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, + 02A43A9C22738E2900FEC494 /* BackupTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, @@ -434,6 +444,7 @@ EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, + 02A43A9722738CF100FEC494 /* Backup.swift */, ); path = Core; sourceTree = ""; @@ -817,6 +828,7 @@ 03A65E7E1C6BB2FB0062603F /* AggregateFunctions.swift in Sources */, 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */, 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */, + 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, ); @@ -839,6 +851,7 @@ 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, + 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */, 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, @@ -875,6 +888,7 @@ 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */, 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */, 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */, + 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */, 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, ); @@ -905,6 +919,7 @@ EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */, + 02A43A9822738CF100FEC494 /* Backup.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, ); @@ -927,6 +942,7 @@ EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, + 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, @@ -963,6 +979,7 @@ EE247B6D1C3F3FEC00AE3E12 /* AggregateFunctions.swift in Sources */, 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */, 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */, + 02A43A9922738CF100FEC494 /* Backup.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, ); @@ -985,6 +1002,7 @@ EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, + 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift new file mode 100644 index 00000000..d001ae7d --- /dev/null +++ b/Sources/SQLite/Core/Backup.swift @@ -0,0 +1,176 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import Dispatch +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +/// An object representing database backup. +/// +/// See: +public final class Backup { + + /// The name of the database to backup + public enum DatabaseName { + + /// The main database + case main + + /// The temporary database + case temp + + /// A database added to the connection with ATTACH statement + case attached(name: String) + + var name: String { + switch self { + case .main: + return "main" + case .temp: + return "temp" + case .attached(let name): + return name + } + } + } + + /// Number of pages to copy while performing a backup step + public enum Pages { + + /// Indicates all remaining pages should be copied + case all + + /// Indicates the maximal number of pages to be copied in single step + case limited(number: Int32) + + var number: Int32 { + switch self { + case .all: + return -1 + case .limited(let number): + return number + } + } + } + + /// Total number of pages to copy + /// + /// See: + public var pageCount: Int32 { + return handle.map { sqlite3_backup_pagecount($0) } ?? 0 + } + + /// Number of remaining pages to copy. + /// + /// See: + public var remainingPages: Int32 { + return handle.map { sqlite3_backup_remaining($0) } ?? 0 + } + + private let targetConnection: Connection + private let sourceConnection: Connection + + private var handle: OpaquePointer? + + /// Initializes a new SQLite backup. + /// + /// - Parameters: + /// + /// - targetConnection: The connection to the database to save backup into. + /// + /// - targetConnection: The name of the database to save backup into. + /// + /// Default: `.main`. + /// + /// - sourceConnection: The connection to the database to backup. + /// + /// - targetConnection: The name of the database to backup. + /// + /// Default: `.main`. + /// + /// - Returns: A new database backup. + /// + /// See: + public init(targetConnection: Connection, + targetName: DatabaseName = .main, + sourceConnection: Connection, + sourceName: DatabaseName = .main) throws { + + self.targetConnection = targetConnection + self.sourceConnection = sourceConnection + + self.handle = sqlite3_backup_init(targetConnection.handle, + targetName.name.withCString { $0 }, + sourceConnection.handle, + sourceName.name.withCString { $0 }) + + if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) { + throw error + } + } + + /// Performs a backup step. + /// + /// - Parameter pagesToCopy: The maximal number of pages to copy in one step + /// + /// - Throws: `Result.Error` if step fails. + // + /// See: + public func step(pagesToCopy pages: Pages = .all) throws { + let status = sqlite3_backup_step(handle, pages.number) + + guard status != SQLITE_DONE else { + finish() + return + } + + if let error = Result(errorCode: status, connection: targetConnection) { + throw error + } + } + + /// Finalizes backup. + /// + /// See: + public func finish() { + guard let handle = self.handle else { + return + } + + sqlite3_backup_finish(handle) + self.handle = nil + } + + deinit { + finish() + } +} diff --git a/Tests/SQLiteTests/BackupTests.swift b/Tests/SQLiteTests/BackupTests.swift new file mode 100644 index 00000000..284ba610 --- /dev/null +++ b/Tests/SQLiteTests/BackupTests.swift @@ -0,0 +1,36 @@ +import XCTest +import Foundation +import Dispatch +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class BackupTests : SQLiteTestCase { + override func setUp() { + super.setUp() + + CreateUsersTable() + } + + func test_backup_copies_database() throws { + let source = db! + let target = try Connection() + + try InsertUsers("alice", "betsy") + + let backup = try Backup(targetConnection: target, sourceConnection: source) + try backup.step() + + let users = try target.prepare("SELECT email FROM users ORDER BY email") + XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) + } +} + From d10c9da458377c245a59f8a817196b1ca221414a Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Sat, 27 Apr 2019 00:21:17 +0200 Subject: [PATCH 0706/1046] Pass swift strings instead of UnsafePointers while initializing backup --- Sources/SQLite/Core/Backup.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index d001ae7d..e4fc6204 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -129,9 +129,9 @@ public final class Backup { self.sourceConnection = sourceConnection self.handle = sqlite3_backup_init(targetConnection.handle, - targetName.name.withCString { $0 }, + targetName.name, sourceConnection.handle, - sourceName.name.withCString { $0 }) + sourceName.name) if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) { throw error From 69ca8fed4574a8fac8f3074b4ca025ed5afdeac5 Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Mon, 29 Apr 2019 18:07:45 +0200 Subject: [PATCH 0707/1046] More convenient API for backup --- SQLite.xcodeproj/project.pbxproj | 8 ------ Sources/SQLite/Core/Backup.swift | 4 +-- Sources/SQLite/Core/Connection.swift | 29 +++++++++++++++++++- Tests/SQLiteTests/BackupTests.swift | 36 ------------------------- Tests/SQLiteTests/ConnectionTests.swift | 12 +++++++++ 5 files changed, 42 insertions(+), 47 deletions(-) delete mode 100644 Tests/SQLiteTests/BackupTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index aec22743..b0bf35e7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -11,9 +11,6 @@ 02A43A9922738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; - 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; - 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; - 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9C22738E2900FEC494 /* BackupTests.swift */; }; 03A65E641C6BB0F60062603F /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; @@ -219,7 +216,6 @@ /* Begin PBXFileReference section */ 02A43A9722738CF100FEC494 /* Backup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backup.swift; sourceTree = ""; }; - 02A43A9C22738E2900FEC494 /* BackupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupTests.swift; sourceTree = ""; }; 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; @@ -407,7 +403,6 @@ 19A17E2695737FAB5D6086E3 /* fixtures */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, - 02A43A9C22738E2900FEC494 /* BackupTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, @@ -851,7 +846,6 @@ 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, - 02A43A9F22738E2900FEC494 /* BackupTests.swift in Sources */, 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, @@ -942,7 +936,6 @@ EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, - 02A43A9D22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, @@ -1002,7 +995,6 @@ EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, - 02A43A9E22738E2900FEC494 /* BackupTests.swift in Sources */, EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index e4fc6204..1ea9637d 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -107,13 +107,13 @@ public final class Backup { /// /// - targetConnection: The connection to the database to save backup into. /// - /// - targetConnection: The name of the database to save backup into. + /// - targetName: The name of the database to save backup into. /// /// Default: `.main`. /// /// - sourceConnection: The connection to the database to backup. /// - /// - targetConnection: The name of the database to backup. + /// - sourceName: The name of the database to backup. /// /// Default: `.main`. /// diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..c23076af 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -627,7 +627,34 @@ public final class Connection { } fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32 fileprivate var collations = [String: Collation]() - + + // MARK: - Backup + + /// Prepares a new backup for current connection. + /// + /// - Parameters: + /// + /// - databaseName: The name of the database to backup. + /// + /// Default: `.main` + /// + /// - targetConnection: The name of the database to save backup into. + /// + /// - targetDatabaseName: The name of the database to save backup into. + /// + /// Default: `.main`. + /// + /// - Returns: A new database backup. + + public func backup(databaseName: Backup.DatabaseName = .main, + usingConnection targetConnection: Connection, + andDatabaseName targetDatabaseName: Backup.DatabaseName = .main) throws -> Backup { + return try Backup(targetConnection: targetConnection, + targetName: targetDatabaseName, + sourceConnection: self, + sourceName: databaseName) + } + // MARK: - Error Handling func sync(_ block: () throws -> T) rethrows -> T { diff --git a/Tests/SQLiteTests/BackupTests.swift b/Tests/SQLiteTests/BackupTests.swift deleted file mode 100644 index 284ba610..00000000 --- a/Tests/SQLiteTests/BackupTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -import XCTest -import Foundation -import Dispatch -@testable import SQLite - -#if SQLITE_SWIFT_STANDALONE -import sqlite3 -#elseif SQLITE_SWIFT_SQLCIPHER -import SQLCipher -#elseif os(Linux) -import CSQLite -#else -import SQLite3 -#endif - -class BackupTests : SQLiteTestCase { - override func setUp() { - super.setUp() - - CreateUsersTable() - } - - func test_backup_copies_database() throws { - let source = db! - let target = try Connection() - - try InsertUsers("alice", "betsy") - - let backup = try Backup(targetConnection: target, sourceConnection: source) - try backup.step() - - let users = try target.prepare("SELECT email FROM users ORDER BY email") - XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) - } -} - diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index eab3cf00..bae13971 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -141,6 +141,18 @@ class ConnectionTests : SQLiteTestCase { AssertSQL("BEGIN EXCLUSIVE TRANSACTION") } + + func test_backup_copiesDatabase() throws { + let target = try Connection() + + try InsertUsers("alice", "betsy") + + let backup = try db.backup(usingConnection: target) + try backup.step() + + let users = try target.prepare("SELECT email FROM users ORDER BY email") + XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) + } func test_transaction_beginsAndCommitsTransactions() { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") From 2509ee37b80b7633a8f6dc8e5c72f34ae60f08dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EG=C3=B6kay=20Borulday?= Date: Wed, 22 May 2019 17:57:57 +0300 Subject: [PATCH 0708/1046] Add missing parenthesis --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 05967ff4..70d67c2d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1209,7 +1209,7 @@ We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` function on a `Table` or `VirtualTable`. ```swift -try db.run(users.rename(Table("users_old")) +try db.run(users.rename(Table("users_old"))) // ALTER TABLE "users" RENAME TO "users_old" ``` From c27d53cab370631a21f089a19ac72a183693ab16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=A5gedal=20Reimer?= Date: Thu, 30 May 2019 11:15:11 +0200 Subject: [PATCH 0709/1046] Rename SQLite-Bridging.h -> SQLiteObjc.h Fixes #925 and makes SwiftPM-generated Xcode projects that use SQLite.swift compile out of the box. --- SQLite.xcodeproj/project.pbxproj | 20 +++++++++---------- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ Sources/SQLite/SQLite.h | 2 +- Sources/SQLiteObjc/SQLite-Bridging.m | 2 +- .../{SQLite-Bridging.h => SQLiteObjc.h} | 0 5 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename Sources/SQLiteObjc/include/{SQLite-Bridging.h => SQLiteObjc.h} (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 9d38dbc4..2d7735db 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E761C6BB2E60062603F /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 03A65E771C6BB2E60062603F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; @@ -101,7 +101,7 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; - 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -180,8 +180,8 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ @@ -277,7 +277,7 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808D1C46E5230038162A /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SQLite-Bridging.h"; path = "../../SQLiteObjc/include/SQLite-Bridging.h"; sourceTree = ""; }; + EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -426,7 +426,7 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - EE91808D1C46E5230038162A /* SQLite-Bridging.h */, + EE91808D1C46E5230038162A /* SQLiteObjc.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, @@ -509,7 +509,7 @@ buildActionMask = 2147483647; files = ( 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, - 03A65E751C6BB2DF0062603F /* SQLite-Bridging.h in Headers */, + 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -518,7 +518,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3FB1DB2470600A4F4C6 /* SQLite-Bridging.h in Headers */, + 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */, 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); @@ -528,7 +528,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE91808E1C46E5230038162A /* SQLite-Bridging.h in Headers */, + EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); @@ -540,7 +540,7 @@ files = ( EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, - EE91808F1C46E76D0038162A /* SQLite-Bridging.h in Headers */, + EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Sources/SQLite/SQLite.h b/Sources/SQLite/SQLite.h index 693ce323..69382ea0 100644 --- a/Sources/SQLite/SQLite.h +++ b/Sources/SQLite/SQLite.h @@ -3,4 +3,4 @@ FOUNDATION_EXPORT double SQLiteVersionNumber; FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; -#import +#import diff --git a/Sources/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLite-Bridging.m index e00a7315..9f2be0bd 100644 --- a/Sources/SQLiteObjc/SQLite-Bridging.m +++ b/Sources/SQLiteObjc/SQLite-Bridging.m @@ -22,7 +22,7 @@ // THE SOFTWARE. // -#import "SQLite-Bridging.h" +#import "SQLiteObjc.h" #import "fts3_tokenizer.h" #pragma mark - FTS diff --git a/Sources/SQLiteObjc/include/SQLite-Bridging.h b/Sources/SQLiteObjc/include/SQLiteObjc.h similarity index 100% rename from Sources/SQLiteObjc/include/SQLite-Bridging.h rename to Sources/SQLiteObjc/include/SQLiteObjc.h From dc38f8c2da63aa238b3361e20893714d9c93bf42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=A5gedal=20Reimer?= Date: Thu, 30 May 2019 22:05:11 +0200 Subject: [PATCH 0710/1046] Rename SQLite-Bridging.m -> SQLiteObjc.m --- SQLite.xcodeproj/project.pbxproj | 20 +++++++++---------- .../{SQLite-Bridging.m => SQLiteObjc.m} | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename Sources/SQLiteObjc/{SQLite-Bridging.m => SQLiteObjc.m} (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2d7735db..30a40214 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 03A65E761C6BB2E60062603F /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 03A65E771C6BB2E60062603F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -100,7 +100,7 @@ 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; @@ -113,7 +113,7 @@ EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B081C3F06E900AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -166,7 +166,7 @@ EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */; }; + EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -236,7 +236,7 @@ EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../../SQLiteObjc/fts3_tokenizer.h; sourceTree = ""; }; - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SQLite-Bridging.m"; path = "../../SQLiteObjc/SQLite-Bridging.m"; sourceTree = ""; }; + EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SQLiteObjc.m; path = ../../SQLiteObjc/SQLiteObjc.m; sourceTree = ""; }; EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; @@ -430,7 +430,7 @@ EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, - EE247AF11C3F06E900AE3E12 /* SQLite-Bridging.m */, + EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, @@ -800,7 +800,7 @@ 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, - 03A65E791C6BB2EF0062603F /* SQLite-Bridging.m in Sources */, + 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, @@ -854,7 +854,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3F91DB246E700A4F4C6 /* SQLite-Bridging.m in Sources */, + 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, @@ -900,7 +900,7 @@ EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, - EE247B061C3F06E900AE3E12 /* SQLite-Bridging.m in Sources */, + EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, @@ -946,7 +946,7 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, - EE247B681C3F3FEC00AE3E12 /* SQLite-Bridging.m in Sources */, + EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */, EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */, diff --git a/Sources/SQLiteObjc/SQLite-Bridging.m b/Sources/SQLiteObjc/SQLiteObjc.m similarity index 100% rename from Sources/SQLiteObjc/SQLite-Bridging.m rename to Sources/SQLiteObjc/SQLiteObjc.m From 8dbbf80a28a42847e8b41a649c3a384c37106475 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 19 Jun 2019 17:17:21 +0300 Subject: [PATCH 0711/1046] version bump --- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 57eda40a..524e721c 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.12.0" + s.version = "0.12.1" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index db84c711..f7f7ce7f 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.0 + 0.12.1 CFBundleSignature ???? CFBundleVersion From 1b1ec920f1e168707b1a6af80d297d8f0d77bbd1 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Thu, 20 Jun 2019 22:52:43 +0300 Subject: [PATCH 0712/1046] Fixed build with module headers --- SQLite.swift.podspec | 28 +++++++++++++++++++++------- Sources/SQLite/SQLite.h | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 524e721c..cae73653 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -15,14 +15,19 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/stephencelis' s.module_name = 'SQLite' - s.ios.deployment_target = "8.0" - s.tvos.deployment_target = "9.1" - s.osx.deployment_target = "10.10" - s.watchos.deployment_target = "2.2" s.default_subspec = 'standard' - s.pod_target_xcconfig = { - 'SWIFT_VERSION' => '5', - } + s.swift_versions = ['4.2', '5'] + + + ios_deployment_target = '8.0' + tvos_deployment_target = '9.1' + osx_deployment_target = '10.10' + watchos_deployment_target = '2.2' + + s.ios.deployment_target = ios_deployment_target + s.tvos.deployment_target = tvos_deployment_target + s.osx.deployment_target = osx_deployment_target + s.watchos.deployment_target = watchos_deployment_target s.subspec 'standard' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' @@ -33,6 +38,9 @@ Pod::Spec.new do |s| ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' + test_spec.ios.deployment_target = ios_deployment_target + test_spec.tvos.deployment_target = tvos_deployment_target + test_spec.osx.deployment_target = osx_deployment_target end end @@ -49,6 +57,9 @@ Pod::Spec.new do |s| ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' + test_spec.ios.deployment_target = ios_deployment_target + test_spec.tvos.deployment_target = tvos_deployment_target + test_spec.osx.deployment_target = osx_deployment_target end end @@ -64,6 +75,9 @@ Pod::Spec.new do |s| ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' + test_spec.ios.deployment_target = ios_deployment_target + test_spec.tvos.deployment_target = tvos_deployment_target + test_spec.osx.deployment_target = osx_deployment_target end end end diff --git a/Sources/SQLite/SQLite.h b/Sources/SQLite/SQLite.h index 69382ea0..fe1e3dfe 100644 --- a/Sources/SQLite/SQLite.h +++ b/Sources/SQLite/SQLite.h @@ -3,4 +3,4 @@ FOUNDATION_EXPORT double SQLiteVersionNumber; FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; -#import +#import "SQLiteObjc.h" From d0d802e3f99b78112cfb0b71894eeec021a7436c Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Fri, 21 Jun 2019 13:18:14 +0300 Subject: [PATCH 0713/1046] version bump --- SQLite.swift.podspec | 2 +- Sources/SQLite/Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index cae73653..2341cfc4 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.12.1" + s.version = "0.12.2" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index f7f7ce7f..2d956da2 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.1 + 0.12.2 CFBundleSignature ???? CFBundleVersion From 4de5dc458788bc09111d26f97c4ea46c8f08d946 Mon Sep 17 00:00:00 2001 From: Guillaume Algis Date: Wed, 14 Aug 2019 12:44:15 +0200 Subject: [PATCH 0714/1046] Add @discardableResult to all FTSConfig methods returning Self for chaining --- Sources/SQLite/Extensions/FTS4.swift | 18 +++++++++--------- Sources/SQLite/Extensions/FTS5.swift | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 5ef84dd7..f0184565 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -194,25 +194,25 @@ open class FTSConfig { } /// [Tokenizers](https://www.sqlite.org/fts3.html#tokenizer) - open func tokenizer(_ tokenizer: Tokenizer?) -> Self { + @discardableResult open func tokenizer(_ tokenizer: Tokenizer?) -> Self { self.tokenizer = tokenizer return self } /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) - open func prefix(_ prefix: [Int]) -> Self { + @discardableResult open func prefix(_ prefix: [Int]) -> Self { self.prefixes += prefix return self } /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) - open func externalContent(_ schema: SchemaType) -> Self { + @discardableResult open func externalContent(_ schema: SchemaType) -> Self { self.externalContentSchema = schema return self } /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) - open func contentless() -> Self { + @discardableResult open func contentless() -> Self { self.isContentless = true return self } @@ -308,31 +308,31 @@ open class FTS4Config : FTSConfig { } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) - open func compress(_ functionName: String) -> Self { + @discardableResult open func compress(_ functionName: String) -> Self { self.compressFunction = functionName return self } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) - open func uncompress(_ functionName: String) -> Self { + @discardableResult open func uncompress(_ functionName: String) -> Self { self.uncompressFunction = functionName return self } /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) - open func languageId(_ columnName: String) -> Self { + @discardableResult open func languageId(_ columnName: String) -> Self { self.languageId = columnName return self } /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - open func matchInfo(_ matchInfo: MatchInfo) -> Self { + @discardableResult open func matchInfo(_ matchInfo: MatchInfo) -> Self { self.matchInfo = matchInfo return self } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - open func order(_ order: Order) -> Self { + @discardableResult open func order(_ order: Order) -> Self { self.order = order return self } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index 763927ff..cf13f3d8 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -58,19 +58,19 @@ open class FTS5Config : FTSConfig { } /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) - open func contentRowId(_ column: Expressible) -> Self { + @discardableResult open func contentRowId(_ column: Expressible) -> Self { self.contentRowId = column return self } /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) - open func columnSize(_ size: Int) -> Self { + @discardableResult open func columnSize(_ size: Int) -> Self { self.columnSize = size return self } /// [The Detail Option](https://www.sqlite.org/fts5.html#section_4_6) - open func detail(_ detail: Detail) -> Self { + @discardableResult open func detail(_ detail: Detail) -> Self { self.detail = detail return self } From 115ad41835b9de8c2f69fa510c222ce85e526a71 Mon Sep 17 00:00:00 2001 From: Johannes Ebeling Date: Sat, 17 Aug 2019 00:15:34 +0200 Subject: [PATCH 0715/1046] Enable the encodable api to pass the onConflict parameter to the QueryType.insert function --- Sources/SQLite/Typed/Coding.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..d4f99e6e 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -44,6 +44,30 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } + + /// Creates an `INSERT` statement by encoding the given object + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// The onConflict will be passed to the actual insert function to define what should happen + /// when an error occurs during the insert operation. + /// + /// - Parameters: + /// + /// - onConlict: Define what happens when an insert operation fails + /// + /// - encodable: An encodable object to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the insert + /// + /// - Returns: An `INSERT` statement fort the encodable object + public func insert(or onConflict: OnConflict, encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return self.insert(or: onConflict, encoder.setters + otherSetters) + } /// Creates an `UPDATE` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort From d8193fb00255186802fe5182cd84fa4de041f917 Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 5 Nov 2019 08:40:20 +0100 Subject: [PATCH 0716/1046] closing brackets/aposthrophes few things wasnt closed properly --- Documentation/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 70d67c2d..c6c54437 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1792,12 +1792,12 @@ let config = FTS5Config() .column(subject) .column(body, [.unindexed]) -try db.run(emails.create(.FTS5(config)) +try db.run(emails.create(.FTS5(config))) // CREATE VIRTUAL TABLE "emails" USING fts5("subject", "body" UNINDEXED) // Note that FTS5 uses a different syntax to select columns, so we need to rewrite // the last FTS4 query above as: -let replies = emails.filter(emails.match("subject:\"Re:\"*)) +let replies = emails.filter(emails.match("subject:\"Re:\"*")) // SELECT * FROM "emails" WHERE "emails" MATCH 'subject:"Re:"*' // https://www.sqlite.org/fts5.html#_changes_to_select_statements_ From ef3f88f7d02116c95088d7c5dae9a0af42f4dbfc Mon Sep 17 00:00:00 2001 From: Bradley Walters Date: Fri, 15 Nov 2019 09:52:51 -0700 Subject: [PATCH 0717/1046] Fix building with standalone sqlite3 >= 3.30.0 SQLite 3.30.0 changed the definition of SQLITE_DETERMINISTIC: `-#define SQLITE_DETERMINISTIC 0x800` `+#define SQLITE_DETERMINISTIC 0x000000800` Meaning that the (older) system sqlite3 library and the pod have different definitions, even though they're the same value. We've been importing the system sqlite3 module in SQLiteObjc.h even when linking against standalone sqlite. I added a check to SQLiteObjc.h to import the sqlite3 pod when we're using it, instead of always importing the system module. This leads to there being only one definition in scope. --- SQLite.swift.podspec | 3 ++- Sources/SQLiteObjc/include/SQLiteObjc.h | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 2341cfc4..712ddc36 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -50,7 +50,8 @@ Pod::Spec.new do |s| ss.private_header_files = 'Sources/SQLiteObjc/*.h' ss.xcconfig = { - 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE' + 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_SWIFT_STANDALONE=1' } ss.dependency 'sqlite3' diff --git a/Sources/SQLiteObjc/include/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h index f8c2a3b3..e8ba9a7d 100644 --- a/Sources/SQLiteObjc/include/SQLiteObjc.h +++ b/Sources/SQLiteObjc/include/SQLiteObjc.h @@ -23,7 +23,11 @@ // @import Foundation; +#if defined(SQLITE_SWIFT_STANDALONE) +@import sqlite3; +#else @import SQLite3; +#endif NS_ASSUME_NONNULL_BEGIN typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); From 23f4aa42553119aab2e0118afc6b080e70a8d101 Mon Sep 17 00:00:00 2001 From: Svetlana Korosteleva Date: Mon, 2 Dec 2019 15:14:59 -0800 Subject: [PATCH 0718/1046] Add interface to perform migration to new major SQLCipher immediately after setting a key This change adds `keyAndMigrate` methods which perform "PRAGMA cipher_migrate;" call immediately after calling `sqlite3_key_v2` This call is needed to open the database files created in older major SQLCipher version (e.g. 3.x.x) by newer SQLCipher version (e.g. 4.x.x). These calls should be performed only once, when opening older database files for the first time. See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_migrate and https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283 for more details regarding SQLCipher upgrade --- Sources/SQLite/Extensions/Cipher.swift | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 44919aab..4d8d426b 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -32,6 +32,25 @@ extension Connection { try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } + /// Same as `key(_ key: String, db: String = "main")`, running "PRAGMA cipher_migrate;" + /// immediately after calling `sqlite3_key_v2`, which performs the migration of + /// SQLCipher database created by older major version of SQLCipher, to be able to + /// open this database with new major version of SQLCipher + /// (e.g. to open database created by SQLCipher version 3.x.x with SQLCipher version 4.x.x). + /// As "PRAGMA cipher_migrate;" is time-consuming, it is recommended to use this function + /// only after failure of `key(_ key: String, db: String = "main")`, if older versions of + /// your app may ise older version of SQLCipher + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_migrate + /// and https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283 + /// for more details regarding SQLCipher upgrade + public func keyAndMigrate(_ key: String, db: String = "main") throws { + try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count, migrate: true) + } + + /// Same as `[`keyAndMigrate(_ key: String, db: String = "main")` accepting byte array as key + public func keyAndMigrate(_ key: Blob, db: String = "main") throws { + try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true) + } /// Change the key on an open database. If the current database is not encrypted, this routine /// will encrypt it. @@ -47,8 +66,20 @@ extension Connection { } // MARK: - private - private func _key_v2(db: String, keyPointer: UnsafePointer, keySize: Int) throws { + private func _key_v2(db: String, + keyPointer: UnsafePointer, + keySize: Int, + migrate: Bool = false) throws { try check(sqlite3_key_v2(handle, db, keyPointer, Int32(keySize))) + if migrate { + // Run "PRAGMA cipher_migrate;" immediately after `sqlite3_key_v2` + // per recommendation of SQLCipher authors + let migrateResult = try scalar("PRAGMA cipher_migrate;") + if (migrateResult as? String) != "0" { + // "0" is the result of successfull migration + throw Result.error(message: "Error in cipher migration, result \(migrateResult.debugDescription)", code: 1, statement: nil) + } + } try cipher_key_check() } From 3b57e6963303adbca4cbc435e8ef1c43dee02726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 22 Jan 2020 09:13:54 +0100 Subject: [PATCH 0719/1046] Add support for storing and retrieving UUID objects Storing them as string for better readability in the database --- Sources/SQLite/Foundation.swift | 16 ++++++++++++++++ Tests/SQLiteTests/FoundationTests.swift | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index cfb79bec..9986f581 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -68,3 +68,19 @@ public var dateFormatter: DateFormatter = { formatter.timeZone = TimeZone(secondsFromGMT: 0) return formatter }() + +extension UUID : Value { + + public static var declaredDatatype: String { + return String.declaredDatatype + } + + public static func fromDatatypeValue(_ stringValue: String) -> UUID { + return UUID(uuidString: stringValue)! + } + + public var datatypeValue: String { + return self.uuidString + } + +} diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index dd80afc1..ba9685b7 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -13,4 +13,16 @@ class FoundationTests : XCTestCase { let data = Data.fromDatatypeValue(blob) XCTAssertEqual(Data([1, 2, 3]), data) } + + func testStringFromUUID() { + let uuid = UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3")! + let string = uuid.datatypeValue + XCTAssertEqual("4ABE10C9-FF12-4CD4-90C1-4B429001BAD3", string) + } + + func testUUIDFromString() { + let string = "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3" + let uuid = UUID.fromDatatypeValue(string) + XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) + } } From 806f03c91f473d5003372a19083e27ab5e8897a6 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 15 Apr 2020 11:29:23 +0200 Subject: [PATCH 0720/1046] Improving README.md Because of #988 and #983, I added a do...catch in the usage section, and a production implementation example in the related section. --- README.md | 119 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index b7a18e0b..8b0972f6 100644 --- a/README.md +++ b/README.md @@ -34,67 +34,79 @@ syntax _and_ intent. ```swift import SQLite -let db = try Connection("path/to/db.sqlite3") - -let users = Table("users") -let id = Expression("id") -let name = Expression("name") -let email = Expression("email") - -try db.run(users.create { t in - t.column(id, primaryKey: true) - t.column(name) - t.column(email, unique: true) -}) -// CREATE TABLE "users" ( -// "id" INTEGER PRIMARY KEY NOT NULL, -// "name" TEXT, -// "email" TEXT NOT NULL UNIQUE -// ) - -let insert = users.insert(name <- "Alice", email <- "alice@mac.com") -let rowid = try db.run(insert) -// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') - -for user in try db.prepare(users) { - print("id: \(user[id]), name: \(user[name]), email: \(user[email])") - // id: 1, name: Optional("Alice"), email: alice@mac.com +// Wrap everything in a do...catch to handle errors +do { + let db = try Connection("path/to/db.sqlite3") + + let users = Table("users") + let id = Expression("id") + let name = Expression("name") + let email = Expression("email") + + try db.run(users.create { t in + t.column(id, primaryKey: true) + t.column(name) + t.column(email, unique: true) + }) + // CREATE TABLE "users" ( + // "id" INTEGER PRIMARY KEY NOT NULL, + // "name" TEXT, + // "email" TEXT NOT NULL UNIQUE + // ) + + let insert = users.insert(name <- "Alice", email <- "alice@mac.com") + let rowid = try db.run(insert) + // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') + + for user in try db.prepare(users) { + print("id: \(user[id]), name: \(user[name]), email: \(user[email])") + // id: 1, name: Optional("Alice"), email: alice@mac.com + } + // SELECT * FROM "users" + + let alice = users.filter(id == rowid) + + try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) + // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') + // WHERE ("id" = 1) + + try db.run(alice.delete()) + // DELETE FROM "users" WHERE ("id" = 1) + + try db.scalar(users.count) // 0 + // SELECT count(*) FROM "users" +} catch { + print (error) } -// SELECT * FROM "users" - -let alice = users.filter(id == rowid) - -try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) -// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') -// WHERE ("id" = 1) - -try db.run(alice.delete()) -// DELETE FROM "users" WHERE ("id" = 1) - -try db.scalar(users.count) // 0 -// SELECT count(*) FROM "users" ``` SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. ```swift -let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") -for email in ["betty@icloud.com", "cathy@icloud.com"] { - try stmt.run(email) +// Wrap everything in a do...catch to handle errors +do { + // ... + + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") + for email in ["betty@icloud.com", "cathy@icloud.com"] { + try stmt.run(email) + } + + db.totalChanges // 3 + db.changes // 1 + db.lastInsertRowid // 3 + + for row in try db.prepare("SELECT id, email FROM users") { + print("id: \(row[0]), email: \(row[1])") + // id: Optional(2), email: Optional("betty@icloud.com") + // id: Optional(3), email: Optional("cathy@icloud.com") + } + + try db.scalar("SELECT count(*) FROM users") // 2 +} catch { + print (error) } - -db.totalChanges // 3 -db.changes // 1 -db.lastInsertRowid // 3 - -for row in try db.prepare("SELECT id, email FROM users") { - print("id: \(row[0]), email: \(row[1])") - // id: Optional(2), email: Optional("betty@icloud.com") - // id: Optional(3), email: Optional("cathy@icloud.com") -} - -try db.scalar("SELECT count(*) FROM users") // 2 ``` [Read the documentation][See Documentation] or explore more, @@ -253,6 +265,7 @@ These projects enhance or use SQLite.swift: - [SQLiteMigrationManager.swift][] (inspired by [FMDBMigrationManager][]) + - [Delta: Math helper](https://apps.apple.com/app/delta-math-helper/id1436506800) (see [Delta/Utils/Database.swift](https://github.com/GroupeMINASTE/Delta-iOS/blob/master/Delta/Utils/Database.swift) for production implementation example) ## Alternatives From f040e145d328042a4a0f042086ac09b28f4b1b41 Mon Sep 17 00:00:00 2001 From: Daniel Shelley Date: Fri, 17 Jul 2020 10:59:47 -0600 Subject: [PATCH 0721/1046] set deployment target to iOS 9 to fix Archiving with Xcode 12 --- SQLite.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30a40214..80719c33 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1173,7 +1173,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1229,7 +1229,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; @@ -1255,7 +1255,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1276,7 +1276,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; From 135b2fc8b854f8b528956a56d8af8a89f6708d17 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Thu, 13 Aug 2020 07:09:38 -0400 Subject: [PATCH 0722/1046] =?UTF-8?q?Added=20=3D=3D=3D=20as=20explicit=20?= =?UTF-8?q?=E2=80=9CIS=E2=80=9D=20operator=20for=20expressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added === as explicit “IS” operator for expressions. Added tests for === operator. --- Sources/SQLite/Typed/Operators.swift | 27 ++++++++++++++++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 14 +++++++++++++ 2 files changed, 41 insertions(+) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index b5637ea3..0a323b90 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -378,6 +378,33 @@ public func ==(lhs: V?, rhs: Expression) -> Expression whe return Operator.eq.infix(lhs, rhs) } +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { + guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + return "IS".infix(lhs, rhs) +} +public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS".infix(lhs, rhs) +} +public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { + guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + return "IS".infix(lhs, rhs) +} + public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { return Operator.neq.infix(lhs, rhs) } diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 948eb0a4..18a1f52e 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -187,6 +187,20 @@ class OperatorsTests : XCTestCase { AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) } + func test_isOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" IS \"bool\")", bool === bool) + AssertSQL("(\"bool\" IS \"boolOptional\")", bool === boolOptional) + AssertSQL("(\"boolOptional\" IS \"bool\")", boolOptional === bool) + AssertSQL("(\"boolOptional\" IS \"boolOptional\")", boolOptional === boolOptional) + AssertSQL("(\"bool\" IS 1)", bool === true) + AssertSQL("(\"boolOptional\" IS 1)", boolOptional === true) + AssertSQL("(1 IS \"bool\")", true === bool) + AssertSQL("(1 IS \"boolOptional\")", true === boolOptional) + + AssertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) + AssertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) + } + func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { AssertSQL("(\"bool\" != \"bool\")", bool != bool) AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) From 113872d8d01b2b0c927d9b450abfb5fe6a8f312a Mon Sep 17 00:00:00 2001 From: Jake-B Date: Thu, 13 Aug 2020 08:17:00 -0400 Subject: [PATCH 0723/1046] =?UTF-8?q?Add=20!=3D=3D=20as=20explicit=20?= =?UTF-8?q?=E2=80=9CIS=20NOT=E2=80=9D=20operator=20for=20exprsns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added !== as explicit “IS” operator for expressions. Added tests for !== operator. --- Sources/SQLite/Typed/Operators.swift | 28 ++++++++++++++++++++++++++ Tests/SQLiteTests/OperatorsTests.swift | 14 +++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 0a323b90..594f08a0 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -432,6 +432,34 @@ public func !=(lhs: V?, rhs: Expression) -> Expression whe return Operator.neq.infix(lhs, rhs) } +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { + guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { + return "IS NOT".infix(lhs, rhs) +} +public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { + guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + return "IS NOT".infix(lhs, rhs) +} + + public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { return Operator.gt.infix(lhs, rhs) } diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index 18a1f52e..c2416844 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -200,6 +200,20 @@ class OperatorsTests : XCTestCase { AssertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) AssertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) } + + func test_isNotOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" IS NOT \"bool\")", bool !== bool) + AssertSQL("(\"bool\" IS NOT \"boolOptional\")", bool !== boolOptional) + AssertSQL("(\"boolOptional\" IS NOT \"bool\")", boolOptional !== bool) + AssertSQL("(\"boolOptional\" IS NOT \"boolOptional\")", boolOptional !== boolOptional) + AssertSQL("(\"bool\" IS NOT 1)", bool !== true) + AssertSQL("(\"boolOptional\" IS NOT 1)", boolOptional !== true) + AssertSQL("(1 IS NOT \"bool\")", true !== bool) + AssertSQL("(1 IS NOT \"boolOptional\")", true !== boolOptional) + + AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional !== nil) + AssertSQL("(NULL IS NOT \"boolOptional\")", nil !== boolOptional) + } func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { AssertSQL("(\"bool\" != \"bool\")", bool != bool) From e3e031df66fe6f8a35f50a6d2f3cfcd1a3439656 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 5 Nov 2020 16:37:47 -0300 Subject: [PATCH 0724/1046] Removed the force unwrap in the FailableIterator extension for the next () method Although the method accepts an optional return, the function forces unwrap --- SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Info.plist | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30a40214..9972b3f7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1257,6 +1257,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.12.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1278,6 +1279,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.12.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index dc91d3d8..237d4812 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -208,7 +208,7 @@ public protocol FailableIterator : IteratorProtocol { extension FailableIterator { public func next() -> Element? { - return try! failableNext() + return try? failableNext() } } diff --git a/Sources/SQLite/Info.plist b/Sources/SQLite/Info.plist index 2d956da2..ca23c84f 100644 --- a/Sources/SQLite/Info.plist +++ b/Sources/SQLite/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.12.2 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion From 12ac2fd4928e690957e2d3cad57884d450a8cea7 Mon Sep 17 00:00:00 2001 From: turtlemaster19 <46784000+UInt2048@users.noreply.github.com> Date: Thu, 3 Dec 2020 14:29:36 -0500 Subject: [PATCH 0725/1046] Fix #920 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7a18e0b..a12fe539 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ and the [companion repository][SQLiteDataAccessLayer2]. ## Installation -> _Note:_ Version 0.12 requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.6 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. +> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. ### Carthage From 140374a1f25df9a50c9e014679844bb2de70542d Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 11:09:45 -0700 Subject: [PATCH 0726/1046] implement batch insert, insertMany() --- Sources/SQLite/Typed/Coding.swift | 25 +++++++++++++++++++- Sources/SQLite/Typed/Query.swift | 37 ++++++++++++++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 37 ++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index c3fb931b..34edbc49 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -38,13 +38,36 @@ extension QueryType { /// /// - otherSetters: Any other setters to include in the insert /// - /// - Returns: An `INSERT` statement fort the encodable object + /// - Returns: An `INSERT` statement for the encodable object public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } + /// Creates a batch `INSERT` statement by encoding the array of given objects + /// This method converts any custom nested types to JSON data and does not handle any sort + /// of object relationships. If you want to support relationships between objects you will + /// have to provide your own Encodable implementations that encode the correct ids. + /// + /// - Parameters: + /// + /// - encodables: Encodable objects to insert + /// + /// - userInfo: User info to be passed to encoder + /// + /// - otherSetters: Any other setters to include in the inserts, per row/object. + /// + /// - Returns: An `INSERT` statement for the encodable objects + public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + let combinedSetters = try encodables.map { encodable -> [Setter] in + let encoder = SQLiteEncoder(userInfo: userInfo) + try encodable.encode(to: encoder) + return encoder.setters + otherSetters + } + return self.insertMany(combinedSetters) + } + /// Creates an `UPDATE` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f6ef6df8..61deaa1f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -631,6 +631,18 @@ extension QueryType { return insert(onConflict, values) } + public func insertMany( _ values: [[Setter]]) -> Insert { + return insertMany(nil, values) + } + + public func insertMany(or onConflict: OnConflict, _ values: [[Setter]]) -> Insert { + return insertMany(onConflict, values) + } + + public func insertMany(or onConflict: OnConflict, _ values: [Setter]...) -> Insert { + return insertMany(onConflict, values) + } + fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert { let insert = values.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in (insert.columns + [setter.column], insert.values + [setter.value]) @@ -650,6 +662,29 @@ extension QueryType { return Insert(" ".join(clauses.compactMap { $0 }).expression) } + fileprivate func insertMany(_ or: OnConflict?, _ values: [[Setter]]) -> Insert { + guard values.count > 0 else { + return insert() + } + let insertRows = values.map { rowValues in + rowValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in + (insert.columns + [setter.column], insert.values + [setter.value]) + } + } + + let clauses: [Expressible?] = [ + Expression(literal: "INSERT"), + or.map { Expression(literal: "OR \($0.rawValue)") }, + Expression(literal: "INTO"), + tableName(), + "".wrap(insertRows[0].columns) as Expression, + Expression(literal: "VALUES"), + ", ".join(insertRows.map(\.values).map({ "".wrap($0) as Expression })), + whereClause + ] + return Insert(" ".join(clauses.compactMap { $0 }).expression) + } + /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. public func insert() -> Insert { return Insert(" ".join([ @@ -1010,6 +1045,8 @@ extension Connection { /// - SeeAlso: `QueryType.insert(value:_:)` /// - SeeAlso: `QueryType.insert(values:)` /// - SeeAlso: `QueryType.insert(or:_:)` + /// - SeeAlso: `QueryType.insertMany(values:)` + /// - SeeAlso: `QueryType.insertMany(or:_:)` /// - SeeAlso: `QueryType.insert()` /// /// - Parameter query: An insert query. diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2a9e4ecb..f48f49b9 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -247,6 +247,26 @@ class QueryTests : XCTestCase { ) } + func test_insert_many_compilesInsertManyExpression() { + AssertSQL( + "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", + users.insertMany([[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + ) + } + func test_insert_many_compilesInsertManyNoneExpression() { + AssertSQL( + "INSERT INTO \"users\" DEFAULT VALUES", + users.insertMany([]) + ) + } + + func test_insert_many_withOnConflict_compilesInsertManyOrOnConflictExpression() { + AssertSQL( + "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", + users.insertMany(or: .replace, [[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + ) + } + func test_insert_encodable() throws { let emails = Table("emails") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) @@ -270,6 +290,18 @@ class QueryTests : XCTestCase { ) } + func test_insert_many_encodable() throws { + let emails = Table("emails") + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, optional: nil, sub: nil) + let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, optional: nil, sub: nil) + let insert = try emails.insertMany([value1, value2, value3]) + AssertSQL( + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0), (2, '3', 1, 3.0, 5.0), (3, '4', 1, 3.0, 6.0)", + insert + ) + } + func test_update_compilesUpdateExpression() { AssertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", @@ -483,6 +515,11 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(1, id) } + func test_insert_many() { + let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) + XCTAssertEqual(2, id) + } + func test_update() { let changes = try! db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) From 087264792a7c75c446106ef4090d9fd669b37e8a Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 11:27:42 -0700 Subject: [PATCH 0727/1046] Update Index.md --- Documentation/Index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 70d67c2d..2f441c8c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -638,6 +638,18 @@ do { } ``` +Multiple rows can be inserted at once by similarily calling `insertMany` with an array of per-row [setters](#setters). + +```swift +do { + let rowid = try db.run(users.insertMany([mail <- "alice@mac.com"], [email <- "geoff@mac.com"])) + print("inserted id: \(rowid)") +} catch { + print("insertion failed: \(error)") +} +``` + + The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow similar patterns. From 05c404fcec8df8043d68d38617a9c69f54772f50 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 11:37:22 -0700 Subject: [PATCH 0728/1046] cleanup sql creation --- Sources/SQLite/Typed/Query.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 61deaa1f..54e44fa5 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -663,12 +663,14 @@ extension QueryType { } fileprivate func insertMany(_ or: OnConflict?, _ values: [[Setter]]) -> Insert { - guard values.count > 0 else { + guard let firstInsert = values.first else { + // must be at least 1 object or else we don't know columns. Default to default inserts. return insert() } - let insertRows = values.map { rowValues in - rowValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in - (insert.columns + [setter.column], insert.values + [setter.value]) + let columns = firstInsert.map { $0.column } + let insertValues = values.map { rowValues in + rowValues.reduce([Expressible]()) { insert, setter in + insert + [setter.value] } } @@ -677,9 +679,9 @@ extension QueryType { or.map { Expression(literal: "OR \($0.rawValue)") }, Expression(literal: "INTO"), tableName(), - "".wrap(insertRows[0].columns) as Expression, + "".wrap(columns) as Expression, Expression(literal: "VALUES"), - ", ".join(insertRows.map(\.values).map({ "".wrap($0) as Expression })), + ", ".join(insertValues.map({ "".wrap($0) as Expression })), whereClause ] return Insert(" ".join(clauses.compactMap { $0 }).expression) From 4fde8dba065b213ae0a5a067a1d8d31680091b5a Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 14:52:52 -0700 Subject: [PATCH 0729/1046] try and fix travis? --- .travis.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8fc5feb..ea60b063 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -rvm: 2.3 +rvm: 2.7.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.2 +osx_image: xcode12.2 env: global: - - IOS_SIMULATOR="iPhone XS" - - IOS_VERSION="12.2" + - IOS_SIMULATOR="iPhone 11" + - IOS_VERSION="14.2" matrix: include: - env: BUILD_SCHEME="SQLite iOS" @@ -25,7 +25,4 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: -# Workaround for Xcode 10.2/tvOS 9.1 bug -# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 - - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh From 183a43948f40637643c15e6e19a4b983d2df8325 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 15:32:43 -0700 Subject: [PATCH 0730/1046] Update Planning.md --- Documentation/Planning.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 5f885de8..62df1f24 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -33,6 +33,3 @@ be referred to when it comes time to add the corresponding feature._ _Features that are not actively being considered, perhaps because of no clean type-safe way to implement them with the current Swift, or bugs, or just general uncertainty._ - - * provide a mechanism for INSERT INTO multiple values, per - [#168](https://github.com/stephencelis/SQLite.swift/issues/168) From b260b0a4aa1754b609ac48c637578ef1cea0898c Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 15:35:12 -0700 Subject: [PATCH 0731/1046] upgrade test deps to force it working --- .travis.yml | 2 +- Tests/CocoaPods/Gemfile.lock | 46 ++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index ea60b063..2a0460bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ matrix: - env: CARTHAGE_PLATFORM="tvOS" - env: PACKAGE_MANAGER_COMMAND="test" before_install: - - gem update bundler + - gem install bundler - gem install xcpretty --no-document - brew update - brew outdated carthage || brew upgrade carthage diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index b5172144..e5c53ea3 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,18 +1,18 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.11) + CFPropertyList (3.0.3) + activesupport (4.2.11.3) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) atomos (0.1.3) - claide (1.0.2) - cocoapods (1.6.0.beta.2) + claide (1.0.3) + cocoapods (1.6.2) activesupport (>= 4.0.2, < 5) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) + cocoapods-core (= 1.6.2) cocoapods-deintegrate (>= 1.0.2, < 2.0) cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -22,56 +22,56 @@ GEM cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.2.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) + ruby-macho (~> 1.4) + xcodeproj (>= 1.8.1, < 2.0) + cocoapods-core (1.6.2) activesupport (>= 4.0.2, < 6) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.8) escape (0.0.4) - fourflusher (2.0.1) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) i18n (0.9.5) concurrent-ruby (~> 1.0) minitest (5.11.3) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.3.1) + ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) + tzinfo (1.2.9) thread_safe (~> 0.1) - xcodeproj (1.7.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0beta2) + cocoapods (~> 1.6.1) minitest BUNDLED WITH - 1.17.1 + 1.17.2 From e40e3369c14319df540bbac476c419cfea20294e Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 17:43:16 -0700 Subject: [PATCH 0732/1046] update deps --- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 51 +++++++++++++++++++---------- Tests/CocoaPods/integration_test.rb | 2 +- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 77d90eec..19db2b36 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.1' +gem 'cocoapods', '~> 1.10.1' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index e5c53ea3..d99ac49a 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -2,42 +2,51 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.3) - activesupport (4.2.11.3) - i18n (~> 0.7) + activesupport (5.2.5) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) claide (1.0.3) - cocoapods (1.6.2) - activesupport (>= 4.0.2, < 5) + cocoapods (1.10.1) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.2) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-core (= 1.10.1) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (>= 2.2.0, < 3.0) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) ruby-macho (~> 1.4) - xcodeproj (>= 1.8.1, < 2.0) - cocoapods-core (1.6.2) - activesupport (>= 4.0.2, < 6) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.1) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) cocoapods-deintegrate (1.0.4) cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.1.0) cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) @@ -45,18 +54,26 @@ GEM colored2 (3.1.2) concurrent-ruby (1.1.8) escape (0.0.4) + ethon (0.14.0) + ffi (>= 1.15.0) + ffi (1.15.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.10) concurrent-ruby (~> 1.0) + json (2.5.1) minitest (5.11.3) molinillo (0.6.6) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + public_suffix (4.0.6) ruby-macho (1.4.0) thread_safe (0.3.6) + typhoeus (1.4.0) + ethon (>= 0.9.0) tzinfo (1.2.9) thread_safe (~> 0.1) xcodeproj (1.19.0) @@ -70,7 +87,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.1) + cocoapods (~> 1.10.1) minitest BUNDLED WITH diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 98a539b5..2ce2997c 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -40,7 +40,7 @@ def test_pod super unless consumer.platform_name == :watchos end - def xcodebuild(action, scheme, configuration) + def xcodebuild(action, scheme, configuration, _) require 'fourflusher' command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) case consumer.platform_name From 0742ee04976029504502b6212b60462017519887 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 17:46:47 -0700 Subject: [PATCH 0733/1046] apparently carthage doesnt support xcode 12 out of box --- .travis.yml | 8 ++++---- Makefile | 2 +- Tests/Carthage/Makefile | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a0460bb..b8b2eaba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c rvm: 2.7.3 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode12.2 +osx_image: xcode11.6 env: global: - - IOS_SIMULATOR="iPhone 11" - - IOS_VERSION="14.2" + - IOS_SIMULATOR="iPhone XS" + - IOS_VERSION="13.6" matrix: include: - env: BUILD_SCHEME="SQLite iOS" @@ -20,7 +20,7 @@ matrix: - env: CARTHAGE_PLATFORM="tvOS" - env: PACKAGE_MANAGER_COMMAND="test" before_install: - - gem install bundler + - gem install bundler - gem install xcpretty --no-document - brew update - brew outdated carthage || brew upgrade carthage diff --git a/Makefile b/Makefile index 50d07148..b38b90e0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone XS -IOS_VERSION = 12.2 +IOS_VERSION = 13.6 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index f28eb25b..7f068985 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -3,7 +3,7 @@ CARTHAGE_PLATFORM := iOS CARTHAGE_CONFIGURATION := Release CARTHAGE_DIR := Carthage CARTHAGE_ARGS := --no-use-binaries -CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.Swift_3_0 +CARTHAGE_TOOLCHAIN := com.apple.dt.toolchain.XcodeDefault CARTHAGE_CMDLINE := --configuration $(CARTHAGE_CONFIGURATION) --platform $(CARTHAGE_PLATFORM) --toolchain $(CARTHAGE_TOOLCHAIN) $(CARTHAGE_ARGS) test: $(CARTHAGE) Cartfile From a78d8c4ae303c7d4875a29a01bc307e48095f7d6 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 18:04:29 -0700 Subject: [PATCH 0734/1046] try 2.6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8b2eaba..e9d5ec2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -rvm: 2.7.3 +rvm: 2.6 # https://docs.travis-ci.com/user/reference/osx osx_image: xcode11.6 env: From 5052cbbd6d881396c87a9ede9de2e30a8f86a706 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 30 Apr 2021 18:13:28 -0700 Subject: [PATCH 0735/1046] use iphone 11 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9d5ec2a..7bff9ce2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ rvm: 2.6 osx_image: xcode11.6 env: global: - - IOS_SIMULATOR="iPhone XS" + - IOS_SIMULATOR="iPhone 11" - IOS_VERSION="13.6" matrix: include: From f961db5344375ea667f3f5d44c8df202a5cfc5aa Mon Sep 17 00:00:00 2001 From: Bennett Smith Date: Mon, 17 May 2021 18:23:32 -0700 Subject: [PATCH 0736/1046] Updates for Xcode 12.5 * Updates ruby version used by Travis-CI. * Updates Xcode image to 12.5 for Travis-CI. * Removes workaround in run-tests.sh for older simulators. * Updates Carthage toolchain. * Updates Package.swift tool version. * Fixes up SPM build for SQLiteObjc module. * Updated Xcode build settings. --- .travis.yml | 10 ++--- CHANGELOG.md | 8 ++++ Makefile | 2 +- Package.swift | 45 ++++++++++++++++--- SQLite.xcodeproj/project.pbxproj | 30 +++++-------- .../xcschemes/SQLite Mac.xcscheme | 24 +++++----- .../xcschemes/SQLite iOS.xcscheme | 24 +++++----- .../xcschemes/SQLite tvOS.xcscheme | 24 +++++----- .../xcschemes/SQLite watchOS.xcscheme | 6 +-- Sources/SQLiteObjc/SQLiteObjc.h | 36 +++++++++++++++ Sources/SQLiteObjc/include/README.md | 7 +++ Tests/Carthage/Makefile | 5 ++- 12 files changed, 141 insertions(+), 80 deletions(-) create mode 100644 Sources/SQLiteObjc/SQLiteObjc.h create mode 100644 Sources/SQLiteObjc/include/README.md diff --git a/.travis.yml b/.travis.yml index c8fc5feb..98987db1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,11 @@ language: objective-c -rvm: 2.3 +rvm: 2.6 # https://docs.travis-ci.com/user/reference/osx -osx_image: xcode10.2 +osx_image: xcode12.5 env: global: - IOS_SIMULATOR="iPhone XS" - - IOS_VERSION="12.2" + - IOS_VERSION="12.4" matrix: include: - env: BUILD_SCHEME="SQLite iOS" @@ -25,7 +25,7 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: -# Workaround for Xcode 10.2/tvOS 9.1 bug +# Workaround for Xcode 10.2/tvOS 9.1 bug (This is an outdated workaround; commenting out.) # See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 - - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift +# - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 027611ee..1f29afeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +0.12.3 (18-05-2021), [diff][diff-0.12.2] +======================================== + +* Swift 5.3 support. +* Xcode 12.5 support. +* Bumps minimum deployment versions. +* Fixes up Package.swift to build SQLiteObjc module. + 0.11.6 (xxx), [diff][diff-0.11.6] ======================================== diff --git a/Makefile b/Makefile index 50d07148..be426dc1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone XS -IOS_VERSION = 12.2 +IOS_VERSION = 12.4 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else diff --git a/Package.swift b/Package.swift index 430ae6f2..73339b7b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,15 +1,46 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.3 import PackageDescription let package = Package( name: "SQLite.swift", - products: [.library(name: "SQLite", targets: ["SQLite"])], + products: [ + .library( + name: "SQLite", + targets: ["SQLite"] + ) + ], targets: [ - .target(name: "SQLite", dependencies: ["SQLiteObjc"]), - .target(name: "SQLiteObjc"), - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests") - ], - swiftLanguageVersions: [4, 5] + .target( + name: "SQLite", + dependencies: ["SQLiteObjc"], + exclude: [ + "Info.plist" + ] + ), + .target( + name: "SQLiteObjc", + dependencies: [], + exclude: [ + "SQLiteObjc.h", + "fts3_tokenizer.h", + "include/README.md" + ] + ), + .testTarget( + name: "SQLiteTests", + dependencies: [ + "SQLite" + ], + path: "Tests/SQLiteTests", + exclude: [ + "Info.plist" + ], + resources: [ + .copy("fixtures/encrypted-3.x.sqlite"), + .copy("fixtures/encrypted-4.x.sqlite") + ] + ) + ] ) #if os(Linux) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 30a40214..2e8807ed 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -277,7 +277,7 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; + EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/SQLiteObjc.h; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -680,7 +680,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1250; TargetAttributes = { 03A65E591C6BB0F50062603F = { CreatedOnToolsVersion = 7.2; @@ -1033,7 +1033,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1053,7 +1053,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1065,7 +1065,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1077,7 +1077,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1148,6 +1148,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1173,8 +1174,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1210,6 +1211,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -1229,8 +1231,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.9; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; @@ -1255,7 +1257,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1276,7 +1277,6 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; @@ -1288,7 +1288,6 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1299,7 +1298,6 @@ isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1320,7 +1318,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1343,7 +1340,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1359,7 +1355,6 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1373,7 +1368,6 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index 4e94e80a..a0db21a2 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ + + + + @@ -39,17 +48,6 @@ - - - - - - - - + + + + @@ -39,17 +48,6 @@ - - - - - - - - + + + + @@ -39,17 +48,6 @@ - - - - - - - - - - - - Date: Thu, 24 Jun 2021 01:24:18 +0200 Subject: [PATCH 0737/1046] - Set iOS deployment target to 9.0 - Bump watchOS deployment version to 3.0 #971 - Fix SQLCipher compilation #1024 --- .travis.yml | 3 - SQLite.swift.podspec | 14 ++--- SQLite.xcodeproj/project.pbxproj | 8 +-- Sources/SQLiteObjc/SQLiteObjc.h | 2 + Sources/SQLiteObjc/include/README.md | 7 --- Sources/SQLiteObjc/include/SQLiteObjc.h | 36 ----------- Tests/CocoaPods/Gemfile | 2 +- Tests/CocoaPods/Gemfile.lock | 83 +++++++++++++++---------- Tests/CocoaPods/integration_test.rb | 37 +---------- Tests/SQLiteTests/ConnectionTests.swift | 6 +- 10 files changed, 69 insertions(+), 129 deletions(-) delete mode 100644 Sources/SQLiteObjc/include/README.md delete mode 100644 Sources/SQLiteObjc/include/SQLiteObjc.h diff --git a/.travis.yml b/.travis.yml index 98987db1..90ff205c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,4 @@ before_install: - brew update - brew outdated carthage || brew upgrade carthage script: -# Workaround for Xcode 10.2/tvOS 9.1 bug (This is an outdated workaround; commenting out.) -# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 -# - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - ./run-tests.sh diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 712ddc36..bcefa5fe 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -19,10 +19,10 @@ Pod::Spec.new do |s| s.swift_versions = ['4.2', '5'] - ios_deployment_target = '8.0' + ios_deployment_target = '9.0' tvos_deployment_target = '9.1' osx_deployment_target = '10.10' - watchos_deployment_target = '2.2' + watchos_deployment_target = '3.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target @@ -32,7 +32,7 @@ Pod::Spec.new do |s| s.subspec 'standard' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.library = 'sqlite3' ss.test_spec 'tests' do |test_spec| @@ -47,7 +47,7 @@ Pod::Spec.new do |s| s.subspec 'standalone' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', @@ -66,12 +66,12 @@ Pod::Spec.new do |s| s.subspec 'SQLCipher' do |ss| ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' - ss.private_header_files = 'Sources/SQLiteObjc/*.h' + ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' } - ss.dependency 'SQLCipher', '>= 3.4.0' + ss.dependency 'SQLCipher', '>= 4.0.0' ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/fixtures/*' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2e8807ed..92795299 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1099,7 +1099,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.2; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -1121,7 +1121,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.2; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -1174,7 +1174,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1231,7 +1231,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; diff --git a/Sources/SQLiteObjc/SQLiteObjc.h b/Sources/SQLiteObjc/SQLiteObjc.h index e8ba9a7d..610cdf10 100644 --- a/Sources/SQLiteObjc/SQLiteObjc.h +++ b/Sources/SQLiteObjc/SQLiteObjc.h @@ -25,6 +25,8 @@ @import Foundation; #if defined(SQLITE_SWIFT_STANDALONE) @import sqlite3; +#elif defined(SQLITE_SWIFT_SQLCIPHER) +@import SQLCipher; #else @import SQLite3; #endif diff --git a/Sources/SQLiteObjc/include/README.md b/Sources/SQLiteObjc/include/README.md deleted file mode 100644 index 54b375e5..00000000 --- a/Sources/SQLiteObjc/include/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# README - -This folder contains the public interface for the SQLiteObjc module. It is a duplicate -copy of the header file found in the Sources/SQLiteObjc folder. If you change one, change -the other too! - - diff --git a/Sources/SQLiteObjc/include/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h deleted file mode 100644 index e8ba9a7d..00000000 --- a/Sources/SQLiteObjc/include/SQLiteObjc.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@import Foundation; -#if defined(SQLITE_SWIFT_STANDALONE) -@import sqlite3; -#else -@import SQLite3; -#endif - -NS_ASSUME_NONNULL_BEGIN -typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); -int _SQLiteRegisterTokenizer(sqlite3 *db, const char *module, const char *tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); -NS_ASSUME_NONNULL_END - diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 77d90eec..19db2b36 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.1' +gem 'cocoapods', '~> 1.10.1' gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index b5172144..0cf77eda 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,77 +1,94 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.11) - i18n (~> 0.7) + CFPropertyList (3.0.3) + activesupport (5.2.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) - claide (1.0.2) - cocoapods (1.6.0.beta.2) - activesupport (>= 4.0.2, < 5) + claide (1.0.3) + cocoapods (1.10.1) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-core (= 1.10.1) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) - activesupport (>= 4.0.2, < 6) + ruby-macho (~> 1.4) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.1) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.9) escape (0.0.4) - fourflusher (2.0.1) + ethon (0.14.0) + ffi (>= 1.15.0) + ffi (1.15.3) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.10) concurrent-ruby (~> 1.0) - minitest (5.11.3) + json (2.5.1) + minitest (5.14.4) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.3.1) + public_suffix (4.0.6) + ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.9) thread_safe (~> 0.1) - xcodeproj (1.7.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0beta2) + cocoapods (~> 1.10.1) minitest BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb index 98a539b5..67b13385 100755 --- a/Tests/CocoaPods/integration_test.rb +++ b/Tests/CocoaPods/integration_test.rb @@ -13,7 +13,7 @@ def test_validate_project private def validator - @validator ||= CustomValidator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| + @validator ||= Pod::Validator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| validator.config.verbose = true validator.no_clean = true validator.use_frameworks = true @@ -32,39 +32,4 @@ def validator def podspec File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') end - - - class CustomValidator < Pod::Validator - def test_pod - # https://github.com/CocoaPods/CocoaPods/issues/7009 - super unless consumer.platform_name == :watchos - end - - def xcodebuild(action, scheme, configuration) - require 'fourflusher' - command = %W(#{action} -workspace #{File.join(validation_dir, 'App.xcworkspace')} -scheme #{scheme} -configuration #{configuration}) - case consumer.platform_name - when :osx, :macos - command += %w(CODE_SIGN_IDENTITY=) - when :ios - command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator) - command += Fourflusher::SimControl.new.destination(nil, 'iOS', deployment_target) - when :watchos - command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator) - command += Fourflusher::SimControl.new.destination(:oldest, 'watchOS', deployment_target) - when :tvos - command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator) - command += Fourflusher::SimControl.new.destination(:oldest, 'tvOS', deployment_target) - end - - begin - _xcodebuild(command, true) - rescue => e - message = 'Returned an unsuccessful exit code.' - message += ' You can use `--verbose` for more information.' unless config.verbose? - error('xcodebuild', message) - e.message - end - end - end end diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index eab3cf00..fdfc9637 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -38,12 +38,14 @@ class ConnectionTests : SQLiteTestCase { func test_init_withURI_returnsURIConnection() { let db = try! Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + let url = URL(fileURLWithPath: db.description) + XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } func test_init_withString_returnsURIConnection() { let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + let url = URL(fileURLWithPath: db.description) + XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } func test_readonly_returnsFalseOnReadWriteConnections() { From 3976805368faf2d5ba9fbf00a397ec8b3a4d81c8 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 17:39:36 +0200 Subject: [PATCH 0738/1046] Moving to GitHub Actions --- .../template.md} | 0 .github/workflows/build.yml | 60 +++++++++++++++++++ .travis.yml | 31 ---------- 3 files changed, 60 insertions(+), 31 deletions(-) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/template.md} (100%) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/template.md similarity index 100% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/template.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..462233d4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,60 @@ +name: Build and test +on: [push, pull_request] +env: + IOS_SIMULATOR: iPhone XS + IOS_VERSION: 12.4 +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Install + run: | + gem update bundler + gem install xcpretty --no-document + brew update + brew outdated carthage || brew upgrade carthage + - name: "Run tests (BUILD_SCHEME: SQLite iOS)" + env: + BUILD_SCHEME: SQLite iOS + run: ./run-tests.sh + - name: "Run tests (BUILD_SCHEME: SQLite Mac)" + env: + BUILD_SCHEME: SQLite Mac + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: none)" + env: + VALIDATOR_SUBSPEC: none + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: standard)" + env: + VALIDATOR_SUBSPEC: standard + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: standalone)" + env: + VALIDATOR_SUBSPEC: standalone + run: ./run-tests.sh + - name: "Run tests (VALIDATOR_SUBSPEC: SQLCipher)" + env: + VALIDATOR_SUBSPEC: SQLCipher + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: iOS)" + env: + CARTHAGE_PLATFORM: iOS + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: Mac)" + env: + CARTHAGE_PLATFORM: Mac + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: watchOS)" + env: + CARTHAGE_PLATFORM: watchOS + run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: tvOS)" + env: + CARTHAGE_PLATFORM: tvOS + run: ./run-tests.sh + - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" + env: + PACKAGE_MANAGER_COMMAND: test + run: ./run-tests.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 98987db1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: objective-c -rvm: 2.6 -# https://docs.travis-ci.com/user/reference/osx -osx_image: xcode12.5 -env: - global: - - IOS_SIMULATOR="iPhone XS" - - IOS_VERSION="12.4" -matrix: - include: - - env: BUILD_SCHEME="SQLite iOS" - - env: BUILD_SCHEME="SQLite Mac" - - env: VALIDATOR_SUBSPEC="none" - - env: VALIDATOR_SUBSPEC="standard" - - env: VALIDATOR_SUBSPEC="standalone" - - env: VALIDATOR_SUBSPEC="SQLCipher" - - env: CARTHAGE_PLATFORM="iOS" - - env: CARTHAGE_PLATFORM="Mac" - - env: CARTHAGE_PLATFORM="watchOS" - - env: CARTHAGE_PLATFORM="tvOS" - - env: PACKAGE_MANAGER_COMMAND="test" -before_install: - - gem update bundler - - gem install xcpretty --no-document - - brew update - - brew outdated carthage || brew upgrade carthage -script: -# Workaround for Xcode 10.2/tvOS 9.1 bug (This is an outdated workaround; commenting out.) -# See https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10 -# - sudo mkdir /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 9.1.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift - - ./run-tests.sh From 4b91f07fe16d6d4ca42f81d845aa37181913b2d8 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 17:46:22 +0200 Subject: [PATCH 0739/1046] Updating iOS test device --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 462233d4..1c47e3a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: iPhone XS - IOS_VERSION: 12.4 + IOS_SIMULATOR: iPhone 12 + IOS_VERSION: 14.4 jobs: build: runs-on: macos-latest From de2059b37a4da218367685368fa01093a7817d7a Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 18:03:26 +0200 Subject: [PATCH 0740/1046] Update Cocoapods version --- Tests/CocoaPods/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile index 77d90eec..da52ec89 100644 --- a/Tests/CocoaPods/Gemfile +++ b/Tests/CocoaPods/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'cocoapods', '~> 1.6.1' +gem 'cocoapods', '~> 1.10.2' gem 'minitest' From 1e4a1f8092980b93cef68f64edba92dc2b65c573 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 18:16:27 +0200 Subject: [PATCH 0741/1046] Updating Cocoapods gemfile lock --- Tests/CocoaPods/Gemfile.lock | 85 ++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock index b5172144..e0383a67 100644 --- a/Tests/CocoaPods/Gemfile.lock +++ b/Tests/CocoaPods/Gemfile.lock @@ -1,77 +1,96 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.0) - activesupport (4.2.11) - i18n (~> 0.7) + CFPropertyList (3.0.3) + activesupport (5.2.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) atomos (0.1.3) - claide (1.0.2) - cocoapods (1.6.0.beta.2) - activesupport (>= 4.0.2, < 5) + claide (1.0.3) + cocoapods (1.10.2) + addressable (~> 2.6) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.6.0.beta.2) - cocoapods-deintegrate (>= 1.0.2, < 2.0) - cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-core (= 1.10.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-stats (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) - fourflusher (~> 2.0.1) + fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) molinillo (~> 0.6.6) nap (~> 1.0) - ruby-macho (~> 1.3, >= 1.3.1) - xcodeproj (>= 1.7.0, < 2.0) - cocoapods-core (1.6.0.beta.2) - activesupport (>= 4.0.2, < 6) + ruby-macho (~> 1.4) + xcodeproj (>= 1.19.0, < 2.0) + cocoapods-core (1.10.2) + activesupport (> 5.0, < 6) + addressable (~> 2.6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) - cocoapods-deintegrate (1.0.2) - cocoapods-downloader (1.2.2) + netrc (~> 0.11) + public_suffix + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.4.0) cocoapods-plugins (1.0.0) nap - cocoapods-search (1.0.0) - cocoapods-stats (1.0.0) - cocoapods-trunk (1.3.1) + cocoapods-search (1.0.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.9) escape (0.0.4) - fourflusher (2.0.1) + ethon (0.14.0) + ffi (>= 1.15.0) + ffi (1.15.3) + fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - i18n (0.9.5) + httpclient (2.8.3) + i18n (1.8.10) concurrent-ruby (~> 1.0) + json (2.5.1) minitest (5.11.3) molinillo (0.6.6) - nanaimo (0.2.6) + nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - ruby-macho (1.3.1) + public_suffix (4.0.6) + rexml (3.2.5) + ruby-macho (1.4.0) thread_safe (0.3.6) - tzinfo (1.2.5) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.9) thread_safe (~> 0.1) - xcodeproj (1.7.0) + xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.6) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) PLATFORMS ruby DEPENDENCIES - cocoapods (~> 1.6.0beta2) + cocoapods (~> 1.10.2) minitest BUNDLED WITH - 1.17.1 + 2.2.22 From 9016160ae16bc0376f00dd0d50132319b996c750 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 18:41:08 +0200 Subject: [PATCH 0742/1046] Fixed Carthage Makefil for GitHub Actions --- Tests/Carthage/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index f5185854..b44d85b9 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -11,7 +11,7 @@ test: $(CARTHAGE) Cartfile $< bootstrap $(CARTHAGE_CMDLINE) Cartfile: - echo 'git "$(TRAVIS_BUILD_DIR)" "HEAD"' > $@ + echo 'git "$(GITHUB_WORKSPACE)" "HEAD"' > $@ clean: @rm -f Cartfile Cartfile.resolved From 6ccf8a8e7f03d1909bb0452b344151091d664bf5 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 20:01:29 +0200 Subject: [PATCH 0743/1046] Totally removing Travis from repository --- README.md | 5 ++--- SQLite.xcodeproj/project.pbxproj | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8b0972f6..c3c89ce5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SQLite.swift -[![Build Status][TravisBadge]][TravisLink] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] +![Build Status][GitHubActionBadge] [![CocoaPods Version][CocoaPodsVersionBadge]][CocoaPodsVersionLink] [![Swift5 compatible][Swift5Badge]][Swift5Link] [![Platform][PlatformBadge]][PlatformLink] [![Carthage compatible][CartagheBadge]][CarthageLink] [![Join the chat at https://gitter.im/stephencelis/SQLite.swift][GitterBadge]][GitterLink] A type-safe, [Swift][]-language layer over [SQLite3][]. @@ -283,8 +283,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [SQLite3]: http://www.sqlite.org [SQLite.swift]: https://github.com/stephencelis/SQLite.swift -[TravisBadge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat -[TravisLink]: https://travis-ci.org/stephencelis/SQLite.swift +[GitHubActionBadge]: https://img.shields.io/github/workflow/status/stephencelis/SQLite.swift/Build%20and%20test [CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png [CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 92795299..1db9c2b7 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -271,7 +271,6 @@ EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247B771C3F40D700AE3E12 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; - EE247B8C1C3F821200AE3E12 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; EE247B8D1C3F821200AE3E12 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; EE247B8F1C3F822500AE3E12 /* Index.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Index.md; sourceTree = ""; }; EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; @@ -473,7 +472,6 @@ EE247B771C3F40D700AE3E12 /* README.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, - EE247B8C1C3F821200AE3E12 /* .travis.yml */, EE247B8D1C3F821200AE3E12 /* Makefile */, EE9180931C46EA210038162A /* libsqlite3.tbd */, EE9180911C46E9D30038162A /* libsqlite3.tbd */, From 65b0989dbefdb1163ac419d7121a176335a90a85 Mon Sep 17 00:00:00 2001 From: Jake-B Date: Wed, 18 Aug 2021 14:56:18 -0400 Subject: [PATCH 0744/1046] Updated documentation for `===` and `!==` operators. --- Documentation/Index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index c6c54437..76a69206 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -955,8 +955,10 @@ equate or compare different types will prevent compilation. | `~=` | `(Interval, Comparable) -> Bool` | `BETWEEN` | | `&&` | `Bool -> Bool` | `AND` | | `\|\|`| `Bool -> Bool` | `OR` | +| `===` | `Equatable -> Bool` | `IS` | +| `!==` | `Equatable -> Bool` | `IS NOT` | -> *When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` +> * When comparing against `nil`, SQLite.swift will use `IS` and `IS NOT` > accordingly. From b7302f6ed19b1994ef9d68882d1cb447be8fd238 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Wed, 18 Aug 2021 23:21:01 +0200 Subject: [PATCH 0745/1046] SPM as first installation method --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 009167b0..81fb4b5d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,27 @@ and the [companion repository][SQLiteDataAccessLayer2]. > _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. +### Swift Package Manager + +The [Swift Package Manager][] is a tool for managing the distribution of +Swift code. + +1. Add the following to your `Package.swift` file: + + ```swift + dependencies: [ + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + ] + ``` + +2. Build your project: + + ```sh + $ swift build + ``` + +[Swift Package Manager]: https://swift.org/package-manager + ### Carthage [Carthage][] is a simple, decentralized dependency manager for Cocoa. To @@ -177,27 +198,6 @@ SQLite.swift with CocoaPods: [CocoaPods]: https://cocoapods.org [CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started -### Swift Package Manager - -The [Swift Package Manager][] is a tool for managing the distribution of -Swift code. - -1. Add the following to your `Package.swift` file: - - ```swift - dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") - ] - ``` - -2. Build your project: - - ```sh - $ swift build - ``` - -[Swift Package Manager]: https://swift.org/package-manager - ### Manual To install SQLite.swift as an Xcode sub-project: From c5b7258ac175751b966a25bc6ba06f1be4ae2caa Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 19 Aug 2021 00:03:18 +0200 Subject: [PATCH 0746/1046] Fixing missing date in upsert test --- Tests/SQLiteTests/QueryTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 9a19945f..fc47ab27 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -280,10 +280,10 @@ class QueryTests : XCTestCase { func test_upsert_encodable() throws { let emails = Table("emails") let string = Expression("string") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0) ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", insert ) } From 5c49d0ed90279d5f90a22997c95ca4c6bfb5bb9f Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 19 Aug 2021 00:09:34 +0200 Subject: [PATCH 0747/1046] Oups --- Tests/SQLiteTests/QueryTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index fc47ab27..9b845a97 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -283,7 +283,7 @@ class QueryTests : XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\"", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\"", insert ) } From b20d6baca11636ac28de51aa797312811d50c2b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 19 Aug 2021 00:11:54 +0200 Subject: [PATCH 0748/1046] Fix spm package structure --- Package.swift | 78 +++++++++---------- SQLite.xcodeproj/project.pbxproj | 20 ++--- Sources/SQLiteObjc/{ => include}/SQLiteObjc.h | 0 3 files changed, 48 insertions(+), 50 deletions(-) rename Sources/SQLiteObjc/{ => include}/SQLiteObjc.h (100%) diff --git a/Package.swift b/Package.swift index 73339b7b..7522fa45 100644 --- a/Package.swift +++ b/Package.swift @@ -4,52 +4,50 @@ import PackageDescription let package = Package( name: "SQLite.swift", products: [ - .library( - name: "SQLite", - targets: ["SQLite"] - ) - ], + .library( + name: "SQLite", + targets: ["SQLite"] + ) + ], targets: [ .target( - name: "SQLite", - dependencies: ["SQLiteObjc"], - exclude: [ - "Info.plist" - ] - ), + name: "SQLite", + dependencies: ["SQLiteObjc"], + exclude: [ + "Info.plist" + ] + ), .target( - name: "SQLiteObjc", - dependencies: [], - exclude: [ - "SQLiteObjc.h", - "fts3_tokenizer.h", - "include/README.md" - ] - ), + name: "SQLiteObjc", + dependencies: [], + exclude: [ + "fts3_tokenizer.h" + ] + ), .testTarget( - name: "SQLiteTests", - dependencies: [ - "SQLite" - ], - path: "Tests/SQLiteTests", - exclude: [ - "Info.plist" - ], - resources: [ - .copy("fixtures/encrypted-3.x.sqlite"), - .copy("fixtures/encrypted-4.x.sqlite") - ] - ) + name: "SQLiteTests", + dependencies: [ + "SQLite" + ], + path: "Tests/SQLiteTests", + exclude: [ + "Info.plist" + ], + resources: [ + .copy("fixtures/encrypted-3.x.sqlite"), + .copy("fixtures/encrypted-4.x.sqlite") + ] + ) ] ) #if os(Linux) - package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] - package.targets = [ - .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ - "FTS4Tests.swift", - "FTS5Tests.swift" - ]) - ] +package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] +package.targets = [ + .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), + .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ + "FTS4Tests.swift", + "FTS5Tests.swift" + ]) +] #endif diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 1b5bb0f4..a730284d 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 03A65E721C6BB2D30062603F /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 03A65E761C6BB2E60062603F /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 03A65E771C6BB2E60062603F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; @@ -101,9 +100,12 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; - 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; + 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -183,8 +185,6 @@ EE247B731C3F3FEC00AE3E12 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = EE91808D1C46E5230038162A /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE9180941C46EA210038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; EE9180951C46EBCC0038162A /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180911C46E9D30038162A /* libsqlite3.tbd */; }; /* End PBXBuildFile section */ @@ -229,6 +229,7 @@ 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D4DB368A20C09C9B00D5A58E /* SelectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; @@ -280,7 +281,6 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE91808D1C46E5230038162A /* SQLiteObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../../SQLiteObjc/SQLiteObjc.h; sourceTree = ""; }; EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -384,6 +384,7 @@ isa = PBXGroup; children = ( EE247AD61C3F04ED00AE3E12 /* SQLite.h */, + 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, @@ -430,7 +431,6 @@ EE247AED1C3F06E900AE3E12 /* Core */ = { isa = PBXGroup; children = ( - EE91808D1C46E5230038162A /* SQLiteObjc.h */, EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, @@ -512,7 +512,7 @@ buildActionMask = 2147483647; files = ( 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, - 03A65E751C6BB2DF0062603F /* SQLiteObjc.h in Headers */, + 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -521,8 +521,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3FB1DB2470600A4F4C6 /* SQLiteObjc.h in Headers */, 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, + 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */, 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -531,8 +531,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE91808E1C46E5230038162A /* SQLiteObjc.h in Headers */, EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, + 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -542,8 +542,8 @@ buildActionMask = 2147483647; files = ( EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, + 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, - EE91808F1C46E76D0038162A /* SQLiteObjc.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLiteObjc/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h similarity index 100% rename from Sources/SQLiteObjc/SQLiteObjc.h rename to Sources/SQLiteObjc/include/SQLiteObjc.h From 4ddcb1cc6fdec3e4dd24d89e35a1f31630d81632 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 19 Aug 2021 00:12:59 +0200 Subject: [PATCH 0749/1046] Fix query tests --- Tests/SQLiteTests/QueryTests.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 9b845a97..29cbd7e4 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -397,7 +397,7 @@ class QueryIntegrationTests : SQLiteTestCase { let id = Expression("id") let email = Expression("email") let age = Expression("age") - + override func setUp() { super.setUp() @@ -509,16 +509,16 @@ class QueryIntegrationTests : SQLiteTestCase { let fetchAge = { () throws -> Int? in return try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } } - + let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) XCTAssertEqual(1, id) XCTAssertEqual(30, try fetchAge()) - + let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) XCTAssertEqual(1, nextId) XCTAssertEqual(42, try fetchAge()) } - + func test_update() { let changes = try! db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) @@ -528,24 +528,24 @@ class QueryIntegrationTests : SQLiteTestCase { let changes = try! db.run(users.delete()) XCTAssertEqual(0, changes) } - + func test_union() throws { let expectedIDs = [ try db.run(users.insert(email <- "alice@example.com")), try db.run(users.insert(email <- "sally@example.com")) ] - + let query1 = users.filter(email == "alice@example.com") let query2 = users.filter(email == "sally@example.com") - + let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } XCTAssertEqual(expectedIDs, actualIDs) - + let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - + print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) - + let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) } From 75a177a688ada617cef0efbc910f2fc3bdfbd1ff Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 21 Aug 2021 18:03:32 +0200 Subject: [PATCH 0750/1046] Fixed codable insert many --- Tests/SQLiteTests/QueryTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 9e6e9f7e..79e6871e 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -310,12 +310,12 @@ class QueryTests : XCTestCase { func test_insert_many_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, optional: nil, sub: nil) - let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, optional: nil, sub: nil) - let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, optional: nil, sub: nil) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\") VALUES (1, '2', 1, 3.0, 4.0), (2, '3', 1, 3.0, 5.0), (3, '4', 1, 3.0, 6.0)", + "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000'), (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000')", insert ) } From 449edebfd6c91c2dd376ee61359b4c4b65032032 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 21 Aug 2021 19:24:45 +0200 Subject: [PATCH 0751/1046] Updating to 0.13.0 --- .swift-version | 2 +- SQLite.swift.podspec | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.swift-version b/.swift-version index bf77d549..819e07a2 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.2 +5.0 diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index bcefa5fe..499012c1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.12.2" + s.version = "0.13.0" s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." s.description = <<-DESC @@ -16,12 +16,12 @@ Pod::Spec.new do |s| s.module_name = 'SQLite' s.default_subspec = 'standard' - s.swift_versions = ['4.2', '5'] + s.swift_versions = ['5'] ios_deployment_target = '9.0' tvos_deployment_target = '9.1' - osx_deployment_target = '10.10' + osx_deployment_target = '10.15' watchos_deployment_target = '3.0' s.ios.deployment_target = ios_deployment_target From 8096e9b9c5cc694f373d2176f96889d3e4afe053 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 21 Aug 2021 20:22:10 +0200 Subject: [PATCH 0752/1046] Deleted .swift-version --- .swift-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .swift-version diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 819e07a2..00000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.0 From 31f667f71f5e6b401a4ec2391d8e44d835e5d7fa Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 03:13:48 +0200 Subject: [PATCH 0753/1046] Pod lint fixes --- Sources/SQLite/Core/Connection.swift | 82 +++++++++++++--------------- Tests/SQLiteTests/QueryTests.swift | 15 ++++- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 1bbf7f73..f4a9e8cb 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -415,17 +415,17 @@ public final class Connection { /// /// db.trace { SQL in print(SQL) } public func trace(_ callback: ((String) -> Void)?) { - #if SQLITE_SWIFT_SQLCIPHER || os(Linux) + if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { + trace_v2(callback) + } else { trace_v1(callback) - #else - if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) { - trace_v2(callback) - } else { - trace_v1(callback) - } - #endif + } } + @available(OSX, deprecated: 10.2) + @available(iOS, deprecated: 10.0) + @available(watchOS, deprecated: 3.0) + @available(tvOS, deprecated: 10.0) fileprivate func trace_v1(_ callback: ((String) -> Void)?) { guard let callback = callback else { sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */) @@ -447,8 +447,36 @@ public final class Connection { trace = box } + @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) + fileprivate func trace_v2(_ callback: ((String) -> Void)?) { + guard let callback = callback else { + // If the X callback is NULL or if the M mask is zero, then tracing is disabled. + sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) + trace = nil + return + } - + let box: Trace = { (pointer: UnsafeRawPointer) in + callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) + } + sqlite3_trace_v2(handle, UInt32(SQLITE_TRACE_STMT) /* mask */, + { + // A trace callback is invoked with four arguments: callback(T,C,P,X). + // The T argument is one of the SQLITE_TRACE constants to indicate why the + // callback was invoked. The C argument is a copy of the context pointer. + // The P and X arguments are pointers whose meanings depend on T. + (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in + if let P = P, + let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { + unsafeBitCast(C, to: Trace.self)(expandedSQL) + sqlite3_free(expandedSQL) + } + return Int32(0) // currently ignored + }, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ + ) + trace = box + } fileprivate typealias Trace = @convention(block) (UnsafeRawPointer) -> Void fileprivate var trace: Trace? @@ -712,39 +740,3 @@ extension Result : CustomStringConvertible { } } } - -#if !SQLITE_SWIFT_SQLCIPHER && !os(Linux) -@available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) -extension Connection { - fileprivate func trace_v2(_ callback: ((String) -> Void)?) { - guard let callback = callback else { - // If the X callback is NULL or if the M mask is zero, then tracing is disabled. - sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) - trace = nil - return - } - - let box: Trace = { (pointer: UnsafeRawPointer) in - callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) - } - sqlite3_trace_v2(handle, - UInt32(SQLITE_TRACE_STMT) /* mask */, - { - // A trace callback is invoked with four arguments: callback(T,C,P,X). - // The T argument is one of the SQLITE_TRACE constants to indicate why the - // callback was invoked. The C argument is a copy of the context pointer. - // The P and X arguments are pointers whose meanings depend on T. - (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in - if let P = P, - let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { - unsafeBitCast(C, to: Trace.self)(expandedSQL) - sqlite3_free(expandedSQL) - } - return Int32(0) // currently ignored - }, - unsafeBitCast(box, to: UnsafeMutableRawPointer.self) /* pCtx */ - ) - trace = box - } -} -#endif diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 79e6871e..041b7d0c 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -541,10 +541,11 @@ class QueryIntegrationTests : SQLiteTestCase { let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) XCTAssertEqual(2, id) } - + func test_upsert() throws { + guard db.satisfiesMinimumVersion(minor: 24) else { return } let fetchAge = { () throws -> Int? in - return try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } + try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } } let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) @@ -613,3 +614,13 @@ class QueryIntegrationTests : SQLiteTestCase { } } } + +private extension Connection { + func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { + guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } + let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } + guard components.count == 3 else { return false } + + return components[1] >= minor && components[2] >= patch + } +} From af4801483adf9fa26fa4e80df120b1913b380652 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 22 Aug 2021 17:11:01 +0200 Subject: [PATCH 0754/1046] Adding a `vacuum()` fonction to `Connection` --- Sources/SQLite/Core/Connection.swift | 11 +++++++++++ Tests/SQLiteTests/ConnectionTests.swift | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f4a9e8cb..8fd07cc1 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -252,6 +252,17 @@ public final class Connection { @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } + + // MARK: - VACUUM + + /// Run a vacuum on the database + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + @discardableResult public func vacuum() throws -> Statement { + return try run("VACUUM") + } // MARK: - Scalar diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index fdfc9637..a05cceb5 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -111,6 +111,10 @@ class ConnectionTests : SQLiteTestCase { try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) AssertSQL("SELECT * FROM users WHERE admin = 0", 4) } + + func test_vacuum() { + try! db.vacuum() + } func test_scalar_preparesRunsAndReturnsScalarValues() { XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) From e48d1469f7fda8175de4abdc45638a25e3409f6b Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 22 Aug 2021 17:28:37 +0200 Subject: [PATCH 0755/1046] SPM as first installation method --- Documentation/Index.md | 51 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bc8bb52e..14b29b37 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1,9 +1,9 @@ # SQLite.swift Documentation - [Installation](#installation) + - [Swift Package Manager](#swift-package-manager) - [Carthage](#carthage) - [CocoaPods](#cocoapods) - - [Swift Package Manager](#swift-package-manager) - [Manual](#manual) - [Getting Started](#getting-started) - [Connecting to a Database](#connecting-to-a-database) @@ -71,6 +71,30 @@ > _Note:_ SQLite.swift requires Swift 5 (and > [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. +### Swift Package Manager + +The [Swift Package Manager][] is a tool for managing the distribution of +Swift code. It’s integrated with the Swift build system to automate the +process of downloading, compiling, and linking dependencies. + +It is the recommended approach for using SQLite.swift in OSX CLI +applications. + + 1. Add the following to your `Package.swift` file: + + ```swift + dependencies: [ + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + ] + ``` + + 2. Build your project: + + ```sh + $ swift build + ``` + +[Swift Package Manager]: https://swift.org/package-manager ### Carthage @@ -169,31 +193,6 @@ try db.rekey("another secret") [sqlite3pod]: https://github.com/clemensg/sqlite3pod [SQLCipher]: https://www.zetetic.net/sqlcipher/ -### Swift Package Manager - -The [Swift Package Manager][] is a tool for managing the distribution of -Swift code. It’s integrated with the Swift build system to automate the -process of downloading, compiling, and linking dependencies. - -It is the recommended approach for using SQLite.swift in OSX CLI -applications. - - 1. Add the following to your `Package.swift` file: - - ```swift - dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") - ] - ``` - - 2. Build your project: - - ```sh - $ swift build - ``` - -[Swift Package Manager]: https://swift.org/package-manager - ### Manual To install SQLite.swift as an Xcode sub-project: From c648408e467e84d9d2e191fe354080fde23b980c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 21:14:27 +0200 Subject: [PATCH 0756/1046] Ensure file can be cleaned up --- Tests/SQLiteTests/CipherTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index abecffba..a27ac17d 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -88,6 +88,11 @@ class CipherTests: XCTestCase { try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) + defer { + // ensure file can be cleaned up afterwards + try! FileManager.default.setAttributes([FileAttributeKey.immutable : 0], ofItemAtPath: encryptedFile) + } + let conn = try! Connection(encryptedFile) try! conn.key("sqlcipher-test") XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64) From 368c873e75cc3de9e39464ff82a09fc65649607e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 21:42:01 +0200 Subject: [PATCH 0757/1046] Update CHANGELOG --- CHANGELOG.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f29afeb..465438bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,22 @@ -0.12.3 (18-05-2021), [diff][diff-0.12.2] +0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== -* Swift 5.3 support. -* Xcode 12.5 support. -* Bumps minimum deployment versions. -* Fixes up Package.swift to build SQLiteObjc module. +* Swift 5.3 support +* Xcode 12.5 support +* Bumps minimum deployment versions +* Fixes up Package.swift to build SQLiteObjc module -0.11.6 (xxx), [diff][diff-0.11.6] +0.12.1, 0.12.2 (21-06-2019) [diff][diff-0.12.2] +======================================== + +* CocoaPods modular headers support + +0.12.0 (24-04-2019) [diff][diff-0.12.0] +======================================== + +* Version with Swift 5 Support + +0.11.6 (19-04-2019), [diff][diff-0.11.6] ======================================== * Swift 4.2, SQLCipher 4.x ([#866][]) @@ -71,6 +81,9 @@ [diff-0.11.4]: https://github.com/stephencelis/SQLite.swift/compare/0.11.3...0.11.4 [diff-0.11.5]: https://github.com/stephencelis/SQLite.swift/compare/0.11.4...0.11.5 [diff-0.11.6]: https://github.com/stephencelis/SQLite.swift/compare/0.11.5...0.11.6 +[diff-0.12.0]: https://github.com/stephencelis/SQLite.swift/compare/0.11.6...0.12.0 +[diff-0.12.2]: https://github.com/stephencelis/SQLite.swift/compare/0.12.0...0.12.2 +[diff-0.13.0]: https://github.com/stephencelis/SQLite.swift/compare/0.12.2...0.13.0 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 From d27a078f24164c58827f3adba612415b10016190 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 21:53:16 +0200 Subject: [PATCH 0758/1046] Update version --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 81fb4b5d..98a625c4 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") ] ``` @@ -157,7 +157,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12.0 + github "stephencelis/SQLite.swift" ~> 0.13.0 ``` 3. Run `carthage update` and @@ -189,7 +189,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12.0' + pod 'SQLite.swift', '~> 0.13.0' end ``` From 9af51e2edf491c0ea632e369a6566e09b65aa333 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 22 Aug 2021 22:09:47 +0200 Subject: [PATCH 0759/1046] Bump version --- Documentation/Index.md | 12 ++++++------ SQLite.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 14b29b37..21b1c995 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -84,7 +84,7 @@ applications. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.12.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") ] ``` @@ -105,7 +105,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.12.0 + github "stephencelis/SQLite.swift" ~> 0.13.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -135,7 +135,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.12.0' + pod 'SQLite.swift', '~> 0.13.0' end ``` @@ -149,7 +149,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12.0' + pod 'SQLite.swift/standalone', '~> 0.13.0' end ``` @@ -159,7 +159,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.12.0' + pod 'SQLite.swift/standalone', '~> 0.13.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -173,7 +173,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/SQLCipher', '~> 0.12.0' + pod 'SQLite.swift/SQLCipher', '~> 0.13.0' end ``` diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a730284d..3056bd74 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1265,7 +1265,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.12.3; + MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1287,7 +1287,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.12.3; + MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; From 474f966e1159fb7b14e9ca20e891f980a4f9e84c Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Mon, 23 Aug 2021 11:05:13 +0200 Subject: [PATCH 0760/1046] Start GitHub Actions workflow From 1e5ab952f37e3fb8fb6759dc14c151c618395c23 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 01:18:03 +0200 Subject: [PATCH 0761/1046] Add linting --- .github/workflows/build.yml | 3 + .swiftlint.yml | 32 ++ Makefile | 3 + Sources/SQLite/Core/Blob.swift | 4 +- Sources/SQLite/Core/Connection.swift | 42 +- Sources/SQLite/Core/Statement.swift | 17 +- Sources/SQLite/Core/Value.swift | 18 +- Sources/SQLite/Extensions/Cipher.swift | 4 +- Sources/SQLite/Extensions/FTS4.swift | 19 +- Sources/SQLite/Extensions/FTS5.swift | 4 +- Sources/SQLite/Extensions/RTree.swift | 6 +- Sources/SQLite/Foundation.swift | 6 +- Sources/SQLite/Helpers.swift | 9 +- Sources/SQLite/Typed/AggregateFunctions.swift | 14 +- Sources/SQLite/Typed/Coding.swift | 119 ++-- Sources/SQLite/Typed/Collation.swift | 6 +- Sources/SQLite/Typed/CoreFunctions.swift | 28 +- Sources/SQLite/Typed/CustomFunctions.swift | 58 +- Sources/SQLite/Typed/Expression.swift | 12 +- Sources/SQLite/Typed/Operators.swift | 328 +++++------ Sources/SQLite/Typed/Query.swift | 84 ++- Sources/SQLite/Typed/Schema.swift | 172 ++++-- Sources/SQLite/Typed/Setter.swift | 130 ++--- Tests/.swiftlint.yml | 20 + .../SQLiteTests/AggregateFunctionsTests.swift | 78 +-- Tests/SQLiteTests/BlobTests.swift | 2 +- Tests/SQLiteTests/CipherTests.swift | 6 +- Tests/SQLiteTests/ConnectionTests.swift | 113 ++-- Tests/SQLiteTests/CoreFunctionsTests.swift | 146 ++--- Tests/SQLiteTests/CustomFunctionsTests.swift | 16 +- .../DateAndTimeFunctionTests.swift | 42 +- Tests/SQLiteTests/ExpressionTests.swift | 3 +- Tests/SQLiteTests/FTS4Tests.swift | 51 +- Tests/SQLiteTests/FTS5Tests.swift | 5 +- Tests/SQLiteTests/FoundationTests.swift | 2 +- Tests/SQLiteTests/OperatorsTests.swift | 530 +++++++++--------- Tests/SQLiteTests/QueryTests.swift | 263 +++++---- Tests/SQLiteTests/RTreeTests.swift | 4 +- Tests/SQLiteTests/RowTests.swift | 2 +- Tests/SQLiteTests/SchemaTests.swift | 98 +++- Tests/SQLiteTests/SelectTests.swift | 21 +- Tests/SQLiteTests/SetterTests.swift | 180 +++--- Tests/SQLiteTests/StatementTests.swift | 6 +- Tests/SQLiteTests/TestHelpers.swift | 29 +- Tests/SQLiteTests/ValueTests.swift | 2 +- 45 files changed, 1509 insertions(+), 1228 deletions(-) create mode 100644 .swiftlint.yml create mode 100644 Tests/.swiftlint.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c47e3a6..0b9d0cf1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,9 @@ jobs: gem install xcpretty --no-document brew update brew outdated carthage || brew upgrade carthage + brew install swiftlint + - name: "Lint" + run: make lint - name: "Run tests (BUILD_SCHEME: SQLite iOS)" env: BUILD_SCHEME: SQLite iOS diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..e18c09a1 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,32 @@ +disabled_rules: # rule identifiers to exclude from running + - todo + - operator_whitespace + - large_tuple + - closure_parameter_position +included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. + - Sources + - Tests +excluded: # paths to ignore during linting. overridden by `included`. + +identifier_name: + excluded: + - db + - to + - by + - or + - eq + - gt + - lt + - fn + - a + - b + +line_length: + warning: 150 + error: 150 + ignores_comments: true + + +file_length: + warning: 700 + error: 700 diff --git a/Makefile b/Makefile index f3f355ab..5e4ff388 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,9 @@ default: test build: $(BUILD_TOOL) $(BUILD_ARGUMENTS) +lint: + swiftlint + test: ifdef XCPRETTY @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) | $(XCPRETTY) -c diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index 2f5d2a14..a49ef23b 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -43,7 +43,7 @@ public struct Blob { } -extension Blob : CustomStringConvertible { +extension Blob: CustomStringConvertible { public var description: String { return "x'\(toHex())'" @@ -51,7 +51,7 @@ extension Blob : CustomStringConvertible { } -extension Blob : Equatable { +extension Blob: Equatable { } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 9273b36a..a4dad4a6 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -70,7 +70,7 @@ public final class Connection { /// A DELETE operation. case delete - fileprivate init(rawValue:Int32) { + fileprivate init(rawValue: Int32) { switch rawValue { case SQLITE_INSERT: self = .insert @@ -86,7 +86,7 @@ public final class Connection { public var handle: OpaquePointer { return _handle! } - fileprivate var _handle: OpaquePointer? = nil + fileprivate var _handle: OpaquePointer? /// Initializes a new SQLite connection. /// @@ -252,9 +252,9 @@ public final class Connection { @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try prepare(statement).run(bindings) } - + // MARK: - VACUUM - + /// Run a vacuum on the database /// /// - Throws: `Result.Error` if query execution fails. @@ -311,7 +311,7 @@ public final class Connection { // MARK: - Transactions /// The mode in which a transaction acquires a lock. - public enum TransactionMode : String { + public enum TransactionMode: String { /// Defers locking the database till the first read/write executes. case deferred = "DEFERRED" @@ -446,11 +446,9 @@ public final class Connection { let box: Trace = { (pointer: UnsafeRawPointer) in callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) } - sqlite3_trace(handle, - { - (C: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in - if let C = C, let SQL = SQL { - unsafeBitCast(C, to: Trace.self)(SQL) + sqlite3_trace(handle, { (context: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in + if let context = context, let SQL = SQL { + unsafeBitCast(context, to: Trace.self)(SQL) } }, unsafeBitCast(box, to: UnsafeMutableRawPointer.self) @@ -470,13 +468,12 @@ public final class Connection { let box: Trace = { (pointer: UnsafeRawPointer) in callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) } - sqlite3_trace_v2(handle, UInt32(SQLITE_TRACE_STMT) /* mask */, - { + sqlite3_trace_v2(handle, UInt32(SQLITE_TRACE_STMT) /* mask */, { // A trace callback is invoked with four arguments: callback(T,C,P,X). // The T argument is one of the SQLITE_TRACE constants to indicate why the // callback was invoked. The C argument is a copy of the context pointer. // The P and X arguments are pointers whose meanings depend on T. - (T: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, X: UnsafeMutableRawPointer?) in + (_: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in if let P = P, let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { unsafeBitCast(C, to: Trace.self)(expandedSQL) @@ -588,7 +585,9 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { + // swiftlint:disable:next cyclomatic_complexity + public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, + _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 let box: Function = { context, argc, argv in let arguments: [Binding?] = (0.. Statement { reset(clearBindings: false) @@ -202,7 +201,7 @@ extension Statement : Sequence { } -public protocol FailableIterator : IteratorProtocol { +public protocol FailableIterator: IteratorProtocol { func failableNext() throws -> Self.Element? } @@ -221,14 +220,14 @@ extension Array { } } -extension Statement : FailableIterator { +extension Statement: FailableIterator { public typealias Element = [Binding?] public func failableNext() throws -> [Binding?]? { return try step() ? Array(row) : nil } } -extension Statement : CustomStringConvertible { +extension Statement: CustomStringConvertible { public var description: String { return String(cString: sqlite3_sql(handle)) @@ -283,7 +282,7 @@ public struct Cursor { } /// Cursors provide direct access to a statement’s current row. -extension Cursor : Sequence { +extension Cursor: Sequence { public subscript(idx: Int) -> Binding? { switch sqlite3_column_type(handle, Int32(idx)) { @@ -306,7 +305,7 @@ extension Cursor : Sequence { var idx = 0 return AnyIterator { if idx >= self.columnCount { - return Optional.none + return Binding??.none } else { idx += 1 return self[idx - 1] diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index 608f0ce6..b8686eab 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -29,13 +29,13 @@ /// protocol, instead. public protocol Binding {} -public protocol Number : Binding {} +public protocol Number: Binding {} -public protocol Value : Expressible { // extensions cannot have inheritance clauses +public protocol Value: Expressible { // extensions cannot have inheritance clauses associatedtype ValueType = Self - associatedtype Datatype : Binding + associatedtype Datatype: Binding static var declaredDatatype: String { get } @@ -45,7 +45,7 @@ public protocol Value : Expressible { // extensions cannot have inheritance clau } -extension Double : Number, Value { +extension Double: Number, Value { public static let declaredDatatype = "REAL" @@ -59,7 +59,7 @@ extension Double : Number, Value { } -extension Int64 : Number, Value { +extension Int64: Number, Value { public static let declaredDatatype = "INTEGER" @@ -73,7 +73,7 @@ extension Int64 : Number, Value { } -extension String : Binding, Value { +extension String: Binding, Value { public static let declaredDatatype = "TEXT" @@ -87,7 +87,7 @@ extension String : Binding, Value { } -extension Blob : Binding, Value { +extension Blob: Binding, Value { public static let declaredDatatype = "BLOB" @@ -103,7 +103,7 @@ extension Blob : Binding, Value { // MARK: - -extension Bool : Binding, Value { +extension Bool: Binding, Value { public static var declaredDatatype = Int64.declaredDatatype @@ -117,7 +117,7 @@ extension Bool : Binding, Value { } -extension Int : Number, Value { +extension Int: Number, Value { public static var declaredDatatype = Int64.declaredDatatype diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 44919aab..9fce42f7 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -1,7 +1,6 @@ #if SQLITE_SWIFT_SQLCIPHER import SQLCipher - /// Extension methods for [SQLCipher](https://www.zetetic.net/sqlcipher/). /// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/) extension Connection { @@ -32,7 +31,6 @@ extension Connection { try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } - /// Change the key on an open database. If the current database is not encrypted, this routine /// will encrypt it. /// To change the key on an existing encrypted database, it must first be unlocked with the @@ -60,7 +58,7 @@ extension Connection { // the key provided is incorrect. To test that the database can be successfully opened with the // provided key, it is necessary to perform some operation on the database (i.e. read from it). private func cipher_key_check() throws { - let _ = try scalar("SELECT count(*) FROM sqlite_master;") + _ = try scalar("SELECT count(*) FROM sqlite_master;") } } #endif diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index f0184565..a3c253f2 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -97,7 +97,8 @@ public struct Tokenizer { public static let Porter = Tokenizer("porter") - public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], separators: Set = []) -> Tokenizer { + public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], + separators: Set = []) -> Tokenizer { var arguments = [String]() if let removeDiacritics = removeDiacritics { @@ -134,7 +135,7 @@ public struct Tokenizer { } -extension Tokenizer : CustomStringConvertible { +extension Tokenizer: CustomStringConvertible { public var description: String { return ([name] + arguments).joined(separator: " ") @@ -145,13 +146,13 @@ extension Tokenizer : CustomStringConvertible { extension Connection { public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range)?) throws { - try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { ( - input: UnsafePointer, offset: UnsafeMutablePointer, length: UnsafeMutablePointer) in + try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { + (input: UnsafePointer, offset: UnsafeMutablePointer, length: UnsafeMutablePointer) in let string = String(cString: input) guard let (token, range) = next(string) else { return nil } - let view:String.UTF8View = string.utf8 + let view: String.UTF8View = string.utf8 if let from = range.lowerBound.samePosition(in: view), let to = range.upperBound.samePosition(in: view) { @@ -231,7 +232,7 @@ open class FTSConfig { if let tokenizer = tokenizer { options.append("tokenize", value: Expression(literal: tokenizer.description)) } - options.appendCommaSeparated("prefix", values:prefixes.sorted().map { String($0) }) + options.appendCommaSeparated("prefix", values: prefixes.sorted().map { String($0) }) if isContentless { options.append("content", value: "") } else if let externalContentSchema = externalContentSchema { @@ -274,9 +275,9 @@ open class FTSConfig { } /// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. -open class FTS4Config : FTSConfig { +open class FTS4Config: FTSConfig { /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - public enum MatchInfo : CustomStringConvertible { + public enum MatchInfo: CustomStringConvertible { case fts3 public var description: String { return "fts3" @@ -284,7 +285,7 @@ open class FTS4Config : FTSConfig { } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - public enum Order : CustomStringConvertible { + public enum Order: CustomStringConvertible { /// Data structures are optimized for returning results in ascending order by docid (default) case asc /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid. diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index cf13f3d8..07f49ffc 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -32,8 +32,8 @@ extension Module { /// /// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version /// of SQLite. -open class FTS5Config : FTSConfig { - public enum Detail : CustomStringConvertible { +open class FTS5Config: FTSConfig { + public enum Detail: CustomStringConvertible { /// store rowid, column number, term offset case full /// store rowid, column number diff --git a/Sources/SQLite/Extensions/RTree.swift b/Sources/SQLite/Extensions/RTree.swift index 4fc1a235..5ecdf78b 100644 --- a/Sources/SQLite/Extensions/RTree.swift +++ b/Sources/SQLite/Extensions/RTree.swift @@ -23,8 +23,9 @@ // extension Module { - - public static func RTree(_ primaryKey: Expression, _ pairs: (Expression, Expression)...) -> Module where T.Datatype == Int64, U.Datatype == Double { + public static func RTree(_ primaryKey: Expression, + _ pairs: (Expression, Expression)...) + -> Module where T.Datatype == Int64, U.Datatype == Double { var arguments: [Expressible] = [primaryKey] for pair in pairs { @@ -33,5 +34,4 @@ extension Module { return Module(name: "rtree", arguments: arguments) } - } diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 9986f581..d837d8ba 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -24,7 +24,7 @@ import Foundation -extension Data : Value { +extension Data: Value { public static var declaredDatatype: String { return Blob.declaredDatatype @@ -42,7 +42,7 @@ extension Data : Value { } -extension Date : Value { +extension Date: Value { public static var declaredDatatype: String { return String.declaredDatatype @@ -69,7 +69,7 @@ public var dateFormatter: DateFormatter = { return formatter }() -extension UUID : Value { +extension UUID: Value { public static var declaredDatatype: String { return String.declaredDatatype diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 44aaec19..0e3385e3 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -44,7 +44,7 @@ public protocol _OptionalType { } -extension Optional : _OptionalType { +extension Optional: _OptionalType { public typealias WrappedType = Wrapped @@ -75,7 +75,7 @@ extension String { func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { return infix([lhs, rhs], wrap: wrap) } - + func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { let expression = Expression(" \(self) ".join(terms).expression) guard wrap else { @@ -115,10 +115,11 @@ func transcode(_ literal: Binding?) -> String { } } +//swiftlint:disable force_cast func value(_ v: Binding) -> A { - return A.fromDatatypeValue(v as! A.Datatype) as! A + A.fromDatatypeValue(v as! A.Datatype) as! A } func value(_ v: Binding?) -> A { - return value(v!) + value(v!) } diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 2ec28288..325b0277 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -29,13 +29,13 @@ private enum Function: String { case avg case sum case total - + func wrap(_ expression: Expressible) -> Expression { return self.rawValue.wrap(expression) } } -extension ExpressionType where UnderlyingType : Value { +extension ExpressionType where UnderlyingType: Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. /// @@ -66,7 +66,7 @@ extension ExpressionType where UnderlyingType : Value { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value { /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. /// @@ -97,7 +97,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr } -extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Comparable { +extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: Comparable { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. @@ -127,7 +127,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Comparable { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value, UnderlyingType.WrappedType.Datatype: Comparable { /// Builds a copy of the expression wrapped with the `max` aggregate /// function. @@ -157,7 +157,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr } -extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Number { +extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: Number { /// Builds a copy of the expression wrapped with the `avg` aggregate /// function. @@ -200,7 +200,7 @@ extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Number { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value, UnderlyingType.WrappedType.Datatype: Number { /// Builds a copy of the expression wrapped with the `avg` aggregate /// function. diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 7044bc37..5a672883 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -39,12 +39,12 @@ extension QueryType { /// - otherSetters: Any other setters to include in the insert /// /// - Returns: An `INSERT` statement for the encodable object - public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], otherSetters: [Setter] = []) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } - + /// Creates an `INSERT` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -63,7 +63,8 @@ extension QueryType { /// - otherSetters: Any other setters to include in the insert /// /// - Returns: An `INSERT` statement fort the encodable object - public func insert(or onConflict: OnConflict, encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + public func insert(or onConflict: OnConflict, encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = []) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.insert(or: onConflict, encoder.setters + otherSetters) @@ -83,7 +84,8 @@ extension QueryType { /// - otherSetters: Any other setters to include in the inserts, per row/object. /// /// - Returns: An `INSERT` statement for the encodable objects - public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert { + public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = []) throws -> Insert { let combinedSetters = try encodables.map { encodable -> [Setter] in let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) @@ -108,7 +110,8 @@ extension QueryType { /// - onConflictOf: The column that if conflicts should trigger an update instead of insert. /// /// - Returns: An `INSERT` statement fort the encodable object - public func upsert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = [], onConflictOf conflicting: Expressible) throws -> Insert { + public func upsert(_ encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = [], onConflictOf conflicting: Expressible) throws -> Insert { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) @@ -128,7 +131,8 @@ extension QueryType { /// - otherSetters: Any other setters to include in the insert /// /// - Returns: An `UPDATE` statement fort the encodable object - public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update { + public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey: Any] = [:], + otherSetters: [Setter] = []) throws -> Update { let encoder = SQLiteEncoder(userInfo: userInfo) try encodable.encode(to: encoder) return self.update(encoder.setters + otherSetters) @@ -154,8 +158,9 @@ extension Row { } /// Generates a list of settings for an Encodable object -fileprivate class SQLiteEncoder: Encoder { +private class SQLiteEncoder: Encoder { class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { + // swiftlint:disable nesting typealias Key = MyKey let encoder: SQLiteEncoder @@ -197,14 +202,12 @@ fileprivate class SQLiteEncoder: Encoder { self.encoder.setters.append(Expression(key.stringValue) <- value) } - func encode(_ value: T, forKey key: Key) throws where T : Swift.Encodable { + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { if let data = value as? Data { self.encoder.setters.append(Expression(key.stringValue) <- data) - } - else if let date = value as? Date { + } else if let date = value as? Date { self.encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - } - else { + } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) self.encoder.setters.append(Expression(key.stringValue) <- string) @@ -212,15 +215,18 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int8 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an Int8 is not supported")) } func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int16 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an Int16 is not supported")) } func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an Int32 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an Int32 is not supported")) } func encode(_ value: Int64, forKey key: Key) throws { @@ -228,26 +234,32 @@ fileprivate class SQLiteEncoder: Encoder { } func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt is not supported")) } func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt8 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt8 is not supported")) } func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt16 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt16 is not supported")) } func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt32 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt32 is not supported")) } func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, debugDescription: "encoding an UInt64 is not supported")) + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + debugDescription: "encoding an UInt64 is not supported")) } - func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey : CodingKey { + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) + -> KeyedEncodingContainer where NestedKey: CodingKey { fatalError("encoding a nested container is not supported") } @@ -272,13 +284,13 @@ fileprivate class SQLiteEncoder: Encoder { fatalError("not supported") } - func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } } -fileprivate class SQLiteDecoder : Decoder { - class SQLiteKeyedDecodingContainer : KeyedDecodingContainerProtocol { +private class SQLiteDecoder: Decoder { + class SQLiteKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = MyKey let codingPath: [CodingKey] = [] @@ -309,15 +321,18 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int8 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an Int8 is not supported")) } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int16 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an Int16 is not supported")) } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an Int32 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an Int32 is not supported")) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { @@ -325,24 +340,29 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt is not supported")) } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt8 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt8 is not supported")) } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt16 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt16 is not supported")) } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt32 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt32 is not supported")) } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an UInt64 is not supported")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { @@ -358,37 +378,45 @@ fileprivate class SQLiteDecoder : Decoder { } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { + // swiftlint:disable force_cast if type == Data.self { let data = try self.row.get(Expression(key.stringValue)) return data as! T - } - else if type == Date.self { + } else if type == Date.self { let date = try self.row.get(Expression(key.stringValue)) return date as! T } + // swiftlint:enable force_cast guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "an unsupported type was found")) + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + debugDescription: "an unsupported type was found")) } guard let data = JSONString.data(using: .utf8) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "invalid utf8 data found")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "invalid utf8 data found")) } return try JSONDecoder().decode(type, from: data) } - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding nested containers is not supported")) + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws + -> KeyedDecodingContainer where NestedKey: CodingKey { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding nested containers is not supported")) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding unkeyed containers is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding unkeyed containers is not supported")) } func superDecoder() throws -> Swift.Decoder { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super encoders containers is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding super encoders containers is not supported")) } func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding super decoders is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding super decoders is not supported")) } } @@ -401,16 +429,17 @@ fileprivate class SQLiteDecoder : Decoder { self.userInfo = userInfo } - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding an unkeyed container is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding an unkeyed container is not supported")) } func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "decoding a single value container is not supported")) + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + debugDescription: "decoding a single value container is not supported")) } } - diff --git a/Sources/SQLite/Typed/Collation.swift b/Sources/SQLite/Typed/Collation.swift index e2ff9d10..3b268ce0 100644 --- a/Sources/SQLite/Typed/Collation.swift +++ b/Sources/SQLite/Typed/Collation.swift @@ -43,7 +43,7 @@ public enum Collation { } -extension Collation : Expressible { +extension Collation: Expressible { public var expression: Expression { return Expression(literal: description) @@ -51,9 +51,9 @@ extension Collation : Expressible { } -extension Collation : CustomStringConvertible { +extension Collation: CustomStringConvertible { - public var description : String { + public var description: String { switch self { case .binary: return "BINARY" diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 068dcf02..053f17e5 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -45,21 +45,21 @@ private enum Function: String { case regexp = "REGEXP" case collate = "COLLATE" case ifnull - + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { return self.rawValue.infix(lhs, rhs, wrap: wrap) } - + func wrap(_ expression: Expressible) -> Expression { return self.rawValue.wrap(expression) } - + func wrap(_ expressions: [Expressible]) -> Expression { return self.rawValue.wrap(", ".join(expressions)) } } -extension ExpressionType where UnderlyingType : Number { +extension ExpressionType where UnderlyingType: Number { /// Builds a copy of the expression wrapped with the `abs` function. /// @@ -68,13 +68,13 @@ extension ExpressionType where UnderlyingType : Number { /// // abs("x") /// /// - Returns: A copy of the expression wrapped with the `abs` function. - public var absoluteValue : Expression { + public var absoluteValue: Expression { return Function.abs.wrap(self) } } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Number { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Number { /// Builds a copy of the expression wrapped with the `abs` function. /// @@ -83,7 +83,7 @@ extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.Wr /// // abs("x") /// /// - Returns: A copy of the expression wrapped with the `abs` function. - public var absoluteValue : Expression { + public var absoluteValue: Expression { return Function.abs.wrap(self) } @@ -129,7 +129,7 @@ extension ExpressionType where UnderlyingType == Double? { } -extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype == Int64 { +extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype == Int64 { /// Builds an expression representing the `random` function. /// @@ -481,7 +481,7 @@ extension ExpressionType where UnderlyingType == String? { } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) } - + /// Builds a copy of the expression appended with a `LIKE` query against the /// given pattern. /// @@ -671,7 +671,7 @@ extension ExpressionType where UnderlyingType == String? { } -extension Collection where Iterator.Element : Value { +extension Collection where Iterator.Element: Value { /// Builds a copy of the expression prepended with an `IN` check against the /// collection. @@ -708,7 +708,7 @@ extension Collection where Iterator.Element : Value { } extension String { - + /// Builds a copy of the expression appended with a `LIKE` query against the /// given pattern. /// @@ -751,7 +751,7 @@ extension String { /// /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. -public func ??(optional: Expression, defaultValue: V) -> Expression { +public func ??(optional: Expression, defaultValue: V) -> Expression { return Function.ifnull.wrap([optional, defaultValue]) } @@ -771,7 +771,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressi /// /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. -public func ??(optional: Expression, defaultValue: Expression) -> Expression { +public func ??(optional: Expression, defaultValue: Expression) -> Expression { return Function.ifnull.wrap([optional, defaultValue]) } @@ -791,6 +791,6 @@ public func ??(optional: Expression, defaultValue: Expression) /// /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. -public func ??(optional: Expression, defaultValue: Expression) -> Expression { +public func ??(optional: Expression, defaultValue: Expression) -> Expression { return Function.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 4d3e69ee..88fb5aaa 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -39,83 +39,107 @@ public extension Connection { /// The assigned types must be explicit. /// /// - Returns: A closure returning an SQL expression to call the function. - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z) throws + -> () -> Expression { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws -> (() -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping () -> Z?) throws + -> () -> Expression { let fn = try createFunction(function, 0, deterministic) { _ in block() } return { fn([]) } } // MARK: - - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A) -> Z?) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } return { arg in fn([arg]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws -> ((Expression) -> Expression) { + func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?) -> Z?) throws + -> (Expression) -> Expression { let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } return { arg in fn([arg]) } } // MARK: - - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B) -> Z) throws -> (Expression, Expression) + -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B) -> Z) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B?) -> Z) throws -> + (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B?) -> Z) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), value(args[1])) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A, B?) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(value(args[0]), args[1].map(value)) } return { a, b in fn([a, b]) } } - func createFunction(_ function: String, deterministic: Bool = false, _ block: @escaping (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + func createFunction(_ function: String, deterministic: Bool = false, + _ block: @escaping (A?, B?) -> Z?) throws + -> (Expression, Expression) -> Expression { let fn = try createFunction(function, 2, deterministic) { args in block(args[0].map(value), args[1].map(value)) } return { a, b in fn([a, b]) } } // MARK: - - fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z) throws -> (([Expressible]) -> Expression) { + fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, + _ block: @escaping ([Binding?]) -> Z) throws + -> ([Expressible]) -> Expression { createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in block(arguments).datatypeValue } @@ -124,7 +148,9 @@ public extension Connection { } } - fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: @escaping ([Binding?]) -> Z?) throws -> (([Expressible]) -> Expression) { + fileprivate func createFunction(_ function: String, _ argumentCount: UInt, _ deterministic: Bool, + _ block: @escaping ([Binding?]) -> Z?) throws + -> ([Expressible]) -> Expression { createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in block(arguments)?.datatypeValue } diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 76ba04c4..39a3c62f 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public protocol ExpressionType : Expressible { // extensions cannot have inheritance clauses +public protocol ExpressionType: Expressible { // extensions cannot have inheritance clauses associatedtype UnderlyingType = Void @@ -43,14 +43,14 @@ extension ExpressionType { self.init(literal: identifier.quote()) } - public init(_ expression: U) { + public init(_ expression: U) { self.init(expression.template, expression.bindings) } } /// An `Expression` represents a raw SQL fragment and any associated bindings. -public struct Expression : ExpressionType { +public struct Expression: ExpressionType { public typealias UnderlyingType = Datatype @@ -79,7 +79,7 @@ extension Expressible { var idx = 0 return expressed.template.reduce("") { template, character in let transcoded: String - + if character == "?" { transcoded = transcode(expressed.bindings[idx]) idx += 1 @@ -108,7 +108,7 @@ extension ExpressionType { } -extension ExpressionType where UnderlyingType : Value { +extension ExpressionType where UnderlyingType: Value { public init(value: UnderlyingType) { self.init("?", [value.datatypeValue]) @@ -116,7 +116,7 @@ extension ExpressionType where UnderlyingType : Value { } -extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { +extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value { public static var null: Self { return self.init(value: nil) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 954a4af4..32d39fa7 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -45,11 +45,11 @@ private enum Operator: String { case gte = ">=" case lte = "<=" case concatenate = "||" - + func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { return self.rawValue.infix(lhs, rhs, wrap: wrap) } - + func wrap(_ expression: Expressible) -> Expression { return self.rawValue.wrap(expression) } @@ -83,520 +83,521 @@ public func +(lhs: String, rhs: Expression) -> Expression { // MARK: - -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.plus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.mul.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype : Number { +public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype : Number { +public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { return Operator.div.infix(lhs, rhs) } -public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { +public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.wrap(rhs) } -public prefix func -(rhs: Expression) -> Expression where V.Datatype : Number { +public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { return Operator.minus.wrap(rhs) } // MARK: - -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.mod.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseLeft.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseRight.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseAnd.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseOr.infix(lhs, rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { +public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { +public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { return (~(lhs & rhs)) & (lhs | rhs) } -public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { +public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseXor.wrap(rhs) } -public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { +public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { return Operator.bitwiseXor.wrap(rhs) } // MARK: - -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.eq.infix(lhs, rhs) } -public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } return Operator.eq.infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } return "IS".infix(lhs, rhs) } -public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS".infix(lhs, rhs) } -public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } return "IS".infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return Operator.neq.infix(lhs, rhs) } -public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return Operator.neq.infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype : Equatable { +public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { return "IS NOT".infix(lhs, rhs) } -public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype : Equatable { +public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return "IS NOT".infix(lhs, rhs) } - -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gt.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.gte.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lt.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype : Comparable { +public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype : Comparable { +public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { return Operator.lte.infix(lhs, rhs) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } -public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } -public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + + rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) +public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { + return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + + rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } -public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } -public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype : Comparable & Value { +public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } @@ -667,6 +668,7 @@ public func ||(lhs: Bool, rhs: Expression) -> Expression { public prefix func !(rhs: Expression) -> Expression { return Operator.not.wrap(rhs) } + public prefix func !(rhs: Expression) -> Expression { return Operator.not.wrap(rhs) } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 669001d3..8d3ded52 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -21,10 +21,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // - +// swiftlint:disable file_length import Foundation -public protocol QueryType : Expressible { +public protocol QueryType: Expressible { var clauses: QueryClauses { get set } @@ -32,7 +32,7 @@ public protocol QueryType : Expressible { } -public protocol SchemaType : QueryType { +public protocol SchemaType: QueryType { static var identifier: String { get } @@ -141,10 +141,10 @@ extension SchemaType { /// - Parameter all: A list of expressions to select. /// /// - Returns: A query with the given `SELECT` clause applied. - public func select(_ column: Expression) -> ScalarQuery { + public func select(_ column: Expression) -> ScalarQuery { return select(false, [column]) } - public func select(_ column: Expression) -> ScalarQuery { + public func select(_ column: Expression) -> ScalarQuery { return select(false, [column]) } @@ -160,10 +160,10 @@ extension SchemaType { /// - Parameter column: A list of expressions to select. /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. - public func select(distinct column: Expression) -> ScalarQuery { + public func select(distinct column: Expression) -> ScalarQuery { return select(true, [column]) } - public func select(distinct column: Expression) -> ScalarQuery { + public func select(distinct column: Expression) -> ScalarQuery { return select(true, [column]) } @@ -175,7 +175,7 @@ extension SchemaType { extension QueryType { - fileprivate func select(_ distinct: Bool, _ columns: [Expressible]) -> Q { + fileprivate func select(_ distinct: Bool, _ columns: [Expressible]) -> Q { var query = Q.init(clauses.from.name, database: clauses.from.database) query.clauses = clauses query.clauses.select = (distinct, columns) @@ -183,7 +183,7 @@ extension QueryType { } // MARK: UNION - + /// Adds a `UNION` clause to the query. /// /// let users = Table("users") @@ -202,7 +202,7 @@ extension QueryType { query.clauses.union.append(table) return query } - + // MARK: JOIN /// Adds a `JOIN` clause to the query. @@ -589,12 +589,12 @@ extension QueryType { Expression(literal: "OFFSET \(offset)") ]) } - + fileprivate var unionClause: Expressible? { guard !clauses.union.isEmpty else { return nil } - + return " ".join(clauses.union.map { query in " ".join([ Expression(literal: "UNION"), @@ -709,28 +709,28 @@ extension QueryType { query.expression ]).expression) } - + // MARK: UPSERT - + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible) -> Insert { return upsert(insertValues, onConflictOf: conflicting) } - + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible) -> Insert { let setValues = insertValues.filter { $0.column.asSQL() != conflicting.asSQL() } .map { Setter(excluded: $0.column) } return upsert(insertValues, onConflictOf: conflicting, set: setValues) } - + public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { return upsert(insertValues, onConflictOf: conflicting, set: setValues) } - + public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { let insert = insertValues.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in (insert.columns + [setter.column], insert.values + [setter.value]) } - + let clauses: [Expressible?] = [ Expression(literal: "INSERT"), Expression(literal: "INTO"), @@ -744,11 +744,10 @@ extension QueryType { Expression(literal: "DO UPDATE SET"), ", ".join(setValues.map { $0.expression }) ] - + return Insert(" ".join(clauses.compactMap { $0 }).expression) } - // MARK: UPDATE public func update(_ values: Setter...) -> Update { @@ -826,7 +825,7 @@ extension QueryType { // TODO: alias support func tableName(alias aliased: Bool = false) -> Expressible { - guard let alias = clauses.from.alias , aliased else { + guard let alias = clauses.from.alias, aliased else { return database(namespace: clauses.from.alias ?? clauses.from.name) } @@ -874,7 +873,7 @@ extension QueryType { /// Queries a collection of chainable helper functions and expressions to build /// executable SQL statements. -public struct Table : SchemaType { +public struct Table: SchemaType { public static let identifier = "TABLE" @@ -886,7 +885,7 @@ public struct Table : SchemaType { } -public struct View : SchemaType { +public struct View: SchemaType { public static let identifier = "VIEW" @@ -898,7 +897,7 @@ public struct View : SchemaType { } -public struct VirtualTable : SchemaType { +public struct VirtualTable: SchemaType { public static let identifier = "VIRTUAL TABLE" @@ -912,7 +911,7 @@ public struct VirtualTable : SchemaType { // TODO: make `ScalarQuery` work in `QueryType.select()`, `.filter()`, etc. -public struct ScalarQuery : QueryType { +public struct ScalarQuery: QueryType { public var clauses: QueryClauses @@ -924,7 +923,7 @@ public struct ScalarQuery : QueryType { // TODO: decide: simplify the below with a boxed type instead -public struct Select : ExpressionType { +public struct Select: ExpressionType { public var template: String public var bindings: [Binding?] @@ -936,7 +935,7 @@ public struct Select : ExpressionType { } -public struct Insert : ExpressionType { +public struct Insert: ExpressionType { public var template: String public var bindings: [Binding?] @@ -948,7 +947,7 @@ public struct Insert : ExpressionType { } -public struct Update : ExpressionType { +public struct Update: ExpressionType { public var template: String public var bindings: [Binding?] @@ -960,7 +959,7 @@ public struct Update : ExpressionType { } -public struct Delete : ExpressionType { +public struct Delete: ExpressionType { public var template: String public var bindings: [Binding?] @@ -972,7 +971,6 @@ public struct Delete : ExpressionType { } - public struct RowIterator: FailableIterator { public typealias Element = Row let statement: Statement @@ -1003,7 +1001,6 @@ extension Connection { AnyIterator { statement.next().map { Row(columnNames, $0) } } } } - public func prepareRowIterator(_ query: QueryType) throws -> RowIterator { let expression = query.expression @@ -1017,9 +1014,9 @@ extension Connection { var names = each.expression.template.split { $0 == "." }.map(String.init) let column = names.removeLast() let namespace = names.joined(separator: ".") - + func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { - return { (query: QueryType) throws -> (Void) in + return { (query: QueryType) throws -> Void in var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) q.clauses.select = query.clauses.select let e = q.expression @@ -1028,7 +1025,7 @@ extension Connection { for name in names { columnNames[name] = idx; idx += 1 } } } - + if column == "*" { var select = query select.clauses.select = (false, [Expression(literal: "*") as Expressible]) @@ -1047,30 +1044,30 @@ extension Connection { } continue } - + columnNames[each.expression.template] = idx idx += 1 } return columnNames } - public func scalar(_ query: ScalarQuery) throws -> V { + public func scalar(_ query: ScalarQuery) throws -> V { let expression = query.expression return value(try scalar(expression.template, expression.bindings)) } - public func scalar(_ query: ScalarQuery) throws -> V.ValueType? { + public func scalar(_ query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) } - public func scalar(_ query: Select) throws -> V { + public func scalar(_ query: Select) throws -> V { let expression = query.expression return value(try scalar(expression.template, expression.bindings)) } - public func scalar(_ query: Select) throws -> V.ValueType? { + public func scalar(_ query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) @@ -1186,17 +1183,17 @@ public struct Row { return valueAtIndex(idx) } - public subscript(column: Expression) -> T { + public subscript(column: Expression) -> T { return try! get(column) } - public subscript(column: Expression) -> T? { + public subscript(column: Expression) -> T? { return try! get(column) } } /// Determines the join operator for a query’s `JOIN` clause. -public enum JoinType : String { +public enum JoinType: String { /// A `CROSS` join. case cross = "CROSS" @@ -1241,7 +1238,7 @@ public struct QueryClauses { var order = [Expressible]() var limit: (length: Int, offset: Int?)? - + var union = [QueryType]() fileprivate init(_ name: String, alias: String?, database: String?) { @@ -1249,4 +1246,3 @@ public struct QueryClauses { } } - diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index febfe68c..d8d45e41 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -36,7 +36,8 @@ extension Table { // MARK: - CREATE TABLE - public func create(temporary: Bool = false, ifNotExists: Bool = false, withoutRowid: Bool = false, block: (TableBuilder) -> Void) -> String { + public func create(temporary: Bool = false, ifNotExists: Bool = false, withoutRowid: Bool = false, + block: (TableBuilder) -> Void) -> String { let builder = TableBuilder() block(builder) @@ -62,51 +63,59 @@ extension Table { // MARK: - ALTER TABLE … ADD COLUMN - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { + public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } - public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } - public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { + public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, + collate: Collation) -> String where V.Datatype == String { return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } @@ -140,7 +149,6 @@ extension Table { // MARK: - DROP INDEX - public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { return drop("INDEX", indexName(columns), ifExists) } @@ -211,138 +219,175 @@ public final class TableBuilder { fileprivate var definitions = [Expressible]() - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V) { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V) { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) } - public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, defaultValue: Expression? = nil) { + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil) } - public func column(_ name: Expression, primaryKey: Bool, check: Expression, defaultValue: Expression? = nil) { + public func column(_ name: Expression, primaryKey: Bool, check: Expression, + defaultValue: Expression? = nil) { column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, defaultValue, nil, nil) } - public func column(_ name: Expression, primaryKey: PrimaryKey, check: Expression? = nil) where V.Datatype == Int64 { + public func column(_ name: Expression, primaryKey: PrimaryKey, + check: Expression? = nil) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) } - public func column(_ name: Expression, primaryKey: PrimaryKey, check: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, primaryKey: PrimaryKey, + check: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: Expression, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: Expression, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - public func column(_ name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) where V.Datatype == String { + public func column(_ name: Expression, unique: Bool = false, check: Expression, + defaultValue: V, collate: Collation) where V.Datatype == String { column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, collate) } - fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { + // swiftlint:disable:next function_parameter_count + fileprivate func column(_ name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, + _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, + _ references: (QueryType, Expressible)?, _ collate: Collation?) { definitions.append(definition(name, datatype, primaryKey, null, unique, check, defaultValue, references, collate)) } // MARK: - - public func primaryKey(_ column: Expression) { + public func primaryKey(_ column: Expression) { primaryKey([column]) } - public func primaryKey(_ compositeA: Expression, _ b: Expression) { - primaryKey([compositeA, b]) + public func primaryKey(_ compositeA: Expression, + _ expr: Expression) { + primaryKey([compositeA, expr]) } - public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression) { - primaryKey([compositeA, b, c]) + public func primaryKey(_ compositeA: Expression, + _ expr1: Expression, + _ expr2: Expression) { + primaryKey([compositeA, expr1, expr2]) } - public func primaryKey(_ compositeA: Expression, _ b: Expression, _ c: Expression, _ d: Expression) { - primaryKey([compositeA, b, c, d]) + public func primaryKey(_ compositeA: Expression, + _ expr1: Expression, + _ expr2: Expression, + _ expr3: Expression) { + primaryKey([compositeA, expr1, expr2, expr3]) } fileprivate func primaryKey(_ composite: [Expressible]) { @@ -379,29 +424,37 @@ public final class TableBuilder { } - public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, + update: Dependency? = nil, delete: Dependency? = nil) { foreignKey(column, (table, other), update, delete) } - public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ column: Expression, references table: QueryType, _ other: Expression, + update: Dependency? = nil, delete: Dependency? = nil) { foreignKey(column, (table, other), update, delete) } - public func foreignKey(_ composite: (Expression, Expression), references table: QueryType, _ other: (Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ composite: (Expression, Expression), + references table: QueryType, _ other: (Expression, Expression), + update: Dependency? = nil, delete: Dependency? = nil) { let composite = ", ".join([composite.0, composite.1]) let references = (table, ", ".join([other.0, other.1])) foreignKey(composite, references, update, delete) } - public func foreignKey(_ composite: (Expression, Expression, Expression), references table: QueryType, _ other: (Expression, Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + public func foreignKey(_ composite: (Expression, Expression, Expression), + references table: QueryType, + _ other: (Expression, Expression, Expression), + update: Dependency? = nil, delete: Dependency? = nil) { let composite = ", ".join([composite.0, composite.1, composite.2]) let references = (table, ", ".join([other.0, other.1, other.2])) foreignKey(composite, references, update, delete) } - fileprivate func foreignKey(_ column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) { + fileprivate func foreignKey(_ column: Expressible, _ references: (QueryType, Expressible), + _ update: Dependency?, _ delete: Dependency?) { let clauses: [Expressible?] = [ "FOREIGN KEY".prefix(column), reference(references), @@ -439,7 +492,7 @@ public struct Module { } -extension Module : Expressible { +extension Module: Expressible { public var expression: Expression { return name.wrap(arguments) @@ -484,7 +537,10 @@ private extension QueryType { } -private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { +// swiftlint:disable:next function_parameter_count +private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, + _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, + _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { let clauses: [Expressible?] = [ column, Expression(literal: datatype), @@ -508,7 +564,7 @@ private func reference(_ primary: (QueryType, Expressible)) -> Expressible { ]) } -private enum Modifier : String { +private enum Modifier: String { case unique = "UNIQUE" diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 3d3170f6..25e88342 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -35,27 +35,27 @@ public struct Setter { let column: Expressible let value: Expressible - fileprivate init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - fileprivate init(column: Expression, value: V) { + fileprivate init(column: Expression, value: V) { self.column = column self.value = value } - fileprivate init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - fileprivate init(column: Expression, value: Expression) { + fileprivate init(column: Expression, value: Expression) { self.column = column self.value = value } - fileprivate init(column: Expression, value: V?) { + fileprivate init(column: Expression, value: V?) { self.column = column self.value = Expression(value: value) } @@ -67,7 +67,7 @@ public struct Setter { } } -extension Setter : Expressible { +extension Setter: Expressible { public var expression: Expression { return "=".infix(column, value, wrap: false) @@ -75,19 +75,19 @@ extension Setter : Expressible { } -public func <-(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: V) -> Setter { +public func <-(column: Expression, value: V) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: Expression) -> Setter { +public func <-(column: Expression, value: Expression) -> Setter { return Setter(column: column, value: value) } -public func <-(column: Expression, value: V?) -> Setter { +public func <-(column: Expression, value: V?) -> Setter { return Setter(column: column, value: value) } @@ -107,176 +107,176 @@ public func +=(column: Expression, value: String) -> Setter { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column + value } -public func +=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column + value } -public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column - value } -public func -=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column - value } -public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column * value } -public func *=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column * value } -public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: Expression) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { return column <- column / value } -public func /=(column: Expression, value: V) -> Setter where V.Datatype : Number { +public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { return column <- column / value } -public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column % value } -public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column << value } -public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column >> value } -public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column & value } -public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column | value } -public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { +public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { return column <- column ^ value } -public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) += 1 } -public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) += 1 } -public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) -= 1 } -public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { +public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { return Expression(column) -= 1 } diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml new file mode 100644 index 00000000..3537085c --- /dev/null +++ b/Tests/.swiftlint.yml @@ -0,0 +1,20 @@ +parent_config: ../.swiftlint.yml + +disabled_rules: + - force_cast + - force_try + - identifier_name + +type_body_length: + warning: 800 + error: 800 + +function_body_length: + warning: 200 + error: 200 + +file_length: + warning: 1000 + error: 1000 + + diff --git a/Tests/SQLiteTests/AggregateFunctionsTests.swift b/Tests/SQLiteTests/AggregateFunctionsTests.swift index 6b583ccf..71ac79fb 100644 --- a/Tests/SQLiteTests/AggregateFunctionsTests.swift +++ b/Tests/SQLiteTests/AggregateFunctionsTests.swift @@ -1,68 +1,68 @@ import XCTest import SQLite -class AggregateFunctionsTests : XCTestCase { +class AggregateFunctionsTests: XCTestCase { func test_distinct_prependsExpressionsWithDistinctKeyword() { - AssertSQL("DISTINCT \"int\"", int.distinct) - AssertSQL("DISTINCT \"intOptional\"", intOptional.distinct) - AssertSQL("DISTINCT \"double\"", double.distinct) - AssertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct) - AssertSQL("DISTINCT \"string\"", string.distinct) - AssertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct) + assertSQL("DISTINCT \"int\"", int.distinct) + assertSQL("DISTINCT \"intOptional\"", intOptional.distinct) + assertSQL("DISTINCT \"double\"", double.distinct) + assertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct) + assertSQL("DISTINCT \"string\"", string.distinct) + assertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct) } func test_count_wrapsOptionalExpressionsWithCountFunction() { - AssertSQL("count(\"intOptional\")", intOptional.count) - AssertSQL("count(\"doubleOptional\")", doubleOptional.count) - AssertSQL("count(\"stringOptional\")", stringOptional.count) + assertSQL("count(\"intOptional\")", intOptional.count) + assertSQL("count(\"doubleOptional\")", doubleOptional.count) + assertSQL("count(\"stringOptional\")", stringOptional.count) } func test_max_wrapsComparableExpressionsWithMaxFunction() { - AssertSQL("max(\"int\")", int.max) - AssertSQL("max(\"intOptional\")", intOptional.max) - AssertSQL("max(\"double\")", double.max) - AssertSQL("max(\"doubleOptional\")", doubleOptional.max) - AssertSQL("max(\"string\")", string.max) - AssertSQL("max(\"stringOptional\")", stringOptional.max) - AssertSQL("max(\"date\")", date.max) - AssertSQL("max(\"dateOptional\")", dateOptional.max) + assertSQL("max(\"int\")", int.max) + assertSQL("max(\"intOptional\")", intOptional.max) + assertSQL("max(\"double\")", double.max) + assertSQL("max(\"doubleOptional\")", doubleOptional.max) + assertSQL("max(\"string\")", string.max) + assertSQL("max(\"stringOptional\")", stringOptional.max) + assertSQL("max(\"date\")", date.max) + assertSQL("max(\"dateOptional\")", dateOptional.max) } func test_min_wrapsComparableExpressionsWithMinFunction() { - AssertSQL("min(\"int\")", int.min) - AssertSQL("min(\"intOptional\")", intOptional.min) - AssertSQL("min(\"double\")", double.min) - AssertSQL("min(\"doubleOptional\")", doubleOptional.min) - AssertSQL("min(\"string\")", string.min) - AssertSQL("min(\"stringOptional\")", stringOptional.min) - AssertSQL("min(\"date\")", date.min) - AssertSQL("min(\"dateOptional\")", dateOptional.min) + assertSQL("min(\"int\")", int.min) + assertSQL("min(\"intOptional\")", intOptional.min) + assertSQL("min(\"double\")", double.min) + assertSQL("min(\"doubleOptional\")", doubleOptional.min) + assertSQL("min(\"string\")", string.min) + assertSQL("min(\"stringOptional\")", stringOptional.min) + assertSQL("min(\"date\")", date.min) + assertSQL("min(\"dateOptional\")", dateOptional.min) } func test_average_wrapsNumericExpressionsWithAvgFunction() { - AssertSQL("avg(\"int\")", int.average) - AssertSQL("avg(\"intOptional\")", intOptional.average) - AssertSQL("avg(\"double\")", double.average) - AssertSQL("avg(\"doubleOptional\")", doubleOptional.average) + assertSQL("avg(\"int\")", int.average) + assertSQL("avg(\"intOptional\")", intOptional.average) + assertSQL("avg(\"double\")", double.average) + assertSQL("avg(\"doubleOptional\")", doubleOptional.average) } func test_sum_wrapsNumericExpressionsWithSumFunction() { - AssertSQL("sum(\"int\")", int.sum) - AssertSQL("sum(\"intOptional\")", intOptional.sum) - AssertSQL("sum(\"double\")", double.sum) - AssertSQL("sum(\"doubleOptional\")", doubleOptional.sum) + assertSQL("sum(\"int\")", int.sum) + assertSQL("sum(\"intOptional\")", intOptional.sum) + assertSQL("sum(\"double\")", double.sum) + assertSQL("sum(\"doubleOptional\")", doubleOptional.sum) } func test_total_wrapsNumericExpressionsWithTotalFunction() { - AssertSQL("total(\"int\")", int.total) - AssertSQL("total(\"intOptional\")", intOptional.total) - AssertSQL("total(\"double\")", double.total) - AssertSQL("total(\"doubleOptional\")", doubleOptional.total) + assertSQL("total(\"int\")", int.total) + assertSQL("total(\"intOptional\")", intOptional.total) + assertSQL("total(\"double\")", double.total) + assertSQL("total(\"doubleOptional\")", doubleOptional.total) } func test_count_withStar_wrapsStarWithCountFunction() { - AssertSQL("count(*)", count(*)) + assertSQL("count(*)", count(*)) } } diff --git a/Tests/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/BlobTests.swift index 817205d6..87eb5709 100644 --- a/Tests/SQLiteTests/BlobTests.swift +++ b/Tests/SQLiteTests/BlobTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class BlobTests : XCTestCase { +class BlobTests: XCTestCase { func test_toHex() { let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255]) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index a27ac17d..77e8e7f0 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -77,7 +77,7 @@ class CipherTests: XCTestCase { // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); - guard let cipherVersion:String = db1.cipherVersion, + guard let cipherVersion: String = db1.cipherVersion, cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") else { return } @@ -85,12 +85,12 @@ class CipherTests: XCTestCase { fixture("encrypted-3.x", withExtension: "sqlite") : fixture("encrypted-4.x", withExtension: "sqlite") - try! FileManager.default.setAttributes([FileAttributeKey.immutable : 1], ofItemAtPath: encryptedFile) + try! FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) defer { // ensure file can be cleaned up afterwards - try! FileManager.default.setAttributes([FileAttributeKey.immutable : 0], ofItemAtPath: encryptedFile) + try! FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) } let conn = try! Connection(encryptedFile) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index a05cceb5..00fe4f6b 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -13,12 +13,12 @@ import CSQLite import SQLite3 #endif -class ConnectionTests : SQLiteTestCase { +class ConnectionTests: SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() + createUsersTable() } func test_init_withInMemory_returnsInMemoryConnection() { @@ -62,13 +62,13 @@ class ConnectionTests : SQLiteTestCase { } func test_lastInsertRowid_returnsLastIdAfterInserts() { - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) } func test_lastInsertRowid_doesNotResetAfterError() { XCTAssert(db.lastInsertRowid == 0) - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) XCTAssertThrowsError( try db.run("INSERT INTO \"users\" (email, age, admin) values ('invalid@example.com', 12, 'invalid')") @@ -83,17 +83,17 @@ class ConnectionTests : SQLiteTestCase { } func test_changes_returnsNumberOfChanges() { - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.changes) - try! InsertUser("betsy") + try! insertUser("betsy") XCTAssertEqual(1, db.changes) } func test_totalChanges_returnsTotalNumberOfChanges() { XCTAssertEqual(0, db.totalChanges) - try! InsertUser("alice") + try! insertUser("alice") XCTAssertEqual(1, db.totalChanges) - try! InsertUser("betsy") + try! insertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -109,9 +109,9 @@ class ConnectionTests : SQLiteTestCase { try! db.run("SELECT * FROM users WHERE admin = ?", 0) try! db.run("SELECT * FROM users WHERE admin = ?", [0]) try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) - AssertSQL("SELECT * FROM users WHERE admin = 0", 4) + assertSQL("SELECT * FROM users WHERE admin = 0", 4) } - + func test_vacuum() { try! db.vacuum() } @@ -121,31 +121,31 @@ class ConnectionTests : SQLiteTestCase { XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) - AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) + assertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } func test_execute_comment() { try! db.run("-- this is a comment\nSELECT 1") - AssertSQL("-- this is a comment", 0) - AssertSQL("SELECT 1", 0) + assertSQL("-- this is a comment", 0) + assertSQL("SELECT 1", 0) } func test_transaction_executesBeginDeferred() { try! db.transaction(.deferred) {} - AssertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("BEGIN DEFERRED TRANSACTION") } func test_transaction_executesBeginImmediate() { try! db.transaction(.immediate) {} - AssertSQL("BEGIN IMMEDIATE TRANSACTION") + assertSQL("BEGIN IMMEDIATE TRANSACTION") } func test_transaction_executesBeginExclusive() { try! db.transaction(.exclusive) {} - AssertSQL("BEGIN EXCLUSIVE TRANSACTION") + assertSQL("BEGIN EXCLUSIVE TRANSACTION") } func test_transaction_beginsAndCommitsTransactions() { @@ -155,10 +155,10 @@ class ConnectionTests : SQLiteTestCase { try stmt.run() } - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") - AssertSQL("COMMIT TRANSACTION") - AssertSQL("ROLLBACK TRANSACTION", 0) + assertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + assertSQL("COMMIT TRANSACTION") + assertSQL("ROLLBACK TRANSACTION", 0) } func test_transaction_rollsBackTransactionsIfCommitsFail() { @@ -186,10 +186,10 @@ class ConnectionTests : SQLiteTestCase { XCTFail("unexpected error: \(error)") } - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") - AssertSQL("COMMIT TRANSACTION") - AssertSQL("ROLLBACK TRANSACTION") + assertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") + assertSQL("COMMIT TRANSACTION") + assertSQL("ROLLBACK TRANSACTION") // Run another transaction to ensure that a subsequent transaction does not fail with an "cannot start a // transaction within a transaction" error. @@ -210,14 +210,14 @@ class ConnectionTests : SQLiteTestCase { } catch { } - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) + assertSQL("BEGIN DEFERRED TRANSACTION") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + assertSQL("ROLLBACK TRANSACTION") + assertSQL("COMMIT TRANSACTION", 0) } func test_savepoint_beginsAndCommitsSavepoints() { - let db:Connection = self.db + let db: Connection = self.db try! db.savepoint("1") { try db.savepoint("2") { @@ -225,17 +225,17 @@ class ConnectionTests : SQLiteTestCase { } } - AssertSQL("SAVEPOINT '1'") - AssertSQL("SAVEPOINT '2'") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") - AssertSQL("RELEASE SAVEPOINT '2'") - AssertSQL("RELEASE SAVEPOINT '1'") - AssertSQL("ROLLBACK TO SAVEPOINT '2'", 0) - AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) + assertSQL("SAVEPOINT '1'") + assertSQL("SAVEPOINT '2'") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + assertSQL("RELEASE SAVEPOINT '2'") + assertSQL("RELEASE SAVEPOINT '1'") + assertSQL("ROLLBACK TO SAVEPOINT '2'", 0) + assertSQL("ROLLBACK TO SAVEPOINT '1'", 0) } func test_savepoint_beginsAndRollsSavepointsBack() { - let db:Connection = self.db + let db: Connection = self.db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { @@ -254,13 +254,13 @@ class ConnectionTests : SQLiteTestCase { } catch { } - AssertSQL("SAVEPOINT '1'") - AssertSQL("SAVEPOINT '2'") - AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) - AssertSQL("ROLLBACK TO SAVEPOINT '2'") - AssertSQL("ROLLBACK TO SAVEPOINT '1'") - AssertSQL("RELEASE SAVEPOINT '2'", 0) - AssertSQL("RELEASE SAVEPOINT '1'", 0) + assertSQL("SAVEPOINT '1'") + assertSQL("SAVEPOINT '2'") + assertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + assertSQL("ROLLBACK TO SAVEPOINT '2'") + assertSQL("ROLLBACK TO SAVEPOINT '1'") + assertSQL("RELEASE SAVEPOINT '2'", 0) + assertSQL("RELEASE SAVEPOINT '1'", 0) } func test_updateHook_setsUpdateHook_withInsert() { @@ -272,12 +272,12 @@ class ConnectionTests : SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! InsertUser("alice") + try! insertUser("alice") } } func test_updateHook_setsUpdateHook_withUpdate() { - try! InsertUser("alice") + try! insertUser("alice") async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.update, operation) @@ -291,7 +291,7 @@ class ConnectionTests : SQLiteTestCase { } func test_updateHook_setsUpdateHook_withDelete() { - try! InsertUser("alice") + try! insertUser("alice") async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.delete, operation) @@ -310,7 +310,7 @@ class ConnectionTests : SQLiteTestCase { done() } try! db.transaction { - try self.InsertUser("alice") + try self.insertUser("alice") } XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } @@ -321,8 +321,8 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.InsertUser("alice") - try self.InsertUser("alice") // throw + try self.insertUser("alice") + try self.insertUser("alice") // throw } } catch { } @@ -338,7 +338,7 @@ class ConnectionTests : SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.InsertUser("alice") + try self.insertUser("alice") } } catch { } @@ -376,7 +376,7 @@ class ConnectionTests : SQLiteTestCase { func test_interrupt_interruptsLongRunningQuery() { let semaphore = DispatchSemaphore(value: 0) - db.createFunction("sleep") { args in + db.createFunction("sleep") { _ in DispatchQueue.global(qos: .background).async { self.db.interrupt() semaphore.signal() @@ -396,7 +396,7 @@ class ConnectionTests : SQLiteTestCase { func test_concurrent_access_single_connection() { // test can fail on iOS/tvOS 9.x: SQLite compile-time differences? - guard #available(iOS 10.0, OSX 10.10, tvOS 10.0, watchOS 2.2, *) else { return } + guard #available(iOS 10.0, OSX 10.10, tvOS 10.0, watchOS 2.2, *) else { return } let conn = try! Connection("\(NSTemporaryDirectory())/\(UUID().uuidString)") try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") @@ -416,8 +416,7 @@ class ConnectionTests : SQLiteTestCase { } } - -class ResultTests : XCTestCase { +class ResultTests: XCTestCase { let connection = try! Connection(.inMemory) func test_init_with_ok_code_returns_nil() { @@ -434,13 +433,13 @@ class ResultTests : XCTestCase { func test_init_with_other_code_returns_error() { if case .some(.error(let message, let code, let statement)) = - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { XCTAssertEqual("not an error", message) XCTAssertEqual(SQLITE_MISUSE, code) XCTAssertNil(statement) XCTAssert(self.connection === connection) } else { - XCTFail() + XCTFail("no error") } } diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/CoreFunctionsTests.swift index e7402de3..e03e3769 100644 --- a/Tests/SQLiteTests/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/CoreFunctionsTests.swift @@ -1,145 +1,145 @@ import XCTest @testable import SQLite -class CoreFunctionsTests : XCTestCase { +class CoreFunctionsTests: XCTestCase { func test_round_wrapsDoubleExpressionsWithRoundFunction() { - AssertSQL("round(\"double\")", double.round()) - AssertSQL("round(\"doubleOptional\")", doubleOptional.round()) + assertSQL("round(\"double\")", double.round()) + assertSQL("round(\"doubleOptional\")", doubleOptional.round()) - AssertSQL("round(\"double\", 1)", double.round(1)) - AssertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2)) + assertSQL("round(\"double\", 1)", double.round(1)) + assertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2)) } func test_random_generatesExpressionWithRandomFunction() { - AssertSQL("random()", Expression.random()) - AssertSQL("random()", Expression.random()) + assertSQL("random()", Expression.random()) + assertSQL("random()", Expression.random()) } func test_length_wrapsStringExpressionWithLengthFunction() { - AssertSQL("length(\"string\")", string.length) - AssertSQL("length(\"stringOptional\")", stringOptional.length) + assertSQL("length(\"string\")", string.length) + assertSQL("length(\"stringOptional\")", stringOptional.length) } func test_lowercaseString_wrapsStringExpressionWithLowerFunction() { - AssertSQL("lower(\"string\")", string.lowercaseString) - AssertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString) + assertSQL("lower(\"string\")", string.lowercaseString) + assertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString) } func test_uppercaseString_wrapsStringExpressionWithUpperFunction() { - AssertSQL("upper(\"string\")", string.uppercaseString) - AssertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString) + assertSQL("upper(\"string\")", string.uppercaseString) + assertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString) } func test_like_buildsExpressionWithLikeOperator() { - AssertSQL("(\"string\" LIKE 'a%')", string.like("a%")) - AssertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%")) - - AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) - AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) - - AssertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) - AssertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) - - AssertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) - AssertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) - - AssertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) - AssertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) + assertSQL("(\"string\" LIKE 'a%')", string.like("a%")) + assertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%")) + + assertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) + assertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) + + assertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) + assertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) + + assertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) + assertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) + + assertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) + assertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { - AssertSQL("(\"string\" GLOB 'a*')", string.glob("a*")) - AssertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*")) + assertSQL("(\"string\" GLOB 'a*')", string.glob("a*")) + assertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*")) } func test_match_buildsExpressionWithMatchOperator() { - AssertSQL("(\"string\" MATCH 'a*')", string.match("a*")) - AssertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*")) + assertSQL("(\"string\" MATCH 'a*')", string.match("a*")) + assertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*")) } func test_regexp_buildsExpressionWithRegexpOperator() { - AssertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$")) - AssertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$")) + assertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$")) + assertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$")) } func test_collate_buildsExpressionWithCollateOperator() { - AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.binary)) - AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.nocase)) - AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.rtrim)) - AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.custom("CUSTOM"))) + assertSQL("(\"string\" COLLATE BINARY)", string.collate(.binary)) + assertSQL("(\"string\" COLLATE NOCASE)", string.collate(.nocase)) + assertSQL("(\"string\" COLLATE RTRIM)", string.collate(.rtrim)) + assertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.custom("CUSTOM"))) - AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.binary)) - AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.nocase)) - AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.rtrim)) - AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.custom("CUSTOM"))) + assertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.binary)) + assertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.nocase)) + assertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.rtrim)) + assertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.custom("CUSTOM"))) } func test_ltrim_wrapsStringWithLtrimFunction() { - AssertSQL("ltrim(\"string\")", string.ltrim()) - AssertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim()) + assertSQL("ltrim(\"string\")", string.ltrim()) + assertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim()) - AssertSQL("ltrim(\"string\", ' ')", string.ltrim([" "])) - AssertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "])) + assertSQL("ltrim(\"string\", ' ')", string.ltrim([" "])) + assertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "])) } func test_ltrim_wrapsStringWithRtrimFunction() { - AssertSQL("rtrim(\"string\")", string.rtrim()) - AssertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim()) + assertSQL("rtrim(\"string\")", string.rtrim()) + assertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim()) - AssertSQL("rtrim(\"string\", ' ')", string.rtrim([" "])) - AssertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "])) + assertSQL("rtrim(\"string\", ' ')", string.rtrim([" "])) + assertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "])) } func test_ltrim_wrapsStringWithTrimFunction() { - AssertSQL("trim(\"string\")", string.trim()) - AssertSQL("trim(\"stringOptional\")", stringOptional.trim()) + assertSQL("trim(\"string\")", string.trim()) + assertSQL("trim(\"stringOptional\")", stringOptional.trim()) - AssertSQL("trim(\"string\", ' ')", string.trim([" "])) - AssertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "])) + assertSQL("trim(\"string\", ' ')", string.trim([" "])) + assertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "])) } func test_replace_wrapsStringWithReplaceFunction() { - AssertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net")) - AssertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com")) + assertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net")) + assertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com")) } func test_substring_wrapsStringWithSubstrFunction() { - AssertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2)) - AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1)) + assertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2)) + assertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1)) } func test_subscriptWithRange_wrapsStringWithSubstrFunction() { - AssertSQL("substr(\"string\", 1, 2)", string[1..<3]) - AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3]) + assertSQL("substr(\"string\", 1, 2)", string[1..<3]) + assertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3]) } func test_nilCoalescingOperator_wrapsOptionalsWithIfnullFunction() { - AssertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1) + assertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1) // AssertSQL("ifnull(\"doubleOptional\", 1.0)", doubleOptional ?? 1) // rdar://problem/21677256 XCTAssertEqual("ifnull(\"doubleOptional\", 1.0)", (doubleOptional ?? 1).asSQL()) - AssertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal") + assertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal") - AssertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int) - AssertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double) - AssertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string) + assertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int) + assertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double) + assertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string) - AssertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional) - AssertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional) - AssertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional) + assertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional) + assertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional) + assertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional) } func test_absoluteValue_wrapsNumberWithAbsFucntion() { - AssertSQL("abs(\"int\")", int.absoluteValue) - AssertSQL("abs(\"intOptional\")", intOptional.absoluteValue) + assertSQL("abs(\"int\")", int.absoluteValue) + assertSQL("abs(\"intOptional\")", intOptional.absoluteValue) - AssertSQL("abs(\"double\")", double.absoluteValue) - AssertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue) + assertSQL("abs(\"double\")", double.absoluteValue) + assertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue) } func test_contains_buildsExpressionWithInOperator() { - AssertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) - AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) + assertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) + assertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) } } diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 919986b6..0ebcd13c 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class CustomFunctionNoArgsTests : SQLiteTestCase { +class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> Expression typealias FunctionResultOptional = () -> Expression @@ -22,7 +22,7 @@ class CustomFunctionNoArgsTests : SQLiteTestCase { } } -class CustomFunctionWithOneArgTests : SQLiteTestCase { +class CustomFunctionWithOneArgTests: SQLiteTestCase { typealias FunctionNoOptional = (Expression) -> Expression typealias FunctionLeftOptional = (Expression) -> Expression typealias FunctionResultOptional = (Expression) -> Expression @@ -53,7 +53,7 @@ class CustomFunctionWithOneArgTests : SQLiteTestCase { } func testFunctionLeftResultOptional() { - let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a:String?) -> String? in + let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a: String?) -> String? in return "b"+a! } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String @@ -61,14 +61,14 @@ class CustomFunctionWithOneArgTests : SQLiteTestCase { } } -class CustomFunctionWithTwoArgsTests : SQLiteTestCase { - typealias FunctionNoOptional = (Expression, Expression) -> Expression +class CustomFunctionWithTwoArgsTests: SQLiteTestCase { + typealias FunctionNoOptional = (Expression, Expression) -> Expression typealias FunctionLeftOptional = (Expression, Expression) -> Expression - typealias FunctionRightOptional = (Expression, Expression) -> Expression - typealias FunctionResultOptional = (Expression, Expression) -> Expression + typealias FunctionRightOptional = (Expression, Expression) -> Expression + typealias FunctionResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightOptional = (Expression, Expression) -> Expression typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression - typealias FunctionRightResultOptional = (Expression, Expression) -> Expression + typealias FunctionRightResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression func testNoOptional() { diff --git a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift index 628b5910..393e9c7c 100644 --- a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift +++ b/Tests/SQLiteTests/DateAndTimeFunctionTests.swift @@ -1,66 +1,66 @@ import XCTest @testable import SQLite -class DateAndTimeFunctionsTests : XCTestCase { +class DateAndTimeFunctionsTests: XCTestCase { func test_date() { - AssertSQL("date('now')", DateFunctions.date("now")) - AssertSQL("date('now', 'localtime')", DateFunctions.date("now", "localtime")) + assertSQL("date('now')", DateFunctions.date("now")) + assertSQL("date('now', 'localtime')", DateFunctions.date("now", "localtime")) } func test_time() { - AssertSQL("time('now')", DateFunctions.time("now")) - AssertSQL("time('now', 'localtime')", DateFunctions.time("now", "localtime")) + assertSQL("time('now')", DateFunctions.time("now")) + assertSQL("time('now', 'localtime')", DateFunctions.time("now", "localtime")) } func test_datetime() { - AssertSQL("datetime('now')", DateFunctions.datetime("now")) - AssertSQL("datetime('now', 'localtime')", DateFunctions.datetime("now", "localtime")) + assertSQL("datetime('now')", DateFunctions.datetime("now")) + assertSQL("datetime('now', 'localtime')", DateFunctions.datetime("now", "localtime")) } func test_julianday() { - AssertSQL("julianday('now')", DateFunctions.julianday("now")) - AssertSQL("julianday('now', 'localtime')", DateFunctions.julianday("now", "localtime")) + assertSQL("julianday('now')", DateFunctions.julianday("now")) + assertSQL("julianday('now', 'localtime')", DateFunctions.julianday("now", "localtime")) } func test_strftime() { - AssertSQL("strftime('%Y-%m-%d', 'now')", DateFunctions.strftime("%Y-%m-%d", "now")) - AssertSQL("strftime('%Y-%m-%d', 'now', 'localtime')", DateFunctions.strftime("%Y-%m-%d", "now", "localtime")) + assertSQL("strftime('%Y-%m-%d', 'now')", DateFunctions.strftime("%Y-%m-%d", "now")) + assertSQL("strftime('%Y-%m-%d', 'now', 'localtime')", DateFunctions.strftime("%Y-%m-%d", "now", "localtime")) } } -class DateExtensionTests : XCTestCase { +class DateExtensionTests: XCTestCase { func test_time() { - AssertSQL("time('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).time) + assertSQL("time('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).time) } func test_date() { - AssertSQL("date('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).date) + assertSQL("date('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).date) } func test_datetime() { - AssertSQL("datetime('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).datetime) + assertSQL("datetime('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).datetime) } func test_julianday() { - AssertSQL("julianday('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).julianday) + assertSQL("julianday('1970-01-01T00:00:00.000')", Date(timeIntervalSince1970: 0).julianday) } } -class DateExpressionTests : XCTestCase { +class DateExpressionTests: XCTestCase { func test_date() { - AssertSQL("date(\"date\")", date.date) + assertSQL("date(\"date\")", date.date) } func test_time() { - AssertSQL("time(\"date\")", date.time) + assertSQL("time(\"date\")", date.time) } func test_datetime() { - AssertSQL("datetime(\"date\")", date.datetime) + assertSQL("datetime(\"date\")", date.datetime) } func test_julianday() { - AssertSQL("julianday(\"date\")", date.julianday) + assertSQL("julianday(\"date\")", date.julianday) } } diff --git a/Tests/SQLiteTests/ExpressionTests.swift b/Tests/SQLiteTests/ExpressionTests.swift index 036e10ce..32100d4d 100644 --- a/Tests/SQLiteTests/ExpressionTests.swift +++ b/Tests/SQLiteTests/ExpressionTests.swift @@ -1,6 +1,5 @@ import XCTest import SQLite -class ExpressionTests : XCTestCase { - +class ExpressionTests: XCTestCase { } diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index 79f0a8e2..b637955b 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class FTS4Tests : XCTestCase { +class FTS4Tests: XCTestCase { func test_create_onVirtualTable_withFTS4_compilesCreateVirtualTableExpression() { XCTAssertEqual( @@ -25,26 +25,33 @@ class FTS4Tests : XCTestCase { virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false))) ) XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", - virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"]))) + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + \"tokenchars=.\" \"separators=X\") + """.replacingOccurrences(of: "\n", with: ""), + virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, + tokenchars: ["."], separators: ["X"]))) ) } func test_match_onVirtualTableAsExpression_compilesMatchExpression() { - AssertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression) - AssertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) - AssertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) + assertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression) + assertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) + assertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) } func test_match_onVirtualTableAsQueryType_compilesMatchExpression() { - AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", virtualTable.match("string") as QueryType) - AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as QueryType) - AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as QueryType) + assertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", + virtualTable.match("string") as QueryType) + assertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", + virtualTable.match(string) as QueryType) + assertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", + virtualTable.match(stringOptional) as QueryType) } } -class FTS4ConfigTests : XCTestCase { +class FTS4ConfigTests: XCTestCase { var config: FTS4Config! override func setUp() { @@ -108,7 +115,10 @@ class FTS4ConfigTests : XCTestCase { func test_tokenizer_unicode61_with_options() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + \"tokenchars=.\" \"separators=X\") + """.replacingOccurrences(of: "\n", with: ""), sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) } @@ -156,7 +166,11 @@ class FTS4ConfigTests : XCTestCase { func test_config_all() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\")", + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"int\", \"string\", \"date\", + tokenize=porter, prefix=\"2,4\", content=\"table\", notindexed=\"string\", notindexed=\"date\", + languageid=\"lid\", matchinfo=\"fts3\", order=\"desc\") + """.replacingOccurrences(of: "\n", with: ""), sql(config .tokenizer(.Porter) .column(int) @@ -175,7 +189,7 @@ class FTS4ConfigTests : XCTestCase { } } -class FTS4IntegrationTests : SQLiteTestCase { +class FTS4IntegrationTests: SQLiteTestCase { #if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER func test_registerTokenizer_registersTokenizer() { let emails = VirtualTable("emails") @@ -184,9 +198,11 @@ class FTS4IntegrationTests : SQLiteTestCase { let locale = CFLocaleCopyCurrent() let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), + UInt(kCFStringTokenizerUnitWord), locale) try! db.registerTokenizer(tokenizerName) { string in - CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString))) + CFStringTokenizerSetString(tokenizer, string as CFString, + CFRangeMake(0, CFStringGetLength(string as CFString))) if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { return nil } @@ -199,7 +215,10 @@ class FTS4IntegrationTests : SQLiteTestCase { } try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") + assertSQL(""" + CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", + tokenize=\"SQLite.swift\" \"tokenizer\") + """.replacingOccurrences(of: "\n", with: "")) try! _ = db.run(emails.insert(subject <- "Aún más cáfe!")) XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index 63d8dc40..8079b28c 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -107,7 +107,10 @@ class FTS5Tests: XCTestCase { func test_fts5_config_all() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, tokenize=porter, prefix=\"2,4\", content=\"table\")", + """ + CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(\"int\", \"string\" UNINDEXED, \"date\" UNINDEXED, + tokenize=porter, prefix=\"2,4\", content=\"table\") + """.replacingOccurrences(of: "\n", with: ""), sql(config .tokenizer(.Porter) .column(int) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index ba9685b7..cef485fc 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class FoundationTests : XCTestCase { +class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/OperatorsTests.swift index c9665671..370b910b 100644 --- a/Tests/SQLiteTests/OperatorsTests.swift +++ b/Tests/SQLiteTests/OperatorsTests.swift @@ -1,378 +1,378 @@ import XCTest import SQLite -class OperatorsTests : XCTestCase { +class OperatorsTests: XCTestCase { func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { - AssertSQL("(\"string\" || \"string\")", string + string) - AssertSQL("(\"string\" || \"stringOptional\")", string + stringOptional) - AssertSQL("(\"stringOptional\" || \"string\")", stringOptional + string) - AssertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional) - AssertSQL("(\"string\" || 'literal')", string + "literal") - AssertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal") - AssertSQL("('literal' || \"string\")", "literal" + string) - AssertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional) + assertSQL("(\"string\" || \"string\")", string + string) + assertSQL("(\"string\" || \"stringOptional\")", string + stringOptional) + assertSQL("(\"stringOptional\" || \"string\")", stringOptional + string) + assertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional) + assertSQL("(\"string\" || 'literal')", string + "literal") + assertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal") + assertSQL("('literal' || \"string\")", "literal" + string) + assertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional) } func test_numberExpression_plusNumberExpression_buildsAdditiveNumberExpression() { - AssertSQL("(\"int\" + \"int\")", int + int) - AssertSQL("(\"int\" + \"intOptional\")", int + intOptional) - AssertSQL("(\"intOptional\" + \"int\")", intOptional + int) - AssertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional) - AssertSQL("(\"int\" + 1)", int + 1) - AssertSQL("(\"intOptional\" + 1)", intOptional + 1) - AssertSQL("(1 + \"int\")", 1 + int) - AssertSQL("(1 + \"intOptional\")", 1 + intOptional) - - AssertSQL("(\"double\" + \"double\")", double + double) - AssertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional) - AssertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double) - AssertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional) - AssertSQL("(\"double\" + 1.0)", double + 1) - AssertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1) - AssertSQL("(1.0 + \"double\")", 1 + double) - AssertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional) + assertSQL("(\"int\" + \"int\")", int + int) + assertSQL("(\"int\" + \"intOptional\")", int + intOptional) + assertSQL("(\"intOptional\" + \"int\")", intOptional + int) + assertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional) + assertSQL("(\"int\" + 1)", int + 1) + assertSQL("(\"intOptional\" + 1)", intOptional + 1) + assertSQL("(1 + \"int\")", 1 + int) + assertSQL("(1 + \"intOptional\")", 1 + intOptional) + + assertSQL("(\"double\" + \"double\")", double + double) + assertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional) + assertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double) + assertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional) + assertSQL("(\"double\" + 1.0)", double + 1) + assertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1) + assertSQL("(1.0 + \"double\")", 1 + double) + assertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional) } func test_numberExpression_minusNumberExpression_buildsSubtractiveNumberExpression() { - AssertSQL("(\"int\" - \"int\")", int - int) - AssertSQL("(\"int\" - \"intOptional\")", int - intOptional) - AssertSQL("(\"intOptional\" - \"int\")", intOptional - int) - AssertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional) - AssertSQL("(\"int\" - 1)", int - 1) - AssertSQL("(\"intOptional\" - 1)", intOptional - 1) - AssertSQL("(1 - \"int\")", 1 - int) - AssertSQL("(1 - \"intOptional\")", 1 - intOptional) - - AssertSQL("(\"double\" - \"double\")", double - double) - AssertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional) - AssertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double) - AssertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional) - AssertSQL("(\"double\" - 1.0)", double - 1) - AssertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1) - AssertSQL("(1.0 - \"double\")", 1 - double) - AssertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional) + assertSQL("(\"int\" - \"int\")", int - int) + assertSQL("(\"int\" - \"intOptional\")", int - intOptional) + assertSQL("(\"intOptional\" - \"int\")", intOptional - int) + assertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional) + assertSQL("(\"int\" - 1)", int - 1) + assertSQL("(\"intOptional\" - 1)", intOptional - 1) + assertSQL("(1 - \"int\")", 1 - int) + assertSQL("(1 - \"intOptional\")", 1 - intOptional) + + assertSQL("(\"double\" - \"double\")", double - double) + assertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional) + assertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double) + assertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional) + assertSQL("(\"double\" - 1.0)", double - 1) + assertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1) + assertSQL("(1.0 - \"double\")", 1 - double) + assertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional) } func test_numberExpression_timesNumberExpression_buildsMultiplicativeNumberExpression() { - AssertSQL("(\"int\" * \"int\")", int * int) - AssertSQL("(\"int\" * \"intOptional\")", int * intOptional) - AssertSQL("(\"intOptional\" * \"int\")", intOptional * int) - AssertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional) - AssertSQL("(\"int\" * 1)", int * 1) - AssertSQL("(\"intOptional\" * 1)", intOptional * 1) - AssertSQL("(1 * \"int\")", 1 * int) - AssertSQL("(1 * \"intOptional\")", 1 * intOptional) - - AssertSQL("(\"double\" * \"double\")", double * double) - AssertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional) - AssertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double) - AssertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional) - AssertSQL("(\"double\" * 1.0)", double * 1) - AssertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1) - AssertSQL("(1.0 * \"double\")", 1 * double) - AssertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional) + assertSQL("(\"int\" * \"int\")", int * int) + assertSQL("(\"int\" * \"intOptional\")", int * intOptional) + assertSQL("(\"intOptional\" * \"int\")", intOptional * int) + assertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional) + assertSQL("(\"int\" * 1)", int * 1) + assertSQL("(\"intOptional\" * 1)", intOptional * 1) + assertSQL("(1 * \"int\")", 1 * int) + assertSQL("(1 * \"intOptional\")", 1 * intOptional) + + assertSQL("(\"double\" * \"double\")", double * double) + assertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional) + assertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double) + assertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional) + assertSQL("(\"double\" * 1.0)", double * 1) + assertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1) + assertSQL("(1.0 * \"double\")", 1 * double) + assertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional) } func test_numberExpression_dividedByNumberExpression_buildsDivisiveNumberExpression() { - AssertSQL("(\"int\" / \"int\")", int / int) - AssertSQL("(\"int\" / \"intOptional\")", int / intOptional) - AssertSQL("(\"intOptional\" / \"int\")", intOptional / int) - AssertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional) - AssertSQL("(\"int\" / 1)", int / 1) - AssertSQL("(\"intOptional\" / 1)", intOptional / 1) - AssertSQL("(1 / \"int\")", 1 / int) - AssertSQL("(1 / \"intOptional\")", 1 / intOptional) - - AssertSQL("(\"double\" / \"double\")", double / double) - AssertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional) - AssertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double) - AssertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional) - AssertSQL("(\"double\" / 1.0)", double / 1) - AssertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1) - AssertSQL("(1.0 / \"double\")", 1 / double) - AssertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional) + assertSQL("(\"int\" / \"int\")", int / int) + assertSQL("(\"int\" / \"intOptional\")", int / intOptional) + assertSQL("(\"intOptional\" / \"int\")", intOptional / int) + assertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional) + assertSQL("(\"int\" / 1)", int / 1) + assertSQL("(\"intOptional\" / 1)", intOptional / 1) + assertSQL("(1 / \"int\")", 1 / int) + assertSQL("(1 / \"intOptional\")", 1 / intOptional) + + assertSQL("(\"double\" / \"double\")", double / double) + assertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional) + assertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double) + assertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional) + assertSQL("(\"double\" / 1.0)", double / 1) + assertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1) + assertSQL("(1.0 / \"double\")", 1 / double) + assertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional) } func test_numberExpression_prefixedWithMinus_buildsInvertedNumberExpression() { - AssertSQL("-(\"int\")", -int) - AssertSQL("-(\"intOptional\")", -intOptional) + assertSQL("-(\"int\")", -int) + assertSQL("-(\"intOptional\")", -intOptional) - AssertSQL("-(\"double\")", -double) - AssertSQL("-(\"doubleOptional\")", -doubleOptional) + assertSQL("-(\"double\")", -double) + assertSQL("-(\"doubleOptional\")", -doubleOptional) } func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { - AssertSQL("(\"int\" % \"int\")", int % int) - AssertSQL("(\"int\" % \"intOptional\")", int % intOptional) - AssertSQL("(\"intOptional\" % \"int\")", intOptional % int) - AssertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional) - AssertSQL("(\"int\" % 1)", int % 1) - AssertSQL("(\"intOptional\" % 1)", intOptional % 1) - AssertSQL("(1 % \"int\")", 1 % int) - AssertSQL("(1 % \"intOptional\")", 1 % intOptional) + assertSQL("(\"int\" % \"int\")", int % int) + assertSQL("(\"int\" % \"intOptional\")", int % intOptional) + assertSQL("(\"intOptional\" % \"int\")", intOptional % int) + assertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional) + assertSQL("(\"int\" % 1)", int % 1) + assertSQL("(\"intOptional\" % 1)", intOptional % 1) + assertSQL("(1 % \"int\")", 1 % int) + assertSQL("(1 % \"intOptional\")", 1 % intOptional) } func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { - AssertSQL("(\"int\" << \"int\")", int << int) - AssertSQL("(\"int\" << \"intOptional\")", int << intOptional) - AssertSQL("(\"intOptional\" << \"int\")", intOptional << int) - AssertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional) - AssertSQL("(\"int\" << 1)", int << 1) - AssertSQL("(\"intOptional\" << 1)", intOptional << 1) - AssertSQL("(1 << \"int\")", 1 << int) - AssertSQL("(1 << \"intOptional\")", 1 << intOptional) + assertSQL("(\"int\" << \"int\")", int << int) + assertSQL("(\"int\" << \"intOptional\")", int << intOptional) + assertSQL("(\"intOptional\" << \"int\")", intOptional << int) + assertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional) + assertSQL("(\"int\" << 1)", int << 1) + assertSQL("(\"intOptional\" << 1)", intOptional << 1) + assertSQL("(1 << \"int\")", 1 << int) + assertSQL("(1 << \"intOptional\")", 1 << intOptional) } func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { - AssertSQL("(\"int\" >> \"int\")", int >> int) - AssertSQL("(\"int\" >> \"intOptional\")", int >> intOptional) - AssertSQL("(\"intOptional\" >> \"int\")", intOptional >> int) - AssertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional) - AssertSQL("(\"int\" >> 1)", int >> 1) - AssertSQL("(\"intOptional\" >> 1)", intOptional >> 1) - AssertSQL("(1 >> \"int\")", 1 >> int) - AssertSQL("(1 >> \"intOptional\")", 1 >> intOptional) + assertSQL("(\"int\" >> \"int\")", int >> int) + assertSQL("(\"int\" >> \"intOptional\")", int >> intOptional) + assertSQL("(\"intOptional\" >> \"int\")", intOptional >> int) + assertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional) + assertSQL("(\"int\" >> 1)", int >> 1) + assertSQL("(\"intOptional\" >> 1)", intOptional >> 1) + assertSQL("(1 >> \"int\")", 1 >> int) + assertSQL("(1 >> \"intOptional\")", 1 >> intOptional) } func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { - AssertSQL("(\"int\" & \"int\")", int & int) - AssertSQL("(\"int\" & \"intOptional\")", int & intOptional) - AssertSQL("(\"intOptional\" & \"int\")", intOptional & int) - AssertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional) - AssertSQL("(\"int\" & 1)", int & 1) - AssertSQL("(\"intOptional\" & 1)", intOptional & 1) - AssertSQL("(1 & \"int\")", 1 & int) - AssertSQL("(1 & \"intOptional\")", 1 & intOptional) + assertSQL("(\"int\" & \"int\")", int & int) + assertSQL("(\"int\" & \"intOptional\")", int & intOptional) + assertSQL("(\"intOptional\" & \"int\")", intOptional & int) + assertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional) + assertSQL("(\"int\" & 1)", int & 1) + assertSQL("(\"intOptional\" & 1)", intOptional & 1) + assertSQL("(1 & \"int\")", 1 & int) + assertSQL("(1 & \"intOptional\")", 1 & intOptional) } func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQL("(\"int\" | \"int\")", int | int) - AssertSQL("(\"int\" | \"intOptional\")", int | intOptional) - AssertSQL("(\"intOptional\" | \"int\")", intOptional | int) - AssertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional) - AssertSQL("(\"int\" | 1)", int | 1) - AssertSQL("(\"intOptional\" | 1)", intOptional | 1) - AssertSQL("(1 | \"int\")", 1 | int) - AssertSQL("(1 | \"intOptional\")", 1 | intOptional) + assertSQL("(\"int\" | \"int\")", int | int) + assertSQL("(\"int\" | \"intOptional\")", int | intOptional) + assertSQL("(\"intOptional\" | \"int\")", intOptional | int) + assertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional) + assertSQL("(\"int\" | 1)", int | 1) + assertSQL("(\"intOptional\" | 1)", intOptional | 1) + assertSQL("(1 | \"int\")", 1 | int) + assertSQL("(1 | \"intOptional\")", 1 | intOptional) } func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int) - AssertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional) - AssertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int) - AssertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional) - AssertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1) - AssertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1) - AssertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int) - AssertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional) + assertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int) + assertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional) + assertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int) + assertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional) + assertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1) + assertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1) + assertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int) + assertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional) } func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { - AssertSQL("~(\"int\")", ~int) - AssertSQL("~(\"intOptional\")", ~intOptional) + assertSQL("~(\"int\")", ~int) + assertSQL("~(\"intOptional\")", ~intOptional) } func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" = \"bool\")", bool == bool) - AssertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional) - AssertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool) - AssertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional) - AssertSQL("(\"bool\" = 1)", bool == true) - AssertSQL("(\"boolOptional\" = 1)", boolOptional == true) - AssertSQL("(1 = \"bool\")", true == bool) - AssertSQL("(1 = \"boolOptional\")", true == boolOptional) + assertSQL("(\"bool\" = \"bool\")", bool == bool) + assertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional) + assertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool) + assertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional) + assertSQL("(\"bool\" = 1)", bool == true) + assertSQL("(\"boolOptional\" = 1)", boolOptional == true) + assertSQL("(1 = \"bool\")", true == bool) + assertSQL("(1 = \"boolOptional\")", true == boolOptional) - AssertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil) - AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) + assertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil) + assertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) } func test_isOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" IS \"bool\")", bool === bool) - AssertSQL("(\"bool\" IS \"boolOptional\")", bool === boolOptional) - AssertSQL("(\"boolOptional\" IS \"bool\")", boolOptional === bool) - AssertSQL("(\"boolOptional\" IS \"boolOptional\")", boolOptional === boolOptional) - AssertSQL("(\"bool\" IS 1)", bool === true) - AssertSQL("(\"boolOptional\" IS 1)", boolOptional === true) - AssertSQL("(1 IS \"bool\")", true === bool) - AssertSQL("(1 IS \"boolOptional\")", true === boolOptional) - - AssertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) - AssertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) - } - + assertSQL("(\"bool\" IS \"bool\")", bool === bool) + assertSQL("(\"bool\" IS \"boolOptional\")", bool === boolOptional) + assertSQL("(\"boolOptional\" IS \"bool\")", boolOptional === bool) + assertSQL("(\"boolOptional\" IS \"boolOptional\")", boolOptional === boolOptional) + assertSQL("(\"bool\" IS 1)", bool === true) + assertSQL("(\"boolOptional\" IS 1)", boolOptional === true) + assertSQL("(1 IS \"bool\")", true === bool) + assertSQL("(1 IS \"boolOptional\")", true === boolOptional) + + assertSQL("(\"boolOptional\" IS NULL)", boolOptional === nil) + assertSQL("(NULL IS \"boolOptional\")", nil === boolOptional) + } + func test_isNotOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" IS NOT \"bool\")", bool !== bool) - AssertSQL("(\"bool\" IS NOT \"boolOptional\")", bool !== boolOptional) - AssertSQL("(\"boolOptional\" IS NOT \"bool\")", boolOptional !== bool) - AssertSQL("(\"boolOptional\" IS NOT \"boolOptional\")", boolOptional !== boolOptional) - AssertSQL("(\"bool\" IS NOT 1)", bool !== true) - AssertSQL("(\"boolOptional\" IS NOT 1)", boolOptional !== true) - AssertSQL("(1 IS NOT \"bool\")", true !== bool) - AssertSQL("(1 IS NOT \"boolOptional\")", true !== boolOptional) - - AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional !== nil) - AssertSQL("(NULL IS NOT \"boolOptional\")", nil !== boolOptional) + assertSQL("(\"bool\" IS NOT \"bool\")", bool !== bool) + assertSQL("(\"bool\" IS NOT \"boolOptional\")", bool !== boolOptional) + assertSQL("(\"boolOptional\" IS NOT \"bool\")", boolOptional !== bool) + assertSQL("(\"boolOptional\" IS NOT \"boolOptional\")", boolOptional !== boolOptional) + assertSQL("(\"bool\" IS NOT 1)", bool !== true) + assertSQL("(\"boolOptional\" IS NOT 1)", boolOptional !== true) + assertSQL("(1 IS NOT \"bool\")", true !== bool) + assertSQL("(1 IS NOT \"boolOptional\")", true !== boolOptional) + + assertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional !== nil) + assertSQL("(NULL IS NOT \"boolOptional\")", nil !== boolOptional) } func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" != \"bool\")", bool != bool) - AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) - AssertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool) - AssertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional) - AssertSQL("(\"bool\" != 1)", bool != true) - AssertSQL("(\"boolOptional\" != 1)", boolOptional != true) - AssertSQL("(1 != \"bool\")", true != bool) - AssertSQL("(1 != \"boolOptional\")", true != boolOptional) + assertSQL("(\"bool\" != \"bool\")", bool != bool) + assertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) + assertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool) + assertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional) + assertSQL("(\"bool\" != 1)", bool != true) + assertSQL("(\"boolOptional\" != 1)", boolOptional != true) + assertSQL("(1 != \"bool\")", true != bool) + assertSQL("(1 != \"boolOptional\")", true != boolOptional) - AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil) - AssertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional) + assertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil) + assertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional) } func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" > \"bool\")", bool > bool) - AssertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional) - AssertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool) - AssertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional) - AssertSQL("(\"bool\" > 1)", bool > true) - AssertSQL("(\"boolOptional\" > 1)", boolOptional > true) - AssertSQL("(1 > \"bool\")", true > bool) - AssertSQL("(1 > \"boolOptional\")", true > boolOptional) + assertSQL("(\"bool\" > \"bool\")", bool > bool) + assertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional) + assertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool) + assertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional) + assertSQL("(\"bool\" > 1)", bool > true) + assertSQL("(\"boolOptional\" > 1)", boolOptional > true) + assertSQL("(1 > \"bool\")", true > bool) + assertSQL("(1 > \"boolOptional\")", true > boolOptional) } func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" >= \"bool\")", bool >= bool) - AssertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional) - AssertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool) - AssertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional) - AssertSQL("(\"bool\" >= 1)", bool >= true) - AssertSQL("(\"boolOptional\" >= 1)", boolOptional >= true) - AssertSQL("(1 >= \"bool\")", true >= bool) - AssertSQL("(1 >= \"boolOptional\")", true >= boolOptional) + assertSQL("(\"bool\" >= \"bool\")", bool >= bool) + assertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional) + assertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool) + assertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional) + assertSQL("(\"bool\" >= 1)", bool >= true) + assertSQL("(\"boolOptional\" >= 1)", boolOptional >= true) + assertSQL("(1 >= \"bool\")", true >= bool) + assertSQL("(1 >= \"boolOptional\")", true >= boolOptional) } func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" < \"bool\")", bool < bool) - AssertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional) - AssertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool) - AssertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional) - AssertSQL("(\"bool\" < 1)", bool < true) - AssertSQL("(\"boolOptional\" < 1)", boolOptional < true) - AssertSQL("(1 < \"bool\")", true < bool) - AssertSQL("(1 < \"boolOptional\")", true < boolOptional) + assertSQL("(\"bool\" < \"bool\")", bool < bool) + assertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional) + assertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool) + assertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional) + assertSQL("(\"bool\" < 1)", bool < true) + assertSQL("(\"boolOptional\" < 1)", boolOptional < true) + assertSQL("(1 < \"bool\")", true < bool) + assertSQL("(1 < \"boolOptional\")", true < boolOptional) } func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQL("(\"bool\" <= \"bool\")", bool <= bool) - AssertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional) - AssertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool) - AssertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional) - AssertSQL("(\"bool\" <= 1)", bool <= true) - AssertSQL("(\"boolOptional\" <= 1)", boolOptional <= true) - AssertSQL("(1 <= \"bool\")", true <= bool) - AssertSQL("(1 <= \"boolOptional\")", true <= boolOptional) + assertSQL("(\"bool\" <= \"bool\")", bool <= bool) + assertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional) + assertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool) + assertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional) + assertSQL("(\"bool\" <= 1)", bool <= true) + assertSQL("(\"boolOptional\" <= 1)", boolOptional <= true) + assertSQL("(1 <= \"bool\")", true <= bool) + assertSQL("(1 <= \"boolOptional\")", true <= boolOptional) } func test_patternMatchingOperator_withComparableCountableClosedRange_buildsBetweenBooleanExpression() { - AssertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) - AssertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) + assertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) + assertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) } func test_patternMatchingOperator_withComparableClosedRange_buildsBetweenBooleanExpression() { - AssertSQL("\"double\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= double) - AssertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) + assertSQL("\"double\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= double) + assertSQL("\"doubleOptional\" BETWEEN 1.2 AND 4.5", 1.2...4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparableRange_buildsBooleanExpression() { - AssertSQL("\"double\" >= 1.2 AND \"double\" < 4.5", 1.2..<4.5 ~= double) - AssertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) + assertSQL("\"double\" >= 1.2 AND \"double\" < 4.5", 1.2..<4.5 ~= double) + assertSQL("\"doubleOptional\" >= 1.2 AND \"doubleOptional\" < 4.5", 1.2..<4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparablePartialRangeThrough_buildsBooleanExpression() { - AssertSQL("\"double\" <= 4.5", ...4.5 ~= double) - AssertSQL("\"doubleOptional\" <= 4.5", ...4.5 ~= doubleOptional) + assertSQL("\"double\" <= 4.5", ...4.5 ~= double) + assertSQL("\"doubleOptional\" <= 4.5", ...4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparablePartialRangeUpTo_buildsBooleanExpression() { - AssertSQL("\"double\" < 4.5", ..<4.5 ~= double) - AssertSQL("\"doubleOptional\" < 4.5", ..<4.5 ~= doubleOptional) + assertSQL("\"double\" < 4.5", ..<4.5 ~= double) + assertSQL("\"doubleOptional\" < 4.5", ..<4.5 ~= doubleOptional) } func test_patternMatchingOperator_withComparablePartialRangeFrom_buildsBooleanExpression() { - AssertSQL("\"double\" >= 4.5", 4.5... ~= double) - AssertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) + assertSQL("\"double\" >= 4.5", 4.5... ~= double) + assertSQL("\"doubleOptional\" >= 4.5", 4.5... ~= doubleOptional) } func test_patternMatchingOperator_withComparableClosedRangeString_buildsBetweenBooleanExpression() { - AssertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) - AssertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) + assertSQL("\"string\" BETWEEN 'a' AND 'b'", "a"..."b" ~= string) + assertSQL("\"stringOptional\" BETWEEN 'a' AND 'b'", "a"..."b" ~= stringOptional) } func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" AND \"bool\")", bool && bool) - AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) - AssertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool) - AssertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional) - AssertSQL("(\"bool\" AND 1)", bool && true) - AssertSQL("(\"boolOptional\" AND 1)", boolOptional && true) - AssertSQL("(1 AND \"bool\")", true && bool) - AssertSQL("(1 AND \"boolOptional\")", true && boolOptional) - } - + assertSQL("(\"bool\" AND \"bool\")", bool && bool) + assertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) + assertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool) + assertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional) + assertSQL("(\"bool\" AND 1)", bool && true) + assertSQL("(\"boolOptional\" AND 1)", boolOptional && true) + assertSQL("(1 AND \"bool\")", true && bool) + assertSQL("(1 AND \"boolOptional\")", true && boolOptional) + } + func test_andFunction_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and([bool, bool, bool])) - AssertSQL("(\"bool\" AND \"bool\")", and([bool, bool])) - AssertSQL("(\"bool\")", and([bool])) - - AssertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and(bool, bool, bool)) - AssertSQL("(\"bool\" AND \"bool\")", and(bool, bool)) - AssertSQL("(\"bool\")", and(bool)) + assertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and([bool, bool, bool])) + assertSQL("(\"bool\" AND \"bool\")", and([bool, bool])) + assertSQL("(\"bool\")", and([bool])) + + assertSQL("(\"bool\" AND \"bool\" AND \"bool\")", and(bool, bool, bool)) + assertSQL("(\"bool\" AND \"bool\")", and(bool, bool)) + assertSQL("(\"bool\")", and(bool)) } func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" OR \"bool\")", bool || bool) - AssertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional) - AssertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool) - AssertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional) - AssertSQL("(\"bool\" OR 1)", bool || true) - AssertSQL("(\"boolOptional\" OR 1)", boolOptional || true) - AssertSQL("(1 OR \"bool\")", true || bool) - AssertSQL("(1 OR \"boolOptional\")", true || boolOptional) - } - + assertSQL("(\"bool\" OR \"bool\")", bool || bool) + assertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional) + assertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool) + assertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional) + assertSQL("(\"bool\" OR 1)", bool || true) + assertSQL("(\"boolOptional\" OR 1)", boolOptional || true) + assertSQL("(1 OR \"bool\")", true || bool) + assertSQL("(1 OR \"boolOptional\")", true || boolOptional) + } + func test_orFunction_withBooleanExpressions_buildsCompoundExpression() { - AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or([bool, bool, bool])) - AssertSQL("(\"bool\" OR \"bool\")", or([bool, bool])) - AssertSQL("(\"bool\")", or([bool])) - - AssertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or(bool, bool, bool)) - AssertSQL("(\"bool\" OR \"bool\")", or(bool, bool)) - AssertSQL("(\"bool\")", or(bool)) + assertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or([bool, bool, bool])) + assertSQL("(\"bool\" OR \"bool\")", or([bool, bool])) + assertSQL("(\"bool\")", or([bool])) + + assertSQL("(\"bool\" OR \"bool\" OR \"bool\")", or(bool, bool, bool)) + assertSQL("(\"bool\" OR \"bool\")", or(bool, bool)) + assertSQL("(\"bool\")", or(bool)) } func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { - AssertSQL("NOT (\"bool\")", !bool) - AssertSQL("NOT (\"boolOptional\")", !boolOptional) + assertSQL("NOT (\"bool\")", !bool) + assertSQL("NOT (\"boolOptional\")", !boolOptional) } func test_precedencePreserved() { let n = Expression(value: 1) - AssertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) - AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) + assertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) + assertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } func test_dateExpressionLessGreater() { let begin = Date(timeIntervalSince1970: 0) - AssertSQL("(\"date\" < '1970-01-01T00:00:00.000')", date < begin) - AssertSQL("(\"date\" > '1970-01-01T00:00:00.000')", date > begin) - AssertSQL("(\"date\" >= '1970-01-01T00:00:00.000')", date >= begin) - AssertSQL("(\"date\" <= '1970-01-01T00:00:00.000')", date <= begin) + assertSQL("(\"date\" < '1970-01-01T00:00:00.000')", date < begin) + assertSQL("(\"date\" > '1970-01-01T00:00:00.000')", date > begin) + assertSQL("(\"date\" >= '1970-01-01T00:00:00.000')", date >= begin) + assertSQL("(\"date\" <= '1970-01-01T00:00:00.000')", date <= begin) } func test_dateExpressionRange() { let begin = Date(timeIntervalSince1970: 0) let end = Date(timeIntervalSince1970: 5000) - AssertSQL( + assertSQL( "\"date\" >= '1970-01-01T00:00:00.000' AND \"date\" < '1970-01-01T01:23:20.000'", (begin..("id") @@ -28,65 +28,65 @@ class QueryTests : XCTestCase { let tag = Expression("tag") func test_select_withExpression_compilesSelectClause() { - AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) + assertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) } func test_select_withStarExpression_compilesSelectClause() { - AssertSQL("SELECT * FROM \"users\"", users.select(*)) + assertSQL("SELECT * FROM \"users\"", users.select(*)) } func test_select_withNamespacedStarExpression_compilesSelectClause() { - AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) + assertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) } func test_select_withVariadicExpressions_compilesSelectClause() { - AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) + assertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) } func test_select_withExpressions_compilesSelectClause() { - AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select([email, count(*)])) + assertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select([email, count(*)])) } func test_selectDistinct_withExpression_compilesSelectClause() { - AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) + assertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) } func test_selectDistinct_withExpressions_compilesSelectClause() { - AssertSQL("SELECT DISTINCT \"age\", \"admin\" FROM \"users\"", users.select(distinct: [age, admin])) + assertSQL("SELECT DISTINCT \"age\", \"admin\" FROM \"users\"", users.select(distinct: [age, admin])) } func test_selectDistinct_withStar_compilesSelectClause() { - AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) + assertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } func test_join_compilesJoinClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", users.join(posts, on: posts[userId] == users[id]) ) } func test_join_withExplicitType_compilesJoinClauseWithType() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" LEFT OUTER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", users.join(.leftOuter, posts, on: posts[userId] == users[id]) ) - AssertSQL( + assertSQL( "SELECT * FROM \"users\" CROSS JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", users.join(.cross, posts, on: posts[userId] == users[id]) ) } func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" INNER JOIN \"posts\" ON ((\"posts\".\"user_id\" = \"users\".\"id\") AND \"published\")", users.join(posts.filter(published), on: posts[userId] == users[id]) ) } func test_join_whenChained_compilesAggregateJoinClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" " + "INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\") " + "INNER JOIN \"categories\" ON (\"categories\".\"id\" = \"posts\".\"category_id\")", @@ -95,79 +95,79 @@ class QueryTests : XCTestCase { } func test_filter_compilesWhereClause() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) } func test_filter_compilesWhereClause_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(admin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(admin == false)) } func test_filter_compilesWhereClause_optional() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(optionalAdmin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(optionalAdmin == true)) } func test_filter_compilesWhereClause_optional_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(optionalAdmin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.filter(optionalAdmin == false)) } func test_where_compilesWhereClause() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(admin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(admin == true)) } func test_where_compilesWhereClause_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(admin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(admin == false)) } func test_where_compilesWhereClause_optional() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(optionalAdmin == true)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.where(optionalAdmin == true)) } func test_where_compilesWhereClause_optional_false() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(optionalAdmin == false)) + assertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 0)", users.where(optionalAdmin == false)) } func test_filter_whenChained_compilesAggregateWhereClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" WHERE ((\"age\" >= 35) AND \"admin\")", users.filter(age >= 35).filter(admin) ) } func test_group_withSingleExpressionName_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", users.group(age)) } func test_group_withVariadicExpressionNames_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) } func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING \"admin\"", users.group(age, having: admin)) - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING \"admin\"", users.group(age, having: admin)) + assertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) } func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { - AssertSQL( + assertSQL( "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING \"admin\"", users.group([age, admin], having: admin) ) - AssertSQL( + assertSQL( "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", users.group([age, admin], having: age >= 30) ) } func test_order_withSingleExpressionName_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) } func test_order_withVariadicExpressionNames_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) } func test_order_withArrayExpressionNames_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order([age, email])) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order([age, email])) } func test_order_withExpressionAndSortDirection_compilesOrderClause() { @@ -175,7 +175,7 @@ class QueryTests : XCTestCase { } func test_order_whenChained_resetsOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) + assertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) } func test_reverse_withoutOrder_ordersByRowIdDescending() { @@ -187,25 +187,25 @@ class QueryTests : XCTestCase { } func test_limit_compilesLimitClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) + assertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) } func test_limit_withOffset_compilesOffsetClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) + assertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) } func test_limit_whenChained_overridesLimit() { let query = users.limit(5) - AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + assertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) + assertSQL("SELECT * FROM \"users\"", query.limit(nil)) } func test_limit_whenChained_withOffset_overridesOffset() { let query = users.limit(5, offset: 5) - AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + assertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20)) + assertSQL("SELECT * FROM \"users\"", query.limit(nil)) } func test_alias_aliasesTable() { @@ -213,7 +213,7 @@ class QueryTests : XCTestCase { let managers = users.alias("managers") - AssertSQL( + assertSQL( "SELECT * FROM \"users\" " + "INNER JOIN \"users\" AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")", users.join(managers, on: managers[id] == users[managerId]) @@ -221,78 +221,99 @@ class QueryTests : XCTestCase { } func test_insert_compilesInsertExpression() { - AssertSQL( + assertSQL( "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", users.insert(email <- "alice@example.com", age <- 30) ) } func test_insert_withOnConflict_compilesInsertOrOnConflictExpression() { - AssertSQL( + assertSQL( "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", users.insert(or: .replace, email <- "alice@example.com", age <- 30) ) } func test_insert_compilesInsertExpressionWithDefaultValues() { - AssertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert()) + assertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert()) } func test_insert_withQuery_compilesInsertExpressionWithSelectStatement() { let emails = Table("emails") - AssertSQL( + assertSQL( "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE \"admin\"", emails.insert(users.select(email).filter(admin)) ) } func test_insert_many_compilesInsertManyExpression() { - AssertSQL( - "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", - users.insertMany([[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + assertSQL( + """ + INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), + ('alex@example.com', 83) + """.replacingOccurrences(of: "\n", with: ""), + users.insertMany([[email <- "alice@example.com", age <- 30], + [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) ) } func test_insert_many_compilesInsertManyNoneExpression() { - AssertSQL( + assertSQL( "INSERT INTO \"users\" DEFAULT VALUES", users.insertMany([]) ) } func test_insert_many_withOnConflict_compilesInsertManyOrOnConflictExpression() { - AssertSQL( - "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), ('geoff@example.com', 32), ('alex@example.com', 83)", - users.insertMany(or: .replace, [[email <- "alice@example.com", age <- 30], [email <- "geoff@example.com", age <- 32], [email <- "alex@example.com", age <- 83]]) + assertSQL( + """ + INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30), + ('geoff@example.com', 32), ('alex@example.com', 83) + """.replacingOccurrences(of: "\n", with: ""), + users.insertMany(or: .replace, [[email <- "alice@example.com", age <- 30], + [email <- "geoff@example.com", age <- 32], + [email <- "alex@example.com", age <- 83]]) ) } func test_insert_encodable() throws { let emails = Table("emails") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.insert(value) - AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000')", + assertSQL( + """ + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: "optional", sub: value1) let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'optional', '\(encodedJSONString)')", + assertSQL( + """ + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"optional\", + \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'optional', '\(encodedJSONString)') + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { - AssertSQL( - "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30) ON CONFLICT (\"email\") DO UPDATE SET \"age\" = \"excluded\".\"age\"", + assertSQL( + """ + INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30) ON CONFLICT (\"email\") + DO UPDATE SET \"age\" = \"excluded\".\"age\" + """.replacingOccurrences(of: "\n", with: ""), users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email) ) } @@ -300,35 +321,48 @@ class QueryTests : XCTestCase { func test_upsert_encodable() throws { let emails = Table("emails") let string = Expression("string") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) - AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\"", + assertSQL( + """ + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") + DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", + \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\" + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_insert_many_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) - let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) - let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) - AssertSQL( - "INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000'), (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000')", + assertSQL( + """ + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000'), + (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000') + """.replacingOccurrences(of: "\n", with: ""), insert ) } func test_update_compilesUpdateExpression() { - AssertSQL( + assertSQL( "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", users.filter(id == 1).update(age <- 30, admin <- true) ) } func test_update_compilesUpdateLimitOrderExpression() { - AssertSQL( + assertSQL( "UPDATE \"users\" SET \"age\" = 30 ORDER BY \"id\" LIMIT 1", users.order(id).limit(1).update(age <- 30) ) @@ -336,95 +370,104 @@ class QueryTests : XCTestCase { func test_update_encodable() throws { let emails = Table("emails") - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) let update = try emails.update(value) - AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000'", + assertSQL( + """ + UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, + \"date\" = '1970-01-01T00:00:00.000' + """.replacingOccurrences(of: "\n", with: ""), update ) } func test_update_encodable_with_nested_encodable() throws { let emails = Table("emails") - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) - let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) let update = try emails.update(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - AssertSQL( - "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)'", + assertSQL( + """ + UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, + \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)' + """.replacingOccurrences(of: "\n", with: ""), update ) } func test_delete_compilesDeleteExpression() { - AssertSQL( + assertSQL( "DELETE FROM \"users\" WHERE (\"id\" = 1)", users.filter(id == 1).delete() ) } func test_delete_compilesDeleteLimitOrderExpression() { - AssertSQL( + assertSQL( "DELETE FROM \"users\" ORDER BY \"id\" LIMIT 1", users.order(id).limit(1).delete() ) } func test_delete_compilesExistsExpression() { - AssertSQL( + assertSQL( "SELECT EXISTS (SELECT * FROM \"users\")", users.exists ) } func test_count_returnsCountExpression() { - AssertSQL("SELECT count(*) FROM \"users\"", users.count) + assertSQL("SELECT count(*) FROM \"users\"", users.count) } func test_scalar_returnsScalarExpression() { - AssertSQL("SELECT \"int\" FROM \"table\"", table.select(int) as ScalarQuery) - AssertSQL("SELECT \"intOptional\" FROM \"table\"", table.select(intOptional) as ScalarQuery) - AssertSQL("SELECT DISTINCT \"int\" FROM \"table\"", table.select(distinct: int) as ScalarQuery) - AssertSQL("SELECT DISTINCT \"intOptional\" FROM \"table\"", table.select(distinct: intOptional) as ScalarQuery) + assertSQL("SELECT \"int\" FROM \"table\"", table.select(int) as ScalarQuery) + assertSQL("SELECT \"intOptional\" FROM \"table\"", table.select(intOptional) as ScalarQuery) + assertSQL("SELECT DISTINCT \"int\" FROM \"table\"", table.select(distinct: int) as ScalarQuery) + assertSQL("SELECT DISTINCT \"intOptional\" FROM \"table\"", table.select(distinct: intOptional) as ScalarQuery) } func test_subscript_withExpression_returnsNamespacedExpression() { let query = Table("query") - AssertSQL("\"query\".\"blob\"", query[data]) - AssertSQL("\"query\".\"blobOptional\"", query[dataOptional]) + assertSQL("\"query\".\"blob\"", query[data]) + assertSQL("\"query\".\"blobOptional\"", query[dataOptional]) - AssertSQL("\"query\".\"bool\"", query[bool]) - AssertSQL("\"query\".\"boolOptional\"", query[boolOptional]) + assertSQL("\"query\".\"bool\"", query[bool]) + assertSQL("\"query\".\"boolOptional\"", query[boolOptional]) - AssertSQL("\"query\".\"date\"", query[date]) - AssertSQL("\"query\".\"dateOptional\"", query[dateOptional]) + assertSQL("\"query\".\"date\"", query[date]) + assertSQL("\"query\".\"dateOptional\"", query[dateOptional]) - AssertSQL("\"query\".\"double\"", query[double]) - AssertSQL("\"query\".\"doubleOptional\"", query[doubleOptional]) + assertSQL("\"query\".\"double\"", query[double]) + assertSQL("\"query\".\"doubleOptional\"", query[doubleOptional]) - AssertSQL("\"query\".\"int\"", query[int]) - AssertSQL("\"query\".\"intOptional\"", query[intOptional]) + assertSQL("\"query\".\"int\"", query[int]) + assertSQL("\"query\".\"intOptional\"", query[intOptional]) - AssertSQL("\"query\".\"int64\"", query[int64]) - AssertSQL("\"query\".\"int64Optional\"", query[int64Optional]) + assertSQL("\"query\".\"int64\"", query[int64]) + assertSQL("\"query\".\"int64Optional\"", query[int64Optional]) - AssertSQL("\"query\".\"string\"", query[string]) - AssertSQL("\"query\".\"stringOptional\"", query[stringOptional]) + assertSQL("\"query\".\"string\"", query[string]) + assertSQL("\"query\".\"stringOptional\"", query[stringOptional]) - AssertSQL("\"query\".*", query[*]) + assertSQL("\"query\".*", query[*]) } func test_tableNamespacedByDatabase() { let table = Table("table", database: "attached") - AssertSQL("SELECT * FROM \"attached\".\"table\"", table) + assertSQL("SELECT * FROM \"attached\".\"table\"", table) } } -class QueryIntegrationTests : SQLiteTestCase { +class QueryIntegrationTests: SQLiteTestCase { let id = Expression("id") let email = Expression("email") @@ -433,7 +476,7 @@ class QueryIntegrationTests : SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() + createUsersTable() } // MARK: - @@ -452,7 +495,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_prepareRowIterator() { let names = ["a", "b", "c"] - try! InsertUsers(names) + try! insertUsers(names) let emailColumn = Expression("email") let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } @@ -462,7 +505,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_ambiguousMap() { let names = ["a", "b", "c"] - try! InsertUsers(names) + try! insertUsers(names) let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } @@ -494,8 +537,10 @@ class QueryIntegrationTests : SQLiteTestCase { builder.column(Expression("sub")) }) - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) - let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, + date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) try db.run(table.insert(value)) @@ -523,7 +568,7 @@ class QueryIntegrationTests : SQLiteTestCase { XCTAssertEqual(0, try! db.scalar(users.count)) XCTAssertEqual(false, try! db.scalar(users.exists)) - try! InsertUsers("alice") + try! insertUsers("alice") XCTAssertEqual(1, try! db.scalar(users.select(id.average))) } @@ -590,7 +635,7 @@ class QueryIntegrationTests : SQLiteTestCase { func test_no_such_column() throws { let doesNotExist = Expression("doesNotExist") - try! InsertUser("alice") + try! insertUser("alice") let row = try! db.pluck(users.filter(email == "alice@example.com"))! XCTAssertThrowsError(try row.get(doesNotExist)) { error in diff --git a/Tests/SQLiteTests/RTreeTests.swift b/Tests/SQLiteTests/RTreeTests.swift index 7147533e..5525da26 100644 --- a/Tests/SQLiteTests/RTreeTests.swift +++ b/Tests/SQLiteTests/RTreeTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class RTreeTests : XCTestCase { +class RTreeTests: XCTestCase { func test_create_onVirtualTable_withRTree_createVirtualTableExpression() { XCTAssertEqual( @@ -14,4 +14,4 @@ class RTreeTests : XCTestCase { ) } -} \ No newline at end of file +} diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift index 17873e71..36721f80 100644 --- a/Tests/SQLiteTests/RowTests.swift +++ b/Tests/SQLiteTests/RowTests.swift @@ -1,7 +1,7 @@ import XCTest @testable import SQLite -class RowTests : XCTestCase { +class RowTests: XCTestCase { public func test_get_value() { let row = Row(["\"foo\"": 0], ["value"]) diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 30646b98..495a5e51 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -class SchemaTests : XCTestCase { +class SchemaTests: XCTestCase { func test_drop_compilesDropTableExpression() { XCTAssertEqual("DROP TABLE \"table\"", table.drop()) @@ -330,7 +330,10 @@ class SchemaTests : XCTestCase { table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + """ + CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES + \"table\" (\"int64\")) + """.replacingOccurrences(of: "\n", with: ""), table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) } ) @@ -487,48 +490,95 @@ class SchemaTests : XCTestCase { table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, + unique: true, + check: string != "", + defaultValue: stringOptional, + collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') + DEFAULT (\"string\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", + defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", + defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"string\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: string != "", + defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT UNIQUE CHECK (\"stringOptional\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", + defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') + DEFAULT (\"string\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: string != "", + defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') + DEFAULT (\"string\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: stringOptional != "", + defaultValue: string, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: string != "", + defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') + DEFAULT (\"stringOptional\") COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: stringOptional != "", + defaultValue: stringOptional, collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"string\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: string != "", + defaultValue: "string", collate: .rtrim) } ) XCTAssertEqual( - "CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", - table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .rtrim) } + """ + CREATE TABLE \"table\" (\"stringOptional\" TEXT CHECK (\"stringOptional\" != '') + DEFAULT ('string') COLLATE RTRIM) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(stringOptional, check: stringOptional != "", + defaultValue: "string", collate: .rtrim) } ) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index bca01092..c66d401d 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -2,14 +2,14 @@ import XCTest @testable import SQLite class SelectTests: SQLiteTestCase { - + override func setUp() { super.setUp() - CreateUsersTable() - CreateUsersDataTable() + createUsersTable() + createUsersDataTable() } - - func CreateUsersDataTable() { + + func createUsersDataTable() { try! db.execute(""" CREATE TABLE users_name ( id INTEGER, @@ -19,27 +19,26 @@ class SelectTests: SQLiteTestCase { """ ) } - + func test_select_columns_from_multiple_tables() { let usersData = Table("users_name") let users = Table("users") - + let name = Expression("name") let id = Expression("id") let userID = Expression("user_id") let email = Expression("email") - - try! InsertUser("Joey") + + try! insertUser("Joey") try! db.run(usersData.insert( id <- 1, userID <- 1, name <- "Joey" )) - + try! db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { XCTAssertEqual($0[name], "Joey") XCTAssertEqual($0[email], "Joey@example.com") } } - } diff --git a/Tests/SQLiteTests/SetterTests.swift b/Tests/SQLiteTests/SetterTests.swift index d4f189d7..938dd013 100644 --- a/Tests/SQLiteTests/SetterTests.swift +++ b/Tests/SQLiteTests/SetterTests.swift @@ -1,137 +1,137 @@ import XCTest import SQLite -class SetterTests : XCTestCase { +class SetterTests: XCTestCase { func test_setterAssignmentOperator_buildsSetter() { - AssertSQL("\"int\" = \"int\"", int <- int) - AssertSQL("\"int\" = 1", int <- 1) - AssertSQL("\"intOptional\" = \"int\"", intOptional <- int) - AssertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional) - AssertSQL("\"intOptional\" = 1", intOptional <- 1) - AssertSQL("\"intOptional\" = NULL", intOptional <- nil) + assertSQL("\"int\" = \"int\"", int <- int) + assertSQL("\"int\" = 1", int <- 1) + assertSQL("\"intOptional\" = \"int\"", intOptional <- int) + assertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional) + assertSQL("\"intOptional\" = 1", intOptional <- 1) + assertSQL("\"intOptional\" = NULL", intOptional <- nil) } func test_plusEquals_withStringExpression_buildsSetter() { - AssertSQL("\"string\" = (\"string\" || \"string\")", string += string) - AssertSQL("\"string\" = (\"string\" || 'literal')", string += "literal") - AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string) - AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional) - AssertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal") + assertSQL("\"string\" = (\"string\" || \"string\")", string += string) + assertSQL("\"string\" = (\"string\" || 'literal')", string += "literal") + assertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string) + assertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional) + assertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal") } func test_plusEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" + \"int\")", int += int) - AssertSQL("\"int\" = (\"int\" + 1)", int += 1) - AssertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int) - AssertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1) - - AssertSQL("\"double\" = (\"double\" + \"double\")", double += double) - AssertSQL("\"double\" = (\"double\" + 1.0)", double += 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1) + assertSQL("\"int\" = (\"int\" + \"int\")", int += int) + assertSQL("\"int\" = (\"int\" + 1)", int += 1) + assertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int) + assertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1) + + assertSQL("\"double\" = (\"double\" + \"double\")", double += double) + assertSQL("\"double\" = (\"double\" + 1.0)", double += 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1) } func test_minusEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" - \"int\")", int -= int) - AssertSQL("\"int\" = (\"int\" - 1)", int -= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int) - AssertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1) - - AssertSQL("\"double\" = (\"double\" - \"double\")", double -= double) - AssertSQL("\"double\" = (\"double\" - 1.0)", double -= 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1) + assertSQL("\"int\" = (\"int\" - \"int\")", int -= int) + assertSQL("\"int\" = (\"int\" - 1)", int -= 1) + assertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int) + assertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1) + + assertSQL("\"double\" = (\"double\" - \"double\")", double -= double) + assertSQL("\"double\" = (\"double\" - 1.0)", double -= 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1) } func test_timesEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" * \"int\")", int *= int) - AssertSQL("\"int\" = (\"int\" * 1)", int *= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int) - AssertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1) - - AssertSQL("\"double\" = (\"double\" * \"double\")", double *= double) - AssertSQL("\"double\" = (\"double\" * 1.0)", double *= 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1) + assertSQL("\"int\" = (\"int\" * \"int\")", int *= int) + assertSQL("\"int\" = (\"int\" * 1)", int *= 1) + assertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int) + assertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1) + + assertSQL("\"double\" = (\"double\" * \"double\")", double *= double) + assertSQL("\"double\" = (\"double\" * 1.0)", double *= 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1) } func test_dividedByEquals_withNumberExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" / \"int\")", int /= int) - AssertSQL("\"int\" = (\"int\" / 1)", int /= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int) - AssertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1) - - AssertSQL("\"double\" = (\"double\" / \"double\")", double /= double) - AssertSQL("\"double\" = (\"double\" / 1.0)", double /= 1) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional) - AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1) + assertSQL("\"int\" = (\"int\" / \"int\")", int /= int) + assertSQL("\"int\" = (\"int\" / 1)", int /= 1) + assertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int) + assertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1) + + assertSQL("\"double\" = (\"double\" / \"double\")", double /= double) + assertSQL("\"double\" = (\"double\" / 1.0)", double /= 1) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional) + assertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1) } func test_moduloEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" % \"int\")", int %= int) - AssertSQL("\"int\" = (\"int\" % 1)", int %= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int) - AssertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1) + assertSQL("\"int\" = (\"int\" % \"int\")", int %= int) + assertSQL("\"int\" = (\"int\" % 1)", int %= 1) + assertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int) + assertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1) } func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" << \"int\")", int <<= int) - AssertSQL("\"int\" = (\"int\" << 1)", int <<= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int) - AssertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1) + assertSQL("\"int\" = (\"int\" << \"int\")", int <<= int) + assertSQL("\"int\" = (\"int\" << 1)", int <<= 1) + assertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int) + assertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1) } func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int) - AssertSQL("\"int\" = (\"int\" >> 1)", int >>= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int) - AssertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1) + assertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int) + assertSQL("\"int\" = (\"int\" >> 1)", int >>= 1) + assertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int) + assertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1) } func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" & \"int\")", int &= int) - AssertSQL("\"int\" = (\"int\" & 1)", int &= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int) - AssertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1) + assertSQL("\"int\" = (\"int\" & \"int\")", int &= int) + assertSQL("\"int\" = (\"int\" & 1)", int &= 1) + assertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int) + assertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1) } func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (\"int\" | \"int\")", int |= int) - AssertSQL("\"int\" = (\"int\" | 1)", int |= 1) - AssertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int) - AssertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional) - AssertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1) + assertSQL("\"int\" = (\"int\" | \"int\")", int |= int) + assertSQL("\"int\" = (\"int\" | 1)", int |= 1) + assertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int) + assertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional) + assertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1) } func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - AssertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int) - AssertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1) - AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int) - AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional) - AssertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1) + assertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int) + assertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1) + assertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int) + assertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional) + assertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1) } func test_postfixPlus_withIntegerValue_buildsSetter() { - AssertSQL("\"int\" = (\"int\" + 1)", int++) - AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++) + assertSQL("\"int\" = (\"int\" + 1)", int++) + assertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++) } func test_postfixMinus_withIntegerValue_buildsSetter() { - AssertSQL("\"int\" = (\"int\" - 1)", int--) - AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) + assertSQL("\"int\" = (\"int\" - 1)", int--) + assertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) } } diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 326259b2..5a05675d 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -1,14 +1,14 @@ import XCTest import SQLite -class StatementTests : SQLiteTestCase { +class StatementTests: SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() + createUsersTable() } func test_cursor_to_blob() { - try! InsertUsers("alice") + try! insertUsers("alice") let statement = try! db.prepare("SELECT email FROM users") XCTAssert(try! statement.step()) let blob = statement.row[0] as Blob diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 247013b4..0994dd97 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -1,15 +1,15 @@ import XCTest @testable import SQLite -class SQLiteTestCase : XCTestCase { - private var trace:[String: Int]! - var db:Connection! +class SQLiteTestCase: XCTestCase { + private var trace: [String: Int]! + var db: Connection! let users = Table("users") override func setUp() { super.setUp() db = try! Connection() - trace = [String:Int]() + trace = [String: Int]() db.trace { SQL in print(SQL) @@ -17,7 +17,7 @@ class SQLiteTestCase : XCTestCase { } } - func CreateUsersTable() { + func createUsersTable() { try! db.execute(""" CREATE TABLE users ( id INTEGER PRIMARY KEY, @@ -33,22 +33,22 @@ class SQLiteTestCase : XCTestCase { ) } - func InsertUsers(_ names: String...) throws { - try InsertUsers(names) + func insertUsers(_ names: String...) throws { + try insertUsers(names) } - func InsertUsers(_ names: [String]) throws { - for name in names { try InsertUser(name) } + func insertUsers(_ names: [String]) throws { + for name in names { try insertUser(name) } } - @discardableResult func InsertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { + @discardableResult func insertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { return try db.run( "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", "\(name)@example.com", age?.datatypeValue, admin.datatypeValue ) } - func AssertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { + func assertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual( executions, trace[SQL] ?? 0, message ?? SQL, @@ -56,9 +56,9 @@ class SQLiteTestCase : XCTestCase { ) } - func AssertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { + func assertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { try! statement.run() - AssertSQL(SQL, 1, message, file: file, line: line) + assertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } } @@ -97,7 +97,8 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") -func AssertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { +func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, + file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } diff --git a/Tests/SQLiteTests/ValueTests.swift b/Tests/SQLiteTests/ValueTests.swift index bda2b4b3..f880cb34 100644 --- a/Tests/SQLiteTests/ValueTests.swift +++ b/Tests/SQLiteTests/ValueTests.swift @@ -1,6 +1,6 @@ import XCTest import SQLite -class ValueTests : XCTestCase { +class ValueTests: XCTestCase { } From ae42398825c0e391dbbb8393dc8380979e3a9004 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 01:20:49 +0200 Subject: [PATCH 0762/1046] brew --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b9d0cf1..1119284a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: gem install xcpretty --no-document brew update brew outdated carthage || brew upgrade carthage - brew install swiftlint + brew outdated swiftlint || brew upgrade swiftlint - name: "Lint" run: make lint - name: "Run tests (BUILD_SCHEME: SQLite iOS)" From a8f5079a2e0676b74baffe49a3226c65cb7af6e4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 01:38:03 +0200 Subject: [PATCH 0763/1046] Fix warnings --- .swiftlint.yml | 12 +++++++++--- Makefile | 2 +- Sources/SQLite/Core/Connection.swift | 10 +++++----- Sources/SQLite/Extensions/FTS4.swift | 1 + Sources/SQLite/Helpers.swift | 10 +++++----- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- Sources/SQLite/Typed/Query.swift | 20 +++++++++++--------- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index e18c09a1..3bfc5fa5 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -11,6 +11,7 @@ excluded: # paths to ignore during linting. overridden by `included`. identifier_name: excluded: - db + - in - to - by - or @@ -20,13 +21,18 @@ identifier_name: - fn - a - b + - q + - SQLITE_TRANSIENT + +type_body_length: + warning: 260 + error: 260 line_length: warning: 150 error: 150 ignores_comments: true - file_length: - warning: 700 - error: 700 + warning: 760 + error: 760 diff --git a/Makefile b/Makefile index 5e4ff388..a9d29d58 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ build: $(BUILD_TOOL) $(BUILD_ARGUMENTS) lint: - swiftlint + swiftlint --strict test: ifdef XCPRETTY diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index a4dad4a6..61c5f94a 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -473,10 +473,10 @@ public final class Connection { // The T argument is one of the SQLITE_TRACE constants to indicate why the // callback was invoked. The C argument is a copy of the context pointer. // The P and X arguments are pointers whose meanings depend on T. - (_: UInt32, C: UnsafeMutableRawPointer?, P: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in - if let P = P, - let expandedSQL = sqlite3_expanded_sql(OpaquePointer(P)) { - unsafeBitCast(C, to: Trace.self)(expandedSQL) + (_: UInt32, context: UnsafeMutableRawPointer?, pointer: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in + if let pointer = pointer, + let expandedSQL = sqlite3_expanded_sql(OpaquePointer(pointer)) { + unsafeBitCast(context, to: Trace.self)(expandedSQL) sqlite3_free(expandedSQL) } return Int32(0) // currently ignored @@ -585,7 +585,7 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index a3c253f2..bdb89e0e 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -91,6 +91,7 @@ extension VirtualTable { } +// swiftlint:disable identifier_name public struct Tokenizer { public static let Simple = Tokenizer("simple") diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 0e3385e3..f5c67e36 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -115,11 +115,11 @@ func transcode(_ literal: Binding?) -> String { } } -//swiftlint:disable force_cast -func value(_ v: Binding) -> A { - A.fromDatatypeValue(v as! A.Datatype) as! A +// swiftlint:disable force_cast +func value(_ binding: Binding) -> A { + A.fromDatatypeValue(binding as! A.Datatype) as! A } -func value(_ v: Binding?) -> A { - value(v!) +func value(_ binding: Binding?) -> A { + value(binding!) } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 053f17e5..42689578 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -21,7 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // - +// swiftlint:disable file_length import Foundation private enum Function: String { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 8d3ded52..6b1f6b8e 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1015,13 +1015,13 @@ extension Connection { let column = names.removeLast() let namespace = names.joined(separator: ".") - func expandGlob(_ namespace: Bool) -> ((QueryType) throws -> Void) { - return { (query: QueryType) throws -> Void in - var q = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) - q.clauses.select = query.clauses.select - let e = q.expression - var names = try self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } - if namespace { names = names.map { "\(query.tableName().expression.template).\($0)" } } + func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { + return { (queryType: QueryType) throws -> Void in + var query = type(of: queryType).init(queryType.clauses.from.name, database: queryType.clauses.from.database) + query.clauses.select = queryType.clauses.select + let expression = query.expression + var names = try self.prepare(expression.template, expression.bindings).columnNames.map { $0.quote() } + if namespace { names = names.map { "\(queryType.tableName().expression.template).\($0)" } } for name in names { columnNames[name] = idx; idx += 1 } } } @@ -1184,11 +1184,13 @@ public struct Row { } public subscript(column: Expression) -> T { - return try! get(column) + // swiftlint:disable:next force_try + try! get(column) } public subscript(column: Expression) -> T? { - return try! get(column) + // swiftlint:disable:next force_try + try! get(column) } } From 7a8269be26e128fe4731cc39a624470edcc11764 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 10:41:51 +0200 Subject: [PATCH 0764/1046] Remove unnecessary self --- Sources/SQLite/Core/Connection.swift | 4 +- Sources/SQLite/Core/Statement.swift | 10 +-- Sources/SQLite/Extensions/FTS4.swift | 14 ++-- Sources/SQLite/Extensions/FTS5.swift | 4 +- Sources/SQLite/Foundation.swift | 2 +- Sources/SQLite/Typed/Coding.swift | 96 ++++++++++++------------- Sources/SQLite/Typed/Query.swift | 8 +-- Sources/SQLite/Typed/Setter.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 14 ++-- 9 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 61c5f94a..2ba51d80 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -161,7 +161,7 @@ public final class Connection { /// /// - Throws: `Result.Error` if query execution fails. public func execute(_ SQL: String) throws { - _ = try sync { try self.check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) } + _ = try sync { try check(sqlite3_exec(handle, SQL, nil, nil, nil)) } } // MARK: - Prepare @@ -633,7 +633,7 @@ public final class Connection { let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) function(context, argc, value) }, nil, nil, nil) - if functions[function] == nil { self.functions[function] = [:] } + if functions[function] == nil { functions[function] = [:] } functions[function]?[argc] = box } diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index dfb67d22..4332cb49 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -48,10 +48,10 @@ public final class Statement { sqlite3_finalize(handle) } - public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) + public lazy var columnCount: Int = Int(sqlite3_column_count(handle)) - public lazy var columnNames: [String] = (0.. Bool { - return try connection.sync { try self.connection.check(sqlite3_step(self.handle)) == SQLITE_ROW } + return try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } } fileprivate func reset(clearBindings shouldClear: Bool = true) { @@ -304,7 +304,7 @@ extension Cursor: Sequence { public func makeIterator() -> AnyIterator { var idx = 0 return AnyIterator { - if idx >= self.columnCount { + if idx >= columnCount { return Binding??.none } else { idx += 1 diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index bdb89e0e..1de03ff2 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -184,7 +184,7 @@ open class FTSConfig { /// Adds a column definition @discardableResult open func column(_ column: Expressible, _ options: [ColumnOption] = []) -> Self { - self.columnDefinitions.append((column, options)) + columnDefinitions.append((column, options)) return self } @@ -203,19 +203,19 @@ open class FTSConfig { /// [The prefix= option](https://www.sqlite.org/fts3.html#section_6_6) @discardableResult open func prefix(_ prefix: [Int]) -> Self { - self.prefixes += prefix + prefixes += prefix return self } /// [The content= option](https://www.sqlite.org/fts3.html#section_6_2) @discardableResult open func externalContent(_ schema: SchemaType) -> Self { - self.externalContentSchema = schema + externalContentSchema = schema return self } /// [Contentless FTS4 Tables](https://www.sqlite.org/fts3.html#section_6_2_1) @discardableResult open func contentless() -> Self { - self.isContentless = true + isContentless = true return self } @@ -311,19 +311,19 @@ open class FTS4Config: FTSConfig { /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) @discardableResult open func compress(_ functionName: String) -> Self { - self.compressFunction = functionName + compressFunction = functionName return self } /// [The compress= and uncompress= options](https://www.sqlite.org/fts3.html#section_6_1) @discardableResult open func uncompress(_ functionName: String) -> Self { - self.uncompressFunction = functionName + uncompressFunction = functionName return self } /// [The languageid= option](https://www.sqlite.org/fts3.html#section_6_3) @discardableResult open func languageId(_ columnName: String) -> Self { - self.languageId = columnName + languageId = columnName return self } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index 07f49ffc..a5d04dbf 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -59,13 +59,13 @@ open class FTS5Config: FTSConfig { /// [External Content Tables](https://www.sqlite.org/fts5.html#section_4_4_2) @discardableResult open func contentRowId(_ column: Expressible) -> Self { - self.contentRowId = column + contentRowId = column return self } /// [The Columnsize Option](https://www.sqlite.org/fts5.html#section_4_5) @discardableResult open func columnSize(_ size: Int) -> Self { - self.columnSize = size + columnSize = size return self } diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index d837d8ba..81e89d48 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -80,7 +80,7 @@ extension UUID: Value { } public var datatypeValue: String { - return self.uuidString + return uuidString } } diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 5a672883..d2df2fe6 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -149,7 +149,7 @@ extension Row { /// /// - Returns: a decoded object from this row public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { - return try V(from: self.decoder(userInfo: userInfo)) + return try V(from: decoder(userInfo: userInfo)) } public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { @@ -179,82 +179,82 @@ private class SQLiteEncoder: Encoder { } func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- nil) + encoder.setters.append(Expression(key.stringValue) <- nil) } func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: Bool, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: Float, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- Double(value)) + encoder.setters.append(Expression(key.stringValue) <- Double(value)) } func encode(_ value: Double, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: String, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { if let data = value as? Data { - self.encoder.setters.append(Expression(key.stringValue) <- data) + encoder.setters.append(Expression(key.stringValue) <- data) } else if let date = value as? Date { - self.encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) - self.encoder.setters.append(Expression(key.stringValue) <- string) + encoder.setters.append(Expression(key.stringValue) <- string) } } func encode(_ value: Int8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) } func encode(_ value: Int16, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int16 is not supported")) } func encode(_ value: Int32, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int32 is not supported")) } func encode(_ value: Int64, forKey key: Key) throws { - self.encoder.setters.append(Expression(key.stringValue) <- value) + encoder.setters.append(Expression(key.stringValue) <- value) } func encode(_ value: UInt, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt is not supported")) } func encode(_ value: UInt8, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt8 is not supported")) } func encode(_ value: UInt16, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt16 is not supported")) } func encode(_ value: UInt32, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt32 is not supported")) } func encode(_ value: UInt64, forKey key: Key) throws { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: self.codingPath, + throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt64 is not supported")) } @@ -301,98 +301,98 @@ private class SQLiteDecoder: Decoder { } var allKeys: [Key] { - return self.row.columnNames.keys.compactMap({Key(stringValue: $0)}) + return row.columnNames.keys.compactMap({Key(stringValue: $0)}) } func contains(_ key: Key) -> Bool { - return self.row.hasValue(for: key.stringValue) + return row.hasValue(for: key.stringValue) } func decodeNil(forKey key: Key) throws -> Bool { - return !self.contains(key) + return !contains(key) } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int8 is not supported")) } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int16 is not supported")) } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int32 is not supported")) } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt is not supported")) } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt8 is not supported")) } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt16 is not supported")) } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt32 is not supported")) } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt64 is not supported")) } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - return Float(try self.row.get(Expression(key.stringValue))) + return Float(try row.get(Expression(key.stringValue))) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try self.row.get(Expression(key.stringValue)) + return try row.get(Expression(key.stringValue)) } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { // swiftlint:disable force_cast if type == Data.self { - let data = try self.row.get(Expression(key.stringValue)) + let data = try row.get(Expression(key.stringValue)) return data as! T } else if type == Date.self { - let date = try self.row.get(Expression(key.stringValue)) + let date = try row.get(Expression(key.stringValue)) return date as! T } // swiftlint:enable force_cast - guard let JSONString = try self.row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: self.codingPath, + guard let JSONString = try row.get(Expression(key.stringValue)) else { + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "an unsupported type was found")) } guard let data = JSONString.data(using: .utf8) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "invalid utf8 data found")) } return try JSONDecoder().decode(type, from: data) @@ -400,22 +400,22 @@ private class SQLiteDecoder: Decoder { func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding nested containers is not supported")) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding unkeyed containers is not supported")) } func superDecoder() throws -> Swift.Decoder { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super encoders containers is not supported")) } func superDecoder(forKey key: Key) throws -> Swift.Decoder { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super decoders is not supported")) } } @@ -430,16 +430,16 @@ private class SQLiteDecoder: Decoder { } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { - return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row)) + return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an unkeyed container is not supported")) } func singleValueContainer() throws -> SingleValueDecodingContainer { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding a single value container is not supported")) } } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 6b1f6b8e..22742809 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1093,7 +1093,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.lastInsertRowid + return lastInsertRowid } } @@ -1109,7 +1109,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.changes + return changes } } @@ -1124,7 +1124,7 @@ extension Connection { let expression = query.expression return try sync { try self.run(expression.template, expression.bindings) - return self.changes + return changes } } @@ -1244,7 +1244,7 @@ public struct QueryClauses { var union = [QueryType]() fileprivate init(_ name: String, alias: String?, database: String?) { - self.from = (name, alias, database) + from = (name, alias, database) } } diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 25e88342..08fb1748 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -63,7 +63,7 @@ public struct Setter { init(excluded column: Expressible) { let excluded = Expression("excluded") self.column = column - self.value = ".".join([excluded, column.expression]) + value = ".".join([excluded, column.expression]) } } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 00fe4f6b..44c2c302 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -217,7 +217,7 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndCommitsSavepoints() { - let db: Connection = self.db + let db: Connection = db try! db.savepoint("1") { try db.savepoint("2") { @@ -235,7 +235,7 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndRollsSavepointsBack() { - let db: Connection = self.db + let db: Connection = db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { @@ -310,7 +310,7 @@ class ConnectionTests: SQLiteTestCase { done() } try! db.transaction { - try self.insertUser("alice") + try insertUser("alice") } XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) } @@ -321,8 +321,8 @@ class ConnectionTests: SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.insertUser("alice") - try self.insertUser("alice") // throw + try insertUser("alice") + try insertUser("alice") // throw } } catch { } @@ -338,7 +338,7 @@ class ConnectionTests: SQLiteTestCase { db.rollbackHook(done) do { try db.transaction { - try self.insertUser("alice") + try insertUser("alice") } } catch { } @@ -437,7 +437,7 @@ class ResultTests: XCTestCase { XCTAssertEqual("not an error", message) XCTAssertEqual(SQLITE_MISUSE, code) XCTAssertNil(statement) - XCTAssert(self.connection === connection) + XCTAssert(connection === connection) } else { XCTFail("no error") } From e2afa8b6bf00728ab275af6be155a4f7b3b16a6e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 10:48:13 +0200 Subject: [PATCH 0765/1046] Simplify --- Sources/SQLite/Core/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 4332cb49..1f9afbc6 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -305,7 +305,7 @@ extension Cursor: Sequence { var idx = 0 return AnyIterator { if idx >= columnCount { - return Binding??.none + return .none } else { idx += 1 return self[idx - 1] From aebb9c590a469f714b334a9fd2ce84c45475f464 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:01:21 +0200 Subject: [PATCH 0766/1046] Remove unnecessary returns --- Sources/SQLite/Core/Blob.swift | 6 +- Sources/SQLite/Core/Connection.swift | 30 +- Sources/SQLite/Core/Statement.swift | 28 +- Sources/SQLite/Core/Value.swift | 24 +- Sources/SQLite/Extensions/Cipher.swift | 2 +- Sources/SQLite/Extensions/FTS4.swift | 32 +- Sources/SQLite/Extensions/FTS5.swift | 4 +- Sources/SQLite/Foundation.swift | 20 +- Sources/SQLite/Helpers.swift | 12 +- Sources/SQLite/Typed/AggregateFunctions.swift | 34 +- Sources/SQLite/Typed/Coding.swift | 26 +- Sources/SQLite/Typed/Collation.swift | 2 +- Sources/SQLite/Typed/CoreFunctions.swift | 62 +-- .../SQLite/Typed/DateAndTimeFunctions.swift | 24 +- Sources/SQLite/Typed/Expression.swift | 14 +- Sources/SQLite/Typed/Operators.swift | 368 +++++++++--------- Sources/SQLite/Typed/Query.swift | 110 +++--- Sources/SQLite/Typed/Schema.swift | 42 +- Sources/SQLite/Typed/Setter.swift | 130 +++---- Tests/SQLiteTests/ConnectionTests.swift | 4 +- Tests/SQLiteTests/CustomFunctionsTests.swift | 28 +- Tests/SQLiteTests/FTS4Tests.swift | 2 +- Tests/SQLiteTests/FTS5Tests.swift | 2 +- Tests/SQLiteTests/TestHelpers.swift | 6 +- 24 files changed, 506 insertions(+), 506 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index a49ef23b..cd31483b 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -36,7 +36,7 @@ public struct Blob { } public func toHex() -> String { - return bytes.map { + bytes.map { ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) }.joined(separator: "") } @@ -46,7 +46,7 @@ public struct Blob { extension Blob: CustomStringConvertible { public var description: String { - return "x'\(toHex())'" + "x'\(toHex())'" } } @@ -56,5 +56,5 @@ extension Blob: Equatable { } public func ==(lhs: Blob, rhs: Blob) -> Bool { - return lhs.bytes == rhs.bytes + lhs.bytes == rhs.bytes } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 2ba51d80..07c85373 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -84,7 +84,7 @@ public final class Connection { } } - public var handle: OpaquePointer { return _handle! } + public var handle: OpaquePointer { _handle! } fileprivate var _handle: OpaquePointer? @@ -133,23 +133,23 @@ public final class Connection { // MARK: - /// Whether or not the database was opened in a read-only state. - public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } + public var readonly: Bool { sqlite3_db_readonly(handle, nil) == 1 } /// The last rowid inserted into the database via this connection. public var lastInsertRowid: Int64 { - return sqlite3_last_insert_rowid(handle) + sqlite3_last_insert_rowid(handle) } /// The last number of changes (inserts, updates, or deletes) made to the /// database via this connection. public var changes: Int { - return Int(sqlite3_changes(handle)) + Int(sqlite3_changes(handle)) } /// The total number of changes (inserts, updates, or deletes) made to the /// database via this connection. public var totalChanges: Int { - return Int(sqlite3_total_changes(handle)) + Int(sqlite3_total_changes(handle)) } // MARK: - Execute @@ -190,7 +190,7 @@ public final class Connection { /// /// - Returns: A prepared statement. public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement { - return try prepare(statement).bind(bindings) + try prepare(statement).bind(bindings) } /// Prepares a single SQL statement and binds parameters to it. @@ -203,7 +203,7 @@ public final class Connection { /// /// - Returns: A prepared statement. public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { - return try prepare(statement).bind(bindings) + try prepare(statement).bind(bindings) } // MARK: - Run @@ -220,7 +220,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { - return try run(statement, bindings) + try run(statement, bindings) } /// Prepares, binds, and runs a single SQL statement. @@ -235,7 +235,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { - return try prepare(statement).run(bindings) + try prepare(statement).run(bindings) } /// Prepares, binds, and runs a single SQL statement. @@ -250,7 +250,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { - return try prepare(statement).run(bindings) + try prepare(statement).run(bindings) } // MARK: - VACUUM @@ -261,7 +261,7 @@ public final class Connection { /// /// - Returns: The statement. @discardableResult public func vacuum() throws -> Statement { - return try run("VACUUM") + try run("VACUUM") } // MARK: - Scalar @@ -277,7 +277,7 @@ public final class Connection { /// /// - Returns: The first value of the first row returned. public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? { - return try scalar(statement, bindings) + try scalar(statement, bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -291,7 +291,7 @@ public final class Connection { /// /// - Returns: The first value of the first row returned. public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? { - return try prepare(statement).scalar(bindings) + try prepare(statement).scalar(bindings) } /// Runs a single SQL statement (with optional parameter bindings), @@ -305,7 +305,7 @@ public final class Connection { /// /// - Returns: The first value of the first row returned. public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? { - return try prepare(statement).scalar(bindings) + try prepare(statement).scalar(bindings) } // MARK: - Transactions @@ -810,7 +810,7 @@ public final class Connection { extension Connection: CustomStringConvertible { public var description: String { - return String(cString: sqlite3_db_filename(handle, nil)) + String(cString: sqlite3_db_filename(handle, nil)) } } diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1f9afbc6..48347c31 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -63,7 +63,7 @@ public final class Statement { /// /// - Returns: The statement object (useful for chaining). public func bind(_ values: Binding?...) -> Statement { - return bind(values) + bind(values) } /// Binds a list of parameters to a statement. @@ -140,7 +140,7 @@ public final class Statement { /// /// - Returns: The statement object (useful for chaining). @discardableResult public func run(_ bindings: [Binding?]) throws -> Statement { - return try bind(bindings).run() + try bind(bindings).run() } /// - Parameter bindings: A dictionary of named parameters to bind to the @@ -150,7 +150,7 @@ public final class Statement { /// /// - Returns: The statement object (useful for chaining). @discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement { - return try bind(bindings).run() + try bind(bindings).run() } /// - Parameter bindings: A list of parameters to bind to the statement. @@ -170,7 +170,7 @@ public final class Statement { /// /// - Returns: The first value of the first row returned. public func scalar(_ bindings: [Binding?]) throws -> Binding? { - return try bind(bindings).scalar() + try bind(bindings).scalar() } /// - Parameter bindings: A dictionary of named parameters to bind to the @@ -178,11 +178,11 @@ public final class Statement { /// /// - Returns: The first value of the first row returned. public func scalar(_ bindings: [String: Binding?]) throws -> Binding? { - return try bind(bindings).scalar() + try bind(bindings).scalar() } public func step() throws -> Bool { - return try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } + try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } } fileprivate func reset(clearBindings shouldClear: Bool = true) { @@ -207,7 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { - return try? failableNext() + try? failableNext() } } @@ -223,14 +223,14 @@ extension Array { extension Statement: FailableIterator { public typealias Element = [Binding?] public func failableNext() throws -> [Binding?]? { - return try step() ? Array(row) : nil + try step() ? Array(row) : nil } } extension Statement: CustomStringConvertible { public var description: String { - return String(cString: sqlite3_sql(handle)) + String(cString: sqlite3_sql(handle)) } } @@ -247,15 +247,15 @@ public struct Cursor { } public subscript(idx: Int) -> Double { - return sqlite3_column_double(handle, Int32(idx)) + sqlite3_column_double(handle, Int32(idx)) } public subscript(idx: Int) -> Int64 { - return sqlite3_column_int64(handle, Int32(idx)) + sqlite3_column_int64(handle, Int32(idx)) } public subscript(idx: Int) -> String { - return String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) + String(cString: UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) } public subscript(idx: Int) -> Blob { @@ -272,11 +272,11 @@ public struct Cursor { // MARK: - public subscript(idx: Int) -> Bool { - return Bool.fromDatatypeValue(self[idx]) + Bool.fromDatatypeValue(self[idx]) } public subscript(idx: Int) -> Int { - return Int.fromDatatypeValue(self[idx]) + Int.fromDatatypeValue(self[idx]) } } diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index b8686eab..9c463f0c 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -50,11 +50,11 @@ extension Double: Number, Value { public static let declaredDatatype = "REAL" public static func fromDatatypeValue(_ datatypeValue: Double) -> Double { - return datatypeValue + datatypeValue } public var datatypeValue: Double { - return self + self } } @@ -64,11 +64,11 @@ extension Int64: Number, Value { public static let declaredDatatype = "INTEGER" public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int64 { - return datatypeValue + datatypeValue } public var datatypeValue: Int64 { - return self + self } } @@ -78,11 +78,11 @@ extension String: Binding, Value { public static let declaredDatatype = "TEXT" public static func fromDatatypeValue(_ datatypeValue: String) -> String { - return datatypeValue + datatypeValue } public var datatypeValue: String { - return self + self } } @@ -92,11 +92,11 @@ extension Blob: Binding, Value { public static let declaredDatatype = "BLOB" public static func fromDatatypeValue(_ datatypeValue: Blob) -> Blob { - return datatypeValue + datatypeValue } public var datatypeValue: Blob { - return self + self } } @@ -108,11 +108,11 @@ extension Bool: Binding, Value { public static var declaredDatatype = Int64.declaredDatatype public static func fromDatatypeValue(_ datatypeValue: Int64) -> Bool { - return datatypeValue != 0 + datatypeValue != 0 } public var datatypeValue: Int64 { - return self ? 1 : 0 + self ? 1 : 0 } } @@ -122,11 +122,11 @@ extension Int: Number, Value { public static var declaredDatatype = Int64.declaredDatatype public static func fromDatatypeValue(_ datatypeValue: Int64) -> Int { - return Int(datatypeValue) + Int(datatypeValue) } public var datatypeValue: Int64 { - return Int64(self) + Int64(self) } } diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 9fce42f7..25d20158 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -7,7 +7,7 @@ extension Connection { /// - Returns: the SQLCipher version public var cipherVersion: String? { - return (try? scalar("PRAGMA cipher_version")) as? String + (try? scalar("PRAGMA cipher_version")) as? String } /// Specify the key for an encrypted database. This routine should be diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 1de03ff2..115b146a 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -29,15 +29,15 @@ import SQLiteObjc extension Module { public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module { - return FTS4([column] + more) + FTS4([column] + more) } public static func FTS4(_ columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { - return FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) + FTS4(FTS4Config().columns(columns).tokenizer(tokenizer)) } public static func FTS4(_ config: FTS4Config) -> Module { - return Module(name: "fts4", arguments: config.arguments()) + Module(name: "fts4", arguments: config.arguments()) } } @@ -56,15 +56,15 @@ extension VirtualTable { /// - Returns: An expression appended with a `MATCH` query against the given /// pattern. public func match(_ pattern: String) -> Expression { - return "MATCH".infix(tableName(), pattern) + "MATCH".infix(tableName(), pattern) } public func match(_ pattern: Expression) -> Expression { - return "MATCH".infix(tableName(), pattern) + "MATCH".infix(tableName(), pattern) } public func match(_ pattern: Expression) -> Expression { - return "MATCH".infix(tableName(), pattern) + "MATCH".infix(tableName(), pattern) } /// Builds a copy of the query with a `WHERE … MATCH` clause. @@ -78,15 +78,15 @@ extension VirtualTable { /// /// - Returns: A query with the given `WHERE … MATCH` clause applied. public func match(_ pattern: String) -> QueryType { - return filter(match(pattern)) + filter(match(pattern)) } public func match(_ pattern: Expression) -> QueryType { - return filter(match(pattern)) + filter(match(pattern)) } public func match(_ pattern: Expression) -> QueryType { - return filter(match(pattern)) + filter(match(pattern)) } } @@ -120,7 +120,7 @@ public struct Tokenizer { } public static func Custom(_ name: String) -> Tokenizer { - return Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) + Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) } public let name: String @@ -139,7 +139,7 @@ public struct Tokenizer { extension Tokenizer: CustomStringConvertible { public var description: String { - return ([name] + arguments).joined(separator: " ") + ([name] + arguments).joined(separator: " ") } } @@ -220,11 +220,11 @@ open class FTSConfig { } func formatColumnDefinitions() -> [Expressible] { - return columnDefinitions.map { $0.0 } + columnDefinitions.map { $0.0 } } func arguments() -> [Expressible] { - return options().arguments + options().arguments } func options() -> Options { @@ -259,11 +259,11 @@ open class FTSConfig { } @discardableResult mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { - return append(key, value: value?.description) + append(key, value: value?.description) } @discardableResult mutating func append(_ key: String, value: String?) -> Options { - return append(key, value: value.map { Expression($0) }) + append(key, value: value.map { Expression($0) }) } @discardableResult mutating func append(_ key: String, value: Expressible?) -> Options { @@ -281,7 +281,7 @@ open class FTS4Config: FTSConfig { public enum MatchInfo: CustomStringConvertible { case fts3 public var description: String { - return "fts3" + "fts3" } } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index a5d04dbf..f108bbec 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -24,7 +24,7 @@ extension Module { public static func FTS5(_ config: FTS5Config) -> Module { - return Module(name: "fts5", arguments: config.arguments()) + Module(name: "fts5", arguments: config.arguments()) } } @@ -86,7 +86,7 @@ open class FTS5Config: FTSConfig { } override func formatColumnDefinitions() -> [Expressible] { - return columnDefinitions.map { definition in + columnDefinitions.map { definition in if definition.options.contains(.unindexed) { return " ".join([definition.0, Expression(literal: "UNINDEXED")]) } else { diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 81e89d48..2acbc00e 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -27,16 +27,16 @@ import Foundation extension Data: Value { public static var declaredDatatype: String { - return Blob.declaredDatatype + Blob.declaredDatatype } public static func fromDatatypeValue(_ dataValue: Blob) -> Data { - return Data(dataValue.bytes) + Data(dataValue.bytes) } public var datatypeValue: Blob { - return withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in - return Blob(bytes: pointer.baseAddress!, length: count) + withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in + Blob(bytes: pointer.baseAddress!, length: count) } } @@ -45,15 +45,15 @@ extension Data: Value { extension Date: Value { public static var declaredDatatype: String { - return String.declaredDatatype + String.declaredDatatype } public static func fromDatatypeValue(_ stringValue: String) -> Date { - return dateFormatter.date(from: stringValue)! + dateFormatter.date(from: stringValue)! } public var datatypeValue: String { - return dateFormatter.string(from: self) + dateFormatter.string(from: self) } } @@ -72,15 +72,15 @@ public var dateFormatter: DateFormatter = { extension UUID: Value { public static var declaredDatatype: String { - return String.declaredDatatype + String.declaredDatatype } public static func fromDatatypeValue(_ stringValue: String) -> UUID { - return UUID(uuidString: stringValue)! + UUID(uuidString: stringValue)! } public var datatypeValue: String { - return uuidString + uuidString } } diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index f5c67e36..d4c6828e 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -35,7 +35,7 @@ import SQLite3 public typealias Star = (Expression?, Expression?) -> Expression public func *(_: Expression?, _: Expression?) -> Expression { - return Expression(literal: "*") + Expression(literal: "*") } public protocol _OptionalType { @@ -73,7 +73,7 @@ extension String { } func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return infix([lhs, rhs], wrap: wrap) + infix([lhs, rhs], wrap: wrap) } func infix(_ terms: [Expressible], wrap: Bool = true) -> Expression { @@ -85,19 +85,19 @@ extension String { } func prefix(_ expressions: Expressible) -> Expressible { - return "\(self) ".wrap(expressions) as Expression + "\(self) ".wrap(expressions) as Expression } func prefix(_ expressions: [Expressible]) -> Expressible { - return "\(self) ".wrap(expressions) as Expression + "\(self) ".wrap(expressions) as Expression } func wrap(_ expression: Expressible) -> Expression { - return Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) + Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) } func wrap(_ expressions: [Expressible]) -> Expression { - return wrap(", ".join(expressions)) + wrap(", ".join(expressions)) } } diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index 325b0277..bf4fb8fc 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -31,7 +31,7 @@ private enum Function: String { case total func wrap(_ expression: Expressible) -> Expression { - return self.rawValue.wrap(expression) + self.rawValue.wrap(expression) } } @@ -46,7 +46,7 @@ extension ExpressionType where UnderlyingType: Value { /// - Returns: A copy of the expression prefixed with the `DISTINCT` /// keyword. public var distinct: Expression { - return Expression("DISTINCT \(template)", bindings) + Expression("DISTINCT \(template)", bindings) } /// Builds a copy of the expression wrapped with the `count` aggregate @@ -61,7 +61,7 @@ extension ExpressionType where UnderlyingType: Value { /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Function.count.wrap(self) + Function.count.wrap(self) } } @@ -77,7 +77,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression prefixed with the `DISTINCT` /// keyword. public var distinct: Expression { - return Expression("DISTINCT \(template)", bindings) + Expression("DISTINCT \(template)", bindings) } /// Builds a copy of the expression wrapped with the `count` aggregate @@ -92,7 +92,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `count` aggregate /// function. public var count: Expression { - return Function.count.wrap(self) + Function.count.wrap(self) } } @@ -109,7 +109,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: C /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Function.max.wrap(self) + Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -122,7 +122,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: C /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Function.min.wrap(self) + Function.min.wrap(self) } } @@ -139,7 +139,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `max` aggregate /// function. public var max: Expression { - return Function.max.wrap(self) + Function.max.wrap(self) } /// Builds a copy of the expression wrapped with the `min` aggregate @@ -152,7 +152,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var min: Expression { - return Function.min.wrap(self) + Function.min.wrap(self) } } @@ -169,7 +169,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Function.avg.wrap(self) + Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -182,7 +182,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Function.sum.wrap(self) + Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -195,7 +195,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Function.total.wrap(self) + Function.total.wrap(self) } } @@ -212,7 +212,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var average: Expression { - return Function.avg.wrap(self) + Function.avg.wrap(self) } /// Builds a copy of the expression wrapped with the `sum` aggregate @@ -225,7 +225,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var sum: Expression { - return Function.sum.wrap(self) + Function.sum.wrap(self) } /// Builds a copy of the expression wrapped with the `total` aggregate @@ -238,7 +238,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// - Returns: A copy of the expression wrapped with the `min` aggregate /// function. public var total: Expression { - return Function.total.wrap(self) + Function.total.wrap(self) } } @@ -246,7 +246,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra extension ExpressionType where UnderlyingType == Int { static func count(_ star: Star) -> Expression { - return Function.count.wrap(star(nil, nil)) + Function.count.wrap(star(nil, nil)) } } @@ -260,5 +260,5 @@ extension ExpressionType where UnderlyingType == Int { /// - Returns: An expression returning `count(*)` (when called with the `*` /// function literal). public func count(_ star: Star) -> Expression { - return Expression.count(star) + Expression.count(star) } diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index d2df2fe6..3dc1e6cf 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -149,11 +149,11 @@ extension Row { /// /// - Returns: a decoded object from this row public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { - return try V(from: decoder(userInfo: userInfo)) + try V(from: decoder(userInfo: userInfo)) } public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { - return SQLiteDecoder(row: self, userInfo: userInfo) + SQLiteDecoder(row: self, userInfo: userInfo) } } @@ -285,7 +285,7 @@ private class SQLiteEncoder: Encoder { } func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { - return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } } @@ -301,23 +301,23 @@ private class SQLiteDecoder: Decoder { } var allKeys: [Key] { - return row.columnNames.keys.compactMap({Key(stringValue: $0)}) + row.columnNames.keys.compactMap({ Key(stringValue: $0) }) } func contains(_ key: Key) -> Bool { - return row.hasValue(for: key.stringValue) + row.hasValue(for: key.stringValue) } func decodeNil(forKey key: Key) throws -> Bool { - return !contains(key) + !contains(key) } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { @@ -336,7 +336,7 @@ private class SQLiteDecoder: Decoder { } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { @@ -366,15 +366,15 @@ private class SQLiteDecoder: Decoder { } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - return Float(try row.get(Expression(key.stringValue))) + Float(try row.get(Expression(key.stringValue))) } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: String.Type, forKey key: Key) throws -> String { - return try row.get(Expression(key.stringValue)) + try row.get(Expression(key.stringValue)) } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { @@ -430,7 +430,7 @@ private class SQLiteDecoder: Decoder { } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { - return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) + KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } func unkeyedContainer() throws -> UnkeyedDecodingContainer { diff --git a/Sources/SQLite/Typed/Collation.swift b/Sources/SQLite/Typed/Collation.swift index 3b268ce0..fec66129 100644 --- a/Sources/SQLite/Typed/Collation.swift +++ b/Sources/SQLite/Typed/Collation.swift @@ -46,7 +46,7 @@ public enum Collation { extension Collation: Expressible { public var expression: Expression { - return Expression(literal: description) + Expression(literal: description) } } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 42689578..be38d97e 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -47,15 +47,15 @@ private enum Function: String { case ifnull func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return self.rawValue.infix(lhs, rhs, wrap: wrap) + self.rawValue.infix(lhs, rhs, wrap: wrap) } func wrap(_ expression: Expressible) -> Expression { - return self.rawValue.wrap(expression) + self.rawValue.wrap(expression) } func wrap(_ expressions: [Expressible]) -> Expression { - return self.rawValue.wrap(", ".join(expressions)) + self.rawValue.wrap(", ".join(expressions)) } } @@ -69,7 +69,7 @@ extension ExpressionType where UnderlyingType: Number { /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue: Expression { - return Function.abs.wrap(self) + Function.abs.wrap(self) } } @@ -84,7 +84,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra /// /// - Returns: A copy of the expression wrapped with the `abs` function. public var absoluteValue: Expression { - return Function.abs.wrap(self) + Function.abs.wrap(self) } } @@ -138,7 +138,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype == /// /// - Returns: An expression calling the `random` function. public static func random() -> Expression { - return Function.random.wrap([]) + Function.random.wrap([]) } } @@ -154,7 +154,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `randomblob` function. public static func random(_ length: Int) -> Expression { - return Function.randomblob.wrap([]) + Function.randomblob.wrap([]) } /// Builds an expression representing the `zeroblob` function. @@ -166,7 +166,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: An expression calling the `zeroblob` function. public static func allZeros(_ length: Int) -> Expression { - return Function.zeroblob.wrap([]) + Function.zeroblob.wrap([]) } /// Builds a copy of the expression wrapped with the `length` function. @@ -177,7 +177,7 @@ extension ExpressionType where UnderlyingType == Data { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } } @@ -192,7 +192,7 @@ extension ExpressionType where UnderlyingType == Data? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } } @@ -207,7 +207,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -218,7 +218,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Function.lower.wrap(self) + Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -229,7 +229,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Function.upper.wrap(self) + Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -294,7 +294,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Function.glob.infix(self, pattern) + Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -309,7 +309,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Function.match.infix(self, pattern) + Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -320,7 +320,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Function.regexp.infix(self, pattern) + Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -335,7 +335,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Function.collate.infix(self, collation) + Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -406,7 +406,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Function.replace.wrap([self, pattern, replacement]) + Function.replace.wrap([self, pattern, replacement]) } public func substring(_ location: Int, length: Int? = nil) -> Expression { @@ -417,7 +417,7 @@ extension ExpressionType where UnderlyingType == String { } public subscript(range: Range) -> Expression { - return substring(range.lowerBound, length: range.upperBound - range.lowerBound) + substring(range.lowerBound, length: range.upperBound - range.lowerBound) } } @@ -432,7 +432,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `length` function. public var length: Expression { - return Function.length.wrap(self) + Function.length.wrap(self) } /// Builds a copy of the expression wrapped with the `lower` function. @@ -443,7 +443,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `lower` function. public var lowercaseString: Expression { - return Function.lower.wrap(self) + Function.lower.wrap(self) } /// Builds a copy of the expression wrapped with the `upper` function. @@ -454,7 +454,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { - return Function.upper.wrap(self) + Function.upper.wrap(self) } /// Builds a copy of the expression appended with a `LIKE` query against the @@ -519,7 +519,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `GLOB` query against /// the given pattern. public func glob(_ pattern: String) -> Expression { - return Function.glob.infix(self, pattern) + Function.glob.infix(self, pattern) } /// Builds a copy of the expression appended with a `MATCH` query against @@ -534,7 +534,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `MATCH` query /// against the given pattern. public func match(_ pattern: String) -> Expression { - return Function.match.infix(self, pattern) + Function.match.infix(self, pattern) } /// Builds a copy of the expression appended with a `REGEXP` query against @@ -545,7 +545,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `REGEXP` query /// against the given pattern. public func regexp(_ pattern: String) -> Expression { - return Function.regexp.infix(self, pattern) + Function.regexp.infix(self, pattern) } /// Builds a copy of the expression appended with a `COLLATE` clause with @@ -560,7 +560,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `COLLATE` clause /// with the given sequence. public func collate(_ collation: Collation) -> Expression { - return Function.collate.infix(self, collation) + Function.collate.infix(self, collation) } /// Builds a copy of the expression wrapped with the `ltrim` function. @@ -631,7 +631,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `replace` function. public func replace(_ pattern: String, with replacement: String) -> Expression { - return Function.replace.wrap([self, pattern, replacement]) + Function.replace.wrap([self, pattern, replacement]) } /// Builds a copy of the expression wrapped with the `substr` function. @@ -666,7 +666,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `substr` function. public subscript(range: Range) -> Expression { - return substring(range.lowerBound, length: range.upperBound - range.lowerBound) + substring(range.lowerBound, length: range.upperBound - range.lowerBound) } } @@ -752,7 +752,7 @@ extension String { /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: V) -> Expression { - return Function.ifnull.wrap([optional, defaultValue]) + Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -772,7 +772,7 @@ public func ??(optional: Expression, defaultValue: V) -> Expressio /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Function.ifnull.wrap([optional, defaultValue]) + Function.ifnull.wrap([optional, defaultValue]) } /// Builds a copy of the given expressions wrapped with the `ifnull` function. @@ -792,5 +792,5 @@ public func ??(optional: Expression, defaultValue: Expression) /// - Returns: A copy of the given expressions wrapped with the `ifnull` /// function. public func ??(optional: Expression, defaultValue: Expression) -> Expression { - return Function.ifnull.wrap([optional, defaultValue]) + Function.ifnull.wrap([optional, defaultValue]) } diff --git a/Sources/SQLite/Typed/DateAndTimeFunctions.swift b/Sources/SQLite/Typed/DateAndTimeFunctions.swift index 0b9a497f..b4382194 100644 --- a/Sources/SQLite/Typed/DateAndTimeFunctions.swift +++ b/Sources/SQLite/Typed/DateAndTimeFunctions.swift @@ -32,23 +32,23 @@ import Foundation public class DateFunctions { /// The date() function returns the date in this format: YYYY-MM-DD. public static func date(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("date", timestring: timestring, modifiers: modifiers) + timefunction("date", timestring: timestring, modifiers: modifiers) } /// The time() function returns the time as HH:MM:SS. public static func time(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("time", timestring: timestring, modifiers: modifiers) + timefunction("time", timestring: timestring, modifiers: modifiers) } /// The datetime() function returns "YYYY-MM-DD HH:MM:SS". public static func datetime(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("datetime", timestring: timestring, modifiers: modifiers) + timefunction("datetime", timestring: timestring, modifiers: modifiers) } /// The julianday() function returns the Julian day - /// the number of days since noon in Greenwich on November 24, 4714 B.C. public static func julianday(_ timestring: String, _ modifiers: String...) -> Expression { - return timefunction("julianday", timestring: timestring, modifiers: modifiers) + timefunction("julianday", timestring: timestring, modifiers: modifiers) } /// The strftime() routine returns the date formatted according to the format string specified as the first argument. @@ -71,36 +71,36 @@ public class DateFunctions { extension Date { public var date: Expression { - return DateFunctions.date(dateFormatter.string(from: self)) + DateFunctions.date(dateFormatter.string(from: self)) } public var time: Expression { - return DateFunctions.time(dateFormatter.string(from: self)) + DateFunctions.time(dateFormatter.string(from: self)) } public var datetime: Expression { - return DateFunctions.datetime(dateFormatter.string(from: self)) + DateFunctions.datetime(dateFormatter.string(from: self)) } public var julianday: Expression { - return DateFunctions.julianday(dateFormatter.string(from: self)) + DateFunctions.julianday(dateFormatter.string(from: self)) } } extension Expression where UnderlyingType == Date { public var date: Expression { - return Expression("date(\(template))", bindings) + Expression("date(\(template))", bindings) } public var time: Expression { - return Expression("time(\(template))", bindings) + Expression("time(\(template))", bindings) } public var datetime: Expression { - return Expression("datetime(\(template))", bindings) + Expression("datetime(\(template))", bindings) } public var julianday: Expression { - return Expression("julianday(\(template))", bindings) + Expression("julianday(\(template))", bindings) } } diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 39a3c62f..95cdd3b9 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -95,15 +95,15 @@ extension Expressible { extension ExpressionType { public var expression: Expression { - return Expression(template, bindings) + Expression(template, bindings) } public var asc: Expressible { - return " ".join([self, Expression(literal: "ASC")]) + " ".join([self, Expression(literal: "ASC")]) } public var desc: Expressible { - return " ".join([self, Expression(literal: "DESC")]) + " ".join([self, Expression(literal: "DESC")]) } } @@ -119,7 +119,7 @@ extension ExpressionType where UnderlyingType: Value { extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.WrappedType: Value { public static var null: Self { - return self.init(value: nil) + self.init(value: nil) } public init(value: UnderlyingType.WrappedType?) { @@ -131,7 +131,7 @@ extension ExpressionType where UnderlyingType: _OptionalType, UnderlyingType.Wra extension Value { public var expression: Expression { - return Expression(value: self).expression + Expression(value: self).expression } } @@ -139,9 +139,9 @@ extension Value { public let rowid = Expression("ROWID") public func cast(_ expression: Expression) -> Expression { - return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) + Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) } public func cast(_ expression: Expression) -> Expression { - return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) + Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) } diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 32d39fa7..5ffbbceb 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -47,331 +47,331 @@ private enum Operator: String { case concatenate = "||" func infix(_ lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { - return self.rawValue.infix(lhs, rhs, wrap: wrap) + self.rawValue.infix(lhs, rhs, wrap: wrap) } func wrap(_ expression: Expressible) -> Expression { - return self.rawValue.wrap(expression) + self.rawValue.wrap(expression) } } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: Expression, rhs: String) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } public func +(lhs: String, rhs: Expression) -> Expression { - return Operator.concatenate.infix(lhs, rhs) + Operator.concatenate.infix(lhs, rhs) } // MARK: - public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func +(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.plus.infix(lhs, rhs) + Operator.plus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func -(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.infix(lhs, rhs) + Operator.minus.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func *(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.mul.infix(lhs, rhs) + Operator.mul.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: Expression, rhs: V) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public func /(lhs: V, rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.div.infix(lhs, rhs) + Operator.div.infix(lhs, rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.wrap(rhs) + Operator.minus.wrap(rhs) } public prefix func -(rhs: Expression) -> Expression where V.Datatype: Number { - return Operator.minus.wrap(rhs) + Operator.minus.wrap(rhs) } // MARK: - public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func %(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.mod.infix(lhs, rhs) + Operator.mod.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func <<(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseLeft.infix(lhs, rhs) + Operator.bitwiseLeft.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func >>(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseRight.infix(lhs, rhs) + Operator.bitwiseRight.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func &(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseAnd.infix(lhs, rhs) + Operator.bitwiseAnd.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func |(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseOr.infix(lhs, rhs) + Operator.bitwiseOr.infix(lhs, rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: Expression, rhs: V) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public func ^(lhs: V, rhs: Expression) -> Expression where V.Datatype == Int64 { - return (~(lhs & rhs)) & (lhs | rhs) + (~(lhs & rhs)) & (lhs | rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseXor.wrap(rhs) + Operator.bitwiseXor.wrap(rhs) } public prefix func ~(rhs: Expression) -> Expression where V.Datatype == Int64 { - return Operator.bitwiseXor.wrap(rhs) + Operator.bitwiseXor.wrap(rhs) } // MARK: - public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.eq.infix(lhs, rhs) + Operator.eq.infix(lhs, rhs) } public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } @@ -379,26 +379,26 @@ public func ==(lhs: V?, rhs: Expression) -> Expression wher } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } return "IS".infix(lhs, rhs) } public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS".infix(lhs, rhs) + "IS".infix(lhs, rhs) } public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } @@ -406,26 +406,26 @@ public func ===(lhs: V?, rhs: Expression) -> Expression whe } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return Operator.neq.infix(lhs, rhs) + Operator.neq.infix(lhs, rhs) } public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } @@ -433,26 +433,26 @@ public func !=(lhs: V?, rhs: Expression) -> Expression wher } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: V) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return "IS NOT".infix(lhs, rhs) } public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { - return "IS NOT".infix(lhs, rhs) + "IS NOT".infix(lhs, rhs) } public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } @@ -460,215 +460,215 @@ public func !==(lhs: V?, rhs: Expression) -> Expression whe } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gt.infix(lhs, rhs) + Operator.gt.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func >=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.gte.infix(lhs, rhs) + Operator.gte.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lt.infix(lhs, rhs) + Operator.lt.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: Expression, rhs: V) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func <=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Comparable { - return Operator.lte.infix(lhs, rhs) + Operator.lte.infix(lhs, rhs) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } public func ~=(lhs: ClosedRange, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.lowerBound.datatypeValue, lhs.upperBound.datatypeValue]) } public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + - rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", + rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: Range, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", rhs.bindings + [lhs.lowerBound.datatypeValue] + - rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) >= ? AND \(rhs.template) < ?", + rhs.bindings + [lhs.lowerBound.datatypeValue] + rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeThrough, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) <= ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeUpTo, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) + Expression("\(rhs.template) < ?", rhs.bindings + [lhs.upperBound.datatypeValue]) } public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) + Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } public func ~=(lhs: PartialRangeFrom, rhs: Expression) -> Expression where V.Datatype: Comparable & Value { - return Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) + Expression("\(rhs.template) >= ?", rhs.bindings + [lhs.lowerBound.datatypeValue]) } // MARK: - public func and(_ terms: Expression...) -> Expression { - return "AND".infix(terms) + "AND".infix(terms) } public func and(_ terms: [Expression]) -> Expression { - return "AND".infix(terms) + "AND".infix(terms) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Expression, rhs: Bool) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func &&(lhs: Bool, rhs: Expression) -> Expression { - return Operator.and.infix(lhs, rhs) + Operator.and.infix(lhs, rhs) } public func or(_ terms: Expression...) -> Expression { - return "OR".infix(terms) + "OR".infix(terms) } public func or(_ terms: [Expression]) -> Expression { - return "OR".infix(terms) + "OR".infix(terms) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Expression, rhs: Bool) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public func ||(lhs: Bool, rhs: Expression) -> Expression { - return Operator.or.infix(lhs, rhs) + Operator.or.infix(lhs, rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operator.not.wrap(rhs) + Operator.not.wrap(rhs) } public prefix func !(rhs: Expression) -> Expression { - return Operator.not.wrap(rhs) + Operator.not.wrap(rhs) } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 22742809..59b24a8d 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -53,7 +53,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT` clause applied. public func select(_ column1: Expressible, _ more: Expressible...) -> Self { - return select(false, [column1] + more) + select(false, [column1] + more) } /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. @@ -68,7 +68,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. public func select(distinct column1: Expressible, _ more: Expressible...) -> Self { - return select(true, [column1] + more) + select(true, [column1] + more) } /// Builds a copy of the query with the `SELECT` clause applied. @@ -84,7 +84,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT` clause applied. public func select(_ all: [Expressible]) -> Self { - return select(false, all) + select(false, all) } /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. @@ -99,7 +99,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. public func select(distinct columns: [Expressible]) -> Self { - return select(true, columns) + select(true, columns) } /// Builds a copy of the query with the `SELECT *` clause applied. @@ -113,7 +113,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT *` clause applied. public func select(_ star: Star) -> Self { - return select([star(nil, nil)]) + select([star(nil, nil)]) } /// Builds a copy of the query with the `SELECT DISTINCT *` clause applied. @@ -127,7 +127,7 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT *` clause applied. public func select(distinct star: Star) -> Self { - return select(distinct: [star(nil, nil)]) + select(distinct: [star(nil, nil)]) } /// Builds a scalar copy of the query with the `SELECT` clause applied. @@ -142,10 +142,10 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT` clause applied. public func select(_ column: Expression) -> ScalarQuery { - return select(false, [column]) + select(false, [column]) } public func select(_ column: Expression) -> ScalarQuery { - return select(false, [column]) + select(false, [column]) } /// Builds a scalar copy of the query with the `SELECT DISTINCT` clause @@ -161,14 +161,14 @@ extension SchemaType { /// /// - Returns: A query with the given `SELECT DISTINCT` clause applied. public func select(distinct column: Expression) -> ScalarQuery { - return select(true, [column]) + select(true, [column]) } public func select(distinct column: Expression) -> ScalarQuery { - return select(true, [column]) + select(true, [column]) } public var count: ScalarQuery { - return select(Expression.count(*)) + select(Expression.count(*)) } } @@ -223,7 +223,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ table: QueryType, on condition: Expression) -> Self { - return join(table, on: Expression(condition)) + join(table, on: Expression(condition)) } /// Adds a `JOIN` clause to the query. @@ -244,7 +244,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ table: QueryType, on condition: Expression) -> Self { - return join(.inner, table, on: condition) + join(.inner, table, on: condition) } /// Adds a `JOIN` clause to the query. @@ -267,7 +267,7 @@ extension QueryType { /// /// - Returns: A query with the given `JOIN` clause applied. public func join(_ type: JoinType, _ table: QueryType, on condition: Expression) -> Self { - return join(type, table, on: Expression(condition)) + join(type, table, on: Expression(condition)) } /// Adds a `JOIN` clause to the query. @@ -291,7 +291,8 @@ extension QueryType { /// - Returns: A query with the given `JOIN` clause applied. public func join(_ type: JoinType, _ table: QueryType, on condition: Expression) -> Self { var query = self - query.clauses.join.append((type: type, query: table, condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) + query.clauses.join.append((type: type, query: table, + condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) return query } @@ -309,7 +310,7 @@ extension QueryType { /// /// - Returns: A query with the given `WHERE` clause applied. public func filter(_ predicate: Expression) -> Self { - return filter(Expression(predicate)) + filter(Expression(predicate)) } /// Adds a condition to the query’s `WHERE` clause. @@ -332,13 +333,13 @@ extension QueryType { /// Adds a condition to the query’s `WHERE` clause. /// This is an alias for `filter(predicate)` public func `where`(_ predicate: Expression) -> Self { - return `where`(Expression(predicate)) + `where`(Expression(predicate)) } /// Adds a condition to the query’s `WHERE` clause. /// This is an alias for `filter(predicate)` public func `where`(_ predicate: Expression) -> Self { - return filter(predicate) + filter(predicate) } // MARK: GROUP BY @@ -349,7 +350,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY` clause applied. public func group(_ by: Expressible...) -> Self { - return group(by) + group(by) } /// Sets a `GROUP BY` clause on the query. @@ -358,7 +359,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY` clause applied. public func group(_ by: [Expressible]) -> Self { - return group(by, nil) + group(by, nil) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -371,7 +372,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: Expressible, having: Expression) -> Self { - return group([by], having: having) + group([by], having: having) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -384,7 +385,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: Expressible, having: Expression) -> Self { - return group([by], having: having) + group([by], having: having) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -397,7 +398,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: [Expressible], having: Expression) -> Self { - return group(by, Expression(having)) + group(by, Expression(having)) } /// Sets a `GROUP BY`-`HAVING` clause on the query. @@ -410,7 +411,7 @@ extension QueryType { /// /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. public func group(_ by: [Expressible], having: Expression) -> Self { - return group(by, having) + group(by, having) } fileprivate func group(_ by: [Expressible], _ having: Expression?) -> Self { @@ -434,7 +435,7 @@ extension QueryType { /// /// - Returns: A query with the given `ORDER BY` clause applied. public func order(_ by: Expressible...) -> Self { - return order(by) + order(by) } /// Sets an `ORDER BY` clause on the query. @@ -469,7 +470,7 @@ extension QueryType { /// /// - Returns: A query with the given LIMIT clause applied. public func limit(_ length: Int?) -> Self { - return limit(length, nil) + limit(length, nil) } /// Sets LIMIT and OFFSET clauses on the query. @@ -487,7 +488,7 @@ extension QueryType { /// /// - Returns: A query with the given LIMIT and OFFSET clauses applied. public func limit(_ length: Int, offset: Int) -> Self { - return limit(length, offset) + limit(length, offset) } // prevents limit(nil, offset: 5) @@ -504,12 +505,13 @@ extension QueryType { // MARK: - fileprivate var selectClause: Expressible { - return " ".join([ - Expression(literal: clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), - ", ".join(clauses.select.columns), - Expression(literal: "FROM"), - tableName(alias: true) - ]) + " ".join([ + Expression(literal: + clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), + ", ".join(clauses.select.columns), + Expression(literal: "FROM"), + tableName(alias: true) + ]) } fileprivate var joinClause: Expressible? { @@ -616,31 +618,31 @@ extension QueryType { // MARK: INSERT public func insert(_ value: Setter, _ more: Setter...) -> Insert { - return insert([value] + more) + insert([value] + more) } public func insert(_ values: [Setter]) -> Insert { - return insert(nil, values) + insert(nil, values) } public func insert(or onConflict: OnConflict, _ values: Setter...) -> Insert { - return insert(or: onConflict, values) + insert(or: onConflict, values) } public func insert(or onConflict: OnConflict, _ values: [Setter]) -> Insert { - return insert(onConflict, values) + insert(onConflict, values) } public func insertMany( _ values: [[Setter]]) -> Insert { - return insertMany(nil, values) + insertMany(nil, values) } public func insertMany(or onConflict: OnConflict, _ values: [[Setter]]) -> Insert { - return insertMany(onConflict, values) + insertMany(onConflict, values) } public func insertMany(or onConflict: OnConflict, _ values: [Setter]...) -> Insert { - return insertMany(onConflict, values) + insertMany(onConflict, values) } fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert { @@ -689,7 +691,7 @@ extension QueryType { /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. public func insert() -> Insert { - return Insert(" ".join([ + Insert(" ".join([ Expression(literal: "INSERT INTO"), tableName(), Expression(literal: "DEFAULT VALUES") @@ -703,17 +705,17 @@ extension QueryType { /// /// - Returns: The number of updated rows and statement. public func insert(_ query: QueryType) -> Update { - return Update(" ".join([ + Update(" ".join([ Expression(literal: "INSERT INTO"), tableName(), query.expression - ]).expression) + ]).expression) } // MARK: UPSERT public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible) -> Insert { - return upsert(insertValues, onConflictOf: conflicting) + upsert(insertValues, onConflictOf: conflicting) } public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible) -> Insert { @@ -723,7 +725,7 @@ extension QueryType { } public func upsert(_ insertValues: Setter..., onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { - return upsert(insertValues, onConflictOf: conflicting, set: setValues) + upsert(insertValues, onConflictOf: conflicting, set: setValues) } public func upsert(_ insertValues: [Setter], onConflictOf conflicting: Expressible, set setValues: [Setter]) -> Insert { @@ -751,7 +753,7 @@ extension QueryType { // MARK: UPDATE public func update(_ values: Setter...) -> Update { - return update(values) + update(values) } public func update(_ values: [Setter]) -> Update { @@ -785,7 +787,7 @@ extension QueryType { // MARK: EXISTS public var exists: Select { - return Select(" ".join([ + Select(" ".join([ Expression(literal: "SELECT EXISTS"), "".wrap(expression) as Expression ]).expression) @@ -800,15 +802,15 @@ extension QueryType { /// - Returns: A column expression namespaced with the query’s table name or /// alias. public func namespace(_ column: Expression) -> Expression { - return Expression(".".join([tableName(), column]).expression) + Expression(".".join([tableName(), column]).expression) } public subscript(column: Expression) -> Expression { - return namespace(column) + namespace(column) } public subscript(column: Expression) -> Expression { - return namespace(column) + namespace(column) } /// Prefixes a star with the query’s table name or alias. @@ -818,7 +820,7 @@ extension QueryType { /// - Returns: A `*` expression namespaced with the query’s table name or /// alias. public subscript(star: Star) -> Expression { - return namespace(star(nil, nil)) + namespace(star(nil, nil)) } // MARK: - @@ -977,7 +979,7 @@ public struct RowIterator: FailableIterator { let columnNames: [String: Int] public func failableNext() throws -> Row? { - return try statement.failableNext().flatMap { Row(columnNames, $0) } + try statement.failableNext().flatMap { Row(columnNames, $0) } } public func map(_ transform: (Element) throws -> T) throws -> [T] { @@ -1016,7 +1018,7 @@ extension Connection { let namespace = names.joined(separator: ".") func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { - return { (queryType: QueryType) throws -> Void in + { (queryType: QueryType) throws -> Void in var query = type(of: queryType).init(queryType.clauses.from.name, database: queryType.clauses.from.database) query.clauses.select = queryType.clauses.select let expression = query.expression @@ -1074,7 +1076,7 @@ extension Connection { } public func pluck(_ query: QueryType) throws -> Row? { - return try prepareRowIterator(query.limit(1, query.clauses.limit?.offset)).failableNext() + try prepareRowIterator(query.limit(1, query.clauses.limit?.offset)).failableNext() } /// Runs an `Insert` query. diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index d8d45e41..726f3e27 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -27,7 +27,7 @@ extension SchemaType { // MARK: - DROP TABLE / VIEW / VIRTUAL TABLE public func drop(ifExists: Bool = false) -> String { - return drop("TABLE", tableName(), ifExists) + drop("TABLE", tableName(), ifExists) } } @@ -64,63 +64,63 @@ extension Table { // MARK: - ALTER TABLE … ADD COLUMN public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil) -> String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String where V.Datatype == Int64 { - return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) } public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) } public func addColumn(_ name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } public func addColumn(_ name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String where V.Datatype == String { - return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) } fileprivate func addColumn(_ expression: Expressible) -> String { - return " ".join([ + " ".join([ Expression(literal: "ALTER TABLE"), tableName(), Expression(literal: "ADD COLUMN"), @@ -131,7 +131,7 @@ extension Table { // MARK: - ALTER TABLE … RENAME TO public func rename(_ to: Table) -> String { - return rename(to: to) + rename(to: to) } // MARK: - CREATE INDEX @@ -150,7 +150,7 @@ extension Table { // MARK: - DROP INDEX public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { - return drop("INDEX", indexName(columns), ifExists) + drop("INDEX", indexName(columns), ifExists) } fileprivate func indexName(_ columns: [Expressible]) -> Expressible { @@ -188,7 +188,7 @@ extension View { // MARK: - DROP VIEW public func drop(ifExists: Bool = false) -> String { - return drop("VIEW", tableName(), ifExists) + drop("VIEW", tableName(), ifExists) } } @@ -210,7 +210,7 @@ extension VirtualTable { // MARK: - ALTER TABLE … RENAME TO public func rename(_ to: VirtualTable) -> String { - return rename(to: to) + rename(to: to) } } @@ -495,7 +495,7 @@ public struct Module { extension Module: Expressible { public var expression: Expression { - return name.wrap(arguments) + name.wrap(arguments) } } @@ -517,7 +517,7 @@ private extension QueryType { } func rename(to: Self) -> String { - return " ".join([ + " ".join([ Expression(literal: "ALTER TABLE"), tableName(), Expression(literal: "RENAME TO"), @@ -557,7 +557,7 @@ private func definition(_ column: Expressible, _ datatype: String, _ primaryKey: } private func reference(_ primary: (QueryType, Expressible)) -> Expressible { - return " ".join([ + " ".join([ Expression(literal: "REFERENCES"), primary.0.tableName(qualified: false), "".wrap(primary.1) as Expression diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 08fb1748..7910cab8 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -70,213 +70,213 @@ public struct Setter { extension Setter: Expressible { public var expression: Expression { - return "=".infix(column, value, wrap: false) + "=".infix(column, value, wrap: false) } } public func <-(column: Expression, value: Expression) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: V) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: Expression) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: Expression) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func <-(column: Expression, value: V?) -> Setter { - return Setter(column: column, value: value) + Setter(column: column, value: value) } public func +=(column: Expression, value: Expression) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: String) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: String) -> Setter { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func +=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column + value + column <- column + value } public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func -=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column - value + column <- column - value } public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func *=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column * value + column <- column * value } public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: Expression) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func /=(column: Expression, value: V) -> Setter where V.Datatype: Number { - return column <- column / value + column <- column / value } public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func %=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column % value + column <- column % value } public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func <<=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column << value + column <- column << value } public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func >>=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column >> value + column <- column >> value } public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func &=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column & value + column <- column & value } public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func |=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column | value + column <- column | value } public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: Expression) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public func ^=(column: Expression, value: V) -> Setter where V.Datatype == Int64 { - return column <- column ^ value + column <- column ^ value } public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) += 1 + Expression(column) += 1 } public postfix func ++(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) += 1 + Expression(column) += 1 } public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) -= 1 + Expression(column) -= 1 } public postfix func --(column: Expression) -> Setter where V.Datatype == Int64 { - return Expression(column) -= 1 + Expression(column) -= 1 } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 44c2c302..045a8274 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -362,14 +362,14 @@ class ConnectionTests: SQLiteTestCase { func test_createCollation_createsCollation() { try! db.createCollation("NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .diacriticInsensitive) + lhs.compare(rhs, options: .diacriticInsensitive) } XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } func test_createCollation_createsQuotableCollation() { try! db.createCollation("NO DIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .diacriticInsensitive) + lhs.compare(rhs, options: .diacriticInsensitive) } XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 0ebcd13c..af1ef110 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -7,7 +7,7 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { func testFunctionNoOptional() { let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { - return "a" + "a" } let result = try! db.prepare("SELECT test()").scalar() as! String XCTAssertEqual("a", result) @@ -15,7 +15,7 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { func testFunctionResultOptional() { let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { - return "a" + "a" } let result = try! db.prepare("SELECT test()").scalar() as! String? XCTAssertEqual("a", result) @@ -30,7 +30,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionNoOptional() { let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a in - return "b"+a + "b" + a } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -38,7 +38,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionLeftOptional() { let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a in - return "b"+a! + "b" + a! } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -46,7 +46,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionResultOptional() { let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a in - return "b"+a + "b" + a } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -54,7 +54,7 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { func testFunctionLeftResultOptional() { let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a: String?) -> String? in - return "b"+a! + "b" + a! } let result = try! db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) @@ -73,7 +73,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testNoOptional() { let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b + a + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -81,7 +81,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testLeftOptional() { let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b + a! + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -89,7 +89,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testRightOptional() { let _: FunctionRightOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b! + a + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -97,7 +97,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testResultOptional() { let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b + a + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) @@ -105,7 +105,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionLeftRightOptional() { let _: FunctionLeftRightOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b! + a! + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) @@ -113,7 +113,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionLeftResultOptional() { let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b + a! + b } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) @@ -121,7 +121,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionRightResultOptional() { let _: FunctionRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a+b! + a + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) @@ -129,7 +129,7 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { func testFunctionLeftRightResultOptional() { let _: FunctionLeftRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in - return a!+b! + a! + b! } let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index b637955b..33be422c 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -185,7 +185,7 @@ class FTS4ConfigTests: XCTestCase { } func sql(_ config: FTS4Config) -> String { - return virtualTable.create(.FTS4(config)) + virtualTable.create(.FTS4(config)) } } diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index 8079b28c..4cce4523 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -122,6 +122,6 @@ class FTS5Tests: XCTestCase { } func sql(_ config: FTS5Config) -> String { - return virtualTable.create(.FTS5(config)) + virtualTable.create(.FTS5(config)) } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 0994dd97..0aa918f0 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -42,10 +42,8 @@ class SQLiteTestCase: XCTestCase { } @discardableResult func insertUser(_ name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { - return try db.run( - "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - "\(name)@example.com", age?.datatypeValue, admin.datatypeValue - ) + try db.run("INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + "\(name)@example.com", age?.datatypeValue, admin.datatypeValue) } func assertSQL(_ SQL: String, _ executions: Int = 1, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { From 9b3781c66197ed1d30894fbc831112b54786f21e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:06:05 +0200 Subject: [PATCH 0767/1046] Unneeded --- Tests/SQLiteTests/ConnectionTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 045a8274..37b765ab 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -217,8 +217,6 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndCommitsSavepoints() { - let db: Connection = db - try! db.savepoint("1") { try db.savepoint("2") { try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") @@ -235,7 +233,6 @@ class ConnectionTests: SQLiteTestCase { } func test_savepoint_beginsAndRollsSavepointsBack() { - let db: Connection = db let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { From 6a1b8f355a808255a850c58d65ca56b1b68df9bf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:32:10 +0200 Subject: [PATCH 0768/1046] Add a release checklist --- .cocoadocs.yml | 2 -- Documentation/Release.md | 10 ++++++ SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Typed/CustomFunctions.swift | 24 ++++++------- .../SQLiteTests/CustomAggregationTests.swift | 34 +++++++++---------- 5 files changed, 41 insertions(+), 31 deletions(-) delete mode 100644 .cocoadocs.yml create mode 100644 Documentation/Release.md diff --git a/.cocoadocs.yml b/.cocoadocs.yml deleted file mode 100644 index 27840032..00000000 --- a/.cocoadocs.yml +++ /dev/null @@ -1,2 +0,0 @@ -additional_guides: - - Documentation/Index.md diff --git a/Documentation/Release.md b/Documentation/Release.md new file mode 100644 index 00000000..ef174683 --- /dev/null +++ b/Documentation/Release.md @@ -0,0 +1,10 @@ +# SQLite.swift Release checklist + +* [ ] Make sure current master branch has a green build +* [ ] Make sure `CHANGELOG.md` is up-to-date +* [ ] Run `pod lib lint` locally +* [ ] Update the version numbers mentioned in `README.md`, `Documentation/Index.md` +* [ ] Update `MARKETING_VERSION` in `SQLite.xcodeproj/project.pbxproj` +* [ ] Create a tag with the version number (`x.y.z`) +* [ ] Publish to CocoaPods: `pod trunk push` +* [ ] Update the release information on GitHub diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 03d8b326..3b5428d0 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -232,6 +232,7 @@ 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; + 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; @@ -496,6 +497,7 @@ children = ( EE247B8F1C3F822500AE3E12 /* Index.md */, EE247B901C3F822500AE3E12 /* Resources */, + 19A17EA3A313F129011B3FA0 /* Release.md */, ); path = Documentation; sourceTree = ""; diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 88fb5aaa..f2d90b0f 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -160,8 +160,8 @@ public extension Connection { } // MARK: - - - public func createAggregation( + + func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, @@ -169,14 +169,14 @@ public extension Connection { reduce: @escaping (T, [Binding?]) -> T, result: @escaping (T) -> Binding? ) { - + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, ptr) in let p = ptr.pointee.assumingMemoryBound(to: T.self) let current = Unmanaged.fromOpaque(p).takeRetainedValue() let next = reduce(current, bindings) ptr.pointee = Unmanaged.passRetained(next).toOpaque() } - + let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in let p = ptr.pointee.assumingMemoryBound(to: T.self) let obj = Unmanaged.fromOpaque(p).takeRetainedValue() @@ -184,17 +184,17 @@ public extension Connection { ptr.deallocate() return value } - + let state: () -> UnsafeMutablePointer = { let p = UnsafeMutablePointer.allocate(capacity: 1) p.pointee = Unmanaged.passRetained(initialValue).toOpaque() return p } - + createAggregation(aggregate, step: step, final: final, state: state) } - - public func createAggregation( + + func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, @@ -202,25 +202,25 @@ public extension Connection { reduce: @escaping (T, [Binding?]) -> T, result: @escaping (T) -> Binding? ) { - + let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, p) in let current = p.pointee let next = reduce(current, bindings) p.pointee = next } - + let final: (UnsafeMutablePointer) -> Binding? = { (p) in let v = result(p.pointee) p.deallocate() return v } - + let state: () -> UnsafeMutablePointer = { let p = UnsafeMutablePointer.allocate(capacity: 1) p.pointee = initialValue return p } - + createAggregation(aggregate, step: step, final: final, state: state) } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index f8efc7e4..05ed619a 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -16,19 +16,19 @@ import SQLite3 class CustomAggregationTests : SQLiteTestCase { override func setUp() { super.setUp() - CreateUsersTable() - try! InsertUser("Alice", age: 30, admin: true) - try! InsertUser("Bob", age: 25, admin: true) - try! InsertUser("Eve", age: 28, admin: false) + createUsersTable() + try! insertUser("Alice", age: 30, admin: true) + try! insertUser("Bob", age: 25, admin: true) + try! insertUser("Eve", age: 28, admin: false) } - + func testUnsafeCustomSum() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v } } - + let final = { (state: UnsafeMutablePointer) -> Binding? in let v = state.pointee let p = UnsafeMutableBufferPointer(start: state, count: 1) @@ -41,13 +41,13 @@ class CustomAggregationTests : SQLiteTestCase { return v.baseAddress! } let result = try! db.prepare("SELECT mySUM1(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 XCTAssertEqual(83, value) } } - + func testUnsafeCustomSumGrouping() { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { @@ -66,11 +66,11 @@ class CustomAggregationTests : SQLiteTestCase { return v.baseAddress! } let result = try! db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([28, 55])) } - + func testCustomSum() { let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 @@ -78,7 +78,7 @@ class CustomAggregationTests : SQLiteTestCase { } let _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 XCTAssertEqual(2083, value) @@ -92,11 +92,11 @@ class CustomAggregationTests : SQLiteTestCase { } let _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([3028, 3055])) } - + func testCustomStringAgg() { let initial = String(repeating: " ", count: 64) let reduce : (String, [Binding?]) -> String = { (last, bindings) in @@ -105,13 +105,13 @@ class CustomAggregationTests : SQLiteTestCase { } let _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? String XCTAssertEqual("\(initial)Alice@example.comBob@example.comEve@example.com", value) } } - + func testCustomObjectSum() { { let initial = TestObject(value: 1000) @@ -126,7 +126,7 @@ class CustomAggregationTests : SQLiteTestCase { { XCTAssertEqual(TestObject.inits, 1) let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") - let i = result.columnNames.index(of: "s")! + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 XCTAssertEqual(1083, value) @@ -143,7 +143,7 @@ class CustomAggregationTests : SQLiteTestCase { class TestObject { static var inits = 0 static var deinits = 0 - + var value: Int64 init(value: Int64) { self.value = value From 75ef800901784da018a4d9eed1942e49e0a82ae1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:37:02 +0200 Subject: [PATCH 0769/1046] Need to update podspec --- Documentation/Release.md | 1 + SQLite.swift.podspec | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/Release.md b/Documentation/Release.md index ef174683..0177fc8f 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -2,6 +2,7 @@ * [ ] Make sure current master branch has a green build * [ ] Make sure `CHANGELOG.md` is up-to-date +* [ ] Update the version number in `SQLite.swift.podspec` * [ ] Run `pod lib lint` locally * [ ] Update the version numbers mentioned in `README.md`, `Documentation/Index.md` * [ ] Update `MARKETING_VERSION` in `SQLite.xcodeproj/project.pbxproj` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 499012c1..f273ffd8 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" s.version = "0.13.0" - s.summary = "A type-safe, Swift-language layer over SQLite3 for iOS and macOS." + s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and @@ -17,13 +17,12 @@ Pod::Spec.new do |s| s.module_name = 'SQLite' s.default_subspec = 'standard' s.swift_versions = ['5'] - - + ios_deployment_target = '9.0' tvos_deployment_target = '9.1' osx_deployment_target = '10.15' watchos_deployment_target = '3.0' - + s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target s.osx.deployment_target = osx_deployment_target From 8ba6c0e6ea34e3b85f71a47511420fe2ad34bddd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 11:45:50 +0200 Subject: [PATCH 0770/1046] https links, cocoadocs is dead --- CONTRIBUTING.md | 2 +- README.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c367b95..6969a705 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ addresses everything. If it doesn’t, continue the conversation there. If your searches return empty, see the [bug](#bugs) or [feature request](#feature-requests) guidelines below. -[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Ask on Stack Overflow]: https://stackoverflow.com/questions/tagged/sqlite.swift [Search]: https://github.com/stephencelis/SQLite.swift/search?type=Issues diff --git a/README.md b/README.md index 98a625c4..ac044dc0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ syntax _and_ intent. - Extensively tested - [SQLCipher][] support via CocoaPods - Active support at - [StackOverflow](http://stackoverflow.com/questions/tagged/sqlite.swift), + [StackOverflow](https://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) (_experimental_) @@ -119,7 +119,7 @@ For a more comprehensive example, see and the [companion repository][SQLiteDataAccessLayer2]. -[Create a Data Access Layer with SQLite.swift and Swift 2]: http://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html +[Create a Data Access Layer with SQLite.swift and Swift 2]: https://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html [SQLiteDataAccessLayer2]: https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master ## Installation @@ -226,7 +226,7 @@ device: [Xcode]: https://developer.apple.com/xcode/downloads/ -[Submodule]: http://git-scm.com/book/en/Git-Tools-Submodules +[Submodule]: https://git-scm.com/book/en/Git-Tools-Submodules [download]: https://github.com/stephencelis/SQLite.swift/archive/master.zip @@ -243,7 +243,7 @@ device: [See the planning document]: /Documentation/Planning.md [Read the contributing guidelines]: ./CONTRIBUTING.md#contributing -[Ask on Stack Overflow]: http://stackoverflow.com/questions/tagged/sqlite.swift +[Ask on Stack Overflow]: https://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new [Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork @@ -280,16 +280,16 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) [Swift]: https://swift.org/ -[SQLite3]: http://www.sqlite.org +[SQLite3]: https://www.sqlite.org [SQLite.swift]: https://github.com/stephencelis/SQLite.swift [GitHubActionBadge]: https://img.shields.io/github/workflow/status/stephencelis/SQLite.swift/Build%20and%20test [CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png -[CocoaPodsVersionLink]: http://cocoadocs.org/docsets/SQLite.swift +[CocoaPodsVersionLink]: https://cocoapods.org/pods/SQLite.swift [PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png -[PlatformLink]: http://cocoadocs.org/docsets/SQLite.swift +[PlatformLink]: https://cocoapods.org/pods/SQLite.swift [CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat [CarthageLink]: https://github.com/Carthage/Carthage From 66e01e2191707158dff6127ce1055fb084f8f2b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 12:03:19 +0200 Subject: [PATCH 0771/1046] Fix lint post merge --- .swiftlint.yml | 8 +++-- Sources/SQLite/Core/Connection.swift | 32 ++++++++--------- Sources/SQLite/Typed/CoreFunctions.swift | 1 - Sources/SQLite/Typed/CustomFunctions.swift | 36 +++++++++---------- .../SQLiteTests/CustomAggregationTests.swift | 24 +++++++------ 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 3bfc5fa5..b5e53d56 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -28,11 +28,15 @@ type_body_length: warning: 260 error: 260 +function_body_length: + warning: 60 + error: 60 + line_length: warning: 150 error: 150 ignores_comments: true file_length: - warning: 760 - error: 760 + warning: 900 + error: 900 diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 07c85373..2bea05c8 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -35,6 +35,7 @@ import SQLite3 #endif /// A connection to SQLite. +// swiftlint:disable:next type_body_length public final class Connection { /// The location of a SQLite database. @@ -585,7 +586,7 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - // swiftlint:disable:next cyclomatic_complexity function_body_length + // swiftlint:disable:next cyclomatic_complexity public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 @@ -636,7 +637,7 @@ public final class Connection { if functions[function] == nil { functions[function] = [:] } functions[function]?[argc] = box } - + /// Creates or redefines a custom SQL aggregate. /// /// - Parameters: @@ -665,19 +666,19 @@ public final class Connection { /// - state: A block of code to run to produce a fresh state variable for /// each aggregation group. The block should return an /// UnsafeMutablePointer to the fresh state variable. + // swiftlint:disable:next cyclomatic_complexity public func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, - step: @escaping ([Binding?], UnsafeMutablePointer) -> (), + step: @escaping ([Binding?], UnsafeMutablePointer) -> Void, final: @escaping (UnsafeMutablePointer) -> Binding?, state: @escaping () -> UnsafeMutablePointer) { - - + let argc = argumentCount.map { Int($0) } ?? -1 - let box : Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? - let p = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) + let mutablePointer = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) if stepFlag > 0 { let arguments: [Binding?] = (0.. Binding? ) { - let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, ptr) in - let p = ptr.pointee.assumingMemoryBound(to: T.self) - let current = Unmanaged.fromOpaque(p).takeRetainedValue() + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, ptr) in + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let current = Unmanaged.fromOpaque(pointer).takeRetainedValue() let next = reduce(current, bindings) ptr.pointee = Unmanaged.passRetained(next).toOpaque() } let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in - let p = ptr.pointee.assumingMemoryBound(to: T.self) - let obj = Unmanaged.fromOpaque(p).takeRetainedValue() + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let obj = Unmanaged.fromOpaque(pointer).takeRetainedValue() let value = result(obj) ptr.deallocate() return value } let state: () -> UnsafeMutablePointer = { - let p = UnsafeMutablePointer.allocate(capacity: 1) - p.pointee = Unmanaged.passRetained(initialValue).toOpaque() - return p + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque() + return pointer } createAggregation(aggregate, step: step, final: final, state: state) @@ -203,22 +203,22 @@ public extension Connection { result: @escaping (T) -> Binding? ) { - let step: ([Binding?], UnsafeMutablePointer) -> () = { (bindings, p) in - let current = p.pointee + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, pointer) in + let current = pointer.pointee let next = reduce(current, bindings) - p.pointee = next + pointer.pointee = next } - let final: (UnsafeMutablePointer) -> Binding? = { (p) in - let v = result(p.pointee) - p.deallocate() - return v + let final: (UnsafeMutablePointer) -> Binding? = { pointer in + let value = result(pointer.pointee) + pointer.deallocate() + return value } let state: () -> UnsafeMutablePointer = { - let p = UnsafeMutablePointer.allocate(capacity: 1) - p.pointee = initialValue - return p + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = initialValue + return pointer } createAggregation(aggregate, step: step, final: final, state: state) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 05ed619a..99cbb00d 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -13,7 +13,7 @@ import CSQLite import SQLite3 #endif -class CustomAggregationTests : SQLiteTestCase { +class CustomAggregationTests: SQLiteTestCase { override func setUp() { super.setUp() createUsersTable() @@ -35,7 +35,7 @@ class CustomAggregationTests : SQLiteTestCase { p.deallocate() return v } - let _ = db.createAggregation("mySUM1", step: step, final: final) { + _ = db.createAggregation("mySUM1", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -60,7 +60,7 @@ class CustomAggregationTests : SQLiteTestCase { p.deallocate() return v } - let _ = db.createAggregation("mySUM2", step: step, final: final) { + _ = db.createAggregation("mySUM2", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -72,11 +72,11 @@ class CustomAggregationTests : SQLiteTestCase { } func testCustomSum() { - let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } - let _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) + _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -86,11 +86,11 @@ class CustomAggregationTests : SQLiteTestCase { } func testCustomSumGrouping() { - let reduce : (Int64, [Binding?]) -> Int64 = { (last, bindings) in + let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } - let _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) + _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } @@ -99,11 +99,11 @@ class CustomAggregationTests : SQLiteTestCase { func testCustomStringAgg() { let initial = String(repeating: " ", count: 64) - let reduce : (String, [Binding?]) -> String = { (last, bindings) in + let reduce: (String, [Binding?]) -> String = { (last, bindings) in let v = (bindings[0] as? String) ?? "" return last + v } - let _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) + _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -115,14 +115,16 @@ class CustomAggregationTests : SQLiteTestCase { func testCustomObjectSum() { { let initial = TestObject(value: 1000) - let reduce : (TestObject, [Binding?]) -> TestObject = { (last, bindings) in + let reduce: (TestObject, [Binding?]) -> TestObject = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return TestObject(value: last.value + v) } - let _ = db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) + db.createAggregation("myReduceSUMX", initialValue: initial, reduce: reduce, result: { $0.value }) // end this scope to ensure that the initial value is retained // by the createAggregation call. + // swiftlint:disable:next trailing_semicolon }(); + { XCTAssertEqual(TestObject.inits, 1) let result = try! db.prepare("SELECT myReduceSUMX(age) AS s FROM users") From 985b2e4d8c21f94879a084c5d5becafee4821dfe Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 12:04:41 +0200 Subject: [PATCH 0772/1046] Fix warnings --- Tests/SQLiteTests/CustomAggregationTests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 99cbb00d..337a5aa8 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -35,7 +35,7 @@ class CustomAggregationTests: SQLiteTestCase { p.deallocate() return v } - _ = db.createAggregation("mySUM1", step: step, final: final) { + db.createAggregation("mySUM1", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -60,7 +60,7 @@ class CustomAggregationTests: SQLiteTestCase { p.deallocate() return v } - _ = db.createAggregation("mySUM2", step: step, final: final) { + db.createAggregation("mySUM2", step: step, final: final) { let v = UnsafeMutableBufferPointer.allocate(capacity: 1) v[0] = 0 return v.baseAddress! @@ -76,7 +76,7 @@ class CustomAggregationTests: SQLiteTestCase { let v = (bindings[0] as? Int64) ?? 0 return last + v } - _ = db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) + db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -90,7 +90,7 @@ class CustomAggregationTests: SQLiteTestCase { let v = (bindings[0] as? Int64) ?? 0 return last + v } - _ = db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) + db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } @@ -103,7 +103,7 @@ class CustomAggregationTests: SQLiteTestCase { let v = (bindings[0] as? String) ?? "" return last + v } - _ = db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) + db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { From 896d2978548a336bcade3c8e1b3076f7b2205c55 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 13:16:20 +0200 Subject: [PATCH 0773/1046] Fail build if there are compiler warnings Closes #1065 --- .github/workflows/build.yml | 8 ++++---- Sources/SQLite/Core/Connection.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1119284a..af6666ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,10 @@ jobs: brew outdated swiftlint || brew upgrade swiftlint - name: "Lint" run: make lint + - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" + env: + PACKAGE_MANAGER_COMMAND: test -Xswiftc -warnings-as-errors + run: ./run-tests.sh - name: "Run tests (BUILD_SCHEME: SQLite iOS)" env: BUILD_SCHEME: SQLite iOS @@ -57,7 +61,3 @@ jobs: env: CARTHAGE_PLATFORM: tvOS run: ./run-tests.sh - - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" - env: - PACKAGE_MANAGER_COMMAND: test - run: ./run-tests.sh diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 2bea05c8..fa086181 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -434,7 +434,7 @@ public final class Connection { } } - @available(OSX, deprecated: 10.2) + @available(OSX, deprecated: 10.12) @available(iOS, deprecated: 10.0) @available(watchOS, deprecated: 3.0) @available(tvOS, deprecated: 10.0) From 559b796187ab1b71148ac68f37c4b2e23db3b49a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 13:42:34 +0200 Subject: [PATCH 0774/1046] Simplify CocoaPods linting Closes #1066 --- Tests/CocoaPods/.gitignore | 1 - Tests/CocoaPods/Gemfile | 4 -- Tests/CocoaPods/Gemfile.lock | 96 ----------------------------- Tests/CocoaPods/Makefile | 15 ----- Tests/CocoaPods/integration_test.rb | 35 ----------- run-tests.sh | 8 ++- 6 files changed, 6 insertions(+), 153 deletions(-) delete mode 100644 Tests/CocoaPods/.gitignore delete mode 100644 Tests/CocoaPods/Gemfile delete mode 100644 Tests/CocoaPods/Gemfile.lock delete mode 100644 Tests/CocoaPods/Makefile delete mode 100755 Tests/CocoaPods/integration_test.rb diff --git a/Tests/CocoaPods/.gitignore b/Tests/CocoaPods/.gitignore deleted file mode 100644 index 4cf24de2..00000000 --- a/Tests/CocoaPods/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gems/ diff --git a/Tests/CocoaPods/Gemfile b/Tests/CocoaPods/Gemfile deleted file mode 100644 index da52ec89..00000000 --- a/Tests/CocoaPods/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source 'https://rubygems.org' - -gem 'cocoapods', '~> 1.10.2' -gem 'minitest' diff --git a/Tests/CocoaPods/Gemfile.lock b/Tests/CocoaPods/Gemfile.lock deleted file mode 100644 index e0383a67..00000000 --- a/Tests/CocoaPods/Gemfile.lock +++ /dev/null @@ -1,96 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.3) - activesupport (5.2.6) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - claide (1.0.3) - cocoapods (1.10.2) - addressable (~> 2.6) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.10.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.6.6) - nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.19.0, < 2.0) - cocoapods-core (1.10.2) - activesupport (> 5.0, < 6) - addressable (~> 2.6) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.4.0) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.5.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.1.9) - escape (0.0.4) - ethon (0.14.0) - ffi (>= 1.15.0) - ffi (1.15.3) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.8.10) - concurrent-ruby (~> 1.0) - json (2.5.1) - minitest (5.11.3) - molinillo (0.6.6) - nanaimo (0.3.0) - nap (1.1.0) - netrc (0.11.0) - public_suffix (4.0.6) - rexml (3.2.5) - ruby-macho (1.4.0) - thread_safe (0.3.6) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (1.2.9) - thread_safe (~> 0.1) - xcodeproj (1.21.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods (~> 1.10.2) - minitest - -BUNDLED WITH - 2.2.22 diff --git a/Tests/CocoaPods/Makefile b/Tests/CocoaPods/Makefile deleted file mode 100644 index 532dcbff..00000000 --- a/Tests/CocoaPods/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -XCPRETTY := $(shell command -v xcpretty) - -test: install repo_update - @set -e; \ - for test in *_test.rb; do \ - bundle exec ./$$test | $(XCPRETTY) -c; \ - done - -repo_update: - @bundle exec pod repo update --silent - -install: - @bundle install --path gems - -.PHONY: test install diff --git a/Tests/CocoaPods/integration_test.rb b/Tests/CocoaPods/integration_test.rb deleted file mode 100755 index 67b13385..00000000 --- a/Tests/CocoaPods/integration_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env ruby - -require 'cocoapods' -require 'cocoapods/validator' -require 'minitest/autorun' - -class IntegrationTest < Minitest::Test - - def test_validate_project - assert validator.validate, "validation failed: #{validator.failure_reason}" - end - - private - - def validator - @validator ||= Pod::Validator.new(podspec, ['https://github.com/CocoaPods/Specs.git']).tap do |validator| - validator.config.verbose = true - validator.no_clean = true - validator.use_frameworks = true - validator.fail_fast = true - validator.local = true - validator.allow_warnings = true - subspec = ENV['VALIDATOR_SUBSPEC'] - if subspec == 'none' - validator.no_subspecs = true - else - validator.only_subspec = subspec - end - end - end - - def podspec - File.expand_path(File.dirname(__FILE__) + '/../../SQLite.swift.podspec') - end -end diff --git a/run-tests.sh b/run-tests.sh index 0a105c41..edc60987 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,9 +7,13 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - cd Tests/CocoaPods && make test + if [ "$VALIDATOR_SUBSPEC" == "none" ]; then + pod lib lint --no-subspecs + else + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" + fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift ${PACKAGE_MANAGER_COMMAND} + swift "${PACKAGE_MANAGER_COMMAND}" fi From 00b7d3f2eea7a5d98941f88f05c4f9c38eb04654 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 13:51:00 +0200 Subject: [PATCH 0775/1046] rm obsolete swiftcov --- Makefile | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index a9d29d58..0b9a1606 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,6 @@ else endif XCPRETTY := $(shell command -v xcpretty) -SWIFTCOV := $(shell command -v swiftcov) -GCOVR := $(shell command -v gcovr) TEST_ACTIONS := clean build build-for-testing test-without-building default: test @@ -28,36 +26,14 @@ else $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) endif -coverage: -ifdef SWIFTCOV - $(SWIFTCOV) generate --output coverage \ - $(BUILD_TOOL) $(BUILD_ARGUMENTS) -configuration Release test \ - -- ./SQLite/*.swift -ifdef GCOVR - $(GCOVR) \ - --root . \ - --use-gcov-files \ - --html \ - --html-details \ - --output coverage/index.html \ - --keep -else - @echo gcovr must be installed for HTML output: https://github.com/gcovr/gcovr -endif -else - @echo swiftcov must be installed for coverage: https://github.com/realm/SwiftCov - @exit 1 -endif - clean: $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean - rm -r coverage repl: @$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' sloc: - @zsh -c "grep -vE '^ *//|^$$' SQLite/*/*.{swift,h,m} | wc -l" + @zsh -c "grep -vE '^ *//|^$$' Sources/**/*.{swift,h,m} | wc -l" -.PHONY: test coverage clean repl sloc +.PHONY: test clean repl sloc From 325cfd9480f59a280f4bfe64eb05b1b9288a6402 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:00:42 +0200 Subject: [PATCH 0776/1046] Unquote --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index edc60987..1ae7a5ca 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -15,5 +15,5 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift "${PACKAGE_MANAGER_COMMAND}" + swift ${PACKAGE_MANAGER_COMMAND} fi From 05c84149a49ff2c41b3f15c1f971b736985c9553 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:12:21 +0200 Subject: [PATCH 0777/1046] Treat warnings as errors --- SQLite.xcodeproj/project.pbxproj | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3b5428d0..ea7ab871 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -231,8 +231,8 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; - 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; @@ -1042,6 +1042,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1049,6 +1050,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; @@ -1062,6 +1064,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1069,6 +1072,7 @@ PRODUCT_NAME = SQLite; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; @@ -1076,11 +1080,13 @@ 03A65E6D1C6BB0F60062603F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; @@ -1088,11 +1094,13 @@ 03A65E6E1C6BB0F60062603F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; @@ -1107,6 +1115,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1114,6 +1123,7 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 3.0; }; @@ -1129,6 +1139,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1136,6 +1147,7 @@ PRODUCT_NAME = SQLite; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 3.0; }; @@ -1271,6 +1283,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -1280,6 +1293,7 @@ PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; @@ -1293,6 +1307,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -1301,26 +1316,31 @@ PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; EE247AEB1C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; EE247AEC1C3F04ED00AE3E12 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; @@ -1335,6 +1355,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; @@ -1343,6 +1364,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; @@ -1357,6 +1379,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; @@ -1365,6 +1388,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = ""; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; @@ -1373,11 +1397,13 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Debug; }; @@ -1386,11 +1412,13 @@ buildSettings = { CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; }; name = Release; }; From fdac6551b06350181904eab08a0be0a4be4a2160 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:13:58 +0200 Subject: [PATCH 0778/1046] fail fast --- run-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 1ae7a5ca..32465388 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,9 +8,9 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs + pod lib lint --no-subspecs --fail-fast else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From 6d5bb0ca1d561589cbcf5ffbacd154e5c05659cf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 23 Aug 2021 14:52:47 +0200 Subject: [PATCH 0779/1046] Initialize pointer --- Sources/SQLite/Typed/CustomFunctions.swift | 2 +- Tests/SQLiteTests/CustomAggregationTests.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/CustomFunctions.swift b/Sources/SQLite/Typed/CustomFunctions.swift index 1d66d075..c38113d1 100644 --- a/Sources/SQLite/Typed/CustomFunctions.swift +++ b/Sources/SQLite/Typed/CustomFunctions.swift @@ -217,7 +217,7 @@ public extension Connection { let state: () -> UnsafeMutablePointer = { let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.pointee = initialValue + pointer.initialize(to: initialValue) return pointer } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 337a5aa8..d4569eef 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -105,6 +105,7 @@ class CustomAggregationTests: SQLiteTestCase { } db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") + let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? String From 9bfc767ce660df89eaacd35e2ff23c8465c3ac3b Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 24 Aug 2021 10:36:16 +0200 Subject: [PATCH 0780/1046] Triggering GitHub Actions workflow From 7ec7a266bf3cc071db3a6dbdb2c1628ebf3e212c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 24 Aug 2021 17:49:52 +0200 Subject: [PATCH 0781/1046] Fix truncated strings in function Closes #468 --- Sources/SQLite/Core/Connection.swift | 2 +- Tests/SQLiteTests/CustomFunctionsTests.swift | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index fa086181..b2bc2cbd 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -616,7 +616,7 @@ public final class Connection { } else if let result = result as? Int64 { sqlite3_result_int64(context, result) } else if let result = result as? String { - sqlite3_result_text(context, result, Int32(result.count), SQLITE_TRANSIENT) + sqlite3_result_text(context, result, Int32(result.lengthOfBytes(using: .utf8)), SQLITE_TRANSIENT) } else if result == nil { sqlite3_result_null(context) } else { diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index af1ef110..8e702e9c 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -135,3 +135,12 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { XCTAssertEqual("ab", result) } } + +class CustomFunctionTruncation: SQLiteTestCase { + // https://github.com/stephencelis/SQLite.swift/issues/468 + func testStringTruncation() { + _ = try! db.createFunction("customLower") { (value: String) in value.lowercased() } + let result = try! db.prepare("SELECT customLower(?)").scalar("TÖL-AA 12") as? String + XCTAssertEqual("töl-aa 12", result) + } +} From 4b4dc65997c1fce4735178c7563285b988133c6a Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 10:53:04 +0200 Subject: [PATCH 0782/1046] Make reference to libsqlite3.tbd non-version specific on macOS https://github.com/stephencelis/SQLite.swift/pull/846 --- SQLite.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ea7ab871..fcdb521c 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -286,7 +286,7 @@ EE247B911C3F822500AE3E12 /* installation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "installation@2x.png"; sourceTree = ""; }; EE247B921C3F822600AE3E12 /* playground@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "playground@2x.png"; sourceTree = ""; }; EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = SQLite.swift.podspec; sourceTree = ""; }; - EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + EE9180911C46E9D30038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; EE9180931C46EA210038162A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ From be6bed351703b1e4c054e7ef403a50ca8de53e91 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 11:06:07 +0200 Subject: [PATCH 0783/1046] Renamed --- Documentation/Index.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 5cc678a6..fce068a2 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -262,10 +262,8 @@ If you have bundled it in your application, you can use FileManager to copy it t ```swift func copyDatabaseIfNeeded(sourcePath: String) -> Bool { - let path = NSSearchPathForDirectoriesInDomains( - .documentDirectory, .userDomainMask, true - ).first! - let destinationPath = destinationPath + "/db.sqlite3" + let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! + let destinationPath = documents + "/db.sqlite3" let exists = FileManager.default.fileExists(atPath: destinationPath) guard !exists else { return false } do { From 03823fb0ccf57628db29d8644b1e09d0bb6cc2f8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 12:44:22 +0200 Subject: [PATCH 0784/1046] Add Linux tests Closes #1064 --- .github/workflows/build.yml | 10 ++++++++ Documentation/Linux.md | 24 +++++++++++++++++++ SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Core/Connection.swift | 16 ++++++++++--- Tests/LinuxMain.swift | 6 ----- Tests/SQLiteTests/ConnectionTests.swift | 3 +++ .../SQLiteTests/CustomAggregationTests.swift | 4 ++++ Tests/SQLiteTests/CustomFunctionsTests.swift | 5 ++++ Tests/SQLiteTests/QueryTests.swift | 2 ++ 9 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 Documentation/Linux.md delete mode 100644 Tests/LinuxMain.swift diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af6666ba..51654fba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,3 +61,13 @@ jobs: env: CARTHAGE_PLATFORM: tvOS run: ./run-tests.sh + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install + run: | + sudo apt-get update -qq + sudo apt-get install -y libsqlite3-dev + - name: Test + run: swift test diff --git a/Documentation/Linux.md b/Documentation/Linux.md new file mode 100644 index 00000000..19189c53 --- /dev/null +++ b/Documentation/Linux.md @@ -0,0 +1,24 @@ +# Linux + +## Debugging + +### Create and launch docker container + +```shell +$ docker container create swift:focal +$ docker run --cap-add=SYS_PTRACE \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + -i -t swift:focal bash +``` + +### Compile and run tests in debugger + +```shell +$ apt-get update && apt-get install libsqlite3-dev +$ git clone https://github.com/stephencelis/SQLite.swift.git +$ swift test +$ lldb .build/x86_64-unknown-linux-gnu/debug/SQLite.swiftPackageTests.xctest +(lldb) target create ".build/x86_64-unknown-linux-gnu/debug/SQLite.swiftPackageTests.xctest" +(lldb) run +``` diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fcdb521c..6aff8045 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -227,6 +227,7 @@ 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; + 19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; @@ -498,6 +499,7 @@ EE247B8F1C3F822500AE3E12 /* Index.md */, EE247B901C3F822500AE3E12 /* Resources */, 19A17EA3A313F129011B3FA0 /* Release.md */, + 19A1794B7972D14330A65BBD /* Linux.md */, ); path = Documentation; sourceTree = ""; diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index b2bc2cbd..39f19a5c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -629,12 +629,22 @@ public final class Connection { flags |= SQLITE_DETERMINISTIC } #endif - sqlite3_create_function_v2(handle, function, Int32(argc), flags, - unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in + let resultCode = sqlite3_create_function_v2(handle, + function, + Int32(argc), + flags, + unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { context, argc, value in let function = unsafeBitCast(sqlite3_user_data(context), to: Function.self) function(context, argc, value) }, nil, nil, nil) - if functions[function] == nil { functions[function] = [:] } + + if let result = Result(errorCode: resultCode, connection: self, statement: nil) { + fatalError("Error creating function: \(result)") + } + + if functions[function] == nil { + functions[function] = [:] // fails on Linux, https://bugs.swift.org/browse/SR-4429 + } functions[function]?[argc] = box } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 59796fde..00000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,6 +0,0 @@ -import XCTest -@testable import SQLiteTests - -XCTMain([ -testCase([ -])]) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 37b765ab..6c4d170a 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -343,6 +343,8 @@ class ConnectionTests: SQLiteTestCase { } } + // https://bugs.swift.org/browse/SR-4429 + #if !os(Linux) func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } @@ -390,6 +392,7 @@ class ConnectionTests: SQLiteTestCase { } } } + #endif func test_concurrent_access_single_connection() { // test can fail on iOS/tvOS 9.x: SQLite compile-time differences? diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index d4569eef..142d5d6e 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -13,6 +13,9 @@ import CSQLite import SQLite3 #endif +// https://bugs.swift.org/browse/SR-4429 +#if !os(Linux) + class CustomAggregationTests: SQLiteTestCase { override func setUp() { super.setUp() @@ -139,6 +142,7 @@ class CustomAggregationTests: SQLiteTestCase { XCTAssertEqual(TestObject.deinits, 3) // the initial value is still retained by the aggregate's state block, so deinits is one less than inits } } +#endif /// This class is used to test that aggregation state variables /// can be reference types and are properly memory managed when diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 8e702e9c..caa48acd 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,6 +1,9 @@ import XCTest import SQLite +// https://bugs.swift.org/browse/SR-4429 +#if !os(Linux) + class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> Expression typealias FunctionResultOptional = () -> Expression @@ -144,3 +147,5 @@ class CustomFunctionTruncation: SQLiteTestCase { XCTAssertEqual("töl-aa 12", result) } } + +#endif diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 3a58e2fc..c80dd0c7 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -290,6 +290,7 @@ class QueryTests: XCTestCase { ) } + #if !os(Linux) // depends on exact JSON serialization func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, @@ -307,6 +308,7 @@ class QueryTests: XCTestCase { insert ) } + #endif func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { assertSQL( From 7d95523caa831ac0c59c98dd5cbe49675dc74a25 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 15:50:03 +0200 Subject: [PATCH 0785/1046] Link to GH issue --- Sources/SQLite/Core/Connection.swift | 2 +- Tests/SQLiteTests/ConnectionTests.swift | 2 +- Tests/SQLiteTests/CustomAggregationTests.swift | 2 +- Tests/SQLiteTests/CustomFunctionsTests.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 39f19a5c..2c8c408c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -643,7 +643,7 @@ public final class Connection { } if functions[function] == nil { - functions[function] = [:] // fails on Linux, https://bugs.swift.org/browse/SR-4429 + functions[function] = [:] // fails on Linux, https://github.com/stephencelis/SQLite.swift/issues/1071 } functions[function]?[argc] = box } diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 6c4d170a..5bc4d42f 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -343,7 +343,7 @@ class ConnectionTests: SQLiteTestCase { } } - // https://bugs.swift.org/browse/SR-4429 + // https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) func test_createFunction_withArrayArguments() { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 142d5d6e..d5232260 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -13,7 +13,7 @@ import CSQLite import SQLite3 #endif -// https://bugs.swift.org/browse/SR-4429 +// https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) class CustomAggregationTests: SQLiteTestCase { diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index caa48acd..3d897f8e 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -1,7 +1,7 @@ import XCTest import SQLite -// https://bugs.swift.org/browse/SR-4429 +// https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) class CustomFunctionNoArgsTests: SQLiteTestCase { From 5e5ba530c064f93cff583923ae2455f0aaa67d15 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 16:00:59 +0200 Subject: [PATCH 0786/1046] Link to Linux doc --- Documentation/Linux.md | 5 +++++ README.md | 1 + 2 files changed, 6 insertions(+) diff --git a/Documentation/Linux.md b/Documentation/Linux.md index 19189c53..c9e85254 100644 --- a/Documentation/Linux.md +++ b/Documentation/Linux.md @@ -1,5 +1,10 @@ # Linux +## Limitations + +* Custom functions are currently not supported and crash, caused by a bug in Swift. +See [#1071](https://github.com/stephencelis/SQLite.swift/issues/1071). + ## Debugging ### Create and launch docker container diff --git a/README.md b/README.md index ac044dc0..9242b200 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ syntax _and_ intent. - [Well-documented][See Documentation] - Extensively tested - [SQLCipher][] support via CocoaPods + - Works on [Linux](Documentation/Linux.md) (with some limitations) - Active support at [StackOverflow](https://stackoverflow.com/questions/tagged/sqlite.swift), and [Gitter Chat Room](https://gitter.im/stephencelis/SQLite.swift) From 0dd440cfefa79f9088547a0b5ee440736de87bf7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 16:04:47 +0200 Subject: [PATCH 0787/1046] FTS5 --- Documentation/Linux.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Linux.md b/Documentation/Linux.md index c9e85254..640ced6f 100644 --- a/Documentation/Linux.md +++ b/Documentation/Linux.md @@ -4,6 +4,7 @@ * Custom functions are currently not supported and crash, caused by a bug in Swift. See [#1071](https://github.com/stephencelis/SQLite.swift/issues/1071). +* FTS5 might not work, see [#1007](https://github.com/stephencelis/SQLite.swift/issues/1007) ## Debugging From 8583eb66469d69cc8e003d18ac5d8a5391bc14c6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:37:19 +0200 Subject: [PATCH 0788/1046] Closes #285 --- SQLite.xcodeproj/project.pbxproj | 8 + Tests/SQLiteTests/QueryIntegrationTests.swift | 222 ++++++++++++++++++ Tests/SQLiteTests/QueryTests.swift | 203 ---------------- 3 files changed, 230 insertions(+), 203 deletions(-) create mode 100644 Tests/SQLiteTests/QueryIntegrationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 6aff8045..6efd3a47 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; @@ -60,6 +61,7 @@ 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; @@ -71,6 +73,7 @@ 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; @@ -231,6 +234,7 @@ 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; + 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; @@ -430,6 +434,7 @@ 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, + 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -861,6 +866,7 @@ 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, + 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -951,6 +957,7 @@ 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, + 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1011,6 +1018,7 @@ 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, + 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift new file mode 100644 index 00000000..9f5dc5aa --- /dev/null +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -0,0 +1,222 @@ +import XCTest +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif +@testable import SQLite + +class QueryIntegrationTests: SQLiteTestCase { + + let id = Expression("id") + let email = Expression("email") + let age = Expression("age") + + override func setUp() { + super.setUp() + + createUsersTable() + } + + // MARK: - + + func test_select() { + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + _ = user[users[managerId]] + } + } + + func test_prepareRowIterator() { + let names = ["a", "b", "c"] + try! insertUsers(names) + + let emailColumn = Expression("email") + let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } + + func test_ambiguousMap() { + let names = ["a", "b", "c"] + try! insertUsers(names) + + let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } + + func test_select_optional() { + let managerId = Expression("manager_id") + let managers = users.alias("managers") + + let alice = try! db.run(users.insert(email <- "alice@example.com")) + _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + + for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + _ = user[users[managerId]] + } + } + + func test_select_codable() throws { + let table = Table("codable") + try db.run(table.create { builder in + builder.column(Expression("int")) + builder.column(Expression("string")) + builder.column(Expression("bool")) + builder.column(Expression("float")) + builder.column(Expression("double")) + builder.column(Expression("date")) + builder.column(Expression("optional")) + builder.column(Expression("sub")) + }) + + let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, + date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, + date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) + + try db.run(table.insert(value)) + + let rows = try db.prepare(table) + let values: [TestCodable] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 1) + XCTAssertEqual(values[0].int, 5) + XCTAssertEqual(values[0].string, "6") + XCTAssertEqual(values[0].bool, true) + XCTAssertEqual(values[0].float, 7) + XCTAssertEqual(values[0].double, 8) + XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) + XCTAssertEqual(values[0].optional, "optional") + XCTAssertEqual(values[0].sub?.int, 1) + XCTAssertEqual(values[0].sub?.string, "2") + XCTAssertEqual(values[0].sub?.bool, true) + XCTAssertEqual(values[0].sub?.float, 3) + XCTAssertEqual(values[0].sub?.double, 4) + XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0)) + XCTAssertNil(values[0].sub?.optional) + XCTAssertNil(values[0].sub?.sub) + } + + func test_scalar() { + XCTAssertEqual(0, try! db.scalar(users.count)) + XCTAssertEqual(false, try! db.scalar(users.exists)) + + try! insertUsers("alice") + XCTAssertEqual(1, try! db.scalar(users.select(id.average))) + } + + func test_pluck() { + let rowid = try! db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(rowid, try! db.pluck(users)![id]) + } + + func test_insert() { + let id = try! db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(1, id) + } + + func test_insert_many() { + let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) + XCTAssertEqual(2, id) + } + + func test_upsert() throws { + guard db.satisfiesMinimumVersion(minor: 24) else { return } + let fetchAge = { () throws -> Int? in + try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } + } + + let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) + XCTAssertEqual(1, id) + XCTAssertEqual(30, try fetchAge()) + + let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) + XCTAssertEqual(1, nextId) + XCTAssertEqual(42, try fetchAge()) + } + + func test_update() { + let changes = try! db.run(users.update(email <- "alice@example.com")) + XCTAssertEqual(0, changes) + } + + func test_delete() { + let changes = try! db.run(users.delete()) + XCTAssertEqual(0, changes) + } + + func test_union() throws { + let expectedIDs = [ + try db.run(users.insert(email <- "alice@example.com")), + try db.run(users.insert(email <- "sally@example.com")) + ] + + let query1 = users.filter(email == "alice@example.com") + let query2 = users.filter(email == "sally@example.com") + + let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } + XCTAssertEqual(expectedIDs, actualIDs) + + let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") + let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") + + print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) + + let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } + XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) + } + + func test_no_such_column() throws { + let doesNotExist = Expression("doesNotExist") + try! insertUser("alice") + let row = try! db.pluck(users.filter(email == "alice@example.com"))! + + XCTAssertThrowsError(try row.get(doesNotExist)) { error in + if case QueryError.noSuchColumn(let name, _) = error { + XCTAssertEqual("\"doesNotExist\"", name) + } else { + XCTFail("unexpected error: \(error)") + } + } + } + + func test_catchConstraintError() { + try! db.run(users.insert(email <- "alice@example.com")) + do { + try db.run(users.insert(email <- "alice@example.com")) + XCTFail("expected error") + } catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { + // expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } + + // https://github.com/stephencelis/SQLite.swift/issues/285 + func test_order_by_random() throws { + try insertUsers(["a", "b", "c'"]) + let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) + XCTAssertEqual(1, result.count) + } +} + + +private extension Connection { + func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { + guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } + let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } + guard components.count == 3 else { return false } + + return components[1] >= minor && components[2] >= patch + } +} diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index c80dd0c7..e0749f2c 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -468,206 +468,3 @@ class QueryTests: XCTestCase { } } - -class QueryIntegrationTests: SQLiteTestCase { - - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") - - override func setUp() { - super.setUp() - - createUsersTable() - } - - // MARK: - - - func test_select() { - let managerId = Expression("manager_id") - let managers = users.alias("managers") - - let alice = try! db.run(users.insert(email <- "alice@example.com")) - _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - - for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { - _ = user[users[managerId]] - } - } - - func test_prepareRowIterator() { - let names = ["a", "b", "c"] - try! insertUsers(names) - - let emailColumn = Expression("email") - let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } - - XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) - } - - func test_ambiguousMap() { - let names = ["a", "b", "c"] - try! insertUsers(names) - - let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } - - XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) - } - - func test_select_optional() { - let managerId = Expression("manager_id") - let managers = users.alias("managers") - - let alice = try! db.run(users.insert(email <- "alice@example.com")) - _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - - for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { - _ = user[users[managerId]] - } - } - - func test_select_codable() throws { - let table = Table("codable") - try db.run(table.create { builder in - builder.column(Expression("int")) - builder.column(Expression("string")) - builder.column(Expression("bool")) - builder.column(Expression("float")) - builder.column(Expression("double")) - builder.column(Expression("date")) - builder.column(Expression("optional")) - builder.column(Expression("sub")) - }) - - let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) - let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, - date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) - - try db.run(table.insert(value)) - - let rows = try db.prepare(table) - let values: [TestCodable] = try rows.map({ try $0.decode() }) - XCTAssertEqual(values.count, 1) - XCTAssertEqual(values[0].int, 5) - XCTAssertEqual(values[0].string, "6") - XCTAssertEqual(values[0].bool, true) - XCTAssertEqual(values[0].float, 7) - XCTAssertEqual(values[0].double, 8) - XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) - XCTAssertEqual(values[0].optional, "optional") - XCTAssertEqual(values[0].sub?.int, 1) - XCTAssertEqual(values[0].sub?.string, "2") - XCTAssertEqual(values[0].sub?.bool, true) - XCTAssertEqual(values[0].sub?.float, 3) - XCTAssertEqual(values[0].sub?.double, 4) - XCTAssertEqual(values[0].sub?.date, Date(timeIntervalSince1970: 0)) - XCTAssertNil(values[0].sub?.optional) - XCTAssertNil(values[0].sub?.sub) - } - - func test_scalar() { - XCTAssertEqual(0, try! db.scalar(users.count)) - XCTAssertEqual(false, try! db.scalar(users.exists)) - - try! insertUsers("alice") - XCTAssertEqual(1, try! db.scalar(users.select(id.average))) - } - - func test_pluck() { - let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, try! db.pluck(users)![id]) - } - - func test_insert() { - let id = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(1, id) - } - - func test_insert_many() { - let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) - XCTAssertEqual(2, id) - } - - func test_upsert() throws { - guard db.satisfiesMinimumVersion(minor: 24) else { return } - let fetchAge = { () throws -> Int? in - try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } - } - - let id = try db.run(users.upsert(email <- "alice@example.com", age <- 30, onConflictOf: email)) - XCTAssertEqual(1, id) - XCTAssertEqual(30, try fetchAge()) - - let nextId = try db.run(users.upsert(email <- "alice@example.com", age <- 42, onConflictOf: email)) - XCTAssertEqual(1, nextId) - XCTAssertEqual(42, try fetchAge()) - } - - func test_update() { - let changes = try! db.run(users.update(email <- "alice@example.com")) - XCTAssertEqual(0, changes) - } - - func test_delete() { - let changes = try! db.run(users.delete()) - XCTAssertEqual(0, changes) - } - - func test_union() throws { - let expectedIDs = [ - try db.run(users.insert(email <- "alice@example.com")), - try db.run(users.insert(email <- "sally@example.com")) - ] - - let query1 = users.filter(email == "alice@example.com") - let query2 = users.filter(email == "sally@example.com") - - let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } - XCTAssertEqual(expectedIDs, actualIDs) - - let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") - let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - - print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) - - let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } - XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) - } - - func test_no_such_column() throws { - let doesNotExist = Expression("doesNotExist") - try! insertUser("alice") - let row = try! db.pluck(users.filter(email == "alice@example.com"))! - - XCTAssertThrowsError(try row.get(doesNotExist)) { error in - if case QueryError.noSuchColumn(let name, _) = error { - XCTAssertEqual("\"doesNotExist\"", name) - } else { - XCTFail("unexpected error: \(error)") - } - } - } - - func test_catchConstraintError() { - try! db.run(users.insert(email <- "alice@example.com")) - do { - try db.run(users.insert(email <- "alice@example.com")) - XCTFail("expected error") - } catch let Result.error(_, code, _) where code == SQLITE_CONSTRAINT { - // expected - } catch let error { - XCTFail("unexpected error: \(error)") - } - } -} - -private extension Connection { - func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { - guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } - let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } - guard components.count == 3 else { return false } - - return components[1] >= minor && components[2] >= patch - } -} From b94e7a9e453b07c8eea8486d266744e3a8c6751d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:42:45 +0200 Subject: [PATCH 0789/1046] Lint --- Tests/SQLiteTests/QueryIntegrationTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 9f5dc5aa..c98b1601 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -210,7 +210,6 @@ class QueryIntegrationTests: SQLiteTestCase { } } - private extension Connection { func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } From 723cdc8456cc87ce5dadbc6a25b3f4bbf46275c9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:52:21 +0200 Subject: [PATCH 0790/1046] Lint fixes, swap param order --- SQLite.xcodeproj/project.pbxproj | 8 +++ Sources/SQLite/Core/Backup.swift | 69 ++++++++++++------------- Sources/SQLite/Core/Connection.swift | 50 +++--------------- Sources/SQLite/Core/Result.swift | 45 ++++++++++++++++ Tests/SQLiteTests/ConnectionTests.swift | 10 ++-- 5 files changed, 97 insertions(+), 85 deletions(-) create mode 100644 Sources/SQLite/Core/Result.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fb40791a..b5929023 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; @@ -60,6 +61,7 @@ 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; @@ -82,6 +84,7 @@ 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; @@ -241,6 +244,7 @@ 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; + 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; @@ -456,6 +460,7 @@ EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, 02A43A9722738CF100FEC494 /* Backup.swift */, + 19A17E723300E5ED3771DCB5 /* Result.swift */, ); path = Core; sourceTree = ""; @@ -843,6 +848,7 @@ 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, + 19A17073552293CA063BEA66 /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -936,6 +942,7 @@ 02A43A9822738CF100FEC494 /* Backup.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, + 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -998,6 +1005,7 @@ 02A43A9922738CF100FEC494 /* Backup.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, + 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 1ea9637d..84ebf1e0 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -38,19 +38,19 @@ import SQLite3 /// /// See: public final class Backup { - + /// The name of the database to backup public enum DatabaseName { - + /// The main database case main - + /// The temporary database case temp - + /// A database added to the connection with ATTACH statement case attached(name: String) - + var name: String { switch self { case .main: @@ -62,16 +62,16 @@ public final class Backup { } } } - + /// Number of pages to copy while performing a backup step public enum Pages { - + /// Indicates all remaining pages should be copied case all - + /// Indicates the maximal number of pages to be copied in single step case limited(number: Int32) - + var number: Int32 { switch self { case .all: @@ -81,63 +81,60 @@ public final class Backup { } } } - + /// Total number of pages to copy /// /// See: public var pageCount: Int32 { return handle.map { sqlite3_backup_pagecount($0) } ?? 0 } - + /// Number of remaining pages to copy. /// /// See: public var remainingPages: Int32 { return handle.map { sqlite3_backup_remaining($0) } ?? 0 } - + private let targetConnection: Connection private let sourceConnection: Connection - + private var handle: OpaquePointer? - + /// Initializes a new SQLite backup. /// /// - Parameters: /// - /// - targetConnection: The connection to the database to save backup into. - /// - /// - targetName: The name of the database to save backup into. - /// - /// Default: `.main`. - /// /// - sourceConnection: The connection to the database to backup. - /// /// - sourceName: The name of the database to backup. - /// + /// Default: `.main`. + /// + /// - targetConnection: The connection to the database to save backup into. + /// - targetName: The name of the database to save backup into. /// Default: `.main`. /// /// - Returns: A new database backup. /// /// See: - public init(targetConnection: Connection, - targetName: DatabaseName = .main, - sourceConnection: Connection, - sourceName: DatabaseName = .main) throws { - + public init(sourceConnection: Connection, + sourceName: DatabaseName = .main, + targetConnection: Connection, + targetName: DatabaseName = .main) throws { + self.targetConnection = targetConnection self.sourceConnection = sourceConnection - + self.handle = sqlite3_backup_init(targetConnection.handle, targetName.name, sourceConnection.handle, sourceName.name) - - if self.handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), connection: targetConnection) { + + if handle == nil, let error = Result(errorCode: sqlite3_errcode(targetConnection.handle), + connection: targetConnection) { throw error } } - + /// Performs a backup step. /// /// - Parameter pagesToCopy: The maximal number of pages to copy in one step @@ -147,17 +144,17 @@ public final class Backup { /// See: public func step(pagesToCopy pages: Pages = .all) throws { let status = sqlite3_backup_step(handle, pages.number) - + guard status != SQLITE_DONE else { finish() return } - + if let error = Result(errorCode: status, connection: targetConnection) { throw error } } - + /// Finalizes backup. /// /// See: @@ -165,11 +162,11 @@ public final class Backup { guard let handle = self.handle else { return } - + sqlite3_backup_finish(handle) self.handle = nil } - + deinit { finish() } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 5e34457c..a9012462 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -790,9 +790,9 @@ public final class Connection { } fileprivate typealias Collation = @convention(block) (UnsafeRawPointer, UnsafeRawPointer) -> Int32 fileprivate var collations = [String: Collation]() - + // MARK: - Backup - + /// Prepares a new backup for current connection. /// /// - Parameters: @@ -808,16 +808,14 @@ public final class Connection { /// Default: `.main`. /// /// - Returns: A new database backup. - + public func backup(databaseName: Backup.DatabaseName = .main, usingConnection targetConnection: Connection, andDatabaseName targetDatabaseName: Backup.DatabaseName = .main) throws -> Backup { - return try Backup(targetConnection: targetConnection, - targetName: targetDatabaseName, - sourceConnection: self, - sourceName: databaseName) + try Backup(sourceConnection: self, sourceName: databaseName, targetConnection: targetConnection, + targetName: targetDatabaseName) } - + // MARK: - Error Handling func sync(_ block: () throws -> T) rethrows -> T { @@ -866,39 +864,3 @@ extension Connection.Location: CustomStringConvertible { } } - -public enum Result: Error { - - fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] - - /// Represents a SQLite specific [error code](https://sqlite.org/rescode.html) - /// - /// - message: English-language text that describes the error - /// - /// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list) - /// - /// - statement: the statement which produced the error - case error(message: String, code: Int32, statement: Statement?) - - init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { - guard !Result.successCodes.contains(errorCode) else { return nil } - - let message = String(cString: sqlite3_errmsg(connection.handle)) - self = .error(message: message, code: errorCode, statement: statement) - } - -} - -extension Result: CustomStringConvertible { - - public var description: String { - switch self { - case let .error(message, errorCode, statement): - if let statement = statement { - return "\(message) (\(statement)) (code: \(errorCode))" - } else { - return "\(message) (code: \(errorCode))" - } - } - } -} diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift new file mode 100644 index 00000000..3de4d25d --- /dev/null +++ b/Sources/SQLite/Core/Result.swift @@ -0,0 +1,45 @@ +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +public enum Result: Error { + + fileprivate static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + + /// Represents a SQLite specific [error code](https://sqlite.org/rescode.html) + /// + /// - message: English-language text that describes the error + /// + /// - code: SQLite [error code](https://sqlite.org/rescode.html#primary_result_code_list) + /// + /// - statement: the statement which produced the error + case error(message: String, code: Int32, statement: Statement?) + + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { + guard !Result.successCodes.contains(errorCode) else { return nil } + + let message = String(cString: sqlite3_errmsg(connection.handle)) + self = .error(message: message, code: errorCode, statement: statement) + } + +} + +extension Result: CustomStringConvertible { + + public var description: String { + switch self { + case let .error(message, errorCode, statement): + if let statement = statement { + return "\(message) (\(statement)) (code: \(errorCode))" + } else { + return "\(message) (code: \(errorCode))" + } + } + } +} diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 1cb70732..ae881289 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -147,15 +147,15 @@ class ConnectionTests: SQLiteTestCase { assertSQL("BEGIN EXCLUSIVE TRANSACTION") } - + func test_backup_copiesDatabase() throws { let target = try Connection() - - try InsertUsers("alice", "betsy") - + + try insertUsers("alice", "betsy") + let backup = try db.backup(usingConnection: target) try backup.step() - + let users = try target.prepare("SELECT email FROM users ORDER BY email") XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) } From bcbfe980c789f73de1fe8486d78f82fda67b95b5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 18:56:02 +0200 Subject: [PATCH 0791/1046] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 465438bc..fc09fa40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.13.1 (tba) +======================================== + +* Support for database backup ([#919][]) +* Support for custom SQL aggregates ([#881][]) + 0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== @@ -116,3 +122,5 @@ [#726]: https://github.com/stephencelis/SQLite.swift/pull/726 [#797]: https://github.com/stephencelis/SQLite.swift/pull/797 [#866]: https://github.com/stephencelis/SQLite.swift/pull/866 +[#881]: https://github.com/stephencelis/SQLite.swift/pull/881 +[#919]: https://github.com/stephencelis/SQLite.swift/pull/919 From 2ae7b9b61dfdc7e593108f8048ba19da5b51d656 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 19:46:58 +0200 Subject: [PATCH 0792/1046] Missing target --- SQLite.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index b5929023..b30c18d2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; @@ -911,6 +912,7 @@ 02A43A9B22738CF100FEC494 /* Backup.swift in Sources */, 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, + 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 5e64bb587b8b45a478634eed54df5f26d84b0b5e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 19:25:53 +0200 Subject: [PATCH 0793/1046] Split and simplify code, remove duplication --- .swiftlint.yml | 5 +- SQLite.xcodeproj/project.pbxproj | 8 + .../SQLite/Core/Connection+Aggregation.swift | 155 ++++++++++++ Sources/SQLite/Core/Connection.swift | 225 +++++------------- Sources/SQLite/Typed/CustomFunctions.swift | 65 ----- Sources/SQLite/Typed/Query.swift | 1 - 6 files changed, 226 insertions(+), 233 deletions(-) create mode 100644 Sources/SQLite/Core/Connection+Aggregation.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index b5e53d56..34bb253b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -38,5 +38,6 @@ line_length: ignores_comments: true file_length: - warning: 900 - error: 900 + warning: 500 + error: 500 + ignore_comment_only_lines: true diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index b30c18d2..5bb52f4e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; @@ -67,6 +68,7 @@ 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; + 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; @@ -75,6 +77,7 @@ 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; @@ -237,6 +240,7 @@ 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Aggregation.swift"; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; sourceTree = ""; }; @@ -462,6 +466,7 @@ 19A1710E73A46D5AC721CDA9 /* Errors.swift */, 02A43A9722738CF100FEC494 /* Backup.swift */, 19A17E723300E5ED3771DCB5 /* Result.swift */, + 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, ); path = Core; sourceTree = ""; @@ -850,6 +855,7 @@ 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, 19A17073552293CA063BEA66 /* Result.swift in Sources */, + 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -945,6 +951,7 @@ 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, + 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1008,6 +1015,7 @@ 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, + 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift new file mode 100644 index 00000000..257d48ed --- /dev/null +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -0,0 +1,155 @@ +import Foundation +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +extension Connection { + private typealias Aggregate = @convention(block) (Int, OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + + /// Creates or redefines a custom SQL aggregate. + /// + /// - Parameters: + /// + /// - aggregate: The name of the aggregate to create or redefine. + /// + /// - argumentCount: The number of arguments that the aggregate takes. If + /// `nil`, the aggregate may take any number of arguments. + /// + /// Default: `nil` + /// + /// - deterministic: Whether or not the aggregate is deterministic (_i.e._ + /// the aggregate always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - step: A block of code to run for each row of an aggregation group. + /// The block is called with an array of raw SQL values mapped to the + /// aggregate’s parameters, and an UnsafeMutablePointer to a state + /// variable. + /// + /// - final: A block of code to run after each row of an aggregation group + /// is processed. The block is called with an UnsafeMutablePointer to a + /// state variable, and should return a raw SQL value (or nil). + /// + /// - state: A block of code to run to produce a fresh state variable for + /// each aggregation group. The block should return an + /// UnsafeMutablePointer to the fresh state variable. + public func createAggregation( + _ functionName: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + step: @escaping ([Binding?], UnsafeMutablePointer) -> Void, + final: @escaping (UnsafeMutablePointer) -> Binding?, + state: @escaping () -> UnsafeMutablePointer) { + + let argc = argumentCount.map { Int($0) } ?? -1 + let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let nBytes = Int32(MemoryLayout>.size) + guard let aggregateContext = sqlite3_aggregate_context(context, nBytes) else { + fatalError("Could not get aggregate context") + } + let mutablePointer = aggregateContext.assumingMemoryBound(to: UnsafeMutableRawPointer.self) + if stepFlag > 0 { + let arguments = getArguments(argc: argc, argv: argv) + if aggregateContext.assumingMemoryBound(to: Int64.self).pointee == 0 { + mutablePointer.pointee = UnsafeMutableRawPointer(mutating: state()) + } + step(arguments, mutablePointer.pointee.assumingMemoryBound(to: T.self)) + } else { + let result = final(mutablePointer.pointee.assumingMemoryBound(to: T.self)) + set(result: result, on: context) + } + } + + func xStep(context: OpaquePointer?, argc: Int32, value: UnsafeMutablePointer?) { + unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(1, context, argc, value) + } + + func xFinal(context: OpaquePointer?) { + unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(0, context, 0, nil) + } + + let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0) + let resultCode = sqlite3_create_function_v2( + handle, + functionName, + Int32(argc), + flags, + /* pApp */ unsafeBitCast(box, to: UnsafeMutableRawPointer.self), + /* xFunc */ nil, xStep, xFinal, /* xDestroy */ nil + ) + if let result = Result(errorCode: resultCode, connection: self) { + fatalError("Error creating function: \(result)") + } + register(functionName, argc: argc, value: box) + } + + func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, ptr) in + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let current = Unmanaged.fromOpaque(pointer).takeRetainedValue() + let next = reduce(current, bindings) + ptr.pointee = Unmanaged.passRetained(next).toOpaque() + } + + let final: (UnsafeMutablePointer) -> Binding? = { ptr in + let pointer = ptr.pointee.assumingMemoryBound(to: T.self) + let obj = Unmanaged.fromOpaque(pointer).takeRetainedValue() + let value = result(obj) + ptr.deallocate() + return value + } + + let state: () -> UnsafeMutablePointer = { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque() + return pointer + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + + func createAggregation( + _ aggregate: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, + initialValue: T, + reduce: @escaping (T, [Binding?]) -> T, + result: @escaping (T) -> Binding? + ) { + + let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, pointer) in + let current = pointer.pointee + let next = reduce(current, bindings) + pointer.pointee = next + } + + let final: (UnsafeMutablePointer) -> Binding? = { pointer in + let value = result(pointer.pointee) + pointer.deallocate() + return value + } + + let state: () -> UnsafeMutablePointer = { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: initialValue) + return pointer + } + + createAggregation(aggregate, step: step, final: final, state: state) + } + +} diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index a9012462..35996b29 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -35,7 +35,6 @@ import SQLite3 #endif /// A connection to SQLite. -// swiftlint:disable:next type_body_length public final class Connection { /// The location of a SQLite database. @@ -586,182 +585,42 @@ public final class Connection { /// - block: A block of code to run when the function is called. The block /// is called with an array of raw SQL values mapped to the function’s /// parameters and should return a raw SQL value (or nil). - // swiftlint:disable:next cyclomatic_complexity - public func createFunction(_ function: String, argumentCount: UInt? = nil, deterministic: Bool = false, + public func createFunction(_ functionName: String, + argumentCount: UInt? = nil, + deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 let box: Function = { context, argc, argv in - let arguments: [Binding?] = (0..?) { + unsafeBitCast(sqlite3_user_data(context), to: Function.self)(context, argc, value) } + let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0) + let resultCode = sqlite3_create_function_v2( + handle, + functionName, + Int32(argc), + flags, + /* pApp */ unsafeBitCast(box, to: UnsafeMutableRawPointer.self), + xFunc, /*xStep*/ nil, /*xFinal*/ nil, /*xDestroy*/ nil + ) - if functions[function] == nil { - functions[function] = [:] // fails on Linux, https://github.com/stephencelis/SQLite.swift/issues/1071 + if let result = Result(errorCode: resultCode, connection: self) { + fatalError("Error creating function: \(result)") } - functions[function]?[argc] = box + register(functionName, argc: argc, value: box) } - /// Creates or redefines a custom SQL aggregate. - /// - /// - Parameters: - /// - /// - aggregate: The name of the aggregate to create or redefine. - /// - /// - argumentCount: The number of arguments that the aggregate takes. If - /// `nil`, the aggregate may take any number of arguments. - /// - /// Default: `nil` - /// - /// - deterministic: Whether or not the aggregate is deterministic (_i.e._ - /// the aggregate always returns the same result for a given input). - /// - /// Default: `false` - /// - /// - step: A block of code to run for each row of an aggregation group. - /// The block is called with an array of raw SQL values mapped to the - /// aggregate’s parameters, and an UnsafeMutablePointer to a state - /// variable. - /// - /// - final: A block of code to run after each row of an aggregation group - /// is processed. The block is called with an UnsafeMutablePointer to a - /// state variable, and should return a raw SQL value (or nil). - /// - /// - state: A block of code to run to produce a fresh state variable for - /// each aggregation group. The block should return an - /// UnsafeMutablePointer to the fresh state variable. - // swiftlint:disable:next cyclomatic_complexity - public func createAggregation( - _ aggregate: String, - argumentCount: UInt? = nil, - deterministic: Bool = false, - step: @escaping ([Binding?], UnsafeMutablePointer) -> Void, - final: @escaping (UnsafeMutablePointer) -> Binding?, - state: @escaping () -> UnsafeMutablePointer) { - - let argc = argumentCount.map { Int($0) } ?? -1 - let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in - let ptr = sqlite3_aggregate_context(context, 64)! // needs to be at least as large as uintptr_t; better way to do this? - let mutablePointer = ptr.assumingMemoryBound(to: UnsafeMutableRawPointer.self) - if stepFlag > 0 { - let arguments: [Binding?] = (0..?) -> Void fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void - fileprivate var functions = [String: [Int: Function]]() - fileprivate var aggregations = [String: [Int: Aggregate]]() + fileprivate var functions = [String: [Int: Any]]() /// Defines a new collating sequence. /// @@ -808,7 +667,6 @@ public final class Connection { /// Default: `.main`. /// /// - Returns: A new database backup. - public func backup(databaseName: Backup.DatabaseName = .main, usingConnection targetConnection: Connection, andDatabaseName targetDatabaseName: Backup.DatabaseName = .main) throws -> Backup { @@ -864,3 +722,40 @@ extension Connection.Location: CustomStringConvertible { } } + +func getArguments(argc: Int32, argv: UnsafeMutablePointer?) -> [Binding?] { + (0..( - _ aggregate: String, - argumentCount: UInt? = nil, - deterministic: Bool = false, - initialValue: T, - reduce: @escaping (T, [Binding?]) -> T, - result: @escaping (T) -> Binding? - ) { - - let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, ptr) in - let pointer = ptr.pointee.assumingMemoryBound(to: T.self) - let current = Unmanaged.fromOpaque(pointer).takeRetainedValue() - let next = reduce(current, bindings) - ptr.pointee = Unmanaged.passRetained(next).toOpaque() - } - - let final: (UnsafeMutablePointer) -> Binding? = { (ptr) in - let pointer = ptr.pointee.assumingMemoryBound(to: T.self) - let obj = Unmanaged.fromOpaque(pointer).takeRetainedValue() - let value = result(obj) - ptr.deallocate() - return value - } - - let state: () -> UnsafeMutablePointer = { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.pointee = Unmanaged.passRetained(initialValue).toOpaque() - return pointer - } - - createAggregation(aggregate, step: step, final: final, state: state) - } - - func createAggregation( - _ aggregate: String, - argumentCount: UInt? = nil, - deterministic: Bool = false, - initialValue: T, - reduce: @escaping (T, [Binding?]) -> T, - result: @escaping (T) -> Binding? - ) { - - let step: ([Binding?], UnsafeMutablePointer) -> Void = { (bindings, pointer) in - let current = pointer.pointee - let next = reduce(current, bindings) - pointer.pointee = next - } - - let final: (UnsafeMutablePointer) -> Binding? = { pointer in - let value = result(pointer.pointee) - pointer.deallocate() - return value - } - - let state: () -> UnsafeMutablePointer = { - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.initialize(to: initialValue) - return pointer - } - - createAggregation(aggregate, step: step, final: final, state: state) - } - } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 59b24a8d..2d88db63 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -21,7 +21,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -// swiftlint:disable file_length import Foundation public protocol QueryType: Expressible { From e5a10f5bcad9f9c25f948a9c90c5861e1bb83163 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 25 Aug 2021 22:23:36 +0200 Subject: [PATCH 0794/1046] Use typealiases --- .../SQLite/Core/Connection+Aggregation.swift | 12 +-- Sources/SQLite/Core/Connection.swift | 74 ++++++++++--------- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 257d48ed..106f292c 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -10,7 +10,7 @@ import SQLite3 #endif extension Connection { - private typealias Aggregate = @convention(block) (Int, OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + private typealias Aggregate = @convention(block) (Int, Context, Int32, Argv) -> Void /// Creates or redefines a custom SQL aggregate. /// @@ -49,29 +49,29 @@ extension Connection { state: @escaping () -> UnsafeMutablePointer) { let argc = argumentCount.map { Int($0) } ?? -1 - let box: Aggregate = { (stepFlag: Int, context: OpaquePointer?, argc: Int32, argv: UnsafeMutablePointer?) in + let box: Aggregate = { (stepFlag: Int, context: Context, argc: Int32, argv: Argv) in let nBytes = Int32(MemoryLayout>.size) guard let aggregateContext = sqlite3_aggregate_context(context, nBytes) else { fatalError("Could not get aggregate context") } let mutablePointer = aggregateContext.assumingMemoryBound(to: UnsafeMutableRawPointer.self) if stepFlag > 0 { - let arguments = getArguments(argc: argc, argv: argv) + let arguments = argv.getBindings(argc: argc) if aggregateContext.assumingMemoryBound(to: Int64.self).pointee == 0 { mutablePointer.pointee = UnsafeMutableRawPointer(mutating: state()) } step(arguments, mutablePointer.pointee.assumingMemoryBound(to: T.self)) } else { let result = final(mutablePointer.pointee.assumingMemoryBound(to: T.self)) - set(result: result, on: context) + context.set(result: result) } } - func xStep(context: OpaquePointer?, argc: Int32, value: UnsafeMutablePointer?) { + func xStep(context: Context, argc: Int32, value: Argv) { unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(1, context, argc, value) } - func xFinal(context: OpaquePointer?) { + func xFinal(context: Context) { unsafeBitCast(sqlite3_user_data(context), to: Aggregate.self)(0, context, 0, nil) } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 35996b29..6b5481e3 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -590,10 +590,10 @@ public final class Connection { deterministic: Bool = false, _ block: @escaping (_ args: [Binding?]) -> Binding?) { let argc = argumentCount.map { Int($0) } ?? -1 - let box: Function = { context, argc, argv in - set(result: block(getArguments(argc: argc, argv: argv)), on: context) + let box: Function = { (context: Context, argc, argv: Argv) in + context.set(result: block(argv.getBindings(argc: argc))) } - func xFunc(context: OpaquePointer?, argc: Int32, value: UnsafeMutablePointer?) { + func xFunc(context: Context, argc: Int32, value: Argv) { unsafeBitCast(sqlite3_user_data(context), to: Function.self)(context, argc, value) } let flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0) @@ -619,7 +619,7 @@ public final class Connection { functions[functionName]?[argc] = value } - fileprivate typealias Function = @convention(block) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void + fileprivate typealias Function = @convention(block) (Context, Int32, Argv) -> Void fileprivate var functions = [String: [Int: Any]]() /// Defines a new collating sequence. @@ -723,39 +723,45 @@ extension Connection.Location: CustomStringConvertible { } -func getArguments(argc: Int32, argv: UnsafeMutablePointer?) -> [Binding?] { - (0..? +extension Argv { + func getBindings(argc: Int32) -> [Binding?] { + (0.. Date: Mon, 23 Aug 2021 00:14:23 +0200 Subject: [PATCH 0795/1046] Add trigram tokenizer --- Sources/SQLite/Extensions/FTS4.swift | 9 +++++++-- Tests/SQLiteTests/FTS5Tests.swift | 10 ++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 115b146a..0e48943a 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -95,10 +95,10 @@ extension VirtualTable { public struct Tokenizer { public static let Simple = Tokenizer("simple") - public static let Porter = Tokenizer("porter") - public static func Unicode61(removeDiacritics: Bool? = nil, tokenchars: Set = [], + public static func Unicode61(removeDiacritics: Bool? = nil, + tokenchars: Set = [], separators: Set = []) -> Tokenizer { var arguments = [String]() @@ -119,6 +119,11 @@ public struct Tokenizer { return Tokenizer("unicode61", arguments) } + // https://sqlite.org/fts5.html#the_experimental_trigram_tokenizer + public static func Trigram(caseSensitive: Bool = false) -> Tokenizer { + return Tokenizer("trigram", ["case_sensitive", caseSensitive ? "1" : "0"]) + } + public static func Custom(_ name: String) -> Tokenizer { Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) } diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index 4cce4523..c271be9d 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -81,6 +81,16 @@ class FTS5Tests: XCTestCase { sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) } + func test_tokenizer_trigram() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=trigram case_sensitive 0)", + sql(config.tokenizer(.Trigram()))) + + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=trigram case_sensitive 1)", + sql(config.tokenizer(.Trigram(caseSensitive: true)))) + } + func test_column_size() { XCTAssertEqual( "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(columnsize=1)", From 3dc24496883090888fdbd658a766eb622b815aec Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 00:19:07 +0200 Subject: [PATCH 0796/1046] Add integration tests --- SQLite.xcodeproj/project.pbxproj | 8 +++ Tests/SQLiteTests/CipherTests.swift | 16 ++--- Tests/SQLiteTests/ConnectionTests.swift | 7 +- .../SQLiteTests/CustomAggregationTests.swift | 12 ++-- Tests/SQLiteTests/FTSIntegrationTests.swift | 64 +++++++++++++++++++ Tests/SQLiteTests/QueryIntegrationTests.swift | 11 ++-- Tests/SQLiteTests/SelectTests.swift | 12 ++-- Tests/SQLiteTests/StatementTests.swift | 6 +- Tests/SQLiteTests/TestHelpers.swift | 10 +-- 9 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 Tests/SQLiteTests/FTSIntegrationTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 5bb52f4e..92400180 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; + 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; @@ -80,8 +81,10 @@ 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; @@ -236,6 +239,7 @@ 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; @@ -449,6 +453,7 @@ 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, + 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -887,6 +892,7 @@ 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, + 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -983,6 +989,7 @@ 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, + 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1047,6 +1054,7 @@ 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, + 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 77e8e7f0..0995fca0 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -8,22 +8,22 @@ class CipherTests: XCTestCase { let db1 = try! Connection() let db2 = try! Connection() - override func setUp() { + override func setUpWithError() throws { // db - try! db1.key("hello") + try db1.key("hello") - try! db1.run("CREATE TABLE foo (bar TEXT)") - try! db1.run("INSERT INTO foo (bar) VALUES ('world')") + try db1.run("CREATE TABLE foo (bar TEXT)") + try db1.run("INSERT INTO foo (bar) VALUES ('world')") // db2 let key2 = keyData() - try! db2.key(Blob(bytes: key2.bytes, length: key2.length)) + try db2.key(Blob(bytes: key2.bytes, length: key2.length)) - try! db2.run("CREATE TABLE foo (bar TEXT)") - try! db2.run("INSERT INTO foo (bar) VALUES ('world')") + try db2.run("CREATE TABLE foo (bar TEXT)") + try db2.run("INSERT INTO foo (bar) VALUES ('world')") - super.setUp() + try super.setUpWithError() } func test_key() { diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ae881289..ae9d4dfd 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -15,10 +15,9 @@ import SQLite3 class ConnectionTests: SQLiteTestCase { - override func setUp() { - super.setUp() - - createUsersTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() } func test_init_withInMemory_returnsInMemoryConnection() { diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index d5232260..5cbfbbef 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -17,12 +17,12 @@ import SQLite3 #if !os(Linux) class CustomAggregationTests: SQLiteTestCase { - override func setUp() { - super.setUp() - createUsersTable() - try! insertUser("Alice", age: 30, admin: true) - try! insertUser("Bob", age: 25, admin: true) - try! insertUser("Eve", age: 28, admin: false) + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + try insertUser("Alice", age: 30, admin: true) + try insertUser("Bob", age: 25, admin: true) + try insertUser("Eve", age: 28, admin: false) } func testUnsafeCustomSum() { diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/FTSIntegrationTests.swift new file mode 100644 index 00000000..561c756f --- /dev/null +++ b/Tests/SQLiteTests/FTSIntegrationTests.swift @@ -0,0 +1,64 @@ +import XCTest +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif +@testable import SQLite + +class FTSIntegrationTests: SQLiteTestCase { + let email = Expression("email") + let index = VirtualTable("index") + + private func createIndex() throws { + try db.run(index.create(.FTS5(FTS5Config() + .column(email) + .tokenizer(.Unicode61()) + ))) + + for user in try db.prepare(users) { + try db.run(index.insert(email <- user[email])) + } + } + + private func createTrigramIndex() throws { + try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 34)) + try db.run(index.create(.FTS5(FTS5Config() + .column(email) + .tokenizer(.Trigram(caseSensitive: false)) + ))) + + for user in try db.prepare(users) { + try db.run(index.insert(email <- user[email])) + } + } + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + try insertUsers("John", "Paul", "George", "Ringo") + } + + func testMatch() throws { + try createIndex() + let matches = Array(try db.prepare(index.match("Paul"))) + XCTAssertEqual(matches.map { $0[email ]}, ["Paul@example.com"]) + } + + func testMatchPartial() throws { + try insertUsers("Paula") + try createIndex() + let matches = Array(try db.prepare(index.match("Pa*"))) + XCTAssertEqual(matches.map { $0[email ]}, ["Paul@example.com", "Paula@example.com"]) + } + + func testTrigramIndex() throws { + try createTrigramIndex() + let matches = Array(try db.prepare(index.match("Paul"))) + XCTAssertEqual(1, matches.count) + } +} diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index c98b1601..4b9c4bbf 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -16,10 +16,9 @@ class QueryIntegrationTests: SQLiteTestCase { let email = Expression("email") let age = Expression("age") - override func setUp() { - super.setUp() - - createUsersTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() } // MARK: - @@ -131,7 +130,7 @@ class QueryIntegrationTests: SQLiteTestCase { } func test_upsert() throws { - guard db.satisfiesMinimumVersion(minor: 24) else { return } + try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 24)) let fetchAge = { () throws -> Int? in try self.db.pluck(self.users.filter(self.email == "alice@example.com")).flatMap { $0[self.age] } } @@ -210,7 +209,7 @@ class QueryIntegrationTests: SQLiteTestCase { } } -private extension Connection { +extension Connection { func satisfiesMinimumVersion(minor: Int, patch: Int = 0) -> Bool { guard let version = try? scalar("SELECT sqlite_version()") as? String else { return false } let components = version.split(separator: ".", maxSplits: 3).compactMap { Int($0) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index c66d401d..d1126b66 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -3,14 +3,14 @@ import XCTest class SelectTests: SQLiteTestCase { - override func setUp() { - super.setUp() - createUsersTable() - createUsersDataTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + try createUsersDataTable() } - func createUsersDataTable() { - try! db.execute(""" + func createUsersDataTable() throws { + try db.execute(""" CREATE TABLE users_name ( id INTEGER, user_id INTEGER REFERENCES users(id), diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 5a05675d..b4ad7c28 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -2,9 +2,9 @@ import XCTest import SQLite class StatementTests: SQLiteTestCase { - override func setUp() { - super.setUp() - createUsersTable() + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() } func test_cursor_to_blob() { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 0aa918f0..07cc6f06 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -6,9 +6,9 @@ class SQLiteTestCase: XCTestCase { var db: Connection! let users = Table("users") - override func setUp() { - super.setUp() - db = try! Connection() + override func setUpWithError() throws { + try super.setUpWithError() + db = try Connection() trace = [String: Int]() db.trace { SQL in @@ -17,8 +17,8 @@ class SQLiteTestCase: XCTestCase { } } - func createUsersTable() { - try! db.execute(""" + func createUsersTable() throws { + try db.execute(""" CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, From 7aaccafedd157148a5a69e3e4c1a767b6d704ef9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 00:21:03 +0200 Subject: [PATCH 0797/1046] Skip on Linux --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 7522fa45..c1373ad8 100644 --- a/Package.swift +++ b/Package.swift @@ -46,6 +46,7 @@ package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.g package.targets = [ .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ + "FTSIntegrationTests.swift", "FTS4Tests.swift", "FTS5Tests.swift" ]) From b1031e33bc8c08898858c3d2873e2985f8ff19cf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 00:49:30 +0200 Subject: [PATCH 0798/1046] Skip tests if not supported --- Tests/SQLiteTests/FTSIntegrationTests.swift | 30 ++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/FTSIntegrationTests.swift index 561c756f..d629ca8b 100644 --- a/Tests/SQLiteTests/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/FTSIntegrationTests.swift @@ -15,10 +15,16 @@ class FTSIntegrationTests: SQLiteTestCase { let index = VirtualTable("index") private func createIndex() throws { - try db.run(index.create(.FTS5(FTS5Config() - .column(email) - .tokenizer(.Unicode61()) - ))) + do { + try db.run(index.create(.FTS5( + FTS5Config() + .column(email) + .tokenizer(.Unicode61())) + )) + } catch let error as Result { + try XCTSkipIf(error.description.starts(with: "no such model")) + throw error + } for user in try db.prepare(users) { try db.run(index.insert(email <- user[email])) @@ -26,11 +32,17 @@ class FTSIntegrationTests: SQLiteTestCase { } private func createTrigramIndex() throws { - try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 34)) - try db.run(index.create(.FTS5(FTS5Config() - .column(email) - .tokenizer(.Trigram(caseSensitive: false)) - ))) + do { + try db.run(index.create(.FTS5( + FTS5Config() + .column(email) + .tokenizer(.Trigram(caseSensitive: false))) + )) + } catch let error as Result { + try XCTSkipIf(error.description.starts(with: "no such model") || + error.description.starts(with: "parse error in")) + throw error + } for user in try db.prepare(users) { try db.run(index.insert(email <- user[email])) From 7f98a7b34b362a8776a429fcbc2359ae11b78aee Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 26 Aug 2021 01:12:21 +0200 Subject: [PATCH 0799/1046] Move into method --- Tests/SQLiteTests/FTSIntegrationTests.swift | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/FTSIntegrationTests.swift index d629ca8b..da9ba8dc 100644 --- a/Tests/SQLiteTests/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/FTSIntegrationTests.swift @@ -15,15 +15,12 @@ class FTSIntegrationTests: SQLiteTestCase { let index = VirtualTable("index") private func createIndex() throws { - do { + try createOrSkip { db in try db.run(index.create(.FTS5( FTS5Config() .column(email) .tokenizer(.Unicode61())) )) - } catch let error as Result { - try XCTSkipIf(error.description.starts(with: "no such model")) - throw error } for user in try db.prepare(users) { @@ -32,16 +29,12 @@ class FTSIntegrationTests: SQLiteTestCase { } private func createTrigramIndex() throws { - do { + try createOrSkip { db in try db.run(index.create(.FTS5( FTS5Config() .column(email) .tokenizer(.Trigram(caseSensitive: false))) )) - } catch let error as Result { - try XCTSkipIf(error.description.starts(with: "no such model") || - error.description.starts(with: "parse error in")) - throw error } for user in try db.prepare(users) { @@ -73,4 +66,15 @@ class FTSIntegrationTests: SQLiteTestCase { let matches = Array(try db.prepare(index.match("Paul"))) XCTAssertEqual(1, matches.count) } + + private func createOrSkip(_ createIndex: (Connection) throws -> Void) throws { + do { + try createIndex(db) + } catch let error as Result { + try XCTSkipIf(error.description.starts(with: "no such module:") || + error.description.starts(with: "parse error") + ) + throw error + } + } } From 7e6d8b98d47c3cf7cfcb60d63fff17868eefd8c0 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 26 Aug 2021 14:24:52 +0200 Subject: [PATCH 0800/1046] Reverting a change in `FailableIterator` --- Sources/SQLite/Core/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 48347c31..abeee24b 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -207,7 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { - try? failableNext() + try! failableNext() } } From 62fe865ae8b80bab15f373dfeefae04df3b555ea Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 26 Aug 2021 14:33:12 +0200 Subject: [PATCH 0801/1046] Fix linting --- Sources/SQLite/Core/Statement.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index abeee24b..29d6938d 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -207,6 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { + // swiftlint:disable:next force_cast try! failableNext() } } From 870cf7b2cebe315a5e05176ca05a111cd8ab39f6 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Thu, 26 Aug 2021 15:04:55 +0200 Subject: [PATCH 0802/1046] Fix linting --- Sources/SQLite/Core/Statement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 29d6938d..1e2489b5 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -207,7 +207,7 @@ public protocol FailableIterator: IteratorProtocol { extension FailableIterator { public func next() -> Element? { - // swiftlint:disable:next force_cast + // swiftlint:disable:next force_try try! failableNext() } } From 86c3f6a580fcc335694f12c90515d15671a35636 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 5 Sep 2021 17:23:39 +0200 Subject: [PATCH 0803/1046] Add SPM integration test --- .github/workflows/build.yml | 4 ++++ Package.swift | 6 +++++- Tests/SPM/.gitignore | 7 +++++++ Tests/SPM/Package.swift | 19 +++++++++++++++++++ Tests/SPM/Sources/test/main.swift | 11 +++++++++++ Tests/SPM/db.sqlite | Bin 0 -> 8192 bytes run-tests.sh | 2 ++ 7 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 Tests/SPM/.gitignore create mode 100644 Tests/SPM/Package.swift create mode 100644 Tests/SPM/Sources/test/main.swift create mode 100644 Tests/SPM/db.sqlite diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51654fba..60d487bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,10 @@ jobs: env: PACKAGE_MANAGER_COMMAND: test -Xswiftc -warnings-as-errors run: ./run-tests.sh + - name: "Run tests (SPM integration test)" + env: + SPM: run + run: ./run-tests.sh - name: "Run tests (BUILD_SCHEME: SQLite iOS)" env: BUILD_SCHEME: SQLite iOS diff --git a/Package.swift b/Package.swift index c1373ad8..f4938678 100644 --- a/Package.swift +++ b/Package.swift @@ -44,7 +44,11 @@ let package = Package( #if os(Linux) package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] package.targets = [ - .target(name: "SQLite", exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"]), + .target( + name: "SQLite", + dependencies: [.product(name: "CSQLite", package: "CSQLite")], + exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] + ), .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ "FTSIntegrationTests.swift", "FTS4Tests.swift", diff --git a/Tests/SPM/.gitignore b/Tests/SPM/.gitignore new file mode 100644 index 00000000..bb460e7b --- /dev/null +++ b/Tests/SPM/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift new file mode 100644 index 00000000..810026ce --- /dev/null +++ b/Tests/SPM/Package.swift @@ -0,0 +1,19 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "test", + dependencies: [ + .package(path: "../..") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "test", + dependencies: [.product(name: "SQLite", package: "SQLite.swift")] + ) + ] +) diff --git a/Tests/SPM/Sources/test/main.swift b/Tests/SPM/Sources/test/main.swift new file mode 100644 index 00000000..94f12af8 --- /dev/null +++ b/Tests/SPM/Sources/test/main.swift @@ -0,0 +1,11 @@ +import SQLite + +let table = Table("test") +let name = Expression("name") + +let db = try Connection("db.sqlite", readonly: true) + +for row in try db.prepare(table) { + print(row[name]) +} + diff --git a/Tests/SPM/db.sqlite b/Tests/SPM/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..f7b2e84c051df3ffdddb463bdd0dd990358789cb GIT binary patch literal 8192 zcmeI#u?oU45C-5&YTcwlDXvE+U0lT7TCx>H&CoT~6a=fFc|#{($IZ=T=8p?MmqFk7GxXmQ(Y$<&{40?>$u9%~5P$## zAOHafKmY;|fB*y_0D)fwmSQ-`GO=-{Ia_D%E|e^Hs?dscv91({2~Rn{n9k;`Rjqjz t{^l}yQS$a10s#m>00Izz00bZa0SG_<0uX?}p9sWKrbpYxLZ+rUd;sf39n=5- literal 0 HcmV?d00001 diff --git a/run-tests.sh b/run-tests.sh index 32465388..5f397553 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -14,6 +14,8 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" +elif [ -n "$SPM" ]; then + cd Tests/SPM && swift run elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then swift ${PACKAGE_MANAGER_COMMAND} fi From 79eee35ca996dd0653bc06a823f4394c61b0a3d8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 5 Sep 2021 17:31:38 +0200 Subject: [PATCH 0804/1046] Lint fix --- .github/workflows/build.yml | 4 ++++ Tests/SPM/Sources/test/main.swift | 3 +-- run-tests.sh | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60d487bd..88c5803f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,3 +75,7 @@ jobs: sudo apt-get install -y libsqlite3-dev - name: Test run: swift test + - name: "Run tests (SPM integration test)" + env: + SPM: run + run: ./run-tests.sh diff --git a/Tests/SPM/Sources/test/main.swift b/Tests/SPM/Sources/test/main.swift index 94f12af8..939b3391 100644 --- a/Tests/SPM/Sources/test/main.swift +++ b/Tests/SPM/Sources/test/main.swift @@ -1,4 +1,4 @@ -import SQLite +import SQLite let table = Table("test") let name = Expression("name") @@ -8,4 +8,3 @@ let db = try Connection("db.sqlite", readonly: true) for row in try db.prepare(table) { print(row[name]) } - diff --git a/run-tests.sh b/run-tests.sh index 5f397553..49330c12 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -15,7 +15,7 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "$SPM" ]; then - cd Tests/SPM && swift run + cd Tests/SPM && swift ${SPM} elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then swift ${PACKAGE_MANAGER_COMMAND} fi From 09f730eaa8848ff3387bccaddbf573751694f876 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 6 Sep 2021 00:43:50 +0200 Subject: [PATCH 0805/1046] Update documentation - Closes #1076 - Clarify BusyHandler usage (Closes #786) - Integrate playground into project --- CHANGELOG.md | 4 + Documentation/Index.md | 90 ++++++++++++++++--- Documentation/Planning.md | 2 +- Documentation/Release.md | 1 + README.md | 9 +- SQLite.playground/Contents.swift | 78 +++++++++++++--- SQLite.playground/contents.xcplayground | 2 +- .../contents.xcworkspacedata | 7 ++ SQLite.xcodeproj/project.pbxproj | 2 + .../SQLite/Core/Connection+Aggregation.swift | 4 +- Tests/SPM/Package.swift | 5 +- 11 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 SQLite.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/CHANGELOG.md b/CHANGELOG.md index fc09fa40..c9d09c12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * Support for database backup ([#919][]) * Support for custom SQL aggregates ([#881][]) +* Restore previous iteration behavior ([#1075][]) +* Fix compilation on Linux ([#1077][]) 0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== @@ -124,3 +126,5 @@ [#866]: https://github.com/stephencelis/SQLite.swift/pull/866 [#881]: https://github.com/stephencelis/SQLite.swift/pull/881 [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 +[#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 +[#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 diff --git a/Documentation/Index.md b/Documentation/Index.md index fce068a2..1e08f6c4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -77,9 +77,6 @@ The [Swift Package Manager][] is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. -It is the recommended approach for using SQLite.swift in OSX CLI -applications. - 1. Add the following to your `Package.swift` file: ```swift @@ -342,16 +339,15 @@ execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. -If you maintain multiple connections for a single database, consider setting a timeout (in seconds) and/or a busy handler: +If you maintain multiple connections for a single database, consider setting a timeout +(in seconds) *or* a busy handler. There can only be one active at a time, so setting a busy +handler will effectively override `busyTimeout`. ```swift -db.busyTimeout = 5 +db.busyTimeout = 5 // error after 5 seconds (does multiple retries) db.busyHandler({ tries in - if tries >= 3 { - return false - } - return true + tries < 3 // error after 3 tries }) ``` @@ -656,12 +652,13 @@ do { } ``` -Multiple rows can be inserted at once by similarily calling `insertMany` with an array of per-row [setters](#setters). +Multiple rows can be inserted at once by similarily calling `insertMany` with an array of +per-row [setters](#setters). ```swift do { - let rowid = try db.run(users.insertMany([mail <- "alice@mac.com"], [email <- "geoff@mac.com"])) - print("inserted id: \(rowid)") + let lastRowid = try db.run(users.insertMany([mail <- "alice@mac.com"], [email <- "geoff@mac.com"])) + print("last inserted id: \(lastRowid)") } catch { print("insertion failed: \(error)") } @@ -799,6 +796,33 @@ for user in try db.prepare(users) { } ``` +Note that the iterator can throw *undeclared* database errors at any point during +iteration: + +```swift +let query = try db.prepare(users) +for user in query { + // 💥 can throw an error here +} +```` + +#### Failable iteration + +It is therefore recommended using the `RowIterator` API instead, +which has explicit error handling: + +```swift +let rowIterator = try db.prepareRowIterator(users) +for user in try Array(rowIterator) { + print("id: \(user[id]), email: \(user[email])") +} + +/// or using `map()` +let mapRowIterator = try db.prepareRowIterator(users) +let userIds = try mapRowIterator.map { $0[id] } + +``` + ### Plucking Rows We can pluck the first row by passing a query to the `pluck` function on a @@ -1285,7 +1309,6 @@ try db.run(users.addColumn(suffix)) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` - #### Added Column Constraints The `addColumn` function shares several of the same [`column` function @@ -1337,6 +1360,13 @@ tables](#creating-a-table). // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` +### Renaming Columns + +Added in SQLite 3.25.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) + +### Dropping Columns + +Added in SQLite 3.35.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) ### Indexes @@ -1762,6 +1792,19 @@ for row in stmt.bind(kUTTypeImage) { /* ... */ } [UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto +## Custom Aggregations + +We can create custom aggregation functions by calling `createAggregation`: + +```swift +let reduce: (String, [Binding?]) -> String = { (last, bindings) in + last + " " + (bindings.first as? String ?? "") +} + +db.createAggregation("customConcat", initialValue: "", reduce: reduce, result: { $0 }) +let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +``` + ## Custom Collations We can create custom collating sequences by calling `createCollation` on a @@ -1944,6 +1987,19 @@ using the following functions. let count = try stmt.scalar() as! Int64 ``` +## Online Database Backup + +To copy a database to another using the +[SQLite Online Backup API](https://sqlite.org/backup.html): + +```swift +// creates an in-memory copy of db.sqlite +let db = try Connection("db.sqlite") +let target = try Connection(.inMemory) + +let backup = try db.backup(usingConnection: target) +try backup.step() +``` ## Logging @@ -1955,6 +2011,14 @@ We can log SQL using the database’s `trace` function. #endif ``` +## Vacuum + +To run the [vacuum](https://www.sqlite.org/lang_vacuum.html) command: + +```swift +try db.vacuum() +``` + [ROWID]: https://sqlite.org/lang_createtable.html#rowid [SQLiteMigrationManager.swift]: https://github.com/garriguv/SQLiteMigrationManager.swift diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 62df1f24..44e02c7a 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. > ⚠ This document is currently not actively maintained. See -> the [0.13.0 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.0) +> the [0.13.1 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.1) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/Documentation/Release.md b/Documentation/Release.md index 0177fc8f..87d715f6 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -1,6 +1,7 @@ # SQLite.swift Release checklist * [ ] Make sure current master branch has a green build +* [ ] Make sure `SQLite.playground` runs without errors * [ ] Make sure `CHANGELOG.md` is up-to-date * [ ] Update the version number in `SQLite.swift.podspec` * [ ] Run `pod lib lint` locally diff --git a/README.md b/README.md index 9242b200..ec92729f 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ Swift code. $ swift build ``` +See the [Tests/SPM](https://github.com/stephencelis/SQLite.swift/tree/master/Tests/SPM) folder for a small demo project which uses SPM. + [Swift Package Manager]: https://swift.org/package-manager ### Carthage @@ -175,8 +177,7 @@ install SQLite.swift with Carthage: [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift - requires version 1.6.1 or greater.) + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. ```sh # Using the default Ruby install will require you to use sudo when @@ -266,7 +267,8 @@ These projects enhance or use SQLite.swift: - [SQLiteMigrationManager.swift][] (inspired by [FMDBMigrationManager][]) - - [Delta: Math helper](https://apps.apple.com/app/delta-math-helper/id1436506800) (see [Delta/Utils/Database.swift](https://github.com/GroupeMINASTE/Delta-iOS/blob/master/Delta/Utils/Database.swift) for production implementation example) + - [Delta: Math helper](https://apps.apple.com/app/delta-math-helper/id1436506800) + (see [Delta/Utils/Database.swift](https://github.com/GroupeMINASTE/Delta-iOS/blob/master/Delta/Utils/Database.swift) for production implementation example) ## Alternatives @@ -278,7 +280,6 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - [Squeal](https://github.com/nerdyc/Squeal) - [SwiftData](https://github.com/ryanfowler/SwiftData) - - [SwiftSQLite](https://github.com/chrismsimpson/SwiftSQLite) [Swift]: https://swift.org/ [SQLite3]: https://www.sqlite.org diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 11e13139..ebd5b020 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -1,43 +1,93 @@ import SQLite -let db = try! Connection() +/// Create an in-memory database +let db = try Connection(.inMemory) +/// enable statement logging db.trace { print($0) } +/// define a "users" table with some fields let users = Table("users") let id = Expression("id") -let email = Expression("email") -let name = Expression("name") +let email = Expression("email") // non-null +let name = Expression("name") // nullable -try! db.run(users.create { t in +/// prepare the query +let statement = users.create { t in t.column(id, primaryKey: true) t.column(email, unique: true, check: email.like("%@%")) t.column(name) -}) +} + +/// …and run it +try db.run(statement) + +/// insert "alice" +let rowid = try db.run(users.insert(email <- "alice@mac.com")) + +/// insert multiple rows using `insertMany` +let lastRowid = try db.run(users.insertMany([ + [email <- "bob@mac.com"], + [email <- "mallory@evil.com"] +])) + + +let query = try db.prepare(users) +for user in query { + print("id: \(user[id]), email: \(user[email])") +} -let rowid = try! db.run(users.insert(email <- "alice@mac.com")) -let alice = users.filter(id == rowid) +// re-requery just rowid of Alice +let alice = try db.prepare(users.filter(id == rowid)) +for user in alice { + print("id: \(user[id]), email: \(user[email])") +} -for user in try! db.prepare(users) { +/// using the `RowIterator` API +let rowIterator = try db.prepareRowIterator(users) +for user in try Array(rowIterator) { print("id: \(user[id]), email: \(user[email])") } +/// also with `map()` +let mapRowIterator = try db.prepareRowIterator(users) +let userIds = try mapRowIterator.map { $0[id] } + +/// define a virtual tabe for the FTS index let emails = VirtualTable("emails") -let subject = Expression("subject") +let subject = Expression("subject") let body = Expression("body") -try! db.run(emails.create(.FTS4(subject, body))) +/// create the index +try db.run(emails.create(.FTS5( + FTS5Config() + .column(subject) + .column(body) +))) -try! db.run(emails.insert( +/// populate with data +try db.run(emails.insert( subject <- "Hello, world!", body <- "This is a hello world message." )) -let row = try! db.pluck(emails.match("hello")) +/// run a query +let ftsQuery = try db.prepare(emails.match("hello")) -let query = try! db.prepare(emails.match("hello")) -for row in query { +for row in ftsQuery { print(row[subject]) } + +/// custom aggregations +let reduce: (String, [Binding?]) -> String = { (last, bindings) in + last + " " + (bindings.first as? String ?? "") +} + +db.createAggregation("customConcat", + initialValue: "users:", + reduce: reduce, + result: { $0 }) +let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +print(result) diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index fd676d5b..441c60ef 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/SQLite.playground/playground.xcworkspace/contents.xcworkspacedata b/SQLite.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/SQLite.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 92400180..ba14e9ae 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -256,6 +256,7 @@ 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; + 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; @@ -386,6 +387,7 @@ children = ( EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, + 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, 3D67B3E41DB2469200A4F4C6 /* Frameworks */, diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 106f292c..4eea76c3 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -90,7 +90,7 @@ extension Connection { register(functionName, argc: argc, value: box) } - func createAggregation( + public func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, @@ -122,7 +122,7 @@ extension Connection { createAggregation(aggregate, step: step, final: final, state: state) } - func createAggregation( + public func createAggregation( _ aggregate: String, argumentCount: UInt? = nil, deterministic: Bool = false, diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 810026ce..265021e5 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -6,11 +6,12 @@ import PackageDescription let package = Package( name: "test", dependencies: [ + // for testing from same repository .package(path: "../..") + // normally this would be: + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")] From ab67978f154c404e05a6a634cca30fa0206d1dd0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 6 Sep 2021 01:00:54 +0200 Subject: [PATCH 0806/1046] Update screenshot --- Documentation/Resources/playground@2x.png | Bin 124729 -> 409028 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Documentation/Resources/playground@2x.png b/Documentation/Resources/playground@2x.png index 32646d6aa411b7b5a7163fc692c7252fe8ee8cce..da132718cf4bc7d2cafdff82eb5020abcb46ec93 100644 GIT binary patch literal 409028 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μ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 mauvc0dsvBsb|L^lwr~O9ysL| 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

      2tG!%YcIj$@$EG)wF{Hf_B z7*hWZ`|~XEYOd~c!1dkE9%zjiv!fA44YjrN6YyIcQL3W1UNWDp1!xODGn+Blfk(C}ZJaR1gixHp4H|MF{*qV8MvA#)nQ@MDxV ze!4cnW*HMEgFE1DZaCJ^kkhVO4h)ji+Rk;6QEj!Q#lK$T-Hpd&x@eq3{n`B`YEaa7 zcVxiq2$T}`8gzr}m~LcutHFZn=z;0K6dB=daMgI0JP1>J(B;u9aw)9(9-bqBC_d3-Bu5;(6b;+% zpSm?IUxl@XE#=giM=Dm3nXPTY5s;K5U7k0cxA+Il=16Vezzu%P0$HzLEhHoA-5Dta zE$?-;Dey!sVPIGm(~tHZ!u%nEYi+ralcq*vHP&%j>k3Hj{AnAgTA3t1K5@h>>M3y> zpUbC4Jws|27w|DHH+Dt(-KW2K)JF8mO{S3WitjmDjZsrj{6-NmjPK*Efx60l)i+Tl zBFcoE$GbJ)qodel1mloGH@*z#Q3wwv$Dd2^?MF3fXkwv%s-V7WR%Xb`4GrA4)nAdn zya_RMTBS8|x%N^vEaWR>&amJQ`crM@2ZaM+HBL0^Lix0c;TR5_v(Dx=vtI~HW}W)< zLZ8-0g#%SlxzHbl0ym(cr|AgAWw!+JPRW&_tMTAk?>_-aqFVy9XWN3{o)7z!2OBQ~ zssgD8Nf{NdBBWpACthvbPo~k(FV>ry;+0mW)xdV+41Y8RhOyMDlkm}4UFP$TwVc5l z39a2()vnJnPj;=;tTe6pOilu0b}cAD>UjbF+)n4Ul^KE?v^pdXq*j1EjD<8p%`zwp zn}*)qS~44GRIbL0{!G<_-?GrvfAu;)h46grq?GPSfX!G$A8rDibC^5zfVvmu!cy~I@1$-FX*GG`P)Eo1B+b?~Gvk)n z&!*PHrG$PJh~)`Tp<2jEJL`F@!nFz4onbOT&2T7Dlw zn2pSX>s4nO*>6t~aC)DmDxgZQ@L+GUQwec3vwvk4UVX3b=XL0zO|4L4i6HSl{=D#~ zx~Bq@|1NN?#rc2^pJ(v_y(o8%!vDnp4De6Fx1&Ul%Zt?AXB6t_uN9qIC>#b?=Pef?>3 zP>2b^$kYpC;Sx|Y_kh~ha(@p#WA9k4f)*a6%ti zVWFb-pGIizwg1ugFXjKZFb%NO7|<{=SN=T|JWhH5Fd~Lp^Yy|;l3KR?S0Y;;kTB>^ z!AFc}+vDLu3Z?yueI~J0ihaueQ&g5AMP&?G7~s!3sr94UFv4amOfoRtFgzo!FF4Lk z?n39quCf8Dl*CTr7z*+oAPOISy5|H$dGpVE@T*?UgiGCE4Gj@PE9_MWt+&b2lws2A z-6k11*C8LXT1<+RZt|2m9GrQjWxi0E2v>}#m?RQ;jJUf$wpYfudm>-_*{SJyS70Fn z_90@!S!=@NIRAqfo1e|gqWeNLW?$`3<_}ox*3r_xnM10~vy1e96BlJd6Y{Zxkpo3< zZXo<7%iLqr%f0muKa^qPOIK|1(twR$0HN_qR-gwWc)7{%!B^=D-C8q?cWE}EF$!Vb%fDpb zOELm=dSMiJd6tC&2~&PUaq7c!ZFCe82?R(?>qXbJ7)X|2f3^sei_42oS<#S}bLPns z_|Y=ueUP5(DBNZzoW!-#P)G;|NeIMpBjS4+puA-L4HfY@l7MKak!Gh^Wr+7|^A6Tz zCa>#j5HUp+Bms}HqjVcq!3v83*xU!hBpf)A&0OO&R+5`jOxF8u$pG91f-Wdnqm{P) z@PY3>lwh^uhrbThut9HQRTJQfgmaIPvK; ze4Aftny3MJCC^e}o`}uv?fKpAZb!d0A(V95ppauCc45@e@3-TK^=?L>OQYXVnl01} zKx1I=d{dz73{aD-mz{G3@Jov-Pj8mLfc=%+}z)!H|gSz(+CPCr&*85Vh+G4 zz_&K+jOqNb3O^05N2*+M54TI<>>&WEg`+s;3kNP(fHDFiAR)Dl-0xc(pMY;L5QLSI zN#;=s5@AR_J|j-BbwB(8vKF8(dS3i^y{X}t6%+CpSn~~0M>!MIxFU&w@HkKWn=mYxL1i%(ry znoh5LBp&c(Dj3RR*c`faH@};2>DeVQ>QMqV8>;1IAJUuIvAt4&bJ;dw%4xlC`Ao50r7UrfzwNt-ZaiY(5!K!S z5ezk#`!4?Ql0!hD^L3B>wd>;uoZ+?eT89Qa9-!vkf9eJTI5}}dNjRvGC#T<>ZJoqF znNL6&nI8;5Hio=shjx9Xa(d||Q(g}yNF>^)oh`KcSX3E#MAX)R;SRatnaM-q_TGD} zP0qdcn6T6%E2OY=AV?0;$Fl85d)kyV0}vRD#n&HC8Un>hA-z4d?veHe4|{Nq{~MHj zsS_ycC`?PM)60G}s}4iftKaP_txypox5?COh|1swFML%{7}1}Eb0bIjJ_drDr|X3< zRqn5ui=hxa$|~AQ^Zcjf2!Ti{T_$MYUDdtr^VK;M&}2jNw2>g1%!~Jvru$<`Ty7t+ zk=vikY-~g~_NPH?#l5^H^#1nGFq_C|Z;Q8%Xs0Yn`iSN5`8qb-I&)7?Pow&d*~dW^ zLy$n!T!wvDs5o!9HRKkrxXYUbirzA+`J+$dciFT=#*;*mylJR|ZJspGR)fqBbiD4H z6`H}1+pj|I=bKi7m&XWBeQ#kPzc;ev#n9kJ=h-vSDkok&rN*`@5;cO^)BWQom(d%YeK4cFV&s zz?am6W$v4TYraG*G2l8aIqn zjR!Fb3Aa`7ENoP+pbZ!I~>RfP{;{F%hUxuVSd11 z(34YZL8wF#)AzGK4$I4!XdIpqtT05~cK4jRh8L40m1p7F8O#QQOCd<^cqvmm!7M*y zI|}3s0?&|Q=q?!Q;V)Qf`x@LxkJ}N|CMpiimc9SP#lPh<*}C+qvzb-P48t&S8n~xD zcn)bWtAS&t8V)!oV^h+86yCC`j6%{i+6p~(Va?9o8Yvaot3LztX5-9ofJt?M4XBP= zKS0-6Q5Ek?VDOvVS!Y=MoO%V8gnGdJ(osgk49EPEa$^G=mj!C8;GVvcLU3vy60J50 z)Av2WQ3kw}|LzrEn^)x8PTeUPs{wM~I6vUv;kjpSfu7H8qoRWU@)hn9FTd$Tcl?Gz zMCH0z%)7H%hieTq4g#I8UQr?*a@6xX-Q7^udu&nT-|RyUfV3Xts!*D_fhb>>KB_eM z4^9v0AKJ}E-iWRkGrs3N1@gGug`%B22ZA%2{<`#9brQ>JPW(FpG`n}`3GhGY(yA~? zF^Nuk)8qe~FzXlNz2DI(^2IM(Y3hP|MEmJ7dt&A1v_j#`a<24u6*-&dQTM=wAq=aKmKK0Jb)h*gD;my!W{33^5yTW(vSSCkGRd9n;2zXek;_YV~}M9x3v z;uR~DY8+-d=&4ix%Q@4^t2MDD{Mz`d!3MR}7&0pqt0jBda4^0(v?eng9oY?r2i7EXT*F z?w^2Iy1m*=-|yefhC2#;3;o4U^h+Xbxl$p&lyr`!V%)=Q-tgyh9z|^{oZEcHrsM{l zSnI?4y<+GTXS+@xvq|YT5)aqzsCY!j zH55v-AaGFVOHQoU8dd}b|79quEGf4eh-;l?8^o_n3GnrwFF`$&9cu987hBlZQ{w37 za}A5ejd-0|sQ>k{FV^Om47W*>paHGm;_m8&``HOW18?uQc>MH7rEz3?DLv6un!yn} zF*?0JJ=auW7$8jDajeuu50YUqdg(kLT`{3tI6V3rpY@`H2%gLBPt9#;8;WEc3HvhJ zn47Cj*5i*W3vn$XtFMnMp4u(`UDAacr@>Qb8iOzRIeUOTn$evB$sfm~Ba_-C044&W zBj^PeQW)qVY$mtmDt+dBu2z<2xYoU|44n3Q^G6|LXaUW?3gW0HwRY1<7TS8%X1yV6 z-}B&^2vUqh`5TyYlaa3BmAG4EZ0d1kqynm6;;u);e!rSBbTP+DKmA*-)^%7`#YUMQ z7p~gsj<_$t+zwj|HXl^%8Kqw2McmW`%$2s-Rnw)sQ5+gUcH%+DoK`*l9@*`->#g`X zP1BO%`o0)MPYIt3)wsOgqDm(@get^l5`UM_lT7MW^;!^QcRz^{8t%n9RZ|Q$P{}j zgoR#jv_hY#esHh$p2P}p1AFJ0(K^Y;iaT!-Z*ViN6c^(B%hPm*$X2hvt~=ag)M5jW z@_6pNs;YUH&>fx0yH1iO;$Nj9vRoisJvFqO@LGolCb!pak(00$1Mqa21-$trCL{>+ zek}PjI1_T_1z10^epuCTg5%F2P~0QCx1baZMQu z2inNjAYnc^A`&;f%j^%-N;q4Wzg=1tA&U}{ZRYiexd#q4((6tvWdnOE^N7SfT#tUo zIm>qtjs4>rgbKlX#sO{r>92R|7ob28>Uh=-OwK{DilBXoVz%)|?QuA_j2hakQn?zf zmIqox%>QHTD}dr$mT+-`1Pu~`1q~itgA?4{A-FG^5Q1B9cXxMN+}%Am!JPzx+xt1^ z+gzqIUn;f2OBhN^YGCjgz?oDzm=iRa`Lem=6Iz|$` zBNuf#tU>8lY(J07XrV}BDwreEXyz>ASdPk~-buSQ{@d)MYR zhKhQI(-RVYP;b{p!iar(Ag~eqVCoEQ-*$Dm9ff(L*BsXeC8I5e;)NvfJV0A+p1C*B zNe#e#gpLK^*Mz=K919em(}G{Elv95gVve+&o$Byb+qZJE51}nH&ny1 zmo{oR-{Ip@Yqh?7vXf%t;Y(S3JOwpUb0b+ZpdzVt=P?^m-%A`?kI1E)>vYQzO4w|7 z7FhA)4*H0UK?}S}o#2|r>p~%QIp_elI|2z!C=t~t@A96i7#9irJ_emshEVevj~VI>L)Nvo!)`V{rxBke21khmL)x5!CV$N%J;J9jO6= z5~EDm)KmmLk|F>}JCdtsvTS*DR)J(RXjc;pAXM?RM#z&&4d+f2@*<>vX*GY!z<-Bp zE-KfcB|>Uhcy^b|N<-%Gkpz;ywWYCrx?#iy$892&-aVtEmh&gb*Z*({R(^Cw-z*HR z^@En2Q@5$(;t&3aUt1>*Ylh3M>)#ca=Tl=wl=Dh~3=KqoJr`_xhL>W* zoKU;yt5a)hF>$&~N61lwsux9itcHDg#`HEcsrOsY@mR)&M2rt5t#{wVtJwleqeWU# z&PdA5oQY6WiiP;(FYjr4cH~rBWSiEz)CwKRBe22ZFj^~b5Bt|M*NGNauOR7~_ zZIyBu4h!!3oK5R0=TqaKr;bQn*)b=Zs-4o23TXGd_k9#61(yZ(KD!>Yy`eW7Co^}7 z6c045clUYkB+Lap-#c{or$VY=*zZi{#YIhr7-(s$TC}LHN(-0Zx&Byuu-h>KweBZz zB)Yl`#jofSnB273lDC<4LE+xM#a+whTl8x{q|R*lxy5a_7k%iw(?>%m=wH-S;tGp| z%`(YSZM`(`H6;`(xra)(rKt(oqA7Yrd0iwm#OG`_Po|WGV86&V5-DIob-mu4Z=Xw6 z=GlY;wuSabEDGsFT6eBbNdH}^oLeqC>=nw~v{mO8E^<^os+h)1WT4g5olthku&84{ zVyu>KtH)x@PN|XgE>)yVe%3zU8JBKwK(TFTsV;lJu(N!B7d*E6rfrva7ucEh^*m1+ zx}DUlwX6xx0ZZ95ykOoGz#xXiAoipc<#$JN>4c)c6$aC)RV#UUNp#)_TTO;pyzemI z#%w7HY4Jl{;J{iLQfRfa+Gq1bX&y3;8-c8o5fnld8^j(4Tyt(6e4k{)M80VDdC|X# z8i6!v7i%i^=3EWLrtw3qi>my5me}QQbLq6Oro8c8qE-f%r7`-~^YG-<@+qNp9p4;& z(_dy*A1!+Y#F9uFM+n&+Ki9tJ-qq#()-}1lU8WU{MR_jCs1~SnLSxP)@)k2Wc%fCr z&r%F`+AL+PoZ@3=v#Ppbk%C`e%(tRk+U8XifB*itq-{0%$(m{e5Sj;w(0fI888%0! z^L0w&0v(?pDiJjnwbI*Nl@cxQi1?)8?k}_2AHO!lRHT&C;6&FL0e3N*K6c0jfRI@#HxI; zL@#ZR&H#et7EKgu0r=F^Y`D{nHb7Xh&XVK~1|5T-sFtOi{i?7vT&?!kOYErcXpHt} zBmGdc(yhIPn3rsw^ij}IWR?H!yZ&oj=zzT7b=CXE~i*|QHKuST1V63 zzZ(&r7-_^%e$7IPe0{HY8xm#U(rI{NvXba}y}de<;uHj7VKVGQw{di#+iskk&}zNR z&^q{fs!DDb18wCf@(VT0+8n!C-&^%FSudB;8Pr9(L--{N!s5Iz{5WU6hfvP#jvS>k z>WO>w19nTE4R888vOOgqDtdoadS}<`<2BzOeNiZh)zfL%vxLb%Tj(&_)Rh1Si_mF)5=th2a>Tc^KVCF<|ohA6w z1M))h2BjRoz1frcL-wVp@mufr%952hM0kOdKcaU|O^Ck^hYvTZN`xNN6AQCO0pXm2 zT5b3`ppet8@*5-7om^q^ufe!HvM0{l?9Z@^Q)&0>=py=dp1r{GzkD@}no4o<<43d? zB=vR2-GHQfkZLJUrG78dOU8SlD%l4gQT&?TIw!ad8ko9p-x6u(^jS%TQYBU}0T+XJ ztD?X>Hp$z!-yLqNlrE6{kp|klKmrMou=sl%#shIqNZ_rrx1&Z6?STlh%Eh#5@31mA z*r=Z4bXa-0tTm|m5MrPsKpq{U)Oumwxo*bb0C7OETQiGwMD}jH=Z2ZMwbBGR?;P@% zDQlC|wx{qJ*KlsMa_KS2=da)>GHjNM0}76PenzcEeiY8covkZ?T7(A)6kMzr^bbLr zXvyh>MD?uWEFj&K8c8FdOpVVIo-CAhR{rRt`q{yJ*^ein&_3GR)d({_Vv7PsKwJIA zIb#!Qg~CDF#_Dqk&m#k+Ri9bOh)@6Kb*-7H1{uE}YnlFcj?{AJ*p5uBgf>%C>+l~=DW@MXNZjudGR}w{fpHpNw0?QQ|TWyhW$V^^2I({ z{Jp1{>JN!`*P`(JYTm>Sn%Zv*upo9HrRPZ}ECfZ?h;f7`5f2!@`*Ngh3 zg_r2z5;&>^zF;7q-iKfC(a4@d)@O7;b!%PsHiFEz@E!Dz1r~+xD&+b~h%3aAF0Nf-{Q9CQw#27wm(z z=!Ydqej)`ka1vEmv!7%?<-LdZt1F^Yg^6CA7{T+`Gf-jxiW<1?;a#R+gpscQ zG}J*JI^P1_r(%PdO6eq(i5f{0Dk%1yepoDNoiDq}%sYL7ibzNGx~_*t7R+-wMw^+( zQdy#R!8Z(fw!>`jrPIaf(3VGjH9Cr)@W)JoN7&Ct7LFIR+4tJmYplyu>q>X?&z1Hm z4koW!jE9oR%{{j{Aj%%I7kP)hQRYH4x({lzhA4W;C96)kybL4{c8&6!Tz z=Q23h7&gM754MCUHSOc& z^E>l>*YfEC3`h%w{yZ&)!Nnb4gz%v3oCo_`cUu9(&>rFfo(zM9Px1462j1y~MY2`3beq0Srv z{#e-Ltvn)y>&OEpvNlj6v<&5KsUqOGs};6y<9r4}ux~@qD*6V)L6^n*zNXOsVwmpj4fYM zh>lKMpf?eVwOEuozL-O|eCn-koq2=(lCtIA{WFt(DRa~__g|#$tFuJn30$n86jJhe z(b0abhtEsv#+-=zwa4(yWO3v5=DuHi5##L#y)6VeL)`wv zd)@SF&#G$6wh)3X7~*2nq|)Zt4P13M4*n{EPg!$V82{@QX(%pBK-A~)hI-|}`l%BK z9zun}aAa$x?%R7@oM_+_t570o4Or>>ph{KeNKQUOFEr(SrrT*YTQYci$%369Hw8tn zQ(ZL-sY=wxvynQ-21%dI($%^uvE|#>4@-Wcf&_-&wklUWqXE}g!qvro#-jQ}H+gn}ypUa=^7xUnZAkyzGHJV6Jec*iMQs88 z{E+QKG^gPNYh)!BP13%sJ3MQy^L;$ix`F=P>V0#ioA04>q02%W;;^cFlpMp|9W$&! z#u54Hr<=qRHVR-lq}dB~p3^%^e4cW}wIeX9>+X2w%|96C!8)J$5+Nlv)GIr-$i%OR z&?r$!W^?o!9=iq&zeKJ~T#`yhgR>`%Z=`cdl{CF6sy8g&sp3eTh9Hh?RgAb9D%pUL znmV5qtl?GQJ8lb}wcl=-{Mel-5`=dF`uwmD|B<6OE0NU(asS~c_iK&COn+XQ>vuIR zzq0*f4@A&BRci9|XX_#I&6Hlv(u~wS9j^Lx=UwLl|Ih~}4n!8x_usUr3(MtTyoZzy zxSq3zzC>T#)Qia>@x7m{m8PXuw=K@GQ^X18I`jPeS&UXk{pX^3VSe6~h_K<1bEBfM z#@bGDyGY;*U)c1z0uvUlpsDBeS#Njab>CN)anIHpB!uo$BVV zjvF8Fmi$ZKLt=nm!pzrL8No@@sg-~G0{x;dtUmhZ(Ov=R)|vOrbzdE||3~hnl9kpi zy<7I?^EiB8^lr1Ji`_=UeU;&IH?HSo%#meb z7-RK_R#R5TZ}Tf#u~R@kvEIJFKo+;drybUxW0|^)X~P08>(`ys!gSx~UzE`5TtZBw z&-P)(6H9TItGj(`>C3q^HX6%SGul;c$*5>J9o)6zvabHIv6WgiQD0@b(o(_`)*o{= z;0k0!48LLPbvXVY!%%F ztv3x^heha>8({%kU7wqN#Ix*84)a)G-(A-YU7q7q={|hw9R7aleoyRtvc-(t>h|NM zTIw7Zw{8;C`x9(>;%z-Dj`gI72`4@GpQv4?NeKxLaw84TsO}8y{8n-K4udQ;1g)YP( zgJ(iTUYVJ6#0IOd}<6&0F|y%0|e^(OmBUv^mwg?3BiOfT3odMmI+OB6Pe zWubzRXT%0}MKW^>t429`?F${4G-BZT-n928G1h9POv2@+gy5}+Ej-gB9Y+p@^xdo!4I)*DVybtWeHi=+xA1-Doo|U zmUGSTmv7F|Z``!06w0HKT@Hy9@B0wa`hv-UOvmkkUGk4kU!*kH4k(-%aY?p1IOibI zk5^z@C0a{I#hlev3Sbwr`qkPBZsJ{5Qzb@ip|;4b5KiO4i1SqPc`{baAC^*3L{*)4 zT1Hzhg&mX%GUFi>uZY|(sp|91mRD+p1+J>am6HQ-+bu|a7#W-Teux8Jlf^xZ)yZoz(6G+PT?rs$(L}(PqPDj?LAF%ImI|FRS&Unwu;+u6hgOVyy){70^z#s9l;p z7}81YJC+Sj?q=Qw1f)exrS5R4PASKZFlRaP+Fj=J{Q8jvrl?s=dc`vyuuyQJUA}x0 zX#jsdxJQn(SQs8d5C1-r=s8lx+sOk`;tQOBJ|bkOZAM;&3>TiuQ!Oj!}b!WUA^5oMyY&>JeH#< z%y~&MGQNzC_UdoTbD2_!7ip!iTg zoy|5|fksiw#u@5c*uCS!@6Z{Q&WDJNIVrI@w1P(mVS(RL-M5tF_>}smRMYZOuZ#SM zCSIZ`5|pe~qhJ-z_%!wht{#@myAvBrK zLQ5jo*VCyISChkGOzrNynRwc`Z4EEiyaYEDfhhK#+jI8JQ-y7V33)SWxV=7&8Vr50 z6c3HIO*&lS?eu$8l=@x=JZxHZwa zDQ}KoTH6GTkE+FPLvQ+i2)LS&DT zKbbfi^0n+jU*Lg6p;`{DtIERecIq{!BZbx36c2E-l+noHz_jME5M4Px0#&rGc1VZO zPU1}q-<#mHg>WW-Z>-L&$1ox&>$s;W|&YMYmoYG3F)Mf3{--=$6#_c)5nrGV}?iPC6HQ||G@&N zU!JdUEsI+{OleWCqByxU@E+o^DIHOwvva@wX`)_jT2a)wDEe0*C_fZ?2)`BbivqdQ z;FqE9)I9jEK$cLkF&3LEB%)A9Lye?`MT5U(@2?xJX2krnBtHPVw;v8%E`$H3jhv#x z3h+21lj;E{7mF71Xsf(C4zhlmgTZ3N%$VT1o2rSDY1d2X^YHq?hge-o)R>^S57}zp ziR-gEvnIg64lcE~f6-`tv)8tk^96I~Rki83mgT8tpi~N%HgKy-+RH-6g_(>mS9U!E z^0FYhEgAq`@pCTcY-Ra5wN`d~MWuEW98IlVOp!!8NjD>St#U!dK4B|c9UY+RAsGS| z{D6P;oa!wl%;jq$cVe!vs?RvK$?S)IFZ!e&6rn6v9$s9~sjJD!nbj8^rzlh^i6cj! zvs;5?)41p|e)zq0+(UMFe^gIC8WckTxYhwf>ka`1HYT!AnWl5eg|KFaQAhir@>Q+X zB(0vAeX|kVWYW($v<;=GNjE+MTsPh=sNB~1*BKe`T7p>++Zae6BOwqBVLWH=2}rNr z!fXB76??gObrjqe=^W0VOPMaXkjD@jg+FJXUV53)1Cf{?|7_^SI7wH^J$Z%;o!4kXN*wk3%Cp}#UmtVtlxax+wH9?rC)*?&f>v-}k))n>)d507u; zpT{_OJxo!muF00ge#2Xgi$0S4G5M!ybYhYbBB-uiq(hO|b4X$}K-aH>)xeh1bt4|R z^`T%$LwY6%hAEl)LyzeT)9dEgy(Y5Y+T-ex7_0kvo~8O;L>&HoVf_`%Z<;EN<2^2ezERPw*t zaDnbyBdcyn{4-Jip)cI|k(E4F?TP>W=|3O%V;@PDWMP*5JC*1^1Xr>=%B11S`Q(o- zlKof#_;se~(=p z5&^`uWsW-I_a6rS!^U+$D1)DJha#Rn2TF-yfxatvbPD}JRvql4utbFY z6%{f!V1Yk-OBT)xP%_aQQt(HE)&x-Mf*wf7$kBRAkvv zpFUGyt*|JVn#Nl$)Gkr+vj6cu@O45I;LA0b5$k^==uwbuB|@vU*sH)PABA$EN~{98 zkClwYqLU{>P%KR=-F0b&%4xUp)$&{U_;(J~zr$31Z)ZXhXyJ1| zBf+072(H!wsZb`P^gx^;HW3j~wQrV@W-?i=$LYyQWUZBE5*u)|SfBjvRYLaN+S*#= z!8*#*ar-FO>c_d}WGwXu(?AyJU@KS-QTfBZh^+Dt+|?iGz zuxSAhHUsMt6+^2(iC)Df0py5nPOB30ha9zGtSx|q?IP?-$3Yd&FPC3oU|_`7d)nb9 zfa{i0fM9x<%XKvad&~0!PP?mrq1F#jI}1@NN1-I&%1Y< z03uATuO*ZgX>o7&C?t-RO+5f;Ggq=MlgdK{)QlxfMBpleg3mz=Af;CIZX!PA?xP6L z(9_(wsX~1P}SC#({KWAr@j`~8%&da zU}gPtA~~P|3`a4Q5_~#JL6Ycwf>6l~2}(#Dp0LSP5UKzsNP$X8A*a*#4iQeLgZRsv zoAtD`xjdCe+^BR52_bLy--pTL=hbUKt2FYuNl8DgJp2m5Pylj-bQ#tty4ex9){7JX z_+eS#{&*A_ACFt5Q*TF3rCEzS^UiQMk=1UkOWXyo^bh6&U$esirSZ$wfcj~tJT?9K zu`yPm^dNC8H7F`zwd{sBfoTa0${u~xxfZKNAbm%gp8De7$`LH?0hOGTjvMln!K<<4 z{E#Tpd8HHp$R7g`CL^O_&CAO>QE6n~VL2@>bz<_N0Wj@`INhTEP(zpO3c+0 z`GYgoXr8oWZM*m&^I_~bLliLJnovmRcdK{UjT{28*ey3*+5OC{V%5CAg;Xd~k?v37mi@BcEf9DbK34vg`9agt@_=qKzxd6cO&A&j zYL&@;zufu>Gk~>SfOzm3=&0@F*RK~uL^j~Gq1S|jgcyL|{{#hx%EhRO&}>NM@I(Z+Irj098rbK&kw_ z*!}ju68mvf6*W88K4D}y)+1SgB_sBKTdx1pC7wxQk9GNsX8zIldrW|=d(0u)e;Yh) zfKDDEX(xXw#kNC<#}3X{@-RFl7ZRf{`;l7{e)=_k%qFH_KoKAd^#}j)9Qe8jEZ`#q z62U@G6%dH#ktZ~Dg!?j23jo{@;zw&hFRNnUN#FUQ%m8}nrNOuf|K{*#fG>?YUQ)78 zD+`}{o-mY=$!9vr5CI6Wcy=i;+7-vd``Qs08l~~pt|O&BKmt86W|eFpuA}HfWq*X z!2i}LS*lYXY3}%F>EsNmVxXd;1_bhuQic(VvxdAcADB3M%RV>(MrM!o$ji&y=!;PN zV4I>-pRCvat=QFsO0K;8V7^7Y)<(_6fTn%%$!I1f02q-2jTiNYwpdhvsq}%s!Nq;U zYN6t<7SYKvk|`od#>F*nBvinGg}*3wvW+UnS~`e)@13F{iZ!7m7sTl<6H16|U!>G} z3!BZK!QXWj-l^}hx#l}nk~N_jtBLg?d!IzEQi`)+5?qhIk{j~MVpI|qeCvxim}-?| z^a_@P8|TTAXh`@t3S>3QDgKZuyI-3XU0VUaa*nwXm2zcvNGd!wXb$}8Cx$!?hiV3} zGAI-%kcYgvIX{jgSoqiw=%jd2fF$lnUhw_$ySE+v{8>F&X?8>L5W=hv01p-|baO$$ zdtkRvQEc#LzbVP6ekhomIohMvBxTjuZgUSS;d~a?WqFv3-@_Xgh~eYLcuFSIBbn;n zyfRLYJF|Ac=QYu#)hHv~lXEXBcxFhob$IzEVRJ+A} ze=k4UpOP*}f+s;QBqfFKa9?GCmgA7h{n5K|vq&Ue^@zs~>x#R$#=>Pk+3#8kkqie|Pqtl-HY^MGOE^ITBzIQ3c`tF^M2gc1$K z$GV$M7kFpcSnHu^Q4atUB{;8@>hZGHL^gt67l7@nl9YTS6pU!%&67E?=<_;d!OA%u zB0)hmyzb0p+B>N!7)c%YpH%#0j8fjKQ!ddAN~A4TiYJ9eGJmw5gS_|(8}!1LRagAy z`5Bg!F@?!Ua;$Vw+~C|~-a)_X>6JLB$mzYDX1!kMlHpn(1LG&l$;5);b;ZDbtscjaia!JTaKwA!eL{jxG+*|@zAoO_6Lyq=X}2N!w9NPV0X+6 zSS}ZN)xS+deDm=zyqUXJLp|OIZLv7ENJBewBsD@AX`73n>a^1_eX{`c%Jb&6t+sZZ zh<jZlh&{;(Y^fJ`X-oDjMB9H74gAd7{mC0ds)@Q?Y6tjB(J8lz3!jq)IVVPEW7x?GpeM-MqH7FaXXOIY{7JOF}`Iw7*4nb4{3dIK3w^3{EH)2(R8##&7Qzca5#X+r(XYZyjGui z3TrLvq;7;rnHAs3dhQK3`@&^H?asGu3f{e%s9Dr~uoK8VEr^RIXF%ESjjObA@Iz-m z*IUuhWDL9BcObpJTX)?3U*3K94u2!CO;<E7DW)x_Quc~=_Cr};ae#aK2f7(>4}e?K z&ac?QG++JMH`#Y^+`|hthu24fL9LDe5hG0c%*vmM@T3 zMi*?eP+EZYYDb1zoN|}?QlkW91MFd+E!LY>e}v*woaLXwx-D*)pNEmyVkBK$=yIH$3V*oAsJ5mRTTw*BI!w z@AX%u2|mkJWe%asZPlUy?e>@XbQ^eIm8)&sWyy|xtSOdT)w za}6Ggz!Zmii(YNAPfcpzn_+#(JiNe$B_-Q|#EVh@^HHHh#?_R|woN|E?1KLx^8>tnOoQ1-8$L z;m@YI3TcM3&#WM5S~=7??uhcP@oWp=_x3mGfS8cT=QLJlj?`oNoxJe)TO&za*2Q;X z_Ct_35c}OlV!2(<#6G!iUY@*_!1?FVeg`yH%?2dq<6RGm(GJHDLP zC`U^vA5x~SOI$~}VVyiH?H4HKDr{4fi_vK{o%W0u(+h7VsoaB^dX+p*y&d!j-QEMq{_QMD5GF-5ml@64KPMXsabMLEIf}SgXjc2cYSF@MRt1z2(B-*? zaex0j`RZa4UEOnk7_psh;pJ@pczI-!zeV(aJl2mp5n_W!^It7>=nsDa@#$Z#6+SJw zLRkFb42=48WE5*+Si~1^dx5*oY2km$iX;Ic1!5>*BBeNQ0GhS!q%gp~>iz|l{J_v; zj*2VSYz(MczaW6NxrS5Yw? zD~@?VhhemWsgr)rJfSTVrd1UJFqR zm3m`sAZsSh7L6$6)c{SLh<2#co_(01_l>w?V*$Ou%ilMgOtHZBDR{Gg@%M+oXC@*~ z+Ba?O%`0oqWBiEheNm0rAg zei4N?Rk%0RzizE8p#6616zT0+1foE!9E-D^#}%7^C>_@~PH)yd%I5^D@?mnV zq-#Q>MdfX|8Ui?@Sbve+crdo}g)sxXFTuMP6h%1P&p9RS6!cGX{MIHiVOWc;{Sh!YC+JhdPySz4($DJ72XM| zhdbk0m2P)%E;I$xirXV8<%h2Uhc@GB`yA?hFAXFtqCQHQV6IXPexu!$LLuuI=Z~qJ zEc)q~Wx&Ezl+Hq*P>j`l*(3C`&QFO?=J_}#v*tb1t|^2(ZC zzC}Ypy9IuP=~QA%Xl{Q=kN#OXGxL#3I54#a~( z|3)-X=rI`I+(k>R?a=}sXw%+OA4)Q*Pq$ntD%*hM!(tc}5w+M)bQ<@Ybyd014Y0A1 zY?^lY)N{F#Qc6%{O~>8--1z?1ZtaJFdIhMI#d3TG{CT<_24^(YTx9)sQ<@n1QS0)@ z4+TE;(-13Sq(k@{?~YY8Z}g=uJ+lO4=V+ckk|H!#ar_!sQtJy&+X~J!&yzVfI<8Lb zHu&=u38EJI_O}^)179%vvxa1mtJs-2xSM??+(T4YZ`4$gxFYAx#Wv;ChpdC2u`!3; z#*KE2YdrG3X+5ZP>@){cEK=Wt&c0GmCS!+1#%RK;iiYXwUf$&ON0~r zSxPBdGpY9K2CrzwXpxf(F)Yzjqe{7>$$B;!$mL^K@pY-qD&3#uh%rdgE&G~4Y2(%e zn|w0akQ&m}1X8rJrb@vepO=G_*-oA0yfCIPN1`#GlTgn2{BFn-{yT_`c6r`_kXJU$ zveI_g4q+i>m3Jy|^mpgp|HCd0_l4m|fks(_P>Sfn1_3_*m@ii+1G|%XM%{tR=h&wJ zqBDiGH`co%lhSB?iK8 zaZ?jR$`sCDzR0Nn7pKr|P1(_B2|e5L7)+qSSu{5MpFZwQ6>~CH&Ca>tv5Xn-i8 zP8zG$ganzBU7W1ra`-s5L@*WH_E^j|LIY8Y5PE#8S`{%yUhr3jJLgEnzzG`xcO@mJ zBTs*8ggpy^OOR-;Owg5r&A?Mykw6t6(Q-CqPpv8_qhk0ZX;sVgmQtL^S0^g02WFEC z4b)nOOjaY-7E>PtvnUduj-RJKajbPOC=z3uDvp`f?$~j+-4~^Ke!!oCeJNcHH%lpc z)TD6lW*)2)ne)TH&SGK4;|&U9Jm&q(2d&e%&{`eqGz3}qWh%v-Ncgl*aED}AQgVd0 zz?Eouy6uA9N(+aL47@6W^O}7r`4%IK`7}Q?y|xdl3Bof9A#w8GnN3uL@n{`72YBV~ zET=4xeO&%xhYqv(aZD$BCha*kWRQnQ8;Krz`7<>bS}5@_5AxMWLdB%4-yZ(+8<6Ac zA4DXp!3YC#M4%jrm}n^2ll`cxy9+);;4Eqn^QgAcNDq+_b7)>W^0+mPAy4*38sR=B zjFFwCn8|P`JNIs0Ad0?S2iV(?;uymnQZzJoAUHyyDA^{SZZ1iZ|kI z@``&G%7fqGt!s2fAgZE1Du2ekUt#K-tI8Ab_)@${699!79crwc{{H2#(efz}-i8ew zY3p6A(KJ?4HEx+{)*U06K3uI|5*rnMQr4d+eWZBE9pTt2U|b_$1euEFfx-VNrSi}-%d8J!Sw0b`~4u56`Iv!ZS@?v|)) zp{LY3bK+SyWvO2lqdgiU8#^};_u!m8mH-!`*=_;8hAw?td;7N`1;rCAC3 zJg8cuffZ=WQ?vRhj@$cJq6;C$YG=;th|(2sJjZKehK*R48FXl>yf)JwMYnIzo|D_K z+OLdiUwDJFYTtk1WE=VOP&S+=a{uc$t9v4=d}3|lU*WU> ztcC0k-8WOJiIqHPZ7!X)hunr|5BQONF85{MC<_Fl!sR28d3#Y{O!c}|+)yql8sS=M z#74sV0AzJR%ki_s*5Wt1>ZF0E`%Y2>ft}f7fMUI z$Ue5AYH^_{~~xCyrbq4OZzr?Gxf+niT{bs(JmuH8!XhVF|2)uE)W4Bmb&t(Seb z4OLNCh-;|Y0qj+S&4mn=dMWn-zhIWJ4aLq8JBRPV0#a@)e z4_^yz3Qku-mD2mBY_U`fgHEc1tE(4!tKnW0p-UQo`u$CEWCT#L9+F(NRx-amd{oiE z*S6_^Hg`XdAcosrJzfUVz8BZiTyTdeNH}XV_(3+y0RgHIiM+r39pUgkkh!d3=D&=g zId;Tz#OaIDYc-8H5tCAnI0-!b60^rjrkq$^d9qD39ZbA08M%*KJ&PhV7=~vYfWV?H zb`r#UI^wDK?u%(_GN&UoSy5MX8h)a)x}FA;RRi+ds86_V{V4S?&k-`D)GNKF8i>hw z)?eM84X1C+pvNP;Bgk#B-Hc)#SXs2>N-G#(YI4*>kB*2Ft=LdsB@&TqCxicWY|Qx_BBI zz3h^j7#hZ-4ed%-Bcydd%E-DGV(>3jhw`ZE2x8eN|8J^JxD5lOx@|wMK`xsrXs3-t z@;s13RNzB0j~lH{&F}$f2EBF@uFWBP9c?t|1)eC2z^5EREYr5}p;d+;_h_pzubZOI z743Un*H%?YXXjI#=!K&KVP(FAqj8T$yuFVE)#n3mI6sLSyWH+-KX;1z;k+F5a4GW<8Ld`YI@r?wV|uPfq2z8_)v6oYEA>7 zy>g;~7~j@#x<;=O0g=Ki6~^MU2BGQRuP?Ja`m)hxuT_?Xe|j&%1&Cl7l!%^Cxzm>|_KxJA-w3-LXc zf@#r35!2ui@3MNIc=TqC@|v@!U6d7i>|!;g@xH~s^%B{ON7=k!eDzR?4*nU-v^Q(L z(DQuH;GmLON)OKc;}t^jQ7gsg+#z+chMnhjyKCg?RUi75ZFTDwb#x}Or70<83FAuk zXFGJ4z87Wc)M7m8&HdpsHK?PNd!NHQ^{2DyaRSA9R9XGc;(7X_0t_wWMO=p!Qq^DP z(9=S=$3k`w>!9j?7t%sK7(Ev2x?P_y|FPx*WzQeWRz8iK)BmdsXroQp8!)>{6GioD zKKz?kTCahZKl*yq%>AJv{?P{fpMY{4LL8Twe-EY03u0h#a38Iz{`h~XSq#?$l>0>Tf7_Dx>W?+2oi$ln{kvwM%Y1~u z2)J7(%4mLSf&RsEVwA@&!SkP2{yuvD_d_zeN9Cx&V`cqBLjNd7|3_xFn0&A>{2$r; zXi(i%8xa$8r-MCNH-`N!!vd;@8UNINJBcPn3mM1dXfB@0laTz*MTK6ciOg&wJJzs2 zvOj^@WD`gp*Vh+I<86LHr=VuHG4z^BAv>H7I5EnVPKwx)`gSQVFK;)@I=fTz=C)@w zOw&&>Pu5~?M1GZF(&#-`NEi&`H^yCQDZ;dOA7x}xxQt|cEt!o5$X+sK#~Fx*^#?>z zLYMj%y&{dbTcmT6y7CXhALn_z2*=Dr7x`Y~JJEiB8w>K*CEyDYvj|PLSk~R@+}0ad zW34*2t}qzjbKR=uC9)wbP{^5ZG2{hs|9h*NcA5{4PTi^u`@Oa!#6$6U*JdKnFKOp5td zx54|ZRgW89H$jD5sn-W#UzT}f#Pl>xrz`7$STSXQ2lRugx0(Iw@89dW3Y z=NxiNm5;;Cu9j;(Ff&AzFTF~Be55>e9I_n}@!1XKO_Crj>=CP%G?iPUr(rSq^ZD&F z3@D1d64(Iu8zhCtoCHKO^ZR*V*PAV!uniUlpDXlFt!zWa4^R zwp-R(HYdYQ@@xr{GPW8Hoe=XZ5DIWml$Bn(ua`;&xek3VVmmKx#jyy%y$&vY=^H_X z09Vz|*V~!GUSMgGLBeNDXt*@r;UL)9X4lg?|DF_#U&xHQmC|fm2T@q&DCCdvUR5zt7IT(7ex88c3vnl z-jD_%#HNz((^L&yc(|N4>lI=%`vf>N*Q z#-Emph&=7k2ispTNLbY`O=_C*l>Kr2qXtrA%x9;?V6*c&osSrd-igRWzvke^Lvh&D zk02`4hFu(6IuD1X*2Gt$!$PvqL~EqpfAEH9Z(4e33?4H6Ga4@M%9_e<%H#28t%+~5 zhnPA3{n;Xly{ug!W_AAo9WDLz;m~>B7llT`^HXKoY`eFTIYk$t`fbF6IO+4g-#IMS zWf}M}uxm~p4i$SsliqC7CI`Jp#Dbt8*!`~7)IM+b##W&GSGnb>AaB*n@>?ZnX1CYE zYpH%6=tXV0%DgT~;!4?e()#|(M}8he`kz^?NP1E*70SjtY$B8Ml_n*)VIa@$u$E47 zzS+ey0BVfyx=VVz1~G;Fs(l-9q?n|!z2`tbLh%w?&bh42$rVJWwb|1w-O{?s1h}}dS?tkv|Ks1!ikQl$gsmz7^z)drET|cm95^3%FiXt^OQVMfqs^ z*8E-A>-C+hnc#C6@#DUD*P}8R7=vDLSDXEi1hrasq7iiSRA+h7{Vm17#189af1TOx z=XaWSFx+NTdtO4>efwmLu*E)2MxX;CPd1pob2aXG-V%SgAa_3mc4q8e5xw&OoN;mN zyoi$#)+KX)IW69&MGp8+BK;V=hhn7^cEKL$PvDv|4V+~l!GPYLiX3INel`R{G3SUA zxY;-(he{D3cj=u0*AC=FUUi1o&N2bLXiQ15IFyKT;>=D-0S#vTZ{(&ayJ?12Im8yl zwxUxDoV`jWY#-15N{_`kIYvptVN^`IQo8zQ8Wz&rxpB>fzL!2al`mc@bS}_pv5Km) z*m@>5_VWDHujn^as())FU`~6d`0fK;_k5+Pn(BrQV%u&rpTMiEpgz9y!+tpuYF{zDx+gyKxWW{ zsKrDU-c8rloE9INy4F3FN4t;m$<6t-eArgAC)U9Tx6wdJvff|ck*J)O6Sjy~_l~eB zd1kkF+{b(@Sr0sl!?NqmzQkd#yE8xCXFAWZj*`BRFtaw)pSNwjq)f_MXT;lM0HOwT zfm~i1+yPH_PDTRu;exaP2F^S8bU(c*Af~fx)&J^{Fj7Pq`qM@KTZ4_<@k+!k;tRQ% z?$4A=z3baI(6@Ef9ux1Z+ARjoS!kB{vv*r;v~E87qY8i*x{RhbGD{- zbvPcUQ}K-_iS>u^5d5_m!IOUHksRg6me2|MKrcyMc*U+>_F)AX(_JyaUyBs80VYkq zcYbH*=Phb+YO#uV2xBIPk^jxxKuOGP+dRSGzpc;RT<2L+sN0M{E}|T5nrp+m=tX3S zN_judsN2%yaKJYwW?&6mqFV1P{ zr#l&FXeAVefi&N=Q&A$Du~PjR7wS*N@&sg}IQngbP8dB+0)BwJ1G8 zPfM6q;G#~hbXG0Tx#89lWD_5JkD0P6({d2y?QSwFeBuX+2K$V$7LF;=CeB^4C*(gK z+q-oti-$acNQiiu%}wFXh+k1SZ+zfV1)A z9;$&XsM+IPfYOz0Q`}!9xetK?G}#SwCa=(pY0vg@dlTFd@uQxEw+Q7?W91q6@v2gI zgjw9QL)xGG+#gV1v$E8#wvRmdgIZrHC&;a9kuKi?*(^s=BGLqua3@1YXKk;}JCHO! zTDB2=T|yPL9;?&u{%GK8;q(oUHnu^Ski?eZ4jwI8eMYio@GXG-=p}#1M)hlNXPfD; zw-aLeGGp)X6KgSL=S<{}?%T!LSr9d8^ZQZX6rMBLuLFvm9oKBypHESx?irtI}@;i#S|-Fl;`ucmJLe;-1Suuin{pw#fwSKzzKYiU?UY>DWKWg`0I9 z%owRheTwjbw!R9`4L(9?j|f^NgDkMwBKg8q5aldr4#?}wu5)j0CMFS7g3Lx~-={?qH0*Xu9$b`>rg5A3z>*Knu)A(O7*2_J>9Er%SFqWZhzd-*yJSIj>5;+{^QjuzOi6mxlFY>jC41+CM`mx9+-Me6n=8tJopFu6Z z_f*F*jP~X4r_T(>0T7gE-X3{GB0;Ia4(g zghDGNc{>Qj)|;Bj7T4F1oXCIb`nTF5K6=ta+`T}rvlA!Mf_Lx7jCA7k{{v8FD^^rkV&+wT+_eNq!F)5Ax!gw>yv)1aG`L|^FM6&%- zLL9y#jjzz?b!hiK%$VT4v>7{WCz&+=QH^hNzS>TigA6w~qI8Iz>TojCulPiF%18oq z+>GQrkk?-f;(NyT*+kcnLe2TfiNoIrXO1h>NF;}eui^6a#PjQ6bh%xPo(JW(JN=D$ z;NV0;dqOzHPGF@yXCkmROjE_t;=C}xWtIl7Y*Wh%0<{Z*TE!;(O~ZMsK=F5VTVjCu z4+zb)arQclQ)&)_D99QX3;r*nu1)X;q(A74TV&KaSrW6_d4$=*v_I~)X<9cE>kv^z zAg5XlRgOWp$-}J-MD29i;Qp~=%6RK?SF!4@pY@aUk7=nVITo#)q5!SOBWs6&5aK+I z5l?Am51E1R@awexqYim9HXZqe z>-WdifY82E6I ztMdd<__`M;-Kk|J(z|}z>MB07HIy|cyT;}q)6kv&H?Y-hLoSuRp@n?6iab-4Wh=IU z&KgxIX`ebXiXR&wXw3jgWTKS!a-cnoZr_22c~Gt@{c4uTc(vNiG$pi-{a(c0lS-Sg z2!O$-2leX#!)MyaIbYr<6&qe>oG;rczgSE%%8xi9!a8B90$C6V7hI%PqB~rw8LeSU zE=0V}&?rTxMSeO4+|E^*;18b3Et=wFIKd)_=BR0#1DR*h2MPiD(y;IV3@jmFR^$NW z_V3?S5*1V~ln4jJ{p#nZTfHXVU_JNF%M5N*zNLQGE^AO`_^#Ys67^bT8RlG#*>ly+ z`5cdol>NmO%Iz>!sD^zIHNtzWIjTQ|{iLFL+Y^-JLTP#bw1q zhFbWGo+yj-<`Jhw%DWO_1mtt&aY-1&GjX|HkNl0_5aZ8Ts~y}PHeNy&Bv_uV?C>?O zBSg0R?*F+XSb9LZpuj9zXx zzbTMNA)fm-cPxTXK5pLO3nS&(JUMQl?jm0#Yr&Ks27E&MfP85j8XONcmJ8VZO8Sln+Q5(3Ezf*!rhPLr!s7@E51$R_UReM0VaK_V!*FK$Co(J&ZkXr*g`!-c ztX`H{-G>+4g&kPHU+s4Q^x)}NEr*^p|MLP!3V&)<#Tw1# zPIofHjZN|Xr^~W8@q?39Jt5&^kNw2Go_;5jPs9UTr{iL0x^O>!|&z6+8M&frnFjmyjgL^-+lFqw*GDec&Udt{@0 z6eM=SA7;n_I>&L`9E=erK4 zv%w6lQ#~$=jNf^LG5o6QweKH|O;6cvYp`qOHYsa2J5fD4; z`kiG;x1hT(2?*nK(Vgwj*G@MsQBPR3VBj{h2!=*dBXvH&Q=+;j-|krX&+Iyb3Z1)! zQI68CFP^$*8ld!psCH3(r}B1R8rHLQe=N>hQv3bUbfu?xw>=f@oM@hD*Da8w3?;MF zemb)IOcD1gL{unUpSTS3bg{A~^m`}Z{qH8xt&$*-6iNjhWlbCkcaO1(a;T<8(QktW zb5hPbmEZp3MJ8;-&74Q#@dxZtNG%$`iwsZY6hbW}q$K2Z(IsT&xe$O;H0H=d?eoIY z*2r=LZ+`5J^|0of6=?y$X^M3w6)r!*VYA^_8QIV8CP(Y-RhwtEoSs0Ia#}G@8=tYQLWGHRVi`-cBjK4r~?X1;h-r%9`w<2vjjsK(y4Tn$Yer9 z@|#A?UwM>nuDc-Uj{KdJKVoGG_>+^_dx7LK+;y>Ay0=G#VJZ?W1HmC^BO0hV_j}j8 z`$sA;GODOcA^73B2132gvK>GA#b-TS{5mxolH?woY|&x$>V9n$_k~5c>mf%h%bTfO z+%JGJ+FzTXi(L8hM3`CM$sj4k7!sb2+!dRtLyx^IZSbZ{VTNST=V4%;u~wLfH;Fn! z(zT)|G@lO~xKLWI7$O(t*u+8|V&Z!q$;bXh*&AJ99WN3Zdj1FNAS_p8?hdYj|HneF zi-h~a)Qy5bs)xi)L*e@3n+JsQdKY@SDuwWxklMC;znQStmY-XT?F?YDdtqLj2TkOP zv`rbCgui!nL46$Dg%u)jD*^O9`D6eZYD2%m#6ac;;TdPWNN7G%+wia5HwmZs@~k7% zjtK%^du>f|hVdi~J|2DxxDL}fYjZJpKO{A%0HwI+NNO0Sw+epxSAUd?`!ULtiU4Zr zuO$BEGP%r?bHlHVs;>{U;(b6LDGjT_0EFsn82khhT~v+}Fs@FS!V9H^NCDmt8n3ZC zgA??v6o38x{o^rezISIed)#`cUe6@Geg7llY=c1~^s|naB(YZ72Dz@0>iK8#7DF6E z0aE1kpG>17mHd|k9$UfJHQ2Ef_dl>RJTTuceH*@>Uc%+QCLo)mF+_fmLiG2r#mgQbvZKJp{&@@j-F{|}ELiHQDBIeGqYSkTJ> z!*)Xb`Vw$t&Y_Vr-*{23a6a}pOk2LijxNoo74p1*oy~h$Qm?z~hY|w0==fWeicgC$ zg_TG#5`|}(N+Siz?OfNGGGt1%J$@UQck#eJ_jmc)T3{4A#9ZY0b zmDL=zapZ-uXOI@!U%muI!54upbQWQmB9^hU$R!&)A*}~SKrWS3L|#s)8nR_DP%w2$ zH+lv%I-a<{{C9UI&yfMoQr4canVkEe)b|?(5yw`*A+?iU)u$A5ek-BJ{#1$uHIEBe z)l6Q|FB0Mxa2y2=)3{W@RlE~IAc`kD{frB22aCg|OqY)FtO#{}i7uJ#pI`*yK!deZ zk0ntvH^~S!ohr3}!%t+a)6|qJPCaB?I=&=o$uf%f0}&x{FD;#btgC)kc69q(`eIED zq@1TKR01X@Rfpp*Mye(fPjV%X_~FzUndZLrWf$ozeQYD;Tcvf~mmSv@FO#`KKXybm z>DbfD-{d5PC))6@+5S=`{sFsj>T%2+FTgPpww|AO8cBupqpb?9x*uzC)PipYKk%A&YWLqxy(ZJ)65{EQ) zP1PTxmv>nPOt{;Km*?swFH5*8l{4?9|qjzys|OB%wZAyYwnM* zdxb=niJ(p>5mcvQL^#|bv?o`}m)f@+f%NE1Q0FKb655awN93POA!Kry{qzjw_hJI& z;xi=j*}3>W+F=jHGxB~~rwVyYE4zMaju;{lnr)5;KQwg-SG>xhBMgU%Sq(gWc&{AM za2F&LK(S=-L?M`%Wez}0@^Od;J*9|4x=wbCyF*Pab1hcuKrkbzND};*2c~}pFCVi4 z-4-5O@TK!NpZK4)S4;jn*uSi`K~mfd9K5dfRMH1F{#b`14tc=!Rc@kF`LTHA85M`A zj7mQ0e0!Y33p}8#^9;hZq~o>VR~esM(X(X`VF=LMf;JYFYS>eeH=0-@Hi?Q)eN;2e z?WW&0*c3H0fV$bw!HZvLGYkL%&6heFAP(i{%F-xNAVpr^(d0_?c@GqZi2ZwB*JuLr z?7G3A{eq!5zzeBovSDQTo&X_m=UD_uTtt?Uw-r2q7C|`?|qCQ?(?KMX-%HX`d9satpLf9t33 z(lK;D`({XWjNxt6ohgdf_?qcZgn!IUliJSXnsV&E^Kt9qkwEEeo6Zx}KWyVjmO!@> zL{d-H#5ziUH9}0{^yJV1s-bbCNnAHu%L#)0W58%}lx}QgF4u7P>X?HLpwlS?`xz39 zioA{^mQ;l?hC*mvqxsclbX%Z2oM`MjVYCs;N*KkIMe=$2V30^o*u!g2e%sqC$ zSDinvg9o}-4+rM;(?iT&1Qck-$+Yv&rT6hk+-xF!EA;x@1rhQma53ER@6i&a zWK^5!-tf!&JhuB^v1qg_99b?|;kx#lJ*2id1{k;#yDVAwU=BG@0@a&qEy|&eZ5}$n zg#sr$qA)OrhwxWDK@xiBvoGWg!`+*IMRB?KhFUp}dtZ?c`ZJMoMpx=Btz|yCl(%gR zEeL-!=;vPL;YB~EYO5SU?m$YG{D zYdiXmv_b6PVlm&#@+XbhgPF7cOfO;cqvQs-az~!Bu|A*JDp|zQSMl*^r75mp$SZ>FiQ2Ubx}m z(0ni1{Sb}Fn*(@uU&|s-JN=ziDe{yoB>)i@_&cl24qyh~$%=sYc@S2j6DcHc+xh_$ zUH~%`=T!xYxPCjTZKjqU@5c#xG$lizeNL?ENDB1&0&`%`u}NWi*iYooFQp!{{3_Jqq*Tp9<9;KA)d2 zaJm&byv4%y-lld~ttg^>Ik7Uf)p8A~y*Pw2kMcj7IDhCapz*`8E=b4{liR+?x_YYh z9=?MW=_9bC{9*wbDP9r{QOH^|S$2+!NUdG%L$tNyw!R6v2l7m;bX}dd+UggVi@Eq*s13vrxuD;Dd#TogfAP6?7~bdjnLOtcBN~YI0-cMJanE z-CglWlaE3;QAldvh&bMi+l@rY+?>ssXY6*YQs(iSDUeH5Lr@lZkepMrnaatQyB%24 zpwrOBx^7=|KpLdZ@0e+D17+p?8uIINVGJtVyA5XoWg($hujsGaPhqkatXP?%(ZM&K zD6Y;ZS8)zudrE^L1jP{xD@fUO{VR^2DX;pL5dkD#~GFJ(92Y6yv~k~j}7O*Aog_Bx^I_VHwfL^(L)I3(%I>B3tw%RRU zr{89O9d+Hq zH=Dr0C$@>?*%*{qco3>?j>ZJs3W^)qVF^?+Uu8Tjf;T)eQ*7`aUgU*eF;F8*+b3O4 zOGB&gUY}E%J|C~*Ag`p>kD4NKD>n(c*i4W9)-B35ZX=m`-%GTiC8zx`Dbm5ACoUV< z9JRH=egG zC$#yR+k{|PePVA&pY=d^;+1n-yj>p>8kp!^d}%H6_S+)pYBIGJm8!AAaLzrF9|Wz- zHjj~J>ZpwZrzBWA=?3_|)O+I`8W#L*vjUIL>F$L#H16 zLJAMTc29{w66FQptQ8Jb7C{zoILu`p{+{6EHLo>C*wN~3J6H%S2f|ZllP$_aFs0_X zO@K!%#h4*0jZ8z>Z#wrj{C>V5N)t2_p5eg0?EPjU07lTGmabCx;JVF+7=4h zAbEZGCM)+lWzMi|mzT8slGiM#Cgcr&I?fCYWRxDFU!1hlTaN*?e1d&CPnT zKWVPXcCgtg`Y-@h7|EULFmHGE9(}`q04_V@ckf|?Ff*sp{7Xod)UlqtF>G|J?bhJ8 zx{QPTu)@-anBled8O*}-kxXS?%^@t8q}OlM zeoG}GPKNtYCV>6+J_<8F^kqlZ-4tZx3%knwUWH};#qra2rGxvA4Y-H*&(!qwpph)j zj_)<58jU1ClH>fv;&jw8Q}I)!hmfJp@W%D%`1Mi!*6-_ApKVnJb;T;mH%Z6DkX^!_ zoocC;>zU~eoglslDrksP`a-K+`OkMx+9#uN%v$k%s|U9X7lmtnK&q8<*757_?^u2n z2I2UfF`&5}qV}HXA=vp8M_s1^XRcCJv5)cZ zR>&yDKbw~wltBiGVH%4W6%*IPO5lJNhLCs7mK6OfRY|5vh|rTTJ7wk6}I|Q zLM-hPL{@#_$Jjha51Q$mes0wwep+0#v3m^0n6IlRjg;>npX6!;G@Lrvedsb;L_evy zg>5Yl>n9<%<0tFOv|seL13rC*wzM5Zg#kB5=UD`$EMyvf+D{Jm^~I_w6EX4pwKPyv zp0b;`VU)z+;XM)UUPKfftb~KeyEAFJ{0r*7g$b7T1irbay~eGSq2_03QiVvgJI6tR z6hHhw$}>pazgHsFsc9>rvxD!s2!(e2U!Q+`<~!8M3KuA5Mh845U@G-|1iA@=6(7+7 z+$_HqS=Q#RY&m5La28_vVlYkX-V+EdAB~S>ISQ8Xk{2$AgtxW?LAl@Ya$#)gXfSJ> z2>$-@w=;lqfy`SFoM+$DI-*cI2f8OX_~BbcE|}w|q5SZ*Umg#Wba^N?s+Vznk&_#Uptpei6t*jH#XrM%-9OdW{{_G@NseIAPKxr0!*sY_1hiW}@8UbM~9qM;vZ*okxC$`A*F0zo;=@qEFH#e@CA2zcI-ukzwD>T@7U7!MSRdOL^Z{W>@)~#krZVCV0 zS!5<51R0f9$6zid1dbp$Y(HD71WW6CxFO{0jIfp$71+RwQJqY5T9XFf8-|+XYe;+*EOQfi<_J5*0au1-q4ffj>;{LpCMFcfP zuorFnJ0Uj7L&|BSL(zQ`xz}qFy1k=t|Iw|w%tTZv^yV*M?+uqHzK31vSGAtv2@0Xp zKV#gN?O{YA3#glrIW1{i^*f~Dn0hciPGNS+UyjcE;yk=)%7&!Nk+cL@@{UZw;;DKx zA*Wuo_Et7@^OKoINZ1`Y20(iHqK*X&*na+Vpx?4JnaRix!G=3R&J%wP#NUofgdXI& z_bsrQ;*eyX1V+20-F)uuC_q6#(?-J!Bu59byhR6g9t(_rM>9|REX5@(jNFQinTuns z#&DLs(!akKpj2{r=tRv-Ji*2Lbq<_2R9YvkTz7_>d6y@~p=&ZIU^b}+gJpkp`n;9wokWZ0d@1V&zmzKi3sFm}3FYRrU6-B5 zB6&Ee;BmR!hov{KVgVk+=x9EK2B_m!*>4Gcm#sP1(@x@Z%m|Ab}G=10q}{>A4zjBJBl)auuzu4!!sp5k;Dq9(eGU6m!I z?vICI^z;V_Nj)v~lJ3UQCZT^h=?Z&ADTfIGyO&nQ3?A#4UFy7+Na*YuM~b<`Z8ke1 zXkqg^ffa;iBwzLg2|QkOjZq-)xNMyz;z1FAz^?(#+j|C*8i}m9V~1LWKivP^q<=e{ zdg6=@K6hUE&}dp{o;%E3TQTzz&jcLW;F_s5;#+zBsaw(Ks(lrY2KU5_PS=kO33GW* z^T4F=d*1Cqy?b1~hgse{5Pgqch1HHGDx#3Ww^E=?!HfzIxpWzNby$s(bGZLv66EdK zUh?;P3SUlL@ZL?;{XA{r*)402sH>t+tu#Hb8Hl{UsUK zn7=K#Fnrucp1yLX8Mk&D)cnqc&u-=&jvD9-@o(=SJpD8APkO2j9g;II#44GIu7!Td zu=wtC24dfgNWl$|RiX+5d4tpbHuvQErPkcNwm@1NY=sV7p<*BTY532SybortKo2#u zeFX1YWLJxz^RxYDv&6zk= z89fjZfro7q=><3$D<=dS9r)1_1K7Lotok4k16zs7el>tf<3Tc3y_Fn&Uq*bV|BKnb zsQ%|79xP8{yp+;Jumfl_N*z-pBrzT_vp0WRrOq<0q}DlDG>(ZhV;vYd#E;Z`u=!0x z(VHAri=9WSk;DE;>h`S!$fdqG7EdUI4L<-wYKskAdeIyN6%m}>8^s^ecpWS3X;RYG z*7)!<$zA5#yR`b5w^s-9s-Wa(Ji!3HCcXR%Qftww9AUZ$E5a+$gLN62;@})9!3_0; zA%f9e;~SOplhZM5cfB$+CdD$B05Xzq#ldA}|+=Gl{t3F{qjr^^+&I(L~r zUO4x-ek8QrbfLxpZU;K;q0#)~ z-DzG`H1H;?;BXoqJYF371Nh2KV!@4YIVLPc0!i!=d=e04Qh+#^iB+e9R3CjuF*K82{}PH*!2{ zWx{<*nc32Tnl#?~5R}X)iIxUyem`F?8*&rhoDZ4JJtS1pYyJ7^OFuN>@#48jyf)Q{o^_o7PAsz!{TJ-0HUo^3U6hVSqYZpN&@s$~Z zB%UdGKYiK9F<8JA*#aIi<|bHF&g~;n-y|85Texl?rnfuWu&!w$qud?e`Lk6F!yc$g z!?U65>C28wtvJ!kzZwoD6dtRbz6n!~zW*&!vjR{*9aV|eTQ zy4XV=_!yQ40B)-zg!H7*ORC9xY4^=wa;cpt<-r(9_k+~^qy7*L+Vd|`31Mh>G~eMg zXW*kp(GXb+iMh-Chj9pwt2wtvGS^I|)2W^TZ$f9Hu$xD)64s)Kh!U?nW!_Mb%O)k; z=_wW|>GyQM`KPz}&a17i$xovDlX%xtJBJ{|4 z*HVk@vQl6M=dqlt+M7GCgDvS71I~@jf9HCsK);`@(unbG(71>FGC^XEE2^1}_wR?bnPqsuY?K^ylNp#qah7-KmA%3szEnIHHco(bCq zhX01Iq2U1JdwwiF9VQF>II0UuiezVeMXBb{nh%1GN@wzz$B23g5G8UbpPPv8%}s+~ z?)V;gu;p;|T@Q4ic!WlR7Sg$Ywd-C%5XTYbTM*Su=jZ}{0v=+_eip~thu??wW3 z-Ns6jK1d#Q*hrz0Z$Gkr^LkRZ%}6-^XoR!fc(l*A+r`xl?G@&Kzlnet=}F$h@X2f1 z0$ST{kEpCb2IN_Iy4|0=8zM{Ht-LK~Gz^FMW!IcQwRFG2_-YS!yeXt$QE$Y@+bzn{ zqR>Wbr2IWC4O&09W0OD9+yxU4(VR60A?e3-7TIYc53CziT;WAKy<)%>A^ds`3$vvr zKjz9b#6`&UDaBfSt>?MDMGKG7G|8q;_}ZA5$eenW-otVH2GU(X@$L7)A1X^Sq73%fCw#Ly~^89A|Aoa{a}{v{8G~x=dkBEODWztg{-W zE?w2tOs11lVFVElf1WxfH2#mzdqzx_kRSL~^ zhIt<#8e%o_`^4U8m+s_i9~F4z><^)+*(}VH)31>J>Bw70T*p2}71n6wGUh@n5*?1q zN0_$XfPUM1_e4?f%E-p)+B#ve`d%+s!juo}Z@$xjR_gGp<7U_XG)=eBWrx(wZ~&xU z!|DTDYjbO=jvk_%Od?_PYx@5CE=6iLzbaJsOjP8pZ^DC?6Zhs;Ek+!jMW>O_BU$pW zOL93b^(Ct{bw}&>&vd>j*tJ>FV%(;`s&Le#Uc#;Vo|ap0!Wvy7yOUn930jqkDO-Xc z8a`DU@Y!(a29B9*UD<+YOcRzL+2^uU+Zh=OLH`OhvcEidT$a2k z&0q$35aCtVCWaUYnv`l`XjJg ze|=z?kA=-p&!y@Xhko!R?d zkmziS=!VN%zWp5}s`w}ovWS1|gJ`!ZKW7@5gL^hSl?)n}vx@f463Jw{`LBz7ekjCt zNxxS+6hFc=u-Wb)d!~<_!`|K*!af!_5ufV$Z#0=Y?(YM5S5fK-%>SST-@+4@2G;2IknqV|tGX2U2F&zz?26;LyZ>ucKQ29_tK_1Fh41y|l|OtXeMv)NKi`v}5`XVvCe!!40Vr zZ(Oxl0oDa8VTburjhGpMWd3yzSLShPRPA30hZ7VuAEx~GZbV*z6B6WzyEzTMD~w}3E{^#*JOFOTkF@Y{D;!fzaXIhTSWNrh_`-pHXL0Of#2`$N6rrN=ZK{(zi zX?@1YbL;_0Xi1VtxxF}WKaW48&U&8a6Ap&M1E>L%uu9pcj0h&`(Ld41VUod>H+%z3<1IjDP`>^FD^JBo~Zh7n@K0x{K6QThvHa ze=ZeuWjoio*nC)<;z$RZ2T)m@dT-st#(PeK`L-+Sxg<`jX*HX1m~mW&%To#I++4Ls7RfES z3L$Qg(AI5GoOuWXm-s6(zOL6~YWIo77i-q90 z)_5HiL63xdcM~;Kk1Jf+{*w*xVb>Xs!2!-HO6M?aT_UQ&R5RcZ@x}h|o=U{?K)kiC zYZgyVveaX#+H&AEBfGNF6!z_j5H=s_ZsJ-AoCU_QyXk|^O$dt2o@do~9?c@_e_Fua zS3ouI;G2;u8I|Y|6k==5scGrgYP)gLC?C3m&5*Xb$M0*6wpe-tUOxyY+Oit!r*#`Q z<0p(4k-rPQwavCb2M97&e?OzLF#A!(4(aaxsraiB8KPFE0vTphY}t#PJk0WsUztUo zRJ%uKQu&AM7E)p^6FU_U+8m?-M1CKC7(w76+87ZYg)zoU;eqFixLGlsLx%$l%3aF* zr|^^SerRMV|7nyw}kttT-%vWd zK=Z&TBRGKBHADRbnqV`(k4j*J*ZntAL-p>ZT_rTwJulame_1c|4ViZCKdRu3kcHyR zpviwJMT`haGsG9-V*+HSf_ZPRbKM%-hqmvdi-s(k;R+@r%om*atNWjvHgh zet5{@$4i-xl|~1Ja7D(`d$yA-xl?}vpMQiY!hC2*7GZf({2E0VW_MVib@NIvZJdJi zz%E4`PaDkxzs-+hyza>IB(ygs&Qs*%dPq}QMS^_;!db6yy)LEvcrHzfn8Nw|jSITq z?!@3b;s1!J;WEgct*D+KOyI|xP-DyQN)L^@-WBjvhSl{;O2OhCNIO+|7#ouiP(4ZCIMXrmo99n_V_qdL9!YM zJ>b*9ZTFN1frF6t2soOFt-t_QzDfl!@TtNwB0J|v1z=?{s8}Ix0?12~$&N?QbMtGH z2GR4tODuz6|HRF(a!pg&f1-#Y@?OY?l_)U}e0^*8LwM;5ky;NzV1}n~>#`waW@sTw z>Vsgsx_b{N0mdQZaOzXxr(n{tG|?t^&k`Pnoi~A7czCd0^lvMmL_9!_wT(C)?j@Hr zz|<<4x+cfi64jfOq@-0tNBGa5F#bJOV3@VpIK4oeQ)pr6UBEI#^^vo5s}CLpp>`&A zOQ`D$e4n~nChA^*HkZbBcB`p{{^1(TrW&&`Nb=)P?%T+g)f22j8Y+;45o>{C zMr1KWv&&PC<<;a6bF}Hl@EuEV?XaR|v z#K{6d+U8xwTVkeI5Gn9J{gN#2kUXqAQj_DAN9eGfEH>hsXs2EytifvQv>c>&&52=! z4hW;2`ZkcXrIDi@AcHN-3V)#xoPkirAoE@>~6iST>pofa+=mAuA>j3F+| znfJyAi}b7YA>%UP1A}jHCsF0%-Ij6LGe#D_tVE|v@UPd1B;63?+e0unRz_v(1 zW;QmR${f%hcuc-w2gE=98bS6hPdN;SK`VfMqzH7|M1!1xiNmP8ruANaFktvVfB znVJbN(PLrdOmoHF9ZK}oFvSfDi!Kn5o{ufwb&p+XI;?oW^maYbxerGoVU>}Qcy5-z z$#!PiN>E8pZs5mVgbfu*o3ep*L1R^HJJqpfS5 z-{w~7X7tUR?^FaM*m^*sy!l4D>`mrY{hN_$W2x z_)#5pbedx+tmri)>9%}DJ;J!5xjzxx&TTvP7vaf?hl1?s?n0?&Y)9-e-YsBX} zcVzJszG?Ujxxdm>h6?fNrB8S(nx%<9`7tIg2a-EBWQcn|7IFU2KbJ&0>AUM07LS zFPl08vp7#azscDMl5=zC6$Ep9K*GHh^FJund9kB|6^Xfa!9}0^J7!Aq+16gUh0+M3 zZNf}=L4E%lG*TefG9fI1x~zTi<|3M%=BD^`Gs4%rPyoui%w2BbWe>UTzJn4-TgyRr zvLkcC76^s<9$6_rmfSo4D!9$JuNx{5ToBJLBzH(|BlAEb_+}V=Tj+J6IKaN~+tq9h@T7PX^0UWD#n2%4Wie()ZXy zms0`sP3u8Gfs(DXJMxKnx|jdQ)?0?v)hye>!QGwUPH=Y*u7L!1cNPx87VZ|@J%r%y z?he7-Ex0>;^WA5kbMLwP`?a2jX3g2Ndd;rts!?M^k?QDQw(nztG%c4PXwciqO$MSa z;COYFr+G-m^U_6UHt)rqt8SQhPZY1uu4y&KW@4ICXe`mSv-6l*des1fF z=J_2f@x+jyjMkYj7@dbOLU#*@%!5t%F6nbFe^98D2JC;Z3b@?UNT>cJPrVrs6!}8) zW>^?_UUy_5-aFa}TEZ3=0o|{;S7ru=KNl{5k*E=6POyiR{p4esdDLPL4=s-ZaMPWmr+oHSn^y zE4$wIY?U(^s>a4{^#}WD%wZeIDCInN)5a z^f=Bx__kH42nLP?)#&M0MCtoxx#n1S#b`uRq*ohsDfs!r+vZbX6OjsocNjkB{ip%= zf#r!Nq~h3&w{v1jP6}0SR6-m2;+>b7X$hBA5Y)bqWdM85!=?_JdO84hX2jdt=`cV2 zuJi@r;dY|)reF4oLptWpbz1o3d+!&M9%j_AAP4Wr`xw7&8sD9aP_9LAEgN>l<9K4gf-vy*?V>ZX~kDztVEb#;UYfk zPhJ7AyD@pG2bn(EyJ3;#_7{QgZBkA#*LOb^bpYC=O=98K7ZK$y$wz>yk@7P+xner! zZ-~zZ;<|hXKC&p`FL*h?Askn-WEKns5jywWO|H?RaAk+nA^1q6!-;KX<1;u6@<2cd z>4?0`L_MF3K60iYP3Cx-B&`HInv08AoWZ8&t0dmtF#vr~Lq)JUcu&6&qM+W z?g`0epDqx*yUaq+nwUTnYxvsV*F~T2ISE9;G&?XZwv5n3mz9w?in1jT2j!y%^~#XU zl$@I~3c(BD-iVH0@>j=oeAepIvkVe3WpdDFU~@c!pk|5yalZ~tqQ$0fzW(98Caw2r zhb=`S%~77{Ik8d+tp3+ODwX9YC2DQH{$O)39&$swGj-P?@%@vOmV+P-B0qHtfX4} z$7ED*LB@v#mP{d*857FQT!_IM?w1dcXEP4*yxvp%+}}gE!Cb;NBgH4AexA$peo_l> zPERrqk96K=c@{`GE&79)h&dxBdEpJzFIN&{sj3KyuAUaWr~sNvn=KOxOWQHJH$@{I zo@?``7?ctze)hL<85%l<2SpX2;j}TC1O>#*nC1^dlllIkvrhXa`~~298uj7iZvPjz zkqjrK#t*R9ZhUBwZ+nY2529#0(oP%B{>a<C$s16g zN8#Wy~AS?C6B_c`LE z!Xl+aBgmOwNHv~SKY#;aiGrTH#dkLOsrMJjfg?it_C3C$BKt*##l1Zv{5AGxa$=@w z)+y~F9*#_RCQ6rs$4!fFXJkOE-v1(8UGm}$&QD<{E}J~BXEA7CL7e%z*lcuv%9Nhg z!jQX7ED#SWNCyT<6@Nud{=&Ko25?ON+ApcL~pWH1Z))_bbkaoCo z2~7*l8V`B}MpRTKLZ?hr=+#uV0+6s2mmnnuXg#Pp={!1R(WgZ(SAA|T&d~Q-+n?%` zHMMj+otk3|y5h&W&teE<;512dW(}0HOAG}DZ%7z9`Eun37+iRR^fTFMHD>3;|Qf z{8`8L);7{{2_xGn@9OhA@85Zfml$%qCiE5!7HKfCu=%DIj~==fDX|{9wyzK?)MtNI z;ljQVtx3pPnS0yiK=RFpa+#j!GmZJm4Kf?Ct}p1m2uO|w!FMZSyLRxr$=@1%DO12m zBsT6wW&J1HF^xALrP6RD`l@k1pGXY>fwXsCRzIt~?Pit<(#5lEoLSd(b=HhRj7^C$v`0TG#Beq@*4bi*HNnR$%soV%i*ZM|vh4(OJ) z?n?8R?I=a~GfohmY0&YGFCKq+*kgP_X2IyZe{hk!dIpxF#Vce*_IIrcM#}0xKNI*s z=;+P4y!tRi`$r%U_X_WYYVV~Knn;%qiXEhob|T>6zao5nHJA_K&4`%Qv2sHyyfDb8U_$|n<{+QiGvw4ozoT8;N0p`0I6hIapIk3kPI=Jc)hy#ZlF z0K0mTbF-5=nBe zg)%>!EziRNe3psn!f?SKtKowQu1S`e10^IB%*>CkviUi4_WR{1z1LAD3mx7%b$CrBRNz0GBa=%-Rq}@i% z(I9vb%#7+ONx9(+#^xprjYv8wzoxIfJlR-W?3R>VK%^kNZ8eBzFjGK;dgwynXM-a< zWt9wYw%fP*#DI?dV)T;C`t*sofseCB;|mRG^djql8W97ntOlP4q0Q9!kV!!sa2dH{ z32G!K4~U_w=%gS8C6wR)U=RhVBf4XCl)k1OyQ+x26bIhH1P*?x9&AEJbNBtmF8cg; z`_Y+ou1Rd?l9|BUz1YjkSBEyk?@e;j%kS5;)RpCArOjS(d_sE)#n}NPA=51Ds6SKv zw!*tb002cC$@kdDs{WumWPl5;D})YeitJ~Nl#Q+og2n_Dm1;~2b2r|@^VOKR_w7@LH)}?=rvEQJkMI*f>ADZ8?VUQWe5grvuhgCf-+m|$O;Su?^)b95*DG=c_k;6yp zDTkXh+ZD3U_Ot#Bqruj?OK840FsAeGC^Iu;MR)C!WvuNAeK9I2(s;rvj&|56t&;mO zJ|X>^!I6Pt)p`@MgU?|5FK%3*CyW<6;CI!Mb zFe<}(AKAo0P?uo7d^*-^EqR8(YE&O5yjsM%NQgrFQUD=6UJJm^)fnih=pPkrdPWsR zoz|s)a7bx)jYHwdbC?8B4Zy&AlMr1Xa8`GKse-u*MNdT4V z!6h5eh4l#4zRn0bzP~};wn%hw&AmR6Z7KnU9j<|8j7uV$8N;$Q*xLHjD)h|m?iBII zsbJa#0D(M(m)f189c#Wm)0dr7NWR&Vfo{4UE7}@k@S1pBA39&fCOPuyGli7UT%_xQ zO#sg{A;@RLzvMs^EAgkwOsH(OXAz!$j+N`NVYk?v^aK8Gmw({J)&h))ey*TD?z&}i zG7SfT40cV&RzM7dW|cfgVEjvMh`hx_n&g7dDc#dS0= zmAMzUKjB;2<*K`c&5{k^+G#@Cz3$V0X_$_>2mHN(Xtd~jsWQyN4v?}~Vh7qh^ zT^Y*pL`y9AOK@^6%EMbA7F>2Q+-i)2%WRwMb?%Qavifd?m8fMD!0rYa69{+eRUF_z z@_UL!ujM2{@UYMP?*o|O=WxpBDX=_}S2I%1$tJcx?A6&{t*hxLYJ^W$E5DhjEz+Yt zA(P%8*hlJC|2G$aGW+hrIaZ=DTfFRmV}I|=Txp3l_cg1wara}qEQyFOvQFn%)^+=F zp&)NIUpD(5Wgb5Vl*TZlR?Vz#&nF-T?M2}^bUYyh#SBYI7f`YDiEg=b25oph<~_=! zn}#uYAn=;}!+G|{^5XUB^JQkyN8hI(459(Mp?ROW=L zZDinjLY#|{Tx`lV=VX#*9pDtR0c{47rLSJ*ypNVFlqqVZvhi>8xcgFrk*-=Q*wOP} z_%;JkpH80@J5dm>BgqUjHA0ge!~D=6$DDow5i-b&78+${dX+QW=C`vID#;P-W#P`V zRh}-=bRHCgAl@z99;A;jLM|a6nRnar;#Sw{Dugq+X3P;4Ex$wg@3V$vZm=g@k`UE>ynRYu^otE zu^S0dAwpyMpkt`Z%Ms-Id*1$4Oz`hS@@J6ky3Ee@xg&TO{toH&rA0ft2MEA?{rN_+ ztOMR;Q+-&pvF9aO=>gQj?*d z2ZjIOQ1S&RbvsXefuA{Ki1zQOK4RGJ3s0D`_o6=eYn;A8$Ztx9De<8d8d5XW@hU{e zpw5QjI78s-#PE|&c>hhXOmAtm8C^Gp} zR-+%SHT=8+mHSdFZp2%WJ{i&vG*^5IRH~psU`gY3gWt{U+rN$1v$6PkarqvZrR1_f z`XYLGbnd^cW=N*X%hVDrOU)8;^gIZKiS%P_C=Rk;3|ge9c*il?HUUHi7f`d%3x*NI z_X0NiwsV4c=OQgMxp84dF+rY}0KX1ESo1Aux!(FOR6&Wt6Z1;F4IZu=ZVFIuRrNpV z4uqSN#=ZnDb5)T;ZoSL;c6TbUnZQsW2)Et%0neU%Jua{J>rNK~_|0(EQt#q#xrg}Z z{ZGY{j!2B<&V^7-@hfNeCPx`eI^D9bB^2I`Jb$hMme=@6;aA=9lRcI=gY1&Gy7XPD zw}Xk^9MddV1bX)Qrk~|783vr66@bQF0#%dbQ+_5~G;5Q-W)BRIV|uP_$r6x6(vu}f zHd}6A`aZ~{UkN{xsc4u#6QospFvLDIMaMe{XOQ4>>NL6Ler$GEYrnRRNXAQy$&hoh zUa#}2m*%A4yPvv04N#Miuy%xCl^r*o=%`%c;0h=Q5!5${8zX$Ye+<4XmSs@pgiC=m znI&lYZ8=?2PsF#KIvbrBqq*Cg?~)!%jB;I#Of?iL@iF2`7RB;isHGFi*|GC4xn4}% ziODZG$QUPbj(N;h1guL2_*Arc!h(RnghSNN?7L~fU>TlJ^d!H@Vkt%SVF3dVedR?YPnPjK2@UjkJ@560vA zWh~Mkt90%>|H;%-KbUZZLu2^7NaDRt89R#@HZ|I`99YBo8 zg(cGyv@d(!cI`%tlzQ(U2q*fh&Yg=aaMbx=UvmJw$+>P~K{6id$v(wc5#IZJ)XNrr zzk}3mqFNw@^0mbOzE_emr?+!GNApCK$qth=k#Dct*jtcr1CnS+&%4_SjVX>(uH%7d z2Wwvz_TKYp`^!uMyV5{08t)Hk18^$UFcErAFxe$@lt3Ngz{LrNviz_r^&aHzxAtdI z;r(gtIr#7wS-`WQgUTY@^Dtt2({c-S4=)Ev_RP|F&f$h^I~F9Z>X6+BPaBUtA(d2< zU%S&Ni61K$67LJXl^UMy2uNcq{G?fhHXBVI&)xHbhZ6yc6S{}9*N88Fp!Wb^IQ{e8nHpB&jghxbn$C^zG__;v!*oB| z>Vx)2=BmewP{*c%%PQ$dmQFTq`v3&Frq~gf{>aI<6&kv83Puc{E=QO@<2)Omv6H+h z5!|_8xWqh};CTfP&7K=Yq3p#M|Bn0CC7CKFKwKJrI$b0wd49Fz^STTeu=5)2Rps~c zJsBK&DtO+DHk*;SdVngGuq%u1>j?RH#6KXde5ukzYsMFa za{xvXVrVFM-m>+rky2WcP&D3GcuEMoC1~nVijuLI4Y*P>iP7y(-S}xD4*pewSO8kE zf)S+{;+|PS1@k>N&q)OKZZHrEZc5tQ9JJj-$!n)OsAc$8Kz$L@;~c5HSY-q?q$$0L zk@K*%{nhq0z?2CdMM1Fk5h(Z2%VCZ5#%i2>q)!cx2V7U_l9YadaxG@8I#B-NNPx>N zZ|I?lA6wWr4#8msQV^Gr}HdJ?;!x|8svHIi?5=-%(x))CC+_F)lIC| zZev-IaPs+Dv+{0gR<5{XH96{e){EX$M7hNHM}dskxWn4L2wW=P?DHr8=bpf95%k-| zIwmS1<*7l0a7QdyWPGX(AD&U77NEchvXsfQX`)4W!t`oG<@8MqVAK z)UJJ>^3xfqE7@Beo{_7wrj!n+f+hX?M_08-87bhx`!h-tc8TgR)~kTcTCC``z5NEp zx(@uJ1u>}@F~O1*l+j;!XNnZj2T!2SS(XB=NxQ-`Qlmb*oedVI@=n>bfqWRqxTSOI z50WB!(#64ZhvNJ=WLNryAO}!5H9NwnM)OhbkZl^;f4^TwhddKtWSve@sIp%t+|2}j z(gbMzH1+?Ygii_YN}}0(hyi+PSDHrfoZ*JyeYTvBPl?p31fWa9-y6S2d-dZyY$|@r z%uT^%f$fH~`P4=V+Osd6x2py_8-PEkaqL7AkDxH;F!*P5EQ`Ndx&n;0l*6`+j6^U! zl&Xj8?HT`2kTZ9Uh$qk}lE(>eNqr*)5$j8Ee{lo?1$bWCJUp~B4$ELO{IOx`uaUys z068~nu>oQa6XI4zz+oXvC8+z-YgYeQ>{|K;Vn-0^S~2YV70l|_g@j=k@5&fLE~(Dy zF)QgsA3gZD3D@`6Mw^Wi&CA$AjKL z`j1(cm^U{E(-LCOGe~kL5c-_UMmW}COxou$~Q~ln+@nF$ZG2VF0Gb zL^j6))s!88R30M3fWw|g>2(+QhXoF4iY~&zEj}nx;eBK@dM-!b_917`T^uO=@95}e zULx}mm3f*?Bw(Y`wpk3^$VzXx&_E-#j(ghEd$UCKCsI{YSatFWHxfNY%`|5^k~O_% zIF9a4I!24@BU%zmuqqrNq@%15lwE)qU$FhwH*a{nLtBZLbt*c2_;;q!CN$9_OU5=G z(}el&jdf#~QiVwkMB-^!BobjoM4a1w+xdT&^k3#A*Z&G0N2ky7JR5*68a^amnyuhI6 zVCUzITB5+AIg5UTxjtpYql62*~gtp!5|c2pg&?jV-(7_h!Ij@$9+XOVlHZxZxO&v zUVZnrSZ~X^ziVf5E1Ce@oAbfEx0?Hedruu0N;Y~iI$q1|$o?%7aULI3D>-rYtI!w_ zwLADx2CYZROlKe~(yiB33$eYm{k2X|LZY`;ps@+Ton@gozBzeH<@lthhTQjAm&EAw8 zc9o<9Z}OU>wF%JndaoXWU_qNt6nA!sT0h$ZZyK^4}`#EKI4O^Q;VX;tE&xh z|6!$Fk$l>Zqf4P+@JVXsMf<>53Re_?6Py3pkmr+kz5%P3_Z_)T_)vdW$D2D-H4^2@ z9-}e}J3bZq-Lv3*k=??3(QdYWB40(`H}K}?>-ROq!25D!5V#<+2C1^}=s`Si;={9s z@2&V#4@~3rJ|CegND<6WJ{*S2L^z(sk&s_&4rkB(0I}X+2j`#qT=w17u>^dlUY+X) zjr5X*rJpOG{9-i&1Oh01l(usX@?%ajRMI?x^!2LE7BMPw?fl8V{@B7{9}{ z!`sswy=&fTz%gW<1B+U}?IJfROnG2}4H-bTR_Ls}+A^kGXMh^U)KROgoDRmKxR+!VA@jbG4 z?3%E98tQv}@}?VG2agTq{Bt5j4Gl5PzC1tbPrS0UgG_V*$LahHdWwWR^BFQ{(Z5Y{ zMFO0~9wn+5MdyRe?8?(6DDN_D{O3KM2)ITb5_;=?Q4Pws?g;QHsi4!;pCFPE-D+sk zKsdWB))}QPA;0a>Y}L`nCf>os=xydo)Qyl;lD_cD4CWMbMFnWNMG0lFVWt(%gApILPUi# z(^+blNM~J7!23tGuqj?=)Ma_Cf^oX2V>ml;2TkSd!1dkg-K#MUyfL5W#8_gH6TRn& zV4H8`AaAmKsNQeYB21lNUj`IZ3%qZo8kH!m158X?EiX31;t`C5X};`vp>w>#V;C7- zk9@WzgeP;d)P$;;bkmQSp6F$*34=G7Ktf?|@cw9;qr?93yMW6x=Iw@!;LZs%-Q#D6 zeS8oE)8?|HT{h>{0}AAsY1Lo>3=V{(D257j53c_O%_ggMTcz+@)2G4M-=<@|o*&Qc z+EiHmLa4FAP%Ox)6WLqcj;O9*QEiDq;ZO?##otfv&?_$E6citI%hJ~C8G_QKNEbuT zrpkAho;qEQ5bMvUz9ha7@>qvu%{~uyTt$xX3&}9G4)d~QdmuYUbuvJ{#j1%3n z^a+}pgn={B+rTElVT}-OpC}Fn(qm z?+M!cUAUJ7#&7bmirQy^#Gug z>sW5LafY8U*#?X84(}MZT69-7rCVhv<|*Yr7Al@uYPy=D_0X}p12WJaC7cu(!Wpyh zcddNq&kj5lNWIVZ^92+#c)vuLn9=F>i zv-*xdkCPn=TxXq2i?|#r;OPmbpt#rNfF#i_51|&k{i(6e?OxhG;zZ$xo9pZ^US_)B z=wS2>A5K&9c38UjDm`AkKGhH9j(wkaO?w22E=3#WlWeW=&p3|o@tGzu`=couGK6ux zmZY6(59_zP7N-Em?~$}y$zSFz0#?Uu-W@Hk%$$1LEX$62(Cbe70iwbCeKu+5J+D+c z5)zx{{Ji192nZ$IF_<%L2mCU<1{|UMU$*t}>~@;O(!2Vz9fil;Ggn6-);pWH$81JC zZRZb2d#X1d@&A}+kLI3lod+@YI5zBiJtWTvA&;^PdLh>S@u7;jHgB)Yc9uyfx1H+* z<6y3?J?}Sp-EO-(Cxg$;o&8ig_k{w^z34VuG-xb*M z2hk;nEYP4D{6n5-%ho#`HA4g(dw9Ds)N`sD*wuQV5&BN~S-BCoPI_0-E9Mi#A2TXB zR_`q)UKrTeQ7WNHX3M=;?ifMZ%df6aUn6?dt$5MQUly*2Vvb79Ly$&UGQ7=l98!JY zZX>JIQUiPCbtkiZ`s%5|?Bfpg_4U~ohcFOo@9+ zXAE9IV1gJESK7G1qwtpBaOhQ29WOUi=B7V%3o3TKjczyv75awEWK+^Nu zK9t8tN!;dj?v0$?AMm`ItS^5(Z|P?n;vP@*)wci`^G#-df9tiw>JXHDtg!QHB#kZC z@`5~LIT5T4N-5wyEV~6V^p8tNOeJ9wvqe?P>(w!*5UV*{!z)qnA!7G==9$ z!k{QqIcx5-OyHAvuwsNB)hB_E`#KsiW{^px%eAj}vnNwJVRZ2z#iC+IahbFU>~x;5 z&PDIikP0eEn^+7uA`Si!LB-Gp0a$bItg$qq91&GWOc3*B)oo|+<)KA)@A{EW8zzsL z>Zj-41#gETy&lSXV&t83=WE=4g}&uqZOqJc3$Jz}KNxZ3RMZ;+60US%>OsEI3{k=S zwpR7N52oK;u47Kv@FaC+3#d+(G5b>OxQFJVTxUCc_9^U0o{@B<+Y>)s&iu?*-Qe1} z`!YZmLEP|VWcho;K+CAQ);xAF?K)0NLq1)#klnpV3x^b~8ur=sO>$P9U$>JD*E4^G znJzFm_ z*};is>Q1kG6q9KzsH|1Cc6|HJ;O1NAbHs za%3F&d}}FEE==qU;!OOty7KZ)auSOaaB{J`HJvobw8qVo-ASdC&qNY^G>7nYxTw(H zatD)w@tmM_9B-5rRPAyGOau=_q`dYPH0gC3I9*TRsXydI}BvZAos_O8(N0*fGI8p&>vXfd9OEfkJ2e5og9~Lf|9&Q1iiZ|=qiIW;|v>$J0nCr z$k2liqO(s3*&&q)9X2|8uL(@-k1JTl7O<)|QAUcr2$i<(K_p-Ky$k)g@H-YO+Xt@es{Lj+~D z@)|{?QRKoyqd{msQOhTPWqi9B3%(UtMePOc>M4T!PfoL>)+KnBu2-Wocs3UE$aJR* zWy`R~D|Loi0ks@bdlQ7wdj}a<1a>+(>^s>0Niz?hhf1vd%@2##LZLQ%s)aHT`%F?= zxG9%J)NN{yPDR6!&ABKiTxT?+zF2*+(z2TIQ~^eRh*c)~O$J;kdPIkAKF17;FrKtJ z?WH#vgp5A07e!t_3At^xcWXyil)HDWQ-Lz*{Jg)Mk~!wM>}2JP?#Qfyf)iS@?X-DDz$21lbCe0>t;ba`N9?N`(VViD%P!s(wFWviVmQAiq4mw~tRgU8xT_Bvx7rf@9gW0^|fODvC$_N7b- zne)&bdzU-XNHHK01ZQ83Vba(eZoyq^>~S>rA*gsC+ z40@SK_4?H#2X=L`Cq-~I62C0|QGloHP&TZ0jaXBqQ2#hY>t3SU%zo3E%tVJc7#TPk z00>?*-6Yx1!=hEoD@jlW9qtd;y2P3Eb}vt+M>Lcvhu50cUB(T(vYc}g z;Z^GLgT97w+7LUhF_^KBTx!9{4YA$QQ9xPtUZUze6Kl217a>j7^&_g>S??__xNus* zs)rpkoEN~yQ>YHqNFD-oh9Qv(wOGXlXbm-^DmCau=8-~^(3geTf(;&Rqh3+yrBXIH zCQ&$PQS#I*j>s63^d?cqo>3NM{k^8d>3N`b|42mKZh^&5r)O(kwp?wAID``+vZUse zpw@4-`xZn3^-+6#QSH7ra~cpiz2@zkZxye*URTD!nB+MGg5Am!wL8xa2Z~Lm+0^5N z73mx!Ps@>mzh+SVdhkc+OBW?MzRJgSyn1oJIy0S2`g2ww{VSx$_9w%5U_odigDZf+ zhLGGWm-?_V2YM#V>a==$aE|Xo_9}%d3si#!#zZkZjxmM?)QyF_+)NPs;p&Y?+cO;E zkiCz)zfZ!#_qXLsBC@BQj*`GO%n;uRUhpH3oJZg>^b81mSsL9?YM|&inOcRD=;9-~ zg{#A0GB%Alz>PJQ43}#3@j0xS6mp~|z=S7*$J61ZIoJG5vX7wsq3J^GyM8MG^LmVP zf#lGM)4$L{W8!j858y>@qUiU9&gI6sieH`M7)|RuMDoe6?9%j1>~<70f$2!Yz@wK=l)ERgj`VBQE?ovfp(x03&E|r}Z9+x;Ma+_eAuN}e$4_8Iu8yqVPtC2ddrFzs>0-^Xqa|GmW0(}FoiMR&} z<^OBRASA&u2)ylxz?zUz!IRD7CtBy!;EJqNRT23zSL?Z5WRiRZHJTGd#qz`=@&$KXGcef%uLq}fTH z#qSydP)J!O)-oo6Yr%P?fCI~g2mDx9Ys`mL#O@y6Z@<58dlAxvbdIMJTk58{zoxpr z{QIBINg(CA+Pw}Gq|4yjWT7|HfA653($lw^WN=ti^u_xn>`Zt3`&hYd2QAvg^~)f3 zzI=m>q;LJSV0tMP&==WqXYuEqA> zr1)@dAI~coZS`s%XS0tR#+?8HZh=rSAwx89*gs0v{zwmDv0c>p-zNCCCI0K<9RYaO zS6WsZ+W#27fW=UOJVObn)MNO^|NqN;|MPJ{PLT_}8tO5BoBD54{Lfs`kRYfxh0CtX zrAz;@`2T$6zwG}}8h8>OUqqJ=|IY}R0(}CW4gzoDgU$7S4E^)n7+~}uEYzOWw8YkmLEglzwrkS%Cd z{XZtOc`EtObKI{U4*ka%u)neVvsmqd<=<8Qz(DfFvb?oHq9Sab6((*_U)RFLVi_SSCJ`LOMp_U?PiFL+$@I-k0H6Y%av4w>O4}1WpX07N1z|m{jo&w6w4pYzk;?-R0mxs}5}^qmgF@x? zyK9MDk{mX#4KvC?cN->-|1*g~>7(Zbsc$nHy5m4Vic~gfrKe#hQ#Vb3^U%Si z3BS`WewLX8f`%*tuf{LSYzlNa=s(ya5+#Vc`^!#n%Q}}n7h!I1SFJwJKAJ~!r2@dw zJ&cqC!*VGC=Vs;?n?H&AZ%Dop(E>s#f)I=Qv0xY91%J`wrscGq87FKOSGn)|Ws zf|K9k@A)0JdSI}jSYVZeg@wNs3>=)_UbgV_vQ##`^h9>`BuX>aD*&h&2@EYPlKZVJ z0e3yFJkN7c zya5+~=UQ2We4b|YQPmwzSNx2;d{HDHO9+r(_^SgbOm920s`tuwZ+9MQ1A94 zk8-wg18kTg-97a~*+{ZWYf{<}s`L9T;qB2(&OwknBm?fDu=@df9X_yq(5<$p{a^2h zKS-KP`7AS$P2zhp(Nj2Wlr*Est=FD%#S)xhbr{DzBF`YqW_&k?x`N!$weNba8m;n7 z$1B|FxA7!2pg)k1>$jdI3LfQDp}P{gHLXIb=!nsKZz$Rkzs)k-LRN^gR|_#G58hsz-e?mA=1 zNR`;^wL;9YjdZT(^z)Ri@+C}j842o6yB;(usSr{*o9EbqXAOs-G81OKd{+~>)aeX; zK_96|Klrpuq#QVAP3CiCZm>q@9t=J9pA5r2|PD@ zgBZZF2?JkhOow+p`Q28baZQ;~+J2n<`y~fLSa=kO6l!Plzpo}}d=1a{XEU=UGe=Wf zlqa}XIA;4knp!~cKhO;6^ zQdle+e*@A2B39i2B87su>OHi)&_sZ|8kLL$oubyv#g+s-b3a?kM^3tvPK+NkisFtt zef4hi^B$ljkK2)NzLkW|^tD}Ct?s+63#+IT4wl2k7i611tNVA-INnF9cKTuzo6}60 zG|5Ez&5D=`ff>vg_%S8iPBH8L^P1;E>sn3*%>O$)|5vE9d5a-4e0@O?jP%FEz+kz> z_2XK**Lgi*^ueBFe*|v6C0qVVY0iUkS--1L6#C&*UY7S^elu|Cj%0IVe3Ya5^za;R z9BP>q!otSFI>{@wn+M0kqu#-~y5qP7>u0;)FEh%NnnUrjA)JoerU6K4n}w!84W*0< z;pps9-eqTZY4a5#P3E}#br1dnqiQBvK2@#57+1TWy{$tRJi|&jF!V;P)Yb1O-&l% zU#{b4kz*4K>@^s$l9+U^PgiihwltxJ!y84;(pSdpCmS3VR@;Tc3S^jiZ60i*wgz$T zfM{kMuam{%_Ssbu1H=H)Y80SgQH`0xLu}Mjqik?5XNzw#l1Lvk)aH8J(6k)10CBLZ zAWXimyIi*peV=YInO$&wz{0B$f0_&5v`ONuazN?0{h8CaPbK`o5zG}2hfHKkiWYiOmdZvi7VvQMaEUb^(qUD({pwH*)F%g;J!&k4zVdT*sYuyc86D6{_j|k?6*osU7y=}m1Cr4r_ozP zb9NTqc5^fQJ)5jFrdX$GO;fxd`TYT@h<<0!Ixg#E;1`WF)&3*Y1W%iJ`#9_U?RJ{Y z%>YK52FAp6+IE192yMB|GZK24TMWFXQ=&=@W2)H-!Rbxc9}XoCiS}mLqSWek${l&^ zfKTxOvI5d3eH*hvr?K1XfVskQ^eK@_UW#UuWcrW2QRNz8NHN8tqS;7dyj1N6s3W%L zCdskT_aC{i5GiyL+TSODINTBf?*!{M8@CP<#K0>xN|$l022VB+MIbwzK)XYEpX++e zJrxH69o=yEwqzpk&On~|P;OobhjuX=^#nN~ey`PGgHPuCcVykx`j5-8S^3F@L$9p~ zw3=ThqXG~M%wR;HzIg-3AYDH1-7r%>-FieW+ph-mVr~9l=M?R?bDK}!S_)4>%;tW7 zvoRH->lthoilcMhA4@z*zpTsTOSdCg|jqe)ZLc6c~#a!gvl0Xu9M z20YI`lTJgjPsIeanllB zgpSa$!~#-HcjGqzpe;hTcXi(2e;?F9RwQ3r=PhpHaK_ZjG{+I0s(+~l*DQ8lET(dO z=r1K{NCgeB9Qb#|;e+Tx`}EgSgLLYxiTzRp3qK`|*2>E%bHi%x=4#%rV}jx}kCD_V zva_?1Ll7b$qH1A?%4nSS$NaPEEGG2>ay$xSg+I~tbZAuSUm8th zS=TLQV<$PRr%X1Ve{3wuObe|R@jH>1t@(NI%5wO0$#LR#j?;e;Ara1AR6zT}<_6{Y z!pf>P_MneVul@D=fNRdGp9}`2G==6_e;8?sxBY5fD_+G^q{ZaeHiYkS^>g-s_xgpd zc0-~`XEe#j+^95+V5a!|Yt2loRq3bImYvMgDkfhSto~x&Vax^fi!!x3JfJB^0ug!y+r4{-6tD!`DeOlo@K<`q_Ht1#LXKavBlz^ zq;-X2mM4L2fbj~p+2xU4uW8dds`)Wf${D{vi?s8w()YE+_~`j!3yLP*Jh(($`iVE3 zPA(|yFq{UvQaL-mK=vMK@~l8)@+{ul^r6i|?tqkVGM0)B->b4vb)sd_>l$*+>n;_M zR!RC1>*r7CX~83m1)cA2WW=prN%@jd3X(q_at{0|vdA}5CQlU$qqD$&`q;Bi-Lr*% zzBWQycNAaT54l-p!+Jtq^1ql7x2TQw^l&X=ygwwOA~rP4vXULF{$ zk9{|jer%`S+=MIua?Vb<0O=+93yUVJhyz?5I8YMp<&Cmjw|SW)6d7`lQ45*J%VxgJ zdFmmdrv)Ad66)!`$Q$OWg{Eq(G*u^r+ZaF*OU#8jQ=_Xl+h`Q+Hm7l3cFOlfZw;fY z9L~1*9X40`#ktMG9VK*pH5$>bNA{zV-bd~@dLV5odtR`gdh?V&noql)(BrekvsnUT zvhl$}+LvVDgG#Or3L&FyMw%L8a>%LD>*Hh7eU<2)EDG=@m$pmWx0v?}XzJ-Dvu$0T zHzKhq#dIUOgV0fr=Z~&k?sC==JB?60$wa$+_RrVq9L@~xXc+D+<+H=Lr{{WUO<$IN zC+5MUL&7ur-lWP_WX<4Boz_AO!*a5FsQ87Kf^Pw0Str}*a!uDXCntqAWHZ|ysGacs zR}Xl$rnW&)ifbIgk18?~%t?XZFCl~bgVjP4ueB+I*lW4eT+9A`U}(Z`#8n$U%%IFX_5!S6l;th-x=GB=Qzp6q!e*tQN$0 zI6U6YSJ57v&rrh>ufO*fVOSEI~YTqp2l)K+poK+4x&ezu^x>6?&WH4J|$2r(s zu|4GPVYAUeoZu1O1PvRxOUn|wR8}wGzV7X-)WKQfxm0(Qa?_7jPaH#9;e|#b*hko* z7;MQlh-Reqj7nT09_;i7j?j?{wrR?L3i1CV>ny|KShKE;yAv$927*Hf5Zr@Hg1fuB zySuxE0156i!QI`x@!;7{n3*(<6p{K^n zv-qtG{g+uy?A$9^p*@0S%B3+;lWP!_f{v|#{?THRC7^}QVdKvYs@GCEEGl>$?kSM2wn2)>D~S}p+4amCR6vZ&?2-`F-WF)^M2B=EQ)VS z(m%FalkQ!%VZzT%*cLZ@xp5oZpP!Po-g*A9@nG(Bb%JA#9a%vXSfR4SJM|jgJpW_u zSMKrSH#YZqHch&c)ECN#fo-NwA0B;AZWD@#m!%S8ts7EZ>!FNMzv#g=s_6QzPARjF z{@px~zyNk$@P~y$(2t656wN49v^>Q^)d-M~6cswSyO9(W>>T7o(iNn8dzKtkiY#Y9 zKZn^&h}FoSABMlWY~d-sar)(QOx_$o;WhHGTgfv8_Xr&cY3dvp4l+qQ6@q*Zpf_fU zWa(9&!rxzp*|uBn<+YgI9G-BpK#f`8xcIMEpRw3w8~*NkHjb>!{F3lJoZ3d`OU$Fy zn05I!_JdY~RWNNizvoSoR1&S6LY7d#tg3WF9~(}a7N%W3x5?rCLXCTA&xMF@wOZsN zkL+7TukKqSE%r?!elIiH#~@C~P&NCl{^%n7vW^g#1naFL*(XMf^!jh~OWHp&9&s6U zD0YMc{geH=-P8G>RhvltuCe>H6aQNgri4l>m@m7h^W<$AWSc5aiqZbDH=Mdmb7(-0 zzx^7QEMx+w{xJ*!p`W1zqQ+R@!u>Ue?{R;m-SnqX__ri3NrMy|fH9Gp*)|X4xI>9{ zV_D6(gwr&?ek zn!&k>u*-TYCrE6mNO8K?U?l&WaRDv1on*~-Q+l3TR&bD?<<~&M>%8(Uz{3!n;UzbC z$p+aE71(?HD0wK{VV`4;WEmCax^Z{cckAb+nE=msxzW~)0^B#14;DiVB6_o5&$sl+ z1sBlJ=FCeUV+&Pr6MD_ig+_dhk^Ami_Jy1eKG>Q28>fuENOBK-ZA+^|)o+H(A?0hj z4NGYv2ivs`x!#8uyp!zcy?-{N2t`^k;4JPFm(@=j+CyK;yM$)P;!Yf zeQn&&`Wf9;6Gl`*6A&MVmVz7@i=-}r5$tlbA_ny45{GZOE#pK=iJ`dmU;>;jw$Fpy z$og=$wE}))V22R<{I*($WWQ>1^udPADrN1;o^5165YaFsC}@S$F<)yTNDTGzT}~wB zu$-e%81?j=)GPDzi^L*Nuz$QTCRYu?aawNmK0f!R0gFpVqO!3-rbcSNK8WePQ4GYM zSGV)voRu4oc%K!#_s#zGelkqN(rj+VBv17}qUe(r`WTy?dGc@OXu0$jg*MR*lE_kiFF;j>QJDn6;?~XotQr;CQ6E50Pd;M}gG=Jv>^|iq`D8=t^Di#rj1$fK;904{=GXci-1v)h_fg?5Z;SkU0{qKOk`RAfMIt z`muiiL(l}3O-w<5!(-9{>#Y^<1TqOASCQAX4tHJ(A8bM&TR22RtSq{F^d)2i1Li*P z3*JG60P&?c0@aVrGMDaDDa;j`h&~mr8(v9`YsE?%R(zm795b=p< zz8i7k!B^{`9!n5f(PDDm3Sd))YyeO{{hXDS$Xoz{8<`8kCvxdFwM}q{dN@-V0km3+ zhbZJ{2pNAfvKJl~PIqBc{6P!_%j6i0=tOG6&JC<3DF!rWSZSo|P4bVs%!Q%zHi|V_ z>a>o*25)-VX;Lo=7!7?23Ez!N5g!`tPX#kT#8R9*17_D>&Z>USP3Y>L3o=!^(kXn{ zd~LUWE9xDEbV;EX^OZEpf?6lWhkQ|#E(t+Nsj;kb4uOL1eHuB^K5o;vcK9?obeg^8 zs4+lE%zWpr6y^A=)|^ou^wx*&gisdcOfz-WjiN7vckDc952vSy*A5)_MtTJOP+(DO z@ePp>ZNMG@RuU{q11B&|Ba=ooURxyFF6um~5zo=gVYdz%2T542#g~vtH zD5I*@*x|Sb*8k8Sg-1;Ux-8PkYptFN`n2L_=jf#T2`ZI3mbMmTmW|!8)GjW&=bccf zgBAd+f+S>l>~~9lUQ(dF^L+T#_1H*#9RD5bDAR3y9kQhmC|k?^ z97D`x^KAM<(;$6BKDp0nI=Zrc!rA^Xp3?ZNL0^=)%r}}8?JBA|4TfLuE8YL<_1Q8* zIUpFd`9`ox;Van1hQUzrnV`Vixxn zr^YXMQY1N~7HcPD;BpRi_@7;)`{JE#qyPN1v(uu&M0C7{M8q0o9U&=FX( zU=)6uie$|<+OA^?`Z0+)oi8Cc{ho+1q)gs%;Z>8ap=%y!ep#~q#HkRs%34I5mgsqw z7fo4Qv)qS#L-7-Nw+ITgblAG~^>)X}BvIcxzQ|5zNOhddAB|B%_8s7YtTa?JtN*4Z zx#^e#&-l=m8)tX}mExZFE(iib5JFN^=qnDe&=@0w-Vudbs^e`z&(vtDph8s?l{MG+ zL7OLMt;)7-f~;sDZ!xj$=+gW=oi5SJ54u^a(z5QIKAT32kY?ez5P#2Re<7>6FWk2feHxbf;~aYSwq9pyvkQ)tYJfo1{lz+uD`Io4)66#*%&WcJ%ixKo z?uwU#ISziN!VNu!h!2(0l8#0R3yvoUAl_VG8S_Yj69ZS7T=~=+;_AkubieS)6DyqF zB_!rFKh38;1klh?Kn+gput;6*#>IpinIjsqUtbKd*#2~Js{QTJy&=T05r&=aM{^_3 zs>ew`NRb|n@6UqJ>VxU@@cpIuJ=9NWeu1!Ao_I+UX%)Z>t*pvmtt2mewQ2{lAa1-c6U6NQIw>j)`Mtft!F;h zueKGUFr?aSx+q)vkn`wx{!}8oF!UOiu`SfphkVOr z7FLE%z#FGjEJkgS<*;hH;L;SoyZMr}yA-`x$?bQ|O7wCBipkp&c z5U=Ln<}ytLG-L~5+L47|6fl|B_}Lqb-mGHUF>3k{b`Teh5SzYGTy{khaz-lQFWJ+8 zg*^d96di;qh61W=f01G!on&57=qfaI!?t4tjk6g`J8Ibol&t63`&lxgg`)Y1(Ifzi zg2tak;pG|rAVU*lHu#@g7=KC#D5gAQscwjlIM;Mh!ba%()Pd48(fr#)L7 zQ&%DN8d}W`f4@gT1~5c0M{nd_FB7dkB;EwD^{+v~W$3`guy?D>8)%0}-ljxVp{vrE zcyoh*52M9x&eehdVk6c-Ah?d&`ux0O72H?M!=}ki(F$~xPVV9yd{pAul6DV+ZR?U- zCE@|&Re~)O5S4Pg-*iYWBVkEl9I(k~Ch|drVuHSRShryWQ^652fWsKUCU9Ypsz1!F zfwJuKP7iWkekl}rC@IVJdO-5QbILYBwR+EWJDr7zSi8YLrhG+oa>Q?GX`6;GmNw`6O-DhMWifVB%$Z=hTWr`fslmihd0wyUl1 za&d@q@76#9jFio-P5xpy_?F#-h*8_Z>?yZdyz+`zMbJ+_G zE)m*YUjiM<&zbGaY2B=>2JHU7!f0e4(isPpSp7}Cp1R+TP=QAvJJ`6`nSA3JBuVEKsk=fB)g|@1e{)1-p zIY&o|AGUjo$;CUc6}OuaVXS3LtV8hDzPDwptifF8X_Rhp4e2gb0*B9&Xj}cLX{)}n zTAdlD`$OffTW)vAy>2G{R*QRkIPRTegYY$}p?ZVWqmB-N?~A^k#qp_Z88e{t0jn=2 z=3^a$vv>f=_Uc36PDmEs0z40*Q*7< zCp&hGp?{|mANBY&-8}L|-2R(ru@qgcw5wU|@R)%4teEeVwu>4rV9&svpBS)M0TIw! z{&&B3&aq=+kCNn?%0X7UjI$DRqu6T5^B_0HK&O>Vl*JE^QyA%nVKQZy^JAB6_6!> zZ7?o>fr~m2xat;b;hV2^)674U#WLx%d`9Q~$dpuj#u&XF;}?iEbA8D9J*o+!g56Xm z_2Wi9MDEsKpnCH$Cg`V?(0i>Q`vO*C#ACj-Uw$BbYqrZWFeOB%vk|1eC- z1uccOx~4oCku-MU1!@;-UgEweHPPrNZ@n@~Z0;v|fflg95wvTS<*km3kb4YYbT#sj z=uyDMIC_rXWmQw27epA95Dyd3gljo@_wq?UI2fND=zi9jkHD50mX-)qh6{$GtuHfx z$z)JTkwFnMT<&ZePPdyJ8UjWJRpMEVC&k;^FcmXTzsElc48b?m8!T)tR$jAIz>+us|LPgQ7kpN_K4A z#hoBWqxpufng`}gHo3NS2_*xD%D{H?hCFRTnOr%>;}Hw%MhLRrAncOIjcD& zq-*p+1#8%J&b|v1iPcEIkD)xo;hZ(t4uXGb zR)Q4AD7d1b<|M|?5yzLo`QR%N@?zQ-*>pDT&7E(Urch|^`P{qUEc8;s5g-WYu+C15 zz?BHmjXyFIuN_L6;$VizPOdO&OJDf>RYU(iT_LUr0 zTKXXwaht>^aj)sjZ=$Y_2ES-bosVjEMB}qYsFZ7L*)d>T)Y-1fUFcck`1zIgGtm+f z;@Sb#Daqfr6H#T1NotX1V&n18}TinqjQ1F+OQM^pieL3cTJ43{Uzc`#lmq-ubX6+`s40De=5H z!863}4X+=)6;ChVm4LV1z zFXn#mBxg#>>#IRw0gWu7W@fyzu8ZqhiL$0=%On_se@;9=q(j?OjDjeXW8?7$5aEPD zZX-F$A!HpI-QM4qbZ+}Gs$z^uCEheEjD{q6_SNp`4zc=y{V=T^I4=QbYT*%gjie$F zGh2xFbAP;9UtD!-rUZ|@c&hXvAq&^8p_iEVGzvne$*mycy zLU;+=GcBU!>gJrrznWG+v@(Z)o_Y|CY3@4Iqgr?bgAf?W7{ae zqmm3w%cWn1buGjS+!$H<+}v~ydJu=eK~sZa#Qz-)`^y@RVFl=WmBILSN24~_j-vZq z4C*glO`>enSi(84NacOgoFl=k(TPMl{UJ|bZz7xPM$duZwsaNp5EW@1@{pUAB4(Ul zAVj^Fdp9hFmOEGfuD>x{kT>9SDCG0JfqOKv=meANQG+qK-g3de>kY>ah4xj&O!45% zbgxjWp0ci=I&Pz2K25@6KdO`qJR%kR0U@0YqD%O^D$#f{!JhlBCa1syc1WS&$BJC( zV|0G#ysczg(||fO)BDKJns$o!cVm2xsol&B;Dj33l|*(co|5^5~n6JS1ZMQ~e$g<3yo0S>-+|ZYzGtm8=Dub?A6`AP4bTOWgNM`Lm zKXwZ+bJZbC4B+5%NhQ)mYrzBg4hJ{1&(Qu1EPcA%xF}Ct0wxp=SHG)gezM2LkGGey ze&S4ym0di~albkvXVhp3Vgk^KT2wL($1p^VH^b-c+$qZsWXm{sNmYJZhW9?pUQ13p zLCc>OTM{N^T!i-fBM`YDDbdP`*5!`lE-?8hCQ3?<2k13W7Wv&zKh0|MsLw6$(n>_L z;cmVqak^bJECE?jwYptP-2@xO^SCm-&JUTcliF^)$tT3 zJhhEtpdV_&`gqFRKH_xOSB1q7*x2)t_nyO9J*jn?Mv#QVI0*3}=R+maWYXCs>+dgp ziupWmrHGA^Xmz4A>TT#A1)&)4p3&2JdJHa(k!jYIs*MSz7OJ<*hf~^(iTnA1ruIui zxS;3Vvf03d-{nyU0tgQEVDeq=*W`weHVdMMZa%T+s=Kxuu}C;cUJNUkb_?(|i0iOt z35o!5?LAXO8P$;1aT)^0Ah#;u)r?v6;KyasvU2r|vG3nf^GNgE888Pm_1ytOwbA32ly+x=2g{DIw&Vp$5o}p{>QJ`!)5`PdVqiLpp zVQp1m+2wZv;e@;&lK-?1gDD~Kk~l0?iV2VSy(#Gzi;bZw1o>}#I%@>Tm&q;z6fzR& zUc1l+H-bBxI%~0w3>R)N#(tw{DO@~p+HVG3^@kn@)F9~T<0S!g9N9c@Mj` zoFi>}i2BjxUji0Mb?Wff3Vh?&x)v)^t-2vOpF+lr+>0t4qKecnhmF&n!Su(@6s+c& z;gV%Pstw+L$6q0Pdz;l7)now58|lEci(T#Wlg{Gi!+9*q$3WtY$<1fnGD(}I)5&=A zH3CsI#oBR%JqB_Q{vm*h^22wL~em992$uYsL3&_v;)hn&wf?06QjJFfFuG?*+A7s>U} z2+ZN<*!1s=p13F!$#)9WjR_R;DthqR!5Z9<=355^CTWrj$#J%$s5g8 zSz$x>sqcv54D$TUWV_9-hl9+*Tf0ZA&G5*9XCr5NSf*(p6=A~9>FN+F@IiEVt99ns z)_R?Zjt=;gx}K9vRzeQv<)w!O_8YcnojPa|HT-co40)o8=VL&A?mGP^m(%yo zi`8)_eBLtG>|UaywMpA1I+Cw|1W;;93AQL4mC(LW*&x^J+7w!KVfv2qQMwt$9xF0f zOH^|tz4K9J!hbnc3$4+7npx~DEW3zaITe=U@46&gkn97DRu)(FPsiezq>tIIdHrFw zjh$BRPFsDlp4=mjFSqTxM49kQz^tkw~UDSdvSc8~D_lK~QPVw&MVr9$U=FAc-qTVL~$*>YznN-)MP+Pdo9 zM>GatqbB&PsU*_fD3+vkza_ZLNvtD(JPz2+kHhzUbRKfjXxA@zo&d5eNe49&5gkgv zKA|rB{(TZItFDvWICq8XHf^8Rl2k{&)@N1)s4)WVRh54PJ;>}}^2(I|1~sMmUEaub zuaR6-4Me~i3b)yNTt6GTz)hldjH++vFzsG0Me5}ZzPA0 zuuv{LBmWG0m`iQo&l+(sbFa0@kT)wqv`xtL8we)jFv-=zpTs8C8MBlz+A^PwrrTs!py@R(XQh?dyEhCk_3=S&?pdd1?#RF3=@mgNk~p$P zg{jwjvwJj9hfBSK+7#${0GlHkFt1QYIk44`LIL?y`5894elK7ucj zE|~-H11e_IN33Hu>%U}EnRHWXcA>gZc$+6aDiw_oEUKQHw(R!X;5JV8MQ2iz2B8@Q zlTGlftO*|8vNY#85kkix*5|(4;`ONV>mUP9V0;K*HyI(5Xb8*=g;(o?d9rA- z=2z&qqQfbSsSb0&t-8zS&_P@Mau@jRjkw5FYf0J~-T+Js?Ory+t9O1xIkcr}0XXkS zaDY*6W9VdVHy5zko^Fv~CWqTm#}5~I`r+w8;dQn($q7uQMMHyj1Ouck&go1KE6K-+ zu9eeeUzUkOU@tCC53=bjL?t$eD)ZJ}r)=tU{>*y8QO4wCj9XdzS;DDt0ZJBepV;Y)SGM0{oiA&$QKm^s6qx zZt@ah6>8Mi=V(fv%BZRG@atYRiOSr-XZ~V1<+1ygnK!!5W^^4stE_@bfAw`%RrWW5 zXDL3H83Kp(RGQL-7M}BlKa^v;@v!PdXJ9B%A1mP62(OK4O5>In@dp+{y*+5d0AtFq zcGN@8rBz)G_<(!eMYcj~N8~NH#`BdWAfBeu#V9ggq0=~iDY*9QQ>qGzphsJc+Ff@r z_wLhTt#RVrMaLx&clls^;1q*8uQ+ypW!^qDr2HcFoxB&KU!efb(vE%d>A11)_WL!y zv-Uq~C`hpmOvsT&i}?o`w=jTd3relqfX?;0uiCR>c+fG`LL6gi3U<5>zj)L!r3|S0Odph(j&-pycGD zy2Yq>cWHn&t9-T>kPgQ!DdM>74Z0)%X=luB>LMu<)|8m@v|;k zrkx!Av-mUz`FoKCnR`Mn@t*=i#&t29S~gqqkA|E#~50o zW1^M`IXPJ&uhVS7_(u+xJC~+)KfHZ^UMeOoC9%nW{MChP_reNBF&GVlYz!9!reDts zJ**FH=gvYgiFe&O0n|AgqWvXUYLbtks~C3p9b_){hQH(yM__gRLvj^|0-AR2qEFo} z@A4$|pu?7%>s}GISaoAi@hQ=?0_x0BsN4vtvh%u}b=bnO<6)VZ7h!iOI9b6oGWgvz zJ?4{do<~TVkau5g>WIaq1N|Q7+BpacUW=MO+N{86)END28mtKLF`y2;y0P9%rX;ew zcyxi2G&+&)e>(^m^*(ZHzb-x4j?cM-sxPqG>FJ`Kp&@hG#9qeXGNe&5=s*H0Y8Uuu+-2?Dg#$x%hlt*0|(fmriTHb!~IVHRr^ZiHsMsjWC-Uqb|H$Sogug^SQiv zqLYS_uRy=c^?3ZI1a$#+=azfeewfyL?e%wfV!3aRZ>ED)WNVBwY{~(k8z+|mdEm%M z#Y3Y$jsJ87)=*)Dr90GP(S*hlX^d5m%RajUL5Y|hyVVWDd5yVWF}Eadim)7bx#MW= zz#++>R%kX?qwD@Mme*^fRf<64!4M&ke9<3nO8iX}%V2_X)6%)s;+cwLyh2qMyyZ}Q zwq2y*ccfNrp!ikA%f0WCcG}p`PuPY{FCRa>m6}&?6AMW`U$&EPccQ_z94{9KpVbwJ zz;3yqnK6=1Ib-zM8}tKgKz!%ZoP0lmtdB3!C;{vwj0OJ|kpG>de+&==pfuDbS0SjG zJA`DAdW|-#4-ko#caK4{q5C?hKMW{*PG9&MAh_Rb#-jU%IWyA2 zuo(}124-~Tp%6@8_Dd3NW$YfhV{6`^;Bze)U)Pi+-^mW+ev@^(J5!_y3PG4Ys$nsu zP;hT}Ur6V?(PE{Jn<&qGLKTA!XmWBD&k`#{ZJX$>dr1M36=ll93P(NXg=6$9hE~_k z`(VXScY9JUjQjF!`8I2xauZx9y;?#Dv2Ll6>!T~SNjT=7e|J@g)LDiI=;P0_)9jQF zr+gtIl|3t?3X56>JNGQ@A9md@(@tU7|!(%S|X{35t z0o~eXC3g_d4)HyM>dm$kog4z)W?gnY6`UHs#A2PysUt0Ll* z)|XPUb-^xjJ`={t-O<;6;n?LDSS!nW{MZj4dC#H(ETuce!5chL?D}1v`nN*xycE?t z`Zg|N=@hmf8-$~}Tq(BfFPVsSYlE>&r6lMsj0;R-b0&1Pp>#+w_!)GbLd_%$@h9N; z+%J->MfER%Nt7m|8L~i7q=)+bgLr_Q(R*xPOEu`l3?Ljn0;Axfi7Qy8O_b36y+7xf zsIsED9_i1B7lRz3yvuKm2wX;rw_xq&>o|*AlNVt`Z0hd-qAs7H zeas;7cMs9L6;f^>!c4uUJX3T38t-Q=vlIzq1EO*PJ!FZV{;-j~O+%W-999CYRt-i> zmwY9rIRs9|^(gTXRsv0~z2mk+L5anZQSFj1spb0ZRt)p6$T9tjjW!|;Whk`Y{qj)H z7ySCW#3Ic%g9g!Lg#(Ey*!&5xHc!dXNU>nZWHWsvjfZO**pb6zp7M>VBkBiDNA5<2 zrnTRzhd6HDzv~(X~w6Z>ln7%1&e_fMvg;I1t9y4oc z*;KN>v4VsyuwHhV5_1W)l_f<1db?(>hj-uz0`?*^atg9sbjwaYpQj#e`%!dw7&5X@ zI9dkPGA3Rv=io~m14en=yS`1mu=x9sGh@!U*!*AwNGA+Br2gxGM@gz&O;^zgBGF`p z1Syuq!%j|0S5_X2GF8RW zc+|;UdOQP+zu14ri|f|+T_#<4fE6U)YQGhdErU|lp40m468TqLl>K7^@l2^}Zbt;q zQJ@P?j=2!uJkk5sUAQr2n7&J#+-^PIUEvwcsLzT6JTAf>L#(@FEA5!yZZM_MQXHy2 zv>!Rfp71(KQ@H5H- ze81;LGmjFcB#iPx8n`SX`+iMEkt1=;iFKG91+I)HqUwQ_p#hKrd!6m8P(haQPnaxN zlt2C%e?ZrgE;wy_R0)Wr2iNjKakA0T>i1Y77@pyDf6W>p84#ID)}j zWPl(iKA2GJ$=VPl8r2r}s~f35-+cNn;j+>n;FeTE=BWJT=lQ>n>OU{svCP8(?1+O` zTWjIzRIoxdX?c$Ome00Bh}>)gdE+<>@l7|;xog%#Q6*xCxW$3^t}8bQFleglTl$~I zW(CDWnL73#^CsIBJ652np0)IM)Zn2nQUF5D5V!~?V6Baa0CN}{X5MP1(=|D|(#8R%bjJ%Iq?h!jv8 z#JlLn*P2i7vYc*-m?b}`>a1l4oD$VgS&{7tQG#} zDd4!1vHU^)pb)SA?>c`9{pZl2n<~Z%{rNR-{LQL)2X7}SDcV`AZvrhC+%LA@P)iwo zay{X_zo&~Mof(`DREAk2hoKM)7k<+jsB_2f9V$-Tk}n>OlbFMQG3ly)2A)!~h47!J z2LGY+{QOc(VQXs}@rL!=2=`dFfP{R`9i01zADMJt#HEs0$a^DjB1w4N1OYSeH|%%v zQ$W3}J$HL{mIs{cLoK_2jhbm#jh&CrP^mI!l}_=S=m*1H28~9WKxbD_g+we#_BVjP zbHoqec3_Y>Ed%c__X@^Ke5Kl+o{j|cFj_sXhkh*|dlbhI)4Cj~)j7;sEY#(-UOwNZHmQ9l0Z|V6P~-N$floo~P>O0y z7<5Wh`@zL&LOvpo(%jF#dm~eUBBxeR;s=aY-q~+C;9q7J3K4|U>ginYWAfYd^W*JL zKn*{MXk)*RzY_s{Z7zEAPTk<5$@#zWI}KLkPy{V;fMZ;`J2VzW(VMaC&-vWp>o2%u z082Y>ibDq_UxyCxyyePPA9S4vPrOAVhE8ivGZnMGhY#%CXfLrCyyx?lUI$|E-oiV> zsfIF7W@B0RSGVi0K|K7YBJkV}M#Z>{8N>Mi_B_&LBn_vm8;Gy|4fm0JenkHU1c6~n zb=+`}7$Nb}p z5RG}jW`An4*B6#7>0)nKTENMQS^1Om!TX?GNAE+INs8c~vdeP`&Nu6|GfmR2k0*EM zTZ$uTIlQ55{ja18m)#bHwz+FbtY1R(I2-?M==@wYTmbGTRV@rbJ(SD_2rz@rI9YH$ z&(0-rfyj7FMZoMN_~M9RwzO~?@L@5z7ss2&eE;-51F*stE4k}-iOg5i4sYKAGg3&dv6=xkj)@+ZteYtTxG+C$h z*Oz#T;5$}q|CDkyA)u9cIYtT)eVTpSFd90-k)9B69`Cp2(Vt9hW(5uR zU+OK*47x!5l!(>otoa5s4EQCS< z=a(D@_hp7w(Hv3p(`XL+xQ@@lgisQjveZ(uDJd!m+ei+n3OzL3RUu$YfNO5*4vkS* z_kj-3CV>@^<`4ekY$WwZeFEf$nPPe0Uygfndc9DqdnZ~UxPV6iDp=F?pZGS66NK%- z>_j$`&&=zdXF=x!{h?1f8^g3{qPY7IXS_C>Y&M!0Q;-W`qE z0kEQt$sI!TTD3`vnF3SJeZo0gz!4^kzOHoq5C@UOBJ%eA{c-B9WAYpdkvw|E8p4Dq zl}ovrx00kK zSlqG=M>ENAywiV%eb~6%Bye6l-#h$J>>TLtC-k;I4;mg#u|GyTKyj;`IRQ}iGPn&h zcyniJK3JVGFYZc9rFCZJD*HI?cPIkNTibBR@u>?_s3iN^ky@ZxPgO`gv03r< z!puURl#>&T6kNdE9EX8{AtwAL4&zPS2O&`yMTM_#sYLfvY;e>v3Y#5Y9oyV5UDlRw zT~dt|eVg!H)_Tgh9rxCFZoTYRG_shJnF_C%b9HN|rN3>LZN`SZt5}KiX@8|7l-IVK zpMFoD8S~nB5;AzV8v2dFazCEEW;aM#rMiyJNDDO**8x&Oy_I|S>8d2a#VTR9m7 zPyj5_=hxc@3uR^<5bajKd>k>4>xTMjA{1y2Q1m2I(MUTG3dEytSnL_biYV!0ddqN$mQgTo7WezQ*X^p&{r#T1&k2=Bu{Ymvnt>hy=(oO& z0RapO6dYurpHK&x9)=q}f>Aebi&C$H!*L&~dc8$?mH_EXG+yF`Qn3`Z@_W@dQAw=9 zC62?Lfk{J>MP4izEr5Yj=yv{1_@_h&Q2hrmfQ}$V6%^zk3Bu8-d?G?ak?*ROLH5{I z%M!=ZWW33XkxWpwt+TVn9-qHUChnqBrQ@%%k(#u8XsVH3TH&_JcN|py8ATGOuy^8T=M&8{B{oCFh;}6;9G~Z_FCS$dV;4`!>9Qd!Nu>T!?ySRY^XN4|ZU* z+4cL>$YbK?+w4+qLINnjKArqs&1xMz%^5xZ76=13=ZE# zk?GIOTpy`^N)@6fAp=H`=yc2P9w~JfinA0_6!C8;?Ryna_XIlKLzd4ZaM0ZxOxCSu z<)|000t_$Z(pxUPP8g;PkD21!iVJOGuGsRtgzm5-p^uvgFZ!E?KNZu%$b#y^v4{BL zs!l6krH4yXs8NUBmyGYm)xEK9@-BBd89z)kXWY}adNWEm2Ys<1LOQInPpE0 zrROzflUT8JygyVMW9lqcmBzoYR2lX&ow&=$AaG&nU(pFUM6M^!=*Yk-{m#x=&3Dh1 zx(OwU1KI_%EVY~{9UeQ zRm@_Iu`)$s*N>|`&)~NB)&S0X(OgHWS-J@iaEB1L)9)%(q(heXP)xsWiDoYc;>h{d zA~z1k4GH^h(c#dY=R6RfwjFWGUw>v_o|}rcXYsjVIu>u%3-Y+Sizl zqiHz|E!OTC0n>w}`ozUL#PFb-E9NV8LnRld&%#qRkTwY!nPliIwVRoye3G5nJAo^c z1<1Jk>q6})2oua+>O5F>->*qNfX}#89lKs>!6@FoTbq*n1i9HJO{6;BgvRZP9$XJQ zc1a7PR^Xw`9eTRFj>~BL-t|r%+wQ~k*B^F5gZ`40aIo(L0hrQH4ZDv>PDR><5rx)v z=u_1Ix}+;qN%%9vl)bZ`T#N6kcPsJgQy&Es75Jdz7Jk_|k!Dn|r?Uv-_B9v(vnS3!*ztQ>H82yvbdG&w;O@m2st>f{(J+Wlr-jCHZzD- zJkKH2B;lu?PLn_E{=%H!Wkpg9^Xk%uQ<_ye66&EEiEe}dGysApeP`h4p#XhyN)CoY z^OCRs!u49ft!z;&ipv1SM2!IAAp@&4BXSk(pOS$+lE&znw?`R9kXq<65UUa@r0vqtZ+1b-A&5z9JGpM1R1tK|vH8 zxb`nvN#tf9GEb|sLSSqe>vha8W2kvrAaIW~P8L^puNjDnQ%I`sFD+}iFyK#uRXW`f z)BKtki#ZhF_lWh@wkH>uI*VZ^FvD10#4IA&@%+>*!M_H7MI{Q1kWO?> zLg;_ur+`f0*Z!34dmEZA&NTCiO-=(?p5=%Om5l4ug8*iO)G*CjzHsox#EaC=?V&O4 z=Bo>A)C1{DT;_fLa84|{7vWIVWyx!>g-3dZGBVyv(W4?J|Jr@L=>0~hq=(1U_dzyb zM{Ga0&g0YWb2^;st8ttZP;k85Nz=)#SCgHP#a(^?zbIp2#oIxx8j@?+k}*56^-j zde-E>y!w*x7WtD4$e;hF`P4T~RmZ%rxW&6;l^O0MxvW4yomtQSW9u#3;%tL%&EW1% z@Zj$51oz;U4#BJA2RUIga@Oe7fnXx~k5#R-;oG zGsi4wzIlH zv3lJmlfZJe@9)J@i~VT?by)3GW>qcAbRF})Y zim!E((=YQ5to!DyiSncedw>@!Cvuptm&_a!Rw39}^7$eSM0vh4DYaaYxt+ zJNzzd`$BT?mF^h=dEe)?4t8I*G+JDbljX@XkyK4POX#vcJ{NhpZatsYX-Oi^tyu9i zCmuTQS|kk%jEo#`jf!7HR5Y$Sl;T8NGDEr|y56pgk#`3{Y1~COx_76m=hW+?Zo_fW zxLwawHd%DamP{}SDv69(A;UDb$WTgT1`hEq1-hHnl@OmmBWrvEOMlTO1q!+B#=an2 zDX#F^Xpf}RA~zgBg)-q}-(IV_2i8~5c8r;3lqPnQ zAFEsisz(-hkJQ3jH5aP6N_gp>0v? z=pK(jZD_k+c`rG}N)9$!&9kW$Suov)^=o^ohIh7j*%G}C-WmYd$YN`tp%ri%bFN_? z=suPsAyK%~d)#b3{H1>L7w9iEO?#luTnLY@N~{1w3UQxDz4XDZmqKwCY>(+qv5O4Aun0;k^&>LPQ5^FWQ=e z8}XO*i%Ef;Qo|elMsU%jL1a!ORc7eXoR&MjG?Ax)WUE}!SFMu=6+%Cq1m$zYUm5Zm z(Pi9NsUpiYzbKWcqr{&i&$-!w4Xy{@x{OF|#_WFV^?YHm6DFy*iBVZ4Vou^NO4j5A zv|OLi*<7w#%1uvInp1c&`Rm@82o#NPSGwGcxO*`(7TmC;06eH1m1E5}0_C`i7dgV- zxWds_nvv+av=qnfZT5mOd@M*61d?wb&HBxe>h}#_2;VS03KYj)mUj~iSVhijIm;jE z>Yrgb)j!tS-OBcOZDPF+5QOC*g*vXcvIreOY3E|yt-q$hx4@lH zRc_xt`BqBup23OWE?N-=_3Y9Td)Tui{z_WG=GMO6msLzp<4z)u z>PeauzfQ~)b(c%-R{CC*OU8U-slK|#USy!4G4|nqc>!=n?Rd9ld*V17Uu>UDd=+bq z{>=Xk!}2{)1{Ynb(M^9bRosjpzV58W`E3#x_)=P}Qy#i;SYwvg)8(=^0ujtdG`{^8 z!=yRbZ*dUBrYa2IR!I5S-7Tu}0|+lp%qyvs(!bqRtz+0mP(Umm;kj{i4MCtm!quhA zwxcpo!guMUb0I1m%Pz9BIN#=TS+%Q)wnV(vu4W7&ode5r6E+Y3*rD7IYx%LAI7Kv? z7Aq6CP>ou+I~z>)3FbguN!%yx`sE@aZnM5TV@hjUV}?B%%EVlYz29gk7KSc)L5h~oMOw+)2GCv|Ur4j6)sSnRQ(eYK%MT`r$QBrHbEsSVs8E^rinb^XIh zf?pV8B0T|GEx;M(KZ*20bpS%(}|( zh;8~y#KjP(>wk=YCZ!uYlIhpWRV>CNUkT$N(U9~9g+fKTE*#(m9d8eEYG~{pW5!X6}`})`D!i`pT;-6z_PXb z;~Dv{qUC)VI*s$}HiQA`{7~=Vi_&;oFkJ|iAodt-j@|s9@Z&9qG|OJ`w+9}min~Q# zm}2SlH+@d~szi9({U#-Tp2@Pi@7tqQZ@3*%Ec5{CK0mG176_loftTnTV?9zWNT_P? zHeHZ6)A?$L<15vo`qnbCCC2CYBK`9Rj^l;6`Vz=b?dH*O3#(oNHHAz+MBG2LU~)26 z?cd!Ia%u1G5aHdUtVa6$n;z)010U2HT_{P$U)6^FTc6i0QZFY574(*HcVFVhzQL_A(NP!Xy!6+l(LQW|!JW}BCNvWTI z*tf5`s1dKe??@P=xTeN8NmGyL*7Sc(MC^=j3GPj^BT!)EPm8FDkMSfWqXR$pLSt?% zMBW&IpH;y=*mBpHy;7wXNHrcG9ZF?K7sPj#Y#2*#P6c*M2RN!w4{!d>>v71+J}e!b z0{jaxs^$0LCTtKBV5KvaFGU)xr)g%&W{;JcXGHfuI=S6qf2|+Az5hr8b1;W063Kr# z%SG65(TI1QckD9ZpIl z(5C8f#+n;7`U>sSUQ!d7EILTfckVp+nyUbmLhAD<M>7VwOD;8;20d(3Y>+j*g#5sS(sr7n>kOkStc%9yG>n=W~j(-mG zs%6Nr|KQJR%!nR~^>?b7j6NjG^a28-4`eFRvHk#i7F~{^#b|c2H2r6~kSHtM_*o=Z z^L}Af3}lSMrzmQ-;I_zjOp(H7!X6x^k2@!hw>~(miy#RmK!iEyi8?R(@@Czon?vyR z#wTMxAYiqaK>syL`|h0G1ct-oUE+_%U*{IG&S|b>XigX z!NGX0nXG=LlLGG{!i1IkuxG~7Ls6Y-3pG+kE!(Ex1@e*4{mq<{@>RDD9(!|9%2#XE zW|9w&3z9BYcd;G{Yn?NV4fFV)5(AaCJH78tD&t@7ELP!*K=$vg6k!xDT*rGUtIexo z>T>pax^H)`=9%OoQ&k&R_IiecdK!ZYL7wEjD9xM?0Y;Jit1(I6P8O?UjKII%GE0|D z?IMNioE96Sctg_=%Hs_5R=!auxfbf9eW>tG;h*|5VVQ(CphkHbV-_!she#dAMgbBJ zVL!Rsc_~u75L78WhqWU{42%MpZ*t4$Y+mbF`y#c@T-#6|=H`jizI}}Cjuy`$-Bgb{ ztn^r41{lGxk+)^gPsYJb(?S@H6E4qrr5<%fvJQfbX(oalmxT0a&w~7D z&C-gXat9;YI#rFCb7MZy!M>?Z1{EXICvNk8Zx$oHg?W*#pc=7q9ZcM7O+1CR7wGIY zYPq;Xkv+9ijx-)$Wg>D6HKUh^2&I1&OtoQ6npj053vq)LRnZX7V5qxc&d_@k^pOp9 z++aCHJxCl$`!dWCrZy~M_Z}PFA5`d`cj9t4K#u+0Xcs~kf&I;*be%AfCVVcdLzi!- zR7f;m2}cnnA@OF_cXi_#73{&*URC zEcZyx_+tg8Ql6Z=zI{P+UZ}A7NBO+b(T1(>g*x8vDB_pU{}cj!l2pL+E&E8e{UQz6 z2sBj2$IQT@IfOdepN*Og`UO5Jp$h$gS-S+IRB$wadOEjGi!Z=WljBJ11b1zzp(h z{=u*3x^%)~#S*M32qg%o2z`4+1h;nQw4T#KN;SoL^3ukU)m(n{Fhb)67Th|Kn^V{7 zuDz@W{P;Ikf}r)EML##-#=p9H!E&o-^QWasR^DocNulv$t@lV7%zw3?7>n{PP95Wi z)w1R0|97(+{o&v+8F7sXS#cTOSkX_)C};Y`9rSVe;KB5y<*i5$d-ExDrP;as>8Vn; z^0#^4%3b}DD+LB@^e%}*!>WCOEZ8&Wg=pv<`)l9=cpiyyScz1-rasYj zMFWey_va2GwgazXs>3 zsmJ$pO_nZ_JxB0US_DBNtot8@Aag|=78Tv-7v1sCwlss5XKQKte-(2rC$fL!$L%Qf zt&{C3E2O3Tg+%xrvDPE_hgp)3e=Q!1*JN&Tf<4jt7c^N7Cts!ury4wewvIF1JGZtp zkwc^$KHyHL)5x0s_{Nv9dzftU{rN0 zi!COLe9QJ!e(5Ol5RUbH4dz5H2!S^9R3q;eExt_QYm49349FJx#qe3IjdE?8{O1ux z+Wb!hm@%5wb72New+AWnU<6F_+p?1pja~XL$q1|DqDa{iqpP*cXpk2VLe4n`sna7( zI(xaN6@}L|GCyl zzP(#LAM(I40#!ig z{YG1r2c5N^*!ro`@k8(DSWiYQpbdGAbvCmOb83tarh7&BEKDt;c-qrFR&_yoZstwH z_xI!Kh`$$4D8KreLJVKVex{iQWrw?qHJqt&c;~Ult+!YHqx{4YflCE}~ zUm&zcRIb*~+`ZpEIPI3?hz?quKZxq$C6Ykk&ITM%8bM$r3O+QYl$7>k7=maiB znzVOX72EAc5hITS0+B-Nbo?t+ukeq>Ddv#2c*z9jzN z*_3mG#n+2_$ISN&!_ChU_AJgr>}I_7Fw@>Yd#xdDG7_YHne}tBmlo3b=FPfqq}TvT5OqPc z73*3FEEF9Z=&9H?Pce|SBJ+Qa!vRk4x=E`?2`*5hbb_F&17-r2{K`*LPY8UNB+!?| zLCD;bM<{C=&!{}55onh-($)1~d~x)DcPh@Eohe*%Dz4+3wgFdYhLNX{`Hv;|wVjy7 zgcLv{vU2q3MiBd)QZ&=JOeK;;fQr<%1*!C}Y8%;YeC2i3xk(lzKMqKfpkvWh<+sVG}VL>BhP> zyd@fCgi_7M46BHhkYg<-HBA2ZE<2uAPiLqATbN=M?;qK5RK`r!6seaI{S|Xg>}fy1L-3vh-9i7 zmVqt9KKh)sD9u+?klLVt9oYSzdx_rf-^pwQD@ytXkq)*nRxMupj^a_#Qs-U@0C5CU z6$O-hh^z2)_bKS_Gcv)|eXh53_ht`Zxv<(td26_U3m3$ARvt^0X{=S(3pTkfwz!bm z@tkBbiwHAnG&JXP(Ee6kC1hv5i1Y13Uc4?f%VHa$nQ1gSfxd(@A}?N(a;ITZ(V>?6 z4cU3py>W{ep@dX?7KRY(sQ9B{x#V?%jjPq->rUGpc+jL-+b64&oDRYMVg_q^QY_4t z%s=C|Lx#eB{-aqg3{h1c_7w*WBpTel$l@UKW34JZ_r?6DxYZ`SF>2I#W#DYbv(cA2 z%fDX>!ZRrI#12^SS-#?n^MxK3Idf+4&WE9lBC~@BEzm8f^K1)5>UrN0$JGSClNxa?8jyH&)|JXM0;2g@+z{slLVdz6Bv>&cg5I zGi)Ymqa@@p-ux zf#!()ptVJSe-XXb5z^UFNsen17PN7ONIm$TPE{Ha2{;QlMk zT%+0K#9R{YQ!gK^N)yp)3OZ_j z6q9&*p~09wD0pS_eb*|V^lvCL51MS?A@2Hg^JqQD;Bl_0>e{yL{%s-N&&bJ)EENs| z=ugdQCs&3tZj-{sz~Q}$}@HsX<)MuGN7%Su`^Dz5WeF-enY{`O8ruc_vaGdBocEf#2O z*R$&K-LxkhI3pM0n%pKz4;d`yF-FtaMCj^7r!m7|xk4@A3tF=nVZ-Qj~z%K-KFX`f-zaJZ$7*Q(l z@aLq&yEe2y6==2q{mBYnHpwY|2DmOH(Z54EV3r~-C*-QHs@uT|mP@S`8Ae*5rK#@t zPgWkM_eS;Br-W0^`)20}yXnYzALDO%?G$RGx)mQpSz)7lh!h-Q7FPrRlg z8A0F%yZhg^;#VHlq49(c6#OuY6c!6$Um^7LH4Z*IlDtYyyaduT^#95^7qpxk7Cn@x z4F8Ktjrt$(>wgvXz;`@&1+XlyRJi+R--MPBmYRt71um%n+@=dCYcuDiTCe88(yl)Q z512%~tMEUC{1g6!kP|9wRRnAHRGSZPdqrO6Os|2jrZ<*pg8 zwE0{YP5{peTa~dG;Si*P^TY<|)naw``mH_M$5!!g1Up6?P27&Z3i- z-;;UcmUz%crku^5WGv((bv|xzq!MzC0$Ppj*yK7Ez}k+xRDg~IUZH(actpiE?2@1U z(OF#~Kl6R#XK}w{{bSmEcASIHaaw|%5PuZ{@sAeh^c*H##>}5}2*R~WL8mO=&BaX| zm(uut>-_Zb)mgT1E&_v&sQ!5ZjI>d!K5#5_kBBg3g{`VN5S&bw<5b0Lkt>^m;&;>2 zfV_U$;lXS=&8@|S4J107gtq_+U;|6d%;|gDe|wjPH8Lpz=oym=ANmYq_h>{xY0KSe zo&*keBno{ZaKi%eK=h5H%*#?30pDg>@FS0u0S- z;x?fyU7=GMgS*B-zE4Y9uq4<3l+c9=m3nn3xL+Voy~!0~HN8Xghq5LQp;1d`tB{2= zI~)~_FK`pk)KhxKu9@aCT>3a!b&uK3-{+Upnbq`Qp8`qtwRN?*?wBd?9&b0)Hc^^ug@Nt22E^Tg)wW7}_5ft-99; zhl5eiE1=ia@Exv}+Lfe^zt({IRRWl2Rj8G-_i$PGe#DR^`SRiypV_;*-k)l)6YMe{ z*;a;*tKH*4)YNCQ?{m@f2UZI;ZfsA!on0&6{?3b2it8{@HX`}yHB6Rls?P_=j!++~kL82OF`AJa$a5#rRiO>CV1ih?>6w7w z0pd54z?jeTu}BZL=^qPAt2OYv`^;H z=$|lvt>t4VsnC^cn#khJ?{I9cWVL$sR1;xBEZ7zg`Him~%A{Z?BZLQUWnzx#F=Qcj zGf#@d&xb_GVp=3`EY*s=EyoS!xTXMUk*FpC*cBT>7$SAu7UbpwLjB9k`9iPJ=zbR_ zuLaP&qfKmr1%eU{w(qQo#k$)#U)B+Yc^3_G^V?MOo=BbfB`yj7n8emU)&tjv()_>F zy`vmWm*rx|DtN2UHL_r#V_6rAzKcAi?CE>`@&$+aO(U!^fVL!3Tv^iCV;s4{LM(5w z8w-NnFEoTeVn09=g`siIKR@%IF>)6x!1fjZULr5$+q@p3Q16L#NWX|i?C;MrP=69C zVu8`;+RYv4hIRkZ5%4hNmgd9Y^;z{|^zZ(GmKp1bVe8 zQ6e_Od|e#k?u>wlBQf~{uS6yjrH!-b7PKB(eeF~c&uj*mgI;^H;{^`o_11=veHFmz zl;-S{PqG>txLDQbaLJ*>st3w3c_F7>8bcY9-RtQb=S5X;KR%ocm?_fI?EgfPs8*2s zqCO5<1YOmS!ds|qWyaYN;pck1E}pZX9&bk;i*_{w3ni8y64h}cOMjhtSQ#kr`J}>c z!)WYz8uMHClW!`*I@UtzZx84kZ4nPO<$i^E zfP9w!>ynrda7;U#%jR+SVTi>VURY*ijHGZuWG)%?1I$6$qt##rk5#Dchv_*KzDOE0 z-kmg2Gw;te8%#=Q8X{qZL3ajK!NHu*vJybs=*YK#3#Us?>jQEW0%e&Cn?jUWo2(~0 z=c05`6xgczOlS518bEw8tsEYG-B`atf2~M8YC96HYjH&1T%@l(a|1|o-1#f?Vl_18 zA(ZeZ{I4VDUTA(}kNjkpJRGJtB$_O#v05K(%p*$&4wIgdq@51}WwcqDzWLo_^Y*PY z_>8ONcO^y^PvRn;yLrzv)F3x^d*96K0oqjG1b4J64Go@dYR1lEr>JVS&`%P=&`AnQ zd~Lva24>Fa;*jKOH~NI`V5FX~aJrV6T&Yh@p?IJ2 zjaL&p48EJpec17&RY_bf?zriZs4s)V>tW(9o~Ky+=i%^qil6LNBAmT#vO0qIvt77+ zP7D{0l|x>{40da-qJ{0>RZ`$jekb>vM|aAEpPX^Uz2V0wj898z{erS=?*4)BWS}ta zx8mWttpr|gQlO5TJray(S`QyqPCz*f43;0NeG3PMpdD+0(B9Ah_j~|aRj&9D>$`*Q zj&R+1F6+B}(4ExA!=(_zpsOxqinwFbKMD5d*oT}t4hf(dEspq7CuKk-0m_R=B14-vH4Wv|2 zKHqUOgym{2D2ig<(SRz$jVaEEUFx{%HNJADQ9%u50yYy{@SK{xTxN3kBc~=wMxI)h zA*aNu&bfu~P%1}P{ZIOQW;3X%TD-W}fptD<_gv&_Tkk8hjg{J>y{Cm!bJk#_k0__G z-5cz^y*?>*OC#Knf=s@E;{_XRW|bx@jqclgUuyW|;`B-G=ZO_z@m3ch0xbt39j4h$ zMNi+%cDVqN?kRZWJ@RNkhnG11cCs|AW{ytcqMs>s*%Q8It&}7%i>Rb;w-nXo2D7+e zDA~1z37)}c19WD#Khh8Q!~2s0J6~d;K_cWknS3Rr0**npPCQsnu+azKT_&f!DAagT z=Nez{Ruw7WGb;lJgqQL}#`sd$wV>IiWC#nTUV|4CA|RUP8i;6;I66t@-XQ${f6)U* ztJ%pisAu)1Z`%$hu_%lr2Zj(WlJYI)Jib-}A|68PI%AznOMvFp4u=FR`vXx%Yw%=? zj>Qry^o%Trz4|phT5uD7(l5wRJw?=1q$2K15xPoQ%H7E_%L@*&-W*+U}Zl zU`M1Xv870>4?6|R%d@9QClDG)8Wg%7eMQ$dD;m*{h6=a-B!qOCu4BLlwmRXw+RKKVro9Q%f=GF*t1 zF*()#IO@1b>fK{!#xbxFh?SV&FWkU0Wmkh_b`U~td*!?}dxd2F7+|D=AxH#sP>G>Q z3ePnDQ<%t1v6H~3I6H~yssj~6;;Ey}^}0Ad7bSr%v}{U*nAn_$zUi)d4P5ay&1o@$W$O}F?7CWSbFN90p8JvOdujGH{mhA57^bWmSgaH= z6S}J@Lil{05RBLnCOqT2c`ptEV^D}{Th9!UG*8JS$e=^z+in@J&bV~tixu-Qvr1N2 zMGLvDHrKjI(T!EFJgag-c`!(@6Qoygl3-bRxGHX5h^=)97C@Xqo#W<)WTzbu7lMAe7^|-@|`&to8UyDTKgokbO2sww%<3=@P`uWTFpJc^^3|S1u{Js1Mq8 z=q$fIU1I(uSF_2f&JxXEatN;JojN$jIKO6c3$&gP?_%YJEWH&=eF*Kw{Vnz13<$h` zBoc8WDJM5qa8&o?>|^3WoN79-pFq5lwi4ZgTR)AIr4=orl^#H&t{ zMk6^MZF-tq;`MwG!OD^Vc{e_Yy~Ek@)PD5ufKj8$cHGp~yhb(!%nAR-aeYRglv)#? z(3K9>{G{u6EnzFu(;k!`;_l+kKKA!2Z0s0TqoOC!1P?@-9-8Kk9kD~_x)+0a{5^}g zIye<6il38BNs~JB_GQx^2nWw898{jU4u(jDtMMf)qvKEqqHSK~zrmzHv)~A2V&0(8 zPj87GgN4r7V>5NyUkzMt>e~?}!Ymk%ulVK@KjHQ^<+5g488ihrajF=9 zl^3cZIJEntm_WqWc8HrkSDz(h_9q=NGFno{etupg=a~*tTEveEi(@lel%za$b~5*< zYRzLt<^u0GIPGu5Y;8Zypr2AjIF0k`yjAm)gnLGb8!qMTzlFeJV`X{v1@?wmfPV)M zayyGRjquMHo`geeAcLu2&ixkpi+KLBz3s&<=w8hFnA`)PhPdg;(F0%{pM(Yk56H+w z2g&`Hu`cocqTKMd7H%v8U9W>Uey$QG%hkvxD6|eWk~dsjouV@T z*4jjEtwzQ*Kwevr02y{Z^b+`%=oiEK>2zkYtWpCd@2rfE25YK!LI*vKL|>#?=kR2BH=$ z|1a6c|GjMe#SfcVAfZeN{A<}nMyBbA4YW|&X`6g2?ibay;ZxZZEZO5#oBWo8^i%+7 zXPn_1bj`^1MW<)X`*nq!k*k7%ml%f!yi3^bYc_276VIsm-FMDp7U64p5chv|5*K*C z<4;4!>ju4)b=J;(%-bhfrm+Jq>e1?G@V5lV9On3-JNtjQblip%?ZA@tCh;8X68iq+ z!gtV?zgnoMsGfyh&PMhUtZv#nnC{S3wRLCOt@CG&4hEuo0nj4sG=8gm0kgO|L+0}J zptES}HnM7UhL&iYOISuVbrALEa(W;PcCG@7{pV6ktCMU4f#+^5^80LQy-vo zqloXNc29%bUdrDNW))zZ-NXx#1n0e6^e=6HX+*e?y0@~EVU#j#LFa{F4!fjaHLskv z(=%~fO#Mh)k*GVzuA7t0;ZOU$o8UtW$)5?ZL!t!ALIjZDjm;yHJvLtE5x!W1aq#IN z`CD+&mQ(*+{cG|w7+%ZbXV@QgZn1LX+>N>)@z-G@2lyJ6Hn3L5Y?Zuz4ZetxtwZgqPUw!=ncNy@QwaKtcc&k zEeLfH+*NL|A`G2Fx2_3qC_f5(-@u*bEvT+#gRNp)p&TGe&5f)Ib(ghvwPMoM$e-C~ z{~ui+B8H%^tbG{Anrm3JVndY!xVwLv%N2(e=|{$L)k0BJ;c${_RnDO68PV<183>?O zIRj0YK|AoTA^$p-BI_N%STNbVDEaGD&`0sLUe&9;!Z@_L5n@oT9Sb>N7*S3x4A&8H z{E^fXA1}UKTp4diHH2EC#-8v`@*{B;R}s~DUfz{i#4CW(dTd<68YQZx;*9QkgzsVF zTSXb;G0m?gNLu-AM`mzstu%2ddHv2|6MJJ%7$s&BjqGo0%3H^-d$A`Be~Uy$ zP2*6^S4oc9!R6y>BUf1eOUi{uXw}xovegiOW<3aj%`rRJigEv)ZnoQG2%Wq_ekvoz zYM?g`Bv5?2*6w8}I#MPy7$$nZ-xMgFob2Unu+GS=-?_TaaH99}H4{Y1F@Q>gwq&;Y zQhC;ErZn=Zmxqv>q2c4Z9I=1|)km9j`;~Ts*^53i9aG&_Go%hVo?lvA*)?5im{+F= zu};f*1_My50BeI5mxi71S3aYj!@FPU9L8K6M@#<#X`_eFoo+|*XIgbxupyq8Rdo|~3sqV~_#Awo=_n@^RSg%mtxvJwM)39@ zopOuue+^1M7E5uU&Y0%1nefn?8Yg_FxLO_8|B0bvJHT59)jIFUxuEXPrmz7lY`{tE zfs>*{%LDy_w!PG)?nHhzmqxJ+6(;L7tV0)H0_<1n4WzAuaIZJfff2l~)BI8f;Zw2~ zF!C>%d1SPhvoa>Jrt7V@F8h%TkK2)Uca-RX>?RwrgOnu;@wYf6>mm9|m@`MBMAFT` zdoEv|m#figaTb{?dY|QO@6&q}t=+?YVEEok!ykpX(1u&m*Tkh=J@!};x;%uzyIRj- z;brk_BF~K*7F{e_-JV@Vwl60RtlxkLQ}TaRH1N1AHKzGQ1q0uyeHJN&2?a>HwU#E}J?u$mpc9q3ey zR>UFpQQ*4gm>&2&K~IAgfLyKBXt5#^`h(=dp<$UriJJ>Kqe0UZTGCmB;=WuhdbN8jq{PZ-OwaW>aXhhRz@eahFBKRF?{D zVr|t?L>$?XBs@yBE&rf6Qs!H8d&5n0X&yaDka_!Y_A@h4An~D7)pyxD5*U3qvb^G? z8(4mXzaQ>s2v1TiAFuXm@zK;fgObANhPsdAZbdq0}Ab0yz0HQ8ChAQZ#D&$ z_bFB=Y+ND&3j8a5C=Bg7aV?bMzwFN>(u$}TE`h|g8|K;9+UYs=*f<=}#EV~XM&FNn zn3_Ixmda1Q99;VD_E5d8K@Vlcr%klZ68p9ZVPRE`_~w_3 z-WZ)()3MY^9oL-4sB6=SqyrNDv%~3}t!0DB*SWIUnS5E6#TtSGsV0l*11tHQNjb>+ z0fSslP8a>YDdWcS!D1jkK|WMnMwsG4+_XGU3|(H#DODS<$iSVInTpX-Joz^H>unH9 z!(x}YKYR2z(I_9%;8t4A2OLMsIwp2KhAq2LwZfD67FjZ@;3d8ddX)2T8BG{1AuT`1 zH@ySVen8e+Bq)KrQU;P$z1rI^ou7JGwi`<58O!ScXbD2=VM7kqYxsWo^eR3xlMVsK zg1#!reu9|v%v{KA(&lD9+uBUDfT$7D1ZnBJwO+MRK)lavNgbs^ehI)&$m2CeEJq;X zhBXVx(DGig(;>ydirrSo3oc7%6zG|Y`r@0-dZm^oN_C>Yz@KQrc2>t4 z56aW~HgjcFUNP*htPI0u7gizStTyUDg~8|P|8(SY%JVRx0SVnFEI@TwsgFO%@z*Vw z4%2rV*Of~;fBhPR0+YgRfqJxD(6&25j&I{kTM;UK{R@;uzI68$)W8PM#x)^}BOMbW zU78mbMxT~&5zhJub!s%Vf$S)t2;z!iP}}dKze?tQ@rmkQbhlbNU2T-9v;FCFcF0?! zINp-{qq<$j9G+|t`A#b{Se6i4{>kcmi;BR?o1L$FvB4ELvf!Gr=Z=FzajXT0mu8e& z^CRy2Nz?H`C z4&(ndRp3Pa9Q7fj0ZKBR-3-sx;=>z5{Sowi7{k@Sv!XLThKgRW4VhFCTzsSmd*}}Z z3_qEj8kHQvIpVQ0lgIzlIHZ9QaCl-Jo9*q&gM%g@mMRnazg!}52TDY>huqwEHRdT| zD#J}QVF^C_*Lx!{+dOm9 z;jY<3EM1|9dnf`KzsH;D7G`{evGzRyH{l3NQjPwA!0z2`My>0Flgm*blVXYh17SF` zqUi3RJt4-MG<&Gw%owc$h5xx~_-M>0R^d~FcDZQ#WhNAeRq;?l1FcTH^*f`ob5@B6KDiNV*j?8NQ$L3f0<_(8@E&;#so#4|BfLGakW3i$7)V5B+K1Fy`K$ z3_j^G-<)yBZQbDr|0t1-52`IL9E!(@p(JJw7n;(P(FAxI6EB1Xx8nQXIV)!IM65{} z83=jD@1&XZ@VKrsNUB1Cl>Onc=3hovAq*O)AC}vEv&fc_-`Rilsy+2=X}sPt;v*&B zS*f1n6%0tLLZyf8(BkOabrirS$Fq*k=0~V18ef+)A@3pyrOtKZDOLOS5CL+bi3CzU$*PcnI{!f0ZxrU zt$7Rknb1!EfFDl!FZ9qDe)ECBKH$rfEVa~tYscbbqD;!-QMAocM z5rJ=SHqM7R%@cdB57o;ZWcxI|4Q$&G6aD$pFxtonS^fz^H{C#!6FV=#M*FW3CoMl< zNrA~}^n^r$~g0fzDc@eaS)6OO2K4Uv0)w}A1%St_jH$w!|Ug%a|U=}(WjRI z(#eH@y83Lf=a)a#O|dr!E;jS)Ao2Q?-=dnzU;bG7r0V-gkV_LWG7?YB}GZQDLiVPPd1`C*xF68FT^7g5|Qeak%e?})DG0b1B& zCRd|C>Cu4ytH8oi6;1~~G}b8|3SAyAL-THjprJe+-}_GfYF4StG#UG~1a@}qT%FeL z)$=WWcSLapD4SYxYrZsJt-WBlpq!rsl`tzA8y844Vaf*63L257H9wd7*0Rx#pz2%f zC47AN`wuyP(m)qBv4Ko{Z)s8-N%|ajUm1;5H&O5qkn?Ww$#+OrHD1;L=CgHV&B7gD z12nVg;iz_?WoU+8A@+l17K7xfm`1_0Ji++x+ke&!WXFosA^THH`5J29$Hub)rN!nx zJln4Q(C2R1YTw6e)@`pCN zL({^Gk9QfD@gI78b_PP8CC#MMN9sfb8Ih8)qOD*=MwCkEQZDbDN$~zoVXG!M?;cKJ*ftu(!dZUx5KdDOy8d1E510XK4 zo-t57bW@F*`dJ4}qY{gXr-W&EB~`2QN{OWTxAqiwje1?y^Dr-wZ&Ct%W)x)yI+5nmQNLe4N^!*39Vzwwr1;cpX z|Hal<$2Ix3?bC<|D0~400fUfMy33$JMY_8~YSb7Zpdg~8v~-8$s8P}~Qbv#N4Wwag z``teAeV+ICzWi~2J|o8N>prjZI^#Hx!#nLu#lbZ|%;llp`6(ge?c0x%c=ab~vSJ=< ze|IXd73w7$y6Tl9GWoc!Y>@lxUS|ejtRJbtmSQ*`KU18#A7P;;=2d*>jTn{(377U6 zgQ#>hvh#P#Y0T;JDFqj_+s5IjP++9?C{Es5P-d;th?>L*%v%W)o<1=3jz~|bEY)sG zr&e7R8qP|-yB=fQBrLUZBz&XpU7A$QkbJ`ljiES>OPcpp1@G7B8l=rhVOPXk6=Cyk z$_$di{jH&1$1ow|fiBwXR8S8#4~Fk{K>wif$LsJ=cgKq|1>DT*t;rDec!AiAJnpJE zS;kfu!0kec*oZzQu@cdV1L`27m)aTUnL&6gP4m994#o7uUtdp5oh>WRTjabQUwD#= z9HSMbegUAryFgkyvl08HzDVqu05DDNWy8MaQ23LFP@D0hR)+&eD1syx?Ft0Bb(3dU zKVd)&iuDQSMY69P!oG*gU-8@t6%M$4FX!HK#HZ{qXClq+H-{3}w0oz{-*-D`Y?|V# z*n$KA_K5K^`0&=)!v!20^pP-qA<9blFJ8SjFK$E)=-TWwKio4^PJFSUMAIE79~P2+ zP=WRmCS~&ACf4a!bYN_7rxBXE75x|YjSA+Q9@pfp#(r?3GniyQZJ|9g*Fn2ePkqrp z9*7fhj0Rp9^^YqKD%T5ElzwNP5=2FQWzFnww2wS?}ArYKLzQD;a5W{tnW6j$LQ z=Vjo_+@B|w88a21e8KA!V^P>D-#{IygAi1` ze8&An`Sz?Y>Pe z`oR40nZY$X7S(vKTaro&n08W2LV@#XpxI}oj?yI+!SC+x{(8;!V>$3)E- zjr5EPicg)|V?uaQ;~~-j(?_3NJAjL-;4D6&-iPdm-n{?8n|vf785jcqgW7PW2U<;L zHw(Q`b0zg0tC_|k=Uz=)J1s-ay}mDOtFtRiCE`-iTT=_S=PKSRX*Y?%FmY8+Qc}?$ z3o)t_Bx-HJre8#zf1G6(86ppg^%(EmYnSXz`3jfkBZAXTRf{ee&~6fUF3hRVOT2wT zANrwzrahkWo(`(xyR6Rgl^98J4qLG$>fc-dq@Nb>qsqLbapxJ}2&IVYN-=)#%yJ3z zYV8D-57yUtP2X(xcGw$#7vavIh-Q%+mI=Nzc;e6JWeAEBcNDf^7 zKBN7ua+YDWnZSzdN4}2~0{45yIa9QTut~aahQ>FGX@nHCOxn1qHgN)tEHTGll5Wk2 zs=1BF(Ql652w&=pMoPSj2sqj4xNBh_`$F^Z6y-=Z6%hQYAQrXzgkve=!~*%1n@*#? zn}kDjTf3vk$QvECZ@cj`m4oGK;ETvIxwt0BxXS~KdSooK*ucRpHp!Tn6R?~ztp~eb zD|Tikf+JGCCV%v9drUxdzk= zf+>D*kY-si>jQU5%Nuk^{x4> z59*EyEw6+YO1{5E1n}yH-zm3?7dK}O%CHzqab=1Px~QQ)>|Dx`J1VCr`GJtC(|7F3 zJO~sfd7WaDh9#Ba5j2rE@O;BZI$V?gsKaQW8NAxOXIMwZ};hea(u|7h9(bHQ-*s$Nbyeoj-dN(gu|Hh6kORi#!CQK+Pa0l0kMXF)+sC*58tjx`ph}&Xc);#jo7UHW_7pt*6?sQcEqnjn zzF47kmiCP>b`gX2^@Ee%2=&NWgFOwxsL#g~ea~Oot0c?CPKIv_z_{k_jltY6_TfWl zaJ)PF@@?q*#RgQF!Lh_H3_bs9kC~+VFI!dBYy3n#WCx@98@ON0-@Z5AO>-0%r2tRJ zF2=2QPcAHtshRw~^Tq*t_ga6;y%92pR@#wYDKc?MDG88H8?Jt7>9x|=FQVmif!tW_ zn>L`!;?8moj=8+d+paYpB}tzESF@Nx7DjGFcrL$Wlzjygc+f1R_nI!qZr5~#@5!~& z%Y9?o?=Grhs`mRWB{FMALm^*0*Xy{%Ozx%11~SdM6IFCGXEqOJwV(P;@`_PI`(F&4 z=RY3km2UnJ@i`OVr#}SO&^nsz-M@H23#U%t)C?JkK0ZWbLsX04Q?j#&jA!Y>rc>o2 zT#_#s!R1isTdQsA<-V%U5siM>&9kJPUu+4<{Kgiy_#VO)W3D|Drf5#O8L~B5lGp2% z_HFG$o#RAE##Sy&wj4x7!d>SF#uJMjo# zmJdTpDhb*oW1ETIh%Of9!rhSIs9EB~9oCOH?wa7fFUK&@1c#bH#q~GIgQM4~;Fl4sjyfC~58N5b9(EcYs>egi87EdVU$BZK zU2uq?gTA=`Tq{UPbAXJHByZS5;##eDJK|T|A6`6$Nz@mBp3dQU9cKNU8|Pre{#n6J z8KJ>Gy-L=xF=>mgUw)VS{-Q;dlFjO>WqhBNF4!+AP`FP@dt))wCtuP$O@ZTGP}r3% zOEJ}&sj|~=&8#baG?&2D#&87pjNrWC>mhtu70iR8@ZCVmK5Tz^_0z2qS4FG%!Y5Vc z?E)hBCXZ!zw2YN=6gc?)@rC00=;nks+ku6?8dWFuAWoI704|rkkub9jj7vSP4bms& zBy*})lQA~;)aMl}vMe>IA23b&CD(50`hlFz{M~`n1|d1ZHCf{Kidyhj#bfkQH^$A1 zY9}I7obGZ954m%Y+OjVY`(2i+*LRT|Ny)+{LKT;qW&e!YbsqhsVKq_u-H7puGux*7 z;!5R1;X1YPD@nY?h0*5?CQeEu4V(GAb!vlrZ;wj8zrl7n_Wcn}+A`d{rRLp2#GjOL zL9wO&DQ;G`z~QNb)5G5s9~;Z=a@H&ym0su2wMJGlX7fj+`Hs*);J(3&?SE~3vmn17 zQn<5c@FZNK*h(>#itfv<_9Z9x76O&f7p*q?Y^%k?obg-1gx85ctTwc;)5WGTQFXmP z$$8xMm)qCe8xYW{rQGw{1mAN4+u>+E!>NpS8@z#)i>O97U)UIyN}Pg~v(G}{JC%l{ z@^hK7J+9&Tcn^!)Vm~L?y}XA$Rrq~Z?C8-m-3yXX&Wl?VDVcawqD+Lg{4C__zk&Gg z@zsBJsLum$^#i=6Zz|tVJI{*Sob32`3eetf+?wRmEmbJFKE&|mu~Ca}+ucyfAbUTX zYZ9B9=~##$kx4&0w?exNJi^Od^wXC_EBC+NI+_!J?yR0RPy~76PHo!a9Ul;f_|?;q^cl)3F`6(P{SsuAEysUV z$1x7fbl}3&u0MN!_KoW$wPxt+>!BMRdmTMqk;M!}d*?Jq;>w@)nhidk7;`+{#`Bk+ zvQAtcz=Rc}!peBKLSH zTjS@LqTC6V<``{~L;k>;$@m9~4A~^be z(!^w)h5YTgw5VG=j5#Ci?}KL^L_Pcg{!E7_2gAUDPtqEvKj16cX$~@K?FJs?wV&T$ zn)y&C>$^8?*jEw-E;~jgb3HDZ4=Y@Ia!vd8P29Yw;<3xv@^S~;B`#W0vc6chhaVK>CMJbM?Oc(T!SP$$04pDT7k4#7NX{D2 z9u6;u2-a-TU}xI_)cBUT>-B%lBmA3}_RmY)TN1ToK7otFwXkb}AsLcjBl_=K`qCyl zNxSanOST@^EttTNAp*LQcH{cv$zbd07V&2dE(?_yclQE8n251rEu$h8RbP35dKugF)J;$`sX`NFxK-hFCPRhwvvvO>b3Z~&TlX&cWrWOqVWlx zH$Cv|mxV6>BIW&iEjsdI6<)ri<~D)e``Om*dAr~*#bU!rCXdc1{Bk8p!lK;7&JqSg zRw9*-UlP1#`a)@zcdHXmqV7s~^mBcS3^}5Rk#zhA;Fy(_$aS`r5141kU{K)ywmrdC z$uQd8k`R?5OaTqP2e4H6V?qM`3eqyzIEmO%jscx5n)!^?j+(i{GUfDAM)pigrk~Y{`U_<6qjDT0Ny$7a6i_ubSNDK> zencRY@_+y5d%l0{V{gULQ~3X|5AdJ=j6BoOi2c9qgDI38AOP))69iNzQh=ez4~*iK zcV+xks9B{RA*zt?X~heNv*ok%wNtd>xwHjR1We-rfI+``;AuZFVhb*$xpwVyM~4Ch zC1nDj^>F@iRVJ0s@b&hY8=GpjT$@J5aVEg9IO%_pgXYr8Frej7`wJxmaPqM7&u838 z=GHOFa+`b!yygy<+Q@a1?`Fie7RjAP2CK7n<_w)H5i(n%%Dc?d?H3Fb;0|B$vM{R-ayMjBA)1a~3 z=lNhvLjYtK;r{spT{~uEv8?II|1hHO9~3kwpaA`2zq)3+3ik4ZU29$m@13ey!P&O>UhB#3cVnr3dkiV9vo@y z%T$__d~0o=DuO?U*Xxomu5pa%R_A;=hF-aME5h1|7lt8Wl6%qJXMyJNI4wi z*&h&o`_)Bsklp?${8>hQ=advKucj~R@+q~#y-|GtG9viqr)0Ro zja&VOIr%n%#zZA2RNkAOtK;;B_+gQ=-w?|p^LDw{C9g@nh2PpNhLIj_b3MdUHu{*K zeSYx#`bEH%!5NVj(CYm7*Dmo#T2pO_W*} z356}SNG2!#AVC`Ue)%c8@u^FV{{+JwDVKMDmObd8lVBiITEDJ#uE{#(#_e;(x>aE- zje(-+2TK_v4{M^?h4jo;C%xvJ%_ z_w<1VXnP8LQATbR&5jdx{Q%-YR8Cy0i%W{+?!@R{?CpwGE8&ZGXZ*ZC6 zc^dfxC$3beafjov?YCN$U%hz)y{kEW>qCNl{wC5OTtP& zAojDHSC@|q;WqzSw`^+g;)s#8xOKARwQABJJ^QQpXEnaV0g|4_UzLOLjzj`LBk^^E zi{sZbm7TW}?8t_WpS=Kg{KDpiT{uZ?@BH~%mQT;>ej zO&4**Da3#Hu3z?bGm%r@S2bOlT-BYbi6`mH569Po!$SxgwKsJ*>14zRMPCn zt>5igdEdCLQOT08nf{{Ev<3a$YzfPknkr7V=>J3ufd7T6Ye@#4X6!}TaSluu140v4 z7KoF}lii!Fa#Y!VB?b3jV=G-z1GDwJG9nU&04dUcqa|*WJlmP1)c460iVL8<;^E&Yu>s>!eGU@&?prSnjF)8MltM&H^^?5r9R(ZLy-Ao!$BPi^ z7pU2a=kyL7$cE6$n*}}JI<1(i@t*N)OlSp2XRSOlG~ZgzhlEH7j1t?-HMpMNlX9}Q z3%0%m5MKG*a~$NH&sP<`=`ls_#>enG&Q7#SBOx&Fk^WVsRaKzt!C z`rtP_j(re6-9pn*JXlV%%0;RKd{Mlk9?viKE{Pt0wr8umSJ}K<*<9IH?uuG4_2JL1 zY)h%POrai^&WYj?eMf6_j~ywwZ^J1*z@GY!fO}*?)NAt;(;|Fd7$3NJ)=h)-NdV~d zh%Zj(zO2aNu7;2hyu9sLZjW1yXv#&Ab5$~Wp+(hO!`os^1nYs1b=z#&55A^B>5~s7 z5O{%sK@X#c*51)!7^l~OO4)h@BQBvxRongi_;T0XsXd5sLsk!Ka`r35e2C`4O@o&& zgm+sq%2U=X_~JQLfs?xDp_2bWXVikfsN1|jxFj<6i?+<~IzSeHcjUq>hippu8(5$X zEoIIY+*w&ru_jtQ@@$a+UdlZpiP0#@Gv7LoY70Ca4BUr2(l-Yeq$jr1K;xOti5+*9 zJ(jy7tg68HgOv99CD@Ha`ORB|BsUcZ{#-h0>K;qtTWdK?-5+Ccm~4}>4@Ecbe5I>d zXLlwm$g`9LcU`dhq>(U&leyxxjtbczSHJ3W{dTH5XW4Db8*+c!W-;&bxDqf{ zu{B?w?e9LCaujCP>#>8}oMU-6_2WI2*f7t0dFtT(r({jan5Thi z)riAr`ms{_ev?U6dIbJq`!5(0hqbbdQM1I9mSAS@biwwfe=b()+x5!LBsKrK*hE^I z#mVlBalY?+g$_Cj^lQ|8D5pisB>LImWUdI*=4Mo*1dgF$%*)L^PT7sJiW4*O=;z%fiI>Zphsh+sjgfm6`S!;%{v65WoxJHiy&2 z6S%U2e#&Obxkk#I9M`(ZBzE+*Qw7$Id}Rwd_>{gzvs5|7DWoms81iff3B}#l!ACYO zXF=vSONU*qdYN%NV9d$)v2We&cx-&)@sZI_? z`xb=#l`6Z4pz&E=ZxO4|)lW%4sk579Hid~K`0E6DwkXz7Gi3H4k#+9)j_?8$->0i@ zCh)ZW!$rkqTSvAR5!3=Po_xmSM=Y^x56soM4PQLyYhMqGyC4QE+a&m+z)GBGgRO?z zX21v1WF{ZyG9n+ddF@^Z0TFOfNMd;Z{#wcrTvA8OO)cq(b>Gk{{`8jgMUyY-gCxy~ zW*ju! za~HCHF`lokJQH8@!fJ;@MI(s+Y(v|$K}}C1O<+M#wh#ND`VHjb$F|UJo;y+c-GF^0 zdqXgbp*-ZfH11PT7T@wIM+gS>g}(G?JgOxMe(|=aPcJl zD1G<}@iRlPR-UD#a~s_O3n)=ut=#z@R6mzZ>ev8hXp`ekuv3XsA8DX09kL&arVzH- z=xbta__7mV|NE_d_JiR7mXumeP{D%W5dDt$=FxsQ=tpX8ovciyfayth12#8!?Mt8S zJhaPHrtC@dKdc70yipAuC-Km0J3hCjQTJ(6gp*M+0qKCC*GW5|2k9VhF~p(n{$fa8 zrXbWvm};t=K5OI-7E$+)Dl5p3Zc5;;hXe!J{1v$i; z()ID)DAD-I>d;;BeIC@=Oxk!|C$rql(U*tqh?e(|$&sITJi92c>+$L@rft5LR_)d? z4H?0!?;8;4r+J;4#r?hnoz>V5Eu5~_?Vly{8>s*qJ@cEA&S_(nmH`3H>5yVzi}9l6 z1+m6fPQ6g9VZWH&o`LK2+O&+)Bp)2-8`nbd5Z>5O@sQfx!;N(Jma}lf{Z}Bef4d%n z&{m>T@yn*otQ^fB?#bV+xm0*vmD-FO;V{9{`jufY-aYP=s;a=9fdnd8B&nwec*O;~mHC#L$*Rp+ zXulnPWuR*Ugf9zwY{BP>LiIXs%;i-C5Mk=6$jfAY*v>=o1hxVOzpWe$fYHn9sgws_ zdIO&f6JuHlCGD!aNJ((VB(RG(E|or82<%_Hclu(S>$K2N7@Va6#?;EB?|5G-IEt^4 zj7W*iEtZHU_GksiV zgnH^_M5lhE%w7YUNP;Tks-PP7aNID!Lm;!Ue1OWb#y#a!9)w5;J?r2;TX-aD+GC2o z*wtu+;0I7@nhDvg{;z607b$TSGpm97Z58w%eSG*d1ON#Gh4$NvwpXie<|lPC^iSdN zana*f;Tp&)KIy5)GxfG*SH}mG{ycc2>}hqWY*4g&(_N2153XYQ3fhCdC^HY8y(oVX zU7EQpW)sQk*ljONL{YnmsFY8cvF)5VD~+*D2NG8dKAYL6MDB=L5td79Wj3rd-Vs{u zuJW=YgFw$$V%BPg1jbTl4&l=KLzvmt8^nK{>_0uN$Da+;PyjGm_p=z8uDSbnwD^=6 z)ueaBChqkkLFvN~RDBAEnY)2;2{>Wl?WXLaZrCUJqhq@9XBdjPT<|ySdKIj&r!8~@ zI7k?;1%d_b=412c>I1e^+l4SAu0?4Mr*xogc+i*E{-Q)oI48|*k!6H+{K8p zO=p;mIPbqU2n}8?Ty~?1L1vQB1d|t+OwQVhU>S^)$MH>lECE5+H`4Prf6LCZyA$X$ z5m)`z(U#?%I6*uAm;HBnZ=Os~q9ZN;d;B?ok5>tH|M zPNo<8sq#KCdDdPQuwT^EpPI0J?Niw%b=p}7`TpG~C%^jyVZMnW$gM(4qoLA$(`~Kw z;X9c%wO=>xKb4G5Zfj9R+_Hy0pAdzn7AnqtI!Ukp(3?EI9yY-tm6J>ffwk{T&a`xc z>}_1ESnha^m~-$wc%3fy^RuB5s=;@INT1thPx5JxQY{}?-A&C*fX?jn1a))Ipq!{u z5hGTaI;2DlWi=wSC(MBPhwFDZ)~D-r7%rWy^T$CK3?3nhoyi$HH1SL7ps32s7*b`7 zFY2wORf0ff{`2fgw0NJOb}Qdp#f%2am9FEdx-DqdH}zzoF&=hbmx*Q0>=Wy_c4^<# zWI>JX6{UL%=j{cxEH-2?Mjj!u8!VeOp(Q;eT(ndy7&1`}M>(h4n=9|aE}eahKMrm5 zuFoN=$=${`coi48uJ(IBe{YqtwjDE;Muy7EXYGz%ox{hmR?=~gD;}9}B-CHJSBVYZK|({1 zu@gPB8TJ`A&XM+*+6`q&W-=3mp1y*Bn zjNwTaFWT8mYH#Xk4IRn3v4q1Q5&3qS*gz9}W#d7=G|-hXO@H1xrCHN1F5mB{H`bzI zx%!;cW6`IaE}-mvt#x&}QJmz)?d^p*MB~@`U`zTau|ENpngu+gX>nL(^+oL3%x0G7 z#Nw~?2^h;oCNm8~N76<{8=_mR)vS!fwc288Za+;Eb38G!UONJ-ekD0cIA3j9)K9N} zPBdgOoawtW`3l{3<_6+hB&ZZ2!x6xF9#lbBoW76P-}tx7ARvmMgc4}+E{T!wEEzeF zn&@LCa+cqzBlkM5`|f?Hf43P}7eQ{aFYmn$<(3y<+_CZ=lv+3 zEEl%Pl~p>Ch@Xk(5EfKg(%$&G1ordT4^j0ezI)IPBVWZm8i%JRXN-(r2UJq8%?Iov zZ4WF^TQAM4Q=GjX1A<+A38(<^ah-H)m+>Qmg{NOX`lG3+4E4zE#P??m)461Z+Fb1* zrsv(MJGYt|4SLVe0}-40A4Nl|ifhjT_BBi#C%p30%jJl@&HGFCzGkFeVjM33b zq%O+JZ<)Y4zHAD*s=H^uoo@51hDOdWJMeI~c{iw2BXiu}qs0{*Vb}Ot7i)Jbs}js}wp_OmAfLXM_75L$Wz-ogZ-BL5gNJa zUykx7w!_!9I;}7emy}e*U6XuyRj6fX`{Djb+-XoXZW1nG*xDuf|+n%ad z%Q6a3nH`HL&b%qAFt2{b2?Gkhs8Nht{PYhJRaQwO2Ha!4a<;l<8syIS#alr~QNZpe zf@fA}ormE3bMsC9aZQ$e4dZ7cjJr8DL#=`kIGxO_CqoAqJIqLXi#11c!exTF{i znMF`BnaA6cok0PrdKl!Md#y-Am7I~K9hcmGSs11!R=4a{L6U=NB^y+QCGHn)ZI*_4 zseA*1t>we@oCTkpd_bs*u?r@Ds0y+u8{|I2TxP4YO1_ZoJ(po^?lGPCEw~Yo5wu&6 zKCv8j1+o#kMmKoXn+4F*ZND7?*{;WiQUUQlvB)^L;A*xu(U3p|VhLl=WL4gpDrz#5 z@b7BMJ*a{q;A<<4p@+vh+R%fam#i&^rOqZ0chG!rnK#Ogt^Bz$A5=%L3Xa;H?qYJj z-|049b3J#e!q^c4!+Q?_b7i_^P~3WN%=MsoqKn|^C=9S=`ZU333p8tU?#g7zkIHEP zA_FJKYV9`SF~mfoGCaY0OtEF@o5H7%(4CNUiH4XkQjSES6YN@v_{o z_Qf~|JvlN!RiKqQ>xPNY?O5z)AV0fDub{Y zkU7-lSvxh(`7>7t<+#hLuD&Ti+(6CX3_l)0nC?&U?->P@5h#Cz{)}+Now)X>3%1oL z$R7ek6-g<$BlkvA2!Xy4F_A=H|KA(oo=xcc_XioAkHj|t(^1-Ue}So)`BlGe<8q3v zjBrn;yeQlZGTvi7j=~`~41FKFUTV08c@{u9AMHBRC7Qn(BdF9Z(;J6@rFF(=$eB$s zTQFLsx1c`JU$~V9*Evq0EGGA!icRikg>c}hi($(t$lCVN(@=p4H@011EJB5TMi-YobPhd7Q z0yrw9-$&7$=~7&7u0y;rJu^c}B1X1KAO38wcICT6m~$=HIj%KFkojzK$TmP;eZ)mF~ZIW#C|4OG&xp zvN$BzzC|Rs|918`oF)4DQf2FkkfiPO%f^_qdNylkZo8C*CCl#LeP@a3?x&*Nh6V(T z<@oPF#=cGE7}w<5in~ldJQ`_-{sxvslW+J>$+dMDU^ZKzVZybg0|vns(nLIT@fPFS z=pbr~cunpB#M)~-`z~Ifg%cVC+j|fO(8_)bzCA0lk5ji+AlOW@z5DOAcx2Xrdm(?8 z0Tc`iV}*S8o=NT$Cy(c~G<@8E;eYMFtEl1MG`7URXJMN@;h0>5Y{y5N3_e~#>RQ=y zyf-^UN3TOi%B<)TRaI5TP4Vv=?wf|+q@1C4@bbcSqR*vwiu2~9K91$}opZ&A%3HBb z?Kz)2P88BZ%LJ?MJuF~201gVT#|)p6PnIt4tCv5No^p-PX2WQ!r0)kTZ!>lHAZ{8H zL(^G3!9V=|=3p3Fm!P~V%tzMy>7JXl*yz+__~>G*P$ zbbJkZ8s8)jWc(c^uk0(%jj9befw~*UJu%J5HTf!2uLGQ|^SglFgW*8{F&98VSTFh) zFMG(u%b##(xBI$|`%XGSC!)iR%j4=**N!WeIoB|j*kQx-RHH3ekY<4&?^3BFGA8`w zcCItRv+wOaVfF5=U$b$GZXZTDBg#GDUB8>MLPmid<%@c>EAsh<-!x3r!Q^1!k^U`* z(0pAIo?2ks(HO`&OiW(O4h&9nF*EQZgsaUfBo(s*k25DPN!4xF-Gz@bEzvr=yUV#T z0^W206(Gw+H3`^r%`M`|zfa|FPS+AeLTcObWiY(G8Z|B+r!1nNPAaEugBalPbsrfqPLp zquSEyg32Sp`4_IiNO-sO^kyEbY;%n6!9pN1+^8aDE(mr41}&3rI_nxjH2{%l?FbW~ z^~%m&5(;3rr**OZjgEh!_A}h7+qSoca^v873VtlJMfS;&v0KvsOPs7AUx@LSnl6?y z-J7!(y9BE5K!j5RA~1B73_hOiQqf!u=PL zyL7l^<)HgHEJbZvxRa~e*H7i+5*vsBznZr;rp#6|++_EX4YIauk)d)xL(J( z>w1T|wW@pk_RS8-7s3BZ{t1ZgQ%)ndHVxT4n8k*s-_qqLk$Eh%uoNK)j3pzfFG1xo zzm7-5G3isdk6t?7^I1@GWIVd5g4QAIkOW}2D!DGE=;&GwC}JBwc<{jVh=u`%T5Owh zAqvM5&cm={)W?7f+_fN!?=(-qm&fDlUB_`JA1VjhI@CTUeh2}~^HUz6BF#4LySln+ zdh{*%wKhUXyTvVniVKT%Dp{y0vDt~*a!32g91!*=(;&o6l@iz+ci@vEGmS%M_i!VmX4#xOGU5|D^xwL!Ub%avOyP~)D=uqWD5geC_bzLPCOv+VdEK_bLbqkyNi(V_ z)XbkRLm4_Qc$VVpDbyG{3`sLsq0V7KebybC`l@Yu!*!`+DAKe6Z)vLEeE^BW zeZT^R7@GERkm!Z_pZ~1w{3-CedXdoTD)0|NWn3SeT+#G=CH7`>o= znLRpEQSa|ndSx2^5+cC}wCaW>1FQP*xU(b!2v}_TEn*CIT%P{H`V(rca3;>VvPlcT zj4o%je^abncv+xvbcE`8C~)H(sAkoEMj29tV#WDp&u9`jaT}Li(idw9 zM8A@J@BJfBCh=&#W zV&}x$7+PmogYw*dMEkl_4>kd;UW1;_VXyHx*U@{gj9-)~X})~%{JAO~2QG*_{cQ`% z)Jzr9eXY|dad)4lWun5o9VW&7Q&h9bvYSkNPDzq-bMNU7pzO8RuxuLlJDd@^UC3-R zU9B@8x%YHU9JzKH`*Ly$1@APQ-PF@MFCXr|QV%{~F8Wq>4BflyIJGJvK5}_(B<-bW z(c~L`sahmV*0SlkUlRd)$xehLm8o7}jCxR56-|M@?aKvV(pX=;6l02jRfrt8_kt@UDNe9g3M_R<) zprS~t>&6CjiKhj9qKK=piZkaY;LHmmsMPZ}9ftLp4)vyPUMa3*$MM!9cQz_fdn2#} zTWhcIa_VmFWa_8d-g<5<+HAnF*%*u)SIGABjdgN5$*g)X%B&Lo@yPEOy5{0IH4!`( z{n7u_2O&}QBlxZ_Q=Ek z)Kty>)Kq=F)-yr#0?PGHsUYi%EJFmLw!``ufQKgb>gw)ZAA!JLVC*FBKdz|$;?rK0 zVC>fR(8Icrf0HWcy75S%cDk5pR`6xh17Zov0Jy}2obwv>*)EldUYp(1>We1z9VFaq zV{lblhQ;Am&)knfUASp*#J;=Fz7%lsOzmfaVbLEy9#_7$XsreUYC&>i@rMte0+k&? z!*vSYa8i$kJfN{xjOhM^ibWt3Uu7k`!KIFFG&%erC54Yj=hOi{BM-PwD{=nWvzr%D z*x4$OsOv9an1jH6MBPb&z~1TadXT~vk#Fr(lM!K~|Ek@c3PKZr6|AnF9K}0Z&s$=C zFbBH2Z1ZzFP1ZTFJE|z}mObwUCjfcSof@kio;w~^5XKJh=|##VC?Hv2pSf$c(S1KR z9GL8F=60R+J{Tz*jf->yEgC}Yx}{vhYn2cJ8L0 zq8URMUhJk2O?l_;5A%#0Vm_4R=^CdpWyJ>mMs*wm?P%MPDU@3q&?#@ON}u;TCH#Wi zWZ#8Q=D(n?dW`Yr~EtILx3qYlySJVX(P_s8)R(D>8#7uw-jHVg$iQGm_ zAydN+)0>Xyno2!hYUo8=BeeMOnxM zfm^o>)sk5tQd7EzNOj??>q{QTMbvjm@XEr@(Wvus)iv)bnu<3fMY40ggL@NHg&||G_r@jf?CXcm3isf@eJ(r;BqSuS1n7 z&D~nC0w~x$*~e8!Z~F#KLC9NvX4CI=W!et9Ajh?8&{pge^J`Oi{Q!(Y?MZpb&zL%+ zDsy=(Uv5qc80No(i<;lOm+n5ikWoTtdgit;PnCoAiqo2__eu%EcgI~CZtW^OPvtkS zax-;hJyb-bEC|ZudG=d(F@e(?7vvD6UxaSI&)&3RW!r|6s#7rpvolxE*Mzn)D_B=&HnootO&ori8cdm*={Am)m=`29oUddth9)6d*dDXpJ zh1ZwE9(&;@h~mI_5hW%Eq-FBV%x;I-ty9iJQ}-|QbVWly<6r}|W@R{8Q3dH=jpc#A zw`c+-*KgdJEzdvUPRU_=hkXjFD0Bj#wXll0mF8CP4N64CH1l{n9%(O_IN%<%XRdi^ z;*}#J)6gc@$a1l`cxuN$^#HWXEb$_JUZz^!oi0(c#Ub0?h>&sXh>y$TP(SDbBU6jj zuI_Mr8$Fk;ThQZB``K}pWc};rqQ}IQd^wTZXCT^cciDhk)RM)~VQUX%3_ zGy^v?&(^@Ga@lDMOLL7}QBac_kk}$+VxUga&gj?V?hv=Bq8@IVtpExNISOHYN}caF zg_JOMb)BM$sa`=axg{w_(s`?jkRuVr5{%h5!uc+?An;1&X*KwL1E*yDgU3Y_YIDhL za!kHXoA5c%mlLVAy$ONt?VKdE@Qdx3mQi_x%an{FQiFWV0;gROxfQ1eQz5z--)t+3fvEF9FnP+wQMl%(qbq zy>QbOA~4c_lWh-5+WWiSNp&Ms1FO{uBDQ-pCzC;zbC+i5?^{WFZgKD}!6bVuPdUyj zn{a0v8Lmo|a4Z}urdWie-p!6g-}iu>lD1<87th+-_BZ%5^UY7Ij2jgj@(Y?q=((~d z&HEY$1lvDSmpeVw1t33}_1iNR1}k#;nprZxV&~$38kj>%w(`12_Hv7tJMN0~5VPM- zo@|-FM><>Y%$s8B3ygBk8Odk-Xc-+;TKR6>JCGMAd&@hD_@wejyU7_BNPPL~V&D+E z=;NsBWf>kJ#iA{%ns$aRO|lg|;aCdf-$S-2mlbn1edAV_hi-a7=G`nJRS1S(*bl)qjcvZ``Spl9K62ICE5S~nlB)KPN#k*LqLA!1h45DGd1^8w zxNXwu>(>eKS(5LrS*P#ytQ>d5^~y%d*~FO1kXZc+bm>0Pqg~2j=^s;7yhZbYd6*R| z_$G4G5jWP75#i3zHDdwhG4mSE4j0>(TB~eVGkLg>5(&9Dqc6fR1?*EbS3TDEX_%jw z%-6-AG#>_)%&G0?{xT1qISut0CQCip26-M1>pO2!TMC^WvDs{Y9YEg>Fex2YIo;yh ztqVA4%&_q4mkr38MWb8YgTM61V`L)BqObxiWtJADWS?=JW^63SCu6z#j1`G7!xyql zh4jI9;)UKu@=fxzYPc$KO@E$aw{O5z8gN0b@=jlEHVxgMmeP3ocO$V`!H~qm*45$u zlsWE@tHijdlJbNvWUdUW@?YBq079+bbQsV7evF#XX`fi+rQ^~{u6y9-NxA;f8KLzV zH(L`ye;Z&le`A<8yc`$>xJ`fU8unFRgawce&S!qsJLzTI<`|Pcw?$Dawh&KP-&FlK z7r>;{_NmzNEtjE!<{=&8Y6&Xa(rsp|Dly`wIkeKTt!=08t*=0^`O!IeoO4f2Q`)8~ zMn$&LpklVh!hNJ+X;_Mkdgof)6}FIhJ<;;%f+^u@{mRhgyGOg7mvh3H8?H>JA!4@H z;cd#U672h$f#At1u3slwt8D>PzV5$}gnG>e{hz;nEgtS~eK0oL{T z7%}jlP9x7tR$>W(8-It^e;_`=03+n5gocW^@#NhucAq0V*LcsAV1x^*{{jX&B_)$w z#i{nLz&IP&W+TIbm08M2A5WD=QUO?$6hi=j@OGbuNTP~t708WiLQZtwPH2ilVRRLe z2>WcTe*dmsGATeS&j0X*$(ELVvoBQAbL}i?eNy7@0{GJB>o+@CUYm)1`UimGUzg|q zkFU3YihAq*hXF+iDG^W_1w@80=vI)FkZw@A8>B%56a=KZQ@R;I7!b*!Ye)go5V+i32Y>tsqD&C1N;|{P1qEDkAdO^%1(UknZerR*lz{; zUs;B-Dwwd#&4`yBb;93Zx?_fSVkiRu67sNs`2zjplmF2yfGD6XGdT43e>TXk5IHXh z{8nWr-=PlssP^nVD%Bo$TLx3a)HKe7r_)SW?SB66Y`K zz6Wc;ba}&kg-q0c4aA!2eQ^(OHni6vYP;bm{o7uzyKBzQ^{WfHBofJ@v|SR)LTD8< zeWhudUW$oHzI~f9oKNy_Zf-6jC4~(nJ9IUAp5kx@hlRPow`O2^&RbI&EB1i~Z9eD^ zrQtC>+28?$!3KA=8aN&WSC>(&juVe_pU~BN@RW?!2jZZ}^%0fSi?7mvtG}$QY%o)T z`>RHUF|{-94CrW(`Fv_gV5mT)fU%v*rI4P1({_k;rJRul-a<%9%D4?fRu31bskOA6 z+fSI8L=8;yIGY|FnIW}Z5*OR18&twUEvNN5r1M@p90(1&(~1- z!}m6hG5|>l;Ttosl6mzS>xS-RetDJ;7L(N>T+3qbVI8qur|n4voRV z!6_)`GhV|EeX{E8timq~3xlpLbZ&W(YreR*@1`-pqf>V#Sv~sowR`}LH38!HP z?N^W19`kNKFE8LERbOBi(f@}{aZqKIiGU1&cndHpW%X0I`i)~qu z71no_`{Q4~%jFGGcyqbu?(AP8SN0rrxo1fC13J3rpsHiuxG#}kPqj#wH9R~VaLPUP zR6Xc$e@yd-{Qp{==zDL{0CF+JrOEvi=-t1T@XEl z+wz7ERhGnKD}O~}6A;ioIoxF3*}}LS{2EEjnf{oHJE_AB-|`6x<9=Mq?9c#zH+LWi&7-j%_%P80w%rOuo#- zO`tisG;qV@s=q0>%PcXITBl(f>o15^Z5n}idp0H8wV4%T^oF`3il=vbxF6^cM@2H8 zGuFFo<2y6|i*Wur`mSW4N^0xrDG5>Mjw%z0#+r?Yh{$q3Dh_26^vrz~_xN>Unt%dR zHoFhRTUJ5ncp>wxiU-%X{Xxj?h}z5@H~emtD-7;&R{{!_)(yLA=*xJsAf;S)!?pCv z@zE+!U$Voz%3K^OJEXDv#STP7bPeuD5g$IhTduO6b_0OGh??Ibz#hCt=$bPgxl7)3 zzOL7|v~Te61)ADy`ICRs6~MZgKLR3P)+$gkULk^J+Tab_5DM-HHXT05tGJ8UZ-8)?X%!E(L{s48~J|Zsx2q}V<3WD;&?@id%$?jnRUP8a0h=-rZ#1`qAZ(FbR+_;F zYOygfj6*p+pt$!TRZc?%1u7eeq-|Ut+0Q7}`>1Sv45$IoL-=K0y)(=W;1sf9Kayxs zPC-Q%;H56ubu`r7pkK!?xx`@y#x|&1xBYVomtef^S2030?1U4 z0}8%lK~LaM{4C|3onesNI9r!R(4_ckpIO*X(8K8TrJkO(kO!64vxyC2-rYwNnmt3L-B~X|yqQ z^ck;8b#Nmt1sMqi1obdQFmLBoqHDiF(Eblb(izLJKid&xcv6<4o@xu;9kNJ=Klvxt z`8Pw+Is!K6FH*n4G0Zt4(Kxdp)|G8D^Yb=MYTAfp{O)>5rqN8J=kpS6Ma2lm?Vl>k z{+*!1oe{{9d>(7?KSr>~rEMs^)H-;DCOg;jmC+Ejdv=3aQU)&3EDBnU0t_o81u77^ zll|2P;o(G=`~aE>#azt1mwxhZx#T79Gt~lK>+Dg#vH|88k!b8$pr05(hl`;CVnzyX z>nM=!zpzTy>pc!&TIHv%#@kd>tQr~`yOTDs&e5XDvkF(#y9kEnkpx8WyKd-&Dhim6 zFjVgQz^&vI=}~ab>@RiVXpj;C^!@}NyOwcZ$^#3;g|0p?BdWZ(MDiBXX-puzveN?)(> zSjd%oBO3u<;31|Kq8=$Ra5~BKLx3G%VFr?W^SVck6pgx~%$_Ia-Ku-~zvI3jrS-v$ z55h#9F75rLQNwos{tLIG>LvR5>1+*P*c7UlJ?%3pH)e9#7^4HGUlb_GmQ;`H!H)#_ zQD#+B7Xt9Di7L2C|6+UxF)B4h(0uc;W~-Z~X5Py8!9LzwseQp^0#aOgt0^U6=a&0=ljU1ATGJL5M{ z)hh6jbbuw(DV3qZA1q}Buw)#!IUdU^OHRwUoNyS@Zg&NHWFA5~_&3_0MaZt=_^7+alC|`p`Jm~7{y>cLr!8WLF z7&`l2`7P}i;LD1&XugZOX=S;~UAXG-LQr=hk~Vc|XHLqr_@bVZ$h}MF6cK$QDt*9# zVSyF1aXgGcMMb_rU^X?olH_5jLUtfC%Gn=?R@+S99*h?o`52>#T{T@kS)J8&aen$j zvqVmCTFtQYkYq4G0}J&6r-wZ1fU?w6djp!#;&Xu8m|G9u+5(2W=I7 zx$$~n;1Vbpn&#De>xugd+{FLa7nXK*>7GGa{2)SwGEL3c$|rw`nA^m}*{Vf6*aQTz z`}>aL-*{CHHzDfBBxGa|r}fe3xVXFA-T)(%8_7&GW7A|*t#e97eBe8v*Q$Y;kH77l zg2BcF<6j1INyw15Mk2yg<)@5 zxnpQs*bzEhgb;~g*QMRdk+)mc-%d0i$(K=ejtQ{GoZ%+f72`Gi=QMpE01WCPc0A6D zdhC=pe5I+HTHD&B!>B=$R8N8{6RSrer$y)uyToflm;PvgXhv+AA9scTx%J$*H;G|< zoYW~gtQms?caTM)!o0w#KrO&gAKPhNs^LVc&N8l0r}ow1*0|D!TlTbC8Ab62T;%|? ztq*i9*>dMMBFdRGmgG`5iWQ#~d2Nj5!YqpSC&Ok6KB$E%{d;S8rY_k;E9o5d(lyPI zULr^EwgUql-OqSx9$OK}yK#juHkbsmjNdDwL@T9(lh6~_3k~^ zDAo796#S2ol40t^5u;IAcod_>G0Cg^FZk#!$_Eo9yZe`j5Lqm^W>njaqw9^%{?L*y zT)Lg4Yy9ux=(+O7NNf-5f{O1!CI=ObEb`gYs?mxqwl9jib|>Y378$CA4E@UoJh#Ep zC0-1TYQ&kNRXuS$})^ z)Xt4nZ26tD;J~$a4N+k;BcxdG{C;DiN=rP9nmEpjULs$mgsn%b$|+EvL|Ti>=GLk$ zIfwauyPYb|&z%t@GGtOQ0pYE9u7_1GkE$M3dXm&ki}DJy2|ed?++xNf=N_`4WSgt2 z*qoS2raGO7eslt*D?OcXBF>gTqF>6OoX_Vfq3h!kYh0$YC;af?CcE_|*{24BV;Hhd zTvp?nn$=e9J5?6-h}Q|afmM(gnMhKJTA^7|a~j*=U{EISP~xR1{570KHU&ZyK9Y4_ zrlS*?LF{$>lS+>a?;FRbNIlr`eZt?z1Rk?y%aZR5v$`*=zNyEp@rOm)_s)~8dU)7; zjf|rYAGJwLX^$BWaigB6fAE1(g8yi3z1oB%0#4c+i{8@vCBd$0s`~r)a-*YOo#Lm~ z6CVaHTINuo<^KbhurV@fCU9A;<$K4l>BQvXn09=5Ui_#nD(Vuvn=ih`!$#oHU)U;(bpY`Tf4c6F1 zhOCbUIO021u|LLjvBYxHqqF)$$Z9yZ=A+aX5jUpkRy0- z5e25EJkp~j8Vt_Vo;Oo|g-$XbWtjhf?j2bT_cCY=x@>efQ2V+bi=SA~*g8+gg8%M; z>_MeIm{>;TxS~raFFeQWG4EEp&0bNb(mxUuM8SYTi#r{}fU0EE-d+OD;!=3{moK!y zvb3vM{<|I*zs~e;OJk%*tCY4@dmp5oYwnG1(km9E9-7+jovx8Q4--WG_%q?8Eom@> zD3%UysCQY`%bInVR)ddP={SvU>F^s(uSH|VJ~C07ph3t^IPr~g#`0=FCb(?WbK7Yi z9Z+z^%*)?hc>y)*+%vp*rRR|GbG8ek66iem_u6Ni-*6%g>JFmXST)z~1$5eEdJXsQ zx_laHJdzXI)E@ZGCpA+oYb+bj#LwCP%5qqp?`VT{gd?xK6yn(sq{Kc!vNA%0>@njZgx$UOQaTHB>PRk~z zN6GChGp>%py5ttmGDw*(xYlytwjhW(d8cJvatjV%dxC@{?B-@lJI2y>Oe=yQNy>&j(7;MxwSEVqSo8e0TeF3|)g3mJe z>DXOAA6j5)XXdb#CJHQ>l&!DQMC_9v4dU>~^fX?q6k(9qnuJ1Mh&Evr`JA>AgA8o# zxI*pe**UJ3m83f{*2Ie}1P!&qR%|CJ@`v&w3{A-vObn zA{LEu<--sC-m1Mpfg9;>hL#kcarLT*6%Bh$l~+xCh0^5*aJ7C#Phu2|$UpbDl9^PT zVz%}&yM%KDJ19Y`lmQEg(?S^(8l$D~b8BfpHo0IRW4+>N}hCM)8J^ezx{7-~= z$7g{+o|Vsg^wp}Yc*VvYig>fVP)SIi<9O{*ZXHgU0PcF^g`a$M9bUxLZYxOPy>1k7 zh+bnqt{Bhh>X9M~Qkui%y&R8IY;^6E;MRXFYG1`m10pCS8r9jBhJmg8 z39WZBRm<%}F8=YGx4sGw#yH_R?5{2@Y2bovCgo=tS~De4Oa5_UtyfTauk-x)dxrka zH?r2%rVf>31}C9baDd>hB?EluTe zFgvgDzwLOOkY@QSp}LZhtwGe9I0`cij^J3rBD=n{d961g?auK@OARiE2JfG4hbnt^ zp{#j(bnUm-@a{(LUALXC#IP0y^&apaJpZs@)ALIs?K*V<3ZsHREg^8iCaIJkfC?AL z_w!g9ZeBxYa{k=h zG%`_@ibwvGeX^00wX}V{O1>&UHleF-gtRHdc?d+@G%%(I$?b5Hl@j)6>q52X!#RVv3c_PrMysiZSHE)U=|aY=qq; z@C}V`IIE~71`53SZJQihL}Ht1f9m5Pt8ju*U+AF{>c=Vhc>ss!uPdd7o{K(pUmgw7 z1~=^m6s`oF7)>P+ zeFw)e$gh^}q-JXQtNUJA{svS@&+E!V`fLRCTO0OXqw{A{>#CbB8BhX!yh zhE)GHu7%BO)_IN6oRYUjuP@%?l~o7;txjYW*feMza%JsK%_hpL9M>J*TMKJ>oy zQ&X&qK1OlUh!6rrP0RG^UyB$(A!&xom_u6#g;XqC^1)jT?Y9FT&i4rpUumaWkJcpC z4EDgjc}uxZl>!8x%GJWZS{foDCT=cly}AFVMTaoD&dyWvw3;~|;7uL%8vE6^c-!`g zPgpFk)t=(O#}NVFNd;F{r%zItWUF4V%lHXdTnzAY44cDJ9;Ot&QH8#u-ix&CXQXLp zxksi`OsD*zP)dXOnHR;ju?CZoXSD`qs?M?wf3-#Ar}zkk+)&ci)q~MG^p{ z*_RU^ezQE)Ce9d==Lu<*A*mcwhf4D8<%Xa$-tA%)kMI*stPQbc^)FECp8O`)u?rZ^ z?IsPS1Uqtiwm2$tP##Ne*^9v8(L*C6OQpRg&#zXKQY3zwJhP8&u+RH%Cn6A*xD4kd zQ&^z78$?=(E>-zesR4yp$j zhK2~#dNBeQoAaC3M&<2KHH&*Gj6 zyt!Zd@j3>0pAV_NEXqU}ZFVgV$`rJ}{q@Tbc0==lu$S5za4&iJ$&V<=oywj5vFcgb z#k9>$g_LQco2k5$A?vQvG3{5?sXL;Y%|}dqNHYI%r--!D>qer5Lx}aU;modeJgsnH zP|2Hl+<78Oi>N(myJG(9h@O8M2Y1n|xA*s?KuMd%>3K z^>t%=ck`BsOqQQGBmm2Jd0N(Y$IDM{M1)-`Hj#bLP>p!SXSnxP#$cK@yE&$i8+Gd9Cv=UkQfS1E& zWYk~C{b*y9@DW3nXju@$I&sep3wyg7RWtvmQO0`dmPfX12C`wA!d3&E1`Il1>op{F11t#l-+^y zYT&I8S?|_BMMWSyAP%-MrHzAsB|Q*791KkN-l*crz-SoFAh7wq{_&N>QRcAA5PfY- z684oPE6`nLZ~ZX(MT=@_&eC3{F<^wLpnkjz)?B8D+CLjbrKWEX1Nc~0OeXO^=kf0H zJiM`aQAt6_bh(kcw8mFG{#a2(V0Ghe<(yZ}&KtNC7q=5^#jfm6B%72e7>$jqItTQP z6<G%aTu zMQJLo4SjoXF<*HM(x3Kvarn0MgjQbl{rKf^7Tng_T8l_Xo&+HP;6>NGO++-;JCdRn zoz6m4=e&7;ImIESeoJ2H;FZtgD9@y)jvqfm|GYT%+~jlpol3B7@#v4caOpr&xI5CW z#+@BBZQ*|EwC2cN1x(j1LG$e%s*{rg;lA$fsQvx@vqYxfQ%FrXa$;Y;hQyM;y5V~O z@Xmh<3W}Jncaaen4|s=QwM;%6I^TvXc<0_Za4!IzbIe8yAJAZvA# z>x0#=r)Ir-#_eBH74)!oZcjv<)vFB}U=f7bmIP*a%}nl`lsLSO0k5Q1znq4J9a2{| zZ?C7^*=a#0>>}k3VPUvhtrxPzpgb~BXXqC zZ%J9>o1v8NQH;w>b2W)&0@6jK)Rn8JzGpBc3@H#?mr~7sI@#U(1bKk9c%Z+28W_R0 ztye-Hc7bR_s-O3Xo^z+spYmzLNe^unsA^{NyWr`Al$(2fT39W7K?gdOOGo9!tScy+0_mSUMh&>t|Kwjssn6 zf;Ke~k@D2DjfK4x4tU0-ERr!~dTuX47U^!c8|WRH^%73%nsc0*wJjNI1doO1z0SO* z*KxW`QkCL;&)s9SHTLpZ)SptmJCI`NEASTjS_n`%8XY>=04_c*1}F&`?F>kZ&uqp__^0GA6h?c?@HQn^rPOR zQNuNbblCtmFfb4f8qI9!I;46Y`g7p|znE@!sI$&&3z_1R7Xpo+<9H$T2VufqGpt8*H{GTiPbNZAotEy| z4}8R>cks^9wp$!|n_2|kv(Y9K&~aVl#C)-L#=qM*`mIEEdmprD)&Q;HYIaM=-)#w} z?#wf;Y?sC^>i>*=+0c%`KOC+Pt4ZLrV2;&wE1QJ5Xi)U=ePK|_k&9H*JBMMUM;vMI zl`MO1dBRAXl3t$c9$NSD>kdB|BU^>X=5|cg?k#vDPnr8TSkpSL0Vi%QM3{M;PduBzzc8#-qt%$<|CcoX>3V|V&U zxI@it^PGE+vU}U-Z1PB$J$TP`Y4u^2)DYuSqg`d!!&>iSZk^ek%AtVu?Fga-Rm1h| z5ep31vmkfDg&75@*glvSQy;GvWP zzR68LrwX89)#8c`rJ9hDZ9o_Q&Y=p&A3{!MZcV-CnISk|O^hirc*7-Z(jQwb8?NIv z-~ApF*U10koZJ%FcsbuIA29c??mt(od8ss>>wg(J0EFaGhpJHsUnxsU6cXMwn37ikl4=Tgd+3fq} z2W?i!7KG3DW%SFwj1Fb%c^#{O4h7o!g2<05k{XW}k>9P#ygs?loS(F2o4nf}2>j95 zf6FOO*v402|97UnM<4Y`>#}TXaZJ7o0(o!(BdOB{2Ge3MzYWCEtMuN{Ixowh!wx}i zid)cSH|UGp-G0%quCrrYjJ}2k=?RU^^-ue58IcqXkN>q9&~yG|a`w3P}&CH`?$h zrnkpN3J}*BmxU2cpFJM@JkpYEzqfE?-za+{CBW?#cyKoy@L;wG7TKCxK^4aDHw|SY z%uZIni`9mmVCRSR+6VfxB6}g~ch7gdb+OL)pX^RNt*OUMHG99|Rd@cmHt1DIdzQoGjTOoepONehKEHUz7W?UVIvv zXc(PJaK86iqs}tkee2+$IMRCx=(UOKld(kz*#c`D1Vyx!dcj;eX6{RsoF(Cah;M<@ za{GZgGg|vI=I-)j+}&|PN-6FaGsWav`d&BhRHv5nq%NnrYm#%BGx=lY1Hy|^xqE>e z-y6(3!ES7g_mF!c1TWm`72F<5vrNS5M7itg|UQp)M@(Lk&wD@RRP zCsoq7hGXt;1O#LFVcPLVo;&Dpkx-=Pkh zT9Sue*h^VXDn31+`B`G^&SFa|dSU1HgEQ|TKeuHJ4UOxQ$H_#gO%Qe%J*!rgE$q%u zuT!1lv@iA9_@j_?sR+=7cxB5&OVCqpHnzXnntSX7JH$y(mY}ck{U+?2EF`2M`Za8K zO=b`pzTMj&yDulb9ea0ilaiPEKcF&?90SB?i|f{K&cAz>ik3@1vJf(<^U|mS51(A7 z3Tg}ddSO;XlVdfOP(9)=H<}cRcfoGEfjbOdqHB?@Abx|oTuVU@0K~CIw@0cTN2c*FEp;ZFsK@4GP&7)2Vq;mt;kox4bvWber zPdY1$1b(4yGPCV(dns8gdUHA($Z>uGv`0ZjxHAmPNzb1$Jx9lmc=l2M4@ryCXP=Ds z@6oS2G@*$rD|ZVG5fd}NeGy@ZeoZ5a!OyU>*$C;LpPP^{9zQ)kPNg5K^vhY^CHtbT zzOG(wTI=FuOR!#6A`?buh?(riX)%m7;dZimkiFbWmS9Em4GE)tLQFBo2B#V`lE|BU zr5(&z-@~?9>x0=w{2+xRk@&WKoP(Wlrnn+<-_3XuT(oSLREjhEsd*>Jxss*^$ z{AmpbVqWJd&<(j#7oAGoxh?0zk34e-F=A!mWK&v~h2Vd`UyO)GTpwYbA~@1?D3NvH zA57P&bNJm~%BCrb3H>}3dq^!KUyljTH%Xyh4HhXSy-XfB%OI_BJ*cx8OY^O?o>l}f z!kADWIqfpDJZ~GuIdR%E`mABIT+g&4NGq^r=A^_XTPx3&?yGK5LXx|+%OeE^Rw|Cq z=PSg=`xSjXb%|R=+%r#N?8kf^dlI<9jrqpV*RiZ8mvUq_zai87_;kJJStEs}Qmd^r z*Z0;Kz17Syv{|gI-2QM`jEc41?|bWZkk9$laZJ#Q;xmr(>k=8yQ(cp_y5C1H#lP%B zsNb}vEGxXeMRimv?zhMxR!U^viG%x+Lc4K5TWRIU-zcR)Jk|At*PzU7PZZfP($7mfOn#&cn*>5=o_En=Hl$R6#~s{Q2b*DW^!3aV&p{P>;@ z-WV$vjg7Yx7=P^pUwK9wwfAY4f9l!OB(&J@`LK2;avQH)<$NYqEsjV}J|B~leT{Mh zi3#f-r2-{vr}W7>6^BkW+SKj@UbWGD=yrf5Yt!6)i;2pzZ3Kdv=Lh#U5zFz2K@tjG zo{4HJs|+^HN--VH!t&10_1#43Yl7%<*`s8n=S(*2@}7>Jc14yqaBaZr;?T8-EC=+@ z6lZa%ljBFz_x+_~qoxyUx{g{NH680aW=lo3?J{*EIX8yI=er7uW#{-OHo89u;L&j`$2>SL%Y^~(Qw#7{tfhN=cZYeZ6ZG+5!PuOb`1Iw z*S&;YON^7vOmS~^F}VBnT7Qm{j(!LLkMSI?LF3V?e2GGXyP=aer79{CZfpir-QgQk z#0?YX(#}WZF$RwWZM>;s#3h>w<*3=?)}Gq8Zkf^s5zgPU*TV8ntBi;$eBgBtN51#t z5sHBlDH`Xktjf5<`t%aVjp1fP(2SDcLsh`XrFYK~`zT7no+SSURf4`A2P25x-pRE# zDOU0R0c20j%yFQ1P~FlCiN}SILNrU4w?ESrv!(MX>dzmY&_DKpMmXcB47k=DLSUR0 zqj$-6zvA6$LwgIApU9;)1lbX!F*U5jdk0Mnf=e?64ijG^l(xyBy8M)Xa)PVROnJu) z@M|o`W^dhdKGHEpTz2vnMpAmPODNr7c{>muYSDwUly39}zXI6OU7(KMDYQe(z?VqQ zr=s{s-`C+_q(LQbM>}_Ox&raBDg8L$BGj>{cG8MmT~F|IWA88*c8g^8jH*|<^2kW@ zC@wM^wuWyzR_f(9Rb*{!*8@dAx9q5g;^T|F`!gpIGO1Ts-%j3h{)xkEmuW{P^5szBUw9uFh8T(}g+e!xV*~_e@x6c%*HZHj{ zTRQso)TFAm`(_ol;jbT3To7-wNm@&vyS(~Mv?Q2{y;bB^6tgCj<-eX*)ekxdNJdg& z^1y`!`+U?>V3)600&hZ`5U)UFA(yYq4eff8u2}BAuOw@~CVTx&`yzFIX@vM{#aS~3 zHWHVNy7=R*p|e(|vJYPJHW_?p^5lb-@jf43tckJp`b4&0yr}DLRO;t+RjH~I%ppp? zysqn;E}SzI6I}87je_0cn~@_78LJrECKO!OPq*)8%fv>6ZccVMZ^k0u{4!+MnYF1V zoz<+d&VC%6AlfRkP2?ZJuVyWZ#fHzgE}5br!xJ~DR-`%dBc8)d`iqT8ca0|*4{8n{C6wb93I%h|6`l}W4Y_AGK`+dT9uM~XhcRaAY8SY_vO$pakQ4A`k zug5%n-F(m3Z_}dx=O2~?Eh+le`@OzMh}(I)A=y`l+x);@wcrcvA72cuCP1LCjN(xR zmoyxfk1sfPXGiz;JaBAKNPo|#HYj8K_K3zii6y$KA}dC7s@8$lKc@^<5rMYct8;y) zd?o~u*mAeXVMjLcFjA1-JDzk1H1p4Uhd})BUP+&8i{|r!QUwk;w&id)>NR6QY>93n zEaTcaz6JLDw-*4e1A4>Z8r--sUCk+lK1uIG5D8@MoAHAlzCvD7mOrh9lfuqIO`GP} zPLa+Xll|Y0HSC8%xbFhl*7DTOC_@1G-E^pS$vSYcgT!3rb*;3K_cK1LCo{vr?2VKh zH-C++#WV(LUNl+#9L$!%G#Upzg|G>82bH`yC@n9-V;bDuM+-}D)Gkd3HDa00ODMG& z#p<$<9enHmNYT-PS2|d`@*USh&28)^(So3pg?W*SHoO%WT8ZnVs_~vBu-Cvn#o~FQ zZBnR1o0LUzU&mt0nLEt(sBP!YZ2xiaam6e%SyI5Xr%jMW?J}sgd^|n61I&}BG4h8sI;sQi?-n{Tf2NiS4nAY6n4FOGkcnn_ByYhV!>%Xf%dnd^ zxur~2!XHu~3H>bgXC*TXbYE|aKd8L;kYa+BP9S{dW23TCoo=nm)^1wzC@XNi!P?hV zZ4Mo4>+?YEMO0AU-8{eJQP9muu1airos`UZy2y`$Zo>V~6!r9Cet~;@7U56Yrk6PH zZd7jf_ij}8#rH$+k#IeA6UdWyzB4vaA6(}h|$_A;vzg>yXbEFsx*Ds{$( z>-=+J%yC;WTg#45HYeI?!k$ew@Fa+-s6|3_zY~IlS&Jj1Rq$ZfOHdT9x^y=UqvgZB z``VimRr_}ZW9i41P71^yI+1zCEh&cMQL+dalG8`6%RA2q7fb?nSZE)S(QQ3 zl@R_&ORYHiod1HT57S7+>A5SDq|dtBHrk`Y%*n%*j}62@lKNQ{qsrNeYuP>S%H-B54Pt!60Z9KZq3 zN7wEyMvG4To>PDS{%7J#c5;s`_d~F9 zhiS&~idKHuM`H5dyT8Xx49>Av*kNb+X<_T+c&EDq87 zoH&psdD6mK@Uu2KOSQ;+F#hw8&<3Ln%l2sMneyWS{_jn`E?6w55BzcXxUsc= zQ4ZVSP_S2Cd(6SgM=1eY(8z`+cbR45_OzdG)~&tI#<~u*D5;+ilY$?ps1nWg+aU*6 zmSs))2N~eQn%l3RE$f75y!_Ftvi!8!&HUAlr=Nq4T2UQsZxd#=6Z?uYrTEaj#GilC z4{n{GPdUgZ%1x>1dX3y#@!V;B&9~^U$qgKqb&nI?WVF1;W9ubMp**haPW0ZNq?&Zo za|ubBF0%MiO1zeHC%L5*T1HAt*bAC_kC1(xmELX++}eYohnG;OG89MLr1to+98i1w zm|M}5C&E#T)%C{RqfEcRskv>rf-_T|Kk+Zs?;T7%QmFqDtixk+?Q>i z9%Km>Dz425OU2}43jFRir{=LzChp@nIiG*JWoW4Jvu`V&fk2Mzv&(ilw}0C23qiSVkQN zM0)Df{b*}PXS?n%Aa$l_fmNn>uuk<{G3jONz&zYH z#MZPo*R^fan{nJtxABqgV;^O`}XcqmHz%1WRqRFDfZ`gOPFC&Z^1pbzfjZ{0v7KS&n2lnZ43mR3*Iw_s0o*k z1ALNiCgg`&NaEinY6Yrq&D94^GvGNEKl;Ew{FX3gApAX~O*}!5HS@c;c-T#+zEBM9 z%08cNw~IOi+Ks;1>4)03{<@~*VFJ7QP*Y=ylvM(OKc(AG1^z}wn>!LL0)`UeB|~2} zo*tOUZPqvZ$-rIM?$I*p8crP+v?H0<=xuzO)<@#tGsk`6+jHnc7^t&CAhG>8=5V`j zZwcP@V+7ct6pxC|H&VS0{nr#EUfm*w)Z^HaKvaJ}toab}&GXdRq5luo`b1UmcbUgq zH)B{dezo|l8vEV&d2?C-vqtJ+v|!4c!lB~8JZ6=Um`*E`k zlsXy(wNpRSDs-`yn4h$NeP4GgdG9NSQh1R`)w6>yj;XcM!#_@sXbgRW%(k7^*7XGS ziceIi6ON+2S3aJ|hD~NUUcApaW0*w1qa*K!jr}IhDaWA880QpJ5h=)O&m0-p3oj&F z>eAR#$$Dh8+%5y!UP2_G6=K`QA5R zLGaNZPEQx(XI;sIq9Cj~TLa+^35f6Pjj_UB`$GN9+`OU*%(#}RPnJA+b>OTRPj0r7 zH$QxS+oU^Eug%L!EWQ6)o*Jy!wM;}A%G4aJuZixk%Gt2?;?e8uj6NEOMCENbER{`$ z@fALE!8j)w{E_V1`apuT8UF&SEN@-VrfJ$JBz4@zlTz8ES&?C(8}23&+kW zdq&O3(4S-<)f+Fq4oZd<`Djl3`XhVpSZlJ5AD>C-TsW@3TjAZ?h*W)~oS$W>eHLhW z?hvipLZw^@H!Al2JyY)x+dbl_+W(So>ftPr+u70&x)(n13Cyks9|WBz6~H4lRG&_> zjF{e({!p_)hXafyfO31^Hh5|OrV`o zBu)>%Imd6k5yVHnGh%CgFH+|rxE6-*-O()-$ZOF5HLSMtiOaJkJcpu<_fK)pBumf& z!w19p4#DkS!wsM7!l_}M=p{x(r#Tgk)Vt^x3wgG;+O>S|!i#xS?aTRsP4K2I`iI`; zh?CyWzqh<+Vt4f9AFuzJ|4TlvxAol;iq+1TDPESUD2x0ha3L#yQBY3Of38|Lr-cqz+{gGs96;yKxfB@W~Eqa z@M$F^;jCq!7S76+kHZ!r`|5iqG4C$|Fjc6lW6B(7Z{cOj> zRIWA=D*ozNp_5`mKJVy9H+q}!AUBL=#~pOGEgTCj-;-X*j%|x`IxTD?5ID`1JMFBb zd(2qJG^;MvMLZlk=GqiWu9I)W(S$a)}7y&v<*wNM`S%S5eMZTJg*9+|(C7 zGP?z_R>0ykl@PejiFFsEdS(`Ln75-C{ZL+)WLEX0n5DENM6;V@Egmm1Wk&wD^kxT}2I zdv_=~J42n}qlj<8cWPngrCq%Oj3fv~^F+~}4!0CKNexF%>=ZSb{m{O{%(2*Luijnl z(Rk)VGrGe+=-NsT{Evfo5f>Ro4XaqTb!F#GLW<>XM*d_>xC2BQ&qR+2=b8orDvIls znK|=wg_U%@Zv^NT96IwtT~a^Oa4B-TJ2AOy;+Z+@jXZDnrpHEPrwGeNWjV;oQ9fx? zH<{S2o6g11j{n6Z8!RS7j`)zd*ur;v)Qb?0sEHaQNiF|~EYR?Mi+95=7KyMG2g{QL z<0+`TBmFzJXE>nqB<`7k-Mn4$e_e+@*F!OV)vrU)lmCysuMVqn+xi6rDJdywB&C(^Mi3+f>F(~%MGDfPbV^A# zEMUHU&V$-S?p_tbk% zZ3_g0@#A$>EnFM~6}KV5n@`glE_oUNzh2TFa%!F?Wt--Mq|;znkik zT!Ai5y3Bhy&qJUM%RIwM+2!~TQJ8&7s*_0SwWPyyUacgn;vy(=8xjnIU(o#HpjNZA z=HMHae6LrJGks|ha6d)0%&Gu#$wBF|mugB3Q%#Fjn$Zg`Jc@TVBy*>}KLKMDXDKof zS6i#oE2z&TNoETAmm`n!oFD~-8$19xfOw4%-3H(O36c$O9lt9dW36PQSHD{E-Pu?; zxh?xv#@^T_ZybJcjxD&^Rvea@(%QP%E5dl~DEMtgXhF_h603V5hhQEHkm5 z6%ekRSUnl_C@`png%umuVBS%ET4Rd84c%uf%Jtc3c?!lIv_#rv4f&+hr4; zwY`V4lUEb74tjOrI_%T8%qcA);|%ZGiiM_cpE@p+kKE;m#p<5=yZIs$4mjuFf5cs~ zDV)-|PM_??_byHRnasGjEf8JGSMvGE*QpV$kQet5Tb*dM8<*?#eAiry)7PUvLs7$9 z9qfh#4VA0awc*=E_xkTs86@aGja9}{_QR?Ii7Sh>Afts~-DjxTPn8)8gC^Wxuf02^ zQRnwjjCT99a2?bwbolZ4j!UI)0gFv}lZ9-($0QIQ@%(Dy#JuJDSo`$!kS57=86MUq z?Jc???36kJ_tCv~sv&It0db0`?-=BH1C(dqRQ;8Co`w-_9N~V7OwibQmsXqYjb+4J zf^!qI^R-bKmP!0tOJ`4if@L#ZcxSO63Y8Yr<{(d9NB)$1HR>vz8^9;!BJIBY@@w7N zw>U|O?2?mtN|vA7%RZy7BJMVl1#|D`$#*2qR^trU%3X-zXQ(N?8f-9&zEW$%>EI{t zV%m^{uFN1%$7NaEr(Hm+u^~MDFpJaE5II(Mr#dZMrArk7~i?;c7*p^7; zWOH@Z9p~J8YvnM`HDsG7X9%eY!Zf4g*+>&gXO^P$RE5 zo#SUnG|ka_J+yCWgj#OubtSdPf@Bid_b#?`Qp+von#_ydbFZ@Vl*nj02Pf$&QGb(X zfiQt-F||m`uZ!n!M9uu)5?_Tb!{RMr;4x#waj#*JR<3{l%%06M|I22} zO#^R$wbTt^)d(#h`YB}t87*|nK^NvC(F$t1JxRt7~gIPUe_@^HNCf9-&|g8&*LJY3BA1?ng6um z!JbBosrV9uu$bay9r6ahm@jQ8zK+`<*wm@ytyLEDScG4s4P*^}>NJce*h(02&HGYl zOLMrMB-qBZE`qGVY1Nc7W4LnfPz|1Yikg)7`!XRK6c<#i7GtpBq59&%4@$=yd!Ie2 zY&`Dxq#Y;%W;&_~i=H;*GRUB*%Q&XgoWeYL4%8UlY8^3^J&|%0l8AE!4|X}ef6;KE zA^!wp^Pt2B>fx~H?V7R|6%2!zkivZzLrKc@MQE@WaA(pMO$g{i;K61yOnmnktxw4A zz!lNeo0)FME8Wt4cP_x@hwJ_-2hq>Qf;1|I&Jh)8zZO~Lz(eLKpQ;SBxN-#_g3qwGvr_r;g6 z>cKpoMGQ7eA84cV2N|EaMbGxqw;FF<6)8Pw^L#YpMYOc>>>yHXGqCH9?+`4T%xOun zTuSsL)b|-8>Mh(%E$*=EE6Z1PUegZs_kO{$Sb#kJAp$Fhz+K}zyQbOd{^)p^wk)lZ zCewEEb~4vABrC0~VKMB>Zr&x{yODrBwN?Vy((qa~+K&jSS-2;N8)GF-UzFAAsi~os zG;aiRROpe89%R|#m&o>3W};!k3yG1@X_ETh^K zOqc`Kf_6n^?HryjQ{5*dDQiIzaYiu>(fm20;x+BLM%gIljp!uNJG_f?J=!*%&e>B6 z{*Y?7SV)CcL+@S0#gtTsqJ?sf{z&b)!@$}nis)&A^^DgE=?oLI>>L%F5FQn6Oy&!v zc8@3VHaz3hVO)rFhIfL17!{vavN$(787o5^xO!Zak6w2^0`9FeKSoKq`+noo5MG*k z1HcmUy7Sr;4D_iGawUA#1P?PU8Q59Eqk_wsUqrx6jTxEFsYhP54)K=7dmb1DoF`(h z`Cdaa>;SxP!A7`C+{AB0`r}{mjYRd>%jU@%7`2NEtDDYwtK8S+g^G~a7orqzKcUdm zLesK$S;4*FF} zYcthFvCI=uHklmt%*}odNyWG0)^btc1X7{H2kKPl&82)hHK_T*@&2X&r%X$P7yUKj zn@SYJdu4sATzl2&eGv!}^|K=JsY3MAhP2C0ZTgLM6+f#CuUr*J&Q=NYHbo2b!fhG> z4w`Qg#vNgeG7&BV{m{B4kS<|D0%w}G$6iXxnfY#*8U8^lZ!<0n~2j&9S#b%zm^o|iT^V~o}fcFYie z3SOaTme-V;Tz*^CHP1oKw~F^pZu*#Sn1;?!>})p6m>4Mg%y_Tn_HWxfVemVBeXR0J*Jr`IH&Wl**($W^V>$HnIbH`;f z=}*O#;nbhERDXq)cHUG9p)rH!Me)OBVpPP8On?cl)PC+$a>0eBgmpkr=y05HidUMD;VN^T0D+dZ;IZZUx#RMxmV2D)~0{s762kQ zVxn>D(4RNnW?-(TyRGz+*FkI=yst@GXkY&LQa;#~s9UmxNwY(2R@Ov1N4AmYI7{Wq z2M>Nq9l8sHPzKtCUJ89;X(49Wa;$bNRIlp+Ji+0$HD5u+USZp_ikMDgHUlgSD~ zJ})e#oAT58By(ybNZJmg%t{bA+b=+%P!Dc4MAQMd0IK#P_ zmj+eZ^6ZjgEhefZo13SI)Xm5A+z$-J#{Iwg?#+~| z`?$?{^4MMWH2CNU`vw$sA5L&Ar8CV^m>{6ihRE?zsI@x3n~H*|mttCqM;w4fMCImC z_qI)~$Z-kT zW0GlzSMH}GRZWc|Ws;p5j{}yeTWZOs{7 zC;?Z~iK5qL-%#M@0x;%iop;jlG|q8aT5b-uyo`Pjpp9Y4^V2IADdU3Sn@TUwU)wqJ zp=WQ0%i7-J;6~|-MZ@5C&l?^tq;nu|_}3rj!TKO0IOFg=eo|*Mp=hynYiLJj+_50w zIq~%9x#Pg)wqx3MORJz$HTAY@A(1o=i@w$mCLM_Qrl_YxEfBUf1dIR1SM1uX&$Tbq znM8-@b;QZzgh+zZ1^|$mC$G3Y;-JneCaVUy*b zP@3t6MKz!6-W(kee4tcRH{cT2;xtxo9meqXu4&uWFn2?v{0~113}RT_auh#Rjm6Zb z0gbzoN4BcFc`0utc{hw9$K9~qnB=iA3DXbJWq1lv3bsw>$@;iOeQKWRF}ecen0kcQ@`GAOF%v*m zS~mIs`!&HUmWglx)Y)dqS+U_YE>AfCQP4u08$EgQp>JY;#T*v@QzT4&ynWDn-`NV2 z01b?$sOt&yM0MXTqug0?Voth=?Sh>M zJbg7NX^LR*SrO+r^cXGl*OB>c?gnmdCFwc&Wf8!j4ix3Y)CK^zkp`J&>~8U%|G4)d zAmxB(AK~5;4fU?QdB|}WywbB>>9yj7yHZYVTuy;WNTAl*hHKQB8DrrLAb8-;x~T-t z*tPj%C16M5GYiDsDZ?IINWrCQ%+@GbHU?5!z;dxFS74mZ;Hq%tapTSPmGI-r2dm&xD-7PpSXE#x- z_JzfKwj7S@X!U$u9(+&Wtklrg*+p!J(6@CC>-uE~Au5$YBdgen)T7EIp7DU#k4Aq< zJ|GNkGMKQZ%2y_+DdlDP9pDRKC37M_0paTy&}d^|N2L5qc;3OoZBT2<;UMQM>oWki zUEbh&x-okNy$m`@_4p_pGQPa3u_}y1@HIPH-`z+o$H@f*&UI!8^xo2&0n@oHjj5Y* zu;F3-xc|fBp#OLzZ421JU9AH-Xp3H}Xbx2^75tm2RRi+<){}?bcg?%GW{EQ+i{Dx! zQ(gH){}4o8O4Cp7?16I@ZYa9V6=@?A1$SxAU|FXy#wAbN?#^AYN=3G+Pb1w4C^-eh z@LARdIaX|K7*1IuI$6M_MuZt#tT!GTXa`GtPJ46^aCuLw>vg64Ry_!NNslB%P8WXa zyojlFAA;`cV+A>5rUU|ME`|p^tChj{OBz#XJsoi9>n%XwY_}{WW6phih7lWg;yN)i{?#+aIcWF>1&>bNFr_t_VjZTrE})(8;ygsnpa9sc)st5e zwxXP@*IfxUvXBL&eW0w#Q$BocZdhj02a#fu2eXO7jOhUQabelYs1pORQ?(wv1K;}4 zYN26(*=|5!u4Z?{{SLs*%y#Vfa99>NRr_4*;0Teh>Ext{)&2YXONIW1*$)CgTPZ8w zORS&4CzuyokNU!L-Kgw%U(e~mXWBXYD%(nM1o~}x^_A3Jy`z}F^a+dx6j6*f>E=R! zzLP=?Y37n^C68d8cu5_e^#wk+sW^?>B^SGj#aF1mlz_O z!p`nJz7-erJZ`F}xGDdqbyc-@zy397bKs^6e`!g1X)JC8f%>&7#|XYw;QaN9koR>s zdE6rBHD)KV-Bxu50QUyRrG06#vZa_iE|K0)kebB|Lvc00NzijO#5Ipth(HX_9a9%y zh@iy|m)elo|CthqlagFZ=UH8wtqD^sF}{M6FuvufGb5I7f5yAXX0Be^kgA%SRsX|h zx>luRWkHT3%FLy?EBGZW*uW^8myMagT-i5S)>a!uxToJ<-tY!t){;^ZL~VENI? zc2US7ofx1w1=1%fjY!oFX&9Hq!1*Qf*60LaneQiD}t8A0kZ&bcc$8QKE14L zVRv5g?SESDF1zu%#zixif>@Lrl^gt#F$d2sjP~t2RBm}+zRMUsc6Wc;*6ph{i2Xom zQ|B7kd;WEe*%($IUS=-=sJecVzP0dpQ5Z3D)Bh=435wlN^Kwro3Oty(!-n-1-^bo5 zQkW}tNNjiIV@6wc#~e6o_oedv?P-O@c1j<3X@3FgKm6iW_Foj5a6q9U*1|Sec;Phj zJh0(S2d9%>8-n%?`u@>ae@wWmQQ$bQ-QxvbVU5Cbto9x<5PgQ%Ns@X`#`x$}Oe!wK zwW;u+;bfct`$q!_yJmF&ouy`ucBs;B*`wT0vNcQ z%2y%4#Vg$a@jju4)U$Ifc<_$) zHNW{Yy+;Rp;9D``z*VN!EcByscB@2>ulDW7PnYx};ESjNft01>=AhyF=9l@C(4ELi ztNKcsbkD;chnCz)yqrnql=hC>kGbgIXlk4=Co()70_{HCFY@{)J0Ka)+d zWktR+oNaMKvU@>a;`wagl|fR9|3F@iD=L3!q1Bd zKSa~Niob(*n`&d)S2Lv@vBl~>isNn^ZYuF{v0(o42N~fgkr2DL3A7txnRIn48wIn8 z?$zp>U$=Ny22z_SROYzE;xv+gEfq(unVT~G4p{4;yGVyL#(nVnXD1M z9T_AqzL<3tnO4zOi04DrR#*wDG48|58zZ27jMgET=J*|LzQOriXvKGY7cBXtu~dbZ z*L1FK(dV!QT)!RFbq((7OzXi-Y(CkJN>!iLqyLI+{koM~yh zXg--OPaH&egiHFQd-b8wKSi0!{Qc7lQC}A>eI;wWFP62g*K|H zmObuIZ9e5iSSBP6G03;URjSOh4|?BVH}dSA?A^SF&?(K0A2Xy)J+Wc1exZB+-uabV zee7EW)ckBX8DloYIf)Nf^7`;#Jnwr--1ojRqr0wiBvCVax5hv?;(8E%2FLa*KS2F- z&xYP2nU}n1{IcGcd^J{BXKk{q=O*Bg5MA5fq62A2m#7~kq&x;WN zQO}rD%8C!lW`5Cv49+C>$tPusY)g4m`gVV3bR*vKQ@vdE)g8Aw$L7=vY3x_bFO0AAZJiWmLV!lPW2SWPhmlN9W8t$JM4VNzy>^LsmCikr&FdoL=OuR z?q?;}{cQ5dhoVkiXvA*84MB6}Clvtt`mJVnB=mZR%Om#*#swG_@Bb#cjHO32Lt zBCF5s6j0mUl6upnb>`bP?oxNe&1i@_XH#R~QKi|jfvZyTuDSDxfY1)X7;+T)Hqm$l z&7h>29TV5f!yzSk_rvXYx)q@U8$zt3`OrumCl|x!Y|$XpqqV82B6vU68u6o#xR#&8g7V2Zg<{3o5 zko{UdS+WoNzUZ{AxpiJjW;C}I;d4Blqbb(hu;@Jjo&>j8!-()A3_x&P-q^GBNUM-> zESt-G(`_BgQiUZOj2h00tILD^dg_`MHYMM%+Ii9G588U9ytf~?Ua0AmzJ1GnKr?bV zobAa)mj%buen^iebo~RVDpKg6BF_cXaG7F_DjGzvmY~rX4qXQ9*_Zr*VY@am4D%7t z(gbvp*%T@}{Od*6MXueL(fZjEm|%F|8paHiwkdrRCe)X3pLGyQb^;0p?o`<&14xT* zD0bXO02Xbc$hw+j%GGd32O9pZ3(sp$+2?6QRyX)8u;|9Q&KMoZSD`xh$A!62DMu%p zAs~=gcCuL8^09KJw!@~Z_sza*P0NYq<;&ai>huAhP9z3qAoVN}jGO&* z(#O*By}CdsY1K~(lt(%=Ju_t!zz2gkyXT#uapNS;(&+z~uRLU!JG9)FkjvtTR_ zx%e#`rP0MHmjE2`K?j!(nPK{&nb_hxSeyClb2v+tC#f$op_54Jvc3VTqF~}4Pt=7? zNx|*kj z6L`O@%{QYKkE+_JXt_UkoQ~KNhXHL z^P!<;b$WdIkEaYkxnYUY^s^7dQoN}aE(?G{#s75e;spX2i0g>SLK}MJAM9$}7AiTf zCLNm!J%2Inq9A)a!c}nRfOYrT`ptGeSyGg;6?h|AP$UjHnC7e9b%A1oigJfrS58v1r+B9rN#9h7 z@r4MvzXA^7;Lny)#YQED4!eyavTv`Q7oX9k3K@J|ql=a=A-U{Rb=?iCpW-Z#8G;)< zU(j^c^+~JRFZUwVM+c9+Q|VFpPNNPKT(&OwJaP8@UJ0urMul3PrpTFK&UZ#3)9qcs zL`CjZ{W~UtlF!fK!AgV^320#x4I_iF`KQ&m3Nco_@cpo++6sjTsgv_4IS4X1aW)v} ztot2#xeUhFGLn+%nb8&{gfdtp$9CdEnfTs3uk4yW>O~5JU(BPx9!zstpZAY~GvC9a z?_VD35<9=!c?}n(gog1hA?YyxdxvcBxuADxoD7SyjEwcT;6&7m{+iZ5Lz4m1R+w-x z95HVQfd7Fx{toxxnakl!Nx-;H>>{xx#q2V6DdkglE!X{tQKltU`_ZU4XME>FfsVB^ww>J@!%7t2y6+eJv-4(m9s!R{zX5JKW1mKn22N2A1nv~(IfoP< z#KqJ>ShWJ@lQa(t>s8(-#`Ond?VqhT1w=P#H+W{6JnI&d@R*w$(bC3%Dn6+~lIgSd zTYy)t&Oo>N*l-zJBk$(s{ykFjZD`+|EZrm^Pe|~K5Qy{eLU3xWrs>6k7gjaJgtZ6- zZ}rdDl&GwpmcI_3Xb(!qC!U2Rk@FRMyv-zJm9;&GBr_42#`|m;Z?XKVqlXwEE8X9h zdWG_zd<)29dBHx^!CTj`&o1~5o;##!%eOdFDU;kN4+h`kHmk+D(YOw%y`gN=A}jE6 zJ2P;+U8RYJ%xy`ZttT@AzYl6MJ9SpuSKB|Fs~Oynv!s^=`~1lidg;WhEtyp@E5+wc z8&?GGkkD^EPBdog`V%NIKRzhHGFQ#agdauJ!2}!DS>sHOaH7}m+OTDIt%rEWh+0^K z2Z%PAsy2PA&fn~kzHXV1*m0=VZmi{e)4ASB=JScmY4{D5{_`4B$IiEDB+O${a9SEY z0ecVS;Sv|o@#YJH><>n zaCSgAWxOzT0`TlgFfd-0)z?Px@b4;O3pEeJnvi+@d>4IVXmwb2_#<(Xvk6wz0tiv- zi*Kp>W`w&`s9aRIh&z+?r=G%F`cL!bW6lD3fbyV58d4b z6!`EsQ@T4gU4&4~7p3a2bU19*S??OpHkE|p)i_O@OcC4r9GxuGUO>+0l--PNVA z8~!ILq!E*844|s|hIMjjPU=i<&Dt=Q|JPIhsOH$uY#X^><2U^ev1>jAW_}gv>j^$b za!zFj^};N>NmUo+2|2Aj5FH`?c>}HA1+1yX;pw+G16Bvt zMT$Pvp0K$MO@68?bJJh3OAJk@DRC6R7!^>7EA+Lmxcm`_N9XgI$u;U!vZNo)t*hNH zn}~hONVX6`kfDEXbW`a3ntZ0tJO9&Hk5KmJ6*xm!S_koF;xEi(7{pKBFex@UE15f~ zV^nz`#*@TyJEmLdHZ9yPR2h3lXHGTTD=Qw1?}XBc+@2hj{kF~2~KXJ(e^C$>`>AO<`EA74)h2$38>bfn)Goh?GANF0e3;kKH|BZRL}FL7I}lW^*T!OlQyTYAeKflD6*XoNOtADy z8mbOcL&lhFXu=E9_qI-@EIN?R5Yw#osq79i)>CtdX1B2C%s4)8Zwd9*ilk zhIK=xfBUW5K|Owd=FpTbs0%#ik6IT@N=QnRSBKnl{GA~?B4ouS@drGHpk2mzfv3iB zh+g9mGf`m(7bE$~wDofQ?o>&_>>-kiBR+F_V~I{vgGG{k)HJ)*R#P`h_kpYS#PT!y z;uz7os>bimO=YhdgE2sJA)*DZBzo?EZD`yTDl{|AU_`d-yQDu$Z!L{~NX z+^e$^<=LCsO2l3tF3Y9yeskZvIAThiZ&-TP{L=w4_=aO(KGi^AhLc>Pz4b#se>woN z^X+p}Y4i?9VG`#={6}}HsbY0XUHP9Xd9pXXHWvvO9!Ff_Y6LlsC+*36O~*sZ-KyHo zLA}mLOZs`tSfL^2jwS*i&&!MA4AlZ1`vmk#`wm&I1--d~#>3!efs=K%wuWl4syA0h zS{3eUu+fsXH*0o8-DXan!W3!^B8ce3-*m!f$A(78?wrXsE4j`zIvwd=IyOk_pD(?< zSRFc_kavX_t;JoU-}&}@s0L;!>xK+u-OAFBSjRW<0c|@5Lq{>H4rZ#fz2L+uOvauh z(baW)k#r2gRHRIN%4kRUiC=jJ8U(!N3>C;zW_tTUHhHq%^wyYwrjMW1D=fV23}PS;GB8!42@{cihOUnQIgDe>Y;+lz~QivUHnbc{1%PoCwnUNlT-rYh~t+Yg19QZa1QL8)F}G`UIfVWg2BcC&5ScK)A*gU15zC zV+6C1kR3FOJ4g65cYUW`Zdfju%3Dpm(wEJ6epYugltEANIYD7)xpWu!N>2U9Byhwl z%5BDdx{rP^81vg$Qjm3gb0HDZ}m!zwZQYi)M?D>DaS>(+L& z9b3=csFwUk?XffpU*iIC%F6V&8{N;BGakz*^i5bgT@V=>ceERuVBEDLP;Vg3H8@!R z;8_}V5J4tQ+-0t-lyqkXb*y%8On1sPxk@A15_D zWqYVDNhicHe{g;PlLOXEK7mD7P7jI@ndrEA*uqs65s^$z87nLe2QCA`3&?YwLrEZI znI*xW*|ca)CwyTg@>3Px1U<2~RL;4#T$f&iULJGj5!q}p07_AG8n1+G3sIBb!EvFa zi6AJugwhS4L8bo*b8q;jPxX2?18MkIJ6-UjQU$z)Q!LTCRp|4{tHrk;uJeJ*k_qPG zTJKmtR8@L$;`{a~vm{)&OLfbcS$!L8hJuZE9B?D}Ji_=+|0suoiwO!3MT z7BYPQa&;D@Rrc8FO9$LRcg6yna+xN?A2M4|Rx0K)yxaAn(qtk;s(dA>W}AiJ-7^{B;Rdtme^_JH1JTq*1;|3*8Yf}}92 z7S&efD%t?>5`p>INdMz}CkvD5o^35?Vd4sRa890mc zZD^FWCMxp^HATU+PGVJ-Eb-qX+E#tNc+QYrRfn&~c8Z#SphlBf(~R5$gO@;t>gZN1 zsbYhRa&O5>{8TwrU)#K=ao1Xs#Q+CGafmr8W8r3&PE@1?tjarbl;wu@j7|cfB7_w; zq5($z8s}?-H0rv@mx0PZU`f_%EG}4f7P%5qXXdPFE}_OAp>+bKE^m8$;XWWCt?K3g)V_itnvI zh8mM44Pb&%iduBQky?WG4Gn#aFE#}}#3b<;IfQ5quH?VR1g|lwS5*o!p7~`}v>MzL z)>OLPQY}v8GL`ksLn9;>Hr7ABwyK$}V9be6ogZhSML(K~YKal5<*yd}Cl-J*JQ}_i zsq*LG$$|L#6WaBxDUjKVOrI~hQy7|FKPkhBuRh)WTwNPB`^ACrN!SF*uBW=4F&tis zd<W7; zXv|h4CzyHqW+;B2&qe$*vtrIlSJ<1BNv4T&XPp}Q3k8_3(2q?1ligeRAD~t&{uuP< z8kUS@#l(JguM)J|V!H4G)-|GLwA;4_X`M1NZZGXJ>YUPqGOQzojuKjNzgF}mxr5<6Yj$sSO+jBM|ln| zdVx^qL;;LgNr^waWp;0ogfZQ9Hcy$fzDCdS9=Tljkzntr*F^*P0bA8_&N-PL7y(#M zi;2CaWkDTrM74SvEH7-Y$HW7e@qZXx94dEw=?Y7bHdxsyfBB*{sLSERnlY9(g#VS(*FVg_+x`q01SuEz?1jeWC1H3@)01CSpEv@w{GD9{bfc?V^sP4D|LX# zBY?>CGULg!U%KTBuSf~J!ofTDZ_`B;y$%1O)tFb&MC#W;+W7-q=IWn@sS^BeRQMjK zK%boc{Efm44*m6yI}e~hqBQp)k^eE{|1zj&ENQTmC0zZH-VIIz}FcwP*mS7ltqB z{*sZzJWs15gnl9X2YG{s8R4{f`;YDXr{Oc&8Og{Jwp1ZYbhO&skWQ^!ZuI{BXH-B>cjoL`{aHJ4q z-|!iSL1jGkV@?hgPi!hs{Q+Ep?Gw&!5xr=R>qTK!|lPn&;2K{No|@2Rv{P zs#X{vn2&$W$`W$iu$pf6Zd4yi4E@~GEv;Q$*S+OEtRW1Gix#NDa zrVMM7i()5}EB&4-Fn9q>r}n|{)vuqAem!?3c1q#)Pzo>I;0|c7O2)!tka~i)E@h9GE<7& z5s(~x#F70$zp;ELwVfie-2>MGz}8<|Ro-p}+^G88+E-S%6aiFQwW&AUWsp*t_%=2D z8MPbU-(6d-fAqvtXZxMUzKK3;+pM^*!ryqTQ+z4s=@KEQ75Qg+W%f(}%4{Rd0?3_? z0m877_5pZ==m;V%Y2Yezes5r+2^1t_nd}lz`uN*a03W@8b@0N`dj9KX{+0(XM+jT` zI)YFUh(`1v#9ljWxsM03vTsh?abGA1%kiPzhy%Nk`Q~DuZP$4xl?I5Wm^z0C4*v{} zd6TL%(7#hMpar&~i_6>J#B4MNQYy|LICMF0;@AcV+8{P#`9li$pWc|KXJwyuQGbi= zk})ziFi19k_8*qO8R);_W=GTSJ0nB57XM*qPymG+q5;2H($xZavpOo5=k=0BrBQDz zkXvv-&QK>Bg!1O*!kQx);HvoV-#@1!4zjMs{<@sL@CMXC*Hs+e{w7zWl^>)Cv+V+8 zG*6bHH-RO?o{PtHP;0uuSu@me41ldtEPVfl$L%QdZ7O%Kt1M7mhND2UF7hKr!sirD zXc`3**Khj__?Qy~uK`V@j5(FpwBfpDx$WrlKiD;T5wJ ztGAqDX1mt}__trM^J=&3b1Z`@Gk{=TpyvZ#Tcc!sC`8_JFRb8k_nX`XK1vAzl78E? z8|U{)40{kS^eV;V0G#}IRZQkRE#lowT!6KKmK-)aF!$|J|MZ_k5J zaNo;-=VGIy_2!gF1--cAixWTm`g=fbki!8Jfz@iq_N%xOu6>{|n<`C;^y?RZ97B7M zW8LlsOuxyo|ApHDNsIY#dcP7jeERD=`7uiP!fD0xY8Ny98xijZB6mD%SiiALjvVmH zju$xL-$xP$OnIMCW`E?bUo8a!2jT&c(O-=DZ&Yj_s9avHkNmbwAvu^!NWY1=e+t>( zw>h~B(1?L)&!>N1rvLDtXn$c8*irz0mX5;y(eM5FZLI(EQP>HPh!Q;?D}JZ^AA3Yj z4|L~Cd#BWYApYl}_vZs939vFF_6Cf0LxynREwuKg)4)Kib`CyF;nnkag>s=Ml;nJ;a!u>-Zy8LF(GDbN zrD)u)f4*D<$xoJOZW6!gR%K8wqyj>H=*k0$ICkp8O}`QUo!!bBB&>x1?qhCzTc}b( zqm=hHhR4ywR659-#x!k0Yo^YYrAUSPyCj*n5m$ynU*iFx#SWW6C$@coD&#auCO3)h zzmL<%5zZcW=`lL-SI3O?gpq7rAOPpWi2nP78gPI2XdKR}MZ-7Z`#6M+p~?-_=y*`5 ziu)zme-{VbNl2Z_zC^5R%s|zYXxa>7f~whGDcVN|FMuQy3VL$X!*BsuRXLyj>0Xp#GP0 z^Vd7}U;AFT_Cd{w%;#KyQwL_0lF;N|LWcjh-cH8@Qjj}+<{z^aX_m0`QPKSO^T01Iwe=MMUC~?*>R(9_kjGrs@i4Exa{q#=a9pYj zIj!rj|I29hzn?B4=?|?G>*08xxAzdgT0ySr*f0eJM|t1Yb~pMQ58nTbG3@Xt)vNfum2{Kg%vmbn zc5?!-S1(X7leGpS{h9tvnT~29!V+u^j2H9&#i<2^Q&`m4%s-SCCh49N`-n~3UGv05?r$hRm;!!aRJ*SzjLX2LpaGyYl)}or z04*QW8!avI*R%LCu*R!Oc@QuwIc*KMsqN`AL;9bTdj&`=|IZQ3R%N2|-zOEgZ;;!) zg$F+wk9zZ)&i|jeid+Uz&k}Ow@EK(O#(?a;Jeb_SWIUgY`Wu?!|8M)>6=+~iq`o?8 zq#2F7ZCB>ztpIpH-xU#CzyD@I`Rm;K*YmLTqqr~J+(#!)HT?g6$ddap%0JxMgo5~C z{?4zlA1uAt+g9bwe}~P~(}77LH{?4yyp8|Im+R4o<g@1d;^sOsJi%CZM)Z}e||xfS3CmEV}4+j2K=W_Ut&(NcaN1eMC}|K4d+Lqp z=&ToXf1#%=p{#Tl<-54v)!NW*)MbV_;xH+E4cCvQT1hJqf5FFnmJA`V>X`)$(j1N7 zP2iBOHA}15z1tMh3`R5P=TxW;x!3*54eer!Y*p_eMs?iv9+C~qh_A=&&%2;apu*^- zd^6}v(Z|EZg+|#YJ^2{dLlk_An#K#wUOJVEhTsAVXpDbBPBOOg_LRraf zKhPfb?Zn&bLxM;F=o=Y6GBddj2y*}9ZfQS#pM{iM=VcVF_IU}Jq28n>zP%XdqpD>oUJJ8hR3P2@lCx(>;F?#%;QFIk#FD+pt#ZV5*j zDSo(&Kks@k3#S757oljJ^ByORkod>3ciY%ND@+U{Q!FEx$IB@cOhnGa~R^ z{}En+$Rf?YOrEv~`(cR^8U3}0fvkEmz$zapx7)pUiTX}b^CS8mxwJwKcQWBAb$KvpZ9J>8%f(fx<8tUeLy4<>n9^6 zv@4gYfEff&OJS#%Jq=}pL;ZdW8+JZ^!)>o-sfi=$* zZ(w%0DbRB^rgbeBq}F^=;Rg)r`0jKTYYdY3p}kQgRoV8@ELn%&ewq8HtEq^qu1$=L zmJRbSbs@FbUiUDF)*{OpsfW5=_nmP8hl{s&ND#4Qhowuh7SI)R=njJ&d8_A@Y{2Qr zdU5hbdl;r$$uJXVntS;XRMQ>)y1w=4(a*cwgvSqDIB=&s7QaJw0rHG`dYS-T-1w-U zk?nQ2qr41p;x1Z{FoZ!MEo$g;;KN!JYDckq8G0sJX|W$gn%NwfoC1pm?_w4};O1W5 zY&GDb#Y~97LP%q#6{idHq&C^;!&?~4T7>Tg5BrHl?+qcC7DDv#l5Cc&OxgG*jOdT; ze{Kzz#(OQg9AS5&L;v%x?SZ>apec)F3&FT8EobutQIFAX@76B4Zph9Ak90mm(CM34 ze%wDbBl>Z_em<2|(P@eoPg4#^28=z>%R`L33Zwf+z;7-74^3ws*7Wzke<|ti93dqj zAV|X$1f&r~Qo1`tn$ZZ7(jlXfH%NDlZX`sy8vz-;!Px7&&+oc^f9<+n+aJ5mcFyyh z^StlJZGm|)w>p8SbM*YF`lw?*G3{>+>ZHJCCun1pS=X-NnDtw}j6(kC%$<>Im|V#_ zKWEP3?a^^v4|>tX-@j||{8DDBHclmBFBMz^US%!lxlcMJUoi&=x8*J1>&3n?w`>#t z`%!{`&HKy+zRfj4Qk)I&R`*~o?;m%00NPdaXu5t8oganwdaz*5nUi#OjkSo6<7X&4 zKj%kc_2SuzPp4FRdPK8@oNHe=>6)>_si^KBLJlV6&Kl0TzY6+w-D0wT27IAe*E{8~i4T6kB5pKc#!(>U@bg;Hrb zSzNP~?9x1g2J?wXf=hE0pUwXCkV(x;;F(MHtI|nLbA|X=k#c_2=&PB6EW;^`tR@iE|Y#0D1L{d#=|JhAbrO*@i^K! zmgs)1tg+}Aq%aB5mcT1|wURekKw8tEXSHnpyPOk1%q@7GOsAWH?5haC{hr_q()K5l zke1#O{SJ{>i;9TA-oQskA%!(FWb_F#DfeNYOh*(s!Io%&2}m^3-Lh+MxJ7=ev)71A z5cu@&#{|Rk@6mgZ0=#|gXHAJRmS_z`B2bAk2qKqy+w;mXT6>?f7vd#k9-1q3g3AGl z1iGdb*+fuDt+vZ@ttqeB5*AQgTxyTtlw!KcWAv5?%e!%h}0VkG& zB<{P3lmZdmg!{j)12#rs81!MyWx0{Ifnc@Bl`Y#$s$q2imCC71^sY|PKNSp7xI~`s z?4P#KXN_mpAof)az?s8v)UEGtbAsG5PnN5N z-zvJWN%_|?&08K@DY%DCx5yNpi-z9OhD|U>*X&w>M)$ci0XSmEmt%uC2Z!}QqF@007J%J7#9&eqH)7S;CjgZ1Psq-2k_4ttnfCB>q0mgec%o?V z?9ez09}u``dEZk09OSZQp;xBK-`MJL9?oylT#vlotyz~5dd&e?8C=w&-o2sCfT2O@ z;LK-l`!v5xsL4{oWPMMcPbsX-nRY$xz3IES1qzX8XWt-qx1UXQ(9GB57_+wc?=tP( z*Uv~se|_w*T{Um>!b1_Vp<9li+ffA*RUCY_{1=Tj4lTvq^+sV)M1EczI{AWaqgfmg zqgm3$Z4WmqlcTA;lbECO__GLBxv&) zjcT#p#`onX2_>bSRz5c;M&4T}ywTwg(svu5?_KwsO-4IKpr_28<+oG(a{`l(UUnD% zY2)(y_GG9;EA`i!6Kn*#!1Z<1xA1e=^}GqGdj7W zLZ9ym*u_qSr$E}R07O116&{Jq7Zw`Pdw4~C>5ntyM0Y~4;tcy+7Ay$bMUYNRYJ zUAvJbREG48=DetkvYuF%zt>y~AjRk&gJV zI~de_94)6!0s|X72oj!vd`Q}uZ1X~En__gC`10avOo_UY%SozNQ4XI&CPU(m2EX5{4*c!q55uowp1+cQH{ zJ8U0n4XG5)d7;VTddT>3mZWm+=*XHY+`nun#IcT3kZ{VT^p~9)SiZP{C^->Jk3Hw) zx2=_or&i}$4Z~!YGG^E9T-oS*+LC z9M(1t5o`q>;7hhj&7F7xsU{|aRIms?z>$e4%42I8->p0j;oWj@ z{lGCq(fdk=060E{BwALG!w3@q8#mLU2Z&j_jkcwB{6JPW^9KEvITkSDKCT16sL=^m z5UZ^k!{$Ys$^9L|n}08S{=TrHA1s8Va7NYrgW=toU=hIW!3_zrpZ`fr=C-`1#E3c! zAi##t!gn5jFDCje6KqoHmeM{s^)uPBjbGdM?V(6A0K*wXV^I6le}Rr399f2vSBl3V z%UaiR2+qXUNXJ_3a`Bdp&mE@JvNu0(_(R^(gI}xzXjz2MgodxhFAa{)7i`;@@@XBC zS@{UfBI~;r%$t8f>VaF6L3NM)-TMvyPZKkzEkF2_)q2~@c02euviM@$;MrC#I3dsTXzF1KhW3GtW^?5R?QG;Dew{40X&`8U+sw2; zZGh#L{^}q{ZL4lGVk5E}7C8iiqG8rkzGQ*SF|_yVFkl<~Fr@*>S$TDD;@+XkUfAie ziawBer6b4)S|le#BM$chdjks8jCXn$uMIYnR?lrGo`|n^ zKwwDJqNmMQV&L-#r<+!+f)eslS0+#TGI079WVxuK`<=Z9S}=usJ8%w)Wrg%&W&s#h zA(-8eg%9ck$@ME=VtDsi5BOvQz0hE9`fmC$r!2)ui0@cF+u2NO0}Lv9FysDY*Y!}9 zao7xqDFc?k@Q-=;!iu{=S2|?Uy~Rlin6ENs>aR5o&hybNM|;X$bug@(Ok z8}i(PLiS{FVVZZ;y#EBKI?!*_gc6OhAIxk1iP>$2cAMRUk-I-C-Zp5ufIPe?Ot$#8jZHU3_pPU%e0An1oYfd`=ZP@LqHd5F_4_O#7 zcv~$( z{41oBC!gT?Kq1`~{>Z(|385C<*jJeN)9EUsj`i2dUEyjEBs2Z5-%k&`p8908TSLsN zYInKfu9M|+ESY85ubKlER%Eo_viIA4OYq0wO$h!g6G{hSwKs@A(El72kh||zsP+@n zC--hnL1X)hPkVJVCrp3W0FUs(Rt48ikV9qmQ*N#Gx+UsAGhvz`2t%YM8e3Vk6)jS% zLh?dK?yZ&>5>m<@a#GS_H)(un)j{S!#}NFUhtC-C+>ww((Np*>-GJ{Eq}#~)ccrTD zS0RDhmXVM6OfM}R!myREa5kN3)Do4whlfpU-yI)S(yMzu9$4z zN+TI{Gh{425IkM?vE$~4s$;SIkDu>y+UM&#zdImg2+-^DvR$5{K?83Z{p*)nEfd;6 zx%T_tr30H8#o;fK&=>NCPYEV+BcY3Ez&&!K6M0z{c>8M>u%YnUop5$qsIoK*3{IX- zo)b1L!A)a_4Hg5Fn;W}r(Ph48e4oYjs*s2s*8R4jsTKFQDbtwLFgxasi$cCHUi%ZS zPVK+Vc?G)*CJ<~4J1B)57%JsF->z{Mfz3aC8#%YY$RO#%p?D>G>e^oCuNeuHCT>g zW`$s6q@u)h6PDDWoOX7yk}1(Sjp*Vm)k^S3B@dUyAKm`fe-yB@Z7Pg95~YIH3W^iY6 zSNo+hN{AcnJYOZ|y3*z1eYjFZq1$=BY>_IDFV~J&`UoZ5jhO93--$F}gufN>tu{g8 z5f~92w^P~#7GI|=aK8y2yvCdOT&RdQnRmz-)~WYl%lp^Ezm>CzWgj=84h(J~-a%ql z!vg)zp(@7T10P}3U?YZ!(s88?Z+@?;6-gj!P-AS%qn#6{OAYqlfx5=0TxVRje*3`` z>x!aVFLK!-sA{3-)5R0)o66;FF@oR75x+JWFy}EKJ_E}K%)PHM)7T%nHTWsQ_}nI0 zuByYYZg!DR7e0_zS7uvDSEg~0c##iQite;5o653W`7f(;u{r%{dW*ZWNw)`A#%-`aL5A{FLv&k4iN zl$~GoCO;3@@Yv~p&8>?@MHRk@!KN8pMeW={Z?Q)c!IHymH$kWweBBb=BYNTx*J8)D zvhSuH^PY*;t!8DCyqe-%stGFEb8$|@`%~`GWPz;S%dp+9d$hDYJojX#QW6o@;D^6n zPl%{9%WF{DsnNlqvHaMV^vdvf7#jHr?iuzME-`R>TQ%X?May>aCw%(vbV?6PraW_Z zf9Y2^TP|n(xt#ySj>)KXQL+7Y>?|xW$;|G2yVP@YDkkQ0caVSSYGxMXsX?&bW~Kl+ z+tK8?Lt=BY|0SKBm|qF32>3QgYyORU_vAC`BY9Bi0E`|GEshT3?P22P zpV!QnBef2Ud?-WcZW)jm-@bsMXW2_W|7@2DI6)ns0l|ILt545VNkF#iIk@>255~xl zML^76B{wX>9Onrur0PZ81I+jKGoRcK8VgoUb@`uq$Jemz=eF(s!G|JY{Kt(af!OdS zvU>r%I_;W|F~L*gcF5;U&u!1^8{Ic3ozL!x#o{pvnb@=07K62FVHLaGz z6NOYJL%-1SC2x>0I~_=U1s||fe?6YJOgvsvz0rQ+oyw!7`M&0#VDyvhb4klP?_dFo z+q~HW!XSt-4#dd*5#S1PZ5mUKXbbFeT+8upVU_REKln$)*WVWSjZr!vEnD81r~8lM zMZBC++eP}`aGxiH>Jdq-Nd|X`WBWoT;Mb{&XFwx8 zmq^Srkt7Vh$187#%p%db;K$~AuP3}s!i1#cqr}eddl{$?CY= zr+xZ&_2Z>KAj+Y^C%to{)LIp@+@;@9 zp2tuHb~jB*PAVR1b~f$pW6n|Sn_6u8$KzZx$*7 z)1z|!&6%o2;v`-JT^BNG8Eo$$wB7y<^S^{>qu*8sF^V1iB>bHB!%EkG=w(Ov0Zh){ zYfH8#5b4ntTbCm!4LGTR(CH!TR@G)>54?Mm;&JB4mOZnUO z?BJIPg_lkBc-VE)eNqdLb|^=3bM9F6QbX}%@L1LP z0TaNYan(vEsMJt>*W~2m7}XE;yY}%bG|iWbdM~Po$dEucC%a@*e)d_4;}!|fh-sE$*Zk@)zL?BniY> zgOaD&N9wkEsJ)$NyFLrJGhdFchu{I=1c(TVZZh}CjPQLC=Tm!a4e}A5STJce5iP&l z{lB3jnEli+r#l7b3JAEJ3Yi_4ah^yU{XNOtADuJs*Sxhm0VHW$shAE8QJ3c2eSN~< zs1OgCE$eMZvd#ZaV^@uoa{Zrt|LSmtc8{ieKOOs(P=nAQ!{2i_XP<#b?p;-z0^*$?i8K4jMbTo-KTp$XK;2pes5??-t;!Fs=S+;Pe4{Rf@U|A@KmPYb;s^CL_hrQ41_UTp#RN%bF^F75eYz~g+XgU} z+JTW7J1T{OMZ@Yv1Fh0A+DTmK9;K%l%rbQr4@unzSv^cCBRb+s_(DD|zf?>4$z@5~ zP2YNov!uB1_AojCS9$l-PBuLmx)3L@XHIq7HIh-@POhqyC+lafWL6-1ylq@IjN(QYgo}bbJH=DhMGHn1zh*-2*V`2P zAQ-He&M6hZ>2ox@Ru$x%{>dw&DtjVsPXkA5W=xSbhN3MIV^|@OU{;>c?8vCo=3$id zbUQJgFBBV^@N}0u=M>!m8~nUsr6Niu8H|SGq~&W(O2V(Kt>fs09%KEiZmG^1 z7jWHEl9ReSddo0l(e<#cQK;IBjW@B{)?k`~3wQ;|p%L$%13yhVjMLN=U#A@|7snX2 z+q5-Ch#X923x%uULlCtoc2sGL=rhPxACgC>dYC5P)k23rhfc^a+&&E1!XS8W*C#T- zZD23*&{i1KrbI8@AWDln!8~6RHh9LW7q66WecuXaxO=!hSz-;}7%~=mnk#0X?@zm7 zqKKMm+6S(51A=R9i=B&()(-T=AZW!xe0k%Id@%l4gNq_01cJe;N;Fu~q~Xo~5g0x8 zW8KxKJPQwp)xhqUj{n|o8})JKHLyWYYFFG<+qPO^j|kM^c2klm7bUdke;4R4M@Zoy z)X?XDsKu3@`q^clzqt?hmKDDg`tD@?XK~*(yEukP`j3fU4oz=`YbP-27A>HT;=lHE5cowkK8Jb|+!fvMel=RM$R?6ZwF3K;dij_Z5DZl&Uv}D zutM1XHe{!4vP^kg!kFA+JpEp@fxvpM)U=H_E1aTPCs)CUjWXo@T;={L9`SaMdE=pL zzwW5!MxK=A*gJ+e;HiGK&5(7zH=@PmnlsX#dFd1X*)p>nIzu=RKi|M8Yn(;KZ*eYE z6gRaP^5Z(mr~QtqZzCZF77md=)^Uz>{Wbo2I2c&gGYhlBHG>pmjnZw2q92&T_|OuZ5G)gM9VH}_Eyk8*zt zA0&R()v-|3zJneM*3t}hFo_s4J+}$Hsxt^X3wK5St*p^3 zR?*tLzW7=QFLjn=DytO_alMzvMvk>>v>eR&WBDy9F8l^MKD$wqtE)o(P2PeQ&k|sx z6xta=5pB$jJl4S>I7E9$@8Z2Yr9R+j7^F3-M~hn)VP0m+fAT=F6vjA>c3TU~;nuK> z3_b4+F5r#tJrqLon9W0mPRMZPQ44reiMf5b~vi?a|&Q zaR}c)y94}p7#!4^@~jOGN&D1+IA}MR8M~;!8pj3Xm?zgF$8ta%l9xw z7=#rcohD#HXVykt0waK#wD4Um)xW+3nL?<<&@4eMXu9y^faNQ-?X|zd;Fs(Lq4dFK z{uEFZ%UrOIjITCoJyRW{ zd9L&8?RDhp@56*g&csm+Ez zSOR@#Ng{fy->7z^ho+}PV@vPJ8Az; z%e=wLaDNamsLu0q$`40X*o@XIvfp+|_}>_vfCB4VOIAe?mRF1|XdZDOky%V8k;HcQ zN1a_?uDcP-D|QeH@(plXqpENHh3K@r5?@1(|w8M0yZUYn06mEK`U&T=>=3IDAm5L)}objtB!$I zQ7+B&|I+0bi%(j8H&R1(Uc$lRXjYUTkyJoWSgxXKtw0I+uECjKtSzx2yxX|xzgoT` z3*~m9>2W`L-mU8&bqM${%r=fz$O+H>BxF7UR!hXb^}X|0ZlyMCz4d0>{kq(#{{iu1fpSXH*(xAz z_;JH>pNq9AMKhb+V<87Mw5+M%aIapiy1wndPLgJ(5cc}boe&H`yUb_hCL{5t!kryO z`;gnOk%axiMWMG$4fYrX_*O7PRMsV}2^B zTm7&D`bmXyr&BmS*TIGS0sh_JyN7SIH_M7( zMgICX;UDeAkZ^@Hs>`pa3w-)@` zo27$xsr~$VvvB)pfV*Le5hflzjWOZ>kS7RHzw7yA_Z^iXh&JD*gXlP2+q&gifID;< z3<`o%cFvk_igV)6>Zu(LL+P$asK;hQ?m)6PZT{P8e81Ucf2C%!d3Y0G^FKp}AjH%B z>jOw_VnvP@swBu2u0rX0g(cfr3+AlbcdBJ4Zd^fM{?t2PixAu#*705!1{oV_hrqXZ zR%HoZk7PADi%h{5&8i7wEiEx z?kZw4DlXeljA7zHtO@y%zj-{PCeoLE{!{08JcVXa=w(6CPrZs;)to+Pxd@$qLC{L` zPp#q1S6pAwR(aR2zUsfXXGnD1DSJV{<$4}ot`ZgZXCi3>a@K2qKT4w>cDxCER{Wwt ztmpcnYDRQHar1JCCUQwgiQ^enSNJ-Egr?xxj}k3XrN^!gtLMDgt_d@u-WH=W6pv|l zhc{V^LRUePapw3NSau1>%~J?j=NmXmI4uL4&W!+FtoDw9@yd(&qn$)q$bPIWKwpyD(HhmmRuUNz{7rT%9F|=;={92l?CjKwm$s{#uehYUYW|V4|8( ze5wN}Cab>+Lkgtl58jF6JbmK?d=;X@uM8{w{+^61v!Dhxr&QXIHHMxH7ZU^w7q72F z@Pei|I~Q^YtwrN`*>+YFa@{x8J`|eT{$}VD8OHaPM%RRN*-!rK3fwhvV<;9X5@n)( zF8|oN5iF|UC-1`~x2c|yM(>8wP(aQw`7e>J7oGjaA)_#E4tgcWh3(Gm&4TE(Y@H^e z1xbnzoYzK@fyPO{L=ky@KeyItAFE2f~;(?R^(T!p?W*ZLnJ!no_FW0y!JS zNpWjU)4ESTq zg2nNDkA8)sHI|j@FqW9Vnt)LUnOwQwwbCyDDbe>yIb zMb$x2m&;R--7x#3aHK9=ov2a44_#P3mRi5D@PqNc(08c8P!Fum?l54p7iaT5RubLZ zFw%YL`H8v3w7OnwRmS7UXKE&>WNbJz;Er*G8BC}Kp;Ps_7^nJX^Zlz1L=uo&U?6H; z|9};PT@T3czlRO*bS;6712NmU_%DE-GqydJmpclA~kr;kwglO7v2>!gDt z&Lo1+TGMtK`n+0lox#m+GLeq_8Iy2RKU=(tz2+4RAYCXKH4n%Z2-*FSC?2@3!8qf4zWgFDh96hq6Vj za3Ie6fUXBf7d+}8@Cp^N<&8d^JF?wOtRuC-2ofBBgfffQLT7QN;&dell|BC7HZ4;+ ztZ6%3{%8Ky?&VSgD$xbl(Wd5~^wq4hV?v-7%$UnIK5mU(r#!U*C*U283xjNsdM?2?BcKT2&ItcUe8r zSnGDSV;?BO3J3&TKwnM7czQJ%#d7|uee8Ct$81RTI>1NaT{%`2)r}GkF*zQ=hmS>n z4$>-~)H;KV`EZpP#;G@u!^xVsxsuglw`;yNpC1X-!BrF4aFWtuFY=Q7qO}4!zB2O}R`F;&_5^yy7#pVb8Br5qy@?X};Q~*r&l>c| z@B#}(5F|;0OG>j~#^q1s+w&dRavhd`0-5qtbsG~RJfo(F;lgHU>JMMR=jn~Ppfx*! z-G3dJqfTg`BVLF~7~9S(%$42YlGtm*o^6_01@yqX?e%O)cQ&H}@eJV&raIG&HtWpB zgj@$>1!Iyyy_k%oyBTUViQ(=)CjNAqs+1qp_;-ENw}kr{$B;@y#var;t#Uz$&PD`5 z{^467g;t%NZO)oY8P1(88jqr@9xEs2c({OVLOYcCaxl}pLNDdII4CZSUo)beBAO#a zcz6Os@SKP}>rQpHv&>&5Fw$@H^7nI-4^5V7={4Dl9BAj|`gQ3=^%pXPQ?1ulKN{)2 zwH|mDwl_Id#BNET6p&f(76ZZIGw@enN;%jazY^$66U2$HTB^^ECjFZe!xo~8yGAfnc0Zsa8g^?P#vj;8k5r5;5h}hRA^9~^8)a5T4jn8 zBs|Ayo*t=(3s(NxuR(tBvh`etJnDYS5VUIGUuVxHm(F?rQ@Z7pqb^h8WJ|*;wYiqBdl_7D)~)57i(|S)xywH zGjjQGcw^7=3U(F}1ia+)zo1z>gneyO9KU2fT_MMoUqySOJ4q?&DnG!w@^sam#8wp6 z6O}Lh+X_RM+^=()`y8WbwSALht+}udR=E3v;@ZZxbu@lsW$Bboox`d2|HEXVsU4o} zW5sCWO_Z}etSO~g5{Ts=D4Rm7->#7a@4Rqj36b8H^*McZz65=pRh7uP?A&TZ3=Dog zoxFFR-&!T~V-*^{vw}{A@%Gd3k@?jWym?Va+k1=pDJS@h&Es>*a0i?2uE+rU!gHc0 zD@{Ab8FLwuwYBdmB~>9mS2)AL*g0^&NoJJKvHP=u9Q!vsnGA~Fk8#NxU2J~9%4$dE ziNx_~Rn2#Dg~%c@kBXkm9mt=57aowBh_4lFqzE`4oX;BPYDx&b)m*!?0|{Od0Op1u z0_cvX&`^v<*m>CH(B`_ZrR}-bT8_SMkNjc%D3d|!8#U?7;|gBr^d!E;8^wpB&|J+e z`jlvWi&J9?ZIW6>YzTu)vB#Fx;-P81cwLGwCRvZL#B+FDiSYub6V?_MVAN+mCSdqvxaYY)f!m^WmGa8bJ`{F`9#QpR zt>YfVJ~>4aTe2E4vr(DqWW68NQaIq0z$nx^!xDN8^JJ81)6wTsO_wH-(ze?yU2J1Q z(NQ+r(yy-Sw^!Hcf;EL%)f^_Omb^uTBVs5&Vf$@;fNs6TXyu0>3yDbsJK6$&%mXl4 zeQoau6#TtjVyOzN-ZZ&9m+?pq!?iH)OOD|<>K-Ud9vd;ZzoMIx>b8^mRloOCeeuiH zku>D+c6bv0G*ZTA^xS$?#beD9kq8on)P|__3{C>;@@7sObrqiH2ErTp&9!JGfww8f z3*t%5p)L-vWD?UgV}|k{^|s2Z#b$~=2_9ldEPpXz=Ju;k zs=s%N1S?q0udY@#>1A^NWtNlNLz*Ag4hzSVTpjzSBHfQ@+$(ZTQ$s(ya+$-%FY3AJAxPJ zB6}S%jO~k&j7=sUF1yK?y|I0E1}NcUCUhVt(_TC?z{4s213OXqi6YLtvtzt-+WY8C z!k*h5odU<-WwgS~!?+$Fe|)UusmyfBwMejMkWD0QPC4j>n?56(tFx}c*7jn~_Fa8+ za|Q{CuxFpj=6QjU#*s_`%A4RN-8|Q?2k8Y;n4im$6NBh9mc_JtQeACf&S># zB@yvSVdd5hA)`1VskHPkC(X9!wicRVuPO&>o_hD_aD27@GTPGWzI3M?iP-qjxR1lp zj`~~rb3W>+Em^b-JsykOm!!}P=)lg@`N18GA@a4Dr`?xEY;f0or`5g^7Ow-LU}Rrp z(I5XF>77^}3YzQ0xDIWXmB(hSqnEQ1uyXNZ9^r|GT*5CLD|)5vA3a=S9yGq&I|ag- z0F`~ zjRJx)#jIGB*=;O)0l^rbtV~q3)YDbeu>8L>dl3Umc99FJJ)LK7r59Sx9>I(0Jx$ff z-~AqIL!j?L-IBKI2Jt)c19mfkhpv2^`4xC zmucrug^?u4k}gyo?G`2UExI;vco@#6J;X}@yDevUB8uDiWcU0x6s2Rehbbl`@*2_< zw{k9ow=7q5>rE!KB`(yLmzpw!gmDj>|NU|He6K;9O3%^O;<7~4{lI$u_z5s|^V&K| zLpeD^-zFBk;P|hSEr{ayiq&@{+uyXD-s(FY>X8Oh7|d}iaj|_Z{p&9`T29REc%Ia(iKYLt4bm$k z5ox{1lepCmXWQ{##-(vbKLSbo8FS(XB9Xj@U-KV#Qg7tH6yl3?ZhrgBA5+S-P7vfD zdiKJ~7o`K@>fB1=8K3)I#N-qBz&vKGPD~u7?h1?|JeR8F(-cEZFyuI~^U1mbeJ?SP z`mjOmGhH+F9j5$PNjAB~!x?K=r6#v#-=0td%QRC1MXhfJUt+WQua2Xn6FRqKUb}Es z-FlkpbYwCl#8L*T_wfa?*l>;7ZczN`5PtgP0t>|qUZ99m9yxT;8{|AX75U8>&Nt>y zuoKWA98PsCs9zS&;2=W2rD2Ck$w9;~boOox%z|lxB+D+!6c$Ar$MMV0}$M67N6va*|0WE=+$S57~#GDv6;p zQ#f?!CnguxRdaCz7cQ@ulI(w_EvCnPJXiEPoN?s=x;!PdU|cvSr#( zfk5tQ>&>1Jv!;CS&{O80O2%4VgJB>dh@Dy{kC39#2ImGStSPuIy-LlYej;JlT-z`k z8|#uqA)gmge3vO&ycv5V6|Y6e5H0-;a5@9Cd2d%|?+YWL7_*6wv3F z$Ert}VJzVjt&gbR6Btl*+Vk@3348fu?r>)Di+1QWUCxmGUHB=9b{$XP{E4odAZ|j` zVBbC}+&5GjY)quWI)kVGzL(6x6~mmOrVqZgz$DrPTs;zt5uq;9zv%@@l0}dbQSoR~ z&HZi2AlM(Q@0T$RL@sDVMg@jBLS+%*#sqiwTdLtpGVxmN8jW(FNbLC#OZE1H(mY== z_!7fNBRmlzb2ZEv!vS;YNcuWFD4Os%miNCM<8zS0l@B5R&Qs^rl5C{I%CMJ7cJ&3t zGfI5Aj0F0h9p{_I2(hcY$O{{O5f`KSS01*`mRhErtU;<%i1g>Yzs-L`=`2!GEDUF! zF&fZ#7!q60v#XO6Nx2jq2V2OXrD0s*se#+EZ-76KVYJLzlB#k!RiB`Kh}b?{OcOzhVV*lvt1lt*q?C1M@;# zSPMo`m3ecYUg_iIMVRA0+bb_vy{kmq_eY%j8Xn4?0*4IIDncWw-~4WvX%#)+6#zR? z1KUMO3B8!Diqz6(9pCzB=EY@niUV!fA4*QLKXSVc3Z7pqL4a3RNNS*z68>Y&oPo~y95aaJ- z#?-!wQkkZj{H?oz!D33!D7q zg<7i}g-WV@vP5yjb81vD!sEWU+GjRatvtrG>V4z%6pxdc19@g)hBK`ke50y@*k;dC zZr*DeH;PF5D8Iqge-4}GbTjPFCTQrnq?2?tb|#PhfY{q&RpVJpfEctK0~RRxqUHGa zEUs(yGsnPs&D9J18`jr;ebP_gs3@?>Xzme`N=NQHc+fGtsKC$rFaRgq5vBX6P7v}n zL(02S!#-1srg7lwkgD%SaQPLuOZ@5kD$d7PW-1yGeCX7}1BeLtNO4gTj-LlUU2Pi| zcfeH^nY9rZ;C`qW39&vDiBw>8eyTr>w#|>WXg3#jB(&6Ivzqyg0yN@xTwC zVYuWwvl+|5yY2)O-G2D=0$hf36ImI{u3ryN($+8Gntf|Tz_)SzS`iGh9XwN#JOx9{ zQ)!AK5V%5VigQr>093AHPxl<3{G6_U&I3V<|_dVF*h;anckI|L)*t zUU=bo`*HBx?J44=au6PFF3p(5GumHA9uiU0L7WjsnFgQG1Fl>59#RLDeH4r`(Ks+0 z=PD4$|NZfuqkl3Or|m~LOY*?1jEXrLlAk}PUNv{ZqvpepHhUq0xymp+92G=yjIY2G z>_2!=ypU4BfSTznc)!}{r!T+yDna<{J3s%!a@QLFG&Gq#YdW(@+76}qvyGafRXYFp z``_hBto(!U)22-?VRp;F$TU=1wfcS25$8Z&hOq$opTmduBBW!aj718)sjzd2k+B$g zDrL%)?hyjUN8JIff5l3>UQ!*QN24NP|J_z=qB+s*Qo zD`7<5&2UYaJZZcbCOdwSoCLjRyN;$|DBw##%M8L9RJdsJbemLm9pR{ z3*Coy?$Qm0hi}OE8$KL+T__=UZr>(G_uOMswoO-FCk{~sMQQ7(5hFz5?bhGUkVDYe-%BVOYW%bB9DKdf64#o4ygI#$ z_~Vc5^@pG&@XS^gH)vo#ucUauK>^Y9}N>;3@mzW?zD zI2ySx3$6iUVvyPw}Y6N$!#d5Mp4&q29n^}F8V_ugBLGh{k>_q^cY^5x2xmO20^*K8d)B@Jwwy3-FV$id+awgqAH{kg``%L5UJ&VJ-QKt4fQ?oq+-|d0c@H z7Ap}L|wTKpsDRZB@*G#5?|mroYEdAe#u#{X%VT z!>0HL!*AQR73hHp24#Oa`71mt-T^(D3p~=jIOy@qFF$+mW1_7B4o@hQs0I%>!hhgj z_e(5u=P}3-l!c3w5X(*$77sYtsZLe9(SYhA+G^tTb3Ef1`|2ygf9J*IwxM&9^) z6o*OstyjB4I7kMEsXi=Y`uBeoR~{1Hf+894^TYSBpetJm^>N%VXM_mW=S0s!nAQM> zwg$<=pL6HWLE-bXctkMY(IW@-U7!doyMKa06=|kAL{XELlNgZb%xqHllCj zh5l$=_LqXK(y*eDBn^p0-$DUL3l1b1`_`gm;Gll;j6XaB?j#me0W9!uZv0a*#R;B? zhuh}OTSyc>@<4uwT;Tf)EOLO0c4vi~_cL{$F@xcy=aRG$)->8i3IPxXC!2a2xIFwz zv}qMit5;6^{`sdN6g+*Q=#fO?#!EspsiX@OM9ZLnW8zZ^UMOT{0~v0L;ZXhFm@y1b z(4QeF|Gx204_aE;yTtL@9qnTB&p)*Kjj~q176Zw%2WJe_MJP%UI#4*xmq=ozbrDut zZVchM&Q;tzgD|*Prc)PVsneF_OFaV)3OFPx;yFg8ZYdfXe{Q;*5{Kmq!E=hCYb2rW zjPU$w)~u;_PvZ_+wr9@FbGP(6<;&5c^mr5C5}tpH7sL5#_6#_d5f3=br7jH|&`-`m zbob6fE`Y@KkN?Ml&VU>8`Wt5Yw3)W5ScEU^Bw>mK7VxB{@WMq)a86xev~ikif+z_W zWBpV5$C?2H`@uR@1zXG%z(G5L_=g%F-Fom$4ediW{@1Uy9&lbC8l3HcLwuoV`*_XaUZrZ)rRx{&QLod8>&HfL@--7b$ z5Dz$5kh;W>CH3pW^VrV69Mf&!fw_L&ddVWF;fEVo*~CeOIF}R~*zqtn3S?rp#~=Ts z-TL*b1uy2MMJnn4q7Sd>(`VwaaR{rV@2-FR32gpY`tJJqoK~oF{8UF8!-z~piNtpiz4jN)+&Wee{e=-ywy@YKuWNqz^vFM+F zwF3SExNrXV3b_lw@WW@FP2C*F1;WywHSOgbSHBSi?;2*;!KVT)e}TMa#`J(UW>gfy zn89k+4`0KW(U1sMzfi!HheCsMe7q2pv}T4yvJ#rVq30h@IE|bK=^f%cs0hGIiO1h< z%>D{~sPQM{@?g^{9AQtgx6tZQlb3#YSnSg zC=B1hQd2|9>So!?#LjUks5rfMcoq^XZ-8&HZ@*U+jLYx-+WtRr;(PR!3T)*S>ZbQ$ z7Vrc?Rm*EyJ##(Lgd7QszuB`9RiaQ~nSTz!1CHm4ve}{N?++2iGm5Ez+YJ8nUWA}z zqEQYNaPixdFnl3yi1F{>GaOE9gb_hVfMO5`ISmQ4%~al;lR zfIr`*x;>Ry{r*aX9!|r|aTD4`qk~bH`$FwM3BOCHjsYbhN|&S1)>-dLZP$j+@6Z2z z`ROmbPHzb9gQ&U(At>wBZz#(2#S6jf*u+q@6fV$PhI>^eo+ZqP3SJ!mARe!>CqmnB zKLB&w?Ag(h-@zyUcjzE^BxK=_@MBPp1Tw{?zgvGLyXLaLq4D3j6W+^E91y2f z3z)k%WA3K+G6m}>K6&zVGj!N+&zp+*=$TcbbXmMc2HWT|P{7gXLK2>-2ORiX^nayF zP{55Ft?%$Fo+O;(u0elREFN%{_vSYu3OIqsw}J7OSp03H1%7Y{%H9Y;`8GV@G!K8N zv}XuPH+O%F`2Zm(u}%bgz!5?6!VJsK^N=M3B}l}?b%MrdB$*$6r_LSYDUl_D4A*YDyDq~;}9nH8Raj2_*Ak^M4#CvqR(Iq z2xZlc@*l1q*G>U;jqjiRKYYY6gt)Kf6{%M3DUmTa`sak0i$@;G zfaoAAy_(;9?|no`=qPP*Q3_#Mms{Cp7-WJ894Wuf3C}Ou_d%Qtxe9#f_C{Es2$X5dM@Y6HVz- zoZCVq!a)+Ay+8NX@J>Ix--}2QfR8KGg@5sq@=$6t&;4}2es(`?b9$)Qd#+98p}cm< z<0V5>&L|C{fV21~)c-U(kB*+{s#N(E=PlG3O#gpU{!zJdMJS<1dvx;@gI-JB1sWRS z`=hMu5TZVDh?D;Bcrs07w|&Q#x_{Z|DnrP{Vc5Ua!~W$#76trRjN$ee!|O1V+uffysWGg4( zDBa$u7xn@D`t=cc4x_}-*pktdh!uGo8nJV&y#*dgG{P3S@)g$s`q$x4#bR$CUZPke zUP4NYMj!nJ8TrxvSEE9e=kW2!QKR8;Hwk6!C`L9WTb16(9iIV9`#z9~&*hC*=vwgQ zgYf|9g?uj^;Q?strYz1=I7yaRdj_0FqhgecY11Z46XI0mJqcV48u%LajkEDU@o&(e zzUk-Wagt3im9`T;o51J}Wx?Ze{8Itfk&$quZSoVS++NulF z>1A5&i5iH`al)f7jc}a&6c4di;_*222=vJ&8G;fH3~blU(qAiqQTTQ6AMF2|(Z956 z)e5-1?4x0T{xoQ*QuPVh1IknWNYabM=wE_KEj(Ip-?$$+#=QB~MAYu!|L4ZPqklXF zTqc~0q>0lD8fe?9ZRSUp1ntK;J+QW)2djGy1|~8_gK=1&CPj?-v=;jqJt-FX6js=B z^cq+U7f{|mnjr%Y`HV4lA>@EdJUo_%$Z`P4!Jf>BHZd0azP*UYk2<}ZZ@P~F#Xx)MGNLbIIc>qcR`N@3l~F}i^uzEWd|7`e{7Fa80}eS>f{qF(R_IAs zbd9F6?29kHlvgBQASFEh+?epgCo`f-4=@X`FlLCKEt@w;*gv*#jumilb|(GjBes6N ze0inmyLLkX2M;(0|55RPqe4ZoW5~BfR>3?T!bN%oIyw&+IM{ss_1C6R!v==di0-+L zd5mp5VE;4Z6_p!~R%_O*HOG!0N9e2k@JtMNfXzZk5(+Juj}{}eD0{YHeFy=HdF{hX z4~|@0pxLtLGFPu$1@5EKMs83v@nGF=5LTig>M4hq*g|Zysa^w|cd&c6-UInISU~iA zHI}m$EeNf`1%xytedqzlGvH)Q;z2_F&l>pF;g1X3=D6ekz}{Wv76{Xs$lw$zTwJc1 znK`|_wofHaI~M{pi2ct+ziu7%(vvKBUIYRYy0bg@Pd{B}T757{ zIMlHii(8zqEf!;OK7?&hz!4w^2MY$lCxB4+3nzs#!SqiSnOub0uEF$xqe3H?{l37$fIF*@NJ?L6%Y(T;fL>y385L^C{Tt{riG6K#xQYe z5#>s21CM=r44ym_rJco!LJ5R32wn0k*f)Zg=1mu)dJ^oeP~MH+n)|4AzRO+imIgo zKO>9<=FgiA#n@f?67}jiH+yj(fFD?2kO9vFUPpS*%2_|p_j>R_D)$kZ@{e;egmH@d z#Pl7@D*tmm-(Px!75iZ8MprE%XtE$DPMiz@)JVX!1jKV*DzAgFh{UW^*w8bbcjPDB z_-B|N*+|*q%-;-Fem!~+FMhq+FYd46RY{n51<5z~KL|b^A_e*%m)Z~?{~=x!5TNrN zJyobcWjX*0+1k(4g=PI)at0iWG6dyNC|mdL!8ud6q3;3m&=GhvU=!+km!*| z5AKBv=6UcX1SN!=Gp0|GJp7>$mtkw%SR<7sjG3(A=+AGyHRa%i$R2VbD5WqmwnPX@ zRlr@F{>xxxn+;ZpM4Sq^TD9s*75a1I_;V;|x;A{t=ROXU@rp@dh3~9fi9cyUVQ1Ys zwP0k|-z(t4sz?RMxX|Nuq>zLM{`_r*y0SWnLLTND&{8QUDIQ!6At6sTs zMGRs}lqf3s2=^5DjqhME^cX_;x>UGu350?F&Ag2{A|K91agCV`WiQW4VyK%;nKDXt z*73v>Pn*v^JBDof{XhK_c!Gx^NCi%hJn{&+UP%I3`Kt`oRufQ*&*0Fj2j09+!pYs>C;QosetR(Z=eOjKR4h1 zouS-n-3sT3qBgWzgcIMw`zO@==NTRnG}6j<-vyd1rSp~B|BS`ib;pl?fjR=~UoBV* zzcyr$l=Fb$?(1((fbIw-3Z<4bKpLX-Ck#c;l~ zoYrZ8=W(@#;o?cN*v$igC<+#M!1>SE5T#K~Rjf)I(f^O-%nncX1&R|M(m>+1A;UZz zi5K@GZqG@y)gY=D^2EiTbdZ85Luv=owfldUJ-?Ird1hF02qT#e>_2E~)u|_nH0J>) zbN8?jSigA&Twdgg@Vu$~@Tq{9G;xB|L;7?7NyA$9bw%uXJi`j)Xw~XPJ+nwU95@hZ zB497I66~LsL(z>nj<5KQ)T~XR=k>3@Y@g0zT4N~ww>Uyjdd3rgAB9k3Z(HLD&d((A zVkqD=1f?Et!Eq+*M6VvwZW$j*lZq^YkXmA$pn=IkC}=HO5`yvq!U1}15|m+;jr|CP zk&*zbER_AKL=McqdJbwgWI+bhDCaKk1&`+I|DmrB@s9jpzP$MgBW(3VnKxNV4G*~Y zv@`*H?&n+xO2TI}jwKKT-wgl3dI+CTJXrb=P4_*s_|q7>7!27Yp`rN-7DhPvKX4ns z-y@&Q<-UmCL$}?2o8*}tJ;w|f zphpwD4Vj?GTP#3br++dvu{Y;&94=MEI(_UI#{thTyI1!1ln-9T6NbR#xqM7Jw(l|x zpKD?%lra5^K(<0d8P~Vsxw~OAs@L_$|NZ@w@(;?@#?y$y4lHfgCc#aAuKoRSzUNu< zF0aTmY2aZ=q0b}0U!)U0!{P4%v~b^gYm!6`A%OyUuz$&d(z}Z|Y#|`RQ}{(q+@9Z-0c`?*-*s3PhZF*L20)dlmevn|}z@|NS$H!z+ChFE*Lc zJ;)* zUTf3rS#!(}KZDOlsQOHq9>r7Ci9`p6Pd`7V9&k9Sgy}N&fHbnF9EHtiUbg8681m)J zp4}A{$CJ}g!m77?PRYl8`nt92C2E}RU#zSa?VlMP1F!AD$cG!yU+4jaq@ag#+$+_q zTN`s=KifLWPkAQZX~9dz2?@uy`VZGG{Wbly%de0BVExJyECB%A0BmCPFI*SiUAffo z*d|G~*}U%aN?*vJEI*9rf|q2{@t+v|ixiU|#h*u!aNIj^GhnDQNDOr_@`Yx6V}at{ zU1~jXzdx zV__uuBb8}w#frjfU?znXn&DFjG>;xVDqbd;xtKa?AqNi_Wr-WE5QUwILijgs96aFA>9z`21b^c`Y18IKvu@o+d2U5&nnOt> zZFlh59n}v;w79pkzk9Eg4pA^Hy0rs$h|n@ds{k)%)*DcnY?iONuMd2oCgp0(p2g&t+K6O*3RZC`gcg05L=LKbtCDCeT8U|H#OsBJ7~`5YyLRe}j!a z4wpsMk`!IVS}Kb`8?zxS9S+b1{BwW6>t*UR^9cH@di81msAp?1O`BL zZ;G*)>UL?*#*G`nU>r6L>(?=9F&3Gq$lJbsr}V$nVd>9CvHz}xzf>wK4>J3{Kb|im z9&n@?Uh&KX2}vQh0s6b}Y;5ZhjJ4xXuJBwnCeFLByxLEM(|pgWav=mIt-_UieEuJ;-$8+=MVoe*Pd5nuT$A!cAkM1JoIQhes+b7S zDGW&oW>?|5vavX*egTwdvo&u0N~|yLl^>w*+O04A{wO%`d%%4LzMR zVUjA8FDFH~Ip^68OzY84SIIN{29&{yKgX>kOK$ligPB)&8DjqP)4$1c&zY#+ z5Ue5{!JKyN_!pjqKEM4wc8+uKH3Vf|IY&qe;ip$r;0!nif6?LvR)}jo;3Nd4&jU`o zNQBcHFz-u~QCC9akHYD!80$=)2ONO0oFoBkNhy_GAO0kf&>DZW#Ep;t967Q}7+5xn zLJ+RE86L@iP_$e#aNg&oKOxAQ0iNWNfTgGHzCAl3$cq#d7%Hk^qo&BIyv|1-Z8mq` zr3MY|OsKZ4alT>4mn8qz4?Zx@Ki^V9*JZb!Gq1fq6vCjDILlZDm^~s2=fub z8Zpdo@chI43BmWT|Nims;PV_a^?+k75Y|KK;CaB|-r>)B#*7`Ip`sD`AeRDs2EkJi+Zf|PJCjdhaRx9V$E<h!RgdfwYJr6h;dh&zP*RlSvxR4=e{O$73-SAHD+!=E>2M_+y zqk+On@m@yxN#KWv!Fzcgj1GC87!!q0rL7k!`=9HNKLjP4NZ}rbano#Z?m9RT>wVEDsx4E^w1wrB>WN+(+v zjgy{4$g|_PM}sZ>qY(0nA^MnzN`Zn!M3GAVkWYU6@n?AcmW>0yEEI5_2OJ8?aS1TK z^UGRLI9^gxh2}rlZb|HTB>5xA@Cq-3@SibbI>N9Okyp(8Sg!o>_)l4D@lLA754}_L z3^=J>;P`)-&;KUiL5ipvV@87)<^EQ4czNB0{mVTCdDZ*{3qS!k%d378LSQm<`d@v2 zJU?DM;H0Fh+*N~+ial`j3L;TlYy7i?*P8$Q{ZHcX`=Qr)M<{h2{#_7uOCqeek1L4& zgr8XX2g8QG)UFL$r%)v9GKACw!Fe+(s)q-^RqJ+;sjSEOtOq;~xO(*(nlx$CdLbgu zo;eHe%@X3}#&e)pSR%djW2Bb|^ElRNdMS~LJY#R=iuZ*6MX|Th@cK$u?my?w)jg7H zfV2?fj|O2J?@T*(?0^iVDXy+P|4;!J9X*ppBx$I}jdC5_|No@?qf%uUG(eW*=uZV4 zLr{|b*Yo`)gS=2Y;7|i;N)I>}f>IvXdhWT$?Oz0C??*>7#&A!J;YBps5;@Hu~Y z9&l1hOL!GnAi9?Z--CO}!OpT-Wy+A-gx7z;-_Cz5NBEgCWrD{rjN5R{GdCw={ffOx zC7P`q`EN;?0%Nq7c^vYr3l}fJ`{ty`51xO%F=PZO&Aact2ZdNK$i;4jVfR^4jBz|o zn>GcCpki{)nA`s{6mD|NM@%$=WBA{gm|0>hMvuCQ6Q?4adUsP5=U`EeO#}4x8#Z9B z){v_!SFVz^@>Uoo@*Lc&R^brgNb-e!CsExzTN!q@DJ7hG!&lc<7b!RoX@UZ zDIdP}_*aT0j{c$G`}rR?|L4e_19FRUc$Ea@oLrOmN?Lv&edI62F-|H zsRMs(@uJRaKrmLqczNYQpK){Ep<@@g|F4t5mL(Hn>jnu(?9@~+ptcB z@}jDbxzo3Ce_5n$+jfW}yFu`B^|}t{;PAswi2khA!PgLBPN+>UJ%*5rQoupCvLSQ!e;@tTQSU`%2btK`(#7sAZVVNeKtz?$EeV0=1tFKRh zv1u&fE|?*H2ufH!V)3CeRPc4<%C(V&@GQV2%LDuXlBK`4o6qA4-dQ2(#=QJwyyWV( zk7AMUui#4!(m>P(xH8!hzD+y>E-ySYh(FJOYXA>82met_nyXi>bXmpYgdsjTM!IzA z1i?acZJiJ=77P3tS+p6tWZAM6=Buy1R17Tq4Dh5`%`@eE_;1*>5oA)&%JP`lG{qvk z<8NSOi^svgTQ|nghY-M~vEwE{!8txb`qO%@MT-}Zn+;@&KpRhs&}Z-7eP-!WwDIe& zfIICSS1mjZkA?|k+ANj zGRKa8-5f`S#6J*%5}AGD-_c*H0JPSaPuH(qFFnt5FKBJVbOsx{f`y8TQj`{$OsHlo@$jdwzWN##m0Km2KH<|~hlV?>chknT za>z9iT)J$f>5BOIDhQvBDv%lUA2oUm!cI?={A8~Do_ztrco+)k0A6e5-cNry*xDD1 zoEgqxGf)-+w$mro^3Fg0r2_ug?hVF2c>dE6E*rdJyzp9wi{H+i#-{2i@f`Jk|Cuu* z-uj9qwwxt;i1ANBFs&|wzkfL|0M?cMbPh%l6i_oP+Rd(i#DCWgoMiw@XSR;tU)9Q$ ztp$Fp`Sj>#5cvHBfnP~!0yn`7V-`m^4S`MLCQYSZSqbr{h1?nm_oO(h#TqO%9iM>O zL;+`&!K5Ymu^!~dP(d*L1-PwSXoLs;{`cy2dGMZIzEM$_`%oJ<6CFBs6^qs+5lN-r ziAxIzM41Sm3b+E|0XG?U+V0*_JVyu~a7?yt=S9IX-$6+r;ZA^8B{?67!W2ym&$b>$ zO5Aj5+4%eipEEpLmaNu0gdT9>X`}G}UF)A~qo9c!HjycK{M{)0jNo-vty;z!@xNBS zffnb|{}fJ_)yH^!>eOkmI&BLDy?VgKg650ScYP$912^nU42!>N^%`^ZlLKTG_~)Wgjws+* za|9G{O<}OLBTioFN}`11#vjk=DN>{mo4tyyrR3 zbIy6rcvfS&T;qN&wEk!8lZ6XEGPm4(v(2sFzY5O)x-N>n|1&c^E`(yhp^o6&v=&Zm z|MTOou4pNUkAR}X4!G>MeBdhO160(i2RwiG_`Ci;7goNiWI!t&3aP8Iic%*3_~G*=G5r65`yW6XjAn6? zob|Gw|I+h7ZV1|m3$2$+mt_pN=b&hzf(BYWv~1DZ%%9J-FndIV9Q?X@f(1JPtsDP> zFWS15Sek}}$fXg-a39>L`r*I$A_Q=)TPxlm;A7<<7lEH2f5x0q0i4pmP@$sI9-Nz) zbUrQxGJ#5V{D}pea~q00v=W>B!ECIBFKzviknuSCK~?X{Eq@| zDmf~q?n`&){<`N_BhYIR+*C7#;!5KN^jD(tjZ6AWlm#F-DpaTt6wN1~v!Pw_&ncQS zx1avu{qKfo0ytMTzQHAX$gyL`aY+2QIZTCRJdpmtY^am#|N1;`{udkkX3ZL#=FOV2 zBuQe`NI^J(BKh0jqiX`;4;rMgbsR3AtXQemJE(BxOgH1OHk$&&ZwnsZmlHX`UotqLcuMziY&VAQ z@#9ZRM!I5-EqR~gg7d1J)h4k2X+;I3fVfop7buwDj75C0R8YcSwQ3dCmdDK_kCYWb z|99W*gOS1;vZfOylmhT}F#+D*;O&cTMsFNc2q29rR(cke+S^1Z%zgK8#5No}WT@nG z<1c^yd}bWHjipSP%B%z|@_|QiW_%6SLEIm|4p?iToapEO-Z*sxO<@01MPZ%eNdFJO z4`^}cmHQ9VpY3_E{$K1t3gEEEvK`1D7oD`7`vDXZMd>bNrLLVr%Vrzh3;t06>-?MH zy8YT~uQeGnX23e1Yj90nyY?`XA|s(F2Co`)L5ajYMaPb>qkoKoS23)K0Kdh{t#HP> z$n3r65@fs{OhX0_;q#xj7PfS{ko+Z_|9SC!l}zw7 z#(s}A8we)-jr0FMO#c`HIB`LVN}-BUZKfGf_;L8VfTfZRuecc! z_l|lHpQix(|1$4;C;9@NUhH_$|Kbn?S@_)ER;1#u-V z#1*O?^7m>8A+~P^UN&*#Lv1#e7f%1ThJzkuuz6w;$WOr!i=sfupX%3dgg6r`C12!3 zTEG>S{G{)*)t^HU-s`X!$}d`f`Saz)r!382Grb5wm0QYp&IKj$WStvPQwIksTm%Zx zIPmwopk$ftplL%g5rohSmn?6|lEqjAoDiDmOV$B7`A;rm;?H~+gU^p$`wN|1d8`8J z{yyBj6+r(XpbZ;0LmRm!-{4<_Xf1Ff#zZ*m1B^|?aE!aUi%*f&2-lo7yYy4 z$Z8@d(kSO8=3AgSxlpnY&wl+8d>3u1?HwT-2)Z$3ZkBRx{~SDtB0vv>1pI-gGWzJ z0LOml@Tc$f?O3!r{58liVa4QseoI*Pzjp0A>QKS8^Z_~-K|fdmeo8?$uQl*|0jUUs z-si=^Skfd(%w`;nT@B%%B#RGgOm`;Tdi21dLw29g!-)IFdC;`9+?HKhQ2Y^xBdgcY z!XX0IWiGxN-^RqB?a%(t_&!^4NUkF=asB@u^gYHRiJlx>_g4kcRjbyN*q)?27a_EO zbK_sWM|MYlK@9wdn+G@F@}-~uk#2O2>M#^5aA@$l`N#FYCDsCtFlZe*cu;?n9*a`8 z7U9zZhOT*@gjFoz%h(t>+T_fc4Tr5x;3PGad5H&^IbpTRL=3<9Vymh0%(GIK;)Mmd z)GCBO^SVU(SE@|Bwpo0ML5TiTTv>v{(3>DMckTBUe1s1iFqFBZ7@?D;7atH;>3e9` zaQxXPq^35%b3y6w=UYkkJ?|GnIMB3t3tWZzf3|1`VA>bw&V$tiKJd?fKmNF1`l6%1 zgHIPTica!zPdCfc;V#@I#=BzhcIII_FtIPvC7&jrvVahWnV8w; zx8u*xe=df9?YiJ`?e(xucpQZ7y`ZpE35rX$3Klm1sAWSTtb@k+VnM!PEv5+9&3`jz zPdB-9<&tmr$ssPaS)Q`V9NDuXM}&-9Ih+pbe}4GV zj|Il%X@BW!cKsRN{}M}o(Xs?yu_aQ&PydrAxz@Q4%4}}493gS{I&^#k!P-Z`g7yLF z=Pg>c#u!_O&GI(ydkJQK^eEgDuKf;_F1i6 zZ5tO>`m3+~!|6Y1(l|Xl1Y+BFAci0Kuj{Ab{5Q7tckuc5a?#%pzcpg_g@jwix}e-2 zEB_ldWT0o^Od`p8o#Y_?LKn{en5Us6!+F}zf0{MHfpLqMJo@o_CR%Sl@@NIbXgL{e z{QK$Ox>a+r_~1Aq{i$FeE+}z(G5QPr@SUIk`0-Z@Cko)yx}Py$$Oovv7)q{J(&gQ~ z5FGpg9=8UX^SXAX0RL%x=gK85^5)HJrca$B^&^Rf42H}5x8A~4i2u_|zz)wv826lZ zKZjg?{4X~D2>r)U$`i1Fn*s~CLe>KA z;K4)Yfnp_5ZfO5ox^yWsV#ILobH)>6y~+Q`A-z5+|rhK;E3sPIcc0LQqK ze)x3(t34FoG>3spvl?bIUAp<`KX}jp9A;4gQBz*TJ|Z7fWwa=`0X!tJ?)~sfmo8~W zzBLSwv>%6DQ2GUM315FCg#HuW9fLT<*t1&f3PEA+#PL(n@DJyIS6^e@P)i$O{=?dO zE|j($P1*Qu+r8@1e>OaNyo&zF@h4$I9)Xu45R>#Iui^nCHR0Vj;majZ6gZB<@j~|& z#5ouCYqo~t$7{Sff#svWl*T?O$Qwi9#Y)8Kbn7q236VjwAS)H}n!bd!pU^)#e1H3g zUf3s&F z>_5gs*z$!a&GKb2#K^k^f;zf@Cb@R+`38OWY15*4QwZQ1$@h_w>QCj5Go|8g6}6v3?!N&jx|e{F@f*su8smY$NyF%6vCVjKT>!s z8E}}FXNhZYR(m=7zt9B5UkHB(pLyB0#*G^V;ah4LMJy3MSsiii!V|r!z41nS@i@cS zhMl@}$LHs9Zn7D|vt)ulE#T;lV&ZrxR~5ZaLfJ4(j-zY-eEFapHd?%GEn2b^Yn(%- zIy`OJPUwN87b|)gVv=@q_~1S<^B*lp2MvbRu9QeiKEwfs6&LYOO#j1I(tiH=2M_ca z%9MWu7DxZ{(?7oOAB6BrT~I0#b?Vl~Isa;bNtz5{*dpM`#2Bh{>^lRYLzu!PJl3_?<;lFqY4{+*^U788^7m~j?{^R;zIe1;7!i}T@L%rWC(z{^&Cu73z45liB1V1ulP+oNF5Zw$(uuQ>` z`Huj>~5hjSP?{BbfK`^LnMR)OV)&J;&rB;rQ#PsN^Nm|9$!-w*pI=5%YSY>`9%{%|r|t`8^=F^SxN!nF$%vm{e)+{5`0+<6j^D8V(FFiWaPZ(^ z)1)c1--sfS$G_KJg#}!_7v%aVtRIRLMcZ5i|4f-~FGzm!E@Mk{twUE!{{A06 z{s`aS|Fj%djx~4hq`LYFzw(CPvE&QzAC~*cvPD?P-$Hbyu!7L~JoW2IDbV46IpOEQ z0Wn=ry2Qh_HPm+Z^}o!wLIAgTp+ixGmHqn-Vg=wNXvch--@tYZ(3O*0mOJfGdYfSd?=7FI+SamWatb{v$L;<*4<`!1FrsEB?un z#iA@=g?*%fAOB%P=q?5eDJ0n$qEN$~*gGO>7REfe_OVg@V ztK;ykfx4x`w{%@N>YXvsTt2wG-bW6oz=CVd2ObiOv7$xceg?E>V)4_kP$vFeyS{Qx z7KP&SODlu}=vL64Um$lZQ{6pK5FxFBL)xJ@z#QSKE&1Z={}OZ^@Y=R{)zkxCni*CI zkCr#z?fnk7(eWn)R?j7Yh@Xq$?*fhUJ6gakS+dM@@2asC-1v88xz3rbqR!P*r(j!G2^A*iKmk?=$wmoI-FGiky&Ee}OcpE1j{$HAV1 z&zOg6*L~{6$=kbkuLu_ipYc|Q4Ij*`l4Om9!|99Be-1Q0ol6vPL5ai&u#PTj)deMAk^gphUA0wlQopC-!Sk{%x>*04Gyz^Eirgn< zZG)nrv+iLzEbO14zXdJuTfqX3?#x{Bgj@E3zx{^}gA2+>9+9Srf>KQO?74vgkRTq? zT*Qi0X5s_h8*|Gox4^n|p69Yr9=gUrKCw>6p(>L+*3a}F4_;|S;Y9;|ho>dQeculX zA|CKVI7pH5BOvf20o=tBR$b9PDSRN4?0Lia0P7|(;2>a`J9maC+7U%6x)nyeL8+kr zz~UK)0yqqFi99Y62YN0MKKE(cBo(IFobV-MxEf z#2l$&3C%a!5RP|GnlwIIyy+4p%i;j_N90oYty*e4KGuGq8zw3W2qX@>}#EI_`3rRDk&ob>g zP~j7pVLnNbGKE=>GlXQY8eww0+uuJJ&y@2-rd!ZI7JUdoLox?KzClrN#|{^zSPKhX zZ9@`1T{p82JNTXpN(-L_FqFeE2rykDPvbtkO65w-1$v!_l2=8OHceWAO9APF50()3 zc@iTWaG0$m+KgcbT;=Fyi!Q$HYgpr<&7kOWIo2QX8UMt800b+KBW{?(|A{9miYswP zf9`*$VZY5fGI6X7#-aAMFE;7}{EmZwiGn8o{6{4}3hJ0qRVbG5!{F(O7D6s<+OkDhkXWl-1U> zrtdtPr$m#CSO$fI;SbX?Zja`A$|y#^lj;a9#^$aB^?~lMs56q>ge5!-)^;R9q03@A zl?f|59kA@)fn;aE6(1%`rDkdK(5%3fIU+6l(So6)UYaji@+$gO_tg`<$~T(i7GhgO zzx)|b_|cTv8Z17t=?Zb&Fd^fs$=Me_UXtk)LmV9~caoWc%^{+`F<&99skaWs z*oALI_eVq`w2F6n9h?pGUXjquz8`Hz+9PhFL-jtEl{4^nDMTq7A){?HEn4x4+DvPm zmusU}`qex>YH&O|+ar{Nhq~VylamB+ixmnEZ7dVW+TH!j)brN0B|=|%@;S{E43)jT z6@h*^dw8px4_-;tE@a0*JPFDs%cUXHC_E6T;qE#@<7z70`oRpax@e)XKT(f2leAtK z<2cM= z^>47BhUD{+l-I}$lTm>(7*|MsgikOzu-Jv0%AK?jC27fbs1z4O)&F}~oI8D+dPr*F zPDmqHf_RwBuQ%Wrj=6LO(FH5XAKI6K#V8Kdi>|1P#&jj&`fC1T1lbjcrb1*ZkmMr} zW%_&a$XG2?I;rWwoop{YJ?O67BBML0n+3xTM<*s&5qFp*aMTF8P zjOh_`HJ!JJ=ikRnDk#3bhK>QaDz+637HY*Es4Nn!xf|U+Y}cN@%1?i8gCx2WjK8MF zygGuNHD%5cf%+-KC~(TI4|!ei`aB&Oh^EagKcj0uYR<>?YAlNBg!8HVVS)&(Hzp#Y424)G zqT7C!s%IP=z-=wfwBC77{&$EkPWlqV-S1@2oV^B5OOF%E<_evlyG~OGh7bXCM8;EY z(G`LigQJ`KRg{&vbpmKLvE>+fT!(w9{~*l#cb*%{2bRU5&}^>w01q}<#6V7fjN9~GG-IL#W!b21?j`B8p9CWXVs1106o_Zd(>>A*lLvSo;fs};Ss<}ophfGFf}h24q>l3HtZsJ zFx&~N@im6QT!|`iko%rjm9|?!Z97R6K0%FW|NP>6tpJBVkF;F_as*Hib;Sz8oN#h0 zACRx5e%)65r=efED&E$ML9n6^KDXHo6l#cgXXrr}XL7F67hjpeyZSF*UO^XrYDP29 z{KnOozmN-bKfaFW5R-F%YjI!#Ol;D9je?CYxs{#(jl~Z(-$Ur1%&1QfrP#grv~WO* z0oj4}xSp@=&J;iv&WVU$>jUIP^&wwY3+OMI#w)6Yk)j!LKzO3g+gYPbm6|8!#Zp2?6oas0dw~gNXeZq>j zGUrW>8v`iI|7>xIJ;7WXW05};(Jq*%#I1`~i#K33PPxr$*@37Gu_ zl0BsF#ys(bN#zgzAT&PWrW$0Mhng;rJ zYzIT$^Dkn>xQ!7$;20O*a8P2$S?|fAQLU7ZXQa7_VQtVf1h5L)d8U*r`jf$E#j+d} zd*4yu5ilSG=H~rg7B1Jmr>ApHx*v3wppm2^*On>anplniIf-uV9 ztU6;8g}(jm6$WU#gA;9hZ+3~JedoK7 zFqu7yh|Jg<(8v%#FcMA?Rb$>Z>5OQYH9kXvAm@~QY_2WE{r84r{KE$TKb_uJv%q2gN+f0t(_~vF%{Dq-C?xV_t9l9 z8Jl;<@TzElXR93e#Dw`r7V1!nlDW1}y{(`QP=GGgkXUL%qLIzZ4B`Qwf2cLgW;lbK zu-w647&ouTAN-|A6p;slIr*b)dTuKhO+N)QR;LU8RR59HawJqS7f=ueURkrmaQ~A8 z?e^vdUWSMCEym9XU=-1|xN##fSE^t+9f~5LcJFfi;^<0)QYx9Z5y^&S z8lndvkS{0JSpttMALum_K$}AvqjE=xMte=`El;W|QqWWp^H*PT@=g7`Q<>_>P%Q^8 z@x{uIQ@k^aM&=p)$|6DV#F;0yzXw^Y z(Dl45@t5|wh!PJZwl(rUDIoro!ypCwVyN9oV4idr{d;a`*fO4KipAUMxG}#~t&{C4 z(ZpfChjKSiJnT)_X*wG?m}dIhE3ATG)!(l!vSF|yG-=9Sk1pwD@?OI5v|Bd4w3ly6 zi#PHK$GbXH1F51f4vG+d@Fq%+!rC<3TAV>51m)3dVwE`>?DFcr`5P3(tR3V6r8CHPADdqu-APyDyRbvHt6>e8Mc*e68>6!)z_wbH@ioKJon-^T6$cybCBZIc5%fvttcY&!k$a{j}UwXKI} z7W1FHZMJI3<4^17f{8qIOVp)KlJ#a&t*_NhatTLp0 zFE%WHzV22G9IR*C`Ck2?Fp&9p zAXmb=#<;uxmFt?8&D2~x&Vme>q`yn(L8o=#&D}p;qx>VwVw-nodx9kp;t}W@4x> zC)=F|6M8|Wqc%3>fS+0tw!t6yeD;Vf5~;e85b=0obGKfOOLp}>pt9}9@u#ApdcS1^ z6yhZ(7`JdHWDJ-cNE$T#vrW*Kdu2<~D|{ZE&01@edtBw_xsTEI=@DPp_v^*d6hl;l zGVOJ8s|qxns@Y)Fb)LPsqPX8Pn&>+_ynyQtHQ38<(YT%j4fuV%%p9&)m0XyGl&)OJ z(aX3p^vm6)KD9?9vfzSWVzCmC{XF)EC5$ZiRca53L+$pbsY2^CD>M$+!{xdgmB2^4 zkAB?(mYSwOk}S|v@!x(KC}jP=x(y6hW2|!aIIlAJ`bZNujlhEM{a3MNiPd-@@*X)00$P&-f(1Ia?P$9BBoj4#ml(xQxTB7kp* z*gL$xVh=n~=B|yAbMi05h7muAli*Mp_(8t?8;kpNR9jcN!xRkKYkq^H`NZ0tK1RbF z)&I2n&M~arpvIJ627+TrZt@Qm=a~pPI>kqJs@;!dP{hXbE`LIqPE)yltB#Cz$ySV< z9kim=Dy4L-(P(yIoL03`LJxq5WBXWE3f{WEwr4&Hi{z6-*&D0iQSyRl{#G|K9*5_} zb3nf75GkDHiaJEChh*2s1{}uF1J0$f?(0_Ad>k5G5@^A`3 z4De~~h+T5~UmpT>1R}-Du)0~`*hJ8X;=I{@iZ#-R`wvxIO017p%hxE2&nUEHN{CkE z7w3@Y)r|Nt{L(?&6YzFJ#qbXrvYSl#Xmu0aK#x45n6pNG^G?#u6bm@ebD2N6pJEog z;T!&17?L?zu_2jJ^j!}Xxe^~QCucBc_|23(IEebUzb^8K4K5OGJ|@!M{Tai3;3_+j z*be&gYq1`mExxC{xOyUOpPq4WI99=P3ae>fIn*N7)iO`Q&1u?NFOxrU?DL>d-N}~P z3z^cS$)nU8w^9Y(zI(R~oQsNjCGuZ84*N2fy?=v*J`ZY~6iuwc+in!sUr@OZ=AN`2 z6-sQJMePY*tX4+}-y|~1QH5F_#jnpB3buyCvoZ=fr5SUsFgNNgBgm!U13Q{y)O`x- z^EfIGCTD|xmkM4vG02l^#Kk8CyVI(5dz~GVHc@xxOD=~Zr8ALDBnXsj#-ZT>>MLv`3lF_vzN+JVy$ab_MREeaBbLgAS{mxYV6jI(#iLxEUQzIVvZJ*R{w*S>O0B#z@DZ;QMI#CoTzh{bnO> z5);RNw$$g#@l@d&o_Z^Cn>?z%S0+o<;d(CZ!Gx<10)e>rhP$Z?JMFkJ+$CpiKBJ(b z`*#V%&EyvaKVcdwo<_`M3Rq}dBSVEyqTe>X31tgIr>j4-4VR;9z-SQ^sl5OG)AzCv zN$!gWy)08tKeSPTUUcZ~_c>IPW5yfos}niK=p(Z8P; ziSdTzJoAMfv-+&0d24o@qs|$!+ zF{N8IxM2NK8x}NOsM16G_v$x=vk`_&f}7*FSH()^-H(U#xx{&jD9dDCJnMipZyb=c zDs^v*qRUTAd@8FK^lN+zJWn5iR6q23%RTlut(pjYGg>#J22!^4lE$55LsmpASR_Xv zZ14K7D$M@TidL+_qs37FD;qdz_l5!UWes|to6sbkH*b8W5vJBwx?4(v5aMN5xW|%x z9L|P}jrL5-H%a&zr%F`+4R!<{UwDv-ZWaeDJK6yr~|H8}nl&65D^n>S}jsbLe+vjW^^ao@cE0vx05m)}YMgbxq zkA$N4`nZg26msyzmwvcOd&4vl+F>xZ#O+oXc9<;MGvF!Ej)sFN{l)6rO&1tg@wZ=v z$8pl>EL~H1EcJ?@JTD7g3RY%}Zp5{;2s@=4OYY%_CHkU?F@ng{YuM{yypo|+iH)Ul z*UaW4H+J)Oar-RiKzv#*dwnrSvQO|zn{G!B&oK2~`0%D^^3=^+OkwYmjigjOlH=Es zQCuWv%JXC$iD3#HIf10J{SwpND`;W$?Oy6WlrItUS_S${9{t$A2TrIIowol996Z`& znQf5Um`J0)V&WI_hEZcu=LNdgqnFcqhe1G>ZUDx-FB|ns*RF<9!ZX_gvT}q=xK7qX zv%=DsjKA?xZn5UNEzC}~C}s_V+8cX&P?sJwnMb|RXenY|8x7Tmgrg+I#w9G9B|@JB z|9+Uj_9w@r>%j~DhSH+gv)(pQ@VMt|0=ROC5U4d)iQ+64`>@sdNV3;mD!cHgiy(5``~3SH{B-4TwmPAD8V_x>3hh6npr{fYc~Vg;?ODUR@}<%^s6C}JJ&@m za7J%0whNLmQ0+QRFL-`9J}p*dT!kg z#!fs{VQK3Onz*JNsp6Y%bM1s<72WA&oMpD%YlgNkS^VDmnqQl4a%g@gX4-R2H;!{< zX{eHZ@6sFMW_p!S%;(O$Qx)ZJ@vppKsW}ZN}^4#Ae?%*2YzRGwI<+IP14 zC?4+9;M#P$SJlZ2a`&Q-22Y~TEEXRQ$8*UAtkm#{&0_Dw9!ddFkEJrrqT%Z2pq8Ln zrYtG8ZPe-$9;Me~U$sX;v=2FzQ^k=r31L<ok;HxP)Llg~Mj6z3%;BOb=5N0wG2EM#(`&=2 zeV9XRxkwCCW%H@rkt#ewLPz!K*P5F<16Vt8QiK>K21|OaH?U1<%dOdkH@>2zunYk% zrx(+@Keh9*q?8;y0An=n5i`Jff|l2XhffF?nC~lv1B;osTSk)ymAb^g4@EUB&w|5w zjB)`HW0|}aw}`q1K-EW;<(8e>Y>zLeujAIK(w=Y@!Cr00x^Ffi1pszcxH1J7l+Qdz z1hZUWIiG-38~|tVBM4E7*1juF==eOO(QNPisHVIxIR2hu`hyUGSrgLjqfZ!^g#GIN zp?n%}hLIf)CzbNrPZQsKJRoAhwC%;k^BYg%-+LVs=Gsf0mVa5QMXU8EgPC_cE6oFX z5{^zIaCowW6irYKG;Wq~)X}fL>E?A?Cjx%=qsu}^DG=keh=|N-H;kAt=p+g^AvH~3 zXOV^2J(A5(t`y1zHXIKs<07u;U$$1w%H+U_N>47|@)PIA0B&>Gmf8vW`BO_9`S!^n z5DzN)T3Li;O$A&;XfJ-&)MI<>Jrpd+H14%^WBQRyN9ze+z)zRjdX69TH|!1mYb+;) za2nrurR4l_)3OoUBtt~y^sBNUe}-H0nz}u zK@QcrE+K@C>R;zKg6XMYL%*u~ZlM2IeacXX1h^Vdna5~~G`?N)R--=$3n}!8M0h0W z|I|{i)ZaQaKGup1ndrM*73I6fz+1n&TRz0%yIv^Qt+*R%^*Wp!Q0`6yAw9NvSr;^$BUB`^kB45 zBqRyPQ}&yF(g@t4tudCmDomCh;fo__(6D>9%@(Aya-rYN9BgHdI(2PhzUQpelwm&TWW^mJY``}>z4OeT1 zz=MKHh~>o<=TN|+)47v8>@VP4FkuPLtko@CBzD@~lyz7-Qjw%L6~13i@uz+8F~)R0 z_CVo55#=Zub@{h1lhkPY3dA;uuzl4X(i%A=6&az0lpp^k_2RtZj<7mP@2g{Qy;f>w z;!nM><;ei((|x(J0Ty?PHSL#TZg7HTl>N6M^tcmg^Y$9*-=1u>-7!qlk@Afx-Zl=R zV)-ynLeUfihi0CEO*BU*I}r?}x1*e^WP$qp-7=3|(zwVQu*s?H&>>)cNynf}K$@d<3{J4%uI& z=;e;wTnG0&{b3zuUQZ-Eh@Oe`dDYoV{cwq3!Gd4Eh|?K`j^s)5;w_cpQY}&lkjw9F z@kcV>=Q-b^#Q3YJM^_8r7Y{o!{6^&x;k}SP_JB=nFo7{N|1Ctbs5;?cHIU7`_*oKlf>sJp?ooC z-0}#qt*4ngg?^(_5HVKNt$k4M2oxD(EZ>Mh0fFCh61=>W`M6giJE9`bi74){8Ve7c z!7PWZ5L+&tCPxvqq{^x8I*8~F^o!y;>YnCT`)>31R{qs5*YGjkb|HUo5q0Qxy9kYv z?qPx6zt|$79@v4%@X8sDr0EH)6I<8p+Y*-5Li)lZc4z!r&t-c55e%JeJaS|t@;J9~ zB{@6eSsBY1sp0MF7QxYFFX$R7cX!GaoaD33ev6|8{tgB2v+7&}&TmxGNba+!SH9QZD8HX93u%8Uw)J z_fAb^VD>{QPSYFylErSer@yG&Ranl5-f3Er{v?sS<>p&w5>aFJ<1F-It}Mp#IpfAa z$bo@6jLGSTOS(Ccm=*avmkwtJfSmn=Pk~vUZ0EGDad`xBH&p zlWd87mCXHuqJ*+6%AA*)3*>o6Ch^Nsk2uDU7jKxW4@~d^z75LB1?$X(-|m4~{iekNZfUALKrpKVPeEU7lV{;SRuMqPLP_gtb9|xw<vu;P@HGz6a51k&EtAq^AhTw)DC@5Rb#xMU4Ph7Q|R0403Hi zlk6qIm*nzkbu2-X?5{w3@4yhV{g!2R-2Tri1XEQGc9Gk$a#9%ae3kg&dY0^KHKKWL zjw3Tm*VR@thWxe1EGEw`0SDn;i4Xt&LS6;N1RB!J^0%`Eq-38AJdH43>Xf-!L%9jvgmYP77(RBa;obm@BMH* zBWq^X_L(xGy9#)JopgBI6Jpi=I`BgaK)XOH-nz%(J8IUV?im*fi5l^xd^q*NIU z7rRR!lee=o-=E3DsN_hR9W|>PbKO-`B{wMa7EVoz?r?1T_)V^HsUm%MtnR|=cYh3c zpq%{K*L92))``+gH7)iZTtwWIm3`;Nv@q7yJ`P^|Lr3a90=iOdN>r5pL8% zTBSXtekTi0c^gEJ$8);Ar;kPaEw``;gaI|UC|=Xmq)cRU_yIcCSwv85K9AI?Ssb*fBm zq?&v$JqKXRUvgoX9g9HkInpIRMZ zh|AKRJq7kbHKt$`YkVS4mw=OUDq|Y{cD{36{&kCvKWK5b$Sig_eLw1c5U*@fB;m06 z{h9B8h^@3ZtGY!?XOFB)>DMUd*RBK#A(#wRlLBlc?%W)Es)d#zh^I$g&?*&%e|3`@z(qS28C>6 zB(EP((DojgHT=7uf04*b0gZnlF;df8X{;?_Pq1auUb7cpc`4~{uvw>fRqsMpq5@*< zyG%uUF}qOlY=^Fbaj!MG+lgRHGlX0|7uyNmi(dId@bGi~S3R5*Y1O_kZo&w=_;RFL zkgxll+ULRdiZYmJk~Iz+x7$reW(;w%+^~$=g1?yd86_yLkJlfR@t5XW5bH>@uK@qS z{kg>3SH>qVd;2PQquKNZQv%l!BT2N0D5DR1^sJ5`o90v;LK@k1Ir25zE!*dGgg|uT zSWEq^r+Rg>V&HOK61=8_WtuQLTz8rB8K9?0%)-CT?g^Em7N{lqr6j%RjF^t0G)AXl z(uzP#2x9NV9pQS3@@X8kwImtY8mZ))yd~wR#cuslPKK!(TL&{!QfZruCp2zm_YGl8 z>H)wx{l7Hm7vb;d@}D}_{Fa>mI!1QujNhZ3zf=P83aNFSB*9=v=&-9IBRfi@x~)Ao zR$}^^WBE1jpV+D50Mw{&LKonX-Qm+@^p2;s*?(&I>BR@T#^ zPZbtFF$RSSnkQO&1S*IbC6E5hFuh%Ea(E*(Z}agnfHaJWtk;?#91gvl=JW04aVdN_ zA12JNcYRN|4EfVDZ?v4n;H5t?p@=XmW)`Q^5EGE+aH5??I1#@8@#)LW`Bs;W*y?;+ zF@>-23SaVz6I@W93X0m7ML{`VVT}>CS(K#LojLvW5w>{m2waD(W79D-zT}mH!Wt_Q z`O)cidm+482RJ^cPk_XMq-)ATlqyz`dU2H#&l-#V0X8S!Sc#uCS zQ_Rv4R2)x(Zp50yfGE0?v$W4`@t4#;2&<^N{uYd_R^`T0d8B8c=j)61(sJfPfihnu zf2=_BTQ6jk;to1~^b^@TqI3J*MX_7pG- zd-4c;eZEWkF2ZfB|3UO1@Vn@}m(?BX0EfvGC8BiyQ3}yE!O~)@XDXY9dCFH}U{YQR zJleq`0sW#d%z|5^-kF#dus%*Vv|(ux?ZP)y8ue7TFdX7^1b!HBn`>-&bc6>P-0=Y4 zYvKKlXf>$cwZ9kWoI&5{U>plB80~zxa2^%EiA|d2H^K~han|@wAq4G_ph|oOM@;k~Fesf#z~PeZT${m6-Q7FcmHr_*HnuVAzz|^zhj7s`K@9 z8*}9%q-R1*A-L`S$A|J$?dEEV>?QG$83o&OZ2R7H&fYz}>WqY8tKe36%q2w7k}(jX zgf6>6@lqC!@yw~YFjvMg0zi-kBaKn&37Pws1B{AGv~samN5{%}du}5{I3Q+o=8awA zOj6&2Li};J`xttitVjKHRegdQy$Z!q9PN_E*>I2i3-dDE6-G@B zZjxc5E2w7{sGra* zuIDMsU#dCrs1!!)qxh%w|0vxoQ%4qn2TlQtR{GQ#%^~>b+@>Lxe4xVJ3BeSVfNE}K zWGzp=O>lbu0CQ;3k3(>3@a;LmRA=;KHv!Iw?pv7Ae>ENtU=?=WT5o_Do%JGEU?P!S zSiJ6LI@XSVrQ#U(?lOmr*lFADHvyyUWap4^DlpYh`A@{C!yi((C`nw9^{6=zoDSYM6-2otC(VM3zlCuCp|F=j8@)BejDb)7?vXj zI^y;cVon<{l=~Yq8P023ML5hT-82BDJa&Pz>L)n-4D0$Im+JA`?*bnWjBdIAv9e<| zeFeiH6ZPm;^Ok*0g`M;592xNPo#|ri<`=c$pSZ}M2`mhKPNN3$Vv)6diubVfI2zvt zL(pf|uB1dcVyKuQ5$K01px_aGjY`yAQi3ciB-*AKktngn)et_OA;4k!Ct^(S2dLk3 zOO2`Td`s_UQ(j0S93OAfBjFH4}PxO zT9PkOH=vrgb|IYpr$8RD8!yOQU3)m!gR(K;4QS$R?z~Kc!)2O(NVu$Fq@O(~^(cL` z{##}WbXR}%q}j%xI3LBtV_oVF_X#L0i@6D?nB--Df9U4QNPR|P5jL; zgAsw=m#oY5;w~h<6yvp)>QWm9Db??5!scLr95iO%s=rdn80C7?&gUIyQmy)6P%o))xW?}xlQNFi?g(+}t}E{=%W*8S zx`+wa;V|r7_k)DlN#erQN|tv21?f3``}cb9^K5e|Z}u*q0LqVmOov}NjB-e(2v-wo zSk*s0u4^*Td(D=e_`3trBNfvmVIsU8%Y5-fr{^_iV`sTv&Kmp66de8WjBk_CI|zcxhi6Vew!K-2-~lsw<($D@GF(%JOUKQMU;jqVL4tm^ro`t9zb%Ewao2 z^)SZ3TWe8@ahtr-`V(62(Cq`p*A%0Ss9(h4N;|bMCCTGP!Q!+N zuXWkp9URnR6zXMsI(rAT!V!M3o>DI;j9wJuPs2g#)v315w&=eR8WKsCu`=_ZRVyYW zPtA(|jGq;CsKhXyERgj_gNagZtlx(yE7IQbPHNM*1@^$EZ!x+89{yoHl$>Dx;W1PM z)={dNoN8-S<3{LwJhz!g#MkIq1!6g)?N?j&F#0JRa_zcJ%<2Bf?pHn?;J=Q`0$fa+gWhEPZ2Z)v z={f&phK<-rQLEfGi=m9lPj*S?Ltz8<77$=Tgm4e}GGSkkQ;F(CF#0&HP+?Msc?EOnLU znl%?6ag;nLu8L*vL$!A0<7B6o6l&qR_)Vp6G#!KZDTMC2xIrF|WzdyihM+>>S&r5e z$<*hbS59@2ythW$k0ax;75GlnhBUF*0dgdl0m2zt3@}>!N|`5YFGoZrB_W)*z5vu7 z8SBb^L(Rt{Td*yumAADy@l68L@+D=IKDm2g=jGFHltE2>PnlV($2`894p7C5^ zqOl1#R8vb9VkDP;?T>t>Bf*X?o|``|>KI^e)M>K@U7Bo1l6feC*d6$@fpBnZ&$1C} z4uto^j~l@7i+?yn**21|-XAa|laT!RBKBRzH(HO73fph;PL?v{_*ag-u)szLYIx}! z;5_exUfesl9On428z`_=A>hvL>^-886+;cj%FRdDdWI@P#C^sTy>_SmW7i%Lzz~>r zAhm4VKq2)!QdFsyp-$YI``ZY>nziH-VOX=q9D($p!K;M2<)MyAIHe44fIo{w?0NFF zMt;01#++7AT)I<8{&^wx5eT1YbD0ZEo~$X%*94ENx|4Uiv|(@8Aj-o*$XGX%w?R8b zRr;6$uu~hDPCx`??m;q|=5Ctih7rPmZyQU`bsMeEYGE_~ZToK7lCxGacL&*H%6Ir> z@TU4HpdQbO-%sD^w;K~B>$Pm0mzIe!Ya`*wf!*a+*G~t*=>@sLDTxWE@H<#Ft==Iv z%(D6cEuxl7lZ=ODu#ag8+na!6ddis1s{7!q#eX5dc~igu#~!~!|G<0tT>SAEAa%&( zM7*?G82m$GtOKVGOFqUp`{@H`cLezF@Uu1zfXq2?c;nOg_xXq&dNDTL>gv` zEWvug4-(rie<3(N!FlzHL!5{t(wd=%K>IEeiDg97Mld!7a4R%$$_%V?G6VC}tTvmr z2CV$be6JO@IQ{TkpTtO{G%4Vb383F?eX&xv6FG{YnXYhJ7$)J7h-`df3PJs~)0P#m zW!Dy7ubMZRE5e%#o-iIX=#&I7I)_WH6=9~psRxRle>-ikku#4=dt8^Dq0|1pSKccc zr=|ds;5~>(){i{CIFgE(cp9PZHn0S@_z(^#upHGPr9~ueMj-+xv;{3+Li@w#=VMs( zhBPP-3YT;!-BtHM6)VQS5793D*AO*wRvrOf z;Rj--BtIKsTisCht#3ktqOV9c8`4xMdcVv1=q~{zHoUu>w*Msh^_4DD>uDeB9y}vW z89!zS267*LD~nzwbb7&(GpWd$&~eew^Z{_Rr;!IJTgi2{OMS%852Y_Ze58x=DeFOO zXB~V6C$Hwr-0^{riU3jdK<`)oHB(vIfbi*b!PZ?4@R>V@2#8g`cH-`ba1}795J0c*5M@RjqOA^Xl&%5``IaOD zCnH78Nnt-;R7me{0O8LgO#HuoYJVd}3oodSDWaesLf1OXBp3fR=XuCBbR90j(iSzCbJ zOaS7}Sm)rM+7Z)bx#Y$0^`-49{h#6{B&U%}gVDq^C{rF{HD}ETo(p4IKJ~AMzYPt~*$lk5(Ix0>xZUOrg!K);%tRfi z?0YXCU?>t^fp2=*2M+PJ01FnjU$0%cW}tZ1Cl_wr!qI)?el%YjZjyVv;MPISGOj%@ zgx7;3oqs3jd7f~B9#uHTh+b^Dzt}!Sv?_C-|1lF)s@kEOY0Mw#o9Jqx#p9W1*MW zT-CCMaBc=FTX;rc&?AI+M)BA)J>d%yc4Sp_htKSnuzU(Ywl~y+wM8ZuoHz{kNe6js zTkXkQ1)o2+JsQm->Ut5|gMgI^xaA3wor8&Y7`|(hL4$b~5i)hfR7;LccqXWV^hF#6sm$0KcuEr;#D2F7=JwW}eC#klDxrDO-$90ffZ7^INiteodvnE@ zQz)BBqup*{VHokfb%oO>xCoUUiufY)?;3}OJ{k@tteobDiKB4yB?0ocsKJZ6gh#LW_xuJ2dN6Nbn zQ#w-e{EuU!hmskQ(NIj!JY8&-RF3yQ?cQPdcB-i!9>gsA0zWlA1dS5nN+5*H`uO4F zO(?;NfcwZ|1m<2c8;dJ^An}+FXr!HqmX&`jLeWei_z|resMHpMz<^SJ>z|Skp~k#P zRwMrI9R4U_3~-;LtnPv1pXyTEX1k=U?uqGfb?Q+w>cFxpyxm33#k4RPZ^OA2R?@ZE*t;N~OM$W0zfZ5`B;P zX^|6KAqa~rXtINy78Le-rYOBP?w7|x)S-tTbs|^ea9CQ5oZf^n8|u^q2e$WNaiJJjz$zl35}fIjyP0)?1_eF zw4_(yn-^Z;N<#@j`26sJ>+D8*jgoER^zL4rP9!5^(BI|X-2-6#(UJglW0N%mU8T=j zi=yl}gUq?SW%!Z+lCWPmBDL>xO{JYa(}eTaV&qmM z{@ZB5LMW$Z7T-(sYTP!x;x@$e_n3IQRRTdYI5~LR3hwdVRS1D~nrsETv$Q~Bns^`9 zi<|pj`*tom|D}2q%|6&C=)_&x?t^o#ATXb|tq@EF0E{Dr0tzW9n~#HyCmK?S(jm2r z4ZX@ERT>BGj)ybh5o@WJK#U_dS5b^>;MWi17^g*6FzW5Q%SD{>R3gJ%Cs6s$Y{|eO zO?CM}Gj7mskt{%ia3T;F+xl_Q$nu-54m{4Usj>G+_w2ccvoL7m_HudMmbB%x(`qT` z{*iJF0at(WD#QUfOR%x@zJpUe>hkz4j%CSp;0!r2{J}3g(doPUw~bi@d}#+Aq3E@r zK$ex%77Rbn)P?HQk{bsM1F}BTuIQ+y1YHh`UGR?$z&{6{7mTA+6X;+ef%BeZu5Cmh z>|5d|B)1<8j{Z?2IN|k6;sCIzpFkVm2|3u3g~Z{P6+D$gjpjwzW43L?1c7UN+bqr! zlO31V_Pdt zgd3cx8Zg`4_oPc>Q4c>z7@?>Jgx?rh+#<4%dR$DJ^43~D+wfP?A24nP^nGEq8NLd_ z{qdTMqCf%j$nBW;48QO3$MlO>dB9XI8}$ff(98vhDIICnPI~1ZW62`Qbxu^f|NWYK zY;dUH({QBu`T!ocA~9;YRd+&hkqbQkN77nl2~K9M1-=Y=vxN;+qtZl0ym7!iWU>(V5@2D%J+v%Ar%|hiGdE#~h20s|!!rindSh(| z(N*_INr&C}*251wV+%n{^yAV$5~;@qk93TgkUNuL1w(F-_sh8}(rSfBqP{0lW5Bu! z^3xXu2&|*+_F=qxrkodcgxs1FR){Oh6~GWl?;IvaexWUzGpCyyV>fE-*E;$S_4*ed z>^w7**q7I;?9lhd?+STu7ImVX%}HLqM^PvS%p$_BsKHEN)awC9Xclos-E|%Q(~JME zc;v#9D684t;t-L`=!D%xAFvf`#uQCBMQ6BM?GH&4Ffk{w`RVi-kF23Hd8<>4iI||BqQ_yq3jbS+)r?IDGEiZ9kpvQ`TV zA+L8H4n{iB;kFfQCy|o&W#5zLFvhZJQnE24SvA1t1i9<8EMdsyBJl5ftZ@E!k$O^+x-+$4x?o(03ov&p?Qg9=5Q?zNNQV_ zLf?~&F#v%)pSC!li$B7k=8>kOy%e2K1W9DuZ2K9N9ja-3Z!C3_Ggh)XhEMHN>@q z)PXJ`mYx_7{O^A@a~szeBWF^{{+C@DXzA;SUk!f4&+F`$!Y?zw9$E80Po`nc zuY!OecGG7~lOovsJuHjcIEN+Cybb5gR08e0zjyyO^})x0P3UMGxgdPz-sU4{I}(Lk zXF_?jNxnVOhMQqN-HF zckWd0e7~JUdXUutf^ezo0MNy!DscFAmGvvcyR1ewSUD2AG6V`_e+_>{*Goyjr}_u- z=D~g_8iLrZ@FBu*4~s0IxN}8ex1HONL~W$`W*f&V+MXvKn;wig02gIf-H(d0{0-D2 zSaGzJouxSuQmV$#^9!g87Nd)xsA{5~PFoQ1^*wP;6AfDN-c8pA>Y0JZ)5} zDqO}_s1m+tlCNE*Hd!j|u=th*Stz(-ui%{<9<6jj5SD!0k9(gLJ0bm{n~RwuyV(V% zajQYlNfh-At)DGF{3=WYRPa3O{HJ*~=y4OjE@?TQc4L{{CXR#!o_=(75@T>iya#`- z^h_)=*92_^zvFJws#Jqp$UiaPFVf^a{wo6JF^wlF(?#1wka+(sSIwU@`Fj7G&J+R` zbQ6Q@v8v?=f?y0W(R=jf&szu*O=)=gL!%w>;lSs+%ftOiVkFr2w@RvGI47tTw-{u> z;O!lXE4MDFM8E@U9YPX~lw%sRs-yk2oRL!gj#CH6*|GG)2rOMl0*UJB8RfRM-gAG( zvH^4jq_0Hd5Hos zElk9r=^`#v37t-F&#w0rXX)*rLJk%sUbtLFt30g9^rE05@vJRCx&Z&D?-iNg6hjYIvQhcVy=*|V&>iiC|V z!qVUNV+C=P-60JhY~4s;sG{u+uFhhwNYxjC(7*~$6Xue%GuxQE!o!9$1=6&{8VS3~;+dSBzu)zQo%!i!eDD%_) z75`2>?CHZ{;9%7~%MmKeGKa`BQ~m%G)bi>;zVE&h2EMjd9fCYv6FSnMfr4MrY|oz) zpVv^Yir)F@-Fb8b#0OM5-un-+4rj;1FjcLFih>>sO~h5_Xv9P8BA!lb8zUb~L|^S{uU9 zA)?zL_+td@HV9M-g@K+}qYy4!e}u#jM(nVl%2N3)u#%KSP8RVt=1Ta)eWF{JwR%Yu z==J^TLI=EYnA9%|^jYe@JxfbPCnDv9)1&sT2)n zyK=qWWv-l5nXTARA|A_QRJgbm=xzjOhxIgqpksHZOY_d_T}r1jBg2W*(t?sHiMbmq zLBu`qLxZ}L%_`92%nq;rXgKug=16!+_C+I>pW$}0f!8mWweWjHkY=S`;%TKetKtjW z&jAj55N)@N3PE~wP%9vZ!CMM_{RsKoX*a*)+&un+Q`Xsb4=8Ai1eG%?l!rd4}=X=h@ISy825S z`;>WUb0)eZ_7bCBISw$rjQ+0l~vb zR*u|)Uf+Pb#)JolwbI<~>&pD{LLNXpzNpK(vr&8CZyN9k>rgV}5q=3&uHg*70AXYs zlUW;EuKQ3gVJn5wyPul`i%JI0-JAA!{~$j$-+p|;-f)6~Y2`e~JSo9j8~C<)|BfFU z)H)n*juh@*4yN_CpNIE=SH;1VCf%N&3DZ6#QCUAf(r!CNcm3HaC1izW%K`PO;rD=* z>`-z`X2&D9GfTI`!N4PzW{JdjXg1Q;kd^i57*OdBLU^-Cdn0=Wx*5O_#H-}hjSq%$J3l%{ztK>5g#S-v<8fRc zN_17kSp@q*FbF5sZQh*W;ji?Cq;*3w@ofA+kyxZc+AQk(@z|A{F_t)Ze`;RH!p(UhG>fCQO<{K)}B_oOH3B@y8EX>ra?WZ+uhd0_9t&RCckI=kxpH zlH2c>#_-SXCgYJ|`;0Nb;J{LTTjWf$YqmwHc_=ZgRU^T$!?`=4c}K+)U^9h+k%XlH z8-_7>765>A|1A(C(14z&WKN-6uxIMV^E$bjq7F*-2aiR*_);h>MSd+{Kh&`ACr zCV@uycP(eR-e`(Ri_iFBqw-X6{=dj?LJ&&AZbYM(sfb~wWpzSN9Yi)Uq9OVLMgM7t z$=6rr-qmCL&MgA;^@YWN*o^z0TGE@1EB`}|ZV}gL3BalqF?=$W!rHX=S}VN*;6@Y< zs3#%kd7jw7;#6YR$f$VW8lvDHRA1#-?9Kf0gR4=grH(`SX+*f3Zr^ zRguwgh221^`i^;uMr!%IvUgQJw_(YEwu%qA{aat%=mK(-Nf^NFS}f5B$AWj-=g4Qv zE_}j{%Sp>Hy1?o#N-4((ZbcXz_sJ^0O6G0A823x7YzF>)%cp6RcyITo!zH@Lpqs;! z4Qb~!nZ94tru4_L^&|jx7tj8nQDc7vpYd4!rNrbU%ZJNg=VtPR$3M?!>2lGmF=HC1 zGiN}WSNt`bJxOb@ceE)>wU6-psh+2T|GkQPA2$V*m?0Y|oqNhFwVKPpfQPwLm3Dp! zeu&hlqv_m-Yp24QLJd-|zKoSqF)iJF46gF=o+eXO^~AUZZMLp_8hYLlLz!dzmN{i_{9_r)@YZVF zeBjr7s%-OvtBcLvFvzc{u#)jmy+S8K9PR8EhCk}Z{0k#VZ4US?A~llt9s0)D2bfAI zOnnbnF=F27i`&Ooo~Z{fKkH7GzJ#^1Y&9Nu96SBfOT2aqY3D3I*04BMa~ z2=NR^08fl$f-2XY_8*X&J?Myc2#RhhUo{WBgtFQtnJgyy1$WCE@i}#vES8PBkTnE- zrLp_CeilnYKJjE3DeQ9kx%a($&U*3&g6yFwe85Eq+;w0FskvZnuVdf@gcRA;{w8t~ z_F?sb%SlT0G9Ecbel~$)gz$0ycvGBxBD0x*{ydy) z9gmH!TLtn8uk$`dvT`URDu2NM^y4VMBsT3^KP5eIWhm?;pV#U)qH(tCAB4gNS9dON z0`JD-A*k^fE?Hd91{-UH#Io_Z%-vY0o8=Sim7EdGX>a#NuOF+K3J4GO+(yXFh3bvv z5u$euSDc#;>x?Q|PX3nFqJutaVvr;);#Mph5TIH#24^FfhwOe?{DAetTrRaI5Fw@l zem($Ayy|rOV`YCo%z;lQU}A#ZVAT9;0|AkyNTIY}!tbOc^Kk*t4Bi3)2?Y@^_D$A% zAKxXcVaa_gAv7asD1tf5(>4wm;cps_?~Fv}XL4rcQ?rMRD5h%v4SzH6hg1o8B1}#R zYrY3A{-?l-w6N-1fqi=;hjEPI{9-;5B^@ekpC$Ef4BG_P_wuho53_?>=sn@QOKn@& zC&vi$w&Sn1jCGM~AO0O}eeIl=gnaCVUsjRxKqfwSzlt=DjmBpS0A4)_9z$?K@CRQH zy8IL;zYJggscNK@3jDTxK4yL-Dv_5bTzicTAy-zH)+I#x-dO(kxorQYm#G|~L92q} zqCM#H63CT~BxVFAg7ShOpX10miE)D`iI``q{i62zhshB){)um^_>BZ!g=<7wb!+7f zBVE)?*&faDsy=%=%qkoF{`vZr*rQx`ZNXa6!a;so=rM9ANNB6g8CAtX5^mG7yV=LA zjE`^>C@b| zwGthnpBsT^E^=k?g1ftpeoV{z3~eyd#BO3bG=*z8Ik6Bob9SY`s+(G z^;$U%Lm!NsF!;G)f2a4d#9!lK$J3Md+e#m3Ts$2i+-anC|0$vwn_2^V>-P&DDLNPP^+^eb zWbS$hMJ9&Xh*pUiY&Y-TH)vM1TbwXg7zO!4mCGxp=gHCfmzkc14ZE z`oM@HRyR7qbTao()iQsB9*27_`?{fMbXP>lY+k}#WztbELea3B1I`~6`3BP4`K|6Y zk4x%+e3nG2+-zgn;+l||zx`iSm$Hp=+$^sjK2<1JE>Y83P>u@_I-SlKYL|K&dR>wK zct_+};{8ih`V}9ws{sj-1_g^`kkF6@?elCQHc})L-%j+U`Mm9dg9e@Suw^EzUy2&7~t7tdbpqd zYP6m2{6gU66mHo1>=ook2gZ;?GKOm&6HWx>K0=zh-<_ErvUH`rix5-q9au-jo)0)& zF;@?`iUsT)YRZgiX)wPI&6X;}dmICQ>U`}&H3%5S0+R9!BgoZB<9cgAJP2JZEQocDJ3J!bhFxS?&NE zwcHF|Xrv8HSfPy|xq{sd2J1sI|0XhU_N#3J8j`~3Zd!uz|H2TsvTt&GFM716;OE#2 zH<_F>QTGJzRY==jYjx`#wJOw@tu1!K6e@$B#*4jE=Icz_*JpCXiJNmhbL}|viLec@ zm_h5PC3v7tFsm%A@(LijL6xQsS9Q5>hZwf_=k^5Kj5Ez;LGeePca~>z_X)Au!DWp* zpj<)Q6R|P!x^gZJs}i6k2IFy_CU&SskEV9>#r@vAlU}Q3*>e-cS4#c5miCZZBg#G0 zpg%wUk&Mjus!${j>`;ey%$TSHjYKI zt^A__#hb*4#m%?~#ITrhBqAcRH(|{wIU95=<-fo8{lG5PQzVjUeHz{)t0%`X8;EEI#-K&Ms&aEKoc<6LC zZ-$f1;~PXgLg0O-tiWBnrC6sZ7bL*FrUs6n?(o=SVcWh~f?QwIp8#A=-(D@$8@YSYAu-6=!%$C zjjPid$Gy+Q>s~lU#b55)JR#Xy1m1bZ4EM-E##M%S(e#UnS(6T!iNFI2)CPmxxa;S9L5#WKGAS+3uD zb9*?OO{@{uA|nK=Qrs{$6154x-}|jEdwBUwDgaZtx|8&AqCJF}_4m{qh#aM3WxB55 zCx)~*U<5aysq-4%@YIhb_B~$QlU#Y_ILgKcKrp<6h`nc++b|A#>;>xTPRyi+>8r;di0i$06=}qF zg*@MKZU5Tr`Vt%n0YC=lEISrk+~uE`4jigsBX_JWd*Sr6z@# zaJ-6o%i{n<56mD*lCH`I+NHtFpg#(LnU!7VPsuX};OS%l09Nz@4ZlgmIu>jM^h2(1 zqHmTPLFypb{$F4p{Lk@z;8{*{=?DlIMrG1iLo+17Y=oH2{l!(8qP5uCtj}~uS>;MW zMg6+As;i#YysLmhxpG+$g+35bcYRyGWB`GV6r zd{TU5mG~0WvpD?*5O{Kj&WBD_U9@pF9MwpA{6DY9u1b@dIMeG4^ZqgY>WzZIH-DUrf_L>rzpE8Fh(}hfscG;%Sw{ z@IARJftw`_q19e-JOR!yqC4;1K}$dEs>;=d15^^9EDI87GrGpJT+xU|aM>+IyC zaMJbB@w6cDMcpc_5N~%o3ZAodv2d4v%h>#6vz20(UQC`#EC$(YxQ~d)XtLeHpElo_ zVV()3ISTCBORdl@+jPBAzcTZNrGE$-EaHQ5l_$HqL*VMAE(CmAx790#C+~wQNrl(z zTM!4P3pJeRPvTBLK(C3`oamiS83h-D2cGf~5#ccn3T7w|gw?ozgS2{C_`m86q9 ze?>*kxvLch8jEsSt}`UnKV(+T6LASs-JbTZap$l~xLkb--z~t+zhxStXL%?IP?O*< zh>h)@*0xpLgHK?+tD-D#TT* zD5uMA>yh(s3z3nXOw15IYfDQZiXUtrg`k?yvTX-i_BHXa7$sb6w(8#!kkLo%fNng2 ziIWcWlf+F)dDsdOWt^8q`%85P|7QWz!)SwM9jVRw{Q0d-$DxY(c5<;-zqDgmZFlB# z#8o#*TW20uuNqA%@@mFJ-ingUl~<`K^5L19Yl>_1ig4{Yo8n1Z^Bv4&${RPq30dwi ze+ej4j>D1#``22{!B-Si6T_}zoTciZ$QW$p!%GzM47Xt$aDK{A_du5kaif=;=N~o1i=_chPqT`@qsrCW44QMq&eKQk{}#c(&_jukFE}_1uY;(!tF>hU~Cs=B-s-z+#KDoym#q-lc@=l~z9Yd|0fSenTzd`m+Zex3YJ5>bY6*^*QnLZPtD0i{aFF z=i;i7_?FX>Xex9MDa`1z)B-TYVB(S*?YrY~1#j|tZ%dZ9yYu)WaM>LviRoGR^|+)C zWZ)P5-Jm2fk2Mz7=gNw4{1~MenuQzIKuGh(2^jWF-I$&I@`zs7XY}=Mhx6xvxOBvN zEPiE-=yfTnUzHl|U?YAtwev<&=EJUazM26mxADNY5Mh_UEo#Se83DS7@=4lIK3u(A zd5;Fh>e$qz`H!Q1}K~V=k(q|ID)k*K> zKnqx6#K?2r1_=WW+uwk$HnZ4oh@3MFplWFpSYm9U-~?3L*-9%kIFWMdXQaf%O)#lt?|p6EuqTT7ta<@W(lm?vf{;3h3(D9eVR>${ zu^sP(o#JYVhMQn8Sb&7rO+cRD;+M}6VLe{go&2DdE1+o_e~l^X-8H#;r5??@re6mh z6jm2)p3?7A<-{DZ<7&y`&f6I%F^`PvJUi^eZ1T4S|A-(h}1ynrc!B8D=0*R zXZY`7@NFN;$D)7J1g}ajJwEgyNON5* zgzdadQWVeYFifsQ#M)?qXsLWxgY93Xiqy6_micHhrPPL$+(vWd&9x$@yUVthV4Z}> zNRIDC2qW3o3A@-PrEcdnx$aYC17m)ty3bv)<2#D#{OiEW14NB|Ykf@sXYuvLQ<*v@rl z(34gB9Frsk+2CS31_>y7eggJABz(qlB=*t*4p~Rt_)WQRfpW^y`i|6na6Ar~64!>4 zJJvgsXcwrcJ-h<;-TcCSv5`=b8LQNM()d{F>T`bc?bDY%TuVsbX#5tcntrLWP|6)9 zs|t`K1I}Bey{ps4dEfE@K?CuToz&eZmr`C*fAMU{gp}O~1FOrUV~!%x$${igov_HO zHC$V}MAJl3Z(NlGN87nBmKj{PjM;Ghsr!k;*AcSP_WfMB)+paB*aks5YA|Pb&;E%R zcPrCFxK&LQO3bW=5d_E(a#*zQxB{ybNMr`546TMbFXlAw?LQrvzwxaent~C=P{0N6Trd?m263MK z%KQJ8rR5wkxPXV&!IF;0w8KSL@PZF7`1}hY?EIP>iu86Q0&@GBEVPm*E42&sN z{5M$jKGFRR2RMvV*GAEy)o*qOS03Ki!{$&2(m{26T)^FQARRRKV5=epADw zH~$4fAZJJBAZW<9AG;aGQs1O>%s%wP#+yT?3z_`v9p=+a)^-6Lp4g`SdX_UBvx3%$ zilaD+WfZ71A~dtYLuW0Dd4s4G9NX?kKd$%E2=Zx3tT$51NPk^O^}+$`wX8t6;T-s{ zNpb^cnTnQ9p8fI1oBPeqt*KLCrya&)Yr{7CRHH_|WD#mmI9k6W(x29Xkml|BPSqg| zmP1-e{}|_&!*`}_ryr;@sXqb+ODiNdn24S}y9eAImz|fN$gAE_Ha~;Ql6N@jyT7U* z(uM=Ln~ge}$lSY35tSv3#*YW`ZJAuU4galw&(Hx^k}dDi?aomi&*K8iLIjz0*_UhL zT`cBsYEZ;cxsr7ZMjc#iezEKpMh(a%l0nq}n6wzi`3pv< zP+N1a%C;?mNER?+Ra^lu?$z3)zX`h-aa^uTB%`qX?ByCoU9;9&>0~xcFKGMi?&M_` zb?4H42@7j}oL*FNSi(zg%?O>pKJf7$o)`kw?otJvsm4v8P7RU9V4?sPuj0fb@E+8#!)O`NKjUww^#+7XOpge(St+^rB_C2_|;!5rH2EW=6&*df**q zM&Lf;VL~WTh~B#=@W&@F!i|rvFNf4V*7_tozdpsKdJwY=J2vw2?Cg@dGTp*I4r#0^ zJiK>DQh$Xfhw!&31@3&DXYi3ODiiaFDFHA41Sub$F}jpzt1kei)DG8*ed1{C(%X(1 z#%R%@$i*8}5<1cb133KK#Ts&@XZ5b%%NajswDeJ40F&64)aVRbDPh;wFgS0QF2pWr63zF@HzrxN%x8(JhBffM{bD1$Z zEeEr7?*t1eOz9Y1{)kO4SDZhp5XuPBdFZ~oU-|PZ@8J2YQSnnt;6=x<+X|GQvzBOL z^Kql%Vk?Ki>yUb(fhx*7WUI+i+fcSvCXHwXjhBw8Z6{!XQ9-@|yltD7C8Ltf05o~- z^rzrq+nLhLM4d>b-8+yr7`(q2y|xN$6f>c`SnH%7)r6;x1dw;%H=>eh%oKtvDJb8$ zB4t>`0+YhYY7GXy^+^Y@O%A{NfT&2GvDP|LaGu{+%NA5#;C%%n5*-0 zGwpJ-u5rb+&8n6`qE|w%{9UK!juN|NeFQ{5ylTc;tJj#>xfQ4~5Ghweh!C~hwJUUc8*Vjyhu;t4!acnBQM|Pvv^Eh z8!`F9U{Zct%q!M3#KDJLqQhxj5_#6u3i=C0LC@@`2f}}1T8e>Bo(HPt>r}}!;v8k; zNtrtAS`(Yq^9G&lR_@0B*+KWeMe1d3g8%lB4NR1d##^#K;*xYmA{~&9Z$)6Hm)kW; zdv5xy$WcBO%GF?!SLYXFxGqCvPgZx7nE#}Ws#MsVDQ?F#b8xkpr)jgQ%j6mi8Vzbr zX3GqW=Edllh;Fz`HKhKmYj;}N4bS*C^i9%Z_wxxNwu^`-$3`|;UuMt@3DX|K_{6;M z<$z*~O~2B$-I${WkpK(riRRGa$i4UpzwHdguOh6wF2R(lx@M#H-hC&7>c}IQJB%|{ z6QqUjL(TEw*t{_A-%3rf$O)VD1eIW5U-`#OxruTN1%Yo zAf;UmXpMH5jSR*p)8RtpIvBA|rhInQ#TMfbP#V8{10$Zq6*a3`i2(V|+Fg~VAYVvp zJAPr&Jbd&wvneY>rEb+CbsvKlvc>Z9^;-wB%3-JsO5RFE*IS4=^N$tuPSNBnLoQ z4IyAr&&Y8MH~%N%9c*486V2`c@6+%+yonHhd0v_a(XeLF2EUdN{b~fsL`_hQGy-0q zZ?zggd+?d(L6@O^S!JG@|e)$Nti%0M`#-j-F&GP8V}pOxGPo3 zSmSON`Ok;mwJ37p?Zd<5&=p1&9MKck9(HVFGS=qc zF^*Hb75~e=Rs#JG-NIs@5s$F=dDA;GS-TcaQx%H`jid76PGljvWo6WG952EbPGJ#~ zI3_avoBc^c#*Z68JGNQD+zGuwm@%3%n6C zJEVZU#s9nbc=dYEvS#@*?8=V09I&^c(_oTH9`p4vzlXb*U4?Ahz;uTudf4yB#DXVM zv(;*fK!u96cCgsoHN`=`kOL3>5H3eibN$A4o!&VfcQ5hO5>`{h=j9Cz*V$3{+3_?X zdL#TFJgH>1CS1^^6zzm|TKCpHdkddNovHnF**vPXtVl69b8pyK-AX8HwHo#8Y87f= z6s;X5S{7wHI?mi}*>AxU!e#B*46K3GwxD4h!`EmT$5ch0F8;U?AfNJaNwwC>4IS9k z4cPr0`;Ux&U9viMrtuZ&X$$6S(VEhL%LI8O*ro9-E4r%bTMJR$4)i&j%G9_0*#RHe za#O?W);!oI82gsq;q;1MC`ylZ=(5qAMN! zxPNRVDBQf|Jtn27Z+Ss z&P@utEUeq>uMT;Jm@t-@k8DlmSUe1VN;}096M%HWYEYiZZmhYZ4J8Kl6w@G%hN{r+ zgn5eWeiC3IC3Ua#@t;B?tUfoC8vT99ZCspDKl=v@6mT1@V>y+~QcA2soHz=`IH;MM zx2F-*PXbl?rbn+tjn(DgUz5n}P8JbUNKF2H&mm1EMh!cdjrKsxUP*%D7SB`!F~$17aRC3R^x<^a8o<4D47{0~ z6R`VhQV*irSL>vkKmD3@_I_ILM@+dV@b$<^=kS)n28w)7?etaWUn6)Qd2g74PsC*{ z!oU1+vOb1gj5f}cMOSR(=6NSNUpbn!#Q%M%x^->dXUn>$j4T|Qc?mh0o4wkI*)r94-FW=hA7S(|kEk(KV}V_N zDd}5!g2?vlr+%IIlZ7`gNr*^ENHTjJ&MnzJuJqG;65LrZe^mFm_|Ee=;N%!y*YyWb zL9L;R>unLt#>VyW(RkZK;$CEI^B!iWi0j0xm2*vFMdDsb8Ej@cW67d-JFlV^2dYkP z%h`-JSWhHvemo99RP#&@Q?hxYi7amFx6QQXK9`NWCk(d*6;RoUywXvI+g_=%W*fuH zw_Oq}v}I2q%kV%?g+MkmLhsA)Wa-6Z?>ST$5wpq!n4ih8eeqItO8ZBWWC28K3C#35 zKL)d-hBbgBgU+Q6TEYA3-Z1mHr{ve86SuL@Bfvb60yh|P;=b)n{^fP=AlVR6L!|e< zvj_%(T&@62P_#Tx)td^pCo9&0?|*wN!&jlblrb-!=5LGWfo9NeYS1yw!3CrwYJS@q zrwEZ`+W>iXyk^`TTAhz4@tj7#oc|(>;%pJ8ROg(;tXjAVreeRxf{cAf5y;^LTAzf2*O%WMid5Ggp(h2^KN<+Zv_-TC}j$3cA zn++=^eWgi+eJ~JSuu6%IOF~P`FD6qQr|9PQvNZ(rh%C&QT8l6BHac7d>^8*-y)_YAm%&H#pG_=atYrggkp@L-{fR%|8`uw} zj*!gViOoXnZ4h|72>X>?k!V=Un#rU=lYg@9>x*xV&Iz7SZVr`6fRKLQkO!GQ^!=^5 z5TxmyW{2MsdJ29M67$1~T@Tq5lJ(OMyUj0mdKD6+?yKpX5n4t=^18i zj%7BA@0_PP1IjBpglPPdt^_T$dp}?ArU@$IDHVt$ma7(gxY_uo*wBNeEcdGgxMgPW zlDYg`dQv31H7np$sXfSmW-@}VoBCA*5>Lj-Ma-HY|H>k+QY-^aO|jpoNCPk{laX_5 z$Kby^nHBdcN8a4pl{A9BPX;U{2kO5F=fM^kqZZ@TrG=C=evPkss#}!7xYPdmyv*2o=Dy-XdTGq&vKJQ z`0nKL>)Q-OFT(LTv(rXtot|PD4A^ZXaMLGO$0Lo{Rc2}Mt9jn9ngd%}UVZkM{wBE8 zSwSmk55#8mTQ!eQZ>M#$4Pw&boO{F2s6Gx8J(@*VaTc8;04{PImL1H;^FHWK^Xz^- z-B;>z;;E+hyvn=fC1p)wD;lW~wC+U}pwFcVYM*LNFe;JEkbXn)ktIP64s2n9 zrq-WH&(_ckeH8EC9V|S!+im2vaC~4<&5cS%4@9|2XHlu7rzPd^#@=80okrEBj?YX- z`rM%XUoauswvurlydKc6 zO2;ka3}x~iNj&!&j2JuPh>>xcR)$SMPL^1l#EoqAB1yNc&*@T^liRGwe0(-=>Ax1Z zKs{dPJHBDUn-$_?=LF%g8f$)4h9~&}{zs2Kls#S}C=s3I)$tA|16M(!_Z2)?I2xxP zkIV*`L2vp$Rbdry`Uel3!Lwv{8p>gXMu3u$YA_T%76MTTzBvlz^%y^2=?zuln&K|t`)CkQ3?$?-)gQeY;VK2uA%#W@Mvt9l_+|KZCi(b zk3!aKfjhH3M4taP64t-?`1Dun>~G!>LW|)&t2ts<@ef-E3aGAoq<-ZmhfO{!n#S4& zfre|7ZuR<6(#D#QUR-nke7#+4hoAzrZ*9^Fifg3HZYukO1FsUbl;jy>EulU+G?MY< z@EHChs72q2!|#vH4_?z=aa0;NeYjU#86cA!<#rsK;7p8V(Bfn~{{h&pjBPa-`p^~< zeqX}!s{)yj{5#|`MC$D#l&&i>Xh2=P#7TyVwhd@H=f`CL7~L+5~>XiynEZu zF$sV)gz6Go3-fEP`OQl`o{HZ#bs(YMe9srtkMZb>!ij31K;3rZ7~@9)42EB6*o9<8 zss|}&v!m*VHkK6+UC8>#J}Jq-^moG2$lU85&<%LMBh4+Z^y>R{^Oj@aty`!c`?lUL zncAFX@sdbq^4&TJk5AeNN#-0TPRV9SWL!&d81QZnxbBAml<*h@POJklUq&qQqUTK-#+Z_J+z3 zd>ToF4V7=?up(>-Lpa*Rd8hvJgFVy$rFp~U&*sb1Kuo8wEQNmTb%=kjv!Uyl8Rrr~ zeC#BAPbTBRUCBwLeb(05ugk}}ugY#G@DlNdo-%?HmT`U75_TaXRNA?RoLz6xs#_REz|pwWrEfea;}Ek=$+-N}EmExZnYz9g z*wrV^(2^*I()&W$=trpH1BS0A)611S4E~UX3OfxMyWx_oa?SrLmL+D})<->O4pO!M z^K=JXvc*>#Pf{nKRqYRC&^wBVHF67rR8ew65hY1{50nz@!I;EvwQ9$n@i!XxM#Hdz zO?+OeLpZfK%%HqR5jv6;7BLEWn--@@wHS#zb8(0unFh0RMy6?u{?uwDZDBdJ zhBe|NpVbJdENn?4g`JheJ>kzuozHYea|;$dHs5tw{Hc5V8lB$f(7HS;u5B?WHVGSo z%4x7C6L)90uY;zP*z;pKk}FdED{6x#y3zrGl>IB^n^UYNp*db#h!1f|(fle&YCD_+YOB!*9O&B&2tX8Z}k@OT@Qr(FKV`=9^GQWgF7A7yT-fB+Uq z`;c?S?7XeRt}tC*i{v=2?eg}LvBJPPQckRB-Eneo0nF&h>Py}bpgy`jL?v(Hfx9KG6T1nLw%-xpb; zfS_>+#2~1E6D|nq9k$5D{f!Mb`1N^u>gQ4;X!pN8vH7xJ`zW8IX+$D6a~XtQQIT$vYz%YGY? zg4LfIT5xXX_VBB^&DLQPwldp`gw`5)BuTxJ=TKUxJ>9Aw0`(~K-NugjeG*5r$VJh} z3Rt^RZ7JA-cn%!~KBkvt5`l?>A>5`^SX~Pvs(L1Z9do;*(|37Siits{r2WpnlIVkf zN$<;#197RA)0er8KSm``wtc#@{Cz6il;T_sDMG$jJ&fF#d%d~ky+Phh;nz&ze3t?h zdm_Carwb^rFClkB42G}eZqGUU33ILI)4MvEm#%rq z(Z!mnq8EN71{OTNG-~-TyoFmtlrToy--wjrTefBhX{NcmM;Tol(`NyyMv71>Ae53| z(r(pkI6?0KGockF=;2Rh#uRV^Q!*Y2e8U%pk8?Itd-noCbtk-0Age206rKH!My zj`7huCZ>V#fMY~DDsQy4=v>!~vbY22b@*;puCamPB=``ob66$5NNQE#?uQN~-hRmU zF)D9(yG)J+dXkR;qN1i)PsoNxf7c~D17P|^jamw~;VT218^8{3n_WUV#mFc7R3IzXWWw*p8AC+GnkLg=So@PAh%r zSRrPJuZoG3oUhOQA&%Dbup}s6)x_n=LF4pDSChKmSTw^idUo*3`J0UntG>_U{+34} zZ)kWGEGTK{VP3(7LLPOTt|l78M*rXsJ#1x;9_}(Rl_mRUd|D)Cf{k|1262pF_@nRb zTr725%ckZnJ#`wfUi)ov$euM4_-V!bV7wsZ$tdP=hIp#Td>idLRpk8b<*WwA`f7DT z_t>h#9mdZRWUg(5vodw*p>ZkN;*U^MQ0=sshgOz5yD#o5d(_?S^{9INT=z9KnsyDa3ml%uYY{%afVvD@*- z+M}aDnW!I2uqZ6MG4tDyi>%Ru61IS6f&CMwk**KT|Lf_jqoVrat`AZYiZn=xfHX); zj);m#ij)Y9A_z!#2n^jFA|W9q9RkurcXtmRA~EC)%*=i7?|Gi}^5?J?%)0lid(YY5 zy+8X$UMY8FWSg592j#P((ZEOTh&F7LW0!5j`t@Njqd7zj1; z!|u+(Jq=_>a5gMN#_py5;v1sOSb|~}Tg$)m(H(_E#@ezy6i3#!kNMZAKWxs~ zv90-S_SX7u?zrCC3Zo|1H!OoQZ+wy0KKASSE&n$bZ{-2KzVuUZ#P`U0-p5Q5R^ic1 z(y>BQmyc&DH*Fffvwgk#mtq!9{+tnFO_xNgmV@wP~{K-3#V{+7%|BX$MX4BL^A zuL|!k%c>ssS2EXH41G&@?n)He9g*SGdD&5OS|U)HOsQyCa`n#p{5!IW_HWU-$EMnQ zi}BCaBL~+IuVf-)6<$L`@K3$pJzX&3c<|i+P9_I60DX{MUv1ndmKbkG82&a~&!FWw zQ(dntjn9%Jc^qW1#wF;Ow+zoPNuhev0n_kC=a%r0@t4I~BCCr4Q(4T)@0>la&f^&x z`l#@kURMlCv>Q|Ge+S!INI!%(fpS<-CvWIBDioBetE|shZ0SIKovBu;(C5~~x0J5y zi(}{PiK3fvrftz+vlSMAm~iu~E82}_h;w;l8GF*}^IGW#-DeI+mUdukrgFI7_uUPw zUmAv0#3w;s{G@dh%JF|+5_wzK`~Gz{IbfaV>1Yy0bE-%OvFFT+TdIQ@uX^2>WIcR` z+Z351Y9CA7e<vf-dyg>o3vgEY$anDW*$+v%ol2D5AD%b2z0vKi@y)Ot!EXGPt|7 zWPCG$=VJSOxy#5=$?FmB0Jyb6z1s6Hy-8_blBoh~oPM?=ccwa18m2rwl#j9~JJtDa*IrQ<{) zDAcAFs{CD4`T=Ytl_b|WtZ zv(Kh{?0yZp$H>Rt!%y~?7zWkob+`oS3%{?nl8FjJFpf+$-R-P9%Cb8dd+8Bp?pzD^ z&56x26jllZ6zOxCjX6uFZ^cG(8nx%zH)-ZKFJB(!Jkgnpa8yzqDz{fCc)Ta&l~}-B z0=rtdy7#vH4~ER*BZy2ipSMxImE)=;KJJe42$-y+O0ImK{cX&Zx#{zTQF+(ax@dqri9sSJzPlhYrKI1T_ckAl zmY(iBotm%~&H%~)@`xd~h;e5Z`LbR*#xEvSA1chp(lz=J>j+rtdop^C0Sf`eCa{gm zc)a8|-KH|+A&u*EXZtYr5F%rNhEwxqo}k#3dskBf&=E$Kc+Of}WG|!*DeZcK*FQD5 z2oR1}U}?gw|84Q{yO-r9D-pN0H6T4iPZ3=z*_n1&i|@jF&1>BiFnR2Ve|4Fy3T5Rh zK2F%-$nAZ3AW7^Sn;!RM4aH*VcCT z?v)Gf00se`ETYk=$tP#W@UOU-JpZ9@oN=u9w7i*yj-)byZ+$1!ACNl5-ZZ4%X1+n6 zzRl&<*594R_vCxK8S4BF%KCx z3qH(0XsjY1yg886_KZxL?JWpiv$@F*X0GYB8QcmdTGliyPpNcd9t@YTBesc)t-7p| zSg55E^{!GKYC2(fEqaRDj4QqsdzIGu@NW(s-BNz?jF?X#K=?)cbUI&rDqHmIw8NL) z`gLiXDotNgV7ONOYg11oopIlml!@v~i??{} zpz#2A_E-?lWT{~yUQku6`(I!|o?WHUlIU#qGsiGSYxB*K1>4gD5%@!WV+L?ChU2OG zptoKYI{&>ZEH1D&B_wjnr3ZQ@vqKnfh01q+YBMKT!KHjY_Cug(_=h-wmWFlj-q-r~ zl!lxbvOmy~{FfIcTq%k(jE>=(D!59$Gkfc$BdP+<(n7Il3CncKC=#RQYTBW1d?u!T z#7Bg!ek(>Nj$%S8b7og729&QC4igC_SV3d?*1(VVNC3={7OWD))Dnk1q;ou&E1&g# z=hQbE&Lc@KEVLkd7Uq&_mj8JqJ$&Unywie}Rn9F`tw}NxvSejuA_|M<&uds8^aj+g znaO&dCajR@6EVWM7#;sJs(9^h<5!U(dl_z z0YxnB!e}(ysYjYPh3Rj7XW=CX*>H@Z?EyEG$PP;{MGvmeOHXiJCo~&>le84I$?414 zNt#BMS}hrB2y*XXFVIAt(A(rI!)XrB>Au~+EOLM|RIe+3)7Z`i!CEAcM~~+vLr#;_fsCLVJ-($!@jRx?5Kwo$J8;c2vy21#9BV({Jre&`=%4g{H~ zBD=?Hk?9_pEt&o}aD+<5?yH+H>z=eK#gKvN*&26%tH%DTHqjo<2I|!_%~AYMpKF-~ zABkHxYP@jBP%+{)yde{5r z)}B`Bxn!xq?QO~f1+R_dralE%l*b`d#YY7e66CWx1)S~Zux*bYDY$PmBnuf(xnLaP ze|sn~O)ESYFzr?s)Gv$foN?OtPSYNxlpx`>VMxF>&Tc0GFz zQgNjH`^f;SNk;v0t*HVIe-4D%^h(>xX8ek3zQOTPIGxc=y$<|yYO{oHQPDCq(aJ12clcN4LyBiT zkXX?4NG*{|)R@zQq@{m(foyg;ie4Ch|COBc%Uc7t|EwQ(oyt(1xNX{WUB~E@#@^iP zcNE^&A|DVo7Ee6k9g53Zgj@#q?*3b8R-rvy8t8lxrRyZ`bxOD;RnUe4CCuggw0{e& zD*Sr>X2^>U&rU*y{sXC3tiwHJelGW}l3ast37=2&%q_JMn_d~d_^dXf_2RR%Vf#C= z@BD8j&?oRDI4Q>xS$WXQF$%>>9wEmSa9OG=9o>*@5(Z!lnD%H{Y zDz$c*Mys=DqHDx%%It%J9?`6;Ts$S6leFR2^e2wYOB>I0)$1$^VC$WKp1pZ8G0T3_ zr=KfrQpPV8J2)n6A}bI-k`Z%jv}3nd!emzlC78)RIM$^S%VazuHG8lzB7Om=k@+sTTvPIgB#lZ z8;1-HRb~ESg-&CTIM1(Fv(ID*qUoKXerQQSZm~f+>nY-s-O+4H=}$junPhUf{$1J{ zxJ9`Q2@%yw3StjLAVWa?aXZN+srl5b(B$vkv+5HOx=8Nlo+{&U@Bi2)NLY{emZiN3 z(ji7h`@LWu%AgN90mS#l+!KI3S%*d1lbR4acse$$q5UL6oVCsZsq|{1*ICCi!a)+SiYddt;b$82iFVQ=j6raRz0KOV2`` z+rCH+dJ!?8WkyggSS?J-!+Kz1+VHeeZ8x^+VX$q@fVRlX65VnRg#C@;EDG9YTW!Q& zQ{vEu)jw*;3oRekU9EXR&O5TW&R0p~Y~Tn?FfUc(ZO?39LHIjDAjpt~NzLHe>d&W4THa(NTrs@?_ zlIRZY%iDtOUDlMwlKR0jmmG$ddr49GK!Yf!gj8D4#6_CDn6GSkul9{hEQC(8n;xBq zt~40i%|G(pQKlaWRF*iVxiq!J4|Dmcu2xO!IHGo>nljkUY@w*~jOI)r0JLG_4>Fu6 z9Q1gCW%D7zzG*riT6odM9GEXM@=LdEdO!|HE`_2_2gx&XhBl#8Zne(`aS3^jW0{2C z0NwOPS4=l4ug2hD0Kv_LK6?sU(y2lr;L+0^*ae;OE#)1k(~I$0kPIFKsS>`0=|M5> zfVNE<{+KVzUX`7ePIX2viS79R=9`&s^3Y6l4`pp-I zaFuUbn_4iZQTT>gn7ai>LVsvQZ9{BkJJ_1<56Xu0#2KgyIaAm?Gr}>a@7a-;mi4xp@n1N_J}r-PPz+2i#H5cNs`V-vGx9W#g&0LS`agD`C=%r^N~na^ei1&aA9?>liI^y};*--4MPKiC zOPi>Y9BHLsxVF>e4UOAX?`aV68+;OSsf<7| zZ))Rp8!z6q4?>+IxdYm^bi#xJq+ELKdcSNLN#^W6>P^?%+4%NOy5_y3sa|V1Q);3T zwU2@zgF!cu3>CrML1uoZU#{yrKbew#DKY6p*;sc%7_P-%u5gVjP72&*xU#`Zy7bU( zeak@Ns)UvRg@{MCzWvM~_ArS%3Xw$1*O2@9Z7OV4fsw^&^YX;QIx$1U)|kZK_Lm_C->t`scOY5C=A+#V_0PaT-9D zqsFfcczPPi+_U$M1wEsz5G!^u5^3Z8dO-%`yoE1$gK5oL4|b+n(B!tzsNQ=Sk)C36 z#&^_+qhRgBWsKHy70{2^S;IEqNX{4ZqIofivSc`a(+b?-sI=LqHxys>H4rkUsc??t z_hD4)Z*=G)(5Q&!W66tb%ZV9A@=RrWz6p3*Dj#!YUTr3yA41L$;9isp*cq&e;{bqtEw2sS!bMsQe_vBuy;5)K$m~bhg|zUnMS0l(W(cl#P+(k*+IJl{S2u zBCJPEAJg^8ZkYM6%60U~)ggjB)!Q+}CT(v>M2aKhfCDF%rg-NHZe+F=M7*K(?sS`h zIYGpDRR7mDXIN!hAUDOvtO*&q%P5F3g6tvjx2IV1Xa%?QNfu=>rP1`6&*Sg(*$QY;g}^!|FGx7Hd;PpRU+i%{q#xGpehNfy%HM zjYHWhn=G`8HGb;KH;4k3M=NB8eEF(+Zc5z^rCpXrbnwAT!qIppTj77YR$&R<@VOA`xQfHu)P9&q;W zOR4@iFyNhlU)f^2c59CTb*ts+*1LnFrw|~lYdxfiL0s`^B2Je7JXs>kM{)e z2eJ{EC|^(K-nsqI1077?Gh<`e^SHt-)7sr2w9(l4%BB=HNkP=0+5J!_%JNjgtFwW|t`aS$Fhggb zWhvm(+7a|34pWrcuv$Y}K|_-fM;={PBg~ti1nDf+?0NGXmsUOT_TY!Sh5W2sh`NOm z`6u1n7a{5O_y5zVyZv2G_RZuRzuxC(Fn>Cr=-4RZeg342vy3#R5cAPzzR`vAQ%XC+ z*MmcFXesZ}xo`r3sV43}|GR#5Az6EsFy>GGc&Vah!slZ#ww+&ZDwS2OXg&IA`SLI%3PolOayDj^Owz3tYcB|R<{?@)zxH?t3ZorCB5`^hiMKfRVO`^5qvRDO*@M~ z++|Bln3rdddLjVll!~oqxM;)!;=InK&9E0ynU zq}Ip|t8}vv&ibQLtq%zJD?t&p20-zTxIH?3bA4&1mdcI(jE{Jc$r+s{2fi;~fge3% zB7-EqjR@C&htfXCVX-RR%lv`!I^DuQEVA*fU~LFbxgAy=ebjoTSbn8A*=9gF(&l|= zWP8nldwsi&{|><-dx(XOlMFF~ucqej)9!HA%LPk;avdS!imAfaaYyt0pCE>rujo|( z}_R?CU#&`C{x0Kg6uh(ZvpCGH+WS5#AD$;rJm?;}pH2LY zW~CXzDfy*cr?;>*d_l^kS6zm5h`TS!RZc}fvkzFFKA1;36&qdL?Iu#Myl6Uw`XCD^ zo@aJmt-cT6=?R6+=ozT)7RkOY7^IuAE8T=ARhhqg3JS%CrJ1i&MgJT zlaog9Z~N7qnG5w!U}a;&*&`hGwEk2@Yv^9YE^#Xe3ZrysvK)FI$$sJ!_Hm$k)#*JN z2ME()S;2x%J#tZ`_X2S`qw&>j8}!q32zqP+cm}#!<_=m6gT}%KW~{Yom!sDg z%v-Cn7kIn0$*H6kD@Mt0y=Y6rPa99!9dj{D{?2u8goi;L0(>h(d!c&IRriQb?tFn% z0XfNh2Bb&k`|0;!gOUFCdw~2YbZ3#pSW&hg7f8Oidyhq&zT$N4XPnPkFHEu_+V>&_ zr<5gS$37!^+kcVmlAo@m)WG-FP!VkdwT$PQKWzZBwkuU~Db4omdk50NFs;sW%jn8B zk*e$%Ok=AvcT725`M<)8u@*0K2{4?+8+SV!1@$m_^p5agE%-I_D%l)cz@YU{e%eBW z4>l&3hfS}syhi>)m!!L`>F%#)=N%7jW6jHBpvDKc(4_Vn)}-u1d6etmulG$&=p(w- z6S-k{ZVCN93IT8;(6$zNL^DsXn)f-8kUJ%(J4fFT{l32>z9(NEnt@2bOOPZ;oy-Uv>+XDEdBntjA=I_1_;>(K}Da(gtyf%13F&vc4jmy#G{!$ z{?JVA41O*<+xDkI!e*6L3?MDH>h2dk-5^hvvHf*RQlsi*z@Y+~hqQbt9=tGmE50bH z4zA6#=Y&^bC{V1I)0KTC;*R?y$BOMbVa0{oBd7j1v2Ho5qlrV#l|zI-V{VX^Ta~Gh z_?idLe9p4}6P~@%2iqbDF#Fi$R_6s=o>h8Lo*q#8?r+#=oZvhvj-mhUm7zxpSS=IS z_Nbe>jUxty4P%Nc|l=~GEYOLRKq;C674Lcb~Z}-KX1qc|5V(R(Oi*n__A?F z{%PcNZmRbmF!y({iTU{2r~eCfC0;= zg#AaV@&^(JrRSHIVhtVVE$if6v#4$eu7|H zM-=bScYBhm;>rxWt<9+u$i7q89qDHsNk$-vF2-4|YM;VJIWcg1v>SMHG8O_%7PcDK zm^Z#zDYOfdL@0IlG_4^$@J}KVRFQG8-n-tPu4x-(;u0Xkh=K!`hOj$!T;J-=x{L)( zyx6;&{E;dQ4z2_$_iYhv$`ZtEAFD3aUI7B@8;7)I0+$c`=;)A<13iO~S?2^4&B!a( z7P0-z6O|jpcc~X-Pm4#p+xCl(_6z;pVSyN)VK_A~)o!_5c2EJ=PZ5-Be;eKE#UhHQ zN6(w09S={<0p8}niZFCu*dj9&2F}moN@@)l3SjrJV5otWtk59bzwnp?UfQ+7yNe)a zf|YSih81eE?c1Ml^^$J(X33J8)SlNXX#l{`fccgNaP+ahdUl@#tp1 z0yj>!%m7?6T6)!!fY!qLUX-o$;Gyo-qTo(q5R61IeQ$0T{giY!s~NGuJ!mEWn>+

      D9$zF2T`qMbO!>79MgheSx8`$ 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

      lbErWo0fYUhNnO45# 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%0A`zqOC=oLR_v5FR_8z+($29av1W18O7 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 CF|3yW From 84a878807af8e6f317a2451937567ec82a6f80d9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 7 Sep 2021 12:57:15 +0200 Subject: [PATCH 0807/1046] Clarify usage --- Documentation/Index.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 1e08f6c4..1f702653 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -812,15 +812,24 @@ It is therefore recommended using the `RowIterator` API instead, which has explicit error handling: ```swift +// option 1: convert results into an Array of rows let rowIterator = try db.prepareRowIterator(users) for user in try Array(rowIterator) { print("id: \(user[id]), email: \(user[email])") } -/// or using `map()` +/// option 2: transform results using `map()` let mapRowIterator = try db.prepareRowIterator(users) let userIds = try mapRowIterator.map { $0[id] } +/// option 3: handle each row individually with `failableNext()` +do { + while let row = try rowIterator.failableNext() { + // Handle row + } +} catch { + // Handle error +} ``` ### Plucking Rows From e191c7d04583a1fd4e0f95e73bec2161a30e4857 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 7 Sep 2021 13:04:24 +0200 Subject: [PATCH 0808/1046] Add failableNext() example to playground --- SQLite.playground/Contents.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index ebd5b020..c089076d 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -54,6 +54,16 @@ for user in try Array(rowIterator) { let mapRowIterator = try db.prepareRowIterator(users) let userIds = try mapRowIterator.map { $0[id] } +/// using `failableNext()` on `RowIterator` +let iterator = try db.prepareRowIterator(users) +do { + while let row = try rowIterator.failableNext() { + print(row) + } +} catch { + // Handle error +} + /// define a virtual tabe for the FTS index let emails = VirtualTable("emails") From 818af0882507b0e468c9ec40fa2149a52a4ad171 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Wed, 3 Nov 2021 12:59:38 -0700 Subject: [PATCH 0809/1046] Add platform versions to the SPM manifest --- Package.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Package.swift b/Package.swift index f4938678..47c00b37 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,12 @@ import PackageDescription let package = Package( name: "SQLite.swift", + platforms: [ + .iOS(.v9), + .macOS(.v10_15), + .watchOS(.v3), + .tvOS(.v9) + ], products: [ .library( name: "SQLite", @@ -46,6 +52,12 @@ package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.g package.targets = [ .target( name: "SQLite", + platforms: [ + .iOS(.v9), + .macOS(.v10_15), + .watchOS(.v3), + .tvOS(.v9) + ], dependencies: [.product(name: "CSQLite", package: "CSQLite")], exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] ), From 9af505317fff1d9129a4c905fa2e8757c3484fc0 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Wed, 3 Nov 2021 13:00:23 -0700 Subject: [PATCH 0810/1046] Move XCode deployment version from targets to project --- SQLite.xcodeproj/project.pbxproj | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ba14e9ae..aecb424a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1099,7 +1099,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1121,7 +1120,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1135,7 +1133,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1149,7 +1146,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1173,7 +1169,6 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -1197,7 +1192,6 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -1259,8 +1253,10 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 9.1; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -1315,9 +1311,11 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 9.1; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -1334,7 +1332,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1358,7 +1355,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.13.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; From d6eb85b381da062ffe2db30739f868607dc1c4c9 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Fri, 5 Nov 2021 12:13:00 -0700 Subject: [PATCH 0811/1046] Fix test platform versions --- Package.swift | 6 ------ Tests/SPM/Package.swift | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Package.swift b/Package.swift index 47c00b37..abce8395 100644 --- a/Package.swift +++ b/Package.swift @@ -52,12 +52,6 @@ package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.g package.targets = [ .target( name: "SQLite", - platforms: [ - .iOS(.v9), - .macOS(.v10_15), - .watchOS(.v3), - .tvOS(.v9) - ], dependencies: [.product(name: "CSQLite", package: "CSQLite")], exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] ), diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 265021e5..91c0c6d1 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -5,6 +5,12 @@ import PackageDescription let package = Package( name: "test", + platforms: [ + .iOS(.v9), + .macOS(.v10_15), + .watchOS(.v3), + .tvOS(.v9) + ], dependencies: [ // for testing from same repository .package(path: "../..") From d02d0b4090f855acca24e105f2f684c7f71dc9a1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:20:10 +0100 Subject: [PATCH 0812/1046] Move OSX deployment target back to 10.10 --- Package.swift | 2 +- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- Tests/SPM/Package.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index abce8395..24898bca 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ let package = Package( name: "SQLite.swift", platforms: [ .iOS(.v9), - .macOS(.v10_15), + .macOS(.v10_10), .watchOS(.v3), .tvOS(.v9) ], diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index f273ffd8..80871b0b 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -20,7 +20,7 @@ Pod::Spec.new do |s| ios_deployment_target = '9.0' tvos_deployment_target = '9.1' - osx_deployment_target = '10.15' + osx_deployment_target = '10.10' watchos_deployment_target = '3.0' s.ios.deployment_target = ios_deployment_target diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index aecb424a..ecc1195c 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1304,7 +1304,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 91c0c6d1..dd1cd45c 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -7,7 +7,7 @@ let package = Package( name: "test", platforms: [ .iOS(.v9), - .macOS(.v10_15), + .macOS(.v10_10), .watchOS(.v3), .tvOS(.v9) ], From 34798be5eb2cc057040903839ba732b5cab7c02c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:32:17 +0100 Subject: [PATCH 0813/1046] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d09c12..a36c6468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Support for custom SQL aggregates ([#881][]) * Restore previous iteration behavior ([#1075][]) * Fix compilation on Linux ([#1077][]) +* Align platform versions in SPM manifest and Xcode ([#1094][]) +* Revert OSX deployment target back to 10.10 ([#1095][]) 0.13.0 (22-08-2021), [diff][diff-0.13.0] ======================================== @@ -128,3 +130,5 @@ [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 [#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 +[#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 +[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 \ No newline at end of file From 883117cbc847508192d2961f7833e9da5a4c5f6d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:42:45 +0100 Subject: [PATCH 0814/1046] "macos-latest" is not the latest :( https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88c5803f..a633f184 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ env: IOS_VERSION: 14.4 jobs: build: - runs-on: macos-latest + runs-on: macos-11 steps: - uses: actions/checkout@v2 - name: Install From 13d78942f39ab4254ac6856e7f88f5032946ef3b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:49:43 +0100 Subject: [PATCH 0815/1046] iOS 15.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0b9a1606..c62da000 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BUILD_TOOL = xcodebuild BUILD_SCHEME = SQLite Mac IOS_SIMULATOR = iPhone 12 -IOS_VERSION = 14.4 +IOS_VERSION = 15.0 ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else From 7c0106255f742218b3d99d86ed08a31b42882cf7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 21:58:38 +0100 Subject: [PATCH 0816/1046] iOS version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a633f184..84dc9aba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: 14.4 + IOS_VERSION: 15.0 jobs: build: runs-on: macos-11 From 47c70041ac7237b5c1ccdcf6114e6bb78c0d9dc6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 5 Nov 2021 22:07:30 +0100 Subject: [PATCH 0817/1046] YML insanity --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84dc9aba..baa8c2d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: 15.0 + IOS_VERSION: "15.0" jobs: build: runs-on: macos-11 From db48bb0bf98568c0e29976cd5b95c898c013c277 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 17 Nov 2021 00:46:07 +0100 Subject: [PATCH 0818/1046] Update Index.md Clarify SQLCipher integration (#1097) --- Documentation/Index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 1f702653..22c5fea5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -170,6 +170,8 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the ```ruby target 'YourAppTargetName' do + # Make sure you only require the subspec, otherwise you app might link against + # the system SQLite, which means the SQLCipher-specific methods won't work. pod 'SQLite.swift/SQLCipher', '~> 0.13.0' end ``` From 60a65015f6402b7c34b9a924f755ca0a73afeeaa Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 17 Nov 2021 00:55:18 +0100 Subject: [PATCH 0819/1046] Bump version --- CHANGELOG.md | 7 ++++--- Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a36c6468..d89e33f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ -0.13.1 (tba) +0.13.1 (17-11-2021), [diff][diff-0.13.1] ======================================== * Support for database backup ([#919][]) * Support for custom SQL aggregates ([#881][]) -* Restore previous iteration behavior ([#1075][]) +* Restore previous behavior in `FailableIterator` ([#1075][]) * Fix compilation on Linux ([#1077][]) * Align platform versions in SPM manifest and Xcode ([#1094][]) * Revert OSX deployment target back to 10.10 ([#1095][]) @@ -94,6 +94,7 @@ [diff-0.12.0]: https://github.com/stephencelis/SQLite.swift/compare/0.11.6...0.12.0 [diff-0.12.2]: https://github.com/stephencelis/SQLite.swift/compare/0.12.0...0.12.2 [diff-0.13.0]: https://github.com/stephencelis/SQLite.swift/compare/0.12.2...0.13.0 +[diff-0.13.1]: https://github.com/stephencelis/SQLite.swift/compare/0.13.0...0.13.1 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -131,4 +132,4 @@ [#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 -[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 \ No newline at end of file +[#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 diff --git a/Documentation/Index.md b/Documentation/Index.md index 22c5fea5..96e5775c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -81,7 +81,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.1") ] ``` @@ -102,7 +102,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.0 + github "stephencelis/SQLite.swift" ~> 0.13.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -132,7 +132,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.0' + pod 'SQLite.swift', '~> 0.13.1' end ``` @@ -146,7 +146,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.0' + pod 'SQLite.swift/standalone', '~> 0.13.1' end ``` @@ -156,7 +156,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.0' + pod 'SQLite.swift/standalone', '~> 0.13.1' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -172,7 +172,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.13.0' + pod 'SQLite.swift/SQLCipher', '~> 0.13.1' end ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 44e02c7a..bebfb503 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. > ⚠ This document is currently not actively maintained. See -> the [0.13.1 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.1) +> the [0.13.2 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.2) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index ec92729f..bc8eac31 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.1") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.0 + github "stephencelis/SQLite.swift" ~> 0.13.1 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.0' + pod 'SQLite.swift', '~> 0.13.1' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 80871b0b..90a8a348 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.13.0" + s.version = "0.13.1" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index ecc1195c..aee41218 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1333,7 +1333,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.0; + MARKETING_VERSION = 0.13.1; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1356,7 +1356,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.0; + MARKETING_VERSION = 0.13.1; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; From a2bfb9eb3b0641873c886384ed96fd44f70dba0e Mon Sep 17 00:00:00 2001 From: Dylan Nunns Date: Thu, 2 Dec 2021 19:05:01 -0400 Subject: [PATCH 0820/1046] Closing bracket position Updated position of closing bracket where `onConflictOf` parameter. In this particular area it was closing `upsert()` and making `onConflictOf` a parameter of `.run()` --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 96e5775c..061b448b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1174,7 +1174,7 @@ joined by the `<-` operator. Upserting is like inserting, except if there is a conflict on the specified column value, SQLite will perform an update on the row instead. ```swift -try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice"), onConflictOf: email) +try db.run(users.upsert(email <- "alice@mac.com", name <- "Alice", onConflictOf: email)) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') ON CONFLICT (\"email\") DO UPDATE SET \"name\" = \"excluded\".\"name\" ``` From b9489a7cc11f18a15e744193e29de1430ddd35b2 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Mon, 17 Jan 2022 17:08:36 +0100 Subject: [PATCH 0821/1046] Native user_version support in Connection --- Sources/SQLite/Core/Connection.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 6b5481e3..5b1fc50a 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -151,6 +151,13 @@ public final class Connection { public var totalChanges: Int { Int(sqlite3_total_changes(handle)) } + + /// The user version of the database. + /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) + public var userVersion: Int32 { + get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} + set { try! run("PRAGMA user_version = \(newValue)") } + } // MARK: - Execute From 9e162a84c964562cf6248890722c77011f81eaa4 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Mon, 17 Jan 2022 17:30:00 +0100 Subject: [PATCH 0822/1046] Refactoring userVersion + adding tests --- Sources/SQLite/Core/Connection.swift | 20 +++++++++++++++----- Tests/SQLiteTests/ConnectionTests.swift | 5 +++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 5b1fc50a..4b1c712a 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -151,12 +151,22 @@ public final class Connection { public var totalChanges: Int { Int(sqlite3_total_changes(handle)) } - - /// The user version of the database. + + /// Gets the user version of the database. + /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) + /// - Returns: the user version of the database + public func getUserVersion() throws -> Int32? { + guard let userVersion = try scalar("PRAGMA user_version") as? Int64 else { + return nil + } + return Int32(userVersion) + } + + /// Sets the user version of the database. /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - public var userVersion: Int32 { - get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} - set { try! run("PRAGMA user_version = \(newValue)") } + /// - Parameter userVersion: the new user version of the database + public func setUserVersion(to userVersion: Int32) throws { + try run("PRAGMA user_version = \(userVersion)") } // MARK: - Execute diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index ae9d4dfd..707cbc43 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -96,6 +96,11 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } + func test_userVersion() { + try! db.setUserVersion(to: 2) + XCTAssertEqual(2, try! db.getUserVersion()!) + } + func test_prepare_preparesAndReturnsStatements() { _ = try! db.prepare("SELECT * FROM users WHERE admin = 0") _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0) From d52eaaf1795e05dadb5c1c0e59cf56579c7f7112 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 18 Jan 2022 13:28:31 +0100 Subject: [PATCH 0823/1046] Set userVersion as a property --- Sources/SQLite/Core/Connection.swift | 25 ++++++++++++------------- Tests/SQLiteTests/ConnectionTests.swift | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 4b1c712a..becc932e 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -152,21 +152,20 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } - /// Gets the user version of the database. + /// The user version of the database. /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - /// - Returns: the user version of the database - public func getUserVersion() throws -> Int32? { - guard let userVersion = try scalar("PRAGMA user_version") as? Int64 else { - return nil + public var userVersion: Int32? { + get { + guard let userVersion = try? scalar("PRAGMA user_version") as? Int64 else { + return nil + } + return Int32(userVersion) + } + set { + if let userVersion = newValue { + _ = try? run("PRAGMA user_version = \(userVersion)") + } } - return Int32(userVersion) - } - - /// Sets the user version of the database. - /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - /// - Parameter userVersion: the new user version of the database - public func setUserVersion(to userVersion: Int32) throws { - try run("PRAGMA user_version = \(userVersion)") } // MARK: - Execute diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 707cbc43..136271a4 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -97,8 +97,8 @@ class ConnectionTests: SQLiteTestCase { } func test_userVersion() { - try! db.setUserVersion(to: 2) - XCTAssertEqual(2, try! db.getUserVersion()!) + db.userVersion = 2 + XCTAssertEqual(2, db.userVersion!) } func test_prepare_preparesAndReturnsStatements() { From 80c8b30a4e372f92745e197047c9e8549fc4347d Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 13:14:00 +0100 Subject: [PATCH 0824/1046] More compact version --- Sources/SQLite/Core/Connection.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index becc932e..043cfafc 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,10 +156,7 @@ public final class Connection { /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) public var userVersion: Int32? { get { - guard let userVersion = try? scalar("PRAGMA user_version") as? Int64 else { - return nil - } - return Int32(userVersion) + (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) } set { if let userVersion = newValue { From d00473986112b4bce7bb7f15dc68483adf29c67e Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 14:09:11 +0100 Subject: [PATCH 0825/1046] Fixing tests for Linux --- Tests/SQLiteTests/QueryTests.swift | 23 ++++++++++++++--------- Tests/SQLiteTests/TestHelpers.swift | 13 ++++++++++++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index e0749f2c..f225adb4 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -391,15 +391,20 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) let update = try emails.update(value) - let encodedJSON = try JSONEncoder().encode(value1) - let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - assertSQL( - """ - UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, - \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '\(encodedJSONString)' - """.replacingOccurrences(of: "\n", with: ""), - update - ) + + // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, + // and extract JSON to decode it and check the decoded object. + + let expectedPrefix = "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '" + let expectedSuffix = "'" + + let sql = update.asSQL() + XCTAssert(sql.hasPrefix(expectedPrefix)) + XCTAssert(sql.hasSuffix(expectedSuffix)) + + let extractedJSON = String(sql[sql.index(sql.startIndex, offsetBy: expectedPrefix.count) ..< sql.index(sql.endIndex, offsetBy: -expectedSuffix.count)]) + let decodedJSON = try JSONDecoder().decode(TestCodable.self, from: extractedJSON.data(using: .utf8)!) + XCTAssertEqual(decodedJSON, value1) } func test_delete_compilesDeleteExpression() { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 07cc6f06..53dabe79 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -105,7 +105,7 @@ let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") let _view = View("view") // avoid Mac XCTestCase collision -class TestCodable: Codable { +class TestCodable: Codable, Equatable { let int: Int let string: String let bool: Bool @@ -125,4 +125,15 @@ class TestCodable: Codable { self.optional = optional self.sub = sub } + + static func == (lhs: TestCodable, rhs: TestCodable) -> Bool { + lhs.int == rhs.int && + lhs.string == rhs.string && + lhs.bool == rhs.bool && + lhs.float == rhs.float && + lhs.double == rhs.double && + lhs.date == rhs.date && + lhs.optional == rhs.optional && + lhs.sub == rhs.sub + } } From 34d3a9713e77d2a85b4140d505d3dbd3918117ff Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 14:50:37 +0100 Subject: [PATCH 0826/1046] Fixing Linting --- Tests/SQLiteTests/QueryTests.swift | 19 +++++++++++++------ Tests/SQLiteTests/TestHelpers.swift | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index f225adb4..ad0f3b25 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -391,18 +391,25 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) let update = try emails.update(value) - + // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, // and extract JSON to decode it and check the decoded object. - - let expectedPrefix = "UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, \"date\" = '1970-01-01T00:00:00.000', \"sub\" = '" + + let expectedPrefix = + """ + UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, + \"date\" = '1970-01-01T00:00:00.000', \"sub\" = ' + """.replacingOccurrences(of: "\n", with: "") let expectedSuffix = "'" - + let sql = update.asSQL() XCTAssert(sql.hasPrefix(expectedPrefix)) XCTAssert(sql.hasSuffix(expectedSuffix)) - - let extractedJSON = String(sql[sql.index(sql.startIndex, offsetBy: expectedPrefix.count) ..< sql.index(sql.endIndex, offsetBy: -expectedSuffix.count)]) + + let extractedJSON = String(sql[ + sql.index(sql.startIndex, offsetBy: expectedPrefix.count) ..< + sql.index(sql.endIndex, offsetBy: -expectedSuffix.count) + ]) let decodedJSON = try JSONDecoder().decode(TestCodable.self, from: extractedJSON.data(using: .utf8)!) XCTAssertEqual(decodedJSON, value1) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 53dabe79..4d8f78fa 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -125,7 +125,7 @@ class TestCodable: Codable, Equatable { self.optional = optional self.sub = sub } - + static func == (lhs: TestCodable, rhs: TestCodable) -> Bool { lhs.int == rhs.int && lhs.string == rhs.string && From 85921d83ce4921201a6d4106bc609f277253d4d8 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 15:05:28 +0100 Subject: [PATCH 0827/1046] Oups --- Tests/SQLiteTests/QueryTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index ad0f3b25..f4942b88 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -398,7 +398,7 @@ class QueryTests: XCTestCase { let expectedPrefix = """ UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, - \"date\" = '1970-01-01T00:00:00.000', \"sub\" = ' + \"date\" = '1970-01-01T00:00:00.000', \"sub\" = ' """.replacingOccurrences(of: "\n", with: "") let expectedSuffix = "'" From 608684aeea5b6a5c5e8b87866600447c412d3b2e Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 15:19:40 +0100 Subject: [PATCH 0828/1046] Updating documentation --- Documentation/Index.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 061b448b..4b4f45db 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1457,21 +1457,11 @@ try db.run(users.drop(ifExists: true)) ### Migrations and Schema Versioning -You can add a convenience property on `Connection` to query and set the +You can use the convenience property on `Connection` to query and set the [`PRAGMA user_version`](https://sqlite.org/pragma.html#pragma_user_version). This is a great way to manage your schema’s version over migrations. - -```swift -extension Connection { - public var userVersion: Int32 { - get { return Int32(try! scalar("PRAGMA user_version") as! Int64)} - set { try! run("PRAGMA user_version = \(newValue)") } - } -} -``` - -Then you can conditionally run your migrations along the lines of: +You can conditionally run your migrations along the lines of: ```swift if db.userVersion == 0 { From 946f69e79c2503d2dff58813d1abf78280205fa0 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 15:31:17 +0100 Subject: [PATCH 0829/1046] Bump version number --- CHANGELOG.md | 9 +++++++++ Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d89e33f4..11d0218e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.13.2 (25-01-2022), [diff][diff-0.13.2] +======================================== + +* Closing bracket position ([#1100][]) +* Native user_version support in Connection ([#1105][]) + 0.13.1 (17-11-2021), [diff][diff-0.13.1] ======================================== @@ -95,6 +101,7 @@ [diff-0.12.2]: https://github.com/stephencelis/SQLite.swift/compare/0.12.0...0.12.2 [diff-0.13.0]: https://github.com/stephencelis/SQLite.swift/compare/0.12.2...0.13.0 [diff-0.13.1]: https://github.com/stephencelis/SQLite.swift/compare/0.13.0...0.13.1 +[diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -133,3 +140,5 @@ [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 +[#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 +[#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 diff --git a/Documentation/Index.md b/Documentation/Index.md index 4b4f45db..52affae4 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -81,7 +81,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") ] ``` @@ -102,7 +102,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.1 + github "stephencelis/SQLite.swift" ~> 0.13.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -132,7 +132,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.1' + pod 'SQLite.swift', '~> 0.13.2' end ``` @@ -146,7 +146,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.1' + pod 'SQLite.swift/standalone', '~> 0.13.2' end ``` @@ -156,7 +156,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.1' + pod 'SQLite.swift/standalone', '~> 0.13.2' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -172,7 +172,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.13.1' + pod 'SQLite.swift/SQLCipher', '~> 0.13.2' end ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index bebfb503..5b50219a 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. > ⚠ This document is currently not actively maintained. See -> the [0.13.2 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.2) +> the [0.13.3 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.3) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index bc8eac31..a2b5e537 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.1 + github "stephencelis/SQLite.swift" ~> 0.13.2 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.1' + pod 'SQLite.swift', '~> 0.13.2' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 90a8a348..23146eee 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.13.1" + s.version = "0.13.2" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index aee41218..26c5e975 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1333,7 +1333,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.1; + MARKETING_VERSION = 0.13.2; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1356,7 +1356,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.1; + MARKETING_VERSION = 0.13.2; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; From 5f5ad81ac0d0a0f3e56e39e646e8423c617df523 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 25 Jan 2022 16:57:56 +0100 Subject: [PATCH 0830/1046] Reset userVersion if set to nil --- Sources/SQLite/Core/Connection.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 043cfafc..72e8d857 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -159,9 +159,7 @@ public final class Connection { (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) } set { - if let userVersion = newValue { - _ = try? run("PRAGMA user_version = \(userVersion)") - } + _ = try? run("PRAGMA user_version = \(newValue ?? 0)") } } From 52e3ea84895004f81d647ab8582564d1e7ab3c73 Mon Sep 17 00:00:00 2001 From: Amy While <26681721+elihwyma@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:59:33 +0000 Subject: [PATCH 0831/1046] Add a Value conformance for Foundation NSURL --- Sources/SQLite/Foundation.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index 2acbc00e..c1a4c501 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -84,3 +84,20 @@ extension UUID: Value { } } + +extension URL: Value { + + public static var declaredDatatype: String { + String.declaredDatatype + } + + public static func fromDatatypeValue(_ stringValue: String) -> URL { + URL(string: stringValue)! + } + + public var datatypeValue: String { + absoluteString + } + +} + From 3eb1fdceda903ea3b7ba0f49a522258578a168d5 Mon Sep 17 00:00:00 2001 From: Amy While <26681721+elihwyma@users.noreply.github.com> Date: Sat, 12 Feb 2022 13:13:42 +0000 Subject: [PATCH 0832/1046] Remove Extra Trailing New Line --- Sources/SQLite/Foundation.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index c1a4c501..44a31736 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -100,4 +100,3 @@ extension URL: Value { } } - From da8d7b3100471349ac924e15b1351fcd17b83ade Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Fri, 18 Feb 2022 13:45:40 -0600 Subject: [PATCH 0833/1046] UUID Fix --- Sources/SQLite/Typed/Coding.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 3dc1e6cf..a8608579 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -207,6 +207,8 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- data) } else if let date = value as? Date { encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + }else if let uuid = value as? UUID { + encoder.setters.append(Expression(key.stringValue) <- uuid.uuidString) } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) From b71549364be768397d09a91707867d2175f6261e Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Sat, 19 Feb 2022 10:52:13 -0600 Subject: [PATCH 0834/1046] Lint Fix and added test --- Sources/SQLite/Typed/Coding.swift | 2 +- Tests/SQLiteTests/FoundationTests.swift | 26 +++++++++++++++++++++++++ Tests/SQLiteTests/TestHelpers.swift | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index a8608579..fa9ec02d 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -207,7 +207,7 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- data) } else if let date = value as? Date { encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - }else if let uuid = value as? UUID { + } else if let uuid = value as? UUID { encoder.setters.append(Expression(key.stringValue) <- uuid.uuidString) } else { let encoded = try JSONEncoder().encode(value) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index cef485fc..23ae1fe2 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -25,4 +25,30 @@ class FoundationTests: XCTestCase { let uuid = UUID.fromDatatypeValue(string) XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } + + func testUUIDInsert() { + struct Test:Codable{ + var uuid:UUID + var string:String + } + let testUUID = UUID() + let testValue = Test(uuid: testUUID, string: "value") + let db = try! Connection(.temporary) + try! db.run(table.create { t in + t.column(uuid) + t.column(string) + } + ) + + let iQuery = try! table.insert(testValue) + try! db.run(iQuery) + + let fQuery = table.filter(uuid == testUUID) + if let result = try! db.pluck(fQuery){ + let testValueReturned = Test(uuid: result[uuid], string: result[string]) + XCTAssertEqual(testUUID, testValueReturned.uuid) + }else{ + XCTFail() + } + } } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 4d8f78fa..a6efa2e7 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -95,6 +95,9 @@ let int64Optional = Expression("int64Optional") let string = Expression("string") let stringOptional = Expression("stringOptional") +let uuid = Expression("uuid") +let uuidOptional = Expression("uuidOptional") + func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) From 83f0f5de5bd978e5c41e45331154572a41204fe9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 20 Feb 2022 23:46:24 +0100 Subject: [PATCH 0835/1046] Bump iOS version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index baa8c2d8..d1659d9d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: "15.0" + IOS_VERSION: "15.2" jobs: build: runs-on: macos-11 From c4483141f19a4ba796fd0475e279d4db1353000e Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Mon, 21 Feb 2022 19:33:15 -0600 Subject: [PATCH 0836/1046] Fixed linting issues. --- Tests/SQLiteTests/FoundationTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 23ae1fe2..02ab7f13 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -25,11 +25,11 @@ class FoundationTests: XCTestCase { let uuid = UUID.fromDatatypeValue(string) XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } - + func testUUIDInsert() { - struct Test:Codable{ - var uuid:UUID - var string:String + struct Test: Codable { + var uuid: UUID + var string: String } let testUUID = UUID() let testValue = Test(uuid: testUUID, string: "value") @@ -39,16 +39,16 @@ class FoundationTests: XCTestCase { t.column(string) } ) - + let iQuery = try! table.insert(testValue) try! db.run(iQuery) - + let fQuery = table.filter(uuid == testUUID) - if let result = try! db.pluck(fQuery){ + if let result = try! db.pluck(fQuery) { let testValueReturned = Test(uuid: result[uuid], string: result[string]) XCTAssertEqual(testUUID, testValueReturned.uuid) - }else{ - XCTFail() + } else { + XCTFail("Search for uuid failed") } } } From 09e8424805b9ea02b96c6973fe2720c3caa4490f Mon Sep 17 00:00:00 2001 From: Benjamin Lewis Date: Tue, 22 Feb 2022 06:40:20 -0600 Subject: [PATCH 0837/1046] used UUID.datatypeValue and moved tests --- Sources/SQLite/Typed/Coding.swift | 2 +- Tests/SQLiteTests/FoundationTests.swift | 25 ------------------------ Tests/SQLiteTests/QueryTests.swift | 26 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index fa9ec02d..cea2565a 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -208,7 +208,7 @@ private class SQLiteEncoder: Encoder { } else if let date = value as? Date { encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) } else if let uuid = value as? UUID { - encoder.setters.append(Expression(key.stringValue) <- uuid.uuidString) + encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) } else { let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 02ab7f13..075e755b 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -26,29 +26,4 @@ class FoundationTests: XCTestCase { XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } - func testUUIDInsert() { - struct Test: Codable { - var uuid: UUID - var string: String - } - let testUUID = UUID() - let testValue = Test(uuid: testUUID, string: "value") - let db = try! Connection(.temporary) - try! db.run(table.create { t in - t.column(uuid) - t.column(string) - } - ) - - let iQuery = try! table.insert(testValue) - try! db.run(iQuery) - - let fQuery = table.filter(uuid == testUUID) - if let result = try! db.pluck(fQuery) { - let testValueReturned = Test(uuid: result[uuid], string: result[string]) - XCTAssertEqual(testUUID, testValueReturned.uuid) - } else { - XCTFail("Search for uuid failed") - } - } } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index f4942b88..efcbab94 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -310,6 +310,32 @@ class QueryTests: XCTestCase { } #endif + func test_insert_and_search_for_UUID() { + struct Test: Codable { + var uuid: UUID + var string: String + } + let testUUID = UUID() + let testValue = Test(uuid: testUUID, string: "value") + let db = try! Connection(.temporary) + try! db.run(table.create { t in + t.column(uuid) + t.column(string) + } + ) + + let iQuery = try! table.insert(testValue) + try! db.run(iQuery) + + let fQuery = table.filter(uuid == testUUID) + if let result = try! db.pluck(fQuery) { + let testValueReturned = Test(uuid: result[uuid], string: result[string]) + XCTAssertEqual(testUUID, testValueReturned.uuid) + } else { + XCTFail("Search for uuid failed") + } + } + func test_upsert_withOnConflict_compilesInsertOrOnConflictExpression() { assertSQL( """ From f1ab605ad2056944eee80cb7cf55f5b94f17e74e Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 24 Feb 2022 09:49:20 -0700 Subject: [PATCH 0838/1046] improve performance of prefixed column resolution - Previously this allocated arrays every time it was accessed. It will now only allocate in one of the error cases. - It also keeps track of the columnName index for both key and value so it doesn't need to look it up twice. --- Sources/SQLite/Typed/Query.swift | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 2d88db63..ecb501af 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1169,16 +1169,25 @@ public struct Row { } guard let idx = columnNames[column.template] else { - let similar = Array(columnNames.keys).filter { $0.hasSuffix(".\(column.template)") } - - switch similar.count { - case 0: + func match(_ s: String) -> Bool { + return s.hasSuffix(".\(column.template)") + } + + guard let firstIndex = columnNames.firstIndex(where: { match($0.key) }) else { throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) - case 1: - return valueAtIndex(columnNames[similar[0]]!) - default: - throw QueryError.ambiguousColumn(name: column.template, similar: similar) } + + let secondIndex = columnNames + .suffix(from: columnNames.index(after: firstIndex)) + .firstIndex(where: { match($0.key) }) + + guard secondIndex == nil else { + throw QueryError.ambiguousColumn( + name: column.template, + similar: columnNames.keys.filter(match).sorted() + ) + } + return valueAtIndex(columnNames[firstIndex].value) } return valueAtIndex(idx) From 55bf2c10e10b84855ac3a26c10dc601271c6d171 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 24 Feb 2022 09:52:56 -0700 Subject: [PATCH 0839/1046] rename match -> similar to follow convention --- Sources/SQLite/Typed/Query.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index ecb501af..6dd07ac4 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1169,22 +1169,22 @@ public struct Row { } guard let idx = columnNames[column.template] else { - func match(_ s: String) -> Bool { + func similar(_ s: String) -> Bool { return s.hasSuffix(".\(column.template)") } - guard let firstIndex = columnNames.firstIndex(where: { match($0.key) }) else { + guard let firstIndex = columnNames.firstIndex(where: { similar($0.key) }) else { throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) } let secondIndex = columnNames .suffix(from: columnNames.index(after: firstIndex)) - .firstIndex(where: { match($0.key) }) + .firstIndex(where: { similar($0.key) }) guard secondIndex == nil else { throw QueryError.ambiguousColumn( name: column.template, - similar: columnNames.keys.filter(match).sorted() + similar: columnNames.keys.filter(similar).sorted() ) } return valueAtIndex(columnNames[firstIndex].value) From eecb3cb4615534020bf96e32ad2b3a95c91504d6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 24 Feb 2022 20:14:01 +0100 Subject: [PATCH 0840/1046] Adding PR template --- .github/PULL_REQUEST_TEMPLATE/template.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE/template.md diff --git a/.github/PULL_REQUEST_TEMPLATE/template.md b/.github/PULL_REQUEST_TEMPLATE/template.md new file mode 100644 index 00000000..b74ffd8d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/template.md @@ -0,0 +1,9 @@ +Thanks for taking the time to submit a pull request. + +Before submitting, please do the following: + +- Run `make lint` to check if there are any format errors (install [swiftlint](https://github.com/realm/SwiftLint#installation) first) +- Run `swift test` to see if the tests pass. +- Write new tests for new functionality. +- Update documentation comments where applicable. + From 3614f4e317c5969c5fe102cee1f3b3f4e1294185 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 24 Feb 2022 20:24:24 +0100 Subject: [PATCH 0841/1046] Rename --- .../template.md => pull_request_template.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE/template.md => pull_request_template.md} (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE/template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/template.md rename to .github/pull_request_template.md From c897ce99454216dc33caae5d57a2cd021b672e03 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 24 Feb 2022 20:26:45 +0100 Subject: [PATCH 0842/1046] rename --- .github/{ISSUE_TEMPLATE/template.md => issue_template.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/{ISSUE_TEMPLATE/template.md => issue_template.md} (87%) diff --git a/.github/ISSUE_TEMPLATE/template.md b/.github/issue_template.md similarity index 87% rename from .github/ISSUE_TEMPLATE/template.md rename to .github/issue_template.md index 9304a672..f9622b9b 100644 --- a/.github/ISSUE_TEMPLATE/template.md +++ b/.github/issue_template.md @@ -1,5 +1,5 @@ > Issues are used to track bugs and feature requests. -> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). +> Need help or have a general question? Ask on Stack Overflow (tag sqlite.swift). ## Build Information From c659dc9256f1b80505d0bd5bbca026005cd43af2 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Thu, 24 Feb 2022 13:46:25 -0700 Subject: [PATCH 0843/1046] Fix lint issues --- Sources/SQLite/Typed/Query.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 6dd07ac4..93dc2a73 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1169,18 +1169,18 @@ public struct Row { } guard let idx = columnNames[column.template] else { - func similar(_ s: String) -> Bool { - return s.hasSuffix(".\(column.template)") + func similar(_ name: String) -> Bool { + return name.hasSuffix(".\(column.template)") } - + guard let firstIndex = columnNames.firstIndex(where: { similar($0.key) }) else { throw QueryError.noSuchColumn(name: column.template, columns: columnNames.keys.sorted()) } - + let secondIndex = columnNames .suffix(from: columnNames.index(after: firstIndex)) .firstIndex(where: { similar($0.key) }) - + guard secondIndex == nil else { throw QueryError.ambiguousColumn( name: column.template, From ef2257a885a875868177b2a004a839eb4f21a461 Mon Sep 17 00:00:00 2001 From: JIm Boyd Date: Thu, 17 Mar 2022 11:09:33 -0600 Subject: [PATCH 0844/1046] Add prepareRowIterator method to an extension of Statement. --- Sources/SQLite/Core/Statement.swift | 15 +++++++++++++++ Tests/SQLiteTests/StatementTests.swift | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 1e2489b5..8e9ed350 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -228,6 +228,21 @@ extension Statement: FailableIterator { } } +extension Statement { + public func prepareRowIterator() -> RowIterator { + return RowIterator(statement: self, columnNames: self.columnNameMap) + } + + var columnNameMap: [String: Int] { + var result = [String: Int]() + for (index, name) in self.columnNames.enumerated() { + result[name.quote()] = index + } + + return result + } +} + extension Statement: CustomStringConvertible { public var description: String { diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index b4ad7c28..7729a4a9 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -23,4 +23,15 @@ class StatementTests: SQLiteTestCase { let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) XCTAssertEqual([], blobValue.bytes) } + + func test_prepareRowIterator() { + let names = ["a", "b", "c"] + try! insertUsers(names) + + let emailColumn = Expression("email") + let statement = try! db.prepare("SELECT email FROM users") + let emails = try! statement.prepareRowIterator().map { $0[emailColumn] } + + XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) + } } From 1a50a03fe0a216c75bb27cab7a4f2965669e5e30 Mon Sep 17 00:00:00 2001 From: JIm Boyd Date: Thu, 17 Mar 2022 11:22:55 -0600 Subject: [PATCH 0845/1046] Updates to documentation --- Documentation/Index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 52affae4..b58bf995 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1963,6 +1963,14 @@ using the following functions. } } ``` + Statements with results may be iterated over, using a `RowIterator` if + useful. + + ```swift + let emailColumn = Expression("email") + let stmt = try db.prepare("SELECT id, email FROM users") + let emails = try! stmt.prepareRowIterator().map { $0[emailColumn] } + ``` - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, From 4459338f778d49445bdfd182e40e9cd90220f8c7 Mon Sep 17 00:00:00 2001 From: JIm Boyd Date: Thu, 17 Mar 2022 13:43:06 -0600 Subject: [PATCH 0846/1046] Fix linting issues --- Sources/SQLite/Core/Statement.swift | 2 +- Tests/SQLiteTests/StatementTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 8e9ed350..5ec430cf 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -232,7 +232,7 @@ extension Statement { public func prepareRowIterator() -> RowIterator { return RowIterator(statement: self, columnNames: self.columnNameMap) } - + var columnNameMap: [String: Int] { var result = [String: Int]() for (index, name) in self.columnNames.enumerated() { diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 7729a4a9..aa05de34 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -23,7 +23,7 @@ class StatementTests: SQLiteTestCase { let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) XCTAssertEqual([], blobValue.bytes) } - + func test_prepareRowIterator() { let names = ["a", "b", "c"] try! insertUsers(names) From 5af5c858fff85bf7a880bd01bd06b010c91c7436 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 27 Mar 2022 12:37:45 +0200 Subject: [PATCH 0847/1046] Adding primary key support to column with references --- CHANGELOG.md | 9 +++++++++ Documentation/Index.md | 12 ++++++------ Documentation/Planning.md | 2 +- README.md | 6 +++--- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- Sources/SQLite/Typed/Schema.swift | 20 ++++++++++++++++++++ Tests/SQLiteTests/SchemaTests.swift | 27 +++++++++++++++++++++++++++ 8 files changed, 69 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d0218e..9bf5682b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +0.13.3 (25-01-2022), [diff][diff-0.13.3] +======================================== + +* UUID Fix ([#1112][]) +* Adding primary key support to column with references ([#1121][]) + 0.13.2 (25-01-2022), [diff][diff-0.13.2] ======================================== @@ -102,6 +108,7 @@ [diff-0.13.0]: https://github.com/stephencelis/SQLite.swift/compare/0.12.2...0.13.0 [diff-0.13.1]: https://github.com/stephencelis/SQLite.swift/compare/0.13.0...0.13.1 [diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 +[diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 @@ -142,3 +149,5 @@ [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 +[#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 +[#1121]: https://github.com/stephencelis/SQLite.swift/pull/1121 diff --git a/Documentation/Index.md b/Documentation/Index.md index 52affae4..987b0814 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -81,7 +81,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") ] ``` @@ -102,7 +102,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.2 + github "stephencelis/SQLite.swift" ~> 0.13.3 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -132,7 +132,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.2' + pod 'SQLite.swift', '~> 0.13.3' end ``` @@ -146,7 +146,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.2' + pod 'SQLite.swift/standalone', '~> 0.13.3' end ``` @@ -156,7 +156,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.2' + pod 'SQLite.swift/standalone', '~> 0.13.3' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -172,7 +172,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.13.2' + pod 'SQLite.swift/SQLCipher', '~> 0.13.3' end ``` diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 5b50219a..0dea6d71 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. > ⚠ This document is currently not actively maintained. See -> the [0.13.3 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.3) +> the [0.13.4 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.4) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/README.md b/README.md index a2b5e537..1b04c7c8 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.2 + github "stephencelis/SQLite.swift" ~> 0.13.3 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.2' + pod 'SQLite.swift', '~> 0.13.3' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 23146eee..4a1787bb 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.13.2" + s.version = "0.13.3" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 26c5e975..7387d0e9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1333,7 +1333,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.2; + MARKETING_VERSION = 0.13.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1356,7 +1356,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.2; + MARKETING_VERSION = 0.13.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 726f3e27..55e525bc 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -308,6 +308,26 @@ public final class TableBuilder { references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, nil, (table, other), nil) + } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, nil, (table, other), nil) + } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, true, false, check, nil, (table, other), nil) + } + + public func column(_ name: Expression, primaryKey: Bool, check: Expression, + references table: QueryType, _ other: Expression) where V.Datatype == Int64 { + column(name, V.declaredDatatype, primaryKey ? .default : nil, true, false, check, nil, (table, other), nil) + } public func column(_ name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) where V.Datatype == String { diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/SchemaTests.swift index 495a5e51..a7de8bcf 100644 --- a/Tests/SQLiteTests/SchemaTests.swift +++ b/Tests/SQLiteTests/SchemaTests.swift @@ -317,6 +317,10 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, unique: true, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, check: int64 > 0, references: table, int64) } @@ -329,6 +333,10 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0, references: table, int64) } + ) XCTAssertEqual( """ CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES @@ -336,6 +344,13 @@ class SchemaTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) } ) + XCTAssertEqual( + """ + CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES + \"table\" (\"int64\")) + """.replacingOccurrences(of: "\n", with: ""), + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\"))", @@ -345,6 +360,10 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, unique: true, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER PRIMARY KEY REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, primaryKey: true, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, check: int64 > 0, references: table, int64) } @@ -357,10 +376,18 @@ class SchemaTests: XCTestCase { "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER PRIMARY KEY CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, primaryKey: true, check: int64 > 0, references: table, int64) } + ) XCTAssertEqual( "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) } ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER PRIMARY KEY CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, primaryKey: true, check: int64Optional > 0, references: table, int64) } + ) } func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { From 05fce4eeeb7b77aeaf75792276e502d9b88ecff1 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 27 Mar 2022 12:42:30 +0200 Subject: [PATCH 0848/1046] Fixing Trailing Whitespace Violation --- Sources/SQLite/Typed/Schema.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 55e525bc..e162cf34 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -308,7 +308,7 @@ public final class TableBuilder { references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) } - + public func column(_ name: Expression, primaryKey: Bool, check: Expression? = nil, references table: QueryType, _ other: Expression) where V.Datatype == Int64 { column(name, V.declaredDatatype, primaryKey ? .default : nil, false, false, check, nil, (table, other), nil) From b53434e2d2bf6c4457b25c212104fca94dcbba2d Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sun, 27 Mar 2022 12:47:24 +0200 Subject: [PATCH 0849/1046] Updated Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf5682b..786084f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================================== * UUID Fix ([#1112][]) +* Add prepareRowIterator method to an extension of Statement. ([#1119][]) * Adding primary key support to column with references ([#1121][]) 0.13.2 (25-01-2022), [diff][diff-0.13.2] @@ -150,4 +151,5 @@ [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 +[#1119]: https://github.com/stephencelis/SQLite.swift/pull/1119 [#1121]: https://github.com/stephencelis/SQLite.swift/pull/1121 From c42167feb9e33dc97e9da05232fa96cf354efbbb Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 27 Apr 2022 09:37:46 +0200 Subject: [PATCH 0850/1046] removeDiacritics => remove_diacritics https://www.sqlite.org/fts3.html#tokenizer Closes #1128 --- Sources/SQLite/Extensions/FTS4.swift | 2 +- Tests/SQLiteTests/FTS4Tests.swift | 6 +++--- Tests/SQLiteTests/FTS5Tests.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 0e48943a..52fec955 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -103,7 +103,7 @@ public struct Tokenizer { var arguments = [String]() if let removeDiacritics = removeDiacritics { - arguments.append("removeDiacritics=\(removeDiacritics ? 1 : 0)".quote()) + arguments.append("remove_diacritics=\(removeDiacritics ? 1 : 0)".quote()) } if !tokenchars.isEmpty { diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index 33be422c..d6385b0c 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -21,12 +21,12 @@ class FTS4Tests: XCTestCase { virtualTable.create(.FTS4([string], tokenize: .Porter)) ) XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=0\")", + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"remove_diacritics=0\")", virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false))) ) XCTAssertEqual( """ - CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"remove_diacritics=1\" \"tokenchars=.\" \"separators=X\") """.replacingOccurrences(of: "\n", with: ""), virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, @@ -116,7 +116,7 @@ class FTS4ConfigTests: XCTestCase { func test_tokenizer_unicode61_with_options() { XCTAssertEqual( """ - CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" + CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"remove_diacritics=1\" \"tokenchars=.\" \"separators=X\") """.replacingOccurrences(of: "\n", with: ""), sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/FTS5Tests.swift index c271be9d..199ec415 100644 --- a/Tests/SQLiteTests/FTS5Tests.swift +++ b/Tests/SQLiteTests/FTS5Tests.swift @@ -77,7 +77,7 @@ class FTS5Tests: XCTestCase { func test_tokenizer_unicode61_with_options() { XCTAssertEqual( - "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts5(tokenize=unicode61 \"remove_diacritics=1\" \"tokenchars=.\" \"separators=X\")", sql(config.tokenizer(.Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"])))) } From 626cc79344db350887b28016673b71645de5668b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=9Awi=C4=87?= Date: Fri, 29 Apr 2022 17:17:44 +0300 Subject: [PATCH 0851/1046] Fixed build order --- SQLite.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 7387d0e9..61c2773a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -592,9 +592,9 @@ isa = PBXNativeTarget; buildConfigurationList = 03A65E6F1C6BB0F60062603F /* Build configuration list for PBXNativeTarget "SQLite tvOS" */; buildPhases = ( + 03A65E571C6BB0F50062603F /* Headers */, 03A65E551C6BB0F50062603F /* Sources */, 03A65E561C6BB0F50062603F /* Frameworks */, - 03A65E571C6BB0F50062603F /* Headers */, 03A65E581C6BB0F50062603F /* Resources */, ); buildRules = ( @@ -628,9 +628,9 @@ isa = PBXNativeTarget; buildConfigurationList = A121AC4C1CA35C79005A31D1 /* Build configuration list for PBXNativeTarget "SQLite watchOS" */; buildPhases = ( + A121AC421CA35C79005A31D1 /* Headers */, A121AC401CA35C79005A31D1 /* Sources */, A121AC411CA35C79005A31D1 /* Frameworks */, - A121AC421CA35C79005A31D1 /* Headers */, A121AC431CA35C79005A31D1 /* Resources */, ); buildRules = ( @@ -646,9 +646,9 @@ isa = PBXNativeTarget; buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; buildPhases = ( + EE247AD01C3F04ED00AE3E12 /* Headers */, EE247ACE1C3F04ED00AE3E12 /* Sources */, EE247ACF1C3F04ED00AE3E12 /* Frameworks */, - EE247AD01C3F04ED00AE3E12 /* Headers */, EE247AD11C3F04ED00AE3E12 /* Resources */, ); buildRules = ( @@ -682,9 +682,9 @@ isa = PBXNativeTarget; buildConfigurationList = EE247B511C3F3ED000AE3E12 /* Build configuration list for PBXNativeTarget "SQLite Mac" */; buildPhases = ( + EE247B391C3F3ED000AE3E12 /* Headers */, EE247B371C3F3ED000AE3E12 /* Sources */, EE247B381C3F3ED000AE3E12 /* Frameworks */, - EE247B391C3F3ED000AE3E12 /* Headers */, EE247B3A1C3F3ED000AE3E12 /* Resources */, ); buildRules = ( From b45781a6659e186f85509a83b3678d4701221cc6 Mon Sep 17 00:00:00 2001 From: Justin Meiners Date: Mon, 9 May 2022 15:06:50 -0600 Subject: [PATCH 0852/1046] Improve string quote performance --- Sources/SQLite/Helpers.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index d4c6828e..e3d37e11 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -54,12 +54,17 @@ extension Optional: _OptionalType { let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) extension String { - func quote(_ mark: Character = "\"") -> String { - let escaped = reduce("") { string, character in - string + (character == mark ? "\(mark)\(mark)" : "\(character)") + var quoted = "" + quoted.append(mark) + for character in self { + quoted.append(character) + if character == mark { + quoted.append(character) + } } - return "\(mark)\(escaped)\(mark)" + quoted.append(mark) + return quoted } func join(_ expressions: [Expressible]) -> Expressible { From 75dfb4e4af53b572ed433588fece3950d2c52c35 Mon Sep 17 00:00:00 2001 From: Atulya Date: Thu, 9 Jun 2022 17:20:20 -0700 Subject: [PATCH 0853/1046] add decoding for UUID --- Sources/SQLite/Typed/Coding.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index cea2565a..ca9ca055 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -387,7 +387,11 @@ private class SQLiteDecoder: Decoder { } else if type == Date.self { let date = try row.get(Expression(key.stringValue)) return date as! T + } else if type == UUID.self { + let uuid = try row.get(Expression(key.stringValue)) + return uuid as! T } + // swiftlint:enable force_cast guard let JSONString = try row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, From 84783d370c94b15ac23e4624027d56f0735d7b5b Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 13 Jun 2022 18:20:22 -0700 Subject: [PATCH 0854/1046] remove trailing whitespace for swiftlint --- Sources/SQLite/Typed/Coding.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index ca9ca055..4033365f 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -391,7 +391,7 @@ private class SQLiteDecoder: Decoder { let uuid = try row.get(Expression(key.stringValue)) return uuid as! T } - + // swiftlint:enable force_cast guard let JSONString = try row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, From 7715f7d626261e152c5f718e0770a28d728ba632 Mon Sep 17 00:00:00 2001 From: Atulya Date: Tue, 14 Jun 2022 18:22:44 -0700 Subject: [PATCH 0855/1046] add tests for codable UUID --- Tests/SQLiteTests/QueryIntegrationTests.swift | 7 +-- Tests/SQLiteTests/QueryTests.swift | 44 +++++++++---------- Tests/SQLiteTests/TestHelpers.swift | 5 ++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 4b9c4bbf..82ee50a3 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -75,15 +75,15 @@ class QueryIntegrationTests: SQLiteTestCase { builder.column(Expression("float")) builder.column(Expression("double")) builder.column(Expression("date")) + builder.column(Expression("uuid")) builder.column(Expression("optional")) builder.column(Expression("sub")) }) let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, - date: Date(timeIntervalSince1970: 5000), optional: "optional", sub: value1) - + date: Date(timeIntervalSince1970: 5000), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) try db.run(table.insert(value)) let rows = try db.prepare(table) @@ -95,6 +95,7 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(values[0].float, 7) XCTAssertEqual(values[0].double, 8) XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) + XCTAssertEqual(values[0].uuid, UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!) XCTAssertEqual(values[0].optional, "optional") XCTAssertEqual(values[0].sub?.int, 1) XCTAssertEqual(values[0].sub?.string, "2") diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index efcbab94..efaf7d15 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -279,12 +279,12 @@ class QueryTests: XCTestCase { func test_insert_encodable() throws { let emails = Table("emails") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let insert = try emails.insert(value) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -294,16 +294,16 @@ class QueryTests: XCTestCase { func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: "optional", sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"optional\", - \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'optional', '\(encodedJSONString)') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", + \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', '\(encodedJSONString)') """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -350,14 +350,14 @@ class QueryTests: XCTestCase { let emails = Table("emails") let string = Expression("string") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000') ON CONFLICT (\"string\") + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", - \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\" + \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\", \"uuid\" = \"excluded\".\"uuid\" """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -366,17 +366,17 @@ class QueryTests: XCTestCase { func test_insert_many_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000'), - (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), + (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') """.replacingOccurrences(of: "\n", with: ""), insert ) @@ -399,12 +399,12 @@ class QueryTests: XCTestCase { func test_update_encodable() throws { let emails = Table("emails") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let update = try emails.update(value) assertSQL( """ UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, - \"date\" = '1970-01-01T00:00:00.000' + \"date\" = '1970-01-01T00:00:00.000', \"uuid\" = 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F' """.replacingOccurrences(of: "\n", with: ""), update ) @@ -413,9 +413,9 @@ class QueryTests: XCTestCase { func test_update_encodable_with_nested_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), optional: nil, sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: value1) let update = try emails.update(value) // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, @@ -424,7 +424,7 @@ class QueryTests: XCTestCase { let expectedPrefix = """ UPDATE \"emails\" SET \"int\" = 1, \"string\" = '2', \"bool\" = 1, \"float\" = 3.0, \"double\" = 4.0, - \"date\" = '1970-01-01T00:00:00.000', \"sub\" = ' + \"date\" = '1970-01-01T00:00:00.000', \"uuid\" = 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', \"sub\" = ' """.replacingOccurrences(of: "\n", with: "") let expectedSuffix = "'" diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index a6efa2e7..bc538c5b 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -115,16 +115,18 @@ class TestCodable: Codable, Equatable { let float: Float let double: Double let date: Date + let uuid: UUID let optional: String? let sub: TestCodable? - init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, optional: String?, sub: TestCodable?) { + init(int: Int, string: String, bool: Bool, float: Float, double: Double, date: Date, uuid: UUID, optional: String?, sub: TestCodable?) { self.int = int self.string = string self.bool = bool self.float = float self.double = double self.date = date + self.uuid = uuid self.optional = optional self.sub = sub } @@ -136,6 +138,7 @@ class TestCodable: Codable, Equatable { lhs.float == rhs.float && lhs.double == rhs.double && lhs.date == rhs.date && + lhs.uuid == lhs.uuid && lhs.optional == rhs.optional && lhs.sub == rhs.sub } From 8fece72b9be693a41d15ff20d7432bd30964610e Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 27 Jun 2022 12:38:57 -0700 Subject: [PATCH 0856/1046] create a constant for the UUID used in tests --- Tests/SQLiteTests/QueryIntegrationTests.swift | 6 +++--- Tests/SQLiteTests/QueryTests.swift | 20 +++++++++---------- Tests/SQLiteTests/TestHelpers.swift | 2 ++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 82ee50a3..b3d9cf96 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -81,9 +81,9 @@ class QueryIntegrationTests: SQLiteTestCase { }) let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value = TestCodable(int: 5, string: "6", bool: true, float: 7, double: 8, - date: Date(timeIntervalSince1970: 5000), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) + date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue, optional: "optional", sub: value1) try db.run(table.insert(value)) let rows = try db.prepare(table) @@ -95,7 +95,7 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(values[0].float, 7) XCTAssertEqual(values[0].double, 8) XCTAssertEqual(values[0].date, Date(timeIntervalSince1970: 5000)) - XCTAssertEqual(values[0].uuid, UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!) + XCTAssertEqual(values[0].uuid, testUUIDValue) XCTAssertEqual(values[0].optional, "optional") XCTAssertEqual(values[0].sub?.int, 1) XCTAssertEqual(values[0].sub?.string, "2") diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index efaf7d15..e4353e52 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -279,7 +279,7 @@ class QueryTests: XCTestCase { func test_insert_encodable() throws { let emails = Table("emails") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insert(value) assertSQL( """ @@ -294,9 +294,9 @@ class QueryTests: XCTestCase { func test_insert_encodable_with_nested_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: "optional", sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: "optional", sub: value1) let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! @@ -350,7 +350,7 @@ class QueryTests: XCTestCase { let emails = Table("emails") let string = Expression("string") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) assertSQL( """ @@ -366,11 +366,11 @@ class QueryTests: XCTestCase { func test_insert_many_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) assertSQL( """ @@ -399,7 +399,7 @@ class QueryTests: XCTestCase { func test_update_encodable() throws { let emails = Table("emails") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let update = try emails.update(value) assertSQL( """ @@ -413,9 +413,9 @@ class QueryTests: XCTestCase { func test_update_encodable_with_nested_encodable() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, - date: Date(timeIntervalSince1970: 0), uuid: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, optional: nil, sub: value1) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: value1) let update = try emails.update(value) // NOTE: As Linux JSON decoding doesn't order keys the same way, we need to check prefix, suffix, diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index bc538c5b..4a71fd40 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -98,6 +98,8 @@ let stringOptional = Expression("stringOptional") let uuid = Expression("uuid") let uuidOptional = Expression("uuidOptional") +let testUUIDValue = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! + func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclosure () -> Expressible, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) From 92760e03cc71643dbb51402eb21c2dbbb59405b3 Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 27 Jun 2022 12:51:15 -0700 Subject: [PATCH 0857/1046] use a switch to accommodate more types --- Sources/SQLite/Typed/Coding.swift | 166 +++++++++++++++--------------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 4033365f..e7f5b7bb 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -44,7 +44,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } - + /// Creates an `INSERT` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -69,7 +69,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(or: onConflict, encoder.setters + otherSetters) } - + /// Creates a batch `INSERT` statement by encoding the array of given objects /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -93,7 +93,7 @@ extension QueryType { } return self.insertMany(combinedSetters) } - + /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -116,7 +116,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) } - + /// Creates an `UPDATE` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -151,7 +151,7 @@ extension Row { public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { try V(from: decoder(userInfo: userInfo)) } - + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { SQLiteDecoder(row: self, userInfo: userInfo) } @@ -162,130 +162,131 @@ private class SQLiteEncoder: Encoder { class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { // swiftlint:disable nesting typealias Key = MyKey - + let encoder: SQLiteEncoder let codingPath: [CodingKey] = [] - + init(encoder: SQLiteEncoder) { self.encoder = encoder } - + func superEncoder() -> Swift.Encoder { fatalError("SQLiteEncoding does not support super encoders") } - + func superEncoder(forKey key: Key) -> Swift.Encoder { fatalError("SQLiteEncoding does not support super encoders") } - + func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { encoder.setters.append(Expression(key.stringValue) <- nil) } - + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Bool, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Float, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- Double(value)) } - + func encode(_ value: Double, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: String, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { - if let data = value as? Data { + switch value { + case let data as Data: encoder.setters.append(Expression(key.stringValue) <- data) - } else if let date = value as? Date { + case let date as Date: encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - } else if let uuid = value as? UUID { + case let uuid as UUID: encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) - } else { + default: let encoded = try JSONEncoder().encode(value) let string = String(data: encoded, encoding: .utf8) encoder.setters.append(Expression(key.stringValue) <- string) } } - + func encode(_ value: Int8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) } - + func encode(_ value: Int16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int16 is not supported")) } - + func encode(_ value: Int32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int32 is not supported")) } - + func encode(_ value: Int64, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: UInt, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt is not supported")) } - + func encode(_ value: UInt8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt8 is not supported")) } - + func encode(_ value: UInt16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt16 is not supported")) } - + func encode(_ value: UInt32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt32 is not supported")) } - + func encode(_ value: UInt64, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt64 is not supported")) } - + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) - -> KeyedEncodingContainer where NestedKey: CodingKey { + -> KeyedEncodingContainer where NestedKey: CodingKey { fatalError("encoding a nested container is not supported") } - + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { fatalError("encoding nested values is not supported") } } - + fileprivate var setters: [Setter] = [] let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] - + init(userInfo: [CodingUserInfoKey: Any]) { self.userInfo = userInfo } - + func singleValueContainer() -> SingleValueEncodingContainer { fatalError("not supported") } - + func unkeyedContainer() -> UnkeyedEncodingContainer { fatalError("not supported") } - + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } @@ -294,156 +295,157 @@ private class SQLiteEncoder: Encoder { private class SQLiteDecoder: Decoder { class SQLiteKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = MyKey - + let codingPath: [CodingKey] = [] let row: Row - + init(row: Row) { self.row = row } - + var allKeys: [Key] { row.columnNames.keys.compactMap({ Key(stringValue: $0) }) } - + func contains(_ key: Key) -> Bool { row.hasValue(for: key.stringValue) } - + func decodeNil(forKey key: Key) throws -> Bool { !contains(key) } - + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int8 is not supported")) } - + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int16 is not supported")) } - + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int32 is not supported")) } - + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { try row.get(Expression(key.stringValue)) } - + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt is not supported")) - + } - + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt8 is not supported")) } - + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt16 is not supported")) } - + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt32 is not supported")) } - + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt64 is not supported")) } - + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { Float(try row.get(Expression(key.stringValue))) } - + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { try row.get(Expression(key.stringValue)) } - + func decode(_ type: String.Type, forKey key: Key) throws -> String { try row.get(Expression(key.stringValue)) } - + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { // swiftlint:disable force_cast - if type == Data.self { + switch type { + case is Data.Type: let data = try row.get(Expression(key.stringValue)) return data as! T - } else if type == Date.self { + case is Date.Type: let date = try row.get(Expression(key.stringValue)) return date as! T - } else if type == UUID.self { + case is UUID.Type: let uuid = try row.get(Expression(key.stringValue)) return uuid as! T + default: + // swiftlint:enable force_cast + guard let JSONString = try row.get(Expression(key.stringValue)) else { + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, + debugDescription: "an unsupported type was found")) + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, + debugDescription: "invalid utf8 data found")) + } + return try JSONDecoder().decode(type, from: data) } - - // swiftlint:enable force_cast - guard let JSONString = try row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, - debugDescription: "an unsupported type was found")) - } - guard let data = JSONString.data(using: .utf8) else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, - debugDescription: "invalid utf8 data found")) - } - return try JSONDecoder().decode(type, from: data) } - + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws - -> KeyedDecodingContainer where NestedKey: CodingKey { + -> KeyedDecodingContainer where NestedKey: CodingKey { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding nested containers is not supported")) } - + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding unkeyed containers is not supported")) } - + func superDecoder() throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super encoders containers is not supported")) } - + func superDecoder(forKey key: Key) throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super decoders is not supported")) } } - + let row: Row let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] - + init(row: Row, userInfo: [CodingUserInfoKey: Any]) { self.row = row self.userInfo = userInfo } - + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } - + func unkeyedContainer() throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an unkeyed container is not supported")) } - + func singleValueContainer() throws -> SingleValueDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding a single value container is not supported")) From bc63d3ab1a154d941ac67b8c75216b625cd0efdf Mon Sep 17 00:00:00 2001 From: Atulya Date: Mon, 27 Jun 2022 12:52:36 -0700 Subject: [PATCH 0858/1046] swiftlint fix --- Sources/SQLite/Typed/Coding.swift | 124 +++++++++++++++--------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index e7f5b7bb..ec2e0d6c 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -44,7 +44,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(encoder.setters + otherSetters) } - + /// Creates an `INSERT` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -69,7 +69,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.insert(or: onConflict, encoder.setters + otherSetters) } - + /// Creates a batch `INSERT` statement by encoding the array of given objects /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -93,7 +93,7 @@ extension QueryType { } return self.insertMany(combinedSetters) } - + /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -116,7 +116,7 @@ extension QueryType { try encodable.encode(to: encoder) return self.upsert(encoder.setters + otherSetters, onConflictOf: conflicting) } - + /// Creates an `UPDATE` statement by encoding the given object /// This method converts any custom nested types to JSON data and does not handle any sort /// of object relationships. If you want to support relationships between objects you will @@ -151,7 +151,7 @@ extension Row { public func decode(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V { try V(from: decoder(userInfo: userInfo)) } - + public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder { SQLiteDecoder(row: self, userInfo: userInfo) } @@ -162,46 +162,46 @@ private class SQLiteEncoder: Encoder { class SQLiteKeyedEncodingContainer: KeyedEncodingContainerProtocol { // swiftlint:disable nesting typealias Key = MyKey - + let encoder: SQLiteEncoder let codingPath: [CodingKey] = [] - + init(encoder: SQLiteEncoder) { self.encoder = encoder } - + func superEncoder() -> Swift.Encoder { fatalError("SQLiteEncoding does not support super encoders") } - + func superEncoder(forKey key: Key) -> Swift.Encoder { fatalError("SQLiteEncoding does not support super encoders") } - + func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { encoder.setters.append(Expression(key.stringValue) <- nil) } - + func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Bool, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: Float, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- Double(value)) } - + func encode(_ value: Double, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: String, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { switch value { case let data as Data: @@ -216,77 +216,77 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- string) } } - + func encode(_ value: Int8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) } - + func encode(_ value: Int16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int16 is not supported")) } - + func encode(_ value: Int32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int32 is not supported")) } - + func encode(_ value: Int64, forKey key: Key) throws { encoder.setters.append(Expression(key.stringValue) <- value) } - + func encode(_ value: UInt, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt is not supported")) } - + func encode(_ value: UInt8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt8 is not supported")) } - + func encode(_ value: UInt16, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt16 is not supported")) } - + func encode(_ value: UInt32, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt32 is not supported")) } - + func encode(_ value: UInt64, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an UInt64 is not supported")) } - + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { fatalError("encoding a nested container is not supported") } - + func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { fatalError("encoding nested values is not supported") } } - + fileprivate var setters: [Setter] = [] let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] - + init(userInfo: [CodingUserInfoKey: Any]) { self.userInfo = userInfo } - + func singleValueContainer() -> SingleValueEncodingContainer { fatalError("not supported") } - + func unkeyedContainer() -> UnkeyedEncodingContainer { fatalError("not supported") } - + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) } @@ -295,91 +295,91 @@ private class SQLiteEncoder: Encoder { private class SQLiteDecoder: Decoder { class SQLiteKeyedDecodingContainer: KeyedDecodingContainerProtocol { typealias Key = MyKey - + let codingPath: [CodingKey] = [] let row: Row - + init(row: Row) { self.row = row } - + var allKeys: [Key] { row.columnNames.keys.compactMap({ Key(stringValue: $0) }) } - + func contains(_ key: Key) -> Bool { row.hasValue(for: key.stringValue) } - + func decodeNil(forKey key: Key) throws -> Bool { !contains(key) } - + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { try row.get(Expression(key.stringValue)) } - + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int8 is not supported")) } - + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int16 is not supported")) } - + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an Int32 is not supported")) } - + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { try row.get(Expression(key.stringValue)) } - + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt is not supported")) - + } - + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt8 is not supported")) } - + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt16 is not supported")) } - + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt32 is not supported")) } - + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an UInt64 is not supported")) } - + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { Float(try row.get(Expression(key.stringValue))) } - + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { try row.get(Expression(key.stringValue)) } - + func decode(_ type: String.Type, forKey key: Key) throws -> String { try row.get(Expression(key.stringValue)) } - + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable { // swiftlint:disable force_cast switch type { @@ -405,47 +405,47 @@ private class SQLiteDecoder: Decoder { return try JSONDecoder().decode(type, from: data) } } - + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding nested containers is not supported")) } - + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding unkeyed containers is not supported")) } - + func superDecoder() throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super encoders containers is not supported")) } - + func superDecoder(forKey key: Key) throws -> Swift.Decoder { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding super decoders is not supported")) } } - + let row: Row let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] - + init(row: Row, userInfo: [CodingUserInfoKey: Any]) { self.row = row self.userInfo = userInfo } - + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key: CodingKey { KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: row)) } - + func unkeyedContainer() throws -> UnkeyedDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding an unkeyed container is not supported")) } - + func singleValueContainer() throws -> SingleValueDecodingContainer { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "decoding a single value container is not supported")) From 3fac726365dc5813625aed56069c3b9feb446762 Mon Sep 17 00:00:00 2001 From: Atulya Date: Tue, 28 Jun 2022 12:27:52 -0700 Subject: [PATCH 0859/1046] silence swiftlint line_length for some sql strings --- Tests/SQLiteTests/QueryTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index e4353e52..d9cce086 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -300,6 +300,7 @@ class QueryTests: XCTestCase { let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! + // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", @@ -307,6 +308,7 @@ class QueryTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), insert ) + // swiftlint:enable line_length } #endif @@ -352,6 +354,7 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) + // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") @@ -361,6 +364,7 @@ class QueryTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), insert ) + // swiftlint:enable line_length } func test_insert_many_encodable() throws { @@ -372,6 +376,7 @@ class QueryTests: XCTestCase { let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) + // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") @@ -380,6 +385,7 @@ class QueryTests: XCTestCase { """.replacingOccurrences(of: "\n", with: ""), insert ) + // swiftlint:enable line_length } func test_update_compilesUpdateExpression() { From be3a5ced7e790619f225024db1ac93795b01cf81 Mon Sep 17 00:00:00 2001 From: Atulya Date: Wed, 29 Jun 2022 09:34:32 -0700 Subject: [PATCH 0860/1046] remove swiftlint line_length disables --- Tests/SQLiteTests/QueryTests.swift | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index d9cce086..2201caef 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -300,15 +300,14 @@ class QueryTests: XCTestCase { let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", - \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', '\(encodedJSONString)') + \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', + 'optional', '\(encodedJSONString)') """.replacingOccurrences(of: "\n", with: ""), insert ) - // swiftlint:enable line_length } #endif @@ -354,17 +353,16 @@ class QueryTests: XCTestCase { let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) - // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') ON CONFLICT (\"string\") DO UPDATE SET \"int\" = \"excluded\".\"int\", \"bool\" = \"excluded\".\"bool\", - \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\", \"uuid\" = \"excluded\".\"uuid\" + \"float\" = \"excluded\".\"float\", \"double\" = \"excluded\".\"double\", \"date\" = \"excluded\".\"date\", + \"uuid\" = \"excluded\".\"uuid\" """.replacingOccurrences(of: "\n", with: ""), insert ) - // swiftlint:enable line_length } func test_insert_many_encodable() throws { @@ -376,16 +374,15 @@ class QueryTests: XCTestCase { let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) - // swiftlint:disable line_length assertSQL( """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), + (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') """.replacingOccurrences(of: "\n", with: ""), insert ) - // swiftlint:enable line_length } func test_update_compilesUpdateExpression() { From ba5165ac7aaddc3f240f04e54d3f978ae2208a5d Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 1 Jul 2022 12:36:17 -0700 Subject: [PATCH 0861/1046] fix insertMany() failing with encodables which have assymmetric optional values --- Sources/SQLite/Typed/Coding.swift | 88 +++++++++++++++++-- Tests/SQLiteTests/QueryIntegrationTests.swift | 21 +++++ Tests/SQLiteTests/QueryTests.swift | 12 +-- Tests/SQLiteTests/TestHelpers.swift | 20 +++++ 4 files changed, 129 insertions(+), 12 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index ec2e0d6c..14a469ab 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -86,12 +86,22 @@ extension QueryType { /// - Returns: An `INSERT` statement for the encodable objects public func insertMany(_ encodables: [Encodable], userInfo: [CodingUserInfoKey: Any] = [:], otherSetters: [Setter] = []) throws -> Insert { - let combinedSetters = try encodables.map { encodable -> [Setter] in - let encoder = SQLiteEncoder(userInfo: userInfo) + let combinedSettersWithoutNils = try encodables.map { encodable -> [Setter] in + let encoder = SQLiteEncoder(userInfo: userInfo, forcingNilValueSetters: false) try encodable.encode(to: encoder) return encoder.setters + otherSetters } - return self.insertMany(combinedSetters) + // requires the same number of setters per encodable + guard Set(combinedSettersWithoutNils.map(\.count)).count == 1 else { + // asymmetric sets of value insertions (some nil, some not), requires NULL value to satisfy INSERT query + let combinedSymmetricSetters = try encodables.map { encodable -> [Setter] in + let encoder = SQLiteEncoder(userInfo: userInfo, forcingNilValueSetters: true) + try encodable.encode(to: encoder) + return encoder.setters + otherSetters + } + return self.insertMany(combinedSymmetricSetters) + } + return self.insertMany(combinedSettersWithoutNils) } /// Creates an `INSERT ON CONFLICT DO UPDATE` statement, aka upsert, by encoding the given object @@ -165,9 +175,11 @@ private class SQLiteEncoder: Encoder { let encoder: SQLiteEncoder let codingPath: [CodingKey] = [] + let forcingNilValueSetters: Bool - init(encoder: SQLiteEncoder) { + init(encoder: SQLiteEncoder, forcingNilValueSetters: Bool = false) { self.encoder = encoder + self.forcingNilValueSetters = forcingNilValueSetters } func superEncoder() -> Swift.Encoder { @@ -202,6 +214,46 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- value) } + func encodeIfPresent(_ value: Int?, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: Float?, forKey key: Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- Double(value)) + } else if forcingNilValueSetters{ + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: Double?, forKey key: Key) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + + func encodeIfPresent(_ value: String?, forKey key: MyKey) throws { + if let value = value { + encoder.setters.append(Expression(key.stringValue) <- value) + } else if forcingNilValueSetters { + encoder.setters.append(Expression(key.stringValue) <- nil) + } + } + func encode(_ value: T, forKey key: Key) throws where T: Swift.Encodable { switch value { case let data as Data: @@ -217,6 +269,28 @@ private class SQLiteEncoder: Encoder { } } + func encodeIfPresent(_ value: T?, forKey key: Key) throws where T: Swift.Encodable { + guard let value = value else { + guard forcingNilValueSetters else { + return + } + encoder.setters.append(Expression(key.stringValue) <- nil) + return + } + switch value { + case let data as Data: + encoder.setters.append(Expression(key.stringValue) <- data) + case let date as Date: + encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) + case let uuid as UUID: + encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) + default: + let encoded = try JSONEncoder().encode(value) + let string = String(data: encoded, encoding: .utf8) + encoder.setters.append(Expression(key.stringValue) <- string) + } + } + func encode(_ value: Int8, forKey key: Key) throws { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "encoding an Int8 is not supported")) @@ -274,9 +348,11 @@ private class SQLiteEncoder: Encoder { fileprivate var setters: [Setter] = [] let codingPath: [CodingKey] = [] let userInfo: [CodingUserInfoKey: Any] + let forcingNilValueSetters: Bool - init(userInfo: [CodingUserInfoKey: Any]) { + init(userInfo: [CodingUserInfoKey: Any], forcingNilValueSetters: Bool = false) { self.userInfo = userInfo + self.forcingNilValueSetters = forcingNilValueSetters } func singleValueContainer() -> SingleValueEncodingContainer { @@ -288,7 +364,7 @@ private class SQLiteEncoder: Encoder { } func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { - KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self)) + KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self, forcingNilValueSetters: forcingNilValueSetters)) } } diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index b3d9cf96..3ea06745 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -130,6 +130,27 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(2, id) } + func test_insert_many_encodables() throws { + let table = Table("codable") + try db.run(table.create { builder in + builder.column(Expression("int")) + builder.column(Expression("string")) + builder.column(Expression("bool")) + builder.column(Expression("float")) + builder.column(Expression("double")) + builder.column(Expression("date")) + builder.column(Expression("uuid")) + }) + + let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue) + let valueWithNils = TestOptionalCodable(int: nil, string: nil, bool: nil, float: nil, double: nil, date: nil, uuid: nil) + try db.run(table.insertMany([value1, valueWithNils])) + + let rows = try db.prepare(table) + let values: [TestOptionalCodable] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 2) + } + func test_upsert() throws { try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 24)) let fetchAge = { () throws -> Int? in diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 2201caef..b2f679e0 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -365,21 +365,21 @@ class QueryTests: XCTestCase { ) } - func test_insert_many_encodable() throws { + func test_insert_many_encodables() throws { let emails = Table("emails") let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let value2 = TestCodable(int: 2, string: "3", bool: true, float: 3, double: 5, - date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) + date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: "optional", sub: nil) let value3 = TestCodable(int: 3, string: "4", bool: true, float: 3, double: 6, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.insertMany([value1, value2, value3]) assertSQL( """ - INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\") - VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), - (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F'), - (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F') + INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", \"sub\") + VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', NULL, NULL), + (2, '3', 1, 3.0, 5.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', NULL), + (3, '4', 1, 3.0, 6.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', NULL, NULL) """.replacingOccurrences(of: "\n", with: ""), insert ) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 4a71fd40..f43310c9 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -145,3 +145,23 @@ class TestCodable: Codable, Equatable { lhs.sub == rhs.sub } } + +struct TestOptionalCodable: Codable, Equatable { + let int: Int? + let string: String? + let bool: Bool? + let float: Float? + let double: Double? + let date: Date? + let uuid: UUID? + + init(int: Int?, string: String?, bool: Bool?, float: Float?, double: Double?, date: Date?, uuid: UUID?) { + self.int = int + self.string = string + self.bool = bool + self.float = float + self.double = double + self.date = date + self.uuid = uuid + } +} From c4bdad8f3c47a41d1902d32e988f148e4734075e Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Fri, 1 Jul 2022 12:39:10 -0700 Subject: [PATCH 0862/1046] lint --- Sources/SQLite/Typed/Coding.swift | 2 +- Tests/SQLiteTests/QueryIntegrationTests.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 14a469ab..08b7131a 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -233,7 +233,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Float?, forKey key: Key) throws { if let value = value { encoder.setters.append(Expression(key.stringValue) <- Double(value)) - } else if forcingNilValueSetters{ + } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } } diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index 3ea06745..f3b4bcd3 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -142,7 +142,8 @@ class QueryIntegrationTests: SQLiteTestCase { builder.column(Expression("uuid")) }) - let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue) + let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, + date: Date(timeIntervalSince1970: 5000), uuid: testUUIDValue) let valueWithNils = TestOptionalCodable(int: nil, string: nil, bool: nil, float: nil, double: nil, date: nil, uuid: nil) try db.run(table.insertMany([value1, valueWithNils])) From abff512bb57f46cb155a8b9ccf744c72f72a886f Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Sat, 2 Jul 2022 11:04:17 -0700 Subject: [PATCH 0863/1046] feedback --- Sources/SQLite/Typed/Coding.swift | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 08b7131a..b96bc64e 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -216,7 +216,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Int?, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -224,7 +224,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -232,7 +232,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Float?, forKey key: Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- Double(value)) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -240,7 +240,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: Double?, forKey key: Key) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -248,7 +248,7 @@ private class SQLiteEncoder: Encoder { func encodeIfPresent(_ value: String?, forKey key: MyKey) throws { if let value = value { - encoder.setters.append(Expression(key.stringValue) <- value) + try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) } @@ -277,18 +277,7 @@ private class SQLiteEncoder: Encoder { encoder.setters.append(Expression(key.stringValue) <- nil) return } - switch value { - case let data as Data: - encoder.setters.append(Expression(key.stringValue) <- data) - case let date as Date: - encoder.setters.append(Expression(key.stringValue) <- date.datatypeValue) - case let uuid as UUID: - encoder.setters.append(Expression(key.stringValue) <- uuid.datatypeValue) - default: - let encoded = try JSONEncoder().encode(value) - let string = String(data: encoded, encoding: .utf8) - encoder.setters.append(Expression(key.stringValue) <- string) - } + try encode(value, forKey: key) } func encode(_ value: Int8, forKey key: Key) throws { From 12e2166c90b24f900869b916d9d8b8a6ac24e839 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 15:38:55 -0700 Subject: [PATCH 0864/1046] Add support for the WITH clause The `WITH` clause provides the ability to do hierarchical or recursive queries of tree and graph-like data. See https://www.sqlite.org/lang_with.html. **Details** - Add `all` parameter to `QueryType.union` to allow `UNION ALL` to be used in a query. I opted to add the parameter to the start of the list so that it does not dangle at the end when the union's query is long: ```swift users.union(all: true, posts.join(users, on: users[id] == posts[userId])) // It's a little easier to read than: users.union(posts.join(users, on: users[id] == posts[userId]), all: true) ``` - Add `with` function to `QueryType`. This function adds a `WITH` clause to a query. The function may be called multiple times to add multiple clauses to a query. If multiple clauses are added to the query with conflicting `recursive` parameters, the whole `WITH` clause will be considered recursive. Like the `union` function, I put the `subquery` parameter at the end so that the `recursive` and `materializationHint` options don't dangle at the end of a long query. ```swift let users = Table("users") let users = Table("posts") let first = Table("first") let second = Table("second") first.with(first, recursive: true as: users).with(second, recursive: false, as: posts) // WITH RECURSIVE "first" AS (SELECT * from users), "second" AS (SELECT * from posts) SELECT * from "first" ``` --- Sources/SQLite/Typed/Query.swift | 105 +++++++++++++++++- Tests/SQLiteTests/QueryIntegrationTests.swift | 41 +++++++ Tests/SQLiteTests/QueryTests.swift | 60 ++++++++++ 3 files changed, 201 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 93dc2a73..c328aef6 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -193,12 +193,14 @@ extension QueryType { /// /// - Parameters: /// + /// - all: If false, duplicate rows are removed from the result. + /// /// - table: A query representing the other table. /// /// - Returns: A query with the given `UNION` clause applied. - public func union(_ table: QueryType) -> Self { + public func union(all: Bool = false, _ table: QueryType) -> Self { var query = self - query.clauses.union.append(table) + query.clauses.union.append((all, table)) return query } @@ -496,6 +498,37 @@ extension QueryType { query.clauses.limit = length.map { ($0, offset) } return query } + + // MARK: WITH + + /// Sets a `WITH` clause on the query. + /// + /// let users = Table("users") + /// let id = Expression("email") + /// let name = Expression("name") + /// + /// let userNames = Table("user_names") + /// userCategories.with(userNames, as: users.select(name)) + /// // WITH "user_names" as (SELECT "name" FROM "users") SELECT * FROM "user_names" + /// + /// - Parameters: + /// + /// - alias: A name to assign to the table expression. + /// + /// - recursive: Whether to evaluate the expression recursively. + /// + /// - materializationHint: Provides a hint to the query planner for how the expression should be implemented. + /// + /// - subquery: A query that generates the rows for the table expression. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, materializationHint: MaterializationHint? = nil, as subquery: QueryType) -> Self { + var query = self + let clause = WithClauses.Clause(alias: alias, columns: columns, materializationHint: materializationHint, query: subquery) + query.clauses.with.recursive = query.clauses.with.recursive || recursive + query.clauses.with.clauses.append(clause) + return query + } // MARK: - Clauses // @@ -596,13 +629,50 @@ extension QueryType { return nil } - return " ".join(clauses.union.map { query in + return " ".join(clauses.union.map { (all, query) in " ".join([ - Expression(literal: "UNION"), + Expression(literal: all ? "UNION ALL" : "UNION"), query ]) }) } + + fileprivate var withClause: Expressible? { + guard !clauses.with.clauses.isEmpty else { + return nil + } + + let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in + let hintExpr: Expression? + if let hint = clause.materializationHint { + hintExpr = Expression(literal: hint.rawValue) + } else { + hintExpr = nil + } + + let columnExpr: Expression? + if let columns = clause.columns { + columnExpr = "".wrap(", ".join(columns)) + } else { + columnExpr = nil + } + + let expressions: [Expressible?] = [ + clause.alias.tableName(), + columnExpr, + Expression(literal: "AS"), + hintExpr, + "".wrap(clause.query) as Expression + ] + + return " ".join(expressions.compactMap { $0 }) + }) + + return " ".join([ + Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), + innerClauses + ]) + } // MARK: - @@ -856,6 +926,7 @@ extension QueryType { public var expression: Expression { let clauses: [Expressible?] = [ + withClause, selectClause, joinClause, whereClause, @@ -1233,8 +1304,30 @@ public enum OnConflict: String { } +/// Materialization hints for `WITH` clause +public enum MaterializationHint: String { + + case materialized = "MATERIALIZED" + + case notMaterialized = "NOT MATERIALIZED" +} + // MARK: - Private +struct WithClauses { + struct Clause { + var alias: Table + var columns: [Expressible]? + var materializationHint: MaterializationHint? + var query: QueryType + } + /// The `RECURSIVE` flag is applied to the entire `WITH` clause + var recursive: Bool = false + + /// Each `WITH` clause may have multiple subclauses + var clauses: [Clause] = [] +} + public struct QueryClauses { var select = (distinct: false, columns: [Expression(literal: "*") as Expressible]) @@ -1251,7 +1344,9 @@ public struct QueryClauses { var limit: (length: Int, offset: Int?)? - var union = [QueryType]() + var union = [(all: Bool, table: QueryType)]() + + var with = WithClauses() fileprivate init(_ name: String, alias: String?, database: String?) { from = (name, alias, database) diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index f3b4bcd3..c1cd97a5 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -230,6 +230,47 @@ class QueryIntegrationTests: SQLiteTestCase { let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) XCTAssertEqual(1, result.count) } + + func test_with_recursive() { + let nodes = Table("nodes") + let id = Expression("id") + let parent = Expression("parent") + let value = Expression("value") + + try! db.run(nodes.create { builder in + builder.column(id) + builder.column(parent) + builder.column(value) + }) + + try! db.run(nodes.insertMany([ + [id <- 0, parent <- nil, value <- 2], + [id <- 1, parent <- 0, value <- 4], + [id <- 2, parent <- 0, value <- 9], + [id <- 3, parent <- 2, value <- 8], + [id <- 4, parent <- 2, value <- 7], + [id <- 5, parent <- 4, value <- 3], + ])) + + // Compute the sum of the values of node 5 and its ancestors + let ancestors = Table("ancestors") + let sum = try! db.scalar( + ancestors + .select(value.sum) + .with(ancestors, + columns: [id, parent, value], + recursive: true, + as: nodes + .where(id == 5) + .union(all: true, + nodes.join(ancestors, on: nodes[id] == ancestors[parent]) + .select(nodes[id], nodes[parent], nodes[value]) + ) + ) + ) + + XCTAssertEqual(21, sum) + } } extension Connection { diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index b2f679e0..6baf59b3 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -58,6 +58,11 @@ class QueryTests: XCTestCase { func test_selectDistinct_withStar_compilesSelectClause() { assertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } + + func test_union_compilesUnionClause() { + assertSQL("SELECT * FROM \"users\" UNION SELECT * FROM \"posts\"", users.union(posts)) + assertSQL("SELECT * FROM \"users\" UNION ALL SELECT * FROM \"posts\"", users.union(all: true, posts)) + } func test_join_compilesJoinClause() { assertSQL( @@ -219,6 +224,61 @@ class QueryTests: XCTestCase { users.join(managers, on: managers[id] == users[managerId]) ) } + + func test_with_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, as: users)) + } + + func test_with_recursive_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, recursive: true, as: users)) + + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, recursive: false, as: users)) + } + + func test_with_materialization_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH \"temp\" AS MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, materializationHint: .materialized, as: users)) + + assertSQL("WITH \"temp\" AS NOT MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, materializationHint: .notMaterialized, as: users)) + } + + func test_with_columns_compilesWithClause() { + let temp = Table("temp") + + assertSQL("WITH \"temp\" (\"id\", \"email\") AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", + temp.with(temp, columns: [id, email], recursive: false, materializationHint: nil, as: users)) + } + + func test_with_multiple_compilesWithClause() { + let temp = Table("temp") + let second = Table("second") + let third = Table("third") + + let query = temp + .with(temp, recursive: true, as: users) + .with(second, recursive: true, as: posts) + .with(third, materializationHint: .materialized, as:categories) + + assertSQL( + """ + WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\"), + \"second\" AS (SELECT * FROM \"posts\"), + \"third\" AS MATERIALIZED (SELECT * FROM \"categories\") + SELECT * FROM \"temp\" + """.replacingOccurrences(of: "\n", with: ""), + query + ) + } func test_insert_compilesInsertExpression() { assertSQL( From 03b56886891d1816bb03567584f7c6b00ae96e75 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 17:03:31 -0700 Subject: [PATCH 0865/1046] documentation --- Documentation/Index.md | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index e28b24bb..7b5af41f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -34,6 +34,7 @@ - [Filter Operators and Functions](#filter-operators-and-functions) - [Sorting Rows](#sorting-rows) - [Limiting and Paging Results](#limiting-and-paging-results) + - [Recursive and Hierarchical Queries](#recursive-and-hierarchical-queries) - [Aggregation](#aggregation) - [Upserting Rows](#upserting-rows) - [Updating Rows](#updating-rows) @@ -1086,6 +1087,62 @@ users.limit(5, offset: 5) ``` +#### Recursive and Hierarchical Queries + +We can perform a recursive or hierarchical query using a [query's](#queries) `with` +function. Column names and a materialization hint can optionally be provided. + +```swift +// Get the management chain for the manager with id == 8 + +let chain = Table("chain") +let id = Expression("id") +let managerId = Expression("manager_id") + +let query = managers + .where(id == 8) + .union(chain.join(managers, on: chain[managerId] == managers[id]) + +chain.with(chain, recursive: true, as: query) +// WITH RECURSIVE +// "chain" AS ( +// SELECT * FROM "managers" WHERE "id" = 8 +// UNION +// SELECT * from "chain" +// JOIN "managers" ON "chain"."manager_id" = "managers"."id" +// ) +// SELECT * FROM "chain" + +// Add a "level" column to the query representing manager's position in the chain + +let level = Expression("level") + +let queryWithLevel = + managers + .select(id, managerId, 0) + .where(id == 8) + .union( + chain + .select(managers[id], managers[manager_id], level + 1) + .join(managers, on: chain[managerId] == managers[id]) + ) + +chain.with(chain, + columns: [id, managerId, level], + recursive: true, + materializationHint: .materialize, + as: queryWithLevel) +// WITH RECURSIVE +// "chain" ("id", "manager_id", "level") AS MATERIALIZED ( +// SELECT ("id", "manager_id", 0) FROM "managers" WHERE "id" = 8 +// UNION +// SELECT ("manager"."id", "manager"."manager_id", "level" + 1) FROM "chain" +// JOIN "managers" ON "chain"."manager_id" = "managers"."id" +// ) +// SELECT * FROM "chain" +``` + + #### Aggregation [Queries](#queries) come with a number of functions that quickly return From 76d34dd8ff668b58d6b5530528460dd6d2786c0e Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 17:36:18 -0700 Subject: [PATCH 0866/1046] Heed linter warnings Note sure how to get the line length of Query back down below 500 without harming readability though. --- Documentation/Index.md | 2 +- Sources/SQLite/Typed/Query.swift | 29 +++++++++-------- Tests/SQLiteTests/QueryIntegrationTests.swift | 12 +++---- Tests/SQLiteTests/QueryTests.swift | 32 +++++++++---------- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 7b5af41f..44304c1c 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1130,7 +1130,7 @@ let queryWithLevel = chain.with(chain, columns: [id, managerId, level], recursive: true, - materializationHint: .materialize, + hint: .materialize, as: queryWithLevel) // WITH RECURSIVE // "chain" ("id", "manager_id", "level") AS MATERIALIZED ( diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c328aef6..b34570a7 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -498,9 +498,9 @@ extension QueryType { query.clauses.limit = length.map { ($0, offset) } return query } - + // MARK: WITH - + /// Sets a `WITH` clause on the query. /// /// let users = Table("users") @@ -517,14 +517,15 @@ extension QueryType { /// /// - recursive: Whether to evaluate the expression recursively. /// - /// - materializationHint: Provides a hint to the query planner for how the expression should be implemented. + /// - hint: Provides a hint to the query planner for how the expression should be implemented. /// /// - subquery: A query that generates the rows for the table expression. /// /// - Returns: A query with the given `ORDER BY` clause applied. - public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, materializationHint: MaterializationHint? = nil, as subquery: QueryType) -> Self { + public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, + hint: MaterializationHint? = nil, as subquery: QueryType) -> Self { var query = self - let clause = WithClauses.Clause(alias: alias, columns: columns, materializationHint: materializationHint, query: subquery) + let clause = WithClauses.Clause(alias: alias, columns: columns, hint: hint, query: subquery) query.clauses.with.recursive = query.clauses.with.recursive || recursive query.clauses.with.clauses.append(clause) return query @@ -636,7 +637,7 @@ extension QueryType { ]) }) } - + fileprivate var withClause: Expressible? { guard !clauses.with.clauses.isEmpty else { return nil @@ -644,19 +645,19 @@ extension QueryType { let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in let hintExpr: Expression? - if let hint = clause.materializationHint { + if let hint = clause.hint { hintExpr = Expression(literal: hint.rawValue) } else { hintExpr = nil } - + let columnExpr: Expression? if let columns = clause.columns { columnExpr = "".wrap(", ".join(columns)) } else { columnExpr = nil } - + let expressions: [Expressible?] = [ clause.alias.tableName(), columnExpr, @@ -664,10 +665,10 @@ extension QueryType { hintExpr, "".wrap(clause.query) as Expression ] - + return " ".join(expressions.compactMap { $0 }) }) - + return " ".join([ Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), innerClauses @@ -1318,12 +1319,12 @@ struct WithClauses { struct Clause { var alias: Table var columns: [Expressible]? - var materializationHint: MaterializationHint? + var hint: MaterializationHint? var query: QueryType } /// The `RECURSIVE` flag is applied to the entire `WITH` clause var recursive: Bool = false - + /// Each `WITH` clause may have multiple subclauses var clauses: [Clause] = [] } @@ -1345,7 +1346,7 @@ public struct QueryClauses { var limit: (length: Int, offset: Int?)? var union = [(all: Bool, table: QueryType)]() - + var with = WithClauses() fileprivate init(_ name: String, alias: String?, database: String?) { diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index c1cd97a5..db4e2e4e 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -230,28 +230,28 @@ class QueryIntegrationTests: SQLiteTestCase { let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) XCTAssertEqual(1, result.count) } - + func test_with_recursive() { let nodes = Table("nodes") let id = Expression("id") let parent = Expression("parent") let value = Expression("value") - + try! db.run(nodes.create { builder in builder.column(id) builder.column(parent) builder.column(value) }) - + try! db.run(nodes.insertMany([ [id <- 0, parent <- nil, value <- 2], [id <- 1, parent <- 0, value <- 4], [id <- 2, parent <- 0, value <- 9], [id <- 3, parent <- 2, value <- 8], [id <- 4, parent <- 2, value <- 7], - [id <- 5, parent <- 4, value <- 3], + [id <- 5, parent <- 4, value <- 3] ])) - + // Compute the sum of the values of node 5 and its ancestors let ancestors = Table("ancestors") let sum = try! db.scalar( @@ -268,7 +268,7 @@ class QueryIntegrationTests: SQLiteTestCase { ) ) ) - + XCTAssertEqual(21, sum) } } diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index 6baf59b3..a96ff38c 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -58,7 +58,7 @@ class QueryTests: XCTestCase { func test_selectDistinct_withStar_compilesSelectClause() { assertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) } - + func test_union_compilesUnionClause() { assertSQL("SELECT * FROM \"users\" UNION SELECT * FROM \"posts\"", users.union(posts)) assertSQL("SELECT * FROM \"users\" UNION ALL SELECT * FROM \"posts\"", users.union(all: true, posts)) @@ -224,41 +224,41 @@ class QueryTests: XCTestCase { users.join(managers, on: managers[id] == users[managerId]) ) } - + func test_with_compilesWithClause() { let temp = Table("temp") - + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, as: users)) } - + func test_with_recursive_compilesWithClause() { let temp = Table("temp") - + assertSQL("WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, recursive: true, as: users)) - + assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, recursive: false, as: users)) } - + func test_with_materialization_compilesWithClause() { let temp = Table("temp") - + assertSQL("WITH \"temp\" AS MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, materializationHint: .materialized, as: users)) - + temp.with(temp, hint: .materialized, as: users)) + assertSQL("WITH \"temp\" AS NOT MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, materializationHint: .notMaterialized, as: users)) + temp.with(temp, hint: .notMaterialized, as: users)) } - + func test_with_columns_compilesWithClause() { let temp = Table("temp") assertSQL("WITH \"temp\" (\"id\", \"email\") AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, columns: [id, email], recursive: false, materializationHint: nil, as: users)) + temp.with(temp, columns: [id, email], recursive: false, hint: nil, as: users)) } - + func test_with_multiple_compilesWithClause() { let temp = Table("temp") let second = Table("second") @@ -267,8 +267,8 @@ class QueryTests: XCTestCase { let query = temp .with(temp, recursive: true, as: users) .with(second, recursive: true, as: posts) - .with(third, materializationHint: .materialized, as:categories) - + .with(third, hint: .materialized, as: categories) + assertSQL( """ WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\"), From bdacf4df697eb73cb1a0887a6b3db776b55b3b0d Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 7 Jul 2022 17:39:50 -0700 Subject: [PATCH 0867/1046] Slight documentation tweak --- Documentation/Index.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 44304c1c..7a9a390f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1090,7 +1090,7 @@ users.limit(5, offset: 5) #### Recursive and Hierarchical Queries We can perform a recursive or hierarchical query using a [query's](#queries) `with` -function. Column names and a materialization hint can optionally be provided. +function. ```swift // Get the management chain for the manager with id == 8 @@ -1112,9 +1112,12 @@ chain.with(chain, recursive: true, as: query) // JOIN "managers" ON "chain"."manager_id" = "managers"."id" // ) // SELECT * FROM "chain" +``` -// Add a "level" column to the query representing manager's position in the chain +Column names and a materialization hint can optionally be provided. +```swift +// Add a "level" column to the query representing manager's position in the chain let level = Expression("level") let queryWithLevel = From ced185132ea17b9ba7391322bc52982f930538bd Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Wed, 13 Jul 2022 20:22:25 -0700 Subject: [PATCH 0868/1046] Move WITH support members to extension in Query+with.swift --- SQLite.xcodeproj/project.pbxproj | 10 +++ Sources/SQLite/Typed/Query+with.swift | 95 +++++++++++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 69 ------------------- 3 files changed, 105 insertions(+), 69 deletions(-) create mode 100644 Sources/SQLite/Typed/Query+with.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 61c2773a..fc38e5e9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -130,6 +130,10 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; @@ -260,6 +264,7 @@ 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; + 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D4DB368A20C09C9B00D5A58E /* SelectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -499,6 +504,7 @@ EE247AFE1C3F06E900AE3E12 /* Expression.swift */, EE247AFF1C3F06E900AE3E12 /* Operators.swift */, EE247B001C3F06E900AE3E12 /* Query.swift */, + 997DF2AD287FC06D00F8DF95 /* Query+with.swift */, EE247B011C3F06E900AE3E12 /* Schema.swift */, EE247B021C3F06E900AE3E12 /* Setter.swift */, 49EB68C31F7B3CB400D89D40 /* Coding.swift */, @@ -862,6 +868,7 @@ 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, 19A17073552293CA063BEA66 /* Result.swift in Sources */, + 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -904,6 +911,7 @@ files = ( 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, + 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, @@ -959,6 +967,7 @@ 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, + 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1024,6 +1033,7 @@ 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, + 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SQLite/Typed/Query+with.swift b/Sources/SQLite/Typed/Query+with.swift new file mode 100644 index 00000000..005f490c --- /dev/null +++ b/Sources/SQLite/Typed/Query+with.swift @@ -0,0 +1,95 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +import Foundation + +extension QueryType { + + /// Sets a `WITH` clause on the query. + /// + /// let users = Table("users") + /// let id = Expression("email") + /// let name = Expression("name") + /// + /// let userNames = Table("user_names") + /// userCategories.with(userNames, as: users.select(name)) + /// // WITH "user_names" as (SELECT "name" FROM "users") SELECT * FROM "user_names" + /// + /// - Parameters: + /// + /// - alias: A name to assign to the table expression. + /// + /// - recursive: Whether to evaluate the expression recursively. + /// + /// - hint: Provides a hint to the query planner for how the expression should be implemented. + /// + /// - subquery: A query that generates the rows for the table expression. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, + hint: MaterializationHint? = nil, as subquery: QueryType) -> Self { + var query = self + let clause = WithClauses.Clause(alias: alias, columns: columns, hint: hint, query: subquery) + query.clauses.with.recursive = query.clauses.with.recursive || recursive + query.clauses.with.clauses.append(clause) + return query + } + + /// self.clauses.with transformed to an Expressible + var withClause: Expressible? { + guard !clauses.with.clauses.isEmpty else { + return nil + } + + let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in + let hintExpr: Expression? + if let hint = clause.hint { + hintExpr = Expression(literal: hint.rawValue) + } else { + hintExpr = nil + } + + let columnExpr: Expression? + if let columns = clause.columns { + columnExpr = "".wrap(", ".join(columns)) + } else { + columnExpr = nil + } + + let expressions: [Expressible?] = [ + clause.alias.tableName(), + columnExpr, + Expression(literal: "AS"), + hintExpr, + "".wrap(clause.query) as Expression + ] + + return " ".join(expressions.compactMap { $0 }) + }) + + return " ".join([ + Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), + innerClauses + ]) + } +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index b34570a7..f688cfe6 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -499,38 +499,6 @@ extension QueryType { return query } - // MARK: WITH - - /// Sets a `WITH` clause on the query. - /// - /// let users = Table("users") - /// let id = Expression("email") - /// let name = Expression("name") - /// - /// let userNames = Table("user_names") - /// userCategories.with(userNames, as: users.select(name)) - /// // WITH "user_names" as (SELECT "name" FROM "users") SELECT * FROM "user_names" - /// - /// - Parameters: - /// - /// - alias: A name to assign to the table expression. - /// - /// - recursive: Whether to evaluate the expression recursively. - /// - /// - hint: Provides a hint to the query planner for how the expression should be implemented. - /// - /// - subquery: A query that generates the rows for the table expression. - /// - /// - Returns: A query with the given `ORDER BY` clause applied. - public func with(_ alias: Table, columns: [Expressible]? = nil, recursive: Bool = false, - hint: MaterializationHint? = nil, as subquery: QueryType) -> Self { - var query = self - let clause = WithClauses.Clause(alias: alias, columns: columns, hint: hint, query: subquery) - query.clauses.with.recursive = query.clauses.with.recursive || recursive - query.clauses.with.clauses.append(clause) - return query - } - // MARK: - Clauses // // MARK: SELECT @@ -638,43 +606,6 @@ extension QueryType { }) } - fileprivate var withClause: Expressible? { - guard !clauses.with.clauses.isEmpty else { - return nil - } - - let innerClauses = ", ".join(clauses.with.clauses.map { (clause) in - let hintExpr: Expression? - if let hint = clause.hint { - hintExpr = Expression(literal: hint.rawValue) - } else { - hintExpr = nil - } - - let columnExpr: Expression? - if let columns = clause.columns { - columnExpr = "".wrap(", ".join(columns)) - } else { - columnExpr = nil - } - - let expressions: [Expressible?] = [ - clause.alias.tableName(), - columnExpr, - Expression(literal: "AS"), - hintExpr, - "".wrap(clause.query) as Expression - ] - - return " ".join(expressions.compactMap { $0 }) - }) - - return " ".join([ - Expression(literal: clauses.with.recursive ? "WITH RECURSIVE" : "WITH"), - innerClauses - ]) - } - // MARK: - public func alias(_ aliasName: String) -> Self { From ed43285b6e02a7244c86c8aa58d6069cf6ed803d Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Thu, 14 Jul 2022 20:55:31 -0700 Subject: [PATCH 0869/1046] Move WithClauses to Query+with.swift --- Sources/SQLite/Typed/Query+with.swift | 14 ++++++++++++++ Sources/SQLite/Typed/Query.swift | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/SQLite/Typed/Query+with.swift b/Sources/SQLite/Typed/Query+with.swift index 005f490c..697e77fd 100644 --- a/Sources/SQLite/Typed/Query+with.swift +++ b/Sources/SQLite/Typed/Query+with.swift @@ -93,3 +93,17 @@ extension QueryType { ]) } } + +struct WithClauses { + struct Clause { + var alias: Table + var columns: [Expressible]? + var hint: MaterializationHint? + var query: QueryType + } + /// The `RECURSIVE` flag is applied to the entire `WITH` clause + var recursive: Bool = false + + /// Each `WITH` clause may have multiple subclauses + var clauses: [Clause] = [] +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index f688cfe6..b4f9f46f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1246,20 +1246,6 @@ public enum MaterializationHint: String { // MARK: - Private -struct WithClauses { - struct Clause { - var alias: Table - var columns: [Expressible]? - var hint: MaterializationHint? - var query: QueryType - } - /// The `RECURSIVE` flag is applied to the entire `WITH` clause - var recursive: Bool = false - - /// Each `WITH` clause may have multiple subclauses - var clauses: [Clause] = [] -} - public struct QueryClauses { var select = (distinct: false, columns: [Expression(literal: "*") as Expressible]) From 0dfc7b0452455b5ef0805939ed35cccd66f46429 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 15 Jul 2022 08:01:00 -0700 Subject: [PATCH 0870/1046] MaterializationHint > Query+with --- Sources/SQLite/Typed/Query+with.swift | 8 ++++++++ Sources/SQLite/Typed/Query.swift | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Typed/Query+with.swift b/Sources/SQLite/Typed/Query+with.swift index 697e77fd..d06c8896 100644 --- a/Sources/SQLite/Typed/Query+with.swift +++ b/Sources/SQLite/Typed/Query+with.swift @@ -94,6 +94,14 @@ extension QueryType { } } +/// Materialization hints for `WITH` clause +public enum MaterializationHint: String { + + case materialized = "MATERIALIZED" + + case notMaterialized = "NOT MATERIALIZED" +} + struct WithClauses { struct Clause { var alias: Table diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index b4f9f46f..cfa7544e 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1236,14 +1236,6 @@ public enum OnConflict: String { } -/// Materialization hints for `WITH` clause -public enum MaterializationHint: String { - - case materialized = "MATERIALIZED" - - case notMaterialized = "NOT MATERIALIZED" -} - // MARK: - Private public struct QueryClauses { From cfa6010717800bd3ed4bc28bb019223871188853 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 15 Jul 2022 19:28:56 -0700 Subject: [PATCH 0871/1046] Split test cases --- Tests/SQLiteTests/QueryTests.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index a96ff38c..eae1d923 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -61,6 +61,9 @@ class QueryTests: XCTestCase { func test_union_compilesUnionClause() { assertSQL("SELECT * FROM \"users\" UNION SELECT * FROM \"posts\"", users.union(posts)) + } + + func test_union_compilesUnionAllClause() { assertSQL("SELECT * FROM \"users\" UNION ALL SELECT * FROM \"posts\"", users.union(all: true, posts)) } @@ -232,21 +235,22 @@ class QueryTests: XCTestCase { temp.with(temp, as: users)) } - func test_with_recursive_compilesWithClause() { + func test_with_compilesWithRecursiveClause() { let temp = Table("temp") assertSQL("WITH RECURSIVE \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, recursive: true, as: users)) - - assertSQL("WITH \"temp\" AS (SELECT * FROM \"users\") SELECT * FROM \"temp\"", - temp.with(temp, recursive: false, as: users)) } - func test_with_materialization_compilesWithClause() { + func test_with_compilesWithMaterializedClause() { let temp = Table("temp") assertSQL("WITH \"temp\" AS MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, hint: .materialized, as: users)) + } + + func test_with_compilesWithNotMaterializedClause() { + let temp = Table("temp") assertSQL("WITH \"temp\" AS NOT MATERIALIZED (SELECT * FROM \"users\") SELECT * FROM \"temp\"", temp.with(temp, hint: .notMaterialized, as: users)) From ee905fe357157b6b48ed6641aa5abcf16a6ac354 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 16 Jul 2022 23:27:17 +0200 Subject: [PATCH 0872/1046] Add tests for NSURL conformance --- Tests/SQLiteTests/FoundationTests.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 075e755b..453febcd 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -26,4 +26,15 @@ class FoundationTests: XCTestCase { XCTAssertEqual(UUID(uuidString: "4ABE10C9-FF12-4CD4-90C1-4B429001BAD3"), uuid) } + func testURLFromString() { + let string = "http://foo.com" + let url = URL.fromDatatypeValue(string) + XCTAssertEqual(URL(string: string), url) + } + + func testStringFromURL() { + let url = URL(string: "http://foo.com")! + let string = url.datatypeValue + XCTAssertEqual("http://foo.com", string) + } } From 9d0c0b9f58c62099e767bf29a901879c3b8be668 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 15:49:56 +0200 Subject: [PATCH 0873/1046] Add support for ATTACH/DETACH (#30) --- Package.swift | 3 +- SQLite.xcodeproj/project.pbxproj | 28 ++ Sources/SQLite/Core/Connection+Attach.swift | 23 ++ Sources/SQLite/Core/Connection.swift | 16 +- Sources/SQLite/Core/URIQueryParameter.swift | 52 ++++ Tests/SQLiteTests/ConnectionTests.swift | 280 +++++++++++--------- Tests/SQLiteTests/Fixtures.swift | 12 +- Tests/SQLiteTests/ResultTests.swift | 52 ++++ Tests/SQLiteTests/TestHelpers.swift | 4 +- Tests/SQLiteTests/fixtures/test.sqlite | Bin 0 -> 12288 bytes 10 files changed, 330 insertions(+), 140 deletions(-) create mode 100644 Sources/SQLite/Core/Connection+Attach.swift create mode 100644 Sources/SQLite/Core/URIQueryParameter.swift create mode 100644 Tests/SQLiteTests/ResultTests.swift create mode 100644 Tests/SQLiteTests/fixtures/test.sqlite diff --git a/Package.swift b/Package.swift index 24898bca..1130d571 100644 --- a/Package.swift +++ b/Package.swift @@ -41,7 +41,8 @@ let package = Package( ], resources: [ .copy("fixtures/encrypted-3.x.sqlite"), - .copy("fixtures/encrypted-4.x.sqlite") + .copy("fixtures/encrypted-4.x.sqlite"), + .copy("fixtures/test.sqlite") ] ) ] diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index fc38e5e9..4e01067f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -126,6 +126,17 @@ 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; + 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; + 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; + 3DF7B791288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B794288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -263,6 +274,9 @@ 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; + 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; + 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; + 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -461,6 +475,7 @@ D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, + 3DF7B78C28842C23005DD8CA /* ResultTests.swift */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -479,6 +494,8 @@ 02A43A9722738CF100FEC494 /* Backup.swift */, 19A17E723300E5ED3771DCB5 /* Result.swift */, 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, + 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, + 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, ); path = Core; sourceTree = ""; @@ -847,12 +864,14 @@ 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */, 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, + 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */, 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, 03A65E7F1C6BB2FB0062603F /* Collation.swift in Sources */, 03A65E861C6BB2FB0062603F /* Setter.swift in Sources */, + 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */, 03A65E811C6BB2FB0062603F /* CustomFunctions.swift in Sources */, 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */, 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, @@ -877,6 +896,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */, 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */, 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, @@ -910,6 +930,7 @@ buildActionMask = 2147483647; files = ( 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, + 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, @@ -925,6 +946,7 @@ 3D67B3F11DB246D100A4F4C6 /* CustomFunctions.swift in Sources */, 3D67B3F21DB246D100A4F4C6 /* Expression.swift in Sources */, 3D67B3F31DB246D100A4F4C6 /* Operators.swift in Sources */, + 3DF7B794288449BA005DD8CA /* URIQueryParameter.swift in Sources */, 3D67B3F41DB246D100A4F4C6 /* Query.swift in Sources */, 3D67B3F51DB246D100A4F4C6 /* Schema.swift in Sources */, 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */, @@ -946,12 +968,14 @@ 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B0A1C3F06E900AE3E12 /* RTree.swift in Sources */, EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */, + 3DF7B791288449BA005DD8CA /* URIQueryParameter.swift in Sources */, EE247B0B1C3F06E900AE3E12 /* Foundation.swift in Sources */, EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */, EE247B111C3F06E900AE3E12 /* Expression.swift in Sources */, EE247B0C1C3F06E900AE3E12 /* Helpers.swift in Sources */, EE247B0E1C3F06E900AE3E12 /* Collation.swift in Sources */, EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */, + 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B101C3F06E900AE3E12 /* CustomFunctions.swift in Sources */, EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */, EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, @@ -976,6 +1000,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */, EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, @@ -1012,12 +1037,14 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */, EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, + 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */, EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */, EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */, EE247B6E1C3F3FEC00AE3E12 /* Collation.swift in Sources */, EE247B751C3F3FEC00AE3E12 /* Setter.swift in Sources */, + 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B701C3F3FEC00AE3E12 /* CustomFunctions.swift in Sources */, EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */, EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, @@ -1042,6 +1069,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */, EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift new file mode 100644 index 00000000..b9c08117 --- /dev/null +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -0,0 +1,23 @@ +import Foundation +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +extension Connection { + + /// See https://www3.sqlite.org/lang_attach.html + public func attach(_ location: Location, as schemaName: String) throws { + try run("ATTACH DATABASE ? AS ?", location.description, schemaName) + } + + /// See https://www3.sqlite.org/lang_detach.html + public func detach(_ schemaName: String) throws { + try run("DETACH DATABASE ?", schemaName) + } +} diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 72e8d857..60b9f1bd 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -55,7 +55,8 @@ public final class Connection { /// See: /// /// - Parameter filename: A URI filename - case uri(String) + /// - Parameter parameters: optional query parameters + case uri(String, parameters: [URIQueryParameter] = []) } /// An SQL operation passed to update callbacks. @@ -727,8 +728,17 @@ extension Connection.Location: CustomStringConvertible { return ":memory:" case .temporary: return "" - case .uri(let URI): - return URI + case let .uri(URI, parameters): + guard parameters.count > 0, + var components = URLComponents(string: URI) else { + return URI + } + components.queryItems = + (components.queryItems ?? []) + parameters.map(\.queryItem) + if components.scheme == nil { + components.scheme = "file" + } + return components.description } } diff --git a/Sources/SQLite/Core/URIQueryParameter.swift b/Sources/SQLite/Core/URIQueryParameter.swift new file mode 100644 index 00000000..c82a7cf7 --- /dev/null +++ b/Sources/SQLite/Core/URIQueryParameter.swift @@ -0,0 +1,52 @@ +import Foundation + +public enum URIQueryParameter: CustomStringConvertible { + public enum FileMode: String { + case readOnly = "ro", readWrite = "rw", readWriteCreate = "rwc", memory + } + + public enum CacheMode: String { + case shared, `private` + } + + /// The cache query parameter determines if the new database is opened using shared cache mode or with a private cache. + case cache(CacheMode) + + /// The immutable query parameter is a boolean that signals to SQLite that the underlying database file is held on read-only media + /// and cannot be modified, even by another process with elevated privileges. + case immutable(Bool) + + /// When creating a new database file during `sqlite3_open_v2()` on unix systems, SQLite will try to set the permissions of the new database + /// file to match the existing file "filename". + case modeOf(String) + + /// The mode query parameter determines if the new database is opened read-only, read-write, read-write and created if it does not exist, + /// or that the database is a pure in-memory database that never interacts with disk, respectively. + case mode(FileMode) + + /// The nolock query parameter is a boolean that disables all calls to the `xLock`, ` xUnlock`, and `xCheckReservedLock` methods + /// of the VFS when true. + case nolock(Bool) + + /// The psow query parameter overrides the `powersafe_overwrite` property of the database file being opened. + case psow(Bool) + + /// The vfs query parameter causes the database connection to be opened using the VFS called NAME. + case vfs(String) + + public var description: String { + queryItem.description + } + + var queryItem: URLQueryItem { + switch self { + case .cache(let mode): return .init(name: "cache", value: mode.rawValue) + case .immutable(let bool): return .init(name: "immutable", value: NSNumber(value: bool).description) + case .modeOf(let filename): return .init(name: "modeOf", value: filename) + case .mode(let fileMode): return .init(name: "mode", value: fileMode.rawValue) + case .nolock(let bool): return .init(name: "nolock", value: NSNumber(value: bool).description) + case .psow(let bool): return .init(name: "psow", value: NSNumber(value: bool).description) + case .vfs(let name): return .init(name: "vfs", value: name) + } + } +} diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index 136271a4..cf51c104 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -20,39 +20,49 @@ class ConnectionTests: SQLiteTestCase { try createUsersTable() } - func test_init_withInMemory_returnsInMemoryConnection() { - let db = try! Connection(.inMemory) + func test_init_withInMemory_returnsInMemoryConnection() throws { + let db = try Connection(.inMemory) XCTAssertEqual("", db.description) } - func test_init_returnsInMemoryByDefault() { - let db = try! Connection() + func test_init_returnsInMemoryByDefault() throws { + let db = try Connection() XCTAssertEqual("", db.description) } - func test_init_withTemporary_returnsTemporaryConnection() { - let db = try! Connection(.temporary) + func test_init_withTemporary_returnsTemporaryConnection() throws { + let db = try Connection(.temporary) XCTAssertEqual("", db.description) } - func test_init_withURI_returnsURIConnection() { - let db = try! Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + func test_init_withURI_returnsURIConnection() throws { + let db = try Connection(.uri("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) let url = URL(fileURLWithPath: db.description) XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } - func test_init_withString_returnsURIConnection() { - let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + func test_init_withString_returnsURIConnection() throws { + let db = try Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") let url = URL(fileURLWithPath: db.description) XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } + func testLocationWithoutUriParameters() { + let location: Connection.Location = .uri("foo") + XCTAssertEqual(location.description, "foo") + } + + func testLocationWithUriParameters() { + let location: Connection.Location = .uri("foo", parameters: [.mode(.readOnly), .cache(.private)]) + XCTAssertEqual(location.description, "file:foo?mode=ro&cache=private") + } + func test_readonly_returnsFalseOnReadWriteConnections() { XCTAssertFalse(db.readonly) } - func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = try! Connection(readonly: true) + func test_readonly_returnsTrueOnReadOnlyConnections() throws { + let db = try Connection(readonly: true) XCTAssertTrue(db.readonly) } @@ -60,14 +70,14 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(0, db.changes) } - func test_lastInsertRowid_returnsLastIdAfterInserts() { - try! insertUser("alice") + func test_lastInsertRowid_returnsLastIdAfterInserts() throws { + try insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) } - func test_lastInsertRowid_doesNotResetAfterError() { + func test_lastInsertRowid_doesNotResetAfterError() throws { XCTAssert(db.lastInsertRowid == 0) - try! insertUser("alice") + try insertUser("alice") XCTAssertEqual(1, db.lastInsertRowid) XCTAssertThrowsError( try db.run("INSERT INTO \"users\" (email, age, admin) values ('invalid@example.com', 12, 'invalid')") @@ -81,18 +91,18 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, db.lastInsertRowid) } - func test_changes_returnsNumberOfChanges() { - try! insertUser("alice") + func test_changes_returnsNumberOfChanges() throws { + try insertUser("alice") XCTAssertEqual(1, db.changes) - try! insertUser("betsy") + try insertUser("betsy") XCTAssertEqual(1, db.changes) } - func test_totalChanges_returnsTotalNumberOfChanges() { + func test_totalChanges_returnsTotalNumberOfChanges() throws { XCTAssertEqual(0, db.totalChanges) - try! insertUser("alice") + try insertUser("alice") XCTAssertEqual(1, db.totalChanges) - try! insertUser("betsy") + try insertUser("betsy") XCTAssertEqual(2, db.totalChanges) } @@ -101,53 +111,53 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.userVersion!) } - func test_prepare_preparesAndReturnsStatements() { - _ = try! db.prepare("SELECT * FROM users WHERE admin = 0") - _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", 0) - _ = try! db.prepare("SELECT * FROM users WHERE admin = ?", [0]) - _ = try! db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + func test_prepare_preparesAndReturnsStatements() throws { + _ = try db.prepare("SELECT * FROM users WHERE admin = 0") + _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) + _ = try db.prepare("SELECT * FROM users WHERE admin = ?", [0]) + _ = try db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) } - func test_run_preparesRunsAndReturnsStatements() { - try! db.run("SELECT * FROM users WHERE admin = 0") - try! db.run("SELECT * FROM users WHERE admin = ?", 0) - try! db.run("SELECT * FROM users WHERE admin = ?", [0]) - try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + func test_run_preparesRunsAndReturnsStatements() throws { + try db.run("SELECT * FROM users WHERE admin = 0") + try db.run("SELECT * FROM users WHERE admin = ?", 0) + try db.run("SELECT * FROM users WHERE admin = ?", [0]) + try db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) assertSQL("SELECT * FROM users WHERE admin = 0", 4) } - func test_vacuum() { - try! db.vacuum() + func test_vacuum() throws { + try db.vacuum() } - func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + func test_scalar_preparesRunsAndReturnsScalarValues() throws { + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) assertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) } - func test_execute_comment() { - try! db.run("-- this is a comment\nSELECT 1") + func test_execute_comment() throws { + try db.run("-- this is a comment\nSELECT 1") assertSQL("-- this is a comment", 0) assertSQL("SELECT 1", 0) } - func test_transaction_executesBeginDeferred() { - try! db.transaction(.deferred) {} + func test_transaction_executesBeginDeferred() throws { + try db.transaction(.deferred) {} assertSQL("BEGIN DEFERRED TRANSACTION") } - func test_transaction_executesBeginImmediate() { - try! db.transaction(.immediate) {} + func test_transaction_executesBeginImmediate() throws { + try db.transaction(.immediate) {} assertSQL("BEGIN IMMEDIATE TRANSACTION") } - func test_transaction_executesBeginExclusive() { - try! db.transaction(.exclusive) {} + func test_transaction_executesBeginExclusive() throws { + try db.transaction(.exclusive) {} assertSQL("BEGIN EXCLUSIVE TRANSACTION") } @@ -164,10 +174,10 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(users.map { $0[0] as? String }, ["alice@example.com", "betsy@example.com"]) } - func test_transaction_beginsAndCommitsTransactions() { - let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + func test_transaction_beginsAndCommitsTransactions() throws { + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") - try! db.transaction { + try db.transaction { try stmt.run() } @@ -177,8 +187,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("ROLLBACK TRANSACTION", 0) } - func test_transaction_rollsBackTransactionsIfCommitsFail() { - let sqliteVersion = String(describing: try! db.scalar("SELECT sqlite_version()")!) + func test_transaction_rollsBackTransactionsIfCommitsFail() throws { + let sqliteVersion = String(describing: try db.scalar("SELECT sqlite_version()")!) .split(separator: ".").compactMap { Int($0) } // PRAGMA defer_foreign_keys only supported in SQLite >= 3.8.0 guard sqliteVersion[0] == 3 && sqliteVersion[1] >= 8 else { @@ -187,9 +197,9 @@ class ConnectionTests: SQLiteTestCase { } // This test case needs to emulate an environment where the individual statements succeed, but committing the // transaction fails. Using deferred foreign keys is one option to achieve this. - try! db.execute("PRAGMA foreign_keys = ON;") - try! db.execute("PRAGMA defer_foreign_keys = ON;") - let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) + try db.execute("PRAGMA foreign_keys = ON;") + try db.execute("PRAGMA defer_foreign_keys = ON;") + let stmt = try db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) do { try db.transaction { @@ -209,14 +219,14 @@ class ConnectionTests: SQLiteTestCase { // Run another transaction to ensure that a subsequent transaction does not fail with an "cannot start a // transaction within a transaction" error. - let stmt2 = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") - try! db.transaction { + let stmt2 = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + try db.transaction { try stmt2.run() } } - func test_transaction_beginsAndRollsTransactionsBack() { - let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + func test_transaction_beginsAndRollsTransactionsBack() throws { + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { try db.transaction { @@ -232,8 +242,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("COMMIT TRANSACTION", 0) } - func test_savepoint_beginsAndCommitsSavepoints() { - try! db.savepoint("1") { + func test_savepoint_beginsAndCommitsSavepoints() throws { + try db.savepoint("1") { try db.savepoint("2") { try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") } @@ -248,8 +258,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("ROLLBACK TO SAVEPOINT '1'", 0) } - func test_savepoint_beginsAndRollsSavepointsBack() { - let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + func test_savepoint_beginsAndRollsSavepointsBack() throws { + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") do { try db.savepoint("1") { @@ -276,8 +286,8 @@ class ConnectionTests: SQLiteTestCase { assertSQL("RELEASE SAVEPOINT '1'", 0) } - func test_updateHook_setsUpdateHook_withInsert() { - async { done in + func test_updateHook_setsUpdateHook_withInsert() throws { + try async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.insert, operation) XCTAssertEqual("main", db) @@ -285,13 +295,13 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! insertUser("alice") + try insertUser("alice") } } - func test_updateHook_setsUpdateHook_withUpdate() { - try! insertUser("alice") - async { done in + func test_updateHook_setsUpdateHook_withUpdate() throws { + try insertUser("alice") + try async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.update, operation) XCTAssertEqual("main", db) @@ -299,13 +309,13 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! db.run("UPDATE users SET email = 'alice@example.com'") + try db.run("UPDATE users SET email = 'alice@example.com'") } } - func test_updateHook_setsUpdateHook_withDelete() { - try! insertUser("alice") - async { done in + func test_updateHook_setsUpdateHook_withDelete() throws { + try insertUser("alice") + try async { done in db.updateHook { operation, db, table, rowid in XCTAssertEqual(Connection.Operation.delete, operation) XCTAssertEqual("main", db) @@ -313,24 +323,24 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, rowid) done() } - try! db.run("DELETE FROM users WHERE id = 1") + try db.run("DELETE FROM users WHERE id = 1") } } - func test_commitHook_setsCommitHook() { - async { done in + func test_commitHook_setsCommitHook() throws { + try async { done in db.commitHook { done() } - try! db.transaction { + try db.transaction { try insertUser("alice") } - XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(1, try db.scalar("SELECT count(*) FROM users") as? Int64) } } - func test_rollbackHook_setsRollbackHook() { - async { done in + func test_rollbackHook_setsRollbackHook() throws { + try async { done in db.rollbackHook(done) do { try db.transaction { @@ -339,12 +349,12 @@ class ConnectionTests: SQLiteTestCase { } } catch { } - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users") as? Int64) } } - func test_commitHook_withRollback_rollsBack() { - async { done in + func test_commitHook_withRollback_rollsBack() throws { + try async { done in db.commitHook { throw NSError(domain: "com.stephencelis.SQLiteTests", code: 1, userInfo: nil) } @@ -355,41 +365,41 @@ class ConnectionTests: SQLiteTestCase { } } catch { } - XCTAssertEqual(0, try! db.scalar("SELECT count(*) FROM users") as? Int64) + XCTAssertEqual(0, try db.scalar("SELECT count(*) FROM users") as? Int64) } } // https://github.com/stephencelis/SQLite.swift/issues/1071 #if !os(Linux) - func test_createFunction_withArrayArguments() { + func test_createFunction_withArrayArguments() throws { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", try! db.scalar("SELECT hello('world')") as? String) - XCTAssert(try! db.scalar("SELECT hello(NULL)") == nil) + XCTAssertEqual("Hello, world!", try db.scalar("SELECT hello('world')") as? String) + XCTAssert(try db.scalar("SELECT hello(NULL)") == nil) } - func test_createFunction_createsQuotableFunction() { + func test_createFunction_createsQuotableFunction() throws { db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } - XCTAssertEqual("Hello, world!", try! db.scalar("SELECT \"hello world\"('world')") as? String) - XCTAssert(try! db.scalar("SELECT \"hello world\"(NULL)") == nil) + XCTAssertEqual("Hello, world!", try db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(try db.scalar("SELECT \"hello world\"(NULL)") == nil) } - func test_createCollation_createsCollation() { - try! db.createCollation("NODIACRITIC") { lhs, rhs in + func test_createCollation_createsCollation() throws { + try db.createCollation("NODIACRITIC") { lhs, rhs in lhs.compare(rhs, options: .diacriticInsensitive) } - XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + XCTAssertEqual(1, try db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) } - func test_createCollation_createsQuotableCollation() { - try! db.createCollation("NO DIACRITIC") { lhs, rhs in + func test_createCollation_createsQuotableCollation() throws { + try db.createCollation("NO DIACRITIC") { lhs, rhs in lhs.compare(rhs, options: .diacriticInsensitive) } - XCTAssertEqual(1, try! db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + XCTAssertEqual(1, try db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } - func test_interrupt_interruptsLongRunningQuery() { + func test_interrupt_interruptsLongRunningQuery() throws { let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { _ in DispatchQueue.global(qos: .background).async { @@ -399,7 +409,7 @@ class ConnectionTests: SQLiteTestCase { semaphore.wait() return nil } - let stmt = try! db.prepare("SELECT sleep()") + let stmt = try db.prepare("SELECT sleep()") XCTAssertThrowsError(try stmt.run()) { error in if case Result.error(_, let code, _) = error { XCTAssertEqual(code, SQLITE_INTERRUPT) @@ -410,13 +420,13 @@ class ConnectionTests: SQLiteTestCase { } #endif - func test_concurrent_access_single_connection() { + func test_concurrent_access_single_connection() throws { // test can fail on iOS/tvOS 9.x: SQLite compile-time differences? guard #available(iOS 10.0, OSX 10.10, tvOS 10.0, watchOS 2.2, *) else { return } - let conn = try! Connection("\(NSTemporaryDirectory())/\(UUID().uuidString)") - try! conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") - try! conn.run("INSERT INTO test(value) VALUES(?)", 0) + let conn = try Connection("\(NSTemporaryDirectory())/\(UUID().uuidString)") + try conn.execute("DROP TABLE IF EXISTS test; CREATE TABLE test(value);") + try conn.run("INSERT INTO test(value) VALUES(?)", 0) let queue = DispatchQueue(label: "Readers", attributes: [.concurrent]) let nReaders = 5 @@ -430,43 +440,51 @@ class ConnectionTests: SQLiteTestCase { } semaphores.forEach { $0.wait() } } -} -class ResultTests: XCTestCase { - let connection = try! Connection(.inMemory) + func test_attach_detach_memory_database() throws { + let schemaName = "test" - func test_init_with_ok_code_returns_nil() { - XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) - } + try db.attach(.inMemory, as: schemaName) - func test_init_with_row_code_returns_nil() { - XCTAssertNil(Result(errorCode: SQLITE_ROW, connection: connection, statement: nil) as Result?) - } + let table = Table("attached_users", database: schemaName) + let name = Expression("string") - func test_init_with_done_code_returns_nil() { - XCTAssertNil(Result(errorCode: SQLITE_DONE, connection: connection, statement: nil) as Result?) - } + // create a table, insert some data + try db.run(table.create { builder in + builder.column(name) + }) + _ = try db.run(table.insert(name <- "test")) - func test_init_with_other_code_returns_error() { - if case .some(.error(let message, let code, let statement)) = - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { - XCTAssertEqual("not an error", message) - XCTAssertEqual(SQLITE_MISUSE, code) - XCTAssertNil(statement) - XCTAssert(connection === connection) - } else { - XCTFail("no error") - } + // query data + let rows = try db.prepare(table.select(name)).map { $0[name] } + XCTAssertEqual(["test"], rows) + + try db.detach(schemaName) } - func test_description_contains_error_code() { - XCTAssertEqual("not an error (code: 21)", - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) + func test_attach_detach_file_database() throws { + let schemaName = "test" + let testDb = fixture("test", withExtension: "sqlite") + + try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) + + let table = Table("tests", database: schemaName) + let email = Expression("email") + + let rows = try db.prepare(table.select(email)).map { $0[email] } + XCTAssertEqual(["foo@bar.com"], rows) + + try db.detach(schemaName) } - func test_description_contains_statement_and_error_code() { - let statement = try! Statement(connection, "SELECT 1") - XCTAssertEqual("not an error (SELECT 1) (code: 21)", - Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) + func test_detach_invalid_schema_name_errors_with_no_such_database() throws { + XCTAssertThrowsError(try db.detach("no-exist")) { error in + if case let Result.error(message, code, _) = error { + XCTAssertEqual(code, SQLITE_ERROR) + XCTAssertEqual("no such database: no-exist", message) + } else { + XCTFail("unexpected error: \(error)") + } + } } } diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index d0683130..f3851cba 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -2,7 +2,13 @@ import Foundation func fixture(_ name: String, withExtension: String?) -> String { let testBundle = Bundle(for: SQLiteTestCase.self) - return testBundle.url( - forResource: name, - withExtension: withExtension)!.path + + for resource in [name, "fixtures/\(name)"] { + if let url = testBundle.url( + forResource: resource, + withExtension: withExtension) { + return url.path + } + } + fatalError("Cannot find \(name).\(withExtension ?? "")") } diff --git a/Tests/SQLiteTests/ResultTests.swift b/Tests/SQLiteTests/ResultTests.swift new file mode 100644 index 00000000..ccedcfe2 --- /dev/null +++ b/Tests/SQLiteTests/ResultTests.swift @@ -0,0 +1,52 @@ +import XCTest +import Foundation +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class ResultTests: XCTestCase { + let connection = try! Connection(.inMemory) + + func test_init_with_ok_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) + } + + func test_init_with_row_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_ROW, connection: connection, statement: nil) as Result?) + } + + func test_init_with_done_code_returns_nil() { + XCTAssertNil(Result(errorCode: SQLITE_DONE, connection: connection, statement: nil) as Result?) + } + + func test_init_with_other_code_returns_error() { + if case .some(.error(let message, let code, let statement)) = + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + XCTAssertEqual("not an error", message) + XCTAssertEqual(SQLITE_MISUSE, code) + XCTAssertNil(statement) + XCTAssert(connection === connection) + } else { + XCTFail("no error") + } + } + + func test_description_contains_error_code() { + XCTAssertEqual("not an error (code: 21)", + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) + } + + func test_description_contains_statement_and_error_code() { + let statement = try! Statement(connection, "SELECT 1") + XCTAssertEqual("not an error (SELECT 1) (code: 21)", + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) + } +} diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index f43310c9..6e8e9ebb 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -66,9 +66,9 @@ class SQLiteTestCase: XCTestCase { // if let count = trace[SQL] { trace[SQL] = count - 1 } // } - func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) -> Void) { + func async(expect description: String = "async", timeout: Double = 5, block: (@escaping () -> Void) throws -> Void) throws { let expectation = self.expectation(description: description) - block({ expectation.fulfill() }) + try block({ expectation.fulfill() }) waitForExpectations(timeout: timeout, handler: nil) } diff --git a/Tests/SQLiteTests/fixtures/test.sqlite b/Tests/SQLiteTests/fixtures/test.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..7b39b423d7afdd43d0d793bb0c87de0248b8910d GIT binary patch literal 12288 zcmeI$O-sWt7zgktUGWWOcgyvGT^x*o>@2KG&|f4x7qKs;6d&VC&-1iO*VVeItcy^& zk>bcb_Z2roC)t9%AX1kqM`Qr)@1Da90%SsRzud$X}f7K zu=eDc6qLO#()aaSZf^F8-c)7toit}8r%hAe Date: Sun, 17 Jul 2022 16:07:32 +0200 Subject: [PATCH 0874/1046] Remove force tries in tests --- Tests/SQLiteTests/CipherTests.swift | 52 +++++------ .../SQLiteTests/CustomAggregationTests.swift | 22 ++--- Tests/SQLiteTests/CustomFunctionsTests.swift | 90 +++++++++---------- Tests/SQLiteTests/FTS4Tests.swift | 10 +-- Tests/SQLiteTests/QueryIntegrationTests.swift | 76 ++++++++-------- Tests/SQLiteTests/QueryTests.swift | 12 +-- Tests/SQLiteTests/ResultTests.swift | 10 ++- Tests/SQLiteTests/RowTests.swift | 16 ++-- Tests/SQLiteTests/SelectTests.swift | 8 +- Tests/SQLiteTests/StatementTests.swift | 24 ++--- Tests/SQLiteTests/TestHelpers.swift | 4 +- 11 files changed, 164 insertions(+), 160 deletions(-) diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index 0995fca0..f25dbe5b 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -5,8 +5,8 @@ import SQLCipher class CipherTests: XCTestCase { - let db1 = try! Connection() - let db2 = try! Connection() + let db1 = try Connection() + let db2 = try Connection() override func setUpWithError() throws { // db @@ -26,41 +26,41 @@ class CipherTests: XCTestCase { try super.setUpWithError() } - func test_key() { - XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) + func test_key() throws { + XCTAssertEqual(1, try db1.scalar("SELECT count(*) FROM foo") as? Int64) } func test_key_blob_literal() { - let db = try! Connection() - try! db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") + let db = try Connection() + try db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") } - func test_rekey() { - try! db1.rekey("goodbye") - XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64) + func test_rekey() throws { + try db1.rekey("goodbye") + XCTAssertEqual(1, try db1.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_data_key() { - XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) + func test_data_key() throws { + XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_data_rekey() { + func test_data_rekey() throws { let newKey = keyData() - try! db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) - XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64) + try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) + XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_keyFailure() { + func test_keyFailure() throws { let path = "\(NSTemporaryDirectory())/db.sqlite3" _ = try? FileManager.default.removeItem(atPath: path) - let connA = try! Connection(path) - defer { try! FileManager.default.removeItem(atPath: path) } + let connA = try Connection(path) + defer { try FileManager.default.removeItem(atPath: path) } - try! connA.key("hello") - try! connA.run("CREATE TABLE foo (bar TEXT)") + try connA.key("hello") + try connA.run("CREATE TABLE foo (bar TEXT)") - let connB = try! Connection(path, readonly: true) + let connB = try Connection(path, readonly: true) do { try connB.key("world") @@ -72,7 +72,7 @@ class CipherTests: XCTestCase { } } - func test_open_db_encrypted_with_sqlcipher() { + func test_open_db_encrypted_with_sqlcipher() throws { // $ sqlcipher Tests/SQLiteTests/fixtures/encrypted-[version].x.sqlite // sqlite> pragma key = 'sqlcipher-test'; // sqlite> CREATE TABLE foo (bar TEXT); @@ -85,17 +85,17 @@ class CipherTests: XCTestCase { fixture("encrypted-3.x", withExtension: "sqlite") : fixture("encrypted-4.x", withExtension: "sqlite") - try! FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) + try FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) defer { // ensure file can be cleaned up afterwards - try! FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) + try FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) } - let conn = try! Connection(encryptedFile) - try! conn.key("sqlcipher-test") - XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64) + let conn = try Connection(encryptedFile) + try conn.key("sqlcipher-test") + XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) } private func keyData(length: Int = 64) -> NSMutableData { diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/CustomAggregationTests.swift index 5cbfbbef..71cbba9c 100644 --- a/Tests/SQLiteTests/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/CustomAggregationTests.swift @@ -25,7 +25,7 @@ class CustomAggregationTests: SQLiteTestCase { try insertUser("Eve", age: 28, admin: false) } - func testUnsafeCustomSum() { + func testUnsafeCustomSum() throws { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v @@ -43,7 +43,7 @@ class CustomAggregationTests: SQLiteTestCase { v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM1(age) AS s FROM users") + let result = try db.prepare("SELECT mySUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 @@ -51,7 +51,7 @@ class CustomAggregationTests: SQLiteTestCase { } } - func testUnsafeCustomSumGrouping() { + func testUnsafeCustomSumGrouping() throws { let step = { (bindings: [Binding?], state: UnsafeMutablePointer) in if let v = bindings[0] as? Int64 { state.pointee += v @@ -68,19 +68,19 @@ class CustomAggregationTests: SQLiteTestCase { v[0] = 0 return v.baseAddress! } - let result = try! db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") + let result = try db.prepare("SELECT mySUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([28, 55])) } - func testCustomSum() { + func testCustomSum() throws { let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } db.createAggregation("myReduceSUM1", initialValue: Int64(2000), reduce: reduce, result: { $0 }) - let result = try! db.prepare("SELECT myReduceSUM1(age) AS s FROM users") + let result = try db.prepare("SELECT myReduceSUM1(age) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { let value = row[i] as? Int64 @@ -88,26 +88,26 @@ class CustomAggregationTests: SQLiteTestCase { } } - func testCustomSumGrouping() { + func testCustomSumGrouping() throws { let reduce: (Int64, [Binding?]) -> Int64 = { (last, bindings) in let v = (bindings[0] as? Int64) ?? 0 return last + v } db.createAggregation("myReduceSUM2", initialValue: Int64(3000), reduce: reduce, result: { $0 }) - let result = try! db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") + let result = try db.prepare("SELECT myReduceSUM2(age) AS s FROM users GROUP BY admin ORDER BY s") let i = result.columnNames.firstIndex(of: "s")! let values = result.compactMap { $0[i] as? Int64 } XCTAssertTrue(values.elementsEqual([3028, 3055])) } - func testCustomStringAgg() { + func testCustomStringAgg() throws { let initial = String(repeating: " ", count: 64) let reduce: (String, [Binding?]) -> String = { (last, bindings) in let v = (bindings[0] as? String) ?? "" return last + v } db.createAggregation("myReduceSUM3", initialValue: initial, reduce: reduce, result: { $0 }) - let result = try! db.prepare("SELECT myReduceSUM3(email) AS s FROM users") + let result = try db.prepare("SELECT myReduceSUM3(email) AS s FROM users") let i = result.columnNames.firstIndex(of: "s")! for row in result { @@ -116,7 +116,7 @@ class CustomAggregationTests: SQLiteTestCase { } } - func testCustomObjectSum() { + func testCustomObjectSum() throws { { let initial = TestObject(value: 1000) let reduce: (TestObject, [Binding?]) -> TestObject = { (last, bindings) in diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/CustomFunctionsTests.swift index 3d897f8e..c4eb51e6 100644 --- a/Tests/SQLiteTests/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/CustomFunctionsTests.swift @@ -8,19 +8,19 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> Expression typealias FunctionResultOptional = () -> Expression - func testFunctionNoOptional() { - let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { + func testFunctionNoOptional() throws { + let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { "a" } - let result = try! db.prepare("SELECT test()").scalar() as! String + let result = try db.prepare("SELECT test()").scalar() as! String XCTAssertEqual("a", result) } - func testFunctionResultOptional() { - let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { + func testFunctionResultOptional() throws { + let _: FunctionResultOptional = try db.createFunction("test", deterministic: true) { "a" } - let result = try! db.prepare("SELECT test()").scalar() as! String? + let result = try db.prepare("SELECT test()").scalar() as! String? XCTAssertEqual("a", result) } } @@ -31,35 +31,35 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { typealias FunctionResultOptional = (Expression) -> Expression typealias FunctionLeftResultOptional = (Expression) -> Expression - func testFunctionNoOptional() { - let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a in + func testFunctionNoOptional() throws { + let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a in "b" + a } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } - func testFunctionLeftOptional() { - let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a in + func testFunctionLeftOptional() throws { + let _: FunctionLeftOptional = try db.createFunction("test", deterministic: true) { a in "b" + a! } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } - func testFunctionResultOptional() { - let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a in + func testFunctionResultOptional() throws { + let _: FunctionResultOptional = try db.createFunction("test", deterministic: true) { a in "b" + a } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } - func testFunctionLeftResultOptional() { - let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { (a: String?) -> String? in + func testFunctionLeftResultOptional() throws { + let _: FunctionLeftResultOptional = try db.createFunction("test", deterministic: true) { (a: String?) -> String? in "b" + a! } - let result = try! db.prepare("SELECT test(?)").scalar("a") as! String + let result = try db.prepare("SELECT test(?)").scalar("a") as! String XCTAssertEqual("ba", result) } } @@ -74,76 +74,76 @@ class CustomFunctionWithTwoArgsTests: SQLiteTestCase { typealias FunctionRightResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression - func testNoOptional() { - let _: FunctionNoOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testNoOptional() throws { + let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a, b in a + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testLeftOptional() { - let _: FunctionLeftOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testLeftOptional() throws { + let _: FunctionLeftOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testRightOptional() { - let _: FunctionRightOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testRightOptional() throws { + let _: FunctionRightOptional = try db.createFunction("test", deterministic: true) { a, b in a + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testResultOptional() { - let _: FunctionResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testResultOptional() throws { + let _: FunctionResultOptional = try db.createFunction("test", deterministic: true) { a, b in a + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } - func testFunctionLeftRightOptional() { - let _: FunctionLeftRightOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionLeftRightOptional() throws { + let _: FunctionLeftRightOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String XCTAssertEqual("ab", result) } - func testFunctionLeftResultOptional() { - let _: FunctionLeftResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionLeftResultOptional() throws { + let _: FunctionLeftResultOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } - func testFunctionRightResultOptional() { - let _: FunctionRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionRightResultOptional() throws { + let _: FunctionRightResultOptional = try db.createFunction("test", deterministic: true) { a, b in a + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } - func testFunctionLeftRightResultOptional() { - let _: FunctionLeftRightResultOptional = try! db.createFunction("test", deterministic: true) { a, b in + func testFunctionLeftRightResultOptional() throws { + let _: FunctionLeftRightResultOptional = try db.createFunction("test", deterministic: true) { a, b in a! + b! } - let result = try! db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? + let result = try db.prepare("SELECT test(?, ?)").scalar("a", "b") as! String? XCTAssertEqual("ab", result) } } class CustomFunctionTruncation: SQLiteTestCase { // https://github.com/stephencelis/SQLite.swift/issues/468 - func testStringTruncation() { - _ = try! db.createFunction("customLower") { (value: String) in value.lowercased() } - let result = try! db.prepare("SELECT customLower(?)").scalar("TÖL-AA 12") as? String + func testStringTruncation() throws { + _ = try db.createFunction("customLower") { (value: String) in value.lowercased() } + let result = try db.prepare("SELECT customLower(?)").scalar("TÖL-AA 12") as? String XCTAssertEqual("töl-aa 12", result) } } diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index d6385b0c..aa6ac41f 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -191,7 +191,7 @@ class FTS4ConfigTests: XCTestCase { class FTS4IntegrationTests: SQLiteTestCase { #if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER - func test_registerTokenizer_registersTokenizer() { + func test_registerTokenizer_registersTokenizer() throws { let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") @@ -200,7 +200,7 @@ class FTS4IntegrationTests: SQLiteTestCase { let tokenizerName = "tokenizer" let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) - try! db.registerTokenizer(tokenizerName) { string in + try db.registerTokenizer(tokenizerName) { string in CFStringTokenizerSetString(tokenizer, string as CFString, CFRangeMake(0, CFStringGetLength(string as CFString))) if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { @@ -214,14 +214,14 @@ class FTS4IntegrationTests: SQLiteTestCase { return (token as String, string.range(of: input as String)!) } - try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) + try db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) assertSQL(""" CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\") """.replacingOccurrences(of: "\n", with: "")) - try! _ = db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, try! db.scalar(emails.filter(emails.match("aun")).count)) + try _ = db.run(emails.insert(subject <- "Aún más cáfe!")) + XCTAssertEqual(1, try db.scalar(emails.filter(emails.match("aun")).count)) } #endif } diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/QueryIntegrationTests.swift index db4e2e4e..27b78c0a 100644 --- a/Tests/SQLiteTests/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/QueryIntegrationTests.swift @@ -23,45 +23,45 @@ class QueryIntegrationTests: SQLiteTestCase { // MARK: - - func test_select() { + func test_select() throws { let managerId = Expression("manager_id") let managers = users.alias("managers") - let alice = try! db.run(users.insert(email <- "alice@example.com")) - _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + let alice = try db.run(users.insert(email <- "alice@example.com")) + _ = try db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + for user in try db.prepare(users.join(managers, on: managers[id] == users[managerId])) { _ = user[users[managerId]] } } - func test_prepareRowIterator() { + func test_prepareRowIterator() throws { let names = ["a", "b", "c"] - try! insertUsers(names) + try insertUsers(names) let emailColumn = Expression("email") - let emails = try! db.prepareRowIterator(users).map { $0[emailColumn] } + let emails = try db.prepareRowIterator(users).map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } - func test_ambiguousMap() { + func test_ambiguousMap() throws { let names = ["a", "b", "c"] - try! insertUsers(names) + try insertUsers(names) - let emails = try! db.prepare("select email from users", []).map { $0[0] as! String } + let emails = try db.prepare("select email from users", []).map { $0[0] as! String } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } - func test_select_optional() { + func test_select_optional() throws { let managerId = Expression("manager_id") let managers = users.alias("managers") - let alice = try! db.run(users.insert(email <- "alice@example.com")) - _ = try! db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) + let alice = try db.run(users.insert(email <- "alice@example.com")) + _ = try db.run(users.insert(email <- "betsy@example.com", managerId <- alice)) - for user in try! db.prepare(users.join(managers, on: managers[id] == users[managerId])) { + for user in try db.prepare(users.join(managers, on: managers[id] == users[managerId])) { _ = user[users[managerId]] } } @@ -107,26 +107,26 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertNil(values[0].sub?.sub) } - func test_scalar() { - XCTAssertEqual(0, try! db.scalar(users.count)) - XCTAssertEqual(false, try! db.scalar(users.exists)) + func test_scalar() throws { + XCTAssertEqual(0, try db.scalar(users.count)) + XCTAssertEqual(false, try db.scalar(users.exists)) - try! insertUsers("alice") - XCTAssertEqual(1, try! db.scalar(users.select(id.average))) + try insertUsers("alice") + XCTAssertEqual(1, try db.scalar(users.select(id.average))) } - func test_pluck() { - let rowid = try! db.run(users.insert(email <- "alice@example.com")) - XCTAssertEqual(rowid, try! db.pluck(users)![id]) + func test_pluck() throws { + let rowid = try db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(rowid, try db.pluck(users)![id]) } - func test_insert() { - let id = try! db.run(users.insert(email <- "alice@example.com")) + func test_insert() throws { + let id = try db.run(users.insert(email <- "alice@example.com")) XCTAssertEqual(1, id) } - func test_insert_many() { - let id = try! db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) + func test_insert_many() throws { + let id = try db.run(users.insertMany([[email <- "alice@example.com"], [email <- "geoff@example.com"]])) XCTAssertEqual(2, id) } @@ -167,13 +167,13 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(42, try fetchAge()) } - func test_update() { - let changes = try! db.run(users.update(email <- "alice@example.com")) + func test_update() throws { + let changes = try db.run(users.update(email <- "alice@example.com")) XCTAssertEqual(0, changes) } - func test_delete() { - let changes = try! db.run(users.delete()) + func test_delete() throws { + let changes = try db.run(users.delete()) XCTAssertEqual(0, changes) } @@ -200,8 +200,8 @@ class QueryIntegrationTests: SQLiteTestCase { func test_no_such_column() throws { let doesNotExist = Expression("doesNotExist") - try! insertUser("alice") - let row = try! db.pluck(users.filter(email == "alice@example.com"))! + try insertUser("alice") + let row = try db.pluck(users.filter(email == "alice@example.com"))! XCTAssertThrowsError(try row.get(doesNotExist)) { error in if case QueryError.noSuchColumn(let name, _) = error { @@ -212,8 +212,8 @@ class QueryIntegrationTests: SQLiteTestCase { } } - func test_catchConstraintError() { - try! db.run(users.insert(email <- "alice@example.com")) + func test_catchConstraintError() throws { + try db.run(users.insert(email <- "alice@example.com")) do { try db.run(users.insert(email <- "alice@example.com")) XCTFail("expected error") @@ -231,19 +231,19 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(1, result.count) } - func test_with_recursive() { + func test_with_recursive() throws { let nodes = Table("nodes") let id = Expression("id") let parent = Expression("parent") let value = Expression("value") - try! db.run(nodes.create { builder in + try db.run(nodes.create { builder in builder.column(id) builder.column(parent) builder.column(value) }) - try! db.run(nodes.insertMany([ + try db.run(nodes.insertMany([ [id <- 0, parent <- nil, value <- 2], [id <- 1, parent <- 0, value <- 4], [id <- 2, parent <- 0, value <- 9], @@ -254,7 +254,7 @@ class QueryIntegrationTests: SQLiteTestCase { // Compute the sum of the values of node 5 and its ancestors let ancestors = Table("ancestors") - let sum = try! db.scalar( + let sum = try db.scalar( ancestors .select(value.sum) .with(ancestors, diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/QueryTests.swift index eae1d923..82a33808 100644 --- a/Tests/SQLiteTests/QueryTests.swift +++ b/Tests/SQLiteTests/QueryTests.swift @@ -375,25 +375,25 @@ class QueryTests: XCTestCase { } #endif - func test_insert_and_search_for_UUID() { + func test_insert_and_search_for_UUID() throws { struct Test: Codable { var uuid: UUID var string: String } let testUUID = UUID() let testValue = Test(uuid: testUUID, string: "value") - let db = try! Connection(.temporary) - try! db.run(table.create { t in + let db = try Connection(.temporary) + try db.run(table.create { t in t.column(uuid) t.column(string) } ) - let iQuery = try! table.insert(testValue) - try! db.run(iQuery) + let iQuery = try table.insert(testValue) + try db.run(iQuery) let fQuery = table.filter(uuid == testUUID) - if let result = try! db.pluck(fQuery) { + if let result = try db.pluck(fQuery) { let testValueReturned = Test(uuid: result[uuid], string: result[string]) XCTAssertEqual(testUUID, testValueReturned.uuid) } else { diff --git a/Tests/SQLiteTests/ResultTests.swift b/Tests/SQLiteTests/ResultTests.swift index ccedcfe2..fab4a0bb 100644 --- a/Tests/SQLiteTests/ResultTests.swift +++ b/Tests/SQLiteTests/ResultTests.swift @@ -13,7 +13,11 @@ import SQLite3 #endif class ResultTests: XCTestCase { - let connection = try! Connection(.inMemory) + var connection: Connection! + + override func setUpWithError() throws { + connection = try Connection(.inMemory) + } func test_init_with_ok_code_returns_nil() { XCTAssertNil(Result(errorCode: SQLITE_OK, connection: connection, statement: nil) as Result?) @@ -44,8 +48,8 @@ class ResultTests: XCTestCase { Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil)?.description) } - func test_description_contains_statement_and_error_code() { - let statement = try! Statement(connection, "SELECT 1") + func test_description_contains_statement_and_error_code() throws { + let statement = try Statement(connection, "SELECT 1") XCTAssertEqual("not an error (SELECT 1) (code: 21)", Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) } diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/RowTests.swift index 36721f80..506f6b10 100644 --- a/Tests/SQLiteTests/RowTests.swift +++ b/Tests/SQLiteTests/RowTests.swift @@ -3,9 +3,9 @@ import XCTest class RowTests: XCTestCase { - public func test_get_value() { + public func test_get_value() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertEqual("value", result) } @@ -17,9 +17,9 @@ class RowTests: XCTestCase { XCTAssertEqual("value", result) } - public func test_get_value_optional() { + public func test_get_value_optional() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertEqual("value", result) } @@ -31,9 +31,9 @@ class RowTests: XCTestCase { XCTAssertEqual("value", result) } - public func test_get_value_optional_nil() { + public func test_get_value_optional_nil() throws { let row = Row(["\"foo\"": 0], [nil]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertNil(result) } @@ -56,9 +56,9 @@ class RowTests: XCTestCase { } } - public func test_get_type_mismatch_optional_returns_nil() { + public func test_get_type_mismatch_optional_returns_nil() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try! row.get(Expression("foo")) + let result = try row.get(Expression("foo")) XCTAssertNil(result) } diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/SelectTests.swift index d1126b66..52d5bb6b 100644 --- a/Tests/SQLiteTests/SelectTests.swift +++ b/Tests/SQLiteTests/SelectTests.swift @@ -20,7 +20,7 @@ class SelectTests: SQLiteTestCase { ) } - func test_select_columns_from_multiple_tables() { + func test_select_columns_from_multiple_tables() throws { let usersData = Table("users_name") let users = Table("users") @@ -29,14 +29,14 @@ class SelectTests: SQLiteTestCase { let userID = Expression("user_id") let email = Expression("email") - try! insertUser("Joey") - try! db.run(usersData.insert( + try insertUser("Joey") + try db.run(usersData.insert( id <- 1, userID <- 1, name <- "Joey" )) - try! db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { + try db.prepare(users.select(name, email).join(usersData, on: userID == users[id])).forEach { XCTAssertEqual($0[name], "Joey") XCTAssertEqual($0[email], "Joey@example.com") } diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index aa05de34..3286b792 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -7,30 +7,30 @@ class StatementTests: SQLiteTestCase { try createUsersTable() } - func test_cursor_to_blob() { - try! insertUsers("alice") - let statement = try! db.prepare("SELECT email FROM users") - XCTAssert(try! statement.step()) + func test_cursor_to_blob() throws { + try insertUsers("alice") + let statement = try db.prepare("SELECT email FROM users") + XCTAssert(try statement.step()) let blob = statement.row[0] as Blob XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) } - func test_zero_sized_blob_returns_null() { + func test_zero_sized_blob_returns_null() throws { let blobs = Table("blobs") let blobColumn = Expression("blob_column") - try! db.run(blobs.create { $0.column(blobColumn) }) - try! db.run(blobs.insert(blobColumn <- Blob(bytes: []))) - let blobValue = try! db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) + try db.run(blobs.create { $0.column(blobColumn) }) + try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) + let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) XCTAssertEqual([], blobValue.bytes) } - func test_prepareRowIterator() { + func test_prepareRowIterator() throws { let names = ["a", "b", "c"] - try! insertUsers(names) + try insertUsers(names) let emailColumn = Expression("email") - let statement = try! db.prepare("SELECT email FROM users") - let emails = try! statement.prepareRowIterator().map { $0[emailColumn] } + let statement = try db.prepare("SELECT email FROM users") + let emails = try statement.prepareRowIterator().map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 6e8e9ebb..2a8c7e39 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -54,8 +54,8 @@ class SQLiteTestCase: XCTestCase { ) } - func assertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) { - try! statement.run() + func assertSQL(_ SQL: String, _ statement: Statement, _ message: String? = nil, file: StaticString = #file, line: UInt = #line) throws { + try statement.run() assertSQL(SQL, 1, message, file: file, line: line) if let count = trace[SQL] { trace[SQL] = count - 1 } } From 44383dfe5f4d9c4b07a5f921f153e051e82d32a4 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 16:21:17 +0200 Subject: [PATCH 0875/1046] Fix bundle lookup --- Tests/SQLiteTests/Fixtures.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index f3851cba..1306aa5a 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -1,7 +1,11 @@ import Foundation func fixture(_ name: String, withExtension: String?) -> String { + #if SWIFT_PACKAGE + let testBundle = Bundle.module + #else let testBundle = Bundle(for: SQLiteTestCase.self) + #endif for resource in [name, "fixtures/\(name)"] { if let url = testBundle.url( From 96b014df6095a7434d38950fb819f945d1222191 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 18:14:08 +0200 Subject: [PATCH 0876/1046] Always enable URI filenames https://sqlite.org/uri.html --- Sources/SQLite/Core/Connection.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 60b9f1bd..35edfce9 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -105,7 +105,10 @@ public final class Connection { /// - Returns: A new database connection. public init(_ location: Location = .inMemory, readonly: Bool = false) throws { let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + try check(sqlite3_open_v2(location.description, + &_handle, + flags | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI, + nil)) queue.setSpecific(key: Connection.queueKey, value: queueContext) } From 41ce3784a0a521aecb309bc7fbc24fa5017a3daf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 19:06:11 +0200 Subject: [PATCH 0877/1046] Move fixtures to more default `Resources` --- Package.swift | 19 ++++++++++-------- SQLite.swift.podspec | 6 +++--- SQLite.xcodeproj/project.pbxproj | 16 +++++++-------- Sources/SQLite/Core/Connection.swift | 2 +- Tests/SQLiteTests/CipherTests.swift | 15 +++++++------- Tests/SQLiteTests/ConnectionTests.swift | 9 +++++++-- Tests/SQLiteTests/Fixtures.swift | 2 +- .../encrypted-3.x.sqlite | Bin .../encrypted-4.x.sqlite | Bin .../{fixtures => Resources}/test.sqlite | Bin 10 files changed, 39 insertions(+), 30 deletions(-) rename Tests/SQLiteTests/{fixtures => Resources}/encrypted-3.x.sqlite (100%) rename Tests/SQLiteTests/{fixtures => Resources}/encrypted-4.x.sqlite (100%) rename Tests/SQLiteTests/{fixtures => Resources}/test.sqlite (100%) diff --git a/Package.swift b/Package.swift index 1130d571..b32fd881 100644 --- a/Package.swift +++ b/Package.swift @@ -40,9 +40,7 @@ let package = Package( "Info.plist" ], resources: [ - .copy("fixtures/encrypted-3.x.sqlite"), - .copy("fixtures/encrypted-4.x.sqlite"), - .copy("fixtures/test.sqlite") + .copy("Resources") ] ) ] @@ -56,10 +54,15 @@ package.targets = [ dependencies: [.product(name: "CSQLite", package: "CSQLite")], exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] ), - .testTarget(name: "SQLiteTests", dependencies: ["SQLite"], path: "Tests/SQLiteTests", exclude: [ - "FTSIntegrationTests.swift", - "FTS4Tests.swift", - "FTS5Tests.swift" - ]) + .testTarget( + name: "SQLiteTests", + dependencies: ["SQLite"], + path: "Tests/SQLiteTests", exclude: [ + "FTSIntegrationTests.swift", + "FTS4Tests.swift", + "FTS5Tests.swift" + ], + resources: [ .copy("Resources") ] + ) ] #endif diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4a1787bb..451b4886 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -35,7 +35,7 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.test_spec 'tests' do |test_spec| - test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' test_spec.ios.deployment_target = ios_deployment_target test_spec.tvos.deployment_target = tvos_deployment_target @@ -55,7 +55,7 @@ Pod::Spec.new do |s| ss.dependency 'sqlite3' ss.test_spec 'tests' do |test_spec| - test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' test_spec.ios.deployment_target = ios_deployment_target test_spec.tvos.deployment_target = tvos_deployment_target @@ -73,7 +73,7 @@ Pod::Spec.new do |s| ss.dependency 'SQLCipher', '>= 4.0.0' ss.test_spec 'tests' do |test_spec| - test_spec.resources = 'Tests/SQLiteTests/fixtures/*' + test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' test_spec.ios.deployment_target = ios_deployment_target test_spec.tvos.deployment_target = tvos_deployment_target diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 4e01067f..45e6ea63 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; - 19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; @@ -92,10 +91,8 @@ 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; - 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; - 19A17FDA323BAFDEC627E76F /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; }; 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; @@ -137,6 +134,9 @@ 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B794288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + 3DF7B79628846FCC005DD8CA /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; + 3DF7B79828846FED005DD8CA /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; + 3DF7B79928847055005DD8CA /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; 49EB68C41F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -267,7 +267,6 @@ 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; - 19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; @@ -277,6 +276,7 @@ 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; + 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -448,7 +448,7 @@ EE247AE11C3F04ED00AE3E12 /* SQLiteTests */ = { isa = PBXGroup; children = ( - 19A17E2695737FAB5D6086E3 /* fixtures */, + 3DF7B79528846FCC005DD8CA /* Resources */, EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, @@ -812,7 +812,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */, + 3DF7B79828846FED005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -834,7 +834,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 19A17FDA323BAFDEC627E76F /* fixtures in Resources */, + 3DF7B79628846FCC005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -849,7 +849,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 19A175DFF47B84757E547C62 /* fixtures in Resources */, + 3DF7B79928847055005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 35edfce9..68b83821 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -104,7 +104,7 @@ public final class Connection { /// /// - Returns: A new database connection. public init(_ location: Location = .inMemory, readonly: Bool = false) throws { - let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE + let flags = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE) try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI, diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index f25dbe5b..e2d09901 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -4,12 +4,13 @@ import SQLite import SQLCipher class CipherTests: XCTestCase { - - let db1 = try Connection() - let db2 = try Connection() + var db1: Connection! + var db2: Connection! override func setUpWithError() throws { - // db + db1 = try Connection() + db2 = try Connection() + // db1 try db1.key("hello") @@ -30,7 +31,7 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try db1.scalar("SELECT count(*) FROM foo") as? Int64) } - func test_key_blob_literal() { + func test_key_blob_literal() throws { let db = try Connection() try db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'") } @@ -55,7 +56,7 @@ class CipherTests: XCTestCase { _ = try? FileManager.default.removeItem(atPath: path) let connA = try Connection(path) - defer { try FileManager.default.removeItem(atPath: path) } + defer { try? FileManager.default.removeItem(atPath: path) } try connA.key("hello") try connA.run("CREATE TABLE foo (bar TEXT)") @@ -90,7 +91,7 @@ class CipherTests: XCTestCase { defer { // ensure file can be cleaned up afterwards - try FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) + try? FileManager.default.setAttributes([FileAttributeKey.immutable: 0], ofItemAtPath: encryptedFile) } let conn = try Connection(encryptedFile) diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/ConnectionTests.swift index cf51c104..347516f5 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/ConnectionTests.swift @@ -47,12 +47,17 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") } - func testLocationWithoutUriParameters() { + func test_init_with_Uri_and_Parameters() throws { + let testDb = fixture("test", withExtension: "sqlite") + _ = try Connection(.uri(testDb, parameters: [.cache(.shared)])) + } + + func test_location_without_Uri_parameters() { let location: Connection.Location = .uri("foo") XCTAssertEqual(location.description, "foo") } - func testLocationWithUriParameters() { + func test_location_with_Uri_parameters() { let location: Connection.Location = .uri("foo", parameters: [.mode(.readOnly), .cache(.private)]) XCTAssertEqual(location.description, "file:foo?mode=ro&cache=private") } diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index 1306aa5a..95b2b6dc 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -7,7 +7,7 @@ func fixture(_ name: String, withExtension: String?) -> String { let testBundle = Bundle(for: SQLiteTestCase.self) #endif - for resource in [name, "fixtures/\(name)"] { + for resource in [name, "Resources/\(name)"] { if let url = testBundle.url( forResource: resource, withExtension: withExtension) { diff --git a/Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite b/Tests/SQLiteTests/Resources/encrypted-3.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted-3.x.sqlite rename to Tests/SQLiteTests/Resources/encrypted-3.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite b/Tests/SQLiteTests/Resources/encrypted-4.x.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/encrypted-4.x.sqlite rename to Tests/SQLiteTests/Resources/encrypted-4.x.sqlite diff --git a/Tests/SQLiteTests/fixtures/test.sqlite b/Tests/SQLiteTests/Resources/test.sqlite similarity index 100% rename from Tests/SQLiteTests/fixtures/test.sqlite rename to Tests/SQLiteTests/Resources/test.sqlite From 9023fbc52ba86e0b002275698f071efa6eaeb899 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 00:44:53 +0200 Subject: [PATCH 0878/1046] Add documenation, update changelog --- CHANGELOG.md | 24 ++++++++++++++++ Documentation/Index.md | 32 ++++++++++++++++++++- SQLite.xcodeproj/project.pbxproj | 2 ++ Sources/SQLite/Core/URIQueryParameter.swift | 5 ++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 786084f8..210e324d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +0.14.0 (tbd), [diff][diff-0.14.0] +======================================== + +* Support ATTACH/DETACH ([#30][], [##1142][]) +* Support WITH clause ([#1139][]) +* Add value conformance for NSURL ([#1141][]) +* Add decoding for UUID ([#1137][]) +* Fix insertMany([Encodable]) ([#1130][], [#1138]][]) +* Fix incorrect spelling of 'remove_diacritics' ([#1128][]) +* Fix project build order ([#1131][]) +* Performance improvements ([#1109][], [#1115][], [#1132][]) + 0.13.3 (25-01-2022), [diff][diff-0.13.3] ======================================== @@ -110,7 +122,9 @@ [diff-0.13.1]: https://github.com/stephencelis/SQLite.swift/compare/0.13.0...0.13.1 [diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 [diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 +[diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 +[#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 [#426]: https://github.com/stephencelis/SQLite.swift/pull/426 @@ -150,6 +164,16 @@ [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 +[#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 [#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 +[#1115]: https://github.com/stephencelis/SQLite.swift/pull/1115 [#1119]: https://github.com/stephencelis/SQLite.swift/pull/1119 [#1121]: https://github.com/stephencelis/SQLite.swift/pull/1121 +[#1128]: https://github.com/stephencelis/SQLite.swift/issues/1128 +[#1130]: https://github.com/stephencelis/SQLite.swift/issues/1130 +[#1131]: https://github.com/stephencelis/SQLite.swift/pull/1131 +[#1132]: https://github.com/stephencelis/SQLite.swift/pull/1132 +[#1137]: https://github.com/stephencelis/SQLite.swift/pull/1137 +[#1138]: https://github.com/stephencelis/SQLite.swift/pull/1138 +[#1139]: https://github.com/stephencelis/SQLite.swift/pull/1139 +[#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 diff --git a/Documentation/Index.md b/Documentation/Index.md index 7a9a390f..aded5739 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -10,6 +10,7 @@ - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) - [In-Memory Databases](#in-memory-databases) + - [URI parameters](#uri-parameters) - [Thread-Safety](#thread-safety) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) @@ -61,9 +62,9 @@ - [Custom Collations](#custom-collations) - [Full-text Search](#full-text-search) - [Executing Arbitrary SQL](#executing-arbitrary-sql) + - [Attaching and detaching databases](#attaching-and-detaching-databases) - [Logging](#logging) - [↩]: #sqliteswift-documentation @@ -334,6 +335,16 @@ let db = try Connection(.temporary) In-memory databases are automatically deleted when the database connection is closed. +#### URI parameters + +We can pass `.uri` to the `Connection` initializer to control more aspects of +the database connection with the help of `URIQueryParameter`s: + +```swift +let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private)], .noLock(true)])) +``` + +See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html) for more details. #### Thread-Safety @@ -2070,6 +2081,25 @@ let backup = try db.backup(usingConnection: target) try backup.step() ``` +## Attaching and detaching databases + +We can [ATTACH](https://www3.sqlite.org/lang_attach.html) and [DETACH](https://www3.sqlite.org/lang_detach.html) +databases to an existing connection: + +```swift +let db = try Connection("db.sqlite") + +try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)), as: "external") +// ATTACH DATABASE 'file:external.sqlite?mode=ro' AS 'external' + +let table = Table("table", database: "external") +let count = try db.scalar(table.count) +// SELECT count(*) FROM 'external.table' + +try db.detach("external") +// DETACH DATABASE 'external' +``` + ## Logging We can log SQL using the database’s `trace` function. diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 45e6ea63..064357a2 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -277,6 +277,7 @@ 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; + 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -534,6 +535,7 @@ isa = PBXGroup; children = ( EE247B771C3F40D700AE3E12 /* README.md */, + 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, EE247B8D1C3F821200AE3E12 /* Makefile */, diff --git a/Sources/SQLite/Core/URIQueryParameter.swift b/Sources/SQLite/Core/URIQueryParameter.swift index c82a7cf7..abbab2e7 100644 --- a/Sources/SQLite/Core/URIQueryParameter.swift +++ b/Sources/SQLite/Core/URIQueryParameter.swift @@ -1,5 +1,6 @@ import Foundation +/// See https://www.sqlite.org/uri.html public enum URIQueryParameter: CustomStringConvertible { public enum FileMode: String { case readOnly = "ro", readWrite = "rw", readWriteCreate = "rwc", memory @@ -29,7 +30,7 @@ public enum URIQueryParameter: CustomStringConvertible { case nolock(Bool) /// The psow query parameter overrides the `powersafe_overwrite` property of the database file being opened. - case psow(Bool) + case powersafeOverwrite(Bool) /// The vfs query parameter causes the database connection to be opened using the VFS called NAME. case vfs(String) @@ -45,7 +46,7 @@ public enum URIQueryParameter: CustomStringConvertible { case .modeOf(let filename): return .init(name: "modeOf", value: filename) case .mode(let fileMode): return .init(name: "mode", value: fileMode.rawValue) case .nolock(let bool): return .init(name: "nolock", value: NSNumber(value: bool).description) - case .psow(let bool): return .init(name: "psow", value: NSNumber(value: bool).description) + case .powersafeOverwrite(let bool): return .init(name: "psow", value: NSNumber(value: bool).description) case .vfs(let name): return .init(name: "vfs", value: name) } } From 2eed2003e548630a3116a6d0fb850a341e3405a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 00:55:40 +0200 Subject: [PATCH 0879/1046] Fix doc errors --- CHANGELOG.md | 16 +++++++++------- Documentation/Index.md | 6 +++--- Documentation/Planning.md | 2 -- SQLite.xcodeproj/project.pbxproj | 2 ++ 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 210e324d..37e96955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,16 @@ 0.14.0 (tbd), [diff][diff-0.14.0] ======================================== -* Support ATTACH/DETACH ([#30][], [##1142][]) -* Support WITH clause ([#1139][]) -* Add value conformance for NSURL ([#1141][]) -* Add decoding for UUID ([#1137][]) -* Fix insertMany([Encodable]) ([#1130][], [#1138]][]) -* Fix incorrect spelling of 'remove_diacritics' ([#1128][]) +* Support `ATTACH`/`DETACH` ([#30][], [#1142][]) +* Support `WITH` clause ([#1139][]) +* Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) +* Add decoding for `UUID` ([#1137][]) +* Fix `insertMany([Encodable])` ([#1130][], [#1138][]) +* Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) * Performance improvements ([#1109][], [#1115][], [#1132][]) -0.13.3 (25-01-2022), [diff][diff-0.13.3] +0.13.3 (27-03-2022), [diff][diff-0.13.3] ======================================== * UUID Fix ([#1112][]) @@ -165,6 +165,7 @@ [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 +[#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110 [#1112]: https://github.com/stephencelis/SQLite.swift/pull/1112 [#1115]: https://github.com/stephencelis/SQLite.swift/pull/1115 [#1119]: https://github.com/stephencelis/SQLite.swift/pull/1119 @@ -176,4 +177,5 @@ [#1137]: https://github.com/stephencelis/SQLite.swift/pull/1137 [#1138]: https://github.com/stephencelis/SQLite.swift/pull/1138 [#1139]: https://github.com/stephencelis/SQLite.swift/pull/1139 +[#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141 [#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 diff --git a/Documentation/Index.md b/Documentation/Index.md index aded5739..f036520f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -341,10 +341,10 @@ We can pass `.uri` to the `Connection` initializer to control more aspects of the database connection with the help of `URIQueryParameter`s: ```swift -let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private)], .noLock(true)])) +let db = try Connection(.uri("file.sqlite", parameters: [.cache(.private), .noLock(true)])) ``` -See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html) for more details. +See [Uniform Resource Identifiers](https://www.sqlite.org/uri.html#recognized_query_parameters) for more details. #### Thread-Safety @@ -2089,7 +2089,7 @@ databases to an existing connection: ```swift let db = try Connection("db.sqlite") -try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)), as: "external") +try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)], as: "external") // ATTACH DATABASE 'file:external.sqlite?mode=ro' AS 'external' let table = Table("table", database: "external") diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 0dea6d71..6067b81e 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -21,8 +21,6 @@ be referred to when it comes time to add the corresponding feature._ ### Features - * encapsulate ATTACH DATABASE / DETACH DATABASE as methods, per - [#30](https://github.com/stephencelis/SQLite.swift/issues/30) * provide separate threads for update vs read, so updates don't block reads, per [#236](https://github.com/stephencelis/SQLite.swift/issues/236) * expose triggers, per diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 064357a2..45ff075f 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -278,6 +278,7 @@ 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 3DF7B79B2884C901005DD8CA /* Planning.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Planning.md; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -551,6 +552,7 @@ isa = PBXGroup; children = ( EE247B8F1C3F822500AE3E12 /* Index.md */, + 3DF7B79B2884C901005DD8CA /* Planning.md */, EE247B901C3F822500AE3E12 /* Resources */, 19A17EA3A313F129011B3FA0 /* Release.md */, 19A1794B7972D14330A65BBD /* Linux.md */, From 0af0e4d55e7571bc5a53bb10f12e0669fdf0470e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 01:03:57 +0200 Subject: [PATCH 0880/1046] More supported types --- Documentation/Index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index f036520f..0930aab0 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -383,6 +383,9 @@ to their [SQLite counterparts](https://www.sqlite.org/datatype3.html). | `String` | `TEXT` | | `nil` | `NULL` | | `SQLite.Blob`† | `BLOB` | +| `URL` | `TEXT` | +| `UUID` | `TEXT` | +| `Date` | `TEXT` | > *While `Int64` is the basic, raw type (to preserve 64-bit integers on > 32-bit platforms), `Int` and `Bool` work transparently. From 7868fdfcf606c2aaf18fb0471393234113b537a5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 18 Jul 2022 01:22:27 +0200 Subject: [PATCH 0881/1046] Fix doc --- Documentation/Index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 0930aab0..75c1d2cc 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1103,8 +1103,8 @@ users.limit(5, offset: 5) #### Recursive and Hierarchical Queries -We can perform a recursive or hierarchical query using a [query's](#queries) `with` -function. +We can perform a recursive or hierarchical query using a [query's](#queries) +[`WITH`](https://sqlite.org/lang_with.html) function. ```swift // Get the management chain for the manager with id == 8 @@ -2092,7 +2092,7 @@ databases to an existing connection: ```swift let db = try Connection("db.sqlite") -try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)], as: "external") +try db.attach(.uri("external.sqlite", parameters: [.mode(.readOnly)]), as: "external") // ATTACH DATABASE 'file:external.sqlite?mode=ro' AS 'external' let table = Table("table", database: "external") From be14b42fc71f3c3e8cd977d858ecfe593a168434 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 17 Jul 2022 12:45:04 +0200 Subject: [PATCH 0882/1046] Add sqlcipher_export --- CHANGELOG.md | 2 ++ Documentation/Index.md | 11 +++++++++-- Sources/SQLite/Core/Connection+Attach.swift | 12 ++++++++++- Sources/SQLite/Extensions/Cipher.swift | 22 +++++++++++++++++++-- Tests/SQLiteTests/CipherTests.swift | 9 +++++++++ Tests/SQLiteTests/Fixtures.swift | 4 ++++ 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37e96955..eb7213c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) +* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` * Fix `insertMany([Encodable])` ([#1130][], [#1138][]) * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) @@ -162,6 +163,7 @@ [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 +[#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 diff --git a/Documentation/Index.md b/Documentation/Index.md index 75c1d2cc..dd832ee2 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -184,9 +184,16 @@ extend `Connection` with methods to change the database key: ```swift import SQLite -let db = try Connection("path/to/db.sqlite3") +let db = try Connection("path/to/encrypted.sqlite3") try db.key("secret") -try db.rekey("another secret") +try db.rekey("new secret") // changes encryption key on already encrypted db +``` + +To encrypt an existing database: + +```swift +let db = try Connection("path/to/unencrypted.sqlite3") +try db.sqlcipher_export(.uri("encrypted.sqlite3"), key: "secret") ``` [CocoaPods]: https://cocoapods.org diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index b9c08117..47e6af5e 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -10,11 +10,21 @@ import SQLite3 #endif extension Connection { - + #if SQLITE_SWIFT_SQLCIPHER + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#attach + public func attach(_ location: Location, as schemaName: String, key: String? = nil) throws { + if let key = key { + try run("ATTACH DATABASE ? AS ? KEY ?", location.description, schemaName, key) + } else { + try run("ATTACH DATABASE ? AS ?", location.description, schemaName) + } + } + #else /// See https://www3.sqlite.org/lang_attach.html public func attach(_ location: Location, as schemaName: String) throws { try run("ATTACH DATABASE ? AS ?", location.description, schemaName) } + #endif /// See https://www3.sqlite.org/lang_detach.html public func detach(_ schemaName: String) throws { diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index b96c2761..8af04df9 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -6,6 +6,8 @@ import SQLCipher extension Connection { /// - Returns: the SQLCipher version + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_version public var cipherVersion: String? { (try? scalar("PRAGMA cipher_version")) as? String } @@ -23,6 +25,8 @@ extension Connection { /// of key data. /// e.g. x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99' /// @param db name of the database, defaults to 'main' + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlite3_key public func key(_ key: String, db: String = "main") throws { try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count) } @@ -39,6 +43,7 @@ extension Connection { /// As "PRAGMA cipher_migrate;" is time-consuming, it is recommended to use this function /// only after failure of `key(_ key: String, db: String = "main")`, if older versions of /// your app may ise older version of SQLCipher + /// /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_migrate /// and https://discuss.zetetic.net/t/upgrading-to-sqlcipher-4/3283 /// for more details regarding SQLCipher upgrade @@ -51,11 +56,13 @@ extension Connection { try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true) } - /// Change the key on an open database. If the current database is not encrypted, this routine - /// will encrypt it. + /// Change the key on an open database. NB: only works if the database is already encrypted. + /// /// To change the key on an existing encrypted database, it must first be unlocked with the /// current encryption key. Once the database is readable and writeable, rekey can be used /// to re-encrypt every page in the database with a new key. + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlite3_rekey public func rekey(_ key: String, db: String = "main") throws { try _rekey_v2(db: db, keyPointer: key, keySize: key.utf8.count) } @@ -64,6 +71,17 @@ extension Connection { try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } + /// Converts a non-encrypted database to an encrypted one. + /// + /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export + public func sqlcipher_export(_ location: Location, key: String) throws { + let schemaName = "cipher_export" + + try attach(location, as: schemaName, key: key) + try run("SELECT sqlcipher_export(?)", schemaName) + try detach(schemaName) + } + // MARK: - private private func _key_v2(db: String, keyPointer: UnsafePointer, diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/CipherTests.swift index e2d09901..cc43272e 100644 --- a/Tests/SQLiteTests/CipherTests.swift +++ b/Tests/SQLiteTests/CipherTests.swift @@ -99,6 +99,15 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) } + func test_export() throws { + let tmp = temporaryFile() + try db1.sqlcipher_export(.uri(tmp), key: "mykey") + + let conn = try Connection(tmp) + try conn.key("mykey") + XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) + } + private func keyData(length: Int = 64) -> NSMutableData { let keyData = NSMutableData(length: length)! let result = SecRandomCopyBytes(kSecRandomDefault, length, diff --git a/Tests/SQLiteTests/Fixtures.swift b/Tests/SQLiteTests/Fixtures.swift index 95b2b6dc..bd261d2c 100644 --- a/Tests/SQLiteTests/Fixtures.swift +++ b/Tests/SQLiteTests/Fixtures.swift @@ -16,3 +16,7 @@ func fixture(_ name: String, withExtension: String?) -> String { } fatalError("Cannot find \(name).\(withExtension ?? "")") } + +func temporaryFile() -> String { + URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString).path +} From ceb13aa1adc8b05fd5d8ccb29cf1af97c79ebd1d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 01:13:28 +0200 Subject: [PATCH 0883/1046] Document changes --- CHANGELOG.md | 3 ++- Documentation/Index.md | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb7213c1..68a5133f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) -* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` +* SQLCipher: improve documentation ([#1098][]), add `sqlcipher_export` ([#1101][]) * Fix `insertMany([Encodable])` ([#1130][], [#1138][]) * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) @@ -165,6 +165,7 @@ [#1095]: https://github.com/stephencelis/SQLite.swift/pull/1095 [#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 +[#1101]: https://github.com/stephencelis/SQLite.swift/issues/1101 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 [#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110 diff --git a/Documentation/Index.md b/Documentation/Index.md index dd832ee2..96fc1ee1 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -2110,6 +2110,13 @@ try db.detach("external") // DETACH DATABASE 'external' ``` +When compiled for SQLCipher, you can additionally pass a `key` parameter to `attach`: + +```swift +try db.attach(.uri("encrypted.sqlite"), as: "encrypted", key: "secret") +// ATTACH DATABASE 'encrypted.sqlite' AS 'encrypted' KEY 'secret' +``` + ## Logging We can log SQL using the database’s `trace` function. From 136c8a8c4358255fc921be5b1f3148acc514ee36 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 13:01:26 +0200 Subject: [PATCH 0884/1046] Remove FTS4 Tokenizer / SQLiteObjc Closes #1104 --- Package.swift | 11 +- SQLite.xcodeproj/project.pbxproj | 38 +----- Sources/SQLite/Extensions/FTS4.swift | 27 ---- Sources/SQLite/SQLite.h | 2 - Sources/SQLiteObjc/SQLiteObjc.m | 138 -------------------- Sources/SQLiteObjc/fts3_tokenizer.h | 161 ------------------------ Sources/SQLiteObjc/include/SQLiteObjc.h | 38 ------ Tests/SQLiteTests/FTS4Tests.swift | 37 ------ 8 files changed, 7 insertions(+), 445 deletions(-) delete mode 100644 Sources/SQLiteObjc/SQLiteObjc.m delete mode 100644 Sources/SQLiteObjc/fts3_tokenizer.h delete mode 100644 Sources/SQLiteObjc/include/SQLiteObjc.h diff --git a/Package.swift b/Package.swift index b32fd881..ab9cbd43 100644 --- a/Package.swift +++ b/Package.swift @@ -18,18 +18,10 @@ let package = Package( targets: [ .target( name: "SQLite", - dependencies: ["SQLiteObjc"], exclude: [ "Info.plist" ] ), - .target( - name: "SQLiteObjc", - dependencies: [], - exclude: [ - "fts3_tokenizer.h" - ] - ), .testTarget( name: "SQLiteTests", dependencies: [ @@ -57,7 +49,8 @@ package.targets = [ .testTarget( name: "SQLiteTests", dependencies: ["SQLite"], - path: "Tests/SQLiteTests", exclude: [ + path: "Tests/SQLiteTests", + exclude: [ "FTSIntegrationTests.swift", "FTS4Tests.swift", "FTS5Tests.swift" diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 45ff075f..3e67c03e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -17,8 +17,6 @@ 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; 03A65E761C6BB2E60062603F /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 03A65E771C6BB2E60062603F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; - 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; 03A65E7C1C6BB2F70062603F /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -116,13 +114,7 @@ 3D67B3F61DB246D100A4F4C6 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; - 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; @@ -152,8 +144,6 @@ EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; EE247B041C3F06E900AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; - EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B081C3F06E900AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -205,8 +195,6 @@ EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; EE247B661C3F3FEC00AE3E12 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; - EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */; }; - EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */; }; EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; EE247B6B1C3F3FEC00AE3E12 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; @@ -272,13 +260,13 @@ 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; - 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLiteObjc.h; path = ../SQLiteObjc/include/SQLiteObjc.h; sourceTree = ""; }; 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 3DF7B79B2884C901005DD8CA /* Planning.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Planning.md; sourceTree = ""; }; + 3DFC0B862886C239001C8FC9 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -290,8 +278,6 @@ EE247AE41C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EE247AEE1C3F06E900AE3E12 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; EE247AEF1C3F06E900AE3E12 /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fts3_tokenizer.h; path = ../../SQLiteObjc/fts3_tokenizer.h; sourceTree = ""; }; - EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SQLiteObjc.m; path = ../../SQLiteObjc/SQLiteObjc.m; sourceTree = ""; }; EE247AF21C3F06E900AE3E12 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; EE247AF31C3F06E900AE3E12 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; EE247AF51C3F06E900AE3E12 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; @@ -406,9 +392,10 @@ EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( + 3DFC0B862886C239001C8FC9 /* Package.swift */, + 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, - 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, 3D67B3E41DB2469200A4F4C6 /* Frameworks */, @@ -435,7 +422,6 @@ isa = PBXGroup; children = ( EE247AD61C3F04ED00AE3E12 /* SQLite.h */, - 3DDC112E26CDBA0200CE369F /* SQLiteObjc.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, @@ -488,8 +474,6 @@ children = ( EE247AEE1C3F06E900AE3E12 /* Blob.swift */, EE247AEF1C3F06E900AE3E12 /* Connection.swift */, - EE247AF01C3F06E900AE3E12 /* fts3_tokenizer.h */, - EE247AF11C3F06E900AE3E12 /* SQLiteObjc.m */, EE247AF21C3F06E900AE3E12 /* Statement.swift */, EE247AF31C3F06E900AE3E12 /* Value.swift */, 19A1710E73A46D5AC721CDA9 /* Errors.swift */, @@ -505,10 +489,10 @@ EE247AF41C3F06E900AE3E12 /* Extensions */ = { isa = PBXGroup; children = ( + 19A178A39ACA9667A62663CC /* Cipher.swift */, EE247AF51C3F06E900AE3E12 /* FTS4.swift */, - EE247AF61C3F06E900AE3E12 /* RTree.swift */, 19A1730E4390C775C25677D1 /* FTS5.swift */, - 19A178A39ACA9667A62663CC /* Cipher.swift */, + EE247AF61C3F06E900AE3E12 /* RTree.swift */, ); path = Extensions; sourceTree = ""; @@ -576,8 +560,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 03A65E781C6BB2EA0062603F /* fts3_tokenizer.h in Headers */, - 3DDC113626CDBE1900CE369F /* SQLiteObjc.h in Headers */, 03A65E721C6BB2D30062603F /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -587,8 +569,6 @@ buildActionMask = 2147483647; files = ( 3D67B3FC1DB2471B00A4F4C6 /* SQLite.h in Headers */, - 3DDC113726CDBE1900CE369F /* SQLiteObjc.h in Headers */, - 3D67B3FD1DB2472D00A4F4C6 /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -596,8 +576,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE247B051C3F06E900AE3E12 /* fts3_tokenizer.h in Headers */, - 3DDC113826CDBE1C00CE369F /* SQLiteObjc.h in Headers */, EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -606,8 +584,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EE247B671C3F3FEC00AE3E12 /* fts3_tokenizer.h in Headers */, - 3DDC112F26CDBA0200CE369F /* SQLiteObjc.h in Headers */, EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -869,7 +845,6 @@ 03A65E761C6BB2E60062603F /* Blob.swift in Sources */, 03A65E7D1C6BB2F70062603F /* RTree.swift in Sources */, 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */, - 03A65E791C6BB2EF0062603F /* SQLiteObjc.m in Sources */, 03A65E7B1C6BB2F70062603F /* Value.swift in Sources */, 03A65E821C6BB2FB0062603F /* Expression.swift in Sources */, 03A65E731C6BB2D80062603F /* Foundation.swift in Sources */, @@ -933,7 +908,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3D67B3F91DB246E700A4F4C6 /* SQLiteObjc.m in Sources */, 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */, 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */, 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, @@ -986,7 +960,6 @@ EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */, EE247B131C3F06E900AE3E12 /* Query.swift in Sources */, - EE247B061C3F06E900AE3E12 /* SQLiteObjc.m in Sources */, EE247B071C3F06E900AE3E12 /* Statement.swift in Sources */, EE247B0D1C3F06E900AE3E12 /* AggregateFunctions.swift in Sources */, 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */, @@ -1042,7 +1015,6 @@ EE247B651C3F3FEC00AE3E12 /* Blob.swift in Sources */, EE247B6C1C3F3FEC00AE3E12 /* RTree.swift in Sources */, 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */, - EE247B681C3F3FEC00AE3E12 /* SQLiteObjc.m in Sources */, EE247B6A1C3F3FEC00AE3E12 /* Value.swift in Sources */, EE247B711C3F3FEC00AE3E12 /* Expression.swift in Sources */, EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */, diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 52fec955..e9e65746 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -22,10 +22,6 @@ // THE SOFTWARE. // -#if SWIFT_PACKAGE -import SQLiteObjc -#endif - extension Module { public static func FTS4(_ column: Expressible, _ more: Expressible...) -> Module { @@ -149,29 +145,6 @@ extension Tokenizer: CustomStringConvertible { } -extension Connection { - - public func registerTokenizer(_ submoduleName: String, next: @escaping (String) -> (String, Range)?) throws { - try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { - (input: UnsafePointer, offset: UnsafeMutablePointer, length: UnsafeMutablePointer) in - let string = String(cString: input) - - guard let (token, range) = next(string) else { return nil } - - let view: String.UTF8View = string.utf8 - - if let from = range.lowerBound.samePosition(in: view), - let to = range.upperBound.samePosition(in: view) { - offset.pointee += Int32(string[string.startIndex..callback = [__SQLiteTokenizerMap objectForKey:key]; - if (!tokenizer->callback) { - return SQLITE_ERROR; - } - - *ppTokenizer = &tokenizer->base; - return SQLITE_OK; -} - -static int __SQLiteTokenizerDestroy(sqlite3_tokenizer * pTokenizer) { - sqlite3_free(pTokenizer); - return SQLITE_OK; -} - -static int __SQLiteTokenizerOpen(sqlite3_tokenizer * pTokenizer, const char * pInput, int nBytes, sqlite3_tokenizer_cursor ** ppCursor) { - __SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)sqlite3_malloc(sizeof(__SQLiteTokenizerCursor)); - if (!cursor) { - return SQLITE_NOMEM; - } - - cursor->input = pInput; - cursor->inputOffset = 0; - cursor->inputLength = 0; - cursor->idx = 0; - - *ppCursor = (sqlite3_tokenizer_cursor *)cursor; - return SQLITE_OK; -} - -static int __SQLiteTokenizerClose(sqlite3_tokenizer_cursor * pCursor) { - sqlite3_free(pCursor); - return SQLITE_OK; -} - -static int __SQLiteTokenizerNext(sqlite3_tokenizer_cursor * pCursor, const char ** ppToken, int * pnBytes, int * piStartOffset, int * piEndOffset, int * piPosition) { - __SQLiteTokenizerCursor * cursor = (__SQLiteTokenizerCursor *)pCursor; - __SQLiteTokenizer * tokenizer = (__SQLiteTokenizer *)cursor->base; - - cursor->inputOffset += cursor->inputLength; - const char * input = cursor->input + cursor->inputOffset; - const char * token = [tokenizer->callback(input, &cursor->inputOffset, &cursor->inputLength) cStringUsingEncoding:NSUTF8StringEncoding]; - if (!token) { - return SQLITE_DONE; - } - - *ppToken = token; - *pnBytes = (int)strlen(token); - *piStartOffset = cursor->inputOffset; - *piEndOffset = cursor->inputOffset + cursor->inputLength; - *piPosition = cursor->idx++; - return SQLITE_OK; -} - -static const sqlite3_tokenizer_module __SQLiteTokenizerModule = { - 0, - __SQLiteTokenizerCreate, - __SQLiteTokenizerDestroy, - __SQLiteTokenizerOpen, - __SQLiteTokenizerClose, - __SQLiteTokenizerNext -}; - -int _SQLiteRegisterTokenizer(sqlite3 *db, const char * moduleName, const char * submoduleName, _SQLiteTokenizerNextCallback callback) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __SQLiteTokenizerMap = [NSMutableDictionary new]; - }); - - sqlite3_stmt * stmt; - int status = sqlite3_prepare_v2(db, "SELECT fts3_tokenizer(?, ?)", -1, &stmt, 0); - if (status != SQLITE_OK ){ - return status; - } - const sqlite3_tokenizer_module * pModule = &__SQLiteTokenizerModule; - sqlite3_bind_text(stmt, 1, moduleName, -1, SQLITE_STATIC); - sqlite3_bind_blob(stmt, 2, &pModule, sizeof(pModule), SQLITE_STATIC); - sqlite3_step(stmt); - status = sqlite3_finalize(stmt); - if (status != SQLITE_OK ){ - return status; - } - - [__SQLiteTokenizerMap setObject:[callback copy] forKey:[NSString stringWithUTF8String:submoduleName]]; - - return SQLITE_OK; -} diff --git a/Sources/SQLiteObjc/fts3_tokenizer.h b/Sources/SQLiteObjc/fts3_tokenizer.h deleted file mode 100644 index d8a1e44b..00000000 --- a/Sources/SQLiteObjc/fts3_tokenizer.h +++ /dev/null @@ -1,161 +0,0 @@ -/* -** 2006 July 10 -** -** The author disclaims copyright to this source code. -** -************************************************************************* -** Defines the interface to tokenizers used by fulltext-search. There -** are three basic components: -** -** sqlite3_tokenizer_module is a singleton defining the tokenizer -** interface functions. This is essentially the class structure for -** tokenizers. -** -** sqlite3_tokenizer is used to define a particular tokenizer, perhaps -** including customization information defined at creation time. -** -** sqlite3_tokenizer_cursor is generated by a tokenizer to generate -** tokens from a particular input. -*/ -#ifndef _FTS3_TOKENIZER_H_ -#define _FTS3_TOKENIZER_H_ - -/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. -** If tokenizers are to be allowed to call sqlite3_*() functions, then -** we will need a way to register the API consistently. -*/ -#import "sqlite3.h" - -/* -** Structures used by the tokenizer interface. When a new tokenizer -** implementation is registered, the caller provides a pointer to -** an sqlite3_tokenizer_module containing pointers to the callback -** functions that make up an implementation. -** -** When an fts3 table is created, it passes any arguments passed to -** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the -** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer -** implementation. The xCreate() function in turn returns an -** sqlite3_tokenizer structure representing the specific tokenizer to -** be used for the fts3 table (customized by the tokenizer clause arguments). -** -** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() -** method is called. It returns an sqlite3_tokenizer_cursor object -** that may be used to tokenize a specific input buffer based on -** the tokenization rules supplied by a specific sqlite3_tokenizer -** object. -*/ -typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; -typedef struct sqlite3_tokenizer sqlite3_tokenizer; -typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; - -struct sqlite3_tokenizer_module { - - /* - ** Structure version. Should always be set to 0 or 1. - */ - int iVersion; - - /* - ** Create a new tokenizer. The values in the argv[] array are the - ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL - ** TABLE statement that created the fts3 table. For example, if - ** the following SQL is executed: - ** - ** CREATE .. USING fts3( ... , tokenizer arg1 arg2) - ** - ** then argc is set to 2, and the argv[] array contains pointers - ** to the strings "arg1" and "arg2". - ** - ** This method should return either SQLITE_OK (0), or an SQLite error - ** code. If SQLITE_OK is returned, then *ppTokenizer should be set - ** to point at the newly created tokenizer structure. The generic - ** sqlite3_tokenizer.pModule variable should not be initialized by - ** this callback. The caller will do so. - */ - int (*xCreate)( - int argc, /* Size of argv array */ - const char *const*argv, /* Tokenizer argument strings */ - sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ - ); - - /* - ** Destroy an existing tokenizer. The fts3 module calls this method - ** exactly once for each successful call to xCreate(). - */ - int (*xDestroy)(sqlite3_tokenizer *pTokenizer); - - /* - ** Create a tokenizer cursor to tokenize an input buffer. The caller - ** is responsible for ensuring that the input buffer remains valid - ** until the cursor is closed (using the xClose() method). - */ - int (*xOpen)( - sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ - const char *pInput, int nBytes, /* Input buffer */ - sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ - ); - - /* - ** Destroy an existing tokenizer cursor. The fts3 module calls this - ** method exactly once for each successful call to xOpen(). - */ - int (*xClose)(sqlite3_tokenizer_cursor *pCursor); - - /* - ** Retrieve the next token from the tokenizer cursor pCursor. This - ** method should either return SQLITE_OK and set the values of the - ** "OUT" variables identified below, or SQLITE_DONE to indicate that - ** the end of the buffer has been reached, or an SQLite error code. - ** - ** *ppToken should be set to point at a buffer containing the - ** normalized version of the token (i.e. after any case-folding and/or - ** stemming has been performed). *pnBytes should be set to the length - ** of this buffer in bytes. The input text that generated the token is - ** identified by the byte offsets returned in *piStartOffset and - ** *piEndOffset. *piStartOffset should be set to the index of the first - ** byte of the token in the input buffer. *piEndOffset should be set - ** to the index of the first byte just past the end of the token in - ** the input buffer. - ** - ** The buffer *ppToken is set to point at is managed by the tokenizer - ** implementation. It is only required to be valid until the next call - ** to xNext() or xClose(). - */ - /* TODO(shess) current implementation requires pInput to be - ** nul-terminated. This should either be fixed, or pInput/nBytes - ** should be converted to zInput. - */ - int (*xNext)( - sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ - const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ - int *piStartOffset, /* OUT: Byte offset of token in input buffer */ - int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ - int *piPosition /* OUT: Number of tokens returned before this one */ - ); - - /*********************************************************************** - ** Methods below this point are only available if iVersion>=1. - */ - - /* - ** Configure the language id of a tokenizer cursor. - */ - int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid); -}; - -struct sqlite3_tokenizer { - const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ - /* Tokenizer implementations will typically add additional fields */ -}; - -struct sqlite3_tokenizer_cursor { - sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ - /* Tokenizer implementations will typically add additional fields */ -}; - -int fts3_global_term_cnt(int iTerm, int iCol); -int fts3_term_cnt(int iTerm, int iCol); - - -#endif /* _FTS3_TOKENIZER_H_ */ diff --git a/Sources/SQLiteObjc/include/SQLiteObjc.h b/Sources/SQLiteObjc/include/SQLiteObjc.h deleted file mode 100644 index 610cdf10..00000000 --- a/Sources/SQLiteObjc/include/SQLiteObjc.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright © 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@import Foundation; -#if defined(SQLITE_SWIFT_STANDALONE) -@import sqlite3; -#elif defined(SQLITE_SWIFT_SQLCIPHER) -@import SQLCipher; -#else -@import SQLite3; -#endif - -NS_ASSUME_NONNULL_BEGIN -typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char *input, int *inputOffset, int *inputLength); -int _SQLiteRegisterTokenizer(sqlite3 *db, const char *module, const char *tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); -NS_ASSUME_NONNULL_END - diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/FTS4Tests.swift index aa6ac41f..5e595007 100644 --- a/Tests/SQLiteTests/FTS4Tests.swift +++ b/Tests/SQLiteTests/FTS4Tests.swift @@ -188,40 +188,3 @@ class FTS4ConfigTests: XCTestCase { virtualTable.create(.FTS4(config)) } } - -class FTS4IntegrationTests: SQLiteTestCase { -#if !SQLITE_SWIFT_STANDALONE && !SQLITE_SWIFT_SQLCIPHER - func test_registerTokenizer_registersTokenizer() throws { - let emails = VirtualTable("emails") - let subject = Expression("subject") - let body = Expression("body") - - let locale = CFLocaleCopyCurrent() - let tokenizerName = "tokenizer" - let tokenizer = CFStringTokenizerCreate(nil, "" as CFString, CFRangeMake(0, 0), - UInt(kCFStringTokenizerUnitWord), locale) - try db.registerTokenizer(tokenizerName) { string in - CFStringTokenizerSetString(tokenizer, string as CFString, - CFRangeMake(0, CFStringGetLength(string as CFString))) - if CFStringTokenizerAdvanceToNextToken(tokenizer).isEmpty { - return nil - } - let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) - let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string as CFString, range)! - let token = CFStringCreateMutableCopy(nil, range.length, input)! - CFStringLowercase(token, locale) - CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false) - return (token as String, string.range(of: input as String)!) - } - - try db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) - assertSQL(""" - CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", - tokenize=\"SQLite.swift\" \"tokenizer\") - """.replacingOccurrences(of: "\n", with: "")) - - try _ = db.run(emails.insert(subject <- "Aún más cáfe!")) - XCTAssertEqual(1, try db.scalar(emails.filter(emails.match("aun")).count)) - } -#endif -} From f63aca44e06e511dd0bdeeeb4b335b2aafbf36b1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 13:40:38 +0200 Subject: [PATCH 0885/1046] Fix podspec, update docs --- CHANGELOG.md | 3 +++ Documentation/Index.md | 2 -- Package.swift | 23 +++++------------------ SQLite.swift.podspec | 9 +++------ 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a5133f..71ce9858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) * Performance improvements ([#1109][], [#1115][], [#1132][]) +* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144[]]) 0.13.3 (27-03-2022), [diff][diff-0.13.3] ======================================== @@ -166,6 +167,7 @@ [#1098]: https://github.com/stephencelis/SQLite.swift/issues/1098 [#1100]: https://github.com/stephencelis/SQLite.swift/pull/1100 [#1101]: https://github.com/stephencelis/SQLite.swift/issues/1101 +[#1104]: https://github.com/stephencelis/SQLite.swift/issues/1104 [#1105]: https://github.com/stephencelis/SQLite.swift/pull/1105 [#1109]: https://github.com/stephencelis/SQLite.swift/issues/1109 [#1110]: https://github.com/stephencelis/SQLite.swift/pull/1110 @@ -182,3 +184,4 @@ [#1139]: https://github.com/stephencelis/SQLite.swift/pull/1139 [#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141 [#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 +[#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144 diff --git a/Documentation/Index.md b/Documentation/Index.md index 96fc1ee1..b71cdc4f 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1983,8 +1983,6 @@ try db.run(emails.create(.FTS5(config))) // the last FTS4 query above as: let replies = emails.filter(emails.match("subject:\"Re:\"*")) // SELECT * FROM "emails" WHERE "emails" MATCH 'subject:"Re:"*' - -// https://www.sqlite.org/fts5.html#_changes_to_select_statements_ ``` ## Executing Arbitrary SQL diff --git a/Package.swift b/Package.swift index ab9cbd43..9664e99a 100644 --- a/Package.swift +++ b/Package.swift @@ -39,23 +39,10 @@ let package = Package( ) #if os(Linux) -package.dependencies = [.package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3")] -package.targets = [ - .target( - name: "SQLite", - dependencies: [.product(name: "CSQLite", package: "CSQLite")], - exclude: ["Extensions/FTS4.swift", "Extensions/FTS5.swift"] - ), - .testTarget( - name: "SQLiteTests", - dependencies: ["SQLite"], - path: "Tests/SQLiteTests", - exclude: [ - "FTSIntegrationTests.swift", - "FTS4Tests.swift", - "FTS5Tests.swift" - ], - resources: [ .copy("Resources") ] - ) +package.dependencies = [ + .package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3") +] +package.targets.first?.dependencies += [ + .product(name: "CSQLite", package: "CSQLite") ] #endif diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 451b4886..d36d0330 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -29,9 +29,8 @@ Pod::Spec.new do |s| s.watchos.deployment_target = watchos_deployment_target s.subspec 'standard' do |ss| - ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.library = 'sqlite3' ss.test_spec 'tests' do |test_spec| @@ -44,9 +43,8 @@ Pod::Spec.new do |s| end s.subspec 'standalone' do |ss| - ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' - ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', @@ -64,8 +62,7 @@ Pod::Spec.new do |s| end s.subspec 'SQLCipher' do |ss| - ss.source_files = 'Sources/{SQLite,SQLiteObjc}/**/*.{c,h,m,swift}' - ss.private_header_files = 'Sources/SQLiteObjc/fts3_tokenizer.h' + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' From 8d9f74e919a75da41f19c47869a4577c10638a0c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 13:59:27 +0200 Subject: [PATCH 0886/1046] Works now --- CHANGELOG.md | 2 +- Documentation/Linux.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ce9858..7b3f78b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) * Performance improvements ([#1109][], [#1115][], [#1132][]) -* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144[]]) +* Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144][]) 0.13.3 (27-03-2022), [diff][diff-0.13.3] ======================================== diff --git a/Documentation/Linux.md b/Documentation/Linux.md index 640ced6f..0c88ff5c 100644 --- a/Documentation/Linux.md +++ b/Documentation/Linux.md @@ -2,9 +2,8 @@ ## Limitations -* Custom functions are currently not supported and crash, caused by a bug in Swift. +* Custom functions/aggregations are currently not supported and crash, caused by a bug in Swift. See [#1071](https://github.com/stephencelis/SQLite.swift/issues/1071). -* FTS5 might not work, see [#1007](https://github.com/stephencelis/SQLite.swift/issues/1007) ## Debugging From ef4854f0f742f1f52416aab88db75c6b686385ec Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 19 Jul 2022 14:29:59 +0200 Subject: [PATCH 0887/1046] Update mileston --- Documentation/Planning.md | 2 +- SQLite.xcodeproj/project.pbxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/Planning.md b/Documentation/Planning.md index 6067b81e..cdfca9c4 100644 --- a/Documentation/Planning.md +++ b/Documentation/Planning.md @@ -6,7 +6,7 @@ additions and Pull Requests, as well as to keep the Issues list clear of enhancement requests so that bugs are more visible. > ⚠ This document is currently not actively maintained. See -> the [0.13.4 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.13.4) +> the [0.14.1 milestone](https://github.com/stephencelis/SQLite.swift/issues?q=is%3Aopen+is%3Aissue+milestone%3A0.14.1) > on Github for additional information about planned features for the next release. ## Roadmap diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3e67c03e..2656ed6a 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -392,7 +392,6 @@ EE247AC91C3F04ED00AE3E12 = { isa = PBXGroup; children = ( - 3DFC0B862886C239001C8FC9 /* Package.swift */, 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, @@ -523,6 +522,7 @@ 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */, EE247B8B1C3F820300AE3E12 /* CONTRIBUTING.md */, EE247B931C3F826100AE3E12 /* SQLite.swift.podspec */, + 3DFC0B862886C239001C8FC9 /* Package.swift */, EE247B8D1C3F821200AE3E12 /* Makefile */, EE9180931C46EA210038162A /* libsqlite3.tbd */, EE9180911C46E9D30038162A /* libsqlite3.tbd */, From 4442ad0bbd06d2e843a5bb280cc96070aafec2c8 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Thu, 21 Jul 2022 15:01:04 -0500 Subject: [PATCH 0888/1046] Adding public method to reset a prepared statement. This is needed so that clients can reset any prepared statements they keep in memory, which allows for transactions to completely finish. without closing transactions (regardless of commit), the wal file cannot be checkpointed and will grow unbounded. --- Sources/SQLite/Core/Statement.swift | 6 ++- Tests/SQLiteTests/StatementTests.swift | 57 ++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 5ec430cf..7ba4026c 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -185,7 +185,11 @@ public final class Statement { try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW } } - fileprivate func reset(clearBindings shouldClear: Bool = true) { + public func reset() { + reset(clearBindings: true) + } + + fileprivate func reset(clearBindings shouldClear: Bool) { sqlite3_reset(handle) if shouldClear { sqlite3_clear_bindings(handle) } } diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 3286b792..77bc09c1 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -34,4 +34,61 @@ class StatementTests: SQLiteTestCase { XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) } + + /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint + func test_reset_statement() throws { + // Remove old test db if any + let path = "\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3" + try? FileManager.default.removeItem(atPath: path) + try? FileManager.default.removeItem(atPath: path + "-shm") + try? FileManager.default.removeItem(atPath: path + "-wal") + + // create new db on disk in wal mode + let db = try Connection(.uri(path)) + let url = URL(fileURLWithPath: db.description) + XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") + try db.run("PRAGMA journal_mode=WAL;") + + // create users table + try db.execute(""" + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + age INTEGER, + salary REAL, + admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), + manager_id INTEGER, + created_at DATETIME, + FOREIGN KEY(manager_id) REFERENCES users(id) + ) + """ + ) + + // insert single row + try db.run("INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + "alice@example.com", 1.datatypeValue, false.datatypeValue) + + // prepare a statement and read a single row. This will incremeent the cursor which + // prevents the implicit transaction from closing. + // https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions + let statement = try db.prepare("SELECT email FROM users") + XCTAssert(try statement.step()) + let blob = statement.row[0] as Blob + XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + + // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) + do { + try db.run("pragma wal_checkpoint(truncate)") + XCTFail("Database should be locked") + } catch { + // pass + } + + // reset the prepared statement, allowing the implicit transaction to close + statement.reset() + + // truncate succeeds + try db.run("pragma wal_checkpoint(truncate)") + } + } From dd3e813577b8b6ba363b12cff024dd633e9e29ab Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Fri, 22 Jul 2022 12:37:48 +0200 Subject: [PATCH 0889/1046] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..119ccc65 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [stephencelis, jberkel, NathanFallet] From ac1988c9663e8c5f64d21b255d91c154a30e2f35 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 14:12:11 -0500 Subject: [PATCH 0890/1046] Test cleanup for statement reset --- Tests/SQLiteTests/StatementTests.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 77bc09c1..c12fe8cb 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -38,15 +38,13 @@ class StatementTests: SQLiteTestCase { /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint func test_reset_statement() throws { // Remove old test db if any - let path = "\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3" + let path = temporaryFile() + ".sqlite3" try? FileManager.default.removeItem(atPath: path) try? FileManager.default.removeItem(atPath: path + "-shm") try? FileManager.default.removeItem(atPath: path + "-wal") // create new db on disk in wal mode let db = try Connection(.uri(path)) - let url = URL(fileURLWithPath: db.description) - XCTAssertEqual(url.lastPathComponent, "SQLite.swift Tests.sqlite3") try db.run("PRAGMA journal_mode=WAL;") // create users table From 012194b271dc32a0a9d1182edb4bea5201d8d59d Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 15:43:14 -0500 Subject: [PATCH 0891/1046] Adding documentation for Statement.reset(). --- Documentation/Index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index b71cdc4f..ed79f33e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1872,6 +1872,14 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ? for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` +> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either implicit or explicit) might be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. +> +> ```swift +> someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") +> for row in someObj.statement.bind(kUTTypeImage) { /* ... */ } +> someObj.statement.reset() +> ``` + [UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto ## Custom Aggregations From 58fd9dc1b6db7544135af10ced07f720e2dc36ac Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 15:43:42 -0500 Subject: [PATCH 0892/1046] Fix close of markdown code block --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index ed79f33e..2a877a71 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -828,7 +828,7 @@ let query = try db.prepare(users) for user in query { // 💥 can throw an error here } -```` +``` #### Failable iteration From 7b3de3d24b3eaea797a3db1b5a6373196edd4561 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 15:48:15 -0500 Subject: [PATCH 0893/1046] Add link to relevant sqlite.org documentation page --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 2a877a71..29b18c96 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1872,7 +1872,7 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ? for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` -> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either implicit or explicit) might be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. +> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either [implicit or explicit](https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions)) will be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. > > ```swift > someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") From 5a30f299c41f335a0745b0eab773db4f3ee184af Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 17:45:52 -0500 Subject: [PATCH 0894/1046] cleanup test for Statement.reset() --- Tests/SQLiteTests/StatementTests.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index c12fe8cb..b3048211 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -37,13 +37,8 @@ class StatementTests: SQLiteTestCase { /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint func test_reset_statement() throws { - // Remove old test db if any - let path = temporaryFile() + ".sqlite3" - try? FileManager.default.removeItem(atPath: path) - try? FileManager.default.removeItem(atPath: path + "-shm") - try? FileManager.default.removeItem(atPath: path + "-wal") - // create new db on disk in wal mode + let path = temporaryFile() + ".sqlite3" let db = try Connection(.uri(path)) try db.run("PRAGMA journal_mode=WAL;") @@ -70,9 +65,7 @@ class StatementTests: SQLiteTestCase { // prevents the implicit transaction from closing. // https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions let statement = try db.prepare("SELECT email FROM users") - XCTAssert(try statement.step()) - let blob = statement.row[0] as Blob - XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + _ = try statement.step() // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) do { From efa5fcb5c24352d266e9a87075dbfb3e1c5fd7ea Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 17:47:46 -0500 Subject: [PATCH 0895/1046] Better check for throwing error in test --- Tests/SQLiteTests/StatementTests.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index b3048211..e41fa714 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -68,12 +68,7 @@ class StatementTests: SQLiteTestCase { _ = try statement.step() // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) - do { - try db.run("pragma wal_checkpoint(truncate)") - XCTFail("Database should be locked") - } catch { - // pass - } + XCTAssertThrowsError(try db.run("pragma wal_checkpoint(truncate)")) // reset the prepared statement, allowing the implicit transaction to close statement.reset() From da0cd09219fe02d22c721637bc55d0ce600821ff Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Fri, 22 Jul 2022 19:25:52 -0500 Subject: [PATCH 0896/1046] Remove unneeded wal mode --- Tests/SQLiteTests/StatementTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index e41fa714..3cc68aca 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -40,7 +40,6 @@ class StatementTests: SQLiteTestCase { // create new db on disk in wal mode let path = temporaryFile() + ".sqlite3" let db = try Connection(.uri(path)) - try db.run("PRAGMA journal_mode=WAL;") // create users table try db.execute(""" From 210303f7b21f829bf82e17f41a8f912742ea2be3 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Sat, 23 Jul 2022 15:19:10 -0500 Subject: [PATCH 0897/1046] Use in memory database from test setup, verify error when statement isn't reset, update comments --- Tests/SQLiteTests/StatementTests.swift | 46 ++++++++++++-------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index 3cc68aca..bc100cd2 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -1,6 +1,16 @@ import XCTest import SQLite +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + class StatementTests: SQLiteTestCase { override func setUpWithError() throws { try super.setUpWithError() @@ -37,28 +47,8 @@ class StatementTests: SQLiteTestCase { /// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint func test_reset_statement() throws { - // create new db on disk in wal mode - let path = temporaryFile() + ".sqlite3" - let db = try Connection(.uri(path)) - - // create users table - try db.execute(""" - CREATE TABLE users ( - id INTEGER PRIMARY KEY, - email TEXT NOT NULL UNIQUE, - age INTEGER, - salary REAL, - admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), - manager_id INTEGER, - created_at DATETIME, - FOREIGN KEY(manager_id) REFERENCES users(id) - ) - """ - ) - // insert single row - try db.run("INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - "alice@example.com", 1.datatypeValue, false.datatypeValue) + try insertUsers("bob") // prepare a statement and read a single row. This will incremeent the cursor which // prevents the implicit transaction from closing. @@ -66,14 +56,20 @@ class StatementTests: SQLiteTestCase { let statement = try db.prepare("SELECT email FROM users") _ = try statement.step() - // verify that the transaction is not closed, which prevents wal_checkpoints (both explicit and auto) - XCTAssertThrowsError(try db.run("pragma wal_checkpoint(truncate)")) + // verify implicit transaction is not closed, and the users table is still locked + XCTAssertThrowsError(try db.run("DROP TABLE users")) { error in + if case let Result.error(_, code, _) = error { + XCTAssertEqual(code, SQLITE_LOCKED) + } else { + XCTFail("unexpected error") + } + } - // reset the prepared statement, allowing the implicit transaction to close + // reset the prepared statement, unlocking the table and allowing the implicit transaction to close statement.reset() // truncate succeeds - try db.run("pragma wal_checkpoint(truncate)") + try db.run("DROP TABLE users") } } From d970a11bd66747fd3b67d8ce072d944fca03b341 Mon Sep 17 00:00:00 2001 From: Adam Wulf Date: Sat, 23 Jul 2022 15:21:03 -0500 Subject: [PATCH 0898/1046] typo --- Tests/SQLiteTests/StatementTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/StatementTests.swift index bc100cd2..eeb513fe 100644 --- a/Tests/SQLiteTests/StatementTests.swift +++ b/Tests/SQLiteTests/StatementTests.swift @@ -50,7 +50,7 @@ class StatementTests: SQLiteTestCase { // insert single row try insertUsers("bob") - // prepare a statement and read a single row. This will incremeent the cursor which + // prepare a statement and read a single row. This will increment the cursor which // prevents the implicit transaction from closing. // https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions let statement = try db.prepare("SELECT email FROM users") From 5c66460cb97375d9cdc68364afe8e6db434f6cb9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 23 Jul 2022 22:21:59 +0200 Subject: [PATCH 0899/1046] Adds SchemaChanger to perform database schema changes --- SQLite.xcodeproj/project.pbxproj | 70 ++++ Sources/SQLite/Schema/Connection+Schema.swift | 145 ++++++++ Sources/SQLite/Schema/SchemaChanger.swift | 231 ++++++++++++ Sources/SQLite/Schema/SchemaDefinitions.swift | 336 +++++++++++++++++ .../Schema/ConnectionSchemaTests.swift | 139 +++++++ .../Schema/SchemaChangerTests.swift | 87 +++++ .../Schema/SchemaDefinitionsTests.swift | 342 ++++++++++++++++++ 7 files changed, 1350 insertions(+) create mode 100644 Sources/SQLite/Schema/Connection+Schema.swift create mode 100644 Sources/SQLite/Schema/SchemaChanger.swift create mode 100644 Sources/SQLite/Schema/SchemaDefinitions.swift create mode 100644 Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift create mode 100644 Tests/SQLiteTests/Schema/SchemaChangerTests.swift create mode 100644 Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 2656ed6a..abd69301 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -51,24 +51,33 @@ 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -78,19 +87,31 @@ 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; + 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; + 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; + 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; @@ -241,22 +262,28 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Schema.swift"; sourceTree = ""; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 19A171B262DDE8718513CFDA /* SchemaChanger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChanger.swift; sourceTree = ""; }; 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitions.swift; sourceTree = ""; }; 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Aggregation.swift"; sourceTree = ""; }; 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; + 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitionsTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; 19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; + 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionSchemaTests.swift; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChangerTests.swift; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; @@ -381,6 +408,26 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 19A1792D261C689FC988A90A /* Schema */ = { + isa = PBXGroup; + children = ( + 19A171B262DDE8718513CFDA /* SchemaChanger.swift */, + 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */, + 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */, + ); + path = Schema; + sourceTree = ""; + }; + 19A17B56FBA20E7245BC8AC0 /* Schema */ = { + isa = PBXGroup; + children = ( + 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */, + 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */, + 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */, + ); + path = Schema; + sourceTree = ""; + }; 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -427,6 +474,7 @@ EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, + 19A1792D261C689FC988A90A /* Schema */, ); name = SQLite; path = Sources/SQLite; @@ -463,6 +511,7 @@ 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, 3DF7B78C28842C23005DD8CA /* ResultTests.swift */, + 19A17B56FBA20E7245BC8AC0 /* Schema */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -868,6 +917,9 @@ 19A17073552293CA063BEA66 /* Result.swift in Sources */, 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, + 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */, + 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, + 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -901,6 +953,9 @@ D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */, + 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */, + 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */, + 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -935,6 +990,9 @@ 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */, + 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */, + 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */, + 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -970,6 +1028,9 @@ 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, + 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */, + 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, + 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1003,6 +1064,9 @@ D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */, + 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */, + 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */, + 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1038,6 +1102,9 @@ 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, + 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */, + 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, + 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1071,6 +1138,9 @@ D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */, + 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */, + 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */, + 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift new file mode 100644 index 00000000..e368bdaf --- /dev/null +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -0,0 +1,145 @@ +import Foundation + +extension Connection { + // Changing the foreign_keys setting affects the execution of all statements prepared using the database + // connection, including those prepared before the setting was changed. + // + // https://sqlite.org/pragma.html#pragma_foreign_keys + var foreignKeys: Bool { + get { getBoolPragma("foreign_keys") } + set { setBoolPragma("foreign_keys", newValue) } + } + + var deferForeignKeys: Bool { + get { getBoolPragma("defer_foreign_keys") } + set { setBoolPragma("defer_foreign_keys", newValue) } + } + + // https://sqlite.org/pragma.html#pragma_foreign_key_check + + // There are four columns in each result row. + // The first column is the name of the table that + // contains the REFERENCES clause. + // The second column is the rowid of the row that contains the + // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. + // The third column is the name of the table that is referred to. + // The fourth column is the index of the specific foreign key constraint that failed. + func foreignKeyCheck() throws -> [ForeignKeyError] { + try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in + guard let table = row[0] as? String, + let rowId = row[1] as? Int64, + let target = row[2] as? String else { return nil } + + return ForeignKeyError(from: table, rowId: rowId, to: target) + } + } + + // https://sqlite.org/pragma.html#pragma_table_info + // + // This pragma returns one row for each column in the named table. Columns in the result set include the + // column name, data type, whether or not the column can be NULL, and the default value for the column. The + // "pk" column in the result set is zero for columns that are not part of the primary key, and is the + // index of the column in the primary key for columns that are part of the primary key. + func columnInfo(table: String) throws -> [ColumnDefinition] { + func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { + try createTableSQL(name: table).flatMap { .init(sql: $0) } + } + + let foreignKeys: [String: [ForeignKeyDefinition]] = + Dictionary(grouping: try foreignKeyInfo(table: table), by: { $0.column }) + + return try run("PRAGMA table_info(\(table.quote()))").compactMap { row -> ColumnDefinition? in + guard let name = row[1] as? String, + let type = row[2] as? String, + let notNull = row[3] as? Int64, + let defaultValue = row[4] as? String?, + let primaryKey = row[5] as? Int64 else { return nil } + return ColumnDefinition(name: name, + primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, + type: ColumnDefinition.Affinity.from(type), + null: notNull == 0, + defaultValue: LiteralValue.from(defaultValue), + references: foreignKeys[name]?.first) + } + } + + func indexInfo(table: String) throws -> [IndexDefinition] { + func indexSQL(name: String) throws -> String? { + try run(""" + SELECT sql FROM sqlite_master WHERE name=? AND type='index' + UNION ALL + SELECT sql FROM sqlite_temp_master WHERE name=? AND type='index' + """, name, name) + .compactMap { row in row[0] as? String } + .first + } + + func columns(name: String) throws -> [String] { + try run("PRAGMA index_info(\(name.quote()))").compactMap { row in + row[2] as? String + } + } + + return try run("PRAGMA index_list(\(table.quote()))").compactMap { row -> IndexDefinition? in + guard let name = row[1] as? String, + let unique = row[2] as? Int64, + // Indexes SQLite creates implicitly for internal use start with "sqlite_". + // See https://www.sqlite.org/fileformat2.html#intschema + !name.starts(with: "sqlite_") else { + return nil + } + return .init(table: table, + name: name, + unique: unique == 1, + columns: try columns(name: name), + indexSQL: try indexSQL(name: name)) + } + } + + func tableInfo() throws -> [String] { + try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in + if let name = row[0] as? String, !name.starts(with: "sqlite_") { + return name + } else { + return nil + } + } + } + + func foreignKeyInfo(table: String) throws -> [ForeignKeyDefinition] { + try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in + if let table = row[2] as? String, // table + let column = row[3] as? String, // from + let primaryKey = row[4] as? String, // to + let onUpdate = row[5] as? String, + let onDelete = row[6] as? String { + return ForeignKeyDefinition(table: table, column: column, primaryKey: primaryKey, + onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, + onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete + ) + } else { + return nil + } + } + } + + private func createTableSQL(name: String) throws -> String? { + try run(""" + SELECT sql FROM sqlite_master WHERE name=? AND type='table' + UNION ALL + SELECT sql FROM sqlite_temp_master WHERE name=? AND type='table' + """, name, name) + .compactMap { row in row[0] as? String } + .first + } + + private func getBoolPragma(_ key: String) -> Bool { + guard let binding = try? scalar("PRAGMA \(key)"), + let intBinding = binding as? Int64 else { return false } + return intBinding == 1 + } + + private func setBoolPragma(_ key: String, _ newValue: Bool) { + _ = try? run("PRAGMA \(key) = \(newValue ? "1" : "0")") + } +} diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift new file mode 100644 index 00000000..ff0befc3 --- /dev/null +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -0,0 +1,231 @@ +import Foundation + +/* + https://www.sqlite.org/lang_altertable.html + + The only schema altering commands directly supported by SQLite are the "rename table" and "add column" + commands shown above. + + (SQLite 3.25.0: RENAME COLUMN) + (SQLite 3.35.0: DROP COLUMN) + + However, applications can make other arbitrary changes to the format of a table using a + simple sequence of operations. The steps to make arbitrary changes to the schema design of some table X are as follows: + + 1. If foreign key constraints are enabled, disable them using PRAGMA foreign_keys=OFF. + 2. Start a transaction. + 3. Remember the format of all indexes and triggers associated with table X + (SELECT sql FROM sqlite_master WHERE tbl_name='X' AND type='index') + 4. Use CREATE TABLE to construct a new table "new_X" that is in the desired revised format of table X. + 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. + 6. Drop the old table X: DROP TABLE X. + 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. + 8. Use CREATE INDEX and CREATE TRIGGER to reconstruct indexes and triggers associated with table X. + 9. If any views refer to table X in a way that is affected by the schema change, then drop those views using DROP VIEW + 10. If foreign key constraints were originally enabled then run PRAGMA foreign_key_check + 11. Commit the transaction started in step 2. + 12. If foreign keys constraints were originally enabled, reenable them now. +*/ +public class SchemaChanger: CustomStringConvertible { + enum SchemaChangeError: LocalizedError { + case foreignKeyError([ForeignKeyError]) + + var errorDescription: String? { + switch self { + case .foreignKeyError(let errors): + return "Foreign key errors: \(errors)" + } + } + } + + public enum Operation { + case none + case add(ColumnDefinition) + case remove(String) + case renameColumn(String, String) + case renameTable(String) + + /// Returns non-nil if the operation can be executed with a simple SQL statement + func toSQL(_ table: String) -> String? { + switch self { + case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + default: return nil + } + } + } + + public class AlterTableDefinition { + fileprivate var operations: [Operation] = [] + + let name: String + + init(name: String) { + self.name = name + } + + public func add(_ column: ColumnDefinition) { + operations.append(.add(column)) + } + + public func remove(_ column: String) { + operations.append(.remove(column)) + } + + public func rename(_ column: String, to: String) { + operations.append(.renameColumn(column, to)) + } + } + + private let connection: Connection + static let tempPrefix = "tmp_" + typealias Block = () throws -> Void + public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void + + struct Options: OptionSet { + let rawValue: Int + static let `default`: Options = [] + static let temp = Options(rawValue: 1) + } + + public init(connection: Connection) { + self.connection = connection + } + + public func alter(table: String, block: AlterTableDefinitionBlock) throws { + let alterTableDefinition = AlterTableDefinition(name: table) + block(alterTableDefinition) + + for operation in alterTableDefinition.operations { + try run(table: table, operation: operation) + } + } + + public func drop(table: String) throws { + try dropTable(table) + } + + private func run(table: String, operation: Operation) throws { + if let sql = operation.toSQL(table) { + try connection.run(sql) + } else { + try doTheTableDance(table: table, operation: operation) + } + } + + private func doTheTableDance(table: String, operation: Operation) throws { + try connection.transaction { + try disableRefIntegrity { + let tempTable = "\(SchemaChanger.tempPrefix)\(table)" + try moveTable(from: table, to: tempTable, options: [.temp], operation: operation) + try moveTable(from: tempTable, to: table) + let foreignKeyErrors = try connection.foreignKeyCheck() + if foreignKeyErrors.count > 0 { + throw SchemaChangeError.foreignKeyError(foreignKeyErrors) + } + } + } + } + + private func disableRefIntegrity(block: Block) throws { + let oldForeignKeys = connection.foreignKeys + let oldDeferForeignKeys = connection.deferForeignKeys + + connection.deferForeignKeys = true + connection.foreignKeys = false + + defer { + connection.deferForeignKeys = oldDeferForeignKeys + connection.foreignKeys = oldForeignKeys + } + + try block() + } + + private func moveTable(from: String, to: String, options: Options = .default, operation: Operation = .none) throws { + try copyTable(from: from, to: to, options: options, operation: operation) + try dropTable(from) + } + + private func copyTable(from: String, to: String, options: Options = .default, operation: Operation) throws { + let fromDefinition = TableDefinition( + name: from, + columns: try connection.columnInfo(table: from), + indexes: try connection.indexInfo(table: from) + ) + let toDefinition = fromDefinition.apply(.renameTable(to)).apply(operation) + + try createTable(definition: toDefinition, options: options) + try createTableIndexes(definition: toDefinition) + if case .remove = operation { + try copyTableContents(from: fromDefinition.apply(operation), to: toDefinition) + } else { + try copyTableContents(from: fromDefinition, to: toDefinition) + } + } + + private func createTable(definition: TableDefinition, options: Options) throws { + try connection.run(definition.toSQL(temporary: options.contains(.temp))) + } + + private func createTableIndexes(definition: TableDefinition) throws { + for index in definition.indexes { + try index.validate() + try connection.run(index.toSQL()) + } + } + + private func dropTable(_ table: String) throws { + try connection.run("DROP TABLE IF EXISTS \(table.quote())") + } + + private func copyTableContents(from: TableDefinition, to: TableDefinition) throws { + try connection.run(from.copySQL(to: to)) + } + + public var description: String { + "SQLiteSchemaChanger: \(connection.description)" + } +} + +extension IndexDefinition { + func renameTable(to: String) -> IndexDefinition { + func indexName() -> String { + if to.starts(with: SchemaChanger.tempPrefix) { + return "\(SchemaChanger.tempPrefix)\(name)" + } else if table.starts(with: SchemaChanger.tempPrefix) { + return name.replacingOccurrences(of: SchemaChanger.tempPrefix, with: "") + } else { + return name + } + } + return IndexDefinition(table: to, name: indexName(), unique: unique, columns: columns, where: `where`, orders: orders) + } + + func renameColumn(from: String, to: String) -> IndexDefinition { + IndexDefinition(table: table, name: name, unique: unique, columns: columns.map { + $0 == from ? to : $0 + }, where: `where`, orders: orders) + } +} + +extension TableDefinition { + func apply(_ operation: SchemaChanger.Operation) -> TableDefinition { + switch operation { + case .none: return self + case .add: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") + case .remove(let column): + return TableDefinition(name: name, + columns: columns.filter { $0.name != column }, + indexes: indexes.filter { !$0.columns.contains(column) } + ) + case .renameColumn(let from, let to): + return TableDefinition( + name: name, + columns: columns.map { $0.rename(from: from, to: to) }, + indexes: indexes.map { $0.renameColumn(from: from, to: to) } + ) + case .renameTable(let to): + return TableDefinition(name: to, columns: columns, indexes: indexes.map { $0.renameTable(to: to) }) + } + } +} diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift new file mode 100644 index 00000000..563ae331 --- /dev/null +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -0,0 +1,336 @@ +import Foundation + +struct TableDefinition: Equatable { + let name: String + let columns: [ColumnDefinition] + let indexes: [IndexDefinition] + + var quotedColumnList: String { + columns.map { $0.name.quote() }.joined(separator: ", ") + } +} + +// https://sqlite.org/syntax/column-def.html +// column-name -> type-name -> column-constraint* +public struct ColumnDefinition: Equatable { + + // The type affinity of a column is the recommended type for data stored in that column. + // The important idea here is that the type is recommended, not required. Any column can still + // store any type of data. It is just that some columns, given the choice, will prefer to use one + // storage class over another. The preferred storage class for a column is called its "affinity". + enum Affinity: String, CustomStringConvertible, CaseIterable { + case INTEGER + case NUMERIC + case REAL + case TEXT + case BLOB + + var description: String { + rawValue + } + + static func from(_ string: String) -> Affinity { + Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? TEXT + } + } + + enum OnConflict: String, CaseIterable { + case ROLLBACK + case ABORT + case FAIL + case IGNORE + case REPLACE + + static func from(_ string: String) -> OnConflict? { + OnConflict.allCases.first { $0.rawValue == string } + } + } + + struct PrimaryKey: Equatable { + let autoIncrement: Bool + let onConflict: OnConflict? + + // swiftlint:disable:next force_try + static let pattern = try! NSRegularExpression(pattern: "PRIMARY KEY\\s*(?:ASC|DESC)?\\s*(?:ON CONFLICT (\\w+)?)?\\s*(AUTOINCREMENT)?") + + init(autoIncrement: Bool = true, onConflict: OnConflict? = nil) { + self.autoIncrement = autoIncrement + self.onConflict = onConflict + } + + init?(sql: String) { + if let match = PrimaryKey.pattern.firstMatch(in: sql, range: NSRange(location: 0, length: sql.count)) { + let conflict = match.range(at: 1) + var onConflict: ColumnDefinition.OnConflict? + if conflict.location != NSNotFound { + onConflict = .from((sql as NSString).substring(with: conflict)) + } + let autoIncrement = match.range(at: 2).location != NSNotFound + self.init(autoIncrement: autoIncrement, onConflict: onConflict) + } else { + return nil + } + } + } + + let name: String + let primaryKey: PrimaryKey? + let type: Affinity + let null: Bool + let defaultValue: LiteralValue + let references: ForeignKeyDefinition? + + func rename(from: String, to: String) -> ColumnDefinition { + guard from == name else { return self } + return ColumnDefinition(name: to, primaryKey: primaryKey, type: type, null: null, defaultValue: defaultValue, references: references) + } +} + +enum LiteralValue: Equatable, CustomStringConvertible { + // swiftlint:disable force_try + private static let singleQuote = try! NSRegularExpression(pattern: "^'(.*)'$") + private static let doubleQuote = try! NSRegularExpression(pattern: "^\"(.*)\"$") + private static let blob = try! NSRegularExpression(pattern: "^[xX]\'(.*)\'$") + // swiftlint:enable force_try + + case numericLiteral(String) + case stringLiteral(String) + // BLOB literals are string literals containing hexadecimal data and preceded by a single "x" or "X" + // character. Example: X'53514C697465' + case blobLiteral(String) + + // If there is no explicit DEFAULT clause attached to a column definition, then the default value of the + // column is NULL + // swiftlint:disable identifier_name + case NULL + + // Beginning with SQLite 3.23.0 (2018-04-02), SQLite recognizes the identifiers "TRUE" and + // "FALSE" as boolean literals, if and only if those identifiers are not already used for some other + // meaning. + // + // The boolean identifiers TRUE and FALSE are usually just aliases for the integer values 1 and 0, respectively. + case TRUE + case FALSE + case CURRENT_TIME + case CURRENT_DATE + case CURRENT_TIMESTAMP + // swiftlint:enable identifier_name + + static func from(_ string: String?) -> LiteralValue { + func parse(_ value: String) -> LiteralValue { + if let match = singleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { + return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "''", with: "'")) + } else if let match = doubleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { + return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "\"\"", with: "\"")) + } else if let match = blob.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { + return blobLiteral((value as NSString).substring(with: match.range(at: 1))) + } else { + return numericLiteral(value) + } + } + guard let string = string else { return NULL } + + switch string { + case "NULL": return NULL + case "TRUE": return TRUE + case "FALSE": return FALSE + case "CURRENT_TIME": return CURRENT_TIME + case "CURRENT_TIMESTAMP": return CURRENT_TIMESTAMP + case "CURRENT_DATE": return CURRENT_DATE + default: return parse(string) + } + } + + var description: String { + switch self { + case .NULL: return "NULL" + case .TRUE: return "TRUE" + case .FALSE: return "FALSE" + case .CURRENT_TIMESTAMP: return "CURRENT_TIMESTAMP" + case .CURRENT_TIME: return "CURRENT_TIME" + case .CURRENT_DATE: return "CURRENT_DATE" + case .stringLiteral(let value): return value.quote("'") + case .blobLiteral(let value): return "X\(value.quote("'"))" + case .numericLiteral(let value): return value + } + } + + func map(block: (LiteralValue) -> U) -> U? { + if self == .NULL { + return nil + } else { + return block(self) + } + } +} + +// https://sqlite.org/lang_createindex.html +// schema-name.index-name ON table-name ( indexed-column+ ) WHERE expr +struct IndexDefinition: Equatable { + // SQLite supports index names up to 64 characters. + static let maxIndexLength = 64 + + // swiftlint:disable force_try + static let whereRe = try! NSRegularExpression(pattern: "\\sWHERE\\s+(.+)$") + static let orderRe = try! NSRegularExpression(pattern: "\"?(\\w+)\"? DESC") + // swiftlint:enable force_try + + enum Order: String { case ASC, DESC } + + init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { + self.table = table + self.name = name + self.unique = unique + self.columns = columns + self.where = `where` + self.orders = orders + } + + init (table: String, name: String, unique: Bool, columns: [String], indexSQL: String?) { + func wherePart(sql: String) -> String? { + IndexDefinition.whereRe.firstMatch(in: sql, options: [], range: NSRange(location: 0, length: sql.count)).map { + (sql as NSString).substring(with: $0.range(at: 1)) + } + } + + func orders(sql: String) -> [String: IndexDefinition.Order] { + IndexDefinition.orderRe.matches(in: sql, range: NSRange(location: 0, length: sql.count)) + .reduce([String: IndexDefinition.Order]()) { (memo, result) in + var memo2 = memo + let column = (sql as NSString).substring(with: result.range(at: 1)) + memo2[column] = .DESC + return memo2 + } + } + self.init(table: table, + name: name, + unique: unique, + columns: columns, + where: indexSQL.flatMap(wherePart), + orders: indexSQL.flatMap(orders)) + } + + let table: String + let name: String + let unique: Bool + let columns: [String] + let `where`: String? + let orders: [String: Order]? + + enum IndexError: LocalizedError { + case tooLong(String, String) + + var errorDescription: String? { + switch self { + case .tooLong(let name, let table): + return "Index name '\(name)' on table '\(table)' is too long; the limit is " + + "\(IndexDefinition.maxIndexLength) characters" + } + } + } + + func validate() throws { + if name.count > IndexDefinition.maxIndexLength { + throw IndexError.tooLong(name, table) + } + } +} + +struct ForeignKeyDefinition: Equatable { + let table: String + let column: String + let primaryKey: String + let onUpdate: String? + let onDelete: String? +} + +struct ForeignKeyError: CustomStringConvertible { + let from: String + let rowId: Int64 + let to: String + + var description: String { + "\(from) [\(rowId)] => \(to)" + } +} + +extension TableDefinition { + func toSQL(temporary: Bool = false) -> String { + assert(columns.count > 0, "no columns to create") + + return ([ + "CREATE", + temporary ? "TEMPORARY" : nil, + "TABLE", + name, + "(", + columns.map { $0.toSQL() }.joined(separator: ",\n"), + ")" + ] as [String?]).compactMap { $0 } + .joined(separator: " ") + } + + func copySQL(to: TableDefinition) -> String { + assert(columns.count > 0, "no columns to copy") + assert(columns.count == to.columns.count, "column counts don't match") + return "INSERT INTO \(to.name.quote()) (\(to.quotedColumnList)) SELECT \(quotedColumnList) FROM \(name.quote())" + } +} + +extension ColumnDefinition { + func toSQL() -> String { + [ + name.quote(), + type.rawValue, + defaultValue.map { "DEFAULT \($0)" }, + primaryKey.map { $0.toSQL() }, + null ? nil : "NOT NULL", + references.map { $0.toSQL() } + ].compactMap { $0 } + .joined(separator: " ") + } +} + +extension IndexDefinition { + func toSQL(ifNotExists: Bool = false) -> String { + let commaSeparatedColumns = columns.map { (column: String) -> String in + column.quote() + (orders?[column].map { " \($0.rawValue)" } ?? "") + }.joined(separator: ", ") + + return ([ + "CREATE", + unique ? "UNIQUE" : nil, + "INDEX", + ifNotExists ? "IF NOT EXISTS" : nil, + name.quote(), + "ON", + table.quote(), + "(\(commaSeparatedColumns))", + `where`.map { "WHERE \($0)" } + ] as [String?]).compactMap { $0 } + .joined(separator: " ") + } +} + +extension ForeignKeyDefinition { + func toSQL() -> String { + ([ + "REFERENCES", + table.quote(), + "(\(primaryKey.quote()))", + onUpdate.map { "ON UPDATE \($0)" }, + onDelete.map { "ON DELETE \($0)" } + ] as [String?]).compactMap { $0 } + .joined(separator: " ") + } +} + +extension ColumnDefinition.PrimaryKey { + func toSQL() -> String { + [ + "PRIMARY KEY", + autoIncrement ? "AUTOINCREMENT" : nil, + onConflict.map { "ON CONFLICT \($0.rawValue)" } + ].compactMap { $0 }.joined(separator: " ") + } +} diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift new file mode 100644 index 00000000..02321510 --- /dev/null +++ b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift @@ -0,0 +1,139 @@ +import XCTest +@testable import SQLite + +class ConnectionSchemaTests: SQLiteTestCase { + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + } + + func test_column_info() throws { + let columns = try db.columnInfo(table: "users") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: false, onConflict: nil), + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "email", + primaryKey: nil, + type: .TEXT, + null: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "salary", + primaryKey: nil, + type: .REAL, + null: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "admin", + primaryKey: nil, + type: .TEXT, + null: false, + defaultValue: .numericLiteral("0"), + references: nil), + ColumnDefinition(name: "manager_id", + primaryKey: nil, type: .INTEGER, + null: true, + defaultValue: .NULL, + references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), + ColumnDefinition(name: "created_at", + primaryKey: nil, + type: .TEXT, + null: true, + defaultValue: .NULL, + references: nil) + ]) + } + + func test_column_info_parses_conflict_modifier() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT)") + + XCTAssertEqual( + try db.columnInfo(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_column_info_detects_missing_autoincrement() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + XCTAssertEqual( + try db.columnInfo(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + null: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_index_info_no_index() throws { + let indexes = try db.indexInfo(table: "users") + XCTAssertTrue(indexes.isEmpty) + } + + func test_index_info_with_index() throws { + try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") + let indexes = try db.indexInfo(table: "users") + + XCTAssertEqual(indexes, [ + IndexDefinition( + table: "users", + name: "index_users", + unique: true, + columns: ["age"], + where: "age IS NOT NULL", + orders: ["age": .DESC] + ) + ]) + } + + func test_table_info_returns_list_of_tables() throws { + let tables = try db.tableInfo() + XCTAssertEqual(tables, ["users"]) + } + + func test_foreign_key_info_empty() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + let foreignKeys = try db.foreignKeyInfo(table: "t") + XCTAssertTrue(foreignKeys.isEmpty) + } + + func test_foreign_key_info() throws { + let linkTable = Table("test_links") + + let idColumn = SQLite.Expression("id") + let testIdColumn = SQLite.Expression("test_id") + + try db.run(linkTable.create(block: { definition in + definition.column(idColumn, primaryKey: .autoincrement) + definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) + })) + + let foreignKeys = try db.foreignKeyInfo(table: "test_links") + XCTAssertEqual(foreignKeys, [ + ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + ]) + } +} diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift new file mode 100644 index 00000000..21ae6e59 --- /dev/null +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -0,0 +1,87 @@ +import XCTest +@testable import SQLite + +class SchemaChangerTests: SQLiteTestCase { + var schemaChanger: SchemaChanger! + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + + try insertUsers("bob") + + schemaChanger = SchemaChanger(connection: db) + } + + func test_empty_migration_does_not_change_column_definitions() throws { + let previous = try db.columnInfo(table: "users") + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.columnInfo(table: "users") + + XCTAssertEqual(previous, current) + } + + func test_empty_migration_does_not_change_index_definitions() throws { + let previous = try db.indexInfo(table: "users") + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.indexInfo(table: "users") + + XCTAssertEqual(previous, current) + } + + func test_empty_migration_does_not_change_foreign_key_definitions() throws { + let previous = try db.foreignKeyInfo(table: "users") + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.foreignKeyInfo(table: "users") + + XCTAssertEqual(previous, current) + } + + func test_empty_migration_does_not_change_the_row_count() throws { + let previous = try db.scalar(users.count) + try schemaChanger.alter(table: "users") { _ in + } + let current = try db.scalar(users.count) + + XCTAssertEqual(previous, current) + } + + func test_remove_column() throws { + try schemaChanger.alter(table: "users") { table in + table.remove("age") + } + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + } + + func test_rename_column() throws { + try schemaChanger.alter(table: "users") { table in + table.rename("age", to: "age2") + } + + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + XCTAssertTrue(columns.contains("age2")) + } + + func test_add_column() throws { + let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil) + + try schemaChanger.alter(table: "users") { table in + table.add(newColumn) + } + + let columns = try db.columnInfo(table: "users") + XCTAssertTrue(columns.contains(newColumn)) + } + + func test_drop_table() throws { + try schemaChanger.drop(table: "users") + + let tables = try db.tableInfo() + XCTAssertFalse(tables.contains("users")) + } +} diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift new file mode 100644 index 00000000..07fbf371 --- /dev/null +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -0,0 +1,342 @@ +import XCTest +@testable import SQLite + +class ColumnDefinitionTests: XCTestCase { + var definition: ColumnDefinition! + var expected: String! + + static let definitions: [(ColumnDefinition, String)] = [ + (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), + "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), + + (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, + references: ForeignKeyDefinition(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), + "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), + + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil), + "\"text\" TEXT"), + + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: false, defaultValue: .NULL, references: nil), + "\"text\" TEXT NOT NULL"), + + (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .stringLiteral("fo\"o"), references: nil), + "\"text_column\" TEXT DEFAULT 'fo\"o'"), + + (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, null: true, defaultValue: .numericLiteral("123"), references: nil), + "\"integer_column\" INTEGER DEFAULT 123"), + + (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, null: true, defaultValue: .numericLiteral("123.123"), references: nil), + "\"real_column\" REAL DEFAULT 123.123") + ] + + override class var defaultTestSuite: XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) + + for (column, expected) in ColumnDefinitionTests.definitions { + let test = ColumnDefinitionTests(selector: #selector(verify)) + test.definition = column + test.expected = expected + suite.addTest(test) + } + return suite + } + + @objc func verify() { + XCTAssertEqual(definition.toSQL(), expected) + } +} + +class AffinityTests: XCTestCase { + func test_from() { + XCTAssertEqual(ColumnDefinition.Affinity.from("TEXT"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity.from("text"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity.from("INTEGER"), .INTEGER) + } + + func test_returns_TEXT_for_unknown_type() { + XCTAssertEqual(ColumnDefinition.Affinity.from("baz"), .TEXT) + } +} + +class IndexDefinitionTests: XCTestCase { + var definition: IndexDefinition! + var expected: String! + var ifNotExists: Bool! + + static let definitions: [(IndexDefinition, Bool, String)] = [ + (IndexDefinition(table: "tests", name: "index_tests", + unique: false, + columns: ["test_column"], + where: nil, + orders: nil), + false, + "CREATE INDEX \"index_tests\" ON \"tests\" (\"test_column\")"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: true, + columns: ["test_column"], + where: nil, + orders: nil), + false, + "CREATE UNIQUE INDEX \"index_tests\" ON \"tests\" (\"test_column\")"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: true, + columns: ["test_column", "bar_column"], + where: "test_column IS NOT NULL", + orders: nil), + false, + "CREATE UNIQUE INDEX \"index_tests\" ON \"tests\" (\"test_column\", \"bar_column\") WHERE test_column IS NOT NULL"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: true, + columns: ["test_column", "bar_column"], + where: nil, + orders: ["test_column": .DESC]), + false, + "CREATE UNIQUE INDEX \"index_tests\" ON \"tests\" (\"test_column\" DESC, \"bar_column\")"), + + (IndexDefinition(table: "tests", name: "index_tests", + unique: false, + columns: ["test_column"], + where: nil, + orders: nil), + true, + "CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")") + ] + + override class var defaultTestSuite: XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self) + + for (column, ifNotExists, expected) in IndexDefinitionTests.definitions { + let test = IndexDefinitionTests(selector: #selector(verify)) + test.definition = column + test.expected = expected + test.ifNotExists = ifNotExists + suite.addTest(test) + } + return suite + } + + @objc func verify() { + XCTAssertEqual(definition.toSQL(ifNotExists: ifNotExists), expected) + } + + func test_validate() { + + let longIndex = IndexDefinition( + table: "tests", + name: String(repeating: "x", count: 65), + unique: false, + columns: ["test_column"], + where: nil, + orders: nil) + + XCTAssertThrowsError(try longIndex.validate()) { error in + XCTAssertEqual(error.localizedDescription, + "Index name 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' " + + "on table 'tests' is too long; the limit is 64 characters") + } + } + + func test_rename() { + let index = IndexDefinition(table: "tests", name: "index_tests_something", + unique: true, + columns: ["test_column"], + where: "test_column IS NOT NULL", + orders: nil) + + let renamedIndex = index.renameTable(to: "foo") + + XCTAssertEqual(renamedIndex, + IndexDefinition( + table: "foo", + name: "index_tests_something", + unique: true, + columns: ["test_column"], + where: "test_column IS NOT NULL", + orders: nil + ) + ) + } +} + +class ForeignKeyDefinitionTests: XCTestCase { + func test_toSQL() { + XCTAssertEqual( + ForeignKeyDefinition( + table: "foo", + column: "bar", + primaryKey: "bar_id", + onUpdate: nil, + onDelete: "SET NULL" + ).toSQL(), """ + REFERENCES "foo" ("bar_id") ON DELETE SET NULL + """ + ) + } +} + +class TableDefinitionTests: XCTestCase { + func test_quoted_columnList() { + let definition = TableDefinition(name: "foo", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), + ColumnDefinition(name: "baz", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(definition.quotedColumnList, """ + "id", "baz" + """) + } + + func test_toSQL() { + let definition = TableDefinition(name: "foo", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(definition.toSQL(), """ + CREATE TABLE foo ( \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ) + """) + } + + func test_toSQL_temp_table() { + let definition = TableDefinition(name: "foo", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(definition.toSQL(temporary: true), """ + CREATE TEMPORARY TABLE foo ( \"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ) + """) + } + + /* + func test_throws_an_error_when_columns_are_empty() { + let empty = TableDefinition(name: "empty", columns: [], indexes: []) + XCTAssertThrowsError(empty.toSQL()) + } + */ + + func test_copySQL() { + let from = TableDefinition(name: "from_table", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + let to = TableDefinition(name: "to_table", columns: [ + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ], indexes: []) + + XCTAssertEqual(from.copySQL(to: to), """ + INSERT INTO "to_table" ("id") SELECT "id" FROM "from_table" + """) + } +} + +class PrimaryKeyTests: XCTestCase { + func test_toSQL() { + XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false).toSQL(), + "PRIMARY KEY") + } + + func test_toSQL_autoincrement() { + XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: true).toSQL(), + "PRIMARY KEY AUTOINCREMENT") + } + + func test_toSQL_on_conflict() { + XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK).toSQL(), + "PRIMARY KEY ON CONFLICT ROLLBACK") + } +} + +class LiteralValueTests: XCTestCase { + func test_recognizes_TRUE() { + XCTAssertEqual(LiteralValue.from("TRUE"), .TRUE) + } + + func test_recognizes_FALSE() { + XCTAssertEqual(LiteralValue.from("FALSE"), .FALSE) + } + + func test_recognizes_NULL() { + XCTAssertEqual(LiteralValue.from("NULL"), .NULL) + } + + func test_recognizes_nil() { + XCTAssertEqual(LiteralValue.from(nil), .NULL) + } + + func test_recognizes_CURRENT_TIME() { + XCTAssertEqual(LiteralValue.from("CURRENT_TIME"), .CURRENT_TIME) + } + + func test_recognizes_CURRENT_TIMESTAMP() { + XCTAssertEqual(LiteralValue.from("CURRENT_TIMESTAMP"), .CURRENT_TIMESTAMP) + } + + func test_recognizes_CURRENT_DATE() { + XCTAssertEqual(LiteralValue.from("CURRENT_DATE"), .CURRENT_DATE) + } + + func test_recognizes_double_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("\"foo\""), .stringLiteral("foo")) + } + + func test_recognizes_single_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("\'foo\'"), .stringLiteral("foo")) + } + + func test_unquotes_double_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("\"fo\"\"o\""), .stringLiteral("fo\"o")) + } + + func test_unquotes_single_quote_string_literals() { + XCTAssertEqual(LiteralValue.from("'fo''o'"), .stringLiteral("fo'o")) + } + + func test_recognizes_numeric_literals() { + XCTAssertEqual(LiteralValue.from("1.2"), .numericLiteral("1.2")) + XCTAssertEqual(LiteralValue.from("0xdeadbeef"), .numericLiteral("0xdeadbeef")) + } + + func test_recognizes_blob_literals() { + XCTAssertEqual(LiteralValue.from("X'deadbeef'"), .blobLiteral("deadbeef")) + XCTAssertEqual(LiteralValue.from("x'deadbeef'"), .blobLiteral("deadbeef")) + } + + func test_description_TRUE() { + XCTAssertEqual(LiteralValue.TRUE.description, "TRUE") + } + + func test_description_FALSE() { + XCTAssertEqual(LiteralValue.FALSE.description, "FALSE") + } + + func test_description_NULL() { + XCTAssertEqual(LiteralValue.NULL.description, "NULL") + } + + func test_description_CURRENT_TIME() { + XCTAssertEqual(LiteralValue.CURRENT_TIME.description, "CURRENT_TIME") + } + + func test_description_CURRENT_TIMESTAMP() { + XCTAssertEqual(LiteralValue.CURRENT_TIMESTAMP.description, "CURRENT_TIMESTAMP") + } + + func test_description_CURRENT_DATE() { + XCTAssertEqual(LiteralValue.CURRENT_DATE.description, "CURRENT_DATE") + } + + func test_description_string_literal() { + XCTAssertEqual(LiteralValue.stringLiteral("foo").description, "'foo'") + } + + func test_description_numeric_literal() { + XCTAssertEqual(LiteralValue.numericLiteral("1.2").description, "1.2") + XCTAssertEqual(LiteralValue.numericLiteral("0xdeadbeef").description, "0xdeadbeef") + } + + func test_description_blob_literal() { + XCTAssertEqual(LiteralValue.blobLiteral("deadbeef").description, "X'deadbeef'") + } +} From affcec8494488f155a846dc58c388ca798a239e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 24 Jul 2022 02:43:39 +0200 Subject: [PATCH 0900/1046] Use DROP/RENAME COLUMN when available --- Sources/SQLite/Schema/Connection+Schema.swift | 13 +++++++++++ Sources/SQLite/Schema/SchemaChanger.swift | 22 +++++++++++++++---- .../Schema/ConnectionSchemaTests.swift | 7 ++++++ .../Schema/SchemaChangerTests.swift | 22 +++++++++++++++++++ .../Schema/SchemaDefinitionsTests.swift | 4 ++++ 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index e368bdaf..c6cf197f 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,6 +1,19 @@ import Foundation extension Connection { + var sqliteVersion: String? { + (try? scalar("SELECT sqlite_version()")) as? String + } + + var sqliteVersionTriple: (Int, Int, Int) { + guard let version = sqliteVersion, + let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, + let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { + return (0, 0, 0) + } + return (major, minor, point) + } + // Changing the foreign_keys setting affects the execution of all statements prepared using the database // connection, including those prepared before the setting was changed. // diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index ff0befc3..cd6b6f25 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -27,6 +27,8 @@ import Foundation 12. If foreign keys constraints were originally enabled, reenable them now. */ public class SchemaChanger: CustomStringConvertible { + typealias SQLiteVersion = (Int, Int, Int) + enum SchemaChangeError: LocalizedError { case foreignKeyError([ForeignKeyError]) @@ -46,9 +48,14 @@ public class SchemaChanger: CustomStringConvertible { case renameTable(String) /// Returns non-nil if the operation can be executed with a simple SQL statement - func toSQL(_ table: String) -> String? { + func toSQL(_ table: String, version: SQLiteVersion) -> String? { switch self { - case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + case .add(let definition): + return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + case .renameColumn(let from, let to) where version.0 >= 3 && version.1 >= 25: + return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" + case .remove(let column) where version.0 >= 3 && version.1 >= 35: + return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } } @@ -77,6 +84,7 @@ public class SchemaChanger: CustomStringConvertible { } private let connection: Connection + private let version: SQLiteVersion static let tempPrefix = "tmp_" typealias Block = () throws -> Void public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void @@ -87,8 +95,14 @@ public class SchemaChanger: CustomStringConvertible { static let temp = Options(rawValue: 1) } - public init(connection: Connection) { + public convenience init(connection: Connection) { + self.init(connection: connection, + version: connection.sqliteVersionTriple) + } + + init(connection: Connection, version: SQLiteVersion) { self.connection = connection + self.version = version } public func alter(table: String, block: AlterTableDefinitionBlock) throws { @@ -105,7 +119,7 @@ public class SchemaChanger: CustomStringConvertible { } private func run(table: String, operation: Operation) throws { - if let sql = operation.toSQL(table) { + if let sql = operation.toSQL(table, version: version) { try connection.run(sql) } else { try doTheTableDance(table: table, operation: operation) diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift index 02321510..9fbad26c 100644 --- a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift +++ b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift @@ -136,4 +136,11 @@ class ConnectionSchemaTests: SQLiteTestCase { ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) ]) } + + func test_sqlite_version_triple() { + let version = db.sqliteVersionTriple + XCTAssertEqual(version.0, 3) + XCTAssertGreaterThan(version.1, 0) + XCTAssertGreaterThanOrEqual(version.2, 0) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 21ae6e59..165de4a8 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -57,6 +57,16 @@ class SchemaChangerTests: SQLiteTestCase { XCTAssertFalse(columns.contains("age")) } + func test_remove_column_legacy() throws { + schemaChanger = .init(connection: db, version: (3, 24, 0)) // DROP COLUMN introduced in 3.35.0 + + try schemaChanger.alter(table: "users") { table in + table.remove("age") + } + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + } + func test_rename_column() throws { try schemaChanger.alter(table: "users") { table in table.rename("age", to: "age2") @@ -67,6 +77,18 @@ class SchemaChangerTests: SQLiteTestCase { XCTAssertTrue(columns.contains("age2")) } + func test_rename_column_legacy() throws { + schemaChanger = .init(connection: db, version: (3, 24, 0)) // RENAME COLUMN introduced in 3.25.0 + + try schemaChanger.alter(table: "users") { table in + table.rename("age", to: "age2") + } + + let columns = try db.columnInfo(table: "users").map(\.name) + XCTAssertFalse(columns.contains("age")) + XCTAssertTrue(columns.contains("age2")) + } + func test_add_column() throws { let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil) diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 07fbf371..c19840bc 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -29,6 +29,7 @@ class ColumnDefinitionTests: XCTestCase { "\"real_column\" REAL DEFAULT 123.123") ] + #if !os(Linux) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) @@ -44,6 +45,7 @@ class ColumnDefinitionTests: XCTestCase { @objc func verify() { XCTAssertEqual(definition.toSQL(), expected) } + #endif } class AffinityTests: XCTestCase { @@ -105,6 +107,7 @@ class IndexDefinitionTests: XCTestCase { "CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")") ] + #if !os(Linux) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self) @@ -121,6 +124,7 @@ class IndexDefinitionTests: XCTestCase { @objc func verify() { XCTAssertEqual(definition.toSQL(ifNotExists: ifNotExists), expected) } + #endif func test_validate() { From 1b645291774b5d36af7630c8bdd8530373d1368d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 24 Jul 2022 19:49:41 +0200 Subject: [PATCH 0901/1046] ColumnDefinition needs to be public --- SQLite.xcodeproj/project.pbxproj | 10 +++ Sources/SQLite/Core/Connection+Pragmas.swift | 52 +++++++++++ Sources/SQLite/Core/Connection.swift | 11 --- Sources/SQLite/Schema/Connection+Schema.swift | 87 ++++++------------- Sources/SQLite/Schema/SchemaChanger.swift | 8 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 60 +++++++------ .../Schema/ConnectionSchemaTests.swift | 9 +- .../Schema/SchemaDefinitionsTests.swift | 4 +- 8 files changed, 130 insertions(+), 111 deletions(-) create mode 100644 Sources/SQLite/Core/Connection+Pragmas.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index abd69301..a97ef184 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; + 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; @@ -71,6 +72,7 @@ 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; @@ -82,7 +84,9 @@ 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; + 19A178A8B2A34FB6B565DEDA /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; @@ -283,6 +287,7 @@ 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionSchemaTests.swift; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Pragmas.swift"; sourceTree = ""; }; 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChangerTests.swift; sourceTree = ""; }; 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -530,6 +535,7 @@ 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, + 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */, ); path = Core; sourceTree = ""; @@ -920,6 +926,7 @@ 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */, 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, + 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -993,6 +1000,7 @@ 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */, 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */, 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */, + 19A178A8B2A34FB6B565DEDA /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1031,6 +1039,7 @@ 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */, 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, + 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1105,6 +1114,7 @@ 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */, 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, + 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Core/Connection+Pragmas.swift b/Sources/SQLite/Core/Connection+Pragmas.swift new file mode 100644 index 00000000..43ff9610 --- /dev/null +++ b/Sources/SQLite/Core/Connection+Pragmas.swift @@ -0,0 +1,52 @@ +import Foundation + +public typealias UserVersion = Int32 +public typealias SQLiteVersion = (Int, Int, Int) + +public extension Connection { + /// The user version of the database. + /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) + var userVersion: UserVersion? { + get { + (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) + } + set { + _ = try? run("PRAGMA user_version = \(newValue ?? 0)") + } + } + + /// The version of SQLite. + /// See SQLite [sqlite_version()](https://sqlite.org/lang_corefunc.html#sqlite_version) + var sqliteVersion: SQLiteVersion { + guard let version = (try? scalar("SELECT sqlite_version()")) as? String, + let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, + let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { + return (0, 0, 0) + } + return (major, minor, point) + } + + // Changing the foreign_keys setting affects the execution of all statements prepared using the database + // connection, including those prepared before the setting was changed. + // + // https://sqlite.org/pragma.html#pragma_foreign_keys + var foreignKeys: Bool { + get { getBoolPragma("foreign_keys") } + set { setBoolPragma("foreign_keys", newValue) } + } + + var deferForeignKeys: Bool { + get { getBoolPragma("defer_foreign_keys") } + set { setBoolPragma("defer_foreign_keys", newValue) } + } + + private func getBoolPragma(_ key: String) -> Bool { + guard let binding = try? scalar("PRAGMA \(key)"), + let intBinding = binding as? Int64 else { return false } + return intBinding == 1 + } + + private func setBoolPragma(_ key: String, _ newValue: Bool) { + _ = try? run("PRAGMA \(key) = \(newValue ? "1" : "0")") + } +} diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 68b83821..0e51e651 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,17 +156,6 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } - /// The user version of the database. - /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) - public var userVersion: Int32? { - get { - (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) - } - set { - _ = try? run("PRAGMA user_version = \(newValue ?? 0)") - } - } - // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index c6cf197f..8bd219ed 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,52 +1,6 @@ import Foundation extension Connection { - var sqliteVersion: String? { - (try? scalar("SELECT sqlite_version()")) as? String - } - - var sqliteVersionTriple: (Int, Int, Int) { - guard let version = sqliteVersion, - let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, - let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { - return (0, 0, 0) - } - return (major, minor, point) - } - - // Changing the foreign_keys setting affects the execution of all statements prepared using the database - // connection, including those prepared before the setting was changed. - // - // https://sqlite.org/pragma.html#pragma_foreign_keys - var foreignKeys: Bool { - get { getBoolPragma("foreign_keys") } - set { setBoolPragma("foreign_keys", newValue) } - } - - var deferForeignKeys: Bool { - get { getBoolPragma("defer_foreign_keys") } - set { setBoolPragma("defer_foreign_keys", newValue) } - } - - // https://sqlite.org/pragma.html#pragma_foreign_key_check - - // There are four columns in each result row. - // The first column is the name of the table that - // contains the REFERENCES clause. - // The second column is the rowid of the row that contains the - // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. - // The third column is the name of the table that is referred to. - // The fourth column is the index of the specific foreign key constraint that failed. - func foreignKeyCheck() throws -> [ForeignKeyError] { - try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in - guard let table = row[0] as? String, - let rowId = row[1] as? Int64, - let target = row[2] as? String else { return nil } - - return ForeignKeyError(from: table, rowId: rowId, to: target) - } - } - // https://sqlite.org/pragma.html#pragma_table_info // // This pragma returns one row for each column in the named table. Columns in the result set include the @@ -58,7 +12,7 @@ extension Connection { try createTableSQL(name: table).flatMap { .init(sql: $0) } } - let foreignKeys: [String: [ForeignKeyDefinition]] = + let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = Dictionary(grouping: try foreignKeyInfo(table: table), by: { $0.column }) return try run("PRAGMA table_info(\(table.quote()))").compactMap { row -> ColumnDefinition? in @@ -71,7 +25,7 @@ extension Connection { primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, type: ColumnDefinition.Affinity.from(type), null: notNull == 0, - defaultValue: LiteralValue.from(defaultValue), + defaultValue: .from(defaultValue), references: foreignKeys[name]?.first) } } @@ -119,16 +73,16 @@ extension Connection { } } - func foreignKeyInfo(table: String) throws -> [ForeignKeyDefinition] { + func foreignKeyInfo(table: String) throws -> [ColumnDefinition.ForeignKey] { try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in if let table = row[2] as? String, // table let column = row[3] as? String, // from let primaryKey = row[4] as? String, // to let onUpdate = row[5] as? String, let onDelete = row[6] as? String { - return ForeignKeyDefinition(table: table, column: column, primaryKey: primaryKey, - onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, - onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete + return .init(table: table, column: column, primaryKey: primaryKey, + onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, + onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete ) } else { return nil @@ -136,6 +90,25 @@ extension Connection { } } + // https://sqlite.org/pragma.html#pragma_foreign_key_check + + // There are four columns in each result row. + // The first column is the name of the table that + // contains the REFERENCES clause. + // The second column is the rowid of the row that contains the + // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. + // The third column is the name of the table that is referred to. + // The fourth column is the index of the specific foreign key constraint that failed. + func foreignKeyCheck() throws -> [ForeignKeyError] { + try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in + guard let table = row[0] as? String, + let rowId = row[1] as? Int64, + let target = row[2] as? String else { return nil } + + return ForeignKeyError(from: table, rowId: rowId, to: target) + } + } + private func createTableSQL(name: String) throws -> String? { try run(""" SELECT sql FROM sqlite_master WHERE name=? AND type='table' @@ -145,14 +118,4 @@ extension Connection { .compactMap { row in row[0] as? String } .first } - - private func getBoolPragma(_ key: String) -> Bool { - guard let binding = try? scalar("PRAGMA \(key)"), - let intBinding = binding as? Int64 else { return false } - return intBinding == 1 - } - - private func setBoolPragma(_ key: String, _ newValue: Bool) { - _ = try? run("PRAGMA \(key) = \(newValue ? "1" : "0")") - } } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index cd6b6f25..c6529fbe 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -27,8 +27,6 @@ import Foundation 12. If foreign keys constraints were originally enabled, reenable them now. */ public class SchemaChanger: CustomStringConvertible { - typealias SQLiteVersion = (Int, Int, Int) - enum SchemaChangeError: LocalizedError { case foreignKeyError([ForeignKeyError]) @@ -52,9 +50,9 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .add(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .renameColumn(let from, let to) where version.0 >= 3 && version.1 >= 25: + case .renameColumn(let from, let to) where version >= (3, 25, 0): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .remove(let column) where version.0 >= 3 && version.1 >= 35: + case .remove(let column) where version >= (3, 35, 0): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } @@ -97,7 +95,7 @@ public class SchemaChanger: CustomStringConvertible { public convenience init(connection: Connection) { self.init(connection: connection, - version: connection.sqliteVersionTriple) + version: connection.sqliteVersion) } init(connection: Connection, version: SQLiteVersion) { diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 563ae331..156a06fb 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -18,14 +18,14 @@ public struct ColumnDefinition: Equatable { // The important idea here is that the type is recommended, not required. Any column can still // store any type of data. It is just that some columns, given the choice, will prefer to use one // storage class over another. The preferred storage class for a column is called its "affinity". - enum Affinity: String, CustomStringConvertible, CaseIterable { + public enum Affinity: String, CustomStringConvertible, CaseIterable { case INTEGER case NUMERIC case REAL case TEXT case BLOB - var description: String { + public var description: String { rawValue } @@ -34,7 +34,7 @@ public struct ColumnDefinition: Equatable { } } - enum OnConflict: String, CaseIterable { + public enum OnConflict: String, CaseIterable { case ROLLBACK case ABORT case FAIL @@ -46,7 +46,7 @@ public struct ColumnDefinition: Equatable { } } - struct PrimaryKey: Equatable { + public struct PrimaryKey: Equatable { let autoIncrement: Bool let onConflict: OnConflict? @@ -73,12 +73,30 @@ public struct ColumnDefinition: Equatable { } } - let name: String - let primaryKey: PrimaryKey? - let type: Affinity - let null: Bool - let defaultValue: LiteralValue - let references: ForeignKeyDefinition? + public struct ForeignKey: Equatable { + let table: String + let column: String + let primaryKey: String + let onUpdate: String? + let onDelete: String? + } + + public let name: String + public let primaryKey: PrimaryKey? + public let type: Affinity + public let null: Bool + public let defaultValue: LiteralValue + public let references: ForeignKey? + + public init(name: String, primaryKey: PrimaryKey?, type: Affinity, null: Bool, defaultValue: LiteralValue, + references: ForeignKey?) { + self.name = name + self.primaryKey = primaryKey + self.type = type + self.null = null + self.defaultValue = defaultValue + self.references = references + } func rename(from: String, to: String) -> ColumnDefinition { guard from == name else { return self } @@ -86,7 +104,7 @@ public struct ColumnDefinition: Equatable { } } -enum LiteralValue: Equatable, CustomStringConvertible { +public enum LiteralValue: Equatable, CustomStringConvertible { // swiftlint:disable force_try private static let singleQuote = try! NSRegularExpression(pattern: "^'(.*)'$") private static let doubleQuote = try! NSRegularExpression(pattern: "^\"(.*)\"$") @@ -141,7 +159,7 @@ enum LiteralValue: Equatable, CustomStringConvertible { } } - var description: String { + public var description: String { switch self { case .NULL: return "NULL" case .TRUE: return "TRUE" @@ -166,7 +184,7 @@ enum LiteralValue: Equatable, CustomStringConvertible { // https://sqlite.org/lang_createindex.html // schema-name.index-name ON table-name ( indexed-column+ ) WHERE expr -struct IndexDefinition: Equatable { +public struct IndexDefinition: Equatable { // SQLite supports index names up to 64 characters. static let maxIndexLength = 64 @@ -175,9 +193,9 @@ struct IndexDefinition: Equatable { static let orderRe = try! NSRegularExpression(pattern: "\"?(\\w+)\"? DESC") // swiftlint:enable force_try - enum Order: String { case ASC, DESC } + public enum Order: String { case ASC, DESC } - init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { + public init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { self.table = table self.name = name self.unique = unique @@ -236,14 +254,6 @@ struct IndexDefinition: Equatable { } } -struct ForeignKeyDefinition: Equatable { - let table: String - let column: String - let primaryKey: String - let onUpdate: String? - let onDelete: String? -} - struct ForeignKeyError: CustomStringConvertible { let from: String let rowId: Int64 @@ -292,7 +302,7 @@ extension ColumnDefinition { } extension IndexDefinition { - func toSQL(ifNotExists: Bool = false) -> String { + public func toSQL(ifNotExists: Bool = false) -> String { let commaSeparatedColumns = columns.map { (column: String) -> String in column.quote() + (orders?[column].map { " \($0.rawValue)" } ?? "") }.joined(separator: ", ") @@ -312,7 +322,7 @@ extension IndexDefinition { } } -extension ForeignKeyDefinition { +extension ColumnDefinition.ForeignKey { func toSQL() -> String { ([ "REFERENCES", diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift index 9fbad26c..1c66b761 100644 --- a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift +++ b/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift @@ -133,14 +133,11 @@ class ConnectionSchemaTests: SQLiteTestCase { let foreignKeys = try db.foreignKeyInfo(table: "test_links") XCTAssertEqual(foreignKeys, [ - ForeignKeyDefinition(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) ]) } - func test_sqlite_version_triple() { - let version = db.sqliteVersionTriple - XCTAssertEqual(version.0, 3) - XCTAssertGreaterThan(version.1, 0) - XCTAssertGreaterThanOrEqual(version.2, 0) + func test_sqlite_version() { + XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index c19840bc..645bfc34 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -10,7 +10,7 @@ class ColumnDefinitionTests: XCTestCase { "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, - references: ForeignKeyDefinition(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), + references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil), @@ -168,7 +168,7 @@ class IndexDefinitionTests: XCTestCase { class ForeignKeyDefinitionTests: XCTestCase { func test_toSQL() { XCTAssertEqual( - ForeignKeyDefinition( + ColumnDefinition.ForeignKey( table: "foo", column: "bar", primaryKey: "bar_id", From f57c225bad580ddfb22149f8d86e8e12ddca3dbf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 25 Jul 2022 01:04:28 +0200 Subject: [PATCH 0902/1046] Move tests into separate directories --- SQLite.xcodeproj/project.pbxproj | 424 ++++++++++-------- Sources/SQLite/Schema/Connection+Schema.swift | 10 - Tests/SQLiteTests/{ => Core}/BlobTests.swift | 0 .../Core/Connection+AttachTests.swift | 62 +++ .../Core/Connection+PragmaTests.swift | 42 ++ .../{ => Core}/ConnectionTests.swift | 52 --- .../{ => Core}/CoreFunctionsTests.swift | 0 .../SQLiteTests/{ => Core}/ResultTests.swift | 0 .../{ => Core}/StatementTests.swift | 0 Tests/SQLiteTests/{ => Core}/ValueTests.swift | 0 .../{ => Extensions}/CipherTests.swift | 0 .../{ => Extensions}/FTS4Tests.swift | 0 .../{ => Extensions}/FTS5Tests.swift | 0 .../FTSIntegrationTests.swift | 0 .../{ => Extensions}/RTreeTests.swift | 0 ...sts.swift => Connection+SchemaTests.swift} | 9 - .../Schema/SchemaChangerTests.swift | 10 +- .../{ => Schema}/SchemaTests.swift | 0 .../{ => Typed}/AggregateFunctionsTests.swift | 0 .../{ => Typed}/CustomAggregationTests.swift | 0 .../{ => Typed}/CustomFunctionsTests.swift | 0 .../DateAndTimeFunctionTests.swift | 0 .../{ => Typed}/ExpressionTests.swift | 0 .../{ => Typed}/OperatorsTests.swift | 0 .../{ => Typed}/QueryIntegrationTests.swift | 0 .../SQLiteTests/{ => Typed}/QueryTests.swift | 0 Tests/SQLiteTests/{ => Typed}/RowTests.swift | 0 .../SQLiteTests/{ => Typed}/SelectTests.swift | 0 .../SQLiteTests/{ => Typed}/SetterTests.swift | 0 29 files changed, 343 insertions(+), 266 deletions(-) rename Tests/SQLiteTests/{ => Core}/BlobTests.swift (100%) create mode 100644 Tests/SQLiteTests/Core/Connection+AttachTests.swift create mode 100644 Tests/SQLiteTests/Core/Connection+PragmaTests.swift rename Tests/SQLiteTests/{ => Core}/ConnectionTests.swift (90%) rename Tests/SQLiteTests/{ => Core}/CoreFunctionsTests.swift (100%) rename Tests/SQLiteTests/{ => Core}/ResultTests.swift (100%) rename Tests/SQLiteTests/{ => Core}/StatementTests.swift (100%) rename Tests/SQLiteTests/{ => Core}/ValueTests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/CipherTests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/FTS4Tests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/FTS5Tests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/FTSIntegrationTests.swift (100%) rename Tests/SQLiteTests/{ => Extensions}/RTreeTests.swift (100%) rename Tests/SQLiteTests/Schema/{ConnectionSchemaTests.swift => Connection+SchemaTests.swift} (95%) rename Tests/SQLiteTests/{ => Schema}/SchemaTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/AggregateFunctionsTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/CustomAggregationTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/CustomFunctionsTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/DateAndTimeFunctionTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/ExpressionTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/OperatorsTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/QueryIntegrationTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/QueryTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/RowTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/SelectTests.swift (100%) rename Tests/SQLiteTests/{ => Typed}/SetterTests.swift (100%) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a97ef184..bc79473e 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -30,96 +30,136 @@ 03A65E841C6BB2FB0062603F /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; 03A65E861C6BB2FB0062603F /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; - 03A65E871C6BB3030062603F /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; - 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; - 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; - 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; - 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; - 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; - 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; - 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; - 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; - 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; - 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; - 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; - 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; 03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; }; 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; + 19A17021286A4D8D6C2EF12D /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; + 19A17026DCDCDA405B09A229 /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; 19A17073552293CA063BEA66 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A1708D3D58D7BC1168E55F /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; + 19A170AEBAA56DC3355A73B3 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; + 19A170C56745F9D722A73D77 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; + 19A170D938343E30119EDFB3 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; + 19A1714F7CF964D568AB14E0 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A1716BF8E15F91A6B5CB7A /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; 19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; - 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; - 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; - 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; - 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; + 19A17188B4D96636F9C0C209 /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; 19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; - 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; - 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; - 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; - 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; - 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A171F243A589C5EBC47937 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; + 19A1725658E480B9B378F28B /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; + 19A1726002D24C14F876C8FE /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; + 19A172F71EFD65342072D8D2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; + 19A173088B85A7E18E8582A7 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; + 19A173389E53CB24DFA8CEDD /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; + 19A173465F23C64DF3DF469B /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A173F25449876761347072 /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; + 19A173F429D7E46289EB2167 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A17411403D60640467209E /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + 19A174118D11B93DA5DAAF79 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; + 19A17437659BD7FD787D94A6 /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + 19A17444861E1443143DEB44 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A17457B0461F484AF6BE40 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; + 19A17482E6FC5E563F3E6A47 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; 19A17490543609FCED53CACC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; - 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; }; 19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A1755C49154C87304C9146 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; - 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A1766135CE9786B1878603 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; - 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; + 19A176B3316281F004F92276 /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A1772EBE65173EDFB1AFCA /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + 19A1773155AC2BF2CA86A473 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A17746150A815944A6820B /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + 19A1776BD5127DFDF847FF1F /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; + 19A177909023B7B940C5805E /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; + 19A177AA5922527BBDC77CF9 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; - 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A177D5C6542E2D572162E5 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A1781CBA8968ABD3E00877 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + 19A1782444437C7FC6B75CBC /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; 19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; - 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */; }; + 19A178767223229E61C5066F /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; + 19A17885B646CB0201BE4BD5 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; 19A178A8B2A34FB6B565DEDA /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; + 19A178C041DDCF80B533AD13 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; + 19A178DA2BB5970778CCAF13 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; + 19A178DF5A96CFEFF1E271F6 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; + 19A178F9008614B8A8425635 /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; + 19A17900387FDCF578B31E3E /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; + 19A17912DB9D3AC8FECF948B /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; + 19A17923494236793893BF72 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A1793972BDDDB027C113BB /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + 19A179786A6826D58A70F8BC /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; - 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; + 19A1799AF6643CF5081BFA15 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; + 19A179BB9A6665B2B99DA546 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; + 19A179BCD483DEA21661FD37 /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; 19A179CCF9671E345E5A9811 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A179E76EA6207669B60C1B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + 19A17A391BF056E3D729E70A /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; + 19A17A52BF29D27C9AA229E7 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; + 19A17A7B3E3B7E76364A2AEE /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; + 19A17A7DF99B0379FD3396B1 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; + 19A17A9520802ACF45907970 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; + 19A17ABCF0EB4808BDC5B5FF /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; - 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */; }; + 19A17B1D9B5CEBE9CE09280C /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; + 19A17B36ABC6006AB80F693C /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; - 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; - 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; }; - 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */; }; - 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */; }; - 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */; }; + 19A17BACF4C032513DE1F879 /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; + 19A17C74233AFC2EDAFA23DC /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; + 19A17CA4D7B63D845428A9C5 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; + 19A17CA6ADB78A2E545BF836 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; + 19A17CF65C0196E03BC64519 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + 19A17D6EC40BC35A5DC81BA8 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; + 19A17D993398B8215B73E1EA /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; + 19A17DAD5975D9367EAA46E2 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + 19A17DD33C2E43DD6EE05A60 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + 19A17DE1FCDB5695702AD24D /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + 19A17DE34C477232592A8F6B /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17E0ABA6C415F014CD51C /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; + 19A17E1DD976D5CE80018749 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + 19A17E3F47DA087E2B76D087 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + 19A17F2096E83A3181E03317 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + 19A17F7977364EC8CD33C3C3 /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + 19A17F907258E524B3CA2FAE /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; + 19A17FACE8E4D54A50BA934E /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + 19A17FBAA26953EB854E790D /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + 19A17FD22EF43DF428DD93BA /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; - 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; - 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; - 3717F90A221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */; }; 3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; }; 3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; 3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; @@ -144,9 +184,6 @@ 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; 3DF7B78B28842972005DD8CA /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; - 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; - 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; - 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78C28842C23005DD8CA /* ResultTests.swift */; }; 3DF7B791288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B792288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; 3DF7B793288449BA005DD8CA /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; @@ -162,9 +199,6 @@ 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; - D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; - D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; - D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DB368A20C09C9B00D5A58E /* SelectTests.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -185,35 +219,7 @@ EE247B141C3F06E900AE3E12 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; EE247B171C3F127200AE3E12 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; - EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; - EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; - EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; - EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; - EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; - EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; - EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; - EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; - EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; - EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; - EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; - EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; - EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; EE247B461C3F3ED000AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */; }; - EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */; }; - EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1B1C3F137700AE3E12 /* BlobTests.swift */; }; - EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */; }; - EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */; }; - EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */; }; - EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B201C3F137700AE3E12 /* ExpressionTests.swift */; }; - EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B211C3F137700AE3E12 /* FTS4Tests.swift */; }; - EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */; }; - EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */; }; - EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */; }; - EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */; }; - EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B181C3F134A00AE3E12 /* SetterTests.swift */; }; - EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B321C3F142E00AE3E12 /* StatementTests.swift */; }; - EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; }; EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; EE247B621C3F3FDB00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247B631C3F3FDB00AE3E12 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; @@ -266,34 +272,51 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03A65E961C6BB3210062603F /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; + 19A1709D5BDD2691BA160012 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Schema.swift"; sourceTree = ""; }; + 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+AttachTests.swift"; sourceTree = ""; }; + 19A170F141BF21946D159083 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; 19A1710E73A46D5AC721CDA9 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; + 19A17162C9861E5C4900455D /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; + 19A171A2ED4E2640F197F48C /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; + 19A171A7714C6524093255C5 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; 19A171B262DDE8718513CFDA /* SchemaChanger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChanger.swift; sourceTree = ""; }; - 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; - 19A1721B8984686B9963B45D /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; + 19A171ED017645C8B04DF9F2 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitions.swift; sourceTree = ""; }; - 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1730E4390C775C25677D1 /* FTS5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5.swift; sourceTree = ""; }; - 19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + 19A1745BE8623D8C6808DB3C /* ResultTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; + 19A17475DCA068453F787613 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; + 19A174FE5B47A97937A27276 /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; + 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Aggregation.swift"; sourceTree = ""; }; - 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowTests.swift; sourceTree = ""; }; + 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaDefinitionsTests.swift; sourceTree = ""; }; + 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; + 19A17855BD524FF888265B3C /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; + 19A1787E16C8562C09C076F5 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; 19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; + 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctionTests.swift; sourceTree = ""; }; 19A1794B7972D14330A65BBD /* Linux.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Linux.md; sourceTree = ""; }; 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = ""; }; + 19A17AE284BB1DF31D1B753E /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; + 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; 19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = ""; }; + 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tests.swift; sourceTree = ""; }; 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndTimeFunctions.swift; sourceTree = ""; }; - 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; - 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionSchemaTests.swift; sourceTree = ""; }; + 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSIntegrationTests.swift; sourceTree = ""; }; + 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+PragmaTests.swift"; sourceTree = ""; }; + 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+SchemaTests.swift"; sourceTree = ""; }; + 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryIntegrationTests.swift; sourceTree = ""; }; 19A17E723300E5ED3771DCB5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; 19A17EA3A313F129011B3FA0 /* Release.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Release.md; sourceTree = ""; }; + 19A17EC0C43015063945D32E /* SelectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Connection+Pragmas.swift"; sourceTree = ""; }; + 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaChangerTests.swift; sourceTree = ""; }; - 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAggregationTests.swift; sourceTree = ""; }; 3D3C3CCB26E5568800759140 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 3DF7B78728842972005DD8CA /* Connection+Attach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Connection+Attach.swift"; sourceTree = ""; }; - 3DF7B78C28842C23005DD8CA /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = ""; }; 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URIQueryParameter.swift; sourceTree = ""; }; 3DF7B79528846FCC005DD8CA /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = ""; }; 3DF7B79A2884C353005DD8CA /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; @@ -302,7 +325,6 @@ 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D4DB368A20C09C9B00D5A58E /* SelectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -326,20 +348,6 @@ EE247B011C3F06E900AE3E12 /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; EE247B021C3F06E900AE3E12 /* Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setter.swift; sourceTree = ""; }; EE247B161C3F127200AE3E12 /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; - EE247B181C3F134A00AE3E12 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; - EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; - EE247B1B1C3F137700AE3E12 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; - EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; - EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; - EE247B201C3F137700AE3E12 /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - EE247B211C3F137700AE3E12 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; - EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; - EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; - EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; - EE247B321C3F142E00AE3E12 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; - EE247B331C3F142E00AE3E12 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; EE247B3C1C3F3ED000AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247B451C3F3ED000AE3E12 /* SQLiteTests Mac.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests Mac.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; EE247B771C3F40D700AE3E12 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -423,16 +431,62 @@ path = Schema; sourceTree = ""; }; + 19A1798E3459573BEE50FA34 /* Core */ = { + isa = PBXGroup; + children = ( + 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */, + 19A171A2ED4E2640F197F48C /* BlobTests.swift */, + 19A17855BD524FF888265B3C /* ConnectionTests.swift */, + 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */, + 19A1745BE8623D8C6808DB3C /* ResultTests.swift */, + 19A17AE284BB1DF31D1B753E /* ValueTests.swift */, + 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */, + 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */, + ); + path = Core; + sourceTree = ""; + }; + 19A17AECBF878B1DAE0AE3DD /* Typed */ = { + isa = PBXGroup; + children = ( + 19A170F141BF21946D159083 /* ExpressionTests.swift */, + 19A171ED017645C8B04DF9F2 /* QueryTests.swift */, + 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */, + 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */, + 19A17475DCA068453F787613 /* OperatorsTests.swift */, + 19A17EC0C43015063945D32E /* SelectTests.swift */, + 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */, + 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */, + 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */, + 19A1709D5BDD2691BA160012 /* SetterTests.swift */, + 19A174FE5B47A97937A27276 /* RowTests.swift */, + ); + path = Typed; + sourceTree = ""; + }; 19A17B56FBA20E7245BC8AC0 /* Schema */ = { isa = PBXGroup; children = ( 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */, 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */, - 19A17CA1DF7D0F7C9A94C51C /* ConnectionSchemaTests.swift */, + 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */, + 19A171A7714C6524093255C5 /* SchemaTests.swift */, ); path = Schema; sourceTree = ""; }; + 19A17E470E4492D287C0D12F /* Extensions */ = { + isa = PBXGroup; + children = ( + 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */, + 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */, + 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */, + 19A1787E16C8562C09C076F5 /* CipherTests.swift */, + 19A17162C9861E5C4900455D /* RTreeTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 3D67B3E41DB2469200A4F4C6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -489,34 +543,14 @@ isa = PBXGroup; children = ( 3DF7B79528846FCC005DD8CA /* Resources */, - EE247B1A1C3F137700AE3E12 /* AggregateFunctionsTests.swift */, - EE247B1B1C3F137700AE3E12 /* BlobTests.swift */, - EE247B1D1C3F137700AE3E12 /* ConnectionTests.swift */, - EE247B1E1C3F137700AE3E12 /* CoreFunctionsTests.swift */, - EE247B1F1C3F137700AE3E12 /* CustomFunctionsTests.swift */, - 3717F907221F5D7C00B9BD3D /* CustomAggregationTests.swift */, - EE247B201C3F137700AE3E12 /* ExpressionTests.swift */, - EE247B211C3F137700AE3E12 /* FTS4Tests.swift */, - EE247B2A1C3F141E00AE3E12 /* OperatorsTests.swift */, - EE247B2B1C3F141E00AE3E12 /* QueryTests.swift */, - EE247B2C1C3F141E00AE3E12 /* RTreeTests.swift */, - EE247B2D1C3F141E00AE3E12 /* SchemaTests.swift */, - EE247B181C3F134A00AE3E12 /* SetterTests.swift */, - EE247B321C3F142E00AE3E12 /* StatementTests.swift */, - EE247B331C3F142E00AE3E12 /* ValueTests.swift */, EE247B161C3F127200AE3E12 /* TestHelpers.swift */, EE247AE41C3F04ED00AE3E12 /* Info.plist */, - 19A1721B8984686B9963B45D /* FTS5Tests.swift */, 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */, - 19A17399EA9E61235D5D77BF /* CipherTests.swift */, 19A17B93B48B5560E6E51791 /* Fixtures.swift */, - 19A175C1F9CB3BBAB8FCEC7B /* RowTests.swift */, - 19A1729B75C33F9A0B9A89C1 /* DateAndTimeFunctionTests.swift */, - D4DB368A20C09C9B00D5A58E /* SelectTests.swift */, - 19A17BA6B4E282C1315A115C /* QueryIntegrationTests.swift */, - 19A171FDE4D67879B14ACBDF /* FTSIntegrationTests.swift */, - 3DF7B78C28842C23005DD8CA /* ResultTests.swift */, 19A17B56FBA20E7245BC8AC0 /* Schema */, + 19A17E470E4492D287C0D12F /* Extensions */, + 19A1798E3459573BEE50FA34 /* Core */, + 19A17AECBF878B1DAE0AE3DD /* Typed */, ); name = SQLiteTests; path = Tests/SQLiteTests; @@ -934,35 +968,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3DF7B78F28842C23005DD8CA /* ResultTests.swift in Sources */, - 03A65E881C6BB3030062603F /* BlobTests.swift in Sources */, - 03A65E901C6BB3030062603F /* RTreeTests.swift in Sources */, - 03A65E941C6BB3030062603F /* ValueTests.swift in Sources */, - 03A65E8F1C6BB3030062603F /* QueryTests.swift in Sources */, - 03A65E8B1C6BB3030062603F /* CustomFunctionsTests.swift in Sources */, - 03A65E871C6BB3030062603F /* AggregateFunctionsTests.swift in Sources */, - 03A65E921C6BB3030062603F /* SetterTests.swift in Sources */, - 03A65E891C6BB3030062603F /* ConnectionTests.swift in Sources */, - 03A65E8A1C6BB3030062603F /* CoreFunctionsTests.swift in Sources */, - 3717F90A221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */, - 03A65E931C6BB3030062603F /* StatementTests.swift in Sources */, - 03A65E911C6BB3030062603F /* SchemaTests.swift in Sources */, - 03A65E8D1C6BB3030062603F /* FTS4Tests.swift in Sources */, - 03A65E8C1C6BB3030062603F /* ExpressionTests.swift in Sources */, - 03A65E8E1C6BB3030062603F /* OperatorsTests.swift in Sources */, 03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */, - 19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */, 19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */, - 19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */, 19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */, - 19A1785195182AF8731A8BDA /* RowTests.swift in Sources */, - 19A1769C1F3A7542BECF50FF /* DateAndTimeFunctionTests.swift in Sources */, - D4DB368E20C09CFD00D5A58E /* SelectTests.swift in Sources */, - 19A17CB808ACF606726E77A8 /* QueryIntegrationTests.swift in Sources */, - 19A17C9407AC0EE104E5CC85 /* FTSIntegrationTests.swift in Sources */, 19A17B62A4125AF4F6014CF5 /* SchemaDefinitionsTests.swift in Sources */, 19A17FC04708C6ED637DDFD4 /* SchemaChangerTests.swift in Sources */, - 19A17188B4D96636F9C0C209 /* ConnectionSchemaTests.swift in Sources */, + 19A17188B4D96636F9C0C209 /* Connection+SchemaTests.swift in Sources */, + 19A17FACE8E4D54A50BA934E /* FTS5Tests.swift in Sources */, + 19A177909023B7B940C5805E /* FTSIntegrationTests.swift in Sources */, + 19A17E1DD976D5CE80018749 /* FTS4Tests.swift in Sources */, + 19A17411403D60640467209E /* ExpressionTests.swift in Sources */, + 19A17CA4D7B63D845428A9C5 /* StatementTests.swift in Sources */, + 19A17885B646CB0201BE4BD5 /* QueryTests.swift in Sources */, + 19A1708D3D58D7BC1168E55F /* CipherTests.swift in Sources */, + 19A178C041DDCF80B533AD13 /* BlobTests.swift in Sources */, + 19A17021286A4D8D6C2EF12D /* ConnectionTests.swift in Sources */, + 19A17DE34C477232592A8F6B /* CoreFunctionsTests.swift in Sources */, + 19A1799AF6643CF5081BFA15 /* DateAndTimeFunctionTests.swift in Sources */, + 19A17457B0461F484AF6BE40 /* CustomFunctionsTests.swift in Sources */, + 19A17900387FDCF578B31E3E /* OperatorsTests.swift in Sources */, + 19A17C74233AFC2EDAFA23DC /* ResultTests.swift in Sources */, + 19A17A9520802ACF45907970 /* RTreeTests.swift in Sources */, + 19A17A391BF056E3D729E70A /* SchemaTests.swift in Sources */, + 19A17746150A815944A6820B /* SelectTests.swift in Sources */, + 19A1766135CE9786B1878603 /* ValueTests.swift in Sources */, + 19A177D5C6542E2D572162E5 /* QueryIntegrationTests.swift in Sources */, + 19A178DF5A96CFEFF1E271F6 /* AggregateFunctionsTests.swift in Sources */, + 19A17437659BD7FD787D94A6 /* CustomAggregationTests.swift in Sources */, + 19A17F907258E524B3CA2FAE /* SetterTests.swift in Sources */, + 19A17ABCF0EB4808BDC5B5FF /* RowTests.swift in Sources */, + 19A17BACF4C032513DE1F879 /* Connection+PragmaTests.swift in Sources */, + 19A173F25449876761347072 /* Connection+AttachTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1047,35 +1083,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3DF7B78D28842C23005DD8CA /* ResultTests.swift in Sources */, - EE247B261C3F137700AE3E12 /* CoreFunctionsTests.swift in Sources */, - EE247B291C3F137700AE3E12 /* FTS4Tests.swift in Sources */, - EE247B191C3F134A00AE3E12 /* SetterTests.swift in Sources */, - EE247B311C3F141E00AE3E12 /* SchemaTests.swift in Sources */, EE247B171C3F127200AE3E12 /* TestHelpers.swift in Sources */, - EE247B281C3F137700AE3E12 /* ExpressionTests.swift in Sources */, - EE247B271C3F137700AE3E12 /* CustomFunctionsTests.swift in Sources */, - EE247B341C3F142E00AE3E12 /* StatementTests.swift in Sources */, - EE247B301C3F141E00AE3E12 /* RTreeTests.swift in Sources */, - 3717F908221F5D8800B9BD3D /* CustomAggregationTests.swift in Sources */, - EE247B231C3F137700AE3E12 /* BlobTests.swift in Sources */, - EE247B351C3F142E00AE3E12 /* ValueTests.swift in Sources */, - EE247B2F1C3F141E00AE3E12 /* QueryTests.swift in Sources */, - EE247B221C3F137700AE3E12 /* AggregateFunctionsTests.swift in Sources */, - EE247B2E1C3F141E00AE3E12 /* OperatorsTests.swift in Sources */, - EE247B251C3F137700AE3E12 /* ConnectionTests.swift in Sources */, - 19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */, 19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */, - 19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */, 19A17408007B182F884E3A53 /* Fixtures.swift in Sources */, - 19A1720B67ED13E6150C6A3D /* RowTests.swift in Sources */, - 19A17C80076860CF7751A056 /* DateAndTimeFunctionTests.swift in Sources */, - D4DB368C20C09CFB00D5A58E /* SelectTests.swift in Sources */, - 19A172A9B536D02D4A98AAAD /* QueryIntegrationTests.swift in Sources */, - 19A17C013B00682B70D53DB8 /* FTSIntegrationTests.swift in Sources */, 19A17FE78A39E86F330420EC /* SchemaDefinitionsTests.swift in Sources */, 19A177C25834473FAB32CF3B /* SchemaChangerTests.swift in Sources */, - 19A1725658E480B9B378F28B /* ConnectionSchemaTests.swift in Sources */, + 19A1725658E480B9B378F28B /* Connection+SchemaTests.swift in Sources */, + 19A178DA2BB5970778CCAF13 /* FTS5Tests.swift in Sources */, + 19A1755C49154C87304C9146 /* FTSIntegrationTests.swift in Sources */, + 19A17444861E1443143DEB44 /* FTS4Tests.swift in Sources */, + 19A17DD33C2E43DD6EE05A60 /* ExpressionTests.swift in Sources */, + 19A17D6EC40BC35A5DC81BA8 /* StatementTests.swift in Sources */, + 19A17E3F47DA087E2B76D087 /* QueryTests.swift in Sources */, + 19A17A7B3E3B7E76364A2AEE /* CipherTests.swift in Sources */, + 19A1782444437C7FC6B75CBC /* BlobTests.swift in Sources */, + 19A17CF65C0196E03BC64519 /* ConnectionTests.swift in Sources */, + 19A179BB9A6665B2B99DA546 /* CoreFunctionsTests.swift in Sources */, + 19A174118D11B93DA5DAAF79 /* DateAndTimeFunctionTests.swift in Sources */, + 19A17A7DF99B0379FD3396B1 /* CustomFunctionsTests.swift in Sources */, + 19A171F243A589C5EBC47937 /* OperatorsTests.swift in Sources */, + 19A173F429D7E46289EB2167 /* ResultTests.swift in Sources */, + 19A17B1D9B5CEBE9CE09280C /* RTreeTests.swift in Sources */, + 19A172F71EFD65342072D8D2 /* SchemaTests.swift in Sources */, + 19A17F7977364EC8CD33C3C3 /* SelectTests.swift in Sources */, + 19A17FD22EF43DF428DD93BA /* ValueTests.swift in Sources */, + 19A177AA5922527BBDC77CF9 /* QueryIntegrationTests.swift in Sources */, + 19A179786A6826D58A70F8BC /* AggregateFunctionsTests.swift in Sources */, + 19A1793972BDDDB027C113BB /* CustomAggregationTests.swift in Sources */, + 19A1773155AC2BF2CA86A473 /* SetterTests.swift in Sources */, + 19A176B3316281F004F92276 /* RowTests.swift in Sources */, + 19A17FBAA26953EB854E790D /* Connection+PragmaTests.swift in Sources */, + 19A17026DCDCDA405B09A229 /* Connection+AttachTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1122,35 +1160,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3DF7B78E28842C23005DD8CA /* ResultTests.swift in Sources */, - EE247B561C3F3FC700AE3E12 /* CoreFunctionsTests.swift in Sources */, - EE247B5A1C3F3FC700AE3E12 /* OperatorsTests.swift in Sources */, - EE247B541C3F3FC700AE3E12 /* BlobTests.swift in Sources */, - EE247B5D1C3F3FC700AE3E12 /* SchemaTests.swift in Sources */, - EE247B591C3F3FC700AE3E12 /* FTS4Tests.swift in Sources */, - EE247B531C3F3FC700AE3E12 /* AggregateFunctionsTests.swift in Sources */, - EE247B5F1C3F3FC700AE3E12 /* StatementTests.swift in Sources */, - EE247B5C1C3F3FC700AE3E12 /* RTreeTests.swift in Sources */, - EE247B571C3F3FC700AE3E12 /* CustomFunctionsTests.swift in Sources */, - 3717F909221F5D8900B9BD3D /* CustomAggregationTests.swift in Sources */, - EE247B601C3F3FC700AE3E12 /* ValueTests.swift in Sources */, - EE247B551C3F3FC700AE3E12 /* ConnectionTests.swift in Sources */, EE247B611C3F3FC700AE3E12 /* TestHelpers.swift in Sources */, - EE247B581C3F3FC700AE3E12 /* ExpressionTests.swift in Sources */, - EE247B5E1C3F3FC700AE3E12 /* SetterTests.swift in Sources */, - EE247B5B1C3F3FC700AE3E12 /* QueryTests.swift in Sources */, - 19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */, 19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */, - 19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */, 19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */, - 19A171967CC511C4F6F773C9 /* RowTests.swift in Sources */, - 19A172EB202970561E5C4245 /* DateAndTimeFunctionTests.swift in Sources */, - D4DB368D20C09CFC00D5A58E /* SelectTests.swift in Sources */, - 19A176406BDE9D9C80CC9FA3 /* QueryIntegrationTests.swift in Sources */, - 19A171BE056457F13BFBC4C3 /* FTSIntegrationTests.swift in Sources */, 19A17E80F736EEE8EE2AA4CE /* SchemaDefinitionsTests.swift in Sources */, 19A1766AC10D13C4EFF349AD /* SchemaChangerTests.swift in Sources */, - 19A17B36ABC6006AB80F693C /* ConnectionSchemaTests.swift in Sources */, + 19A17B36ABC6006AB80F693C /* Connection+SchemaTests.swift in Sources */, + 19A1776BD5127DFDF847FF1F /* FTS5Tests.swift in Sources */, + 19A173088B85A7E18E8582A7 /* FTSIntegrationTests.swift in Sources */, + 19A178767223229E61C5066F /* FTS4Tests.swift in Sources */, + 19A1781CBA8968ABD3E00877 /* ExpressionTests.swift in Sources */, + 19A17923494236793893BF72 /* StatementTests.swift in Sources */, + 19A17A52BF29D27C9AA229E7 /* QueryTests.swift in Sources */, + 19A17CA6ADB78A2E545BF836 /* CipherTests.swift in Sources */, + 19A1714F7CF964D568AB14E0 /* BlobTests.swift in Sources */, + 19A173465F23C64DF3DF469B /* ConnectionTests.swift in Sources */, + 19A17D993398B8215B73E1EA /* CoreFunctionsTests.swift in Sources */, + 19A170AEBAA56DC3355A73B3 /* DateAndTimeFunctionTests.swift in Sources */, + 19A1716BF8E15F91A6B5CB7A /* CustomFunctionsTests.swift in Sources */, + 19A17482E6FC5E563F3E6A47 /* OperatorsTests.swift in Sources */, + 19A17912DB9D3AC8FECF948B /* ResultTests.swift in Sources */, + 19A17DAD5975D9367EAA46E2 /* RTreeTests.swift in Sources */, + 19A17F2096E83A3181E03317 /* SchemaTests.swift in Sources */, + 19A17DE1FCDB5695702AD24D /* SelectTests.swift in Sources */, + 19A1726002D24C14F876C8FE /* ValueTests.swift in Sources */, + 19A173389E53CB24DFA8CEDD /* QueryIntegrationTests.swift in Sources */, + 19A170C56745F9D722A73D77 /* AggregateFunctionsTests.swift in Sources */, + 19A1772EBE65173EDFB1AFCA /* CustomAggregationTests.swift in Sources */, + 19A17E0ABA6C415F014CD51C /* SetterTests.swift in Sources */, + 19A170D938343E30119EDFB3 /* RowTests.swift in Sources */, + 19A178F9008614B8A8425635 /* Connection+PragmaTests.swift in Sources */, + 19A179BCD483DEA21661FD37 /* Connection+AttachTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 8bd219ed..c3b24f38 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -63,16 +63,6 @@ extension Connection { } } - func tableInfo() throws -> [String] { - try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in - if let name = row[0] as? String, !name.starts(with: "sqlite_") { - return name - } else { - return nil - } - } - } - func foreignKeyInfo(table: String) throws -> [ColumnDefinition.ForeignKey] { try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in if let table = row[2] as? String, // table diff --git a/Tests/SQLiteTests/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift similarity index 100% rename from Tests/SQLiteTests/BlobTests.swift rename to Tests/SQLiteTests/Core/BlobTests.swift diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift new file mode 100644 index 00000000..940a30ca --- /dev/null +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -0,0 +1,62 @@ +import XCTest +import Foundation +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class ConnectionAttachTests: SQLiteTestCase { + func test_attach_detach_memory_database() throws { + let schemaName = "test" + + try db.attach(.inMemory, as: schemaName) + + let table = Table("attached_users", database: schemaName) + let name = Expression("string") + + // create a table, insert some data + try db.run(table.create { builder in + builder.column(name) + }) + _ = try db.run(table.insert(name <- "test")) + + // query data + let rows = try db.prepare(table.select(name)).map { $0[name] } + XCTAssertEqual(["test"], rows) + + try db.detach(schemaName) + } + + func test_attach_detach_file_database() throws { + let schemaName = "test" + let testDb = fixture("test", withExtension: "sqlite") + + try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) + + let table = Table("tests", database: schemaName) + let email = Expression("email") + + let rows = try db.prepare(table.select(email)).map { $0[email] } + XCTAssertEqual(["foo@bar.com"], rows) + + try db.detach(schemaName) + } + + func test_detach_invalid_schema_name_errors_with_no_such_database() throws { + XCTAssertThrowsError(try db.detach("no-exist")) { error in + if case let Result.error(message, code, _) = error { + XCTAssertEqual(code, SQLITE_ERROR) + XCTAssertEqual("no such database: no-exist", message) + } else { + XCTFail("unexpected error: \(error)") + } + } + } +} diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift new file mode 100644 index 00000000..2d0742c9 --- /dev/null +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -0,0 +1,42 @@ +import XCTest +import Foundation +@testable import SQLite + +#if SQLITE_SWIFT_STANDALONE +import sqlite3 +#elseif SQLITE_SWIFT_SQLCIPHER +import SQLCipher +#elseif os(Linux) +import CSQLite +#else +import SQLite3 +#endif + +class ConnectionPragmaTests: SQLiteTestCase { + func test_userVersion() { + db.userVersion = 2 + XCTAssertEqual(2, db.userVersion!) + } + + func test_sqlite_version() { + XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) + } + + func test_foreignKeys_defaults_to_false() { + XCTAssertFalse(db.foreignKeys) + } + + func test_foreignKeys_sets_value() { + db.foreignKeys = true + XCTAssertTrue(db.foreignKeys) + } + + func test_defer_foreignKeys_defaults_to_false() { + XCTAssertFalse(db.deferForeignKeys) + } + + func test_defer_foreignKeys_sets_value() { + db.deferForeignKeys = true + XCTAssertTrue(db.deferForeignKeys) + } +} diff --git a/Tests/SQLiteTests/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift similarity index 90% rename from Tests/SQLiteTests/ConnectionTests.swift rename to Tests/SQLiteTests/Core/ConnectionTests.swift index 347516f5..e9ceff08 100644 --- a/Tests/SQLiteTests/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -111,11 +111,6 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } - func test_userVersion() { - db.userVersion = 2 - XCTAssertEqual(2, db.userVersion!) - } - func test_prepare_preparesAndReturnsStatements() throws { _ = try db.prepare("SELECT * FROM users WHERE admin = 0") _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) @@ -445,51 +440,4 @@ class ConnectionTests: SQLiteTestCase { } semaphores.forEach { $0.wait() } } - - func test_attach_detach_memory_database() throws { - let schemaName = "test" - - try db.attach(.inMemory, as: schemaName) - - let table = Table("attached_users", database: schemaName) - let name = Expression("string") - - // create a table, insert some data - try db.run(table.create { builder in - builder.column(name) - }) - _ = try db.run(table.insert(name <- "test")) - - // query data - let rows = try db.prepare(table.select(name)).map { $0[name] } - XCTAssertEqual(["test"], rows) - - try db.detach(schemaName) - } - - func test_attach_detach_file_database() throws { - let schemaName = "test" - let testDb = fixture("test", withExtension: "sqlite") - - try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) - - let table = Table("tests", database: schemaName) - let email = Expression("email") - - let rows = try db.prepare(table.select(email)).map { $0[email] } - XCTAssertEqual(["foo@bar.com"], rows) - - try db.detach(schemaName) - } - - func test_detach_invalid_schema_name_errors_with_no_such_database() throws { - XCTAssertThrowsError(try db.detach("no-exist")) { error in - if case let Result.error(message, code, _) = error { - XCTAssertEqual(code, SQLITE_ERROR) - XCTAssertEqual("no such database: no-exist", message) - } else { - XCTFail("unexpected error: \(error)") - } - } - } } diff --git a/Tests/SQLiteTests/CoreFunctionsTests.swift b/Tests/SQLiteTests/Core/CoreFunctionsTests.swift similarity index 100% rename from Tests/SQLiteTests/CoreFunctionsTests.swift rename to Tests/SQLiteTests/Core/CoreFunctionsTests.swift diff --git a/Tests/SQLiteTests/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift similarity index 100% rename from Tests/SQLiteTests/ResultTests.swift rename to Tests/SQLiteTests/Core/ResultTests.swift diff --git a/Tests/SQLiteTests/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift similarity index 100% rename from Tests/SQLiteTests/StatementTests.swift rename to Tests/SQLiteTests/Core/StatementTests.swift diff --git a/Tests/SQLiteTests/ValueTests.swift b/Tests/SQLiteTests/Core/ValueTests.swift similarity index 100% rename from Tests/SQLiteTests/ValueTests.swift rename to Tests/SQLiteTests/Core/ValueTests.swift diff --git a/Tests/SQLiteTests/CipherTests.swift b/Tests/SQLiteTests/Extensions/CipherTests.swift similarity index 100% rename from Tests/SQLiteTests/CipherTests.swift rename to Tests/SQLiteTests/Extensions/CipherTests.swift diff --git a/Tests/SQLiteTests/FTS4Tests.swift b/Tests/SQLiteTests/Extensions/FTS4Tests.swift similarity index 100% rename from Tests/SQLiteTests/FTS4Tests.swift rename to Tests/SQLiteTests/Extensions/FTS4Tests.swift diff --git a/Tests/SQLiteTests/FTS5Tests.swift b/Tests/SQLiteTests/Extensions/FTS5Tests.swift similarity index 100% rename from Tests/SQLiteTests/FTS5Tests.swift rename to Tests/SQLiteTests/Extensions/FTS5Tests.swift diff --git a/Tests/SQLiteTests/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift similarity index 100% rename from Tests/SQLiteTests/FTSIntegrationTests.swift rename to Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift diff --git a/Tests/SQLiteTests/RTreeTests.swift b/Tests/SQLiteTests/Extensions/RTreeTests.swift similarity index 100% rename from Tests/SQLiteTests/RTreeTests.swift rename to Tests/SQLiteTests/Extensions/RTreeTests.swift diff --git a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift similarity index 95% rename from Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift rename to Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 1c66b761..56e79734 100644 --- a/Tests/SQLiteTests/Schema/ConnectionSchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -108,11 +108,6 @@ class ConnectionSchemaTests: SQLiteTestCase { ]) } - func test_table_info_returns_list_of_tables() throws { - let tables = try db.tableInfo() - XCTAssertEqual(tables, ["users"]) - } - func test_foreign_key_info_empty() throws { try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") @@ -136,8 +131,4 @@ class ConnectionSchemaTests: SQLiteTestCase { .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) ]) } - - func test_sqlite_version() { - XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) - } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 165de4a8..4d4f8d50 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -102,8 +102,12 @@ class SchemaChangerTests: SQLiteTestCase { func test_drop_table() throws { try schemaChanger.drop(table: "users") - - let tables = try db.tableInfo() - XCTAssertFalse(tables.contains("users")) + XCTAssertThrowsError(try db.scalar(users.count)) { error in + if case Result.error(let message, _, _) = error { + XCTAssertEqual(message, "no such table: users") + } else { + XCTFail("unexpected error \(error)") + } + } } } diff --git a/Tests/SQLiteTests/SchemaTests.swift b/Tests/SQLiteTests/Schema/SchemaTests.swift similarity index 100% rename from Tests/SQLiteTests/SchemaTests.swift rename to Tests/SQLiteTests/Schema/SchemaTests.swift diff --git a/Tests/SQLiteTests/AggregateFunctionsTests.swift b/Tests/SQLiteTests/Typed/AggregateFunctionsTests.swift similarity index 100% rename from Tests/SQLiteTests/AggregateFunctionsTests.swift rename to Tests/SQLiteTests/Typed/AggregateFunctionsTests.swift diff --git a/Tests/SQLiteTests/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift similarity index 100% rename from Tests/SQLiteTests/CustomAggregationTests.swift rename to Tests/SQLiteTests/Typed/CustomAggregationTests.swift diff --git a/Tests/SQLiteTests/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift similarity index 100% rename from Tests/SQLiteTests/CustomFunctionsTests.swift rename to Tests/SQLiteTests/Typed/CustomFunctionsTests.swift diff --git a/Tests/SQLiteTests/DateAndTimeFunctionTests.swift b/Tests/SQLiteTests/Typed/DateAndTimeFunctionTests.swift similarity index 100% rename from Tests/SQLiteTests/DateAndTimeFunctionTests.swift rename to Tests/SQLiteTests/Typed/DateAndTimeFunctionTests.swift diff --git a/Tests/SQLiteTests/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift similarity index 100% rename from Tests/SQLiteTests/ExpressionTests.swift rename to Tests/SQLiteTests/Typed/ExpressionTests.swift diff --git a/Tests/SQLiteTests/OperatorsTests.swift b/Tests/SQLiteTests/Typed/OperatorsTests.swift similarity index 100% rename from Tests/SQLiteTests/OperatorsTests.swift rename to Tests/SQLiteTests/Typed/OperatorsTests.swift diff --git a/Tests/SQLiteTests/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift similarity index 100% rename from Tests/SQLiteTests/QueryIntegrationTests.swift rename to Tests/SQLiteTests/Typed/QueryIntegrationTests.swift diff --git a/Tests/SQLiteTests/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift similarity index 100% rename from Tests/SQLiteTests/QueryTests.swift rename to Tests/SQLiteTests/Typed/QueryTests.swift diff --git a/Tests/SQLiteTests/RowTests.swift b/Tests/SQLiteTests/Typed/RowTests.swift similarity index 100% rename from Tests/SQLiteTests/RowTests.swift rename to Tests/SQLiteTests/Typed/RowTests.swift diff --git a/Tests/SQLiteTests/SelectTests.swift b/Tests/SQLiteTests/Typed/SelectTests.swift similarity index 100% rename from Tests/SQLiteTests/SelectTests.swift rename to Tests/SQLiteTests/Typed/SelectTests.swift diff --git a/Tests/SQLiteTests/SetterTests.swift b/Tests/SQLiteTests/Typed/SetterTests.swift similarity index 100% rename from Tests/SQLiteTests/SetterTests.swift rename to Tests/SQLiteTests/Typed/SetterTests.swift From a6e6e687b47944b4c338e23bb054fd925998222e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 26 Jul 2022 00:44:55 +0200 Subject: [PATCH 0903/1046] WIP --- Sources/SQLite/Schema/Connection+Schema.swift | 2 +- Sources/SQLite/Schema/SchemaChanger.swift | 73 ++++++++++++++----- Sources/SQLite/Schema/SchemaDefinitions.swift | 20 +++-- .../Schema/Connection+SchemaTests.swift | 18 ++--- .../Schema/SchemaChangerTests.swift | 30 +++++++- .../Schema/SchemaDefinitionsTests.swift | 26 +++---- 6 files changed, 119 insertions(+), 50 deletions(-) diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index c3b24f38..378fad68 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -24,7 +24,7 @@ extension Connection { return ColumnDefinition(name: name, primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, type: ColumnDefinition.Affinity.from(type), - null: notNull == 0, + nullable: notNull == 0, defaultValue: .from(defaultValue), references: foreignKeys[name]?.first) } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index c6529fbe..daba2f60 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -27,36 +27,63 @@ import Foundation 12. If foreign keys constraints were originally enabled, reenable them now. */ public class SchemaChanger: CustomStringConvertible { - enum SchemaChangeError: LocalizedError { + public enum Error: LocalizedError { + case invalidColumnDefinition(String) case foreignKeyError([ForeignKeyError]) - var errorDescription: String? { + public var errorDescription: String? { switch self { case .foreignKeyError(let errors): return "Foreign key errors: \(errors)" + case .invalidColumnDefinition(let message): + return "Invalid column definition: \(message)" } } } public enum Operation { - case none - case add(ColumnDefinition) - case remove(String) + case addColumn(ColumnDefinition) + case dropColumn(String) case renameColumn(String, String) case renameTable(String) /// Returns non-nil if the operation can be executed with a simple SQL statement func toSQL(_ table: String, version: SQLiteVersion) -> String? { switch self { - case .add(let definition): + case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" case .renameColumn(let from, let to) where version >= (3, 25, 0): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .remove(let column) where version >= (3, 35, 0): + case .dropColumn(let column) where version >= (3, 35, 0): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } } + + func validate() throws { + switch self { + case .addColumn(let definition): + // The new column may take any of the forms permissible in a CREATE TABLE statement, with the following restrictions: + // - The column may not have a PRIMARY KEY or UNIQUE constraint. + // - The column may not have a default value of CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP, or an expression in parentheses + // - If a NOT NULL constraint is specified, then the column must have a default value other than NULL. + guard definition.primaryKey == nil else { + throw Error.invalidColumnDefinition("can not add primary key column") + } + let invalidValues: [LiteralValue] = [.CURRENT_TIME, .CURRENT_DATE, .CURRENT_TIMESTAMP] + if invalidValues.contains(definition.defaultValue) { + throw Error.invalidColumnDefinition("Invalid default value") + } + if !definition.nullable && definition.defaultValue == .NULL { + throw Error.invalidColumnDefinition("NOT NULL columns must have a default value other than NULL") + } + case .dropColumn: + // The DROP COLUMN command only works if the column is not referenced by any other parts of the schema + // and is not a PRIMARY KEY and does not have a UNIQUE constraint + break + default: break + } + } } public class AlterTableDefinition { @@ -69,11 +96,11 @@ public class SchemaChanger: CustomStringConvertible { } public func add(_ column: ColumnDefinition) { - operations.append(.add(column)) + operations.append(.addColumn(column)) } public func remove(_ column: String) { - operations.append(.remove(column)) + operations.append(.dropColumn(column)) } public func rename(_ column: String, to: String) { @@ -116,7 +143,15 @@ public class SchemaChanger: CustomStringConvertible { try dropTable(table) } + // Beginning with release 3.25.0 (2018-09-15), references to the table within trigger bodies and + // view definitions are also renamed. + public func rename(table: String, to: String) throws { + try connection.run("ALTER TABLE \(table.quote()) RENAME TO \(to.quote())") + } + private func run(table: String, operation: Operation) throws { + try operation.validate() + if let sql = operation.toSQL(table, version: version) { try connection.run(sql) } else { @@ -129,10 +164,10 @@ public class SchemaChanger: CustomStringConvertible { try disableRefIntegrity { let tempTable = "\(SchemaChanger.tempPrefix)\(table)" try moveTable(from: table, to: tempTable, options: [.temp], operation: operation) - try moveTable(from: tempTable, to: table) + try rename(table: tempTable, to: table) let foreignKeyErrors = try connection.foreignKeyCheck() if foreignKeyErrors.count > 0 { - throw SchemaChangeError.foreignKeyError(foreignKeyErrors) + throw Error.foreignKeyError(foreignKeyErrors) } } } @@ -153,22 +188,24 @@ public class SchemaChanger: CustomStringConvertible { try block() } - private func moveTable(from: String, to: String, options: Options = .default, operation: Operation = .none) throws { + private func moveTable(from: String, to: String, options: Options = .default, operation: Operation? = nil) throws { try copyTable(from: from, to: to, options: options, operation: operation) try dropTable(from) } - private func copyTable(from: String, to: String, options: Options = .default, operation: Operation) throws { + private func copyTable(from: String, to: String, options: Options = .default, operation: Operation?) throws { let fromDefinition = TableDefinition( name: from, columns: try connection.columnInfo(table: from), indexes: try connection.indexInfo(table: from) ) - let toDefinition = fromDefinition.apply(.renameTable(to)).apply(operation) + let toDefinition = fromDefinition + .apply(.renameTable(to)) + .apply(operation) try createTable(definition: toDefinition, options: options) try createTableIndexes(definition: toDefinition) - if case .remove = operation { + if case .dropColumn = operation { try copyTableContents(from: fromDefinition.apply(operation), to: toDefinition) } else { try copyTableContents(from: fromDefinition, to: toDefinition) @@ -221,11 +258,11 @@ extension IndexDefinition { } extension TableDefinition { - func apply(_ operation: SchemaChanger.Operation) -> TableDefinition { + func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self - case .add: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") - case .remove(let column): + case .addColumn: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") + case .dropColumn(let column): return TableDefinition(name: name, columns: columns.filter { $0.name != column }, indexes: indexes.filter { !$0.columns.contains(column) } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 156a06fb..a06487b2 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -84,23 +84,27 @@ public struct ColumnDefinition: Equatable { public let name: String public let primaryKey: PrimaryKey? public let type: Affinity - public let null: Bool + public let nullable: Bool public let defaultValue: LiteralValue public let references: ForeignKey? - public init(name: String, primaryKey: PrimaryKey?, type: Affinity, null: Bool, defaultValue: LiteralValue, - references: ForeignKey?) { + public init(name: String, + primaryKey: PrimaryKey? = nil, + type: Affinity, + nullable: Bool = false, + defaultValue: LiteralValue = .NULL, + references: ForeignKey? = nil) { self.name = name self.primaryKey = primaryKey self.type = type - self.null = null + self.nullable = nullable self.defaultValue = defaultValue self.references = references } func rename(from: String, to: String) -> ColumnDefinition { guard from == name else { return self } - return ColumnDefinition(name: to, primaryKey: primaryKey, type: type, null: null, defaultValue: defaultValue, references: references) + return ColumnDefinition(name: to, primaryKey: primaryKey, type: type, nullable: nullable, defaultValue: defaultValue, references: references) } } @@ -254,12 +258,12 @@ public struct IndexDefinition: Equatable { } } -struct ForeignKeyError: CustomStringConvertible { +public struct ForeignKeyError: CustomStringConvertible { let from: String let rowId: Int64 let to: String - var description: String { + public var description: String { "\(from) [\(rowId)] => \(to)" } } @@ -294,7 +298,7 @@ extension ColumnDefinition { type.rawValue, defaultValue.map { "DEFAULT \($0)" }, primaryKey.map { $0.toSQL() }, - null ? nil : "NOT NULL", + nullable ? nil : "NOT NULL", references.map { $0.toSQL() } ].compactMap { $0 } .joined(separator: " ") diff --git a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 56e79734..6eded6b2 100644 --- a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -14,42 +14,42 @@ class ConnectionSchemaTests: SQLiteTestCase { ColumnDefinition(name: "id", primaryKey: .init(autoIncrement: false, onConflict: nil), type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "email", primaryKey: nil, type: .TEXT, - null: false, + nullable: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "age", primaryKey: nil, type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "salary", primaryKey: nil, type: .REAL, - null: true, + nullable: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "admin", primaryKey: nil, type: .TEXT, - null: false, + nullable: false, defaultValue: .numericLiteral("0"), references: nil), ColumnDefinition(name: "manager_id", primaryKey: nil, type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, type: .TEXT, - null: true, + nullable: true, defaultValue: .NULL, references: nil) ]) @@ -64,7 +64,7 @@ class ConnectionSchemaTests: SQLiteTestCase { name: "id", primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil) ] @@ -80,7 +80,7 @@ class ConnectionSchemaTests: SQLiteTestCase { name: "id", primaryKey: .init(autoIncrement: false), type: .INTEGER, - null: true, + nullable: true, defaultValue: .NULL, references: nil) ] diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 4d4f8d50..038a8844 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -90,7 +90,11 @@ class SchemaChangerTests: SQLiteTestCase { } func test_add_column() throws { - let newColumn = ColumnDefinition(name: "new_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil) + let column = Expression("new_column") + let newColumn = ColumnDefinition(name: "new_column", + type: .TEXT, + nullable: true, + defaultValue: .stringLiteral("foo")) try schemaChanger.alter(table: "users") { table in table.add(newColumn) @@ -98,6 +102,24 @@ class SchemaChangerTests: SQLiteTestCase { let columns = try db.columnInfo(table: "users") XCTAssertTrue(columns.contains(newColumn)) + + XCTAssertEqual(try db.pluck(users.select(column))?[column], "foo") + } + + func test_add_column_primary_key_fails() throws { + let newColumn = ColumnDefinition(name: "new_column", + primaryKey: .init(autoIncrement: false, onConflict: nil), + type: .TEXT) + + XCTAssertThrowsError(try schemaChanger.alter(table: "users") { table in + table.add(newColumn) + }) { error in + if case SchemaChanger.Error.invalidColumnDefinition(_) = error { + XCTAssertEqual("Invalid column definition: can not add primary key column", error.localizedDescription) + } else { + XCTFail("invalid error: \(error)") + } + } } func test_drop_table() throws { @@ -110,4 +132,10 @@ class SchemaChangerTests: SQLiteTestCase { } } } + + func test_rename_table() throws { + try schemaChanger.rename(table: "users", to: "users_new") + let users_new = Table("users_new") + XCTAssertEqual((try db.scalar(users_new.count)) as Int, 1) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 645bfc34..9c8c3041 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -6,26 +6,26 @@ class ColumnDefinitionTests: XCTestCase { var expected: String! static let definitions: [(ColumnDefinition, String)] = [ - (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), + (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil), "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), - (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, + (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: true, defaultValue: .NULL, references: nil), + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil), "\"text\" TEXT"), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, null: false, defaultValue: .NULL, references: nil), + (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: false, defaultValue: .NULL, references: nil), "\"text\" TEXT NOT NULL"), - (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, null: true, defaultValue: .stringLiteral("fo\"o"), references: nil), + (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .stringLiteral("fo\"o"), references: nil), "\"text_column\" TEXT DEFAULT 'fo\"o'"), - (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, null: true, defaultValue: .numericLiteral("123"), references: nil), + (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, defaultValue: .numericLiteral("123"), references: nil), "\"integer_column\" INTEGER DEFAULT 123"), - (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, null: true, defaultValue: .numericLiteral("123.123"), references: nil), + (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, defaultValue: .numericLiteral("123.123"), references: nil), "\"real_column\" REAL DEFAULT 123.123") ] @@ -184,8 +184,8 @@ class ForeignKeyDefinitionTests: XCTestCase { class TableDefinitionTests: XCTestCase { func test_quoted_columnList() { let definition = TableDefinition(name: "foo", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil), - ColumnDefinition(name: "baz", primaryKey: nil, type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil), + ColumnDefinition(name: "baz", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(definition.quotedColumnList, """ @@ -195,7 +195,7 @@ class TableDefinitionTests: XCTestCase { func test_toSQL() { let definition = TableDefinition(name: "foo", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(definition.toSQL(), """ @@ -205,7 +205,7 @@ class TableDefinitionTests: XCTestCase { func test_toSQL_temp_table() { let definition = TableDefinition(name: "foo", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(definition.toSQL(temporary: true), """ @@ -222,11 +222,11 @@ class TableDefinitionTests: XCTestCase { func test_copySQL() { let from = TableDefinition(name: "from_table", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) let to = TableDefinition(name: "to_table", columns: [ - ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, null: false, defaultValue: .NULL, references: nil) + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) ], indexes: []) XCTAssertEqual(from.copySQL(to: to), """ From 17a2cb8985b7188a61ddee5af1f9d549b23ab54d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 26 Jul 2022 00:47:57 +0200 Subject: [PATCH 0904/1046] Lint --- Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 9c8c3041..75321440 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -22,10 +22,12 @@ class ColumnDefinitionTests: XCTestCase { (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .stringLiteral("fo\"o"), references: nil), "\"text_column\" TEXT DEFAULT 'fo\"o'"), - (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, defaultValue: .numericLiteral("123"), references: nil), + (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, + defaultValue: .numericLiteral("123"), references: nil), "\"integer_column\" INTEGER DEFAULT 123"), - (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, defaultValue: .numericLiteral("123.123"), references: nil), + (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, + defaultValue: .numericLiteral("123.123"), references: nil), "\"real_column\" REAL DEFAULT 123.123") ] From b5a83033d5727fb7a201e3700baa678e2e861bca Mon Sep 17 00:00:00 2001 From: Andrew Vanderbilt <105957236+a-vanderbilt@users.noreply.github.com> Date: Fri, 29 Jul 2022 20:12:52 -0400 Subject: [PATCH 0905/1046] Fixed typo and added/clarified code comment. Small typo fix (iff -> if). Added code comment, then modified existing code comment to make the intent of the code clearer. --- Documentation/Index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 29b18c96..80f8d9f9 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -288,11 +288,13 @@ On macOS, you can use your app’s **Application Support** directory: ```swift + +// set the path corresponding to application support. var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true ).first! + "/" + Bundle.main.bundleIdentifier! -// create parent directory iff it doesn’t exist +// create parent directory inside application support if it doesn’t exist try FileManager.default.createDirectory( atPath: path, withIntermediateDirectories: true, attributes: nil ) From f2609b9e520c4dcf23b5f57e5b1e9dd1a12db147 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 30 Jul 2022 09:09:59 +0200 Subject: [PATCH 0906/1046] Quick update --- Documentation/Index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 80f8d9f9..f90b5960 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -288,8 +288,7 @@ On macOS, you can use your app’s **Application Support** directory: ```swift - -// set the path corresponding to application support. +// set the path corresponding to application support var path = NSSearchPathForDirectoriesInDomains( .applicationSupportDirectory, .userDomainMask, true ).first! + "/" + Bundle.main.bundleIdentifier! From 607a37aca433adffc83cc1c10ddd07a09774b322 Mon Sep 17 00:00:00 2001 From: Goban Date: Tue, 30 Aug 2022 12:04:13 +0900 Subject: [PATCH 0907/1046] Update Index.md 'pathForResource(_:ofType:)' has been renamed to 'path(forResource:ofType:)' --- Documentation/Index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index f90b5960..d6374e6e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -308,7 +308,7 @@ into your Xcode project and added it to your application target), you can establish a _read-only_ connection to it. ```swift -let path = Bundle.main.pathForResource("db", ofType: "sqlite3")! +let path = Bundle.main.path(forResource: "db", ofType: "sqlite3")! let db = try Connection(path, readonly: true) ``` From 6a02c202c34c4dc81272a3e792025ee6257bdee9 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 30 Aug 2022 09:06:19 +0200 Subject: [PATCH 0908/1046] Fix linting --- Sources/SQLite/Typed/Query.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index cfa7544e..7cb2aef3 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1035,11 +1035,9 @@ extension Connection { select.clauses.select = (false, [Expression(literal: "*") as Expressible]) let queries = [select] + query.clauses.join.map { $0.query } if !namespace.isEmpty { - for q in queries { - if q.tableName().expression.template == namespace { - try expandGlob(true)(q) - continue column - } + for q in queries where q.tableName().expression.template == namespace { + try expandGlob(true)(q) + continue column } throw QueryError.noSuchTable(name: namespace) } From f25798f3d047a923976d55f4ba9b8ab9e25e0428 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 18 Sep 2022 23:51:49 +0200 Subject: [PATCH 0909/1046] SQLiteVersion type --- SQLite.xcodeproj/project.pbxproj | 10 +++++++++ Sources/SQLite/Core/Connection+Pragmas.swift | 5 ++--- Sources/SQLite/Core/SQLiteVersion.swift | 22 +++++++++++++++++++ Sources/SQLite/Schema/Connection+Schema.swift | 16 ++++++++++++++ Sources/SQLite/Schema/SchemaChanger.swift | 4 ++-- .../Core/Connection+PragmaTests.swift | 2 +- .../Schema/SchemaChangerTests.swift | 4 ++-- 7 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 Sources/SQLite/Core/SQLiteVersion.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index bc79473e..d2005575 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -199,6 +199,10 @@ 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -325,6 +329,7 @@ 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -569,6 +574,7 @@ 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, + DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */, 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */, ); path = Core; @@ -954,6 +960,7 @@ 02A43A9A22738CF100FEC494 /* Backup.swift in Sources */, 19A17FF4A10B44D3937C8CAC /* Errors.swift in Sources */, 19A1737286A74F3CF7412906 /* DateAndTimeFunctions.swift in Sources */, + DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 19A17073552293CA063BEA66 /* Result.swift in Sources */, 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, @@ -1012,6 +1019,7 @@ 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, + DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, 3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */, 3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */, @@ -1069,6 +1077,7 @@ 02A43A9822738CF100FEC494 /* Backup.swift in Sources */, 19A1792C0520D4E83C2EB075 /* Errors.swift in Sources */, 19A17E29278A12BC4F542506 /* DateAndTimeFunctions.swift in Sources */, + DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, @@ -1146,6 +1155,7 @@ 02A43A9922738CF100FEC494 /* Backup.swift in Sources */, 19A17490543609FCED53CACC /* Errors.swift in Sources */, 19A17152E32A9585831E3FE0 /* DateAndTimeFunctions.swift in Sources */, + DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, diff --git a/Sources/SQLite/Core/Connection+Pragmas.swift b/Sources/SQLite/Core/Connection+Pragmas.swift index 43ff9610..8f6d854b 100644 --- a/Sources/SQLite/Core/Connection+Pragmas.swift +++ b/Sources/SQLite/Core/Connection+Pragmas.swift @@ -1,7 +1,6 @@ import Foundation public typealias UserVersion = Int32 -public typealias SQLiteVersion = (Int, Int, Int) public extension Connection { /// The user version of the database. @@ -21,9 +20,9 @@ public extension Connection { guard let version = (try? scalar("SELECT sqlite_version()")) as? String, let splits = .some(version.split(separator: ".", maxSplits: 3)), splits.count == 3, let major = Int(splits[0]), let minor = Int(splits[1]), let point = Int(splits[2]) else { - return (0, 0, 0) + return .zero } - return (major, minor, point) + return .init(major: major, minor: minor, point: point) } // Changing the foreign_keys setting affects the execution of all statements prepared using the database diff --git a/Sources/SQLite/Core/SQLiteVersion.swift b/Sources/SQLite/Core/SQLiteVersion.swift new file mode 100644 index 00000000..fa7358da --- /dev/null +++ b/Sources/SQLite/Core/SQLiteVersion.swift @@ -0,0 +1,22 @@ +import Foundation + +public struct SQLiteVersion: Comparable, CustomStringConvertible { + public let major: Int + public let minor: Int + public var point: Int = 0 + + public var description: String { + "SQLite \(major).\(minor).\(point)" + } + + public static func <(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool { + lhs.tuple < rhs.tuple + } + + public static func ==(lhs: SQLiteVersion, rhs: SQLiteVersion) -> Bool { + lhs.tuple == rhs.tuple + } + + static var zero: SQLiteVersion = .init(major: 0, minor: 0) + private var tuple: (Int, Int, Int) { (major, minor, point) } +} diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 378fad68..d92236ee 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -80,6 +80,16 @@ extension Connection { } } + func tableInfo() throws -> [String] { + try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in + if let name = row[0] as? String, !name.starts(with: "sqlite_") { + return name + } else { + return nil + } + } + } + // https://sqlite.org/pragma.html#pragma_foreign_key_check // There are four columns in each result row. @@ -99,6 +109,12 @@ extension Connection { } } + // https://sqlite.org/pragma.html#pragma_integrity_check + func integrityCheck() throws -> [String] { + try run("PRAGMA integrity_check").compactMap { $0[0] as? String }.filter { $0 != "ok" } + } + + private func createTableSQL(name: String) throws -> String? { try run(""" SELECT sql FROM sqlite_master WHERE name=? AND type='table' diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index daba2f60..991e3338 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -52,9 +52,9 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .renameColumn(let from, let to) where version >= (3, 25, 0): + case .renameColumn(let from, let to) where version >= .init(major: 3, minor: 25): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .dropColumn(let column) where version >= (3, 35, 0): + case .dropColumn(let column) where version >= .init(major: 3, minor: 35): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index 2d0742c9..2bcdb6af 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -19,7 +19,7 @@ class ConnectionPragmaTests: SQLiteTestCase { } func test_sqlite_version() { - XCTAssertTrue(db.sqliteVersion >= (3, 0, 0)) + XCTAssertTrue(db.sqliteVersion >= .init(major: 3, minor: 0)) } func test_foreignKeys_defaults_to_false() { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 038a8844..738588fd 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -58,7 +58,7 @@ class SchemaChangerTests: SQLiteTestCase { } func test_remove_column_legacy() throws { - schemaChanger = .init(connection: db, version: (3, 24, 0)) // DROP COLUMN introduced in 3.35.0 + schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // DROP COLUMN introduced in 3.35.0 try schemaChanger.alter(table: "users") { table in table.remove("age") @@ -78,7 +78,7 @@ class SchemaChangerTests: SQLiteTestCase { } func test_rename_column_legacy() throws { - schemaChanger = .init(connection: db, version: (3, 24, 0)) // RENAME COLUMN introduced in 3.25.0 + schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // RENAME COLUMN introduced in 3.25.0 try schemaChanger.alter(table: "users") { table in table.rename("age", to: "age2") From ec35c7ea060f08a3f36382564f2a69206da7aa2b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 18 Sep 2022 23:57:43 +0200 Subject: [PATCH 0910/1046] Lint --- Sources/SQLite/Schema/Connection+Schema.swift | 1 - Sources/SQLite/Typed/Query.swift | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index d92236ee..9c945e18 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -114,7 +114,6 @@ extension Connection { try run("PRAGMA integrity_check").compactMap { $0[0] as? String }.filter { $0 != "ok" } } - private func createTableSQL(name: String) throws -> String? { try run(""" SELECT sql FROM sqlite_master WHERE name=? AND type='table' diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index cfa7544e..7cb2aef3 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1035,11 +1035,9 @@ extension Connection { select.clauses.select = (false, [Expression(literal: "*") as Expressible]) let queries = [select] + query.clauses.join.map { $0.query } if !namespace.isEmpty { - for q in queries { - if q.tableName().expression.template == namespace { - try expandGlob(true)(q) - continue column - } + for q in queries where q.tableName().expression.template == namespace { + try expandGlob(true)(q) + continue column } throw QueryError.noSuchTable(name: namespace) } From 00a86686c4cf88caf09cde6ad0f50d4064a8b653 Mon Sep 17 00:00:00 2001 From: Michael Henry Pantaleon Date: Wed, 12 Oct 2022 23:20:23 +1100 Subject: [PATCH 0911/1046] Fix playground example as the prepare func can throw an error --- SQLite.playground/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index c089076d..945adb50 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -99,5 +99,5 @@ db.createAggregation("customConcat", initialValue: "users:", reduce: reduce, result: { $0 }) -let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +let result = try db.prepare("SELECT customConcat(email) FROM users").scalar() as! String print(result) From d7c26353330ea851691f3f0359aa13e7dbedced5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 16 Oct 2022 02:24:31 +0200 Subject: [PATCH 0912/1046] Split into classes, improve queries --- Makefile | 2 + SQLite.xcodeproj/project.pbxproj | 10 + Sources/SQLite/Schema/Connection+Schema.swift | 129 ++----------- Sources/SQLite/Schema/SchemaChanger.swift | 6 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 107 ++++++---- Sources/SQLite/Schema/SchemaReader.swift | 182 ++++++++++++++++++ Sources/SQLite/Typed/Query.swift | 17 ++ Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- .../Schema/Connection+SchemaTests.swift | 140 +++----------- .../Schema/SchemaChangerTests.swift | 24 +-- .../Schema/SchemaDefinitionsTests.swift | 132 ++++++++----- .../Schema/SchemaReaderTests.swift | 180 +++++++++++++++++ 12 files changed, 603 insertions(+), 328 deletions(-) create mode 100644 Sources/SQLite/Schema/SchemaReader.swift create mode 100644 Tests/SQLiteTests/Schema/SchemaReaderTests.swift diff --git a/Makefile b/Makefile index c62da000..74bf5d18 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,8 @@ build: lint: swiftlint --strict +lint-fix: + swiftlint lint fix test: ifdef XCPRETTY diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index d2005575..a58e0887 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -199,6 +199,10 @@ 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; @@ -329,6 +333,7 @@ 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -429,6 +434,7 @@ 19A1792D261C689FC988A90A /* Schema */ = { isa = PBXGroup; children = ( + DB58B21028FB864300F8EEA4 /* SchemaReader.swift */, 19A171B262DDE8718513CFDA /* SchemaChanger.swift */, 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */, 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */, @@ -968,6 +974,7 @@ 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, 19A17986405D9A875698408F /* Connection+Pragmas.swift in Sources */, + DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1018,6 +1025,7 @@ 997DF2B1287FC06D00F8DF95 /* Query+with.swift in Sources */, 3D67B3F71DB246D700A4F4C6 /* Foundation.swift in Sources */, 3D67B3F81DB246D700A4F4C6 /* Helpers.swift in Sources */, + DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */, 3D67B3E91DB246D100A4F4C6 /* Statement.swift in Sources */, DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, @@ -1085,6 +1093,7 @@ 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, 19A17018F250343BD0F9F4B0 /* Connection+Pragmas.swift in Sources */, + DB58B21128FB864300F8EEA4 /* SchemaReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1163,6 +1172,7 @@ 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, 19A1760CE25615CA015E2E5F /* Connection+Pragmas.swift in Sources */, + DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 9c945e18..868b40d5 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,96 +1,7 @@ import Foundation -extension Connection { - // https://sqlite.org/pragma.html#pragma_table_info - // - // This pragma returns one row for each column in the named table. Columns in the result set include the - // column name, data type, whether or not the column can be NULL, and the default value for the column. The - // "pk" column in the result set is zero for columns that are not part of the primary key, and is the - // index of the column in the primary key for columns that are part of the primary key. - func columnInfo(table: String) throws -> [ColumnDefinition] { - func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { - try createTableSQL(name: table).flatMap { .init(sql: $0) } - } - - let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = - Dictionary(grouping: try foreignKeyInfo(table: table), by: { $0.column }) - - return try run("PRAGMA table_info(\(table.quote()))").compactMap { row -> ColumnDefinition? in - guard let name = row[1] as? String, - let type = row[2] as? String, - let notNull = row[3] as? Int64, - let defaultValue = row[4] as? String?, - let primaryKey = row[5] as? Int64 else { return nil } - return ColumnDefinition(name: name, - primaryKey: primaryKey == 1 ? try parsePrimaryKey(column: name) : nil, - type: ColumnDefinition.Affinity.from(type), - nullable: notNull == 0, - defaultValue: .from(defaultValue), - references: foreignKeys[name]?.first) - } - } - - func indexInfo(table: String) throws -> [IndexDefinition] { - func indexSQL(name: String) throws -> String? { - try run(""" - SELECT sql FROM sqlite_master WHERE name=? AND type='index' - UNION ALL - SELECT sql FROM sqlite_temp_master WHERE name=? AND type='index' - """, name, name) - .compactMap { row in row[0] as? String } - .first - } - - func columns(name: String) throws -> [String] { - try run("PRAGMA index_info(\(name.quote()))").compactMap { row in - row[2] as? String - } - } - - return try run("PRAGMA index_list(\(table.quote()))").compactMap { row -> IndexDefinition? in - guard let name = row[1] as? String, - let unique = row[2] as? Int64, - // Indexes SQLite creates implicitly for internal use start with "sqlite_". - // See https://www.sqlite.org/fileformat2.html#intschema - !name.starts(with: "sqlite_") else { - return nil - } - return .init(table: table, - name: name, - unique: unique == 1, - columns: try columns(name: name), - indexSQL: try indexSQL(name: name)) - } - } - - func foreignKeyInfo(table: String) throws -> [ColumnDefinition.ForeignKey] { - try run("PRAGMA foreign_key_list(\(table.quote()))").compactMap { row in - if let table = row[2] as? String, // table - let column = row[3] as? String, // from - let primaryKey = row[4] as? String, // to - let onUpdate = row[5] as? String, - let onDelete = row[6] as? String { - return .init(table: table, column: column, primaryKey: primaryKey, - onUpdate: onUpdate == TableBuilder.Dependency.noAction.rawValue ? nil : onUpdate, - onDelete: onDelete == TableBuilder.Dependency.noAction.rawValue ? nil : onDelete - ) - } else { - return nil - } - } - } - - func tableInfo() throws -> [String] { - try run("SELECT tbl_name FROM sqlite_master WHERE type = 'table'").compactMap { row in - if let name = row[0] as? String, !name.starts(with: "sqlite_") { - return name - } else { - return nil - } - } - } - - // https://sqlite.org/pragma.html#pragma_foreign_key_check +public extension Connection { + var schemaReader: SchemaReader { SchemaReader(connection: self) } // There are four columns in each result row. // The first column is the name of the table that @@ -99,28 +10,24 @@ extension Connection { // invalid REFERENCES clause, or NULL if the child table is a WITHOUT ROWID table. // The third column is the name of the table that is referred to. // The fourth column is the index of the specific foreign key constraint that failed. - func foreignKeyCheck() throws -> [ForeignKeyError] { - try run("PRAGMA foreign_key_check").compactMap { row -> ForeignKeyError? in - guard let table = row[0] as? String, - let rowId = row[1] as? Int64, - let target = row[2] as? String else { return nil } - - return ForeignKeyError(from: table, rowId: rowId, to: target) - } + // + // https://sqlite.org/pragma.html#pragma_foreign_key_check + func foreignKeyCheck(table: String? = nil) throws -> [ForeignKeyError] { + try run("PRAGMA foreign_key_check" + (table.map { "(\($0.quote()))" } ?? "")) + .compactMap { row -> ForeignKeyError? in + guard let table = row[0] as? String, + let rowId = row[1] as? Int64, + let target = row[2] as? String else { return nil } + + return ForeignKeyError(from: table, rowId: rowId, to: target) + } } + // This pragma does a low-level formatting and consistency check of the database. // https://sqlite.org/pragma.html#pragma_integrity_check - func integrityCheck() throws -> [String] { - try run("PRAGMA integrity_check").compactMap { $0[0] as? String }.filter { $0 != "ok" } - } - - private func createTableSQL(name: String) throws -> String? { - try run(""" - SELECT sql FROM sqlite_master WHERE name=? AND type='table' - UNION ALL - SELECT sql FROM sqlite_temp_master WHERE name=? AND type='table' - """, name, name) - .compactMap { row in row[0] as? String } - .first + func integrityCheck(table: String? = nil, maxErrors: Int? = nil) throws -> [String] { + try run("PRAGMA integrity_check" + (table.map { "(\($0.quote()))" } ?? "")) + .compactMap { $0[0] as? String } + .filter { $0 != "ok" } } } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 991e3338..b557da0a 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -109,6 +109,7 @@ public class SchemaChanger: CustomStringConvertible { } private let connection: Connection + private let schemaReader: SchemaReader private let version: SQLiteVersion static let tempPrefix = "tmp_" typealias Block = () throws -> Void @@ -127,6 +128,7 @@ public class SchemaChanger: CustomStringConvertible { init(connection: Connection, version: SQLiteVersion) { self.connection = connection + schemaReader = connection.schemaReader self.version = version } @@ -196,8 +198,8 @@ public class SchemaChanger: CustomStringConvertible { private func copyTable(from: String, to: String, options: Options = .default, operation: Operation?) throws { let fromDefinition = TableDefinition( name: from, - columns: try connection.columnInfo(table: from), - indexes: try connection.indexInfo(table: from) + columns: try schemaReader.columnDefinitions(table: from), + indexes: try schemaReader.indexDefinitions(table: from) ) let toDefinition = fromDefinition .apply(.renameTable(to)) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index a06487b2..fe2e3931 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -10,6 +10,29 @@ struct TableDefinition: Equatable { } } +// https://sqlite.org/schematab.html#interpretation_of_the_schema_table +public struct ObjectDefinition: Equatable { + public enum ObjectType: String { + case table, index, view, trigger + } + public let type: ObjectType + + // The name of the object + public let name: String + + // The name of a table or view that the object is associated with. + // * For a table or view, a copy of the name column. + // * For an index, the name of the table that is indexed + // * For a trigger, the column stores the name of the table or view that causes the trigger to fire. + public let tableName: String + + // The page number of the root b-tree page for tables and indexes, otherwise 0 or NULL + public let rootpage: Int64 + + // SQL text that describes the object (NULL for the internal indexes) + public let sql: String? +} + // https://sqlite.org/syntax/column-def.html // column-name -> type-name -> column-constraint* public struct ColumnDefinition: Equatable { @@ -29,8 +52,8 @@ public struct ColumnDefinition: Equatable { rawValue } - static func from(_ string: String) -> Affinity { - Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? TEXT + init(_ string: String) { + self = Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? .TEXT } } @@ -41,8 +64,9 @@ public struct ColumnDefinition: Equatable { case IGNORE case REPLACE - static func from(_ string: String) -> OnConflict? { - OnConflict.allCases.first { $0.rawValue == string } + init?(_ string: String) { + guard let value = (OnConflict.allCases.first { $0.rawValue == string }) else { return nil } + self = value } } @@ -59,17 +83,20 @@ public struct ColumnDefinition: Equatable { } init?(sql: String) { - if let match = PrimaryKey.pattern.firstMatch(in: sql, range: NSRange(location: 0, length: sql.count)) { - let conflict = match.range(at: 1) - var onConflict: ColumnDefinition.OnConflict? - if conflict.location != NSNotFound { - onConflict = .from((sql as NSString).substring(with: conflict)) - } - let autoIncrement = match.range(at: 2).location != NSNotFound - self.init(autoIncrement: autoIncrement, onConflict: onConflict) - } else { + guard let match = PrimaryKey.pattern.firstMatch( + in: sql, + range: NSRange(location: 0, length: sql.count)) else { return nil } + let conflict = match.range(at: 1) + let onConflict: ColumnDefinition.OnConflict? + if conflict.location != NSNotFound { + onConflict = OnConflict((sql as NSString).substring(with: conflict)) + } else { + onConflict = nil + } + let autoIncrement = match.range(at: 2).location != NSNotFound + self.init(autoIncrement: autoIncrement, onConflict: onConflict) } } @@ -138,28 +165,19 @@ public enum LiteralValue: Equatable, CustomStringConvertible { case CURRENT_TIMESTAMP // swiftlint:enable identifier_name - static func from(_ string: String?) -> LiteralValue { - func parse(_ value: String) -> LiteralValue { - if let match = singleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { - return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "''", with: "'")) - } else if let match = doubleQuote.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { - return stringLiteral((value as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "\"\"", with: "\"")) - } else if let match = blob.firstMatch(in: value, range: NSRange(location: 0, length: value.count)) { - return blobLiteral((value as NSString).substring(with: match.range(at: 1))) - } else { - return numericLiteral(value) - } + init(_ string: String?) { + guard let string = string else { + self = .NULL + return } - guard let string = string else { return NULL } - switch string { - case "NULL": return NULL - case "TRUE": return TRUE - case "FALSE": return FALSE - case "CURRENT_TIME": return CURRENT_TIME - case "CURRENT_TIMESTAMP": return CURRENT_TIMESTAMP - case "CURRENT_DATE": return CURRENT_DATE - default: return parse(string) + case "NULL": self = .NULL + case "TRUE": self = .TRUE + case "FALSE": self = .FALSE + case "CURRENT_TIME": self = .CURRENT_TIME + case "CURRENT_TIMESTAMP": self = .CURRENT_TIMESTAMP + case "CURRENT_DATE": self = .CURRENT_DATE + default: self = LiteralValue.parse(string) } } @@ -184,6 +202,17 @@ public enum LiteralValue: Equatable, CustomStringConvertible { return block(self) } } + private static func parse(_ string: String) -> LiteralValue { + if let match = LiteralValue.singleQuote.firstMatch(in: string, range: NSRange(location: 0, length: string.count)) { + return .stringLiteral((string as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "''", with: "'")) + } else if let match = LiteralValue.doubleQuote.firstMatch(in: string, range: NSRange(location: 0, length: string.count)) { + return .stringLiteral((string as NSString).substring(with: match.range(at: 1)).replacingOccurrences(of: "\"\"", with: "\"")) + } else if let match = LiteralValue.blob.firstMatch(in: string, range: NSRange(location: 0, length: string.count)) { + return .blobLiteral((string as NSString).substring(with: match.range(at: 1))) + } else { + return .numericLiteral(string) + } + } } // https://sqlite.org/lang_createindex.html @@ -259,9 +288,9 @@ public struct IndexDefinition: Equatable { } public struct ForeignKeyError: CustomStringConvertible { - let from: String - let rowId: Int64 - let to: String + public let from: String + public let rowId: Int64 + public let to: String public var description: String { "\(from) [\(rowId)] => \(to)" @@ -270,7 +299,7 @@ public struct ForeignKeyError: CustomStringConvertible { extension TableDefinition { func toSQL(temporary: Bool = false) -> String { - assert(columns.count > 0, "no columns to create") + precondition(columns.count > 0, "no columns to create") return ([ "CREATE", @@ -285,8 +314,8 @@ extension TableDefinition { } func copySQL(to: TableDefinition) -> String { - assert(columns.count > 0, "no columns to copy") - assert(columns.count == to.columns.count, "column counts don't match") + precondition(columns.count > 0) + precondition(columns.count == to.columns.count, "column counts don't match") return "INSERT INTO \(to.name.quote()) (\(to.quotedColumnList)) SELECT \(quotedColumnList) FROM \(name.quote())" } } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift new file mode 100644 index 00000000..3f02eeae --- /dev/null +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -0,0 +1,182 @@ +import Foundation + +public class SchemaReader { + private let connection: Connection + + init(connection: Connection) { + self.connection = connection + } + + // https://sqlite.org/pragma.html#pragma_table_info + // + // This pragma returns one row for each column in the named table. Columns in the result set include the + // column name, data type, whether or not the column can be NULL, and the default value for the column. The + // "pk" column in the result set is zero for columns that are not part of the primary key, and is the + // index of the column in the primary key for columns that are part of the primary key. + public func columnDefinitions(table: String) throws -> [ColumnDefinition] { + func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { + try createTableSQL(name: table).flatMap { .init(sql: $0) } + } + + let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = + Dictionary(grouping: try foreignKeys(table: table), by: { $0.column }) + + return try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") + .map { (row: Row) -> ColumnDefinition in + ColumnDefinition( + name: row[TableInfoTable.nameColumn], + primaryKey: row[TableInfoTable.primaryKeyColumn] == 1 ? + try parsePrimaryKey(column: row[TableInfoTable.nameColumn]) : nil, + type: ColumnDefinition.Affinity(row[TableInfoTable.typeColumn]), + nullable: row[TableInfoTable.notNullColumn] == 0, + defaultValue: LiteralValue(row[TableInfoTable.defaultValueColumn]), + references: foreignKeys[row[TableInfoTable.nameColumn]]?.first + ) + } + } + + public func objectDefinitions(name: String? = nil, + type: ObjectDefinition.ObjectType? = nil, + temp: Bool = false) throws -> [ObjectDefinition] { + var query: QueryType = temp ? SchemaTable.tempName : SchemaTable.name + if let name = name { + query = query.where(SchemaTable.nameColumn == name) + } + if let type = type { + query = query.where(SchemaTable.typeColumn == type.rawValue) + } + return try connection.prepare(query).map { row -> ObjectDefinition in + guard let type = ObjectDefinition.ObjectType(rawValue: row[SchemaTable.typeColumn]) else { + fatalError("unexpected type") + } + return ObjectDefinition( + type: type, + name: row[SchemaTable.nameColumn], + tableName: row[SchemaTable.nameColumn], + rootpage: row[SchemaTable.rootPageColumn] ?? 0, + sql: row[SchemaTable.sqlColumn] + ) + } + } + + public func indexDefinitions(table: String) throws -> [IndexDefinition] { + func indexSQL(name: String) throws -> String? { + try objectDefinitions(name: name, type: .index) + .compactMap(\.sql) + .first + } + + func columns(name: String) throws -> [String] { + try connection.prepareRowIterator("PRAGMA index_info(\(name.quote()))") + .compactMap { row in + row[IndexInfoTable.nameColumn] + } + } + + return try connection.prepareRowIterator("PRAGMA index_list(\(table.quote()))") + .compactMap { row -> IndexDefinition? in + let name = row[IndexListTable.nameColumn] + guard !name.starts(with: "sqlite_") else { + // Indexes SQLite creates implicitly for internal use start with "sqlite_". + // See https://www.sqlite.org/fileformat2.html#intschema + return nil + } + return IndexDefinition( + table: table, + name: name, + unique: row[IndexListTable.uniqueColumn] == 1, + columns: try columns(name: name), + indexSQL: try indexSQL(name: name) + ) + } + } + + func foreignKeys(table: String) throws -> [ColumnDefinition.ForeignKey] { + try connection.prepareRowIterator("PRAGMA foreign_key_list(\(table.quote()))") + .map { row in + ColumnDefinition.ForeignKey( + table: row[ForeignKeyListTable.tableColumn], + column: row[ForeignKeyListTable.fromColumn], + primaryKey: row[ForeignKeyListTable.toColumn], + onUpdate: row[ForeignKeyListTable.onUpdateColumn] == TableBuilder.Dependency.noAction.rawValue + ? nil : row[ForeignKeyListTable.onUpdateColumn], + onDelete: row[ForeignKeyListTable.onDeleteColumn] == TableBuilder.Dependency.noAction.rawValue + ? nil : row[ForeignKeyListTable.onDeleteColumn] + ) + } + } + + func tableDefinitions() throws -> [TableDefinition] { + try objectDefinitions(type: .table) + .map { table in + TableDefinition( + name: table.name, + columns: try columnDefinitions(table: table.name), + indexes: try indexDefinitions(table: table.name) + ) + } + } + + private func createTableSQL(name: String) throws -> String? { + try ( + objectDefinitions(name: name, type: .table) + + objectDefinitions(name: name, type: .table, temp: true) + ).compactMap(\.sql).first + } +} + +private class SchemaTable { + internal static let name = Table("sqlite_schema", database: "main") + internal static let tempName = Table("sqlite_schema", database: "temp") + + static let typeColumn = Expression("type") + static let nameColumn = Expression("name") + static let tableNameColumn = Expression("tbl_name") + static let rootPageColumn = Expression("rootpage") + static let sqlColumn = Expression("sql") +} + +private class TableInfoTable { + static let idColumn = Expression("cid") + static let nameColumn = Expression("name") + static let typeColumn = Expression("type") + static let notNullColumn = Expression("notnull") + static let defaultValueColumn = Expression("dflt_value") + static let primaryKeyColumn = Expression("pk") +} + +private class IndexInfoTable { + // The rank of the column within the index. (0 means left-most.) + static let seqnoColumn = Expression("seqno") + // The rank of the column within the table being indexed. + // A value of -1 means rowid and a value of -2 means that an expression is being used. + static let cidColumn = Expression("cid") + // The name of the column being indexed. This columns is NULL if the column is the rowid or an expression. + static let nameColumn = Expression("name") +} + +private class IndexListTable { + // A sequence number assigned to each index for internal tracking purposes. + static let seqColumn = Expression("seq") + // The name of the index + static let nameColumn = Expression("name") + // "1" if the index is UNIQUE and "0" if not. + static let uniqueColumn = Expression("unique") + // "c" if the index was created by a CREATE INDEX statement, + // "u" if the index was created by a UNIQUE constraint, or + // "pk" if the index was created by a PRIMARY KEY constraint. + static let originColumn = Expression("origin") + // "1" if the index is a partial index and "0" if not. + static let partialColumn = Expression("partial") +} + +private class ForeignKeyListTable { + static let idColumn = Expression("id") + static let seqColumn = Expression("seq") + static let tableColumn = Expression("table") + static let fromColumn = Expression("from") + static let toColumn = Expression("to") + static let onUpdateColumn = Expression("on_update") + static let onDeleteColumn = Expression("on_delete") + static let matchColumn = Expression("match") +} diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 7cb2aef3..04665f39 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -991,6 +991,15 @@ public struct RowIterator: FailableIterator { } return elements } + + public func compactMap(_ transform: (Element) throws -> T?) throws -> [T] { + var elements = [T]() + while let row = try failableNext() { + guard let element = try transform(row) else { continue } + elements.append(element) + } + return elements + } } extension Connection { @@ -1012,6 +1021,14 @@ extension Connection { return RowIterator(statement: statement, columnNames: try columnNamesForQuery(query)) } + public func prepareRowIterator(_ statement: String, bindings: Binding?...) throws -> RowIterator { + try prepare(statement, bindings).prepareRowIterator() + } + + public func prepareRowIterator(_ statement: String, bindings: [Binding?]) throws -> RowIterator { + try prepare(statement, bindings).prepareRowIterator() + } + private func columnNamesForQuery(_ query: QueryType) throws -> [String: Int] { var (columnNames, idx) = ([String: Int](), 0) column: for each in query.clauses.select.columns { diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index e9ceff08..9d623f3f 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -399,7 +399,7 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(1, try db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) } - func test_interrupt_interruptsLongRunningQuery() throws { + func XXX_test_interrupt_interruptsLongRunningQuery() throws { let semaphore = DispatchSemaphore(value: 0) db.createFunction("sleep") { _ in DispatchQueue.global(qos: .background).async { diff --git a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 6eded6b2..96300529 100644 --- a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -8,127 +8,43 @@ class ConnectionSchemaTests: SQLiteTestCase { try createUsersTable() } - func test_column_info() throws { - let columns = try db.columnInfo(table: "users") - XCTAssertEqual(columns, [ - ColumnDefinition(name: "id", - primaryKey: .init(autoIncrement: false, onConflict: nil), - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "email", - primaryKey: nil, - type: .TEXT, - nullable: false, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "age", - primaryKey: nil, - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "salary", - primaryKey: nil, - type: .REAL, - nullable: true, - defaultValue: .NULL, - references: nil), - ColumnDefinition(name: "admin", - primaryKey: nil, - type: .TEXT, - nullable: false, - defaultValue: .numericLiteral("0"), - references: nil), - ColumnDefinition(name: "manager_id", - primaryKey: nil, type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), - ColumnDefinition(name: "created_at", - primaryKey: nil, - type: .TEXT, - nullable: true, - defaultValue: .NULL, - references: nil) - ]) + func test_foreignKeyCheck() throws { + let errors = try db.foreignKeyCheck() + XCTAssert(errors.isEmpty) } - func test_column_info_parses_conflict_modifier() throws { - try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT)") - - XCTAssertEqual( - try db.columnInfo(table: "t"), [ - ColumnDefinition( - name: "id", - primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil) - ] - ) + func test_foreignKeyCheck_with_table() throws { + let errors = try db.foreignKeyCheck(table: "users") + XCTAssert(errors.isEmpty) } - func test_column_info_detects_missing_autoincrement() throws { - try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") - - XCTAssertEqual( - try db.columnInfo(table: "t"), [ - ColumnDefinition( - name: "id", - primaryKey: .init(autoIncrement: false), - type: .INTEGER, - nullable: true, - defaultValue: .NULL, - references: nil) - ] - ) + func test_foreignKeyCheck_table_not_found() throws { + XCTAssertThrowsError(try db.foreignKeyCheck(table: "xxx")) { error in + guard case Result.error(let message, _, _) = error else { + assertionFailure("invalid error type") + return + } + XCTAssertEqual(message, "no such table: xxx") + } } - func test_index_info_no_index() throws { - let indexes = try db.indexInfo(table: "users") - XCTAssertTrue(indexes.isEmpty) + func test_integrityCheck_global() throws { + let results = try db.integrityCheck() + XCTAssert(results.isEmpty) } - func test_index_info_with_index() throws { - try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") - let indexes = try db.indexInfo(table: "users") - - XCTAssertEqual(indexes, [ - IndexDefinition( - table: "users", - name: "index_users", - unique: true, - columns: ["age"], - where: "age IS NOT NULL", - orders: ["age": .DESC] - ) - ]) + func test_integrityCheck_table() throws { + let results = try db.integrityCheck(table: "users") + XCTAssert(results.isEmpty) } - func test_foreign_key_info_empty() throws { - try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") - - let foreignKeys = try db.foreignKeyInfo(table: "t") - XCTAssertTrue(foreignKeys.isEmpty) - } - - func test_foreign_key_info() throws { - let linkTable = Table("test_links") - - let idColumn = SQLite.Expression("id") - let testIdColumn = SQLite.Expression("test_id") - - try db.run(linkTable.create(block: { definition in - definition.column(idColumn, primaryKey: .autoincrement) - definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) - })) - - let foreignKeys = try db.foreignKeyInfo(table: "test_links") - XCTAssertEqual(foreignKeys, [ - .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) - ]) + func test_integrityCheck_table_not_found() throws { + XCTAssertThrowsError(try db.integrityCheck(table: "xxx")) { error in + guard case Result.error(let message, _, _) = error else { + assertionFailure("invalid error type") + return + } + XCTAssertEqual(message, "no such table: xxx") + } } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 738588fd..af8b6565 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -3,6 +3,7 @@ import XCTest class SchemaChangerTests: SQLiteTestCase { var schemaChanger: SchemaChanger! + var schemaReader: SchemaReader! override func setUpWithError() throws { try super.setUpWithError() @@ -10,32 +11,33 @@ class SchemaChangerTests: SQLiteTestCase { try insertUsers("bob") + schemaReader = SchemaReader(connection: db) schemaChanger = SchemaChanger(connection: db) } func test_empty_migration_does_not_change_column_definitions() throws { - let previous = try db.columnInfo(table: "users") + let previous = try schemaReader.columnDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try db.columnInfo(table: "users") + let current = try schemaReader.columnDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_index_definitions() throws { - let previous = try db.indexInfo(table: "users") + let previous = try schemaReader.indexDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try db.indexInfo(table: "users") + let current = try schemaReader.indexDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_foreign_key_definitions() throws { - let previous = try db.foreignKeyInfo(table: "users") + let previous = try schemaReader.foreignKeys(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try db.foreignKeyInfo(table: "users") + let current = try schemaReader.foreignKeys(table: "users") XCTAssertEqual(previous, current) } @@ -53,7 +55,7 @@ class SchemaChangerTests: SQLiteTestCase { try schemaChanger.alter(table: "users") { table in table.remove("age") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } @@ -63,7 +65,7 @@ class SchemaChangerTests: SQLiteTestCase { try schemaChanger.alter(table: "users") { table in table.remove("age") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } @@ -72,7 +74,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -84,7 +86,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try db.columnInfo(table: "users").map(\.name) + let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -100,7 +102,7 @@ class SchemaChangerTests: SQLiteTestCase { table.add(newColumn) } - let columns = try db.columnInfo(table: "users") + let columns = try schemaReader.columnDefinitions(table: "users") XCTAssertTrue(columns.contains(newColumn)) XCTAssertEqual(try db.pluck(users.select(column))?[column], "foo") diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 75321440..384aab3f 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -5,37 +5,38 @@ class ColumnDefinitionTests: XCTestCase { var definition: ColumnDefinition! var expected: String! - static let definitions: [(ColumnDefinition, String)] = [ - (ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil), - "\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"), + static let definitions: [(String, ColumnDefinition)] = [ + ("\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", + ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil)), - (ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, - references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil)), - "\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")"), + ("\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")", + ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, + references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil))), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil), - "\"text\" TEXT"), + ("\"text\" TEXT", + ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil)), - (ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: false, defaultValue: .NULL, references: nil), - "\"text\" TEXT NOT NULL"), + ("\"text\" TEXT NOT NULL", + ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: false, defaultValue: .NULL, references: nil)), - (ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .stringLiteral("fo\"o"), references: nil), - "\"text_column\" TEXT DEFAULT 'fo\"o'"), + ("\"text_column\" TEXT DEFAULT 'fo\"o'", + ColumnDefinition(name: "text_column", primaryKey: nil, type: .TEXT, nullable: true, + defaultValue: .stringLiteral("fo\"o"), references: nil)), - (ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, - defaultValue: .numericLiteral("123"), references: nil), - "\"integer_column\" INTEGER DEFAULT 123"), + ("\"integer_column\" INTEGER DEFAULT 123", + ColumnDefinition(name: "integer_column", primaryKey: nil, type: .INTEGER, nullable: true, + defaultValue: .numericLiteral("123"), references: nil)), - (ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, - defaultValue: .numericLiteral("123.123"), references: nil), - "\"real_column\" REAL DEFAULT 123.123") + ("\"real_column\" REAL DEFAULT 123.123", + ColumnDefinition(name: "real_column", primaryKey: nil, type: .REAL, nullable: true, + defaultValue: .numericLiteral("123.123"), references: nil)) ] #if !os(Linux) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) - for (column, expected) in ColumnDefinitionTests.definitions { + for (expected, column) in ColumnDefinitionTests.definitions { let test = ColumnDefinitionTests(selector: #selector(verify)) test.definition = column test.expected = expected @@ -51,14 +52,17 @@ class ColumnDefinitionTests: XCTestCase { } class AffinityTests: XCTestCase { - func test_from() { - XCTAssertEqual(ColumnDefinition.Affinity.from("TEXT"), .TEXT) - XCTAssertEqual(ColumnDefinition.Affinity.from("text"), .TEXT) - XCTAssertEqual(ColumnDefinition.Affinity.from("INTEGER"), .INTEGER) + func test_init() { + XCTAssertEqual(ColumnDefinition.Affinity("TEXT"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity("text"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity("INTEGER"), .INTEGER) + XCTAssertEqual(ColumnDefinition.Affinity("BLOB"), .BLOB) + XCTAssertEqual(ColumnDefinition.Affinity("REAL"), .REAL) + XCTAssertEqual(ColumnDefinition.Affinity("NUMERIC"), .NUMERIC) } func test_returns_TEXT_for_unknown_type() { - XCTAssertEqual(ColumnDefinition.Affinity.from("baz"), .TEXT) + XCTAssertEqual(ColumnDefinition.Affinity("baz"), .TEXT) } } @@ -215,13 +219,6 @@ class TableDefinitionTests: XCTestCase { """) } - /* - func test_throws_an_error_when_columns_are_empty() { - let empty = TableDefinition(name: "empty", columns: [], indexes: []) - XCTAssertThrowsError(empty.toSQL()) - } - */ - func test_copySQL() { let from = TableDefinition(name: "from_table", columns: [ ColumnDefinition(name: "id", primaryKey: .init(), type: .INTEGER, nullable: false, defaultValue: .NULL, references: nil) @@ -239,74 +236,105 @@ class TableDefinitionTests: XCTestCase { class PrimaryKeyTests: XCTestCase { func test_toSQL() { - XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false).toSQL(), - "PRIMARY KEY") + XCTAssertEqual( + ColumnDefinition.PrimaryKey(autoIncrement: false).toSQL(), + "PRIMARY KEY" + ) } func test_toSQL_autoincrement() { - XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: true).toSQL(), - "PRIMARY KEY AUTOINCREMENT") + XCTAssertEqual( + ColumnDefinition.PrimaryKey(autoIncrement: true).toSQL(), + "PRIMARY KEY AUTOINCREMENT" + ) } func test_toSQL_on_conflict() { - XCTAssertEqual(ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK).toSQL(), - "PRIMARY KEY ON CONFLICT ROLLBACK") + XCTAssertEqual( + ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK).toSQL(), + "PRIMARY KEY ON CONFLICT ROLLBACK" + ) + } + + func test_fromSQL() { + XCTAssertEqual( + ColumnDefinition.PrimaryKey(sql: "PRIMARY KEY"), + ColumnDefinition.PrimaryKey(autoIncrement: false) + ) + } + + func test_fromSQL_invalid_sql_is_nil() { + XCTAssertNil(ColumnDefinition.PrimaryKey(sql: "FOO")) + } + + func test_fromSQL_autoincrement() { + XCTAssertEqual( + ColumnDefinition.PrimaryKey(sql: "PRIMARY KEY AUTOINCREMENT"), + ColumnDefinition.PrimaryKey(autoIncrement: true) + ) + } + + func test_fromSQL_on_conflict() { + XCTAssertEqual( + ColumnDefinition.PrimaryKey(sql: "PRIMARY KEY ON CONFLICT ROLLBACK"), + ColumnDefinition.PrimaryKey(autoIncrement: false, onConflict: .ROLLBACK) + ) } } class LiteralValueTests: XCTestCase { func test_recognizes_TRUE() { - XCTAssertEqual(LiteralValue.from("TRUE"), .TRUE) + XCTAssertEqual(LiteralValue("TRUE"), .TRUE) } func test_recognizes_FALSE() { - XCTAssertEqual(LiteralValue.from("FALSE"), .FALSE) + XCTAssertEqual(LiteralValue("FALSE"), .FALSE) } func test_recognizes_NULL() { - XCTAssertEqual(LiteralValue.from("NULL"), .NULL) + XCTAssertEqual(LiteralValue("NULL"), .NULL) } func test_recognizes_nil() { - XCTAssertEqual(LiteralValue.from(nil), .NULL) + XCTAssertEqual(LiteralValue(nil), .NULL) } func test_recognizes_CURRENT_TIME() { - XCTAssertEqual(LiteralValue.from("CURRENT_TIME"), .CURRENT_TIME) + XCTAssertEqual(LiteralValue("CURRENT_TIME"), .CURRENT_TIME) } func test_recognizes_CURRENT_TIMESTAMP() { - XCTAssertEqual(LiteralValue.from("CURRENT_TIMESTAMP"), .CURRENT_TIMESTAMP) + XCTAssertEqual(LiteralValue("CURRENT_TIMESTAMP"), .CURRENT_TIMESTAMP) } func test_recognizes_CURRENT_DATE() { - XCTAssertEqual(LiteralValue.from("CURRENT_DATE"), .CURRENT_DATE) + XCTAssertEqual(LiteralValue("CURRENT_DATE"), .CURRENT_DATE) } func test_recognizes_double_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("\"foo\""), .stringLiteral("foo")) + XCTAssertEqual(LiteralValue("\"foo\""), .stringLiteral("foo")) } func test_recognizes_single_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("\'foo\'"), .stringLiteral("foo")) + XCTAssertEqual(LiteralValue("\'foo\'"), .stringLiteral("foo")) } func test_unquotes_double_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("\"fo\"\"o\""), .stringLiteral("fo\"o")) + XCTAssertEqual(LiteralValue("\"fo\"\"o\""), .stringLiteral("fo\"o")) } func test_unquotes_single_quote_string_literals() { - XCTAssertEqual(LiteralValue.from("'fo''o'"), .stringLiteral("fo'o")) + XCTAssertEqual(LiteralValue("'fo''o'"), .stringLiteral("fo'o")) } func test_recognizes_numeric_literals() { - XCTAssertEqual(LiteralValue.from("1.2"), .numericLiteral("1.2")) - XCTAssertEqual(LiteralValue.from("0xdeadbeef"), .numericLiteral("0xdeadbeef")) + XCTAssertEqual(LiteralValue("1.2"), .numericLiteral("1.2")) + XCTAssertEqual(LiteralValue("0xdeadbeef"), .numericLiteral("0xdeadbeef")) } func test_recognizes_blob_literals() { - XCTAssertEqual(LiteralValue.from("X'deadbeef'"), .blobLiteral("deadbeef")) - XCTAssertEqual(LiteralValue.from("x'deadbeef'"), .blobLiteral("deadbeef")) + XCTAssertEqual(LiteralValue("X'deadbeef'"), .blobLiteral("deadbeef")) + XCTAssertEqual(LiteralValue("x'deadbeef'"), .blobLiteral("deadbeef")) } func test_description_TRUE() { diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift new file mode 100644 index 00000000..165dbc28 --- /dev/null +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -0,0 +1,180 @@ +import XCTest +@testable import SQLite + +class SchemaReaderTests: SQLiteTestCase { + private var schemaReader: SchemaReader! + + override func setUpWithError() throws { + try super.setUpWithError() + try createUsersTable() + + schemaReader = db.schemaReader + } + + func test_columnDefinitions() throws { + let columns = try schemaReader.columnDefinitions(table: "users") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: false, onConflict: nil), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "email", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "salary", + primaryKey: nil, + type: .REAL, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "admin", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .numericLiteral("0"), + references: nil), + ColumnDefinition(name: "manager_id", + primaryKey: nil, type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), + ColumnDefinition(name: "created_at", + primaryKey: nil, + type: .TEXT, + nullable: true, + defaultValue: .NULL, + references: nil) + ]) + } + + func test_columnDefinitions_parses_conflict_modifier() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY ON CONFLICT IGNORE AUTOINCREMENT)") + + XCTAssertEqual( + try schemaReader.columnDefinitions(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_columnDefinitions_detects_missing_autoincrement() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + XCTAssertEqual( + try schemaReader.columnDefinitions(table: "t"), [ + ColumnDefinition( + name: "id", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_indexDefinitions_no_index() throws { + let indexes = try schemaReader.indexDefinitions(table: "users") + XCTAssertTrue(indexes.isEmpty) + } + + func test_indexDefinitions_with_index() throws { + try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") + let indexes = try schemaReader.indexDefinitions(table: "users") + + XCTAssertEqual(indexes, [ + IndexDefinition( + table: "users", + name: "index_users", + unique: true, + columns: ["age"], + where: "age IS NOT NULL", + orders: ["age": .DESC] + ) + ]) + } + + func test_foreignKeys_info_empty() throws { + try db.run("CREATE TABLE t (\"id\" INTEGER PRIMARY KEY)") + + let foreignKeys = try schemaReader.foreignKeys(table: "t") + XCTAssertTrue(foreignKeys.isEmpty) + } + + func test_foreignKeys() throws { + let linkTable = Table("test_links") + + let idColumn = SQLite.Expression("id") + let testIdColumn = SQLite.Expression("test_id") + + try db.run(linkTable.create(block: { definition in + definition.column(idColumn, primaryKey: .autoincrement) + definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) + })) + + let foreignKeys = try schemaReader.foreignKeys(table: "test_links") + XCTAssertEqual(foreignKeys, [ + .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + ]) + } + + func test_tableDefinitions() throws { + let tables = try schemaReader.tableDefinitions() + XCTAssertEqual(tables.count, 1) + XCTAssertEqual(tables.first?.name, "users") + } + + func test_objectDefinitions() throws { + let tables = try schemaReader.objectDefinitions() + + XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["users", "users", "table"], + ["sqlite_autoindex_users_1", "sqlite_autoindex_users_1", "index"] + ]) + } + + func test_objectDefinitions_temporary() throws { + let tables = try schemaReader.objectDefinitions(temp: true) + XCTAssert(tables.isEmpty) + + try db.run("CREATE TEMPORARY TABLE foo (bar TEXT)") + + let tables2 = try schemaReader.objectDefinitions(temp: true) + XCTAssertEqual(tables2.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["foo", "foo", "table"] + ]) + } + + func test_objectDefinitionsFilterByType() throws { + let tables = try schemaReader.objectDefinitions(type: .table) + + XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["users", "users", "table"] + ]) + } + + func test_objectDefinitionsFilterByName() throws { + let tables = try schemaReader.objectDefinitions(name: "users") + + XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ + ["users", "users", "table"] + ]) + } +} From 2fc0decf41a26787c4d3b2b23ef07786b44f801b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 16 Oct 2022 19:54:06 +0200 Subject: [PATCH 0913/1046] Fix tests on older version of SQLite --- .swiftlint.yml | 1 + SQLite.xcodeproj/project.pbxproj | 10 +++++++ Sources/SQLite/Core/SQLiteFeature.swift | 19 ++++++++++++++ Sources/SQLite/Schema/Connection+Schema.swift | 6 +++-- Sources/SQLite/Schema/SchemaReader.swift | 26 ++++++++++++++----- .../Schema/Connection+SchemaTests.swift | 4 ++- 6 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 Sources/SQLite/Core/SQLiteFeature.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 34bb253b..d40be28e 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,6 +3,7 @@ disabled_rules: # rule identifiers to exclude from running - operator_whitespace - large_tuple - closure_parameter_position + - inclusive_language # sqlite_master etc. included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. - Sources - Tests diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a58e0887..6fe89283 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -203,6 +203,10 @@ DB58B21228FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; DB58B21328FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; DB58B21428FB864300F8EEA4 /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; DB7C5DA628D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; @@ -334,6 +338,7 @@ 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; + DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -580,6 +585,7 @@ 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */, 3DF7B78728842972005DD8CA /* Connection+Attach.swift */, 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */, + DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */, DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */, 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */, ); @@ -970,6 +976,7 @@ 19A17073552293CA063BEA66 /* Result.swift in Sources */, 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A179B59450FE7C4811AB8A /* Connection+Aggregation.swift in Sources */, + DB58B21828FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 19A17FC07731779C1B8506FA /* SchemaChanger.swift in Sources */, 19A1740EACD47904AA24B8DC /* SchemaDefinitions.swift in Sources */, 19A1750EF4A5F92954A451FF /* Connection+Schema.swift in Sources */, @@ -1030,6 +1037,7 @@ DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */, 3D67B3EA1DB246D100A4F4C6 /* Value.swift in Sources */, 3D67B3EB1DB246D100A4F4C6 /* FTS4.swift in Sources */, + DB58B21928FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 3D67B3EC1DB246D100A4F4C6 /* RTree.swift in Sources */, 3D67B3ED1DB246D100A4F4C6 /* FTS5.swift in Sources */, 3D67B3EE1DB246D100A4F4C6 /* AggregateFunctions.swift in Sources */, @@ -1089,6 +1097,7 @@ 19A173EFEF0B3BD0B3ED406C /* Result.swift in Sources */, 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A176376CB6A94759F7980A /* Connection+Aggregation.swift in Sources */, + DB58B21628FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 19A1773A335CAB9D0AE14E8E /* SchemaChanger.swift in Sources */, 19A17BA13FD35F058787B7D3 /* SchemaDefinitions.swift in Sources */, 19A174506543905D71BF0518 /* Connection+Schema.swift in Sources */, @@ -1168,6 +1177,7 @@ 19A17F1B3F0A3C96B5ED6D64 /* Result.swift in Sources */, 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */, 19A170ACC97B19730FB7BA4D /* Connection+Aggregation.swift in Sources */, + DB58B21728FC7C4600F8EEA4 /* SQLiteFeature.swift in Sources */, 19A177290558991BCC60E4E3 /* SchemaChanger.swift in Sources */, 19A17B0DF1DDB6BBC9C95D64 /* SchemaDefinitions.swift in Sources */, 19A17F0BF02896E1664F4090 /* Connection+Schema.swift in Sources */, diff --git a/Sources/SQLite/Core/SQLiteFeature.swift b/Sources/SQLite/Core/SQLiteFeature.swift new file mode 100644 index 00000000..36b5b31b --- /dev/null +++ b/Sources/SQLite/Core/SQLiteFeature.swift @@ -0,0 +1,19 @@ +import Foundation + +enum SQLiteFeature { + case partialIntegrityCheck // PRAGMA integrity_check(table) + case sqliteSchemaTable // sqlite_master => sqlite_schema + + func isSupported(by version: SQLiteVersion) -> Bool { + switch self { + case .partialIntegrityCheck, .sqliteSchemaTable: + return version > SQLiteVersion(major: 3, minor: 33) + } + } +} + +extension Connection { + func supports(_ feature: SQLiteFeature) -> Bool { + feature.isSupported(by: sqliteVersion) + } +} diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index 868b40d5..d36812eb 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -25,8 +25,10 @@ public extension Connection { // This pragma does a low-level formatting and consistency check of the database. // https://sqlite.org/pragma.html#pragma_integrity_check - func integrityCheck(table: String? = nil, maxErrors: Int? = nil) throws -> [String] { - try run("PRAGMA integrity_check" + (table.map { "(\($0.quote()))" } ?? "")) + func integrityCheck(table: String? = nil) throws -> [String] { + precondition(table == nil || supports(.partialIntegrityCheck), "partial integrity check not supported") + + return try run("PRAGMA integrity_check" + (table.map { "(\($0.quote()))" } ?? "")) .compactMap { $0[0] as? String } .filter { $0 != "ok" } } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 3f02eeae..bfa2e887 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -38,7 +38,7 @@ public class SchemaReader { public func objectDefinitions(name: String? = nil, type: ObjectDefinition.ObjectType? = nil, temp: Bool = false) throws -> [ObjectDefinition] { - var query: QueryType = temp ? SchemaTable.tempName : SchemaTable.name + var query: QueryType = connection.schemaTable(temp: temp) if let name = name { query = query.where(SchemaTable.nameColumn == name) } @@ -99,9 +99,9 @@ public class SchemaReader { column: row[ForeignKeyListTable.fromColumn], primaryKey: row[ForeignKeyListTable.toColumn], onUpdate: row[ForeignKeyListTable.onUpdateColumn] == TableBuilder.Dependency.noAction.rawValue - ? nil : row[ForeignKeyListTable.onUpdateColumn], + ? nil : row[ForeignKeyListTable.onUpdateColumn], onDelete: row[ForeignKeyListTable.onDeleteColumn] == TableBuilder.Dependency.noAction.rawValue - ? nil : row[ForeignKeyListTable.onDeleteColumn] + ? nil : row[ForeignKeyListTable.onDeleteColumn] ) } } @@ -110,9 +110,9 @@ public class SchemaReader { try objectDefinitions(type: .table) .map { table in TableDefinition( - name: table.name, - columns: try columnDefinitions(table: table.name), - indexes: try indexDefinitions(table: table.name) + name: table.name, + columns: try columnDefinitions(table: table.name), + indexes: try indexDefinitions(table: table.name) ) } } @@ -129,6 +129,10 @@ private class SchemaTable { internal static let name = Table("sqlite_schema", database: "main") internal static let tempName = Table("sqlite_schema", database: "temp") + // legacy table names + internal static let masterName = Table("sqlite_master") + internal static let tempMasterName = Table("sqlite_temp_master") + static let typeColumn = Expression("type") static let nameColumn = Expression("name") static let tableNameColumn = Expression("tbl_name") @@ -180,3 +184,13 @@ private class ForeignKeyListTable { static let onDeleteColumn = Expression("on_delete") static let matchColumn = Expression("match") } + +private extension Connection { + func schemaTable(temp: Bool = false) -> Table { + if supports(.sqliteSchemaTable) { + return temp ? SchemaTable.tempName : SchemaTable.name + } else { + return temp ? SchemaTable.tempMasterName : SchemaTable.masterName + } + } +} diff --git a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift index 96300529..57e2726b 100644 --- a/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/Connection+SchemaTests.swift @@ -33,12 +33,14 @@ class ConnectionSchemaTests: SQLiteTestCase { XCTAssert(results.isEmpty) } - func test_integrityCheck_table() throws { + func test_partial_integrityCheck_table() throws { + guard db.supports(.partialIntegrityCheck) else { return } let results = try db.integrityCheck(table: "users") XCTAssert(results.isEmpty) } func test_integrityCheck_table_not_found() throws { + guard db.supports(.partialIntegrityCheck) else { return } XCTAssertThrowsError(try db.integrityCheck(table: "xxx")) { error in guard case Result.error(let message, _, _) = error else { assertionFailure("invalid error type") From a6a151b3395c83f845d6d88d7d7565df824ef239 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 16 Oct 2022 20:57:25 +0200 Subject: [PATCH 0914/1046] Reuse SQLiteFeature in SchemaChanger --- Sources/SQLite/Core/SQLiteFeature.swift | 8 +++- Sources/SQLite/Schema/Connection+Schema.swift | 4 +- Sources/SQLite/Schema/SchemaChanger.swift | 6 +-- Sources/SQLite/Schema/SchemaDefinitions.swift | 5 ++- Sources/SQLite/Schema/SchemaReader.swift | 40 +++++++++---------- .../Schema/SchemaReaderTests.swift | 4 +- 6 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Sources/SQLite/Core/SQLiteFeature.swift b/Sources/SQLite/Core/SQLiteFeature.swift index 36b5b31b..50d910a3 100644 --- a/Sources/SQLite/Core/SQLiteFeature.swift +++ b/Sources/SQLite/Core/SQLiteFeature.swift @@ -3,11 +3,17 @@ import Foundation enum SQLiteFeature { case partialIntegrityCheck // PRAGMA integrity_check(table) case sqliteSchemaTable // sqlite_master => sqlite_schema + case renameColumn // ALTER TABLE ... RENAME COLUMN + case dropColumn // ALTER TABLE ... DROP COLUMN func isSupported(by version: SQLiteVersion) -> Bool { switch self { case .partialIntegrityCheck, .sqliteSchemaTable: - return version > SQLiteVersion(major: 3, minor: 33) + return version >= .init(major: 3, minor: 33) + case .renameColumn: + return version >= .init(major: 3, minor: 25) + case .dropColumn: + return version >= .init(major: 3, minor: 35) } } } diff --git a/Sources/SQLite/Schema/Connection+Schema.swift b/Sources/SQLite/Schema/Connection+Schema.swift index d36812eb..2977af17 100644 --- a/Sources/SQLite/Schema/Connection+Schema.swift +++ b/Sources/SQLite/Schema/Connection+Schema.swift @@ -1,7 +1,7 @@ import Foundation public extension Connection { - var schemaReader: SchemaReader { SchemaReader(connection: self) } + var schema: SchemaReader { SchemaReader(connection: self) } // There are four columns in each result row. // The first column is the name of the table that @@ -14,7 +14,7 @@ public extension Connection { // https://sqlite.org/pragma.html#pragma_foreign_key_check func foreignKeyCheck(table: String? = nil) throws -> [ForeignKeyError] { try run("PRAGMA foreign_key_check" + (table.map { "(\($0.quote()))" } ?? "")) - .compactMap { row -> ForeignKeyError? in + .compactMap { (row: [Binding?]) -> ForeignKeyError? in guard let table = row[0] as? String, let rowId = row[1] as? Int64, let target = row[2] as? String else { return nil } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index b557da0a..4e1b42c5 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -52,9 +52,9 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .renameColumn(let from, let to) where version >= .init(major: 3, minor: 25): + case .renameColumn(let from, let to) where SQLiteFeature.renameColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" - case .dropColumn(let column) where version >= .init(major: 3, minor: 35): + case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" default: return nil } @@ -128,7 +128,7 @@ public class SchemaChanger: CustomStringConvertible { init(connection: Connection, version: SQLiteVersion) { self.connection = connection - schemaReader = connection.schemaReader + schemaReader = connection.schema self.version = version } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index fe2e3931..4159b35b 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -245,8 +245,9 @@ public struct IndexDefinition: Equatable { } func orders(sql: String) -> [String: IndexDefinition.Order] { - IndexDefinition.orderRe.matches(in: sql, range: NSRange(location: 0, length: sql.count)) - .reduce([String: IndexDefinition.Order]()) { (memo, result) in + IndexDefinition.orderRe + .matches(in: sql, range: NSRange(location: 0, length: sql.count)) + .reduce([String: IndexDefinition.Order]()) { (memo, result) in var memo2 = memo let column = (sql as NSString).substring(with: result.range(at: 1)) memo2[column] = .DESC diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index bfa2e887..b1292471 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -38,7 +38,7 @@ public class SchemaReader { public func objectDefinitions(name: String? = nil, type: ObjectDefinition.ObjectType? = nil, temp: Bool = false) throws -> [ObjectDefinition] { - var query: QueryType = connection.schemaTable(temp: temp) + var query: QueryType = SchemaTable.get(for: connection, temp: temp) if let name = name { query = query.where(SchemaTable.nameColumn == name) } @@ -125,14 +125,22 @@ public class SchemaReader { } } -private class SchemaTable { - internal static let name = Table("sqlite_schema", database: "main") - internal static let tempName = Table("sqlite_schema", database: "temp") +private enum SchemaTable { + private static let name = Table("sqlite_schema", database: "main") + private static let tempName = Table("sqlite_schema", database: "temp") + // legacy names (< 3.33.0) + private static let masterName = Table("sqlite_master") + private static let tempMasterName = Table("sqlite_temp_master") - // legacy table names - internal static let masterName = Table("sqlite_master") - internal static let tempMasterName = Table("sqlite_temp_master") + static func get(for connection: Connection, temp: Bool = false) -> Table { + if connection.supports(.sqliteSchemaTable) { + return temp ? SchemaTable.tempName : SchemaTable.name + } else { + return temp ? SchemaTable.tempMasterName : SchemaTable.masterName + } + } + // columns static let typeColumn = Expression("type") static let nameColumn = Expression("name") static let tableNameColumn = Expression("tbl_name") @@ -140,7 +148,7 @@ private class SchemaTable { static let sqlColumn = Expression("sql") } -private class TableInfoTable { +private enum TableInfoTable { static let idColumn = Expression("cid") static let nameColumn = Expression("name") static let typeColumn = Expression("type") @@ -149,7 +157,7 @@ private class TableInfoTable { static let primaryKeyColumn = Expression("pk") } -private class IndexInfoTable { +private enum IndexInfoTable { // The rank of the column within the index. (0 means left-most.) static let seqnoColumn = Expression("seqno") // The rank of the column within the table being indexed. @@ -159,7 +167,7 @@ private class IndexInfoTable { static let nameColumn = Expression("name") } -private class IndexListTable { +private enum IndexListTable { // A sequence number assigned to each index for internal tracking purposes. static let seqColumn = Expression("seq") // The name of the index @@ -174,7 +182,7 @@ private class IndexListTable { static let partialColumn = Expression("partial") } -private class ForeignKeyListTable { +private enum ForeignKeyListTable { static let idColumn = Expression("id") static let seqColumn = Expression("seq") static let tableColumn = Expression("table") @@ -184,13 +192,3 @@ private class ForeignKeyListTable { static let onDeleteColumn = Expression("on_delete") static let matchColumn = Expression("match") } - -private extension Connection { - func schemaTable(temp: Bool = false) -> Table { - if supports(.sqliteSchemaTable) { - return temp ? SchemaTable.tempName : SchemaTable.name - } else { - return temp ? SchemaTable.tempMasterName : SchemaTable.masterName - } - } -} diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 165dbc28..95dba921 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -8,7 +8,7 @@ class SchemaReaderTests: SQLiteTestCase { try super.setUpWithError() try createUsersTable() - schemaReader = db.schemaReader + schemaReader = db.schema } func test_columnDefinitions() throws { @@ -168,6 +168,7 @@ class SchemaReaderTests: SQLiteTestCase { XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ ["users", "users", "table"] ]) + XCTAssertTrue((try schemaReader.objectDefinitions(type: .trigger)).isEmpty) } func test_objectDefinitionsFilterByName() throws { @@ -176,5 +177,6 @@ class SchemaReaderTests: SQLiteTestCase { XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ ["users", "users", "table"] ]) + XCTAssertTrue((try schemaReader.objectDefinitions(name: "xxx")).isEmpty) } } From 0f6c3e30245d09bdd1fd50d5e0ac0d162131426c Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 01:11:08 +0200 Subject: [PATCH 0915/1046] Add documentation --- Documentation/Index.md | 82 ++++++++++++++++++- Sources/SQLite/Schema/SchemaChanger.swift | 2 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 4 + Sources/SQLite/Schema/SchemaReader.swift | 2 +- .../Schema/SchemaChangerTests.swift | 34 ++++---- .../Schema/SchemaReaderTests.swift | 34 +++++++- 6 files changed, 134 insertions(+), 24 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 29b18c96..e40f786d 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1371,6 +1371,37 @@ try db.transaction { > _Note:_ Transactions run in a serial queue. +## Querying the Schema + +We can obtain generic information about objects in the current schema with a `SchemaReader`: + +```swift +let schema = db.schema +``` + +To query the data: + +```swift +let indexes = try schema.objectDefinitions(type: .index) +let tables = try schema.objectDefinitions(type: .table) +let triggers = try schema.objectDefinitions(type: .trigger) +``` + +### Indexes and Columns + +Specialized methods are available to get more detailed information: + +```swift +let indexes = try schema.indexDefinitions("users") +let columns = try schema.columnDefinitions("users") + +for index in indexes { + print("\(index.name) columns:\(index.columns))") +} +for column in columns { + print("\(column.name) pk:\(column.primaryKey) nullable: \(column.nullable)") +} +``` ## Altering the Schema @@ -1454,11 +1485,56 @@ tables](#creating-a-table). ### Renaming Columns -Added in SQLite 3.25.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) +We can rename columns with the help of the `SchemaChanger` class: + +```swift +let schemaChanger = SchemaChanger(connection: db) +try schemaChanger.alter(table: "users") { table in + table.rename("old_name", to: "new_name") +} +``` ### Dropping Columns -Added in SQLite 3.35.0, not exposed yet. [#1073](https://github.com/stephencelis/SQLite.swift/issues/1073) +```swift +let schemaChanger = SchemaChanger(connection: db) +try schemaChanger.alter(table: "users") { table in + table.drop("column") +} +``` + +These operations will work with all versions of SQLite and use modern SQL +operations such as `DROP COLUMN` when available. + +### Adding Columns (SchemaChanger) + +The `SchemaChanger` provides an alternative API to add new columns: + +```swift +let newColumn = ColumnDefinition( + name: "new_text_column", + type: .TEXT, + nullable: true, + defaultValue: .stringLiteral("foo") +) + +let schemaChanger = SchemaChanger(connection: db) + +try schemaChanger.alter(table: "users") { table in + table.add(newColumn) +} +``` + +### Renaming/dropping Tables (SchemaChanger) + +The `SchemaChanger` provides an alternative API to rename and drop tables: + +```swift +let schemaChanger = SchemaChanger(connection: db) + +try schemaChanger.rename(table: "users", to: "users_new") +try schemaChanger.drop(table: "emails") +``` ### Indexes @@ -1515,7 +1591,6 @@ try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` - ### Dropping Tables We can build @@ -1535,7 +1610,6 @@ try db.run(users.drop(ifExists: true)) // DROP TABLE IF EXISTS "users" ``` - ### Migrations and Schema Versioning You can use the convenience property on `Connection` to query and set the diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 4e1b42c5..d2ada22c 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -99,7 +99,7 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.addColumn(column)) } - public func remove(_ column: String) { + public func drop(_ column: String) { operations.append(.dropColumn(column)) } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 4159b35b..a2700dd2 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -31,6 +31,10 @@ public struct ObjectDefinition: Equatable { // SQL text that describes the object (NULL for the internal indexes) public let sql: String? + + public var isInternal: Bool { + name.starts(with: "sqlite_") || sql == nil + } } // https://sqlite.org/syntax/column-def.html diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index b1292471..1c26fdd6 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -52,7 +52,7 @@ public class SchemaReader { return ObjectDefinition( type: type, name: row[SchemaTable.nameColumn], - tableName: row[SchemaTable.nameColumn], + tableName: row[SchemaTable.tableNameColumn], rootpage: row[SchemaTable.rootPageColumn] ?? 0, sql: row[SchemaTable.sqlColumn] ) diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index af8b6565..d023b8e5 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -3,7 +3,7 @@ import XCTest class SchemaChangerTests: SQLiteTestCase { var schemaChanger: SchemaChanger! - var schemaReader: SchemaReader! + var schema: SchemaReader! override func setUpWithError() throws { try super.setUpWithError() @@ -11,33 +11,33 @@ class SchemaChangerTests: SQLiteTestCase { try insertUsers("bob") - schemaReader = SchemaReader(connection: db) + schema = SchemaReader(connection: db) schemaChanger = SchemaChanger(connection: db) } func test_empty_migration_does_not_change_column_definitions() throws { - let previous = try schemaReader.columnDefinitions(table: "users") + let previous = try schema.columnDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try schemaReader.columnDefinitions(table: "users") + let current = try schema.columnDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_index_definitions() throws { - let previous = try schemaReader.indexDefinitions(table: "users") + let previous = try schema.indexDefinitions(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try schemaReader.indexDefinitions(table: "users") + let current = try schema.indexDefinitions(table: "users") XCTAssertEqual(previous, current) } func test_empty_migration_does_not_change_foreign_key_definitions() throws { - let previous = try schemaReader.foreignKeys(table: "users") + let previous = try schema.foreignKeys(table: "users") try schemaChanger.alter(table: "users") { _ in } - let current = try schemaReader.foreignKeys(table: "users") + let current = try schema.foreignKeys(table: "users") XCTAssertEqual(previous, current) } @@ -51,21 +51,21 @@ class SchemaChangerTests: SQLiteTestCase { XCTAssertEqual(previous, current) } - func test_remove_column() throws { + func test_drop_column() throws { try schemaChanger.alter(table: "users") { table in - table.remove("age") + table.drop("age") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } - func test_remove_column_legacy() throws { + func test_drop_column_legacy() throws { schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // DROP COLUMN introduced in 3.35.0 try schemaChanger.alter(table: "users") { table in - table.remove("age") + table.drop("age") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) } @@ -74,7 +74,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -86,7 +86,7 @@ class SchemaChangerTests: SQLiteTestCase { table.rename("age", to: "age2") } - let columns = try schemaReader.columnDefinitions(table: "users").map(\.name) + let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) XCTAssertTrue(columns.contains("age2")) } @@ -102,7 +102,7 @@ class SchemaChangerTests: SQLiteTestCase { table.add(newColumn) } - let columns = try schemaReader.columnDefinitions(table: "users") + let columns = try schema.columnDefinitions(table: "users") XCTAssertTrue(columns.contains(newColumn)) XCTAssertEqual(try db.pluck(users.select(column))?[column], "foo") diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 95dba921..dd5ae103 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -146,7 +146,7 @@ class SchemaReaderTests: SQLiteTestCase { XCTAssertEqual(tables.map { table in [table.name, table.tableName, table.type.rawValue]}, [ ["users", "users", "table"], - ["sqlite_autoindex_users_1", "sqlite_autoindex_users_1", "index"] + ["sqlite_autoindex_users_1", "users", "index"] ]) } @@ -162,6 +162,38 @@ class SchemaReaderTests: SQLiteTestCase { ]) } + func test_objectDefinitions_indexes() throws { + let emailIndex = users.createIndex(Expression("email"), unique: false, ifNotExists: true) + try db.run(emailIndex) + + let indexes = try schemaReader.objectDefinitions(type: .index) + .filter { !$0.isInternal } + + XCTAssertEqual(indexes.map { index in [index.name, index.tableName, index.type.rawValue, index.sql]}, [ + ["index_users_on_email", + "users", + "index", + "CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")"] + ]) + } + + func test_objectDefinitions_triggers() throws { + let trigger = """ + CREATE TRIGGER test_trigger + AFTER INSERT ON users BEGIN + UPDATE USERS SET name = "update" WHERE id = NEW.rowid; + END; + """ + + try db.run(trigger) + + let triggers = try schemaReader.objectDefinitions(type: .trigger) + + XCTAssertEqual(triggers.map { trigger in [trigger.name, trigger.tableName, trigger.type.rawValue]}, [ + ["test_trigger", "users", "trigger"] + ]) + } + func test_objectDefinitionsFilterByType() throws { let tables = try schemaReader.objectDefinitions(type: .table) From 137788e332f07e04d0a7d07150a043cc04c0d5e5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 01:17:31 +0200 Subject: [PATCH 0916/1046] Named param for rename/drop --- Documentation/Index.md | 4 ++-- Sources/SQLite/Schema/SchemaChanger.swift | 4 ++-- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index e40f786d..68e66457 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1490,7 +1490,7 @@ We can rename columns with the help of the `SchemaChanger` class: ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.rename("old_name", to: "new_name") + table.rename(column: "old_name", to: "new_name") } ``` @@ -1499,7 +1499,7 @@ try schemaChanger.alter(table: "users") { table in ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.drop("column") + table.drop(column: "email") } ``` diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index d2ada22c..67f2b0d3 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -99,11 +99,11 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.addColumn(column)) } - public func drop(_ column: String) { + public func drop(column: String) { operations.append(.dropColumn(column)) } - public func rename(_ column: String, to: String) { + public func rename(column: String, to: String) { operations.append(.renameColumn(column, to)) } } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index d023b8e5..2894e5ce 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -53,7 +53,7 @@ class SchemaChangerTests: SQLiteTestCase { func test_drop_column() throws { try schemaChanger.alter(table: "users") { table in - table.drop("age") + table.drop(column: "age") } let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) @@ -63,7 +63,7 @@ class SchemaChangerTests: SQLiteTestCase { schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // DROP COLUMN introduced in 3.35.0 try schemaChanger.alter(table: "users") { table in - table.drop("age") + table.drop(column: "age") } let columns = try schema.columnDefinitions(table: "users").map(\.name) XCTAssertFalse(columns.contains("age")) @@ -71,7 +71,7 @@ class SchemaChangerTests: SQLiteTestCase { func test_rename_column() throws { try schemaChanger.alter(table: "users") { table in - table.rename("age", to: "age2") + table.rename(column: "age", to: "age2") } let columns = try schema.columnDefinitions(table: "users").map(\.name) @@ -83,7 +83,7 @@ class SchemaChangerTests: SQLiteTestCase { schemaChanger = .init(connection: db, version: .init(major: 3, minor: 24)) // RENAME COLUMN introduced in 3.25.0 try schemaChanger.alter(table: "users") { table in - table.rename("age", to: "age2") + table.rename(column: "age", to: "age2") } let columns = try schema.columnDefinitions(table: "users").map(\.name) From f2c8bdaf71c57a5087efabf447da33f874ae5787 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 01:25:48 +0200 Subject: [PATCH 0917/1046] Changelog --- CHANGELOG.md | 4 ++++ Documentation/Index.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3f78b4..3a12f377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.14.0 (tbd), [diff][diff-0.14.0] ======================================== +* Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) @@ -160,6 +161,7 @@ [#866]: https://github.com/stephencelis/SQLite.swift/pull/866 [#881]: https://github.com/stephencelis/SQLite.swift/pull/881 [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 +[#1073]: https://github.com/stephencelis/SQLite.swift/issues/1073 [#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 @@ -185,3 +187,5 @@ [#1141]: https://github.com/stephencelis/SQLite.swift/pull/1141 [#1142]: https://github.com/stephencelis/SQLite.swift/pull/1142 [#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144 +[#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146 +[#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148 diff --git a/Documentation/Index.md b/Documentation/Index.md index 68e66457..d275db8e 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1514,7 +1514,7 @@ The `SchemaChanger` provides an alternative API to add new columns: let newColumn = ColumnDefinition( name: "new_text_column", type: .TEXT, - nullable: true, + nullable: true, defaultValue: .stringLiteral("foo") ) From 140134d68fd82190972f30232e33363c8ae58a79 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 02:06:21 +0200 Subject: [PATCH 0918/1046] Add playground examples --- SQLite.playground/Contents.swift | 30 ++++++++++++++++++- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- .../Schema/SchemaDefinitionsTests.swift | 10 +++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index c089076d..73091735 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -99,5 +99,33 @@ db.createAggregation("customConcat", initialValue: "users:", reduce: reduce, result: { $0 }) -let result = db.prepare("SELECT customConcat(email) FROM users").scalar() as! String +let result = try db.prepare("SELECT customConcat(email) FROM users").scalar() as! String print(result) + +/// schema queries +let schema = db.schema +let objects = try schema.objectDefinitions() +print(objects) + +let columns = try schema.columnDefinitions(table: "users") +print(columns) + +/// schema alteration + +let schemaChanger = SchemaChanger(connection: db) +try schemaChanger.alter(table: "users") { table in + table.add(.init(name: "age", type: .INTEGER)) + table.rename(column: "email", to: "electronic_mail") + table.drop(column: "name") +} + +let changedColumns = try schema.columnDefinitions(table: "users") +print(changedColumns) + +let age = Expression("age") +let electronicMail = Expression("electronic_mail") + +let newRowid = try db.run(users.insert( + electronicMail <- "carol@mac.com", + age <- 33 +)) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index a2700dd2..b06ddfc7 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -122,7 +122,7 @@ public struct ColumnDefinition: Equatable { public init(name: String, primaryKey: PrimaryKey? = nil, type: Affinity, - nullable: Bool = false, + nullable: Bool = true, defaultValue: LiteralValue = .NULL, references: ForeignKey? = nil) { self.name = name diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 384aab3f..ef97b981 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -49,6 +49,16 @@ class ColumnDefinitionTests: XCTestCase { XCTAssertEqual(definition.toSQL(), expected) } #endif + + func testNullableByDefault() { + let test = ColumnDefinition(name: "test", type: .REAL) + XCTAssertEqual(test.name, "test") + XCTAssertTrue(test.nullable) + XCTAssertEqual(test.defaultValue, .NULL) + XCTAssertEqual(test.type, .REAL) + XCTAssertNil(test.references) + XCTAssertNil(test.primaryKey) + } } class AffinityTests: XCTestCase { From 020ec7aabbda32a384d29fcc4ecf42e52ef48190 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 02:40:18 +0200 Subject: [PATCH 0919/1046] Better example --- SQLite.playground/Contents.swift | 2 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 73091735..5a7ea379 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -114,7 +114,7 @@ print(columns) let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.add(.init(name: "age", type: .INTEGER)) + table.add(ColumnDefinition(name: "age", type: .INTEGER)) table.rename(column: "email", to: "electronic_mail") table.drop(column: "name") } diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index b06ddfc7..284fc4c3 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -154,7 +154,6 @@ public enum LiteralValue: Equatable, CustomStringConvertible { // If there is no explicit DEFAULT clause attached to a column definition, then the default value of the // column is NULL - // swiftlint:disable identifier_name case NULL // Beginning with SQLite 3.23.0 (2018-04-02), SQLite recognizes the identifiers "TRUE" and @@ -164,6 +163,7 @@ public enum LiteralValue: Equatable, CustomStringConvertible { // The boolean identifiers TRUE and FALSE are usually just aliases for the integer values 1 and 0, respectively. case TRUE case FALSE + // swiftlint:disable identifier_name case CURRENT_TIME case CURRENT_DATE case CURRENT_TIMESTAMP From 773ac2611faaeeb16cedd83406ed3b281ffcf8ea Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 21:39:32 +0200 Subject: [PATCH 0920/1046] Reactivate #416 --- Sources/SQLite/Core/Blob.swift | 51 ++++++++++++++++----- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Foundation.swift | 6 +-- Tests/SQLiteTests/Core/BlobTests.swift | 10 +++- Tests/SQLiteTests/Core/StatementTests.swift | 4 +- Tests/SQLiteTests/FoundationTests.swift | 2 +- 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index cd31483b..e1ce5d9c 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -21,26 +21,55 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // +import Foundation -public struct Blob { +public final class Blob { - public let bytes: [UInt8] + public let data: NSData - public init(bytes: [UInt8]) { - self.bytes = bytes + public var bytes: UnsafeRawPointer { + data.bytes } - public init(bytes: UnsafeRawPointer, length: Int) { - let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length) - self.init(bytes: [UInt8](i8bufptr)) + public var length: Int { + data.count } - public func toHex() -> String { - bytes.map { - ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) - }.joined(separator: "") + public convenience init(bytes: [UInt8]) { + let buffer = UnsafeMutablePointer.allocate(capacity: bytes.count) + for idx in 0.. String { + let bytes = bytes.assumingMemoryBound(to: UInt8.self) + + var hex = "" + for idx in 0.. Data { - Data(dataValue.bytes) + dataValue.data as Data } public var datatypeValue: Blob { - withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in - Blob(bytes: pointer.baseAddress!, length: count) - } + Blob(data: self as NSData) } } diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index 87eb5709..463ef10a 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -11,13 +11,19 @@ class BlobTests: XCTestCase { func test_init_array() { let blob = Blob(bytes: [42, 42, 42]) - XCTAssertEqual(blob.bytes, [42, 42, 42]) + XCTAssertEqual(blob.byteArray, [42, 42, 42]) } func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) - XCTAssertEqual(blob.bytes, [42, 42, 42]) + XCTAssertEqual(blob.byteArray, [42, 42, 42]) + } +} + +extension Blob { + var byteArray: [UInt8] { + [UInt8](data) } } diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index eeb513fe..28772ae1 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -22,7 +22,7 @@ class StatementTests: SQLiteTestCase { let statement = try db.prepare("SELECT email FROM users") XCTAssert(try statement.step()) let blob = statement.row[0] as Blob - XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) + XCTAssertEqual("alice@example.com", String(data: blob.data as Data, encoding: .utf8)!) } func test_zero_sized_blob_returns_null() throws { @@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase { try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) - XCTAssertEqual([], blobValue.bytes) + XCTAssertEqual([], blobValue.byteArray) } func test_prepareRowIterator() throws { diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index 453febcd..cca15cc1 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -5,7 +5,7 @@ class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue - XCTAssertEqual([1, 2, 3], blob.bytes) + XCTAssertEqual([1, 2, 3], blob.byteArray) } func testBlobToData() { From 3b91a00c71b34cc3f4343b2c622bf400717a7c3d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 22:13:21 +0200 Subject: [PATCH 0921/1046] Fix test --- Sources/SQLite/Core/Statement.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index ec06a5bb..df4ceebf 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -100,21 +100,24 @@ public final class Statement { } fileprivate func bind(_ value: Binding?, atIndex idx: Int) { - if value == nil { + switch value { + case .none: sqlite3_bind_null(handle, Int32(idx)) - } else if let value = value as? Blob { + case let value as Blob where value.length == 0: + sqlite3_bind_zeroblob(handle, Int32(idx), 0) + case let value as Blob: sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) - } else if let value = value as? Double { + case let value as Double: sqlite3_bind_double(handle, Int32(idx), value) - } else if let value = value as? Int64 { + case let value as Int64: sqlite3_bind_int64(handle, Int32(idx), value) - } else if let value = value as? String { + case let value as String: sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT) - } else if let value = value as? Int { + case let value as Int: self.bind(value.datatypeValue, atIndex: idx) - } else if let value = value as? Bool { + case let value as Bool: self.bind(value.datatypeValue, atIndex: idx) - } else if let value = value { + case .some(let value): fatalError("tried to bind unexpected value \(value)") } } From 0715e9512daf86fe42e592b54eb633a049adb4b7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 22:26:46 +0200 Subject: [PATCH 0922/1046] Fix blob equality --- Sources/SQLite/Core/Blob.swift | 8 +++++++- Tests/SQLiteTests/Core/BlobTests.swift | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index e1ce5d9c..83b78c2c 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -36,6 +36,10 @@ public final class Blob { } public convenience init(bytes: [UInt8]) { + guard bytes.count > 0 else { + self.init(data: NSData()) + return + } let buffer = UnsafeMutablePointer.allocate(capacity: bytes.count) for idx in 0.. String { + guard length > 0 else { return "" } let bytes = bytes.assumingMemoryBound(to: UInt8.self) var hex = "" @@ -85,5 +91,5 @@ extension Blob: Equatable { } public func ==(lhs: Blob, rhs: Blob) -> Bool { - lhs.bytes == rhs.bytes + lhs.data == rhs.data } diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index 463ef10a..9c8700cc 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -5,10 +5,14 @@ class BlobTests: XCTestCase { func test_toHex() { let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255]) - XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") } + func test_toHex_empty() { + let blob = Blob(bytes: []) + XCTAssertEqual(blob.toHex(), "") + } + func test_init_array() { let blob = Blob(bytes: [42, 42, 42]) XCTAssertEqual(blob.byteArray, [42, 42, 42]) @@ -20,6 +24,16 @@ class BlobTests: XCTestCase { let blob = Blob(bytes: pointer, length: 3) XCTAssertEqual(blob.byteArray, [42, 42, 42]) } + + func test_equality() { + let blob1 = Blob(bytes: [42, 42, 42]) + let blob2 = Blob(bytes: [42, 42, 42]) + let blob3 = Blob(bytes: [42, 42, 43]) + + XCTAssertEqual(Blob(bytes: []), Blob(bytes: [])) + XCTAssertEqual(blob1, blob2) + XCTAssertNotEqual(blob1, blob3) + } } extension Blob { From 0ce78d92aebea37bc68585ae4d0ead17641d86b1 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 22:29:40 +0200 Subject: [PATCH 0923/1046] Remove helper --- Tests/SQLiteTests/Core/BlobTests.swift | 10 ++-------- Tests/SQLiteTests/Core/StatementTests.swift | 3 +-- Tests/SQLiteTests/FoundationTests.swift | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index 9c8700cc..01f38197 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -15,14 +15,14 @@ class BlobTests: XCTestCase { func test_init_array() { let blob = Blob(bytes: [42, 42, 42]) - XCTAssertEqual(blob.byteArray, [42, 42, 42]) + XCTAssertEqual([UInt8](blob.data), [42, 42, 42]) } func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) - XCTAssertEqual(blob.byteArray, [42, 42, 42]) + XCTAssertEqual([UInt8](blob.data), [42, 42, 42]) } func test_equality() { @@ -35,9 +35,3 @@ class BlobTests: XCTestCase { XCTAssertNotEqual(blob1, blob3) } } - -extension Blob { - var byteArray: [UInt8] { - [UInt8](data) - } -} diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 28772ae1..2ab84c09 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase { try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) - XCTAssertEqual([], blobValue.byteArray) + XCTAssertEqual([], [UInt8](blobValue.data)) } func test_prepareRowIterator() throws { @@ -71,5 +71,4 @@ class StatementTests: SQLiteTestCase { // truncate succeeds try db.run("DROP TABLE users") } - } diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index cca15cc1..ffd3fae1 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -5,7 +5,7 @@ class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue - XCTAssertEqual([1, 2, 3], blob.byteArray) + XCTAssertEqual([1, 2, 3], [UInt8](blob.data)) } func testBlobToData() { From 932d580deae26f04004bcd0aefa536ef021219c8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 23:08:55 +0200 Subject: [PATCH 0924/1046] Fix SQLCipher subspec --- Sources/SQLite/Core/Blob.swift | 5 ++--- Sources/SQLite/Extensions/Cipher.swift | 6 +++--- Tests/SQLiteTests/Core/BlobTests.swift | 4 ++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index 83b78c2c..fb7ab11b 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -27,8 +27,8 @@ public final class Blob { public let data: NSData - public var bytes: UnsafeRawPointer { - data.bytes + public var bytes: UnsafePointer { + data.bytes.assumingMemoryBound(to: UInt8.self) } public var length: Int { @@ -64,7 +64,6 @@ public final class Blob { public func toHex() -> String { guard length > 0 else { return "" } - let bytes = bytes.assumingMemoryBound(to: UInt8.self) var hex = "" for idx in 0.. Date: Sun, 23 Oct 2022 23:47:17 +0200 Subject: [PATCH 0925/1046] Avoid NSMutableData --- Tests/SQLiteTests/Extensions/CipherTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/SQLiteTests/Extensions/CipherTests.swift b/Tests/SQLiteTests/Extensions/CipherTests.swift index cc43272e..bd7f2042 100644 --- a/Tests/SQLiteTests/Extensions/CipherTests.swift +++ b/Tests/SQLiteTests/Extensions/CipherTests.swift @@ -19,7 +19,7 @@ class CipherTests: XCTestCase { // db2 let key2 = keyData() - try db2.key(Blob(bytes: key2.bytes, length: key2.length)) + try db2.key(Blob(data: key2)) try db2.run("CREATE TABLE foo (bar TEXT)") try db2.run("INSERT INTO foo (bar) VALUES ('world')") @@ -47,7 +47,7 @@ class CipherTests: XCTestCase { func test_data_rekey() throws { let newKey = keyData() - try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) + try db2.rekey(Blob(data: newKey)) XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } @@ -108,12 +108,12 @@ class CipherTests: XCTestCase { XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64) } - private func keyData(length: Int = 64) -> NSMutableData { + private func keyData(length: Int = 64) -> NSData { let keyData = NSMutableData(length: length)! let result = SecRandomCopyBytes(kSecRandomDefault, length, keyData.mutableBytes.assumingMemoryBound(to: UInt8.self)) XCTAssertEqual(0, result) - return keyData + return NSData(data: keyData) } } #endif From bf021c503067675295a18f69ecfc6eb3903c8195 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 23 Oct 2022 23:51:43 +0200 Subject: [PATCH 0926/1046] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a12f377..49590fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ * Fix `insertMany([Encodable])` ([#1130][], [#1138][]) * Fix incorrect spelling of `remove_diacritics` ([#1128][]) * Fix project build order ([#1131][]) -* Performance improvements ([#1109][], [#1115][], [#1132][]) +* Blob performance improvements ([#416][], [#1167][]) +* Various performance improvements ([#1109][], [#1115][], [#1132][]) * Removed FTS3/4 tokenizer integration (`registerTokenizer`, [#1104][], [#1144][]) 0.13.3 (27-03-2022), [diff][diff-0.13.3] @@ -130,6 +131,7 @@ [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 [#315]: https://github.com/stephencelis/SQLite.swift/issues/315 +[#416]: https://github.com/stephencelis/SQLite.swift/pull/416 [#426]: https://github.com/stephencelis/SQLite.swift/pull/426 [#481]: https://github.com/stephencelis/SQLite.swift/pull/481 [#532]: https://github.com/stephencelis/SQLite.swift/issues/532 @@ -189,3 +191,4 @@ [#1144]: https://github.com/stephencelis/SQLite.swift/pull/1144 [#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146 [#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148 +[#1167]: https://github.com/stephencelis/SQLite.swift/pull/1167 From 06e55e794ffb49eda3f97e0ca627fb76ed2f98f6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 24 Oct 2022 12:10:54 +0200 Subject: [PATCH 0927/1046] Simplify --- Sources/SQLite/Core/Blob.swift | 12 +----------- Tests/SQLiteTests/Core/BlobTests.swift | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index fb7ab11b..c8bd2c15 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -40,17 +40,7 @@ public final class Blob { self.init(data: NSData()) return } - let buffer = UnsafeMutablePointer.allocate(capacity: bytes.count) - for idx in 0.. Date: Wed, 26 Oct 2022 01:04:29 +0200 Subject: [PATCH 0928/1046] prepareRowIterator should be internal --- Documentation/Index.md | 8 -------- Sources/SQLite/Core/Statement.swift | 4 ++-- Tests/SQLiteTests/Core/StatementTests.swift | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 3d0639b3..fa91f7b7 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -2125,15 +2125,7 @@ using the following functions. } } ``` - Statements with results may be iterated over, using a `RowIterator` if - useful. - ```swift - let emailColumn = Expression("email") - let stmt = try db.prepare("SELECT id, email FROM users") - let emails = try! stmt.prepareRowIterator().map { $0[emailColumn] } - ``` - - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index df4ceebf..80450258 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -236,8 +236,8 @@ extension Statement: FailableIterator { } extension Statement { - public func prepareRowIterator() -> RowIterator { - return RowIterator(statement: self, columnNames: self.columnNameMap) + func prepareRowIterator() -> RowIterator { + RowIterator(statement: self, columnNames: columnNameMap) } var columnNameMap: [String: Int] { diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 2ab84c09..0d82e890 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -1,5 +1,5 @@ import XCTest -import SQLite +@testable import SQLite #if SQLITE_SWIFT_STANDALONE import sqlite3 From 11bfa260eef892b0a187a6b77ffe8a94d4e97641 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 23:50:13 +0200 Subject: [PATCH 0929/1046] Mention query parameter in changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49590fbe..01b7de5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) +* Expose connection flags (via `URIQueryParameter`) to open db ([#1074][])) * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) @@ -164,6 +165,7 @@ [#881]: https://github.com/stephencelis/SQLite.swift/pull/881 [#919]: https://github.com/stephencelis/SQLite.swift/pull/919 [#1073]: https://github.com/stephencelis/SQLite.swift/issues/1073 +[#1074]: https://github.com/stephencelis/SQLite.swift/issues/1074 [#1075]: https://github.com/stephencelis/SQLite.swift/pull/1075 [#1077]: https://github.com/stephencelis/SQLite.swift/issues/1077 [#1094]: https://github.com/stephencelis/SQLite.swift/pull/1094 From ff3c4a2d4cc8533c45addba6bbd2d0d90e8833d8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 21 Oct 2022 12:20:24 +0200 Subject: [PATCH 0930/1046] Remove outdated/unavailable project refs --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 1b04c7c8..cdc11dd5 100644 --- a/README.md +++ b/README.md @@ -267,19 +267,14 @@ These projects enhance or use SQLite.swift: - [SQLiteMigrationManager.swift][] (inspired by [FMDBMigrationManager][]) - - [Delta: Math helper](https://apps.apple.com/app/delta-math-helper/id1436506800) - (see [Delta/Utils/Database.swift](https://github.com/GroupeMINASTE/Delta-iOS/blob/master/Delta/Utils/Database.swift) for production implementation example) - ## Alternatives Looking for something else? Try another Swift wrapper (or [FMDB][]): - - [Camembert](https://github.com/remirobert/Camembert) - [GRDB](https://github.com/groue/GRDB.swift) - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - [Squeal](https://github.com/nerdyc/Squeal) - - [SwiftData](https://github.com/ryanfowler/SwiftData) [Swift]: https://swift.org/ [SQLite3]: https://www.sqlite.org From dad179a555d0d2714ab1fcf69ba938073d17ff54 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 21 Oct 2022 18:47:32 +0200 Subject: [PATCH 0931/1046] Bump version number --- Documentation/Index.md | 12 ++++++------ README.md | 8 ++++---- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- Sources/SQLite/Typed/Expression.swift | 3 +-- Tests/SPM/Package.swift | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index fa91f7b7..65cf5114 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -83,7 +83,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") ] ``` @@ -104,7 +104,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.3 + github "stephencelis/SQLite.swift" ~> 0.14.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -134,7 +134,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.3' + pod 'SQLite.swift', '~> 0.14.0' end ``` @@ -148,7 +148,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.3' + pod 'SQLite.swift/standalone', '~> 0.14.0' end ``` @@ -158,7 +158,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.13.3' + pod 'SQLite.swift/standalone', '~> 0.14.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -174,7 +174,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.13.3' + pod 'SQLite.swift/SQLCipher', '~> 0.14.0' end ``` diff --git a/README.md b/README.md index cdc11dd5..ab1b67f5 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") ] ``` @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.13.3 + github "stephencelis/SQLite.swift" ~> 0.14.0 ``` 3. Run `carthage update` and @@ -191,7 +191,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.13.3' + pod 'SQLite.swift', '~> 0.14.0' end ``` @@ -250,7 +250,7 @@ device: [Submit a pull request]: https://github.com/stephencelis/SQLite.swift/fork -## Author +## Original author - [Stephen Celis](mailto:stephen@stephencelis.com) ([@stephencelis](https://twitter.com/stephencelis)) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index d36d0330..c30aa765 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.13.3" + s.version = "0.14.0" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 6fe89283..3d62e980 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1497,7 +1497,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.3; + MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1520,7 +1520,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.13.3; + MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index 95cdd3b9..de5afe2e 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -73,8 +73,7 @@ public protocol Expressible { extension Expressible { // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - // FIXME: make internal (0.13.0) - public func asSQL() -> String { + func asSQL() -> String { let expressed = expression var idx = 0 return expressed.template.reduce("") { template, character in diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index dd1cd45c..d5d2e9ff 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.13.0") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") ], targets: [ .target( From 136a5c5c07d0084d459bb6fa466eba1e564c3e9f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 22 Oct 2022 23:51:34 +0200 Subject: [PATCH 0932/1046] Add ExpressionTests --- Sources/SQLite/Typed/Expression.swift | 20 +++++++------- Tests/SQLiteTests/TestHelpers.swift | 2 +- Tests/SQLiteTests/Typed/ExpressionTests.swift | 27 ++++++++++++++++++- .../Typed/QueryIntegrationTests.swift | 7 ++++- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index de5afe2e..af125cc2 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -64,31 +64,31 @@ public struct Expression: ExpressionType { } -public protocol Expressible { +public protocol Expressible: CustomStringConvertible { var expression: Expression { get } } extension Expressible { + public var description: String { + asSQL() + } // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE func asSQL() -> String { let expressed = expression - var idx = 0 - return expressed.template.reduce("") { template, character in - let transcoded: String + return expressed.template.reduce(("", 0)) { memo, character in + let (template, index) = memo if character == "?" { - transcoded = transcode(expressed.bindings[idx]) - idx += 1 + precondition(index < expressed.bindings.count, "not enough bindings for expression") + return (template + transcode(expressed.bindings[index]), index + 1) } else { - transcoded = String(character) + return (template + String(character), index) } - return template + transcoded - } + }.0 } - } extension ExpressionType { diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 2a8c7e39..65cff8bb 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -12,7 +12,7 @@ class SQLiteTestCase: XCTestCase { trace = [String: Int]() db.trace { SQL in - print(SQL) + // print("SQL: \(SQL)") self.trace[SQL, default: 0] += 1 } } diff --git a/Tests/SQLiteTests/Typed/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift index 32100d4d..9155a45c 100644 --- a/Tests/SQLiteTests/Typed/ExpressionTests.swift +++ b/Tests/SQLiteTests/Typed/ExpressionTests.swift @@ -1,5 +1,30 @@ import XCTest -import SQLite +@testable import SQLite class ExpressionTests: XCTestCase { + + func test_asSQL_expression_bindings() { + let expression = Expression("foo ? bar", ["baz"]) + XCTAssertEqual(expression.asSQL(), "foo 'baz' bar") + } + + func test_asSQL_expression_bindings_quoting() { + let expression = Expression("foo ? bar", ["'baz'"]) + XCTAssertEqual(expression.asSQL(), "foo '''baz''' bar") + } + + func test_expression_custom_string_convertible() { + let expression = Expression("foo ? bar", ["baz"]) + XCTAssertEqual(expression.asSQL(), expression.description) + } + + func test_init_literal() { + let expression = Expression(literal: "literal") + XCTAssertEqual(expression.template, "literal") + } + + func test_init_identifier() { + let expression = Expression("identifier") + XCTAssertEqual(expression.template, "\"identifier\"") + } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 27b78c0a..3fd388e9 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -192,7 +192,12 @@ class QueryIntegrationTests: SQLiteTestCase { let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - print(query3.union(query4).order(Expression(literal: "weight")).asSQL()) + let sql = query3.union(query4).order(Expression(literal: "weight")).asSQL() + XCTAssertEqual(sql, + """ + SELECT "users".*, 1 AS weight FROM "users" WHERE ("email" = 'sally@example.com') UNION \ + SELECT "users".*, 2 AS weight FROM "users" WHERE ("email" = 'alice@example.com') ORDER BY weight + """) let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) From fc8f8df40b8434337338633fc5ae853736c1adad Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 24 Oct 2022 10:40:21 +0200 Subject: [PATCH 0933/1046] Remove outdated docs/links --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ab1b67f5..0d9396c6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ syntax _and_ intent. - [Well-documented][See Documentation] - Extensively tested - [SQLCipher][] support via CocoaPods + - [Schema query/migration][] - Works on [Linux](Documentation/Linux.md) (with some limitations) - Active support at [StackOverflow](https://stackoverflow.com/questions/tagged/sqlite.swift), @@ -27,6 +28,7 @@ syntax _and_ intent. [SQLCipher]: https://www.zetetic.net/sqlcipher/ [Full-text search]: Documentation/Index.md#full-text-search +[Schema query/migration]: Documentation/Index.md#querying-the-schema [See Documentation]: Documentation/Index.md#sqliteswift-documentation @@ -115,17 +117,10 @@ interactively, from the Xcode project’s playground. ![SQLite.playground Screen Shot](Documentation/Resources/playground@2x.png) -For a more comprehensive example, see -[this article][Create a Data Access Layer with SQLite.swift and Swift 2] -and the [companion repository][SQLiteDataAccessLayer2]. - - -[Create a Data Access Layer with SQLite.swift and Swift 2]: https://masteringswift.blogspot.com/2015/09/create-data-access-layer-with.html -[SQLiteDataAccessLayer2]: https://github.com/hoffmanjon/SQLiteDataAccessLayer2/tree/master - ## Installation -> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. +> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. +> Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. ### Swift Package Manager From 0eb58d8fa82e864d1071c92a2fc88747484d3f48 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 26 Oct 2022 23:34:17 +0200 Subject: [PATCH 0934/1046] Doc updates --- CHANGELOG.md | 1 + Documentation/Index.md | 90 +++++++++---------- Documentation/Release.md | 1 + Documentation/Upgrading.md | 11 +++ SQLite.playground/Contents.swift | 1 + Sources/SQLite/Schema/SchemaChanger.swift | 10 +-- .../Schema/SchemaChangerTests.swift | 14 +++ 7 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 Documentation/Upgrading.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b7de5a..92c89e9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.14.0 (tbd), [diff][diff-0.14.0] ======================================== +For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). * Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) diff --git a/Documentation/Index.md b/Documentation/Index.md index 65cf5114..bdd1da28 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -41,14 +41,19 @@ - [Updating Rows](#updating-rows) - [Deleting Rows](#deleting-rows) - [Transactions and Savepoints](#transactions-and-savepoints) + - [Querying the Schema](#querying-the-schema) - [Altering the Schema](#altering-the-schema) - [Renaming Tables](#renaming-tables) + - [Dropping Tables](#dropping-tables) - [Adding Columns](#adding-columns) - [Added Column Constraints](#added-column-constraints) + - [Schema Changer](#schemachanger) + - [Renaming Columns](#renaming-columns) + - [Dropping Columns](#dropping-columns) + - [Renaming/dropping tables](#renamingdropping-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) - - [Dropping Tables](#dropping-tables) - [Migrations and Schema Versioning](#migrations-and-schema-versioning) - [Custom Types](#custom-types) - [Date-Time Values](#date-time-values) @@ -1409,7 +1414,6 @@ for column in columns { SQLite.swift comes with several functions (in addition to `Table.create`) for altering a database schema in a type-safe manner. - ### Renaming Tables We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` @@ -1420,6 +1424,24 @@ try db.run(users.rename(Table("users_old"))) // ALTER TABLE "users" RENAME TO "users_old" ``` +### Dropping Tables + +We can build +[`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) +by calling the `dropTable` function on a `SchemaType`. + +```swift +try db.run(users.drop()) +// DROP TABLE "users" +``` + +The `drop` function has one additional parameter, `ifExists`, which (when +`true`) adds an `IF EXISTS` clause to the statement. + +```swift +try db.run(users.drop(ifExists: true)) +// DROP TABLE IF EXISTS "users" +``` ### Adding Columns @@ -1484,57 +1506,54 @@ tables](#creating-a-table). // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` -### Renaming Columns +### SchemaChanger -We can rename columns with the help of the `SchemaChanger` class: +Version 0.14.0 introduces `SchemaChanger`, an alternative API to perform more complex +migrations such as renaming columns. These operations work with all versions of +SQLite but use SQL statements such as `ALTER TABLE RENAME COLUMN` when available. + +#### Adding Columns ```swift +let newColumn = ColumnDefinition( + name: "new_text_column", + type: .TEXT, + nullable: true, + defaultValue: .stringLiteral("foo") +) + let schemaChanger = SchemaChanger(connection: db) + try schemaChanger.alter(table: "users") { table in - table.rename(column: "old_name", to: "new_name") + table.add(newColumn) } ``` -### Dropping Columns +#### Renaming Columns ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.drop(column: "email") + table.rename(column: "old_name", to: "new_name") } ``` -These operations will work with all versions of SQLite and use modern SQL -operations such as `DROP COLUMN` when available. - -### Adding Columns (SchemaChanger) - -The `SchemaChanger` provides an alternative API to add new columns: +#### Dropping Columns ```swift -let newColumn = ColumnDefinition( - name: "new_text_column", - type: .TEXT, - nullable: true, - defaultValue: .stringLiteral("foo") -) - let schemaChanger = SchemaChanger(connection: db) - try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.drop(column: "email") } ``` -### Renaming/dropping Tables (SchemaChanger) - -The `SchemaChanger` provides an alternative API to rename and drop tables: +#### Renaming/dropping Tables ```swift let schemaChanger = SchemaChanger(connection: db) try schemaChanger.rename(table: "users", to: "users_new") -try schemaChanger.drop(table: "emails") +try schemaChanger.drop(table: "emails", ifExists: false) ``` ### Indexes @@ -1592,25 +1611,6 @@ try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` -### Dropping Tables - -We can build -[`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) -by calling the `dropTable` function on a `SchemaType`. - -```swift -try db.run(users.drop()) -// DROP TABLE "users" -``` - -The `drop` function has one additional parameter, `ifExists`, which (when -`true`) adds an `IF EXISTS` clause to the statement. - -```swift -try db.run(users.drop(ifExists: true)) -// DROP TABLE IF EXISTS "users" -``` - ### Migrations and Schema Versioning You can use the convenience property on `Connection` to query and set the diff --git a/Documentation/Release.md b/Documentation/Release.md index 87d715f6..8ec67970 100644 --- a/Documentation/Release.md +++ b/Documentation/Release.md @@ -3,6 +3,7 @@ * [ ] Make sure current master branch has a green build * [ ] Make sure `SQLite.playground` runs without errors * [ ] Make sure `CHANGELOG.md` is up-to-date +* [ ] Add content to `Documentation/Upgrading.md` if needed * [ ] Update the version number in `SQLite.swift.podspec` * [ ] Run `pod lib lint` locally * [ ] Update the version numbers mentioned in `README.md`, `Documentation/Index.md` diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md new file mode 100644 index 00000000..8c398467 --- /dev/null +++ b/Documentation/Upgrading.md @@ -0,0 +1,11 @@ +# Upgrading + +## 0.13 → 0.14 + +- `Expression.asSQL()` is no longer available. Expressions now implement `CustomStringConvertible`, + where `description` returns the SQL. +- `Statement.prepareRowIterator()` is now longer available. Instead, use the methods + of the same name on `Connection`. +- `Blob` no longer wraps byte arrays and now uses `NSData`, which enables memory and + performance improvements. +- `Connection.registerTokenizer` is no longer available to register custom FTS4 tokenizers. diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 5a7ea379..0eafdd0d 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -52,6 +52,7 @@ for user in try Array(rowIterator) { /// also with `map()` let mapRowIterator = try db.prepareRowIterator(users) + let userIds = try mapRowIterator.map { $0[id] } /// using `failableNext()` on `RowIterator` diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 67f2b0d3..af710908 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -141,8 +141,8 @@ public class SchemaChanger: CustomStringConvertible { } } - public func drop(table: String) throws { - try dropTable(table) + public func drop(table: String, ifExists: Bool = true) throws { + try dropTable(table, ifExists: ifExists) } // Beginning with release 3.25.0 (2018-09-15), references to the table within trigger bodies and @@ -192,7 +192,7 @@ public class SchemaChanger: CustomStringConvertible { private func moveTable(from: String, to: String, options: Options = .default, operation: Operation? = nil) throws { try copyTable(from: from, to: to, options: options, operation: operation) - try dropTable(from) + try dropTable(from, ifExists: true) } private func copyTable(from: String, to: String, options: Options = .default, operation: Operation?) throws { @@ -225,8 +225,8 @@ public class SchemaChanger: CustomStringConvertible { } } - private func dropTable(_ table: String) throws { - try connection.run("DROP TABLE IF EXISTS \(table.quote())") + private func dropTable(_ table: String, ifExists: Bool) throws { + try connection.run("DROP TABLE \(ifExists ? "IF EXISTS" : "") \(table.quote())") } private func copyTableContents(from: TableDefinition, to: TableDefinition) throws { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 2894e5ce..40f65b62 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -135,6 +135,20 @@ class SchemaChangerTests: SQLiteTestCase { } } + func test_drop_table_if_exists_true() throws { + try schemaChanger.drop(table: "xxx", ifExists: true) + } + + func test_drop_table_if_exists_false() throws { + XCTAssertThrowsError(try schemaChanger.drop(table: "xxx", ifExists: false)) { error in + if case Result.error(let message, _, _) = error { + XCTAssertEqual(message, "no such table: xxx") + } else { + XCTFail("unexpected error \(error)") + } + } + } + func test_rename_table() throws { try schemaChanger.rename(table: "users", to: "users_new") let users_new = Table("users_new") From 25708c9804d906d039a293538e45877e9262d433 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 26 Oct 2022 23:48:00 +0200 Subject: [PATCH 0935/1046] Updates --- CHANGELOG.md | 2 +- Documentation/Index.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c89e9d..2783a67d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -0.14.0 (tbd), [diff][diff-0.14.0] +0.14.0 (27-10-2022), [diff][diff-0.14.0] ======================================== For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). diff --git a/Documentation/Index.md b/Documentation/Index.md index bdd1da28..20469037 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -50,7 +50,7 @@ - [Schema Changer](#schemachanger) - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - - [Renaming/dropping tables](#renamingdropping-tables) + - [Renaming/dropping Tables](#renamingdropping-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) @@ -173,7 +173,7 @@ See the [sqlite3 podspec][sqlite3pod] for more details. #### Using SQLite.swift with SQLCipher If you want to use [SQLCipher][] with SQLite.swift you can require the -`SQLCipher` subspec in your Podfile: +`SQLCipher` subspec in your Podfile (SPM is not supported yet, see [#1084](/issues/1084)): ```ruby target 'YourAppTargetName' do @@ -2183,7 +2183,7 @@ try db.detach("external") // DETACH DATABASE 'external' ``` -When compiled for SQLCipher, you can additionally pass a `key` parameter to `attach`: +When compiled for SQLCipher, we can additionally pass a `key` parameter to `attach`: ```swift try db.attach(.uri("encrypted.sqlite"), as: "encrypted", key: "secret") From ac09389994bb6549a3f978d996f2d3a06b5a0071 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 00:04:22 +0200 Subject: [PATCH 0936/1046] Mention group container problems Closes #1042 --- Documentation/Index.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 20469037..363443d6 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -9,6 +9,7 @@ - [Connecting to a Database](#connecting-to-a-database) - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) + - [In a Shared Group Container](#in-a-shared-group-container) - [In-Memory Databases](#in-memory-databases) - [URI parameters](#uri-parameters) - [Thread-Safety](#thread-safety) @@ -173,7 +174,7 @@ See the [sqlite3 podspec][sqlite3pod] for more details. #### Using SQLite.swift with SQLCipher If you want to use [SQLCipher][] with SQLite.swift you can require the -`SQLCipher` subspec in your Podfile (SPM is not supported yet, see [#1084](/issues/1084)): +`SQLCipher` subspec in your Podfile (SPM is not supported yet, see [#1084](https://github.com/stephencelis/SQLite.swift/issues/1084)): ```ruby target 'YourAppTargetName' do @@ -330,6 +331,13 @@ let db = try Connection(path, readonly: true) > We welcome changes to the above sample code to show how to successfully copy and use a bundled "seed" > database for writing in an app. +#### In a shared group container + +It is not recommend to store databases in a [shared group container], +some users have reported crashes ([#1042](https://github.com/stephencelis/SQLite.swift/issues/1042)). + +[shared group container]: https://developer.apple.com/documentation/foundation/filemanager/1412643-containerurl# + #### In-Memory Databases If you omit the path, SQLite.swift will provision an [in-memory From 194d99addcfbf76795d00c072470623b4ded5ad8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 00:51:19 +0200 Subject: [PATCH 0937/1046] Fix ambiguous use of description --- Sources/SQLite/Extensions/FTS4.swift | 50 ++++++++----------- Sources/SQLite/Extensions/FTS5.swift | 18 +++---- Sources/SQLite/Typed/Expression.swift | 10 ++-- Tests/SQLiteTests/Typed/ExpressionTests.swift | 5 ++ 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index e9e65746..8e91e453 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -117,7 +117,7 @@ public struct Tokenizer { // https://sqlite.org/fts5.html#the_experimental_trigram_tokenizer public static func Trigram(caseSensitive: Bool = false) -> Tokenizer { - return Tokenizer("trigram", ["case_sensitive", caseSensitive ? "1" : "0"]) + Tokenizer("trigram", ["case_sensitive", caseSensitive ? "1" : "0"]) } public static func Custom(_ name: String) -> Tokenizer { @@ -236,18 +236,12 @@ open class FTSConfig { } } - @discardableResult mutating func append(_ key: String, value: CustomStringConvertible?) -> Options { - append(key, value: value?.description) + @discardableResult mutating func append(_ key: String, value: String) -> Options { + append(key, value: Expression(value)) } - @discardableResult mutating func append(_ key: String, value: String?) -> Options { - append(key, value: value.map { Expression($0) }) - } - - @discardableResult mutating func append(_ key: String, value: Expressible?) -> Options { - if let value = value { - arguments.append("=".join([Expression(literal: key), value])) - } + @discardableResult mutating func append(_ key: String, value: Expressible) -> Options { + arguments.append("=".join([Expression(literal: key), value])) return self } } @@ -256,26 +250,16 @@ open class FTSConfig { /// Configuration for the [FTS4](https://www.sqlite.org/fts3.html) extension. open class FTS4Config: FTSConfig { /// [The matchinfo= option](https://www.sqlite.org/fts3.html#section_6_4) - public enum MatchInfo: CustomStringConvertible { + public enum MatchInfo: String { case fts3 - public var description: String { - "fts3" - } } /// [FTS4 options](https://www.sqlite.org/fts3.html#fts4_options) - public enum Order: CustomStringConvertible { + public enum Order: String { /// Data structures are optimized for returning results in ascending order by docid (default) case asc /// FTS4 stores its data in such a way as to optimize returning results in descending order by docid. case desc - - public var description: String { - switch self { - case .asc: return "asc" - case .desc: return "desc" - } - } } var compressFunction: String? @@ -322,11 +306,21 @@ open class FTS4Config: FTSConfig { for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { options.append("notindexed", value: column) } - options.append("languageid", value: languageId) - options.append("compress", value: compressFunction) - options.append("uncompress", value: uncompressFunction) - options.append("matchinfo", value: matchInfo) - options.append("order", value: order) + if let languageId = languageId { + options.append("languageid", value: languageId) + } + if let compressFunction = compressFunction { + options.append("compress", value: compressFunction) + } + if let uncompressFunction = uncompressFunction { + options.append("uncompress", value: uncompressFunction) + } + if let matchInfo = matchInfo { + options.append("matchinfo", value: matchInfo.rawValue) + } + if let order = order { + options.append("order", value: order.rawValue) + } return options } } diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index f108bbec..2e4f65fb 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -33,21 +33,13 @@ extension Module { /// **Note:** this is currently only applicable when using SQLite.swift together with a FTS5-enabled version /// of SQLite. open class FTS5Config: FTSConfig { - public enum Detail: CustomStringConvertible { + public enum Detail: String { /// store rowid, column number, term offset case full /// store rowid, column number case column /// store rowid case none - - public var description: String { - switch self { - case .full: return "full" - case .column: return "column" - case .none: return "none" - } - } } var detail: Detail? @@ -77,11 +69,15 @@ open class FTS5Config: FTSConfig { override func options() -> Options { var options = super.options() - options.append("content_rowid", value: contentRowId) + if let contentRowId = contentRowId { + options.append("content_rowid", value: contentRowId) + } if let columnSize = columnSize { options.append("columnsize", value: Expression(value: columnSize)) } - options.append("detail", value: detail) + if let detail = detail { + options.append("detail", value: detail.rawValue) + } return options } diff --git a/Sources/SQLite/Typed/Expression.swift b/Sources/SQLite/Typed/Expression.swift index af125cc2..dcc44fe4 100644 --- a/Sources/SQLite/Typed/Expression.swift +++ b/Sources/SQLite/Typed/Expression.swift @@ -22,7 +22,7 @@ // THE SOFTWARE. // -public protocol ExpressionType: Expressible { // extensions cannot have inheritance clauses +public protocol ExpressionType: Expressible, CustomStringConvertible { // extensions cannot have inheritance clauses associatedtype UnderlyingType = Void @@ -47,6 +47,9 @@ extension ExpressionType { self.init(expression.template, expression.bindings) } + public var description: String { + asSQL() + } } /// An `Expression` represents a raw SQL fragment and any associated bindings. @@ -64,16 +67,13 @@ public struct Expression: ExpressionType { } -public protocol Expressible: CustomStringConvertible { +public protocol Expressible { var expression: Expression { get } } extension Expressible { - public var description: String { - asSQL() - } // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE func asSQL() -> String { diff --git a/Tests/SQLiteTests/Typed/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift index 9155a45c..147d62e9 100644 --- a/Tests/SQLiteTests/Typed/ExpressionTests.swift +++ b/Tests/SQLiteTests/Typed/ExpressionTests.swift @@ -18,6 +18,11 @@ class ExpressionTests: XCTestCase { XCTAssertEqual(expression.asSQL(), expression.description) } + func test_builtin_unambiguously_custom_string_convertible() { + let integer: Int = 45 + XCTAssertEqual(integer.description, "45") + } + func test_init_literal() { let expression = Expression(literal: "literal") XCTAssertEqual(expression.template, "literal") From 367f6aaee8228f71eedcfcc34e1433e79c46c632 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 10:17:46 +0200 Subject: [PATCH 0938/1046] add(_ column:) => add(column:) --- Documentation/Index.md | 2 +- SQLite.playground/Contents.swift | 2 +- Sources/SQLite/Schema/SchemaChanger.swift | 2 +- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 363443d6..c518c739 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1533,7 +1533,7 @@ let newColumn = ColumnDefinition( let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.add(column: newColumn) } ``` diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index 0eafdd0d..f651cbc1 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -115,7 +115,7 @@ print(columns) let schemaChanger = SchemaChanger(connection: db) try schemaChanger.alter(table: "users") { table in - table.add(ColumnDefinition(name: "age", type: .INTEGER)) + table.add(column: ColumnDefinition(name: "age", type: .INTEGER)) table.rename(column: "email", to: "electronic_mail") table.drop(column: "name") } diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index af710908..af7b5e27 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -95,7 +95,7 @@ public class SchemaChanger: CustomStringConvertible { self.name = name } - public func add(_ column: ColumnDefinition) { + public func add(column: ColumnDefinition) { operations.append(.addColumn(column)) } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 40f65b62..9f8e21f4 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -99,7 +99,7 @@ class SchemaChangerTests: SQLiteTestCase { defaultValue: .stringLiteral("foo")) try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.add(column: newColumn) } let columns = try schema.columnDefinitions(table: "users") @@ -114,7 +114,7 @@ class SchemaChangerTests: SQLiteTestCase { type: .TEXT) XCTAssertThrowsError(try schemaChanger.alter(table: "users") { table in - table.add(newColumn) + table.add(column: newColumn) }) { error in if case SchemaChanger.Error.invalidColumnDefinition(_) = error { XCTAssertEqual("Invalid column definition: can not add primary key column", error.localizedDescription) From 2c4af8526e112e3a23df141e988d08a28e475ecd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 10:21:13 +0200 Subject: [PATCH 0939/1046] Update docs --- Documentation/Index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index c518c739..ada9b7c3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -49,9 +49,10 @@ - [Adding Columns](#adding-columns) - [Added Column Constraints](#added-column-constraints) - [Schema Changer](#schemachanger) + - [Adding Columns](#adding-columns) - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - - [Renaming/dropping Tables](#renamingdropping-tables) + - [Renaming/Dropping Tables](#renamingdropping-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) @@ -1555,7 +1556,7 @@ try schemaChanger.alter(table: "users") { table in } ``` -#### Renaming/dropping Tables +#### Renaming/Dropping Tables ```swift let schemaChanger = SchemaChanger(connection: db) From 3161f06acd378ff3e5e91c0c590c9dc2edba1ce5 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 27 Oct 2022 12:06:14 +0200 Subject: [PATCH 0940/1046] Use direct links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d9396c6..ac0c2a7a 100644 --- a/README.md +++ b/README.md @@ -277,10 +277,10 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [GitHubActionBadge]: https://img.shields.io/github/workflow/status/stephencelis/SQLite.swift/Build%20and%20test -[CocoaPodsVersionBadge]: https://cocoapod-badges.herokuapp.com/v/SQLite.swift/badge.png +[CocoaPodsVersionBadge]: https://img.shields.io/cocoapods/v/SQLite.swift.svg?style=flat [CocoaPodsVersionLink]: https://cocoapods.org/pods/SQLite.swift -[PlatformBadge]: https://cocoapod-badges.herokuapp.com/p/SQLite.swift/badge.png +[PlatformBadge]: https://img.shields.io/cocoapods/p/SQLite.swift.svg?style=flat [PlatformLink]: https://cocoapods.org/pods/SQLite.swift [CartagheBadge]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat From 1235d44cd3e8596e842a47c03efbcbdb61797024 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 1 Nov 2022 22:28:21 +0100 Subject: [PATCH 0941/1046] Revert changes to Blob --- CHANGELOG.md | 6 +++ Documentation/Index.md | 12 ++--- README.md | 4 +- SQLite.swift.podspec | 2 +- Sources/SQLite/Core/Blob.swift | 50 ++++--------------- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Statement.swift | 4 +- Sources/SQLite/Extensions/Cipher.swift | 6 +-- Sources/SQLite/Foundation.swift | 6 ++- Tests/SPM/Package.swift | 2 +- Tests/SQLiteTests/Core/BlobTests.swift | 8 +-- Tests/SQLiteTests/Core/StatementTests.swift | 4 +- .../SQLiteTests/Extensions/CipherTests.swift | 11 ++-- Tests/SQLiteTests/FoundationTests.swift | 2 +- 14 files changed, 47 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2783a67d..fc759118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.14.1 (01-11-2022), [diff][diff-0.14.1] +======================================== + +* Reverted `Blob` changes (See [#1167][] for rationale). + 0.14.0 (27-10-2022), [diff][diff-0.14.0] ======================================== For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). @@ -129,6 +134,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.13.2]: https://github.com/stephencelis/SQLite.swift/compare/0.13.1...0.13.2 [diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 [diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 +[diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 diff --git a/Documentation/Index.md b/Documentation/Index.md index ada9b7c3..730fe30b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -90,7 +90,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ] ``` @@ -111,7 +111,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.0 + github "stephencelis/SQLite.swift" ~> 0.14.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -141,7 +141,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.0' + pod 'SQLite.swift', '~> 0.14.1' end ``` @@ -155,7 +155,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.0' + pod 'SQLite.swift/standalone', '~> 0.14.1' end ``` @@ -165,7 +165,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.0' + pod 'SQLite.swift/standalone', '~> 0.14.1' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -181,7 +181,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.14.0' + pod 'SQLite.swift/SQLCipher', '~> 0.14.1' end ``` diff --git a/README.md b/README.md index ac0c2a7a..4cf72d03 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ] ``` @@ -155,7 +155,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.0 + github "stephencelis/SQLite.swift" ~> 0.14.1 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index c30aa765..f32282d3 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.14.0" + s.version = "0.14.1" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Sources/SQLite/Core/Blob.swift b/Sources/SQLite/Core/Blob.swift index c8bd2c15..a709fb42 100644 --- a/Sources/SQLite/Core/Blob.swift +++ b/Sources/SQLite/Core/Blob.swift @@ -21,64 +21,36 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -import Foundation -public final class Blob { +public struct Blob { - public let data: NSData + public let bytes: [UInt8] - public var bytes: UnsafePointer { - data.bytes.assumingMemoryBound(to: UInt8.self) + public init(bytes: [UInt8]) { + self.bytes = bytes } - public var length: Int { - data.count - } - - public convenience init(bytes: [UInt8]) { - guard bytes.count > 0 else { - self.init(data: NSData()) - return - } - self.init(data: NSData(bytes: bytes, length: bytes.count)) - } - - public convenience init(bytes: UnsafeRawPointer, length: Int) { - self.init(data: NSData(bytes: bytes, length: length)) - } - - public init(data: NSData) { - precondition(!(data is NSMutableData), "Blob cannot be initialized with mutable data") - self.data = data + public init(bytes: UnsafeRawPointer, length: Int) { + let i8bufptr = UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: UInt8.self), count: length) + self.init(bytes: [UInt8](i8bufptr)) } public func toHex() -> String { - guard length > 0 else { return "" } - - var hex = "" - for idx in 0.. Bool { - lhs.data == rhs.data + lhs.bytes == rhs.bytes } diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 2a7ec487..0e51e651 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -741,7 +741,7 @@ extension Context { func set(result: Binding?) { switch result { case let blob as Blob: - sqlite3_result_blob(self, blob.bytes, Int32(blob.length), nil) + sqlite3_result_blob(self, blob.bytes, Int32(blob.bytes.count), nil) case let double as Double: sqlite3_result_double(self, double) case let int as Int64: diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 80450258..6cb2e5d3 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -103,10 +103,10 @@ public final class Statement { switch value { case .none: sqlite3_bind_null(handle, Int32(idx)) - case let value as Blob where value.length == 0: + case let value as Blob where value.bytes.count == 0: sqlite3_bind_zeroblob(handle, Int32(idx), 0) case let value as Blob: - sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) + sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT) case let value as Double: sqlite3_bind_double(handle, Int32(idx), value) case let value as Int64: diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 71889ae5..8af04df9 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -32,7 +32,7 @@ extension Connection { } public func key(_ key: Blob, db: String = "main") throws { - try _key_v2(db: db, keyPointer: key.bytes, keySize: key.length) + try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } /// Same as `key(_ key: String, db: String = "main")`, running "PRAGMA cipher_migrate;" @@ -53,7 +53,7 @@ extension Connection { /// Same as `[`keyAndMigrate(_ key: String, db: String = "main")` accepting byte array as key public func keyAndMigrate(_ key: Blob, db: String = "main") throws { - try _key_v2(db: db, keyPointer: key.bytes, keySize: key.length, migrate: true) + try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count, migrate: true) } /// Change the key on an open database. NB: only works if the database is already encrypted. @@ -68,7 +68,7 @@ extension Connection { } public func rekey(_ key: Blob, db: String = "main") throws { - try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.length) + try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count) } /// Converts a non-encrypted database to an encrypted one. diff --git a/Sources/SQLite/Foundation.swift b/Sources/SQLite/Foundation.swift index e81c4964..44a31736 100644 --- a/Sources/SQLite/Foundation.swift +++ b/Sources/SQLite/Foundation.swift @@ -31,11 +31,13 @@ extension Data: Value { } public static func fromDatatypeValue(_ dataValue: Blob) -> Data { - dataValue.data as Data + Data(dataValue.bytes) } public var datatypeValue: Blob { - Blob(data: self as NSData) + withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Blob in + Blob(bytes: pointer.baseAddress!, length: count) + } } } diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index d5d2e9ff..3e329552 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.0") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ], targets: [ .target( diff --git a/Tests/SQLiteTests/Core/BlobTests.swift b/Tests/SQLiteTests/Core/BlobTests.swift index bef4bb83..f2e9435e 100644 --- a/Tests/SQLiteTests/Core/BlobTests.swift +++ b/Tests/SQLiteTests/Core/BlobTests.swift @@ -25,14 +25,14 @@ class BlobTests: XCTestCase { func test_init_array() { let blob = Blob(bytes: [42, 43, 44]) - XCTAssertEqual([UInt8](blob.data), [42, 43, 44]) + XCTAssertEqual(blob.bytes, [42, 43, 44]) } func test_init_unsafeRawPointer() { let pointer = UnsafeMutablePointer.allocate(capacity: 3) pointer.initialize(repeating: 42, count: 3) let blob = Blob(bytes: pointer, length: 3) - XCTAssertEqual([UInt8](blob.data), [42, 42, 42]) + XCTAssertEqual(blob.bytes, [42, 42, 42]) } func test_equality() { @@ -44,8 +44,4 @@ class BlobTests: XCTestCase { XCTAssertEqual(blob1, blob2) XCTAssertNotEqual(blob1, blob3) } - - func XXX_test_init_with_mutable_data_fails() { - _ = Blob(data: NSMutableData()) - } } diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 0d82e890..5f212505 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -22,7 +22,7 @@ class StatementTests: SQLiteTestCase { let statement = try db.prepare("SELECT email FROM users") XCTAssert(try statement.step()) let blob = statement.row[0] as Blob - XCTAssertEqual("alice@example.com", String(data: blob.data as Data, encoding: .utf8)!) + XCTAssertEqual("alice@example.com", String(bytes: blob.bytes, encoding: .utf8)!) } func test_zero_sized_blob_returns_null() throws { @@ -31,7 +31,7 @@ class StatementTests: SQLiteTestCase { try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) - XCTAssertEqual([], [UInt8](blobValue.data)) + XCTAssertEqual([], blobValue.bytes) } func test_prepareRowIterator() throws { diff --git a/Tests/SQLiteTests/Extensions/CipherTests.swift b/Tests/SQLiteTests/Extensions/CipherTests.swift index bd7f2042..bc89cfa2 100644 --- a/Tests/SQLiteTests/Extensions/CipherTests.swift +++ b/Tests/SQLiteTests/Extensions/CipherTests.swift @@ -19,7 +19,7 @@ class CipherTests: XCTestCase { // db2 let key2 = keyData() - try db2.key(Blob(data: key2)) + try db2.key(Blob(bytes: key2.bytes, length: key2.length)) try db2.run("CREATE TABLE foo (bar TEXT)") try db2.run("INSERT INTO foo (bar) VALUES ('world')") @@ -47,7 +47,7 @@ class CipherTests: XCTestCase { func test_data_rekey() throws { let newKey = keyData() - try db2.rekey(Blob(data: newKey)) + try db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length)) XCTAssertEqual(1, try db2.scalar("SELECT count(*) FROM foo") as? Int64) } @@ -79,12 +79,11 @@ class CipherTests: XCTestCase { // sqlite> CREATE TABLE foo (bar TEXT); // sqlite> INSERT INTO foo (bar) VALUES ('world'); guard let cipherVersion: String = db1.cipherVersion, - cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") - else { return } + cipherVersion.starts(with: "3.") || cipherVersion.starts(with: "4.") else { return } let encryptedFile = cipherVersion.starts(with: "3.") ? - fixture("encrypted-3.x", withExtension: "sqlite") : - fixture("encrypted-4.x", withExtension: "sqlite") + fixture("encrypted-3.x", withExtension: "sqlite") : + fixture("encrypted-4.x", withExtension: "sqlite") try FileManager.default.setAttributes([FileAttributeKey.immutable: 1], ofItemAtPath: encryptedFile) XCTAssertFalse(FileManager.default.isWritableFile(atPath: encryptedFile)) diff --git a/Tests/SQLiteTests/FoundationTests.swift b/Tests/SQLiteTests/FoundationTests.swift index ffd3fae1..453febcd 100644 --- a/Tests/SQLiteTests/FoundationTests.swift +++ b/Tests/SQLiteTests/FoundationTests.swift @@ -5,7 +5,7 @@ class FoundationTests: XCTestCase { func testDataFromBlob() { let data = Data([1, 2, 3]) let blob = data.datatypeValue - XCTAssertEqual([1, 2, 3], [UInt8](blob.data)) + XCTAssertEqual([1, 2, 3], blob.bytes) } func testBlobToData() { From f06b8df5bc0fb73d6ac46adaf5f047fded953f79 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 2 Nov 2022 09:04:08 +0100 Subject: [PATCH 0942/1046] Removed --- Documentation/Upgrading.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md index 8c398467..f2cc2ecb 100644 --- a/Documentation/Upgrading.md +++ b/Documentation/Upgrading.md @@ -6,6 +6,4 @@ where `description` returns the SQL. - `Statement.prepareRowIterator()` is now longer available. Instead, use the methods of the same name on `Connection`. -- `Blob` no longer wraps byte arrays and now uses `NSData`, which enables memory and - performance improvements. - `Connection.registerTokenizer` is no longer available to register custom FTS4 tokenizers. From d926517c090042ee3268048f1d46020f94b39d0c Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Tue, 20 Dec 2022 12:18:53 -0800 Subject: [PATCH 0943/1046] adds support for extended error codes --- Sources/SQLite/Core/Connection.swift | 7 ++++++ Sources/SQLite/Core/Result.swift | 24 ++++++++++++++++++- Tests/SQLiteTests/Core/ConnectionTests.swift | 4 ++++ Tests/SQLiteTests/Core/ResultTests.swift | 13 ++++++++++ .../Typed/QueryIntegrationTests.swift | 13 ++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 0e51e651..f74ed82c 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -156,6 +156,13 @@ public final class Connection { Int(sqlite3_total_changes(handle)) } + /// Whether or not the database will return extended error codes when errors are handled. + public var usesExtendedErrorCodes: Bool = false { + didSet { + sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0) + } + } + // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 3de4d25d..06f9cc74 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -21,11 +21,27 @@ public enum Result: Error { /// - statement: the statement which produced the error case error(message: String, code: Int32, statement: Statement?) + /// Represents a SQLite specific [extended error code] (https://sqlite.org/rescode.html#primary_result_codes_versus_extended_result_codes) + /// + /// - message: English-language text that describes the error + /// + /// - extendedCode: SQLite [extended error code](https://sqlite.org/rescode.html#extended_result_code_list) + /// + /// - statement: the statement which produced the error + case extendedError(message: String, extendedCode: Int32, statement: Statement?) + init?(errorCode: Int32, connection: Connection, statement: Statement? = nil) { guard !Result.successCodes.contains(errorCode) else { return nil } let message = String(cString: sqlite3_errmsg(connection.handle)) - self = .error(message: message, code: errorCode, statement: statement) + + guard connection.usesExtendedErrorCodes else { + self = .error(message: message, code: errorCode, statement: statement) + return + } + + let extendedErrorCode = sqlite3_extended_errcode(connection.handle) + self = .extendedError(message: message, extendedCode: extendedErrorCode, statement: statement) } } @@ -40,6 +56,12 @@ extension Result: CustomStringConvertible { } else { return "\(message) (code: \(errorCode))" } + case let .extendedError(message, extendedCode, statement): + if let statement = statement { + return "\(message) (\(statement)) (extended code: \(extendedCode))" + } else { + return "\(message) (extended code: \(extendedCode))" + } } } } diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 9d623f3f..6a1d94ae 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -111,6 +111,10 @@ class ConnectionTests: SQLiteTestCase { XCTAssertEqual(2, db.totalChanges) } + func test_useExtendedErrorCodes_returnsFalseDefault() throws { + XCTAssertFalse(db.usesExtendedErrorCodes) + } + func test_prepare_preparesAndReturnsStatements() throws { _ = try db.prepare("SELECT * FROM users WHERE admin = 0") _ = try db.prepare("SELECT * FROM users WHERE admin = ?", 0) diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index fab4a0bb..d3c8bb1f 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -53,4 +53,17 @@ class ResultTests: XCTestCase { XCTAssertEqual("not an error (SELECT 1) (code: 21)", Result(errorCode: SQLITE_MISUSE, connection: connection, statement: statement)?.description) } + + func test_init_extended_with_other_code_returns_error() { + connection.usesExtendedErrorCodes = true + if case .some(.extendedError(let message, let extendedCode, let statement)) = + Result(errorCode: SQLITE_MISUSE, connection: connection, statement: nil) { + XCTAssertEqual("not an error", message) + XCTAssertEqual(extendedCode, 0) + XCTAssertNil(statement) + XCTAssert(connection === connection) + } else { + XCTFail("no error") + } + } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 3fd388e9..a86708ec 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -229,6 +229,19 @@ class QueryIntegrationTests: SQLiteTestCase { } } + func test_extendedErrorCodes_catchConstraintError() throws { + db.usesExtendedErrorCodes = true + try db.run(users.insert(email <- "alice@example.com")) + do { + try db.run(users.insert(email <- "alice@example.com")) + XCTFail("expected error") + } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { + // SQLITE_CONSTRAINT_UNIQUE expected + } catch let error { + XCTFail("unexpected error: \(error)") + } + } + // https://github.com/stephencelis/SQLite.swift/issues/285 func test_order_by_random() throws { try insertUsers(["a", "b", "c'"]) From 0fc4b7b223e5402fd497c5929e220bbec02608d5 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Tue, 20 Dec 2022 12:21:24 -0800 Subject: [PATCH 0944/1046] lint --- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f74ed82c..26cadea4 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -162,7 +162,7 @@ public final class Connection { sqlite3_extended_result_codes(handle, usesExtendedErrorCodes ? 1 : 0) } } - + // MARK: - Execute /// Executes a batch of SQL statements. diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 06f9cc74..9fe47ada 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -56,7 +56,7 @@ extension Result: CustomStringConvertible { } else { return "\(message) (code: \(errorCode))" } - case let .extendedError(message, extendedCode, statement): + case let .extendedError(message, extendedCode, statement): if let statement = statement { return "\(message) (\(statement)) (extended code: \(extendedCode))" } else { diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index a86708ec..3b973fa0 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -235,7 +235,7 @@ class QueryIntegrationTests: SQLiteTestCase { do { try db.run(users.insert(email <- "alice@example.com")) XCTFail("expected error") - } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { + } catch let Result.extendedError(_, extendedCode, _) where extendedCode == 2_067 { // SQLITE_CONSTRAINT_UNIQUE expected } catch let error { XCTFail("unexpected error: \(error)") From 4b9ea97872a241fad38c221afad7a701fc987868 Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 23 Dec 2022 00:37:03 -0800 Subject: [PATCH 0945/1046] Fix incorrect column names for SELECT * preceded by a WITH In #1139 I introduced support for the `WITH` clause. My implementation contains a bug: the statement preparer doesn't produce the correct result column names for queries containing a `SELECT *` preceded by a `WITH`. For example, consider the following statement: ``` WITH temp AS ( SELECT id, email from users) SELECT * from temp ``` An error would be thrown when preparing this statement because the glob expansion procedure would try to look up the column names for the result by looking up the column names for the query `SELECT * from temp`. This does not work because `temp` is a temporary view defined in the `WITH` clause. To fix this, I modified the glob expansion procedure to include the `WITH` clause in the query used to look up the result column names. --- Sources/SQLite/Typed/Query.swift | 19 +++++++++++ .../Typed/QueryIntegrationTests.swift | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 04665f39..feff2f69 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1036,10 +1036,29 @@ extension Connection { let column = names.removeLast() let namespace = names.joined(separator: ".") + // Return a copy of the input "with" clause stripping all subclauses besides "select", "join", and "with". + func strip(_ with: WithClauses) -> WithClauses { + var stripped = WithClauses() + stripped.recursive = with.recursive + for subclause in with.clauses { + let query = subclause.query + var strippedQuery = type(of: query).init(query.clauses.from.name, database: query.clauses.from.database) + strippedQuery.clauses.select = query.clauses.select + strippedQuery.clauses.join = query.clauses.join + strippedQuery.clauses.with = strip(query.clauses.with) + + var strippedSubclause = WithClauses.Clause(alias: subclause.alias, query: strippedQuery) + strippedSubclause.columns = subclause.columns + stripped.clauses.append(strippedSubclause) + } + return stripped + } + func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { { (queryType: QueryType) throws -> Void in var query = type(of: queryType).init(queryType.clauses.from.name, database: queryType.clauses.from.database) query.clauses.select = queryType.clauses.select + query.clauses.with = strip(queryType.clauses.with) let expression = query.expression var names = try self.prepare(expression.template, expression.bindings).columnNames.map { $0.quote() } if namespace { names = names.map { "\(queryType.tableName().expression.template).\($0)" } } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 3fd388e9..d99b8457 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -276,6 +276,39 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(21, sum) } + + /// Verify that `*` is properly expanded in a SELECT statement following a WITH clause. + func test_with_glob_expansion() throws { + let names = Table("names") + let name = Expression("name") + try db.run(names.create { builder in + builder.column(email) + builder.column(name) + }) + + try db.run(users.insert(email <- "alice@example.com")) + try db.run(names.insert(email <- "alice@example.com", name <- "Alice")) + + // WITH intermediate AS ( SELECT ... ) SELECT * FROM intermediate + let intermediate = Table("intermediate") + let rows = try db.prepare( + intermediate + .with(intermediate, + as: users + .select([id, users[email], name]) + .join(names, on: names[email] == users[email]) + .where(users[email] == "alice@example.com") + )) + + // There should be at least one row in the result. + let row = try XCTUnwrap(rows.makeIterator().next()) + + // Verify the column names + XCTAssertEqual(row.columnNames.count, 3) + XCTAssertNotNil(row[id]) + XCTAssertNotNil(row[name]) + XCTAssertNotNil(row[email]) + } } extension Connection { From bdc3be7fdf9b6289ee0e0ea5c28002839d8cf54d Mon Sep 17 00:00:00 2001 From: Matthew Jee Date: Fri, 23 Dec 2022 00:45:34 -0800 Subject: [PATCH 0946/1046] linter error --- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index d99b8457..f5105a09 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -302,7 +302,7 @@ class QueryIntegrationTests: SQLiteTestCase { // There should be at least one row in the result. let row = try XCTUnwrap(rows.makeIterator().next()) - + // Verify the column names XCTAssertEqual(row.columnNames.count, 3) XCTAssertNotNil(row[id]) From b7c6fd6f94eedf6995ff607ae1625f71041377aa Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Sun, 8 Jan 2023 14:07:12 +0200 Subject: [PATCH 0947/1046] Fix typos --- Documentation/Index.md | 2 +- SQLite.playground/Contents.swift | 2 +- Sources/SQLite/Extensions/Cipher.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index 730fe30b..583d5def 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -691,7 +691,7 @@ do { } ``` -Multiple rows can be inserted at once by similarily calling `insertMany` with an array of +Multiple rows can be inserted at once by similarly calling `insertMany` with an array of per-row [setters](#setters). ```swift diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index f651cbc1..d893e696 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -65,7 +65,7 @@ do { // Handle error } -/// define a virtual tabe for the FTS index +/// define a virtual table for the FTS index let emails = VirtualTable("emails") let subject = Expression("subject") diff --git a/Sources/SQLite/Extensions/Cipher.swift b/Sources/SQLite/Extensions/Cipher.swift index 8af04df9..03194ef1 100644 --- a/Sources/SQLite/Extensions/Cipher.swift +++ b/Sources/SQLite/Extensions/Cipher.swift @@ -93,7 +93,7 @@ extension Connection { // per recommendation of SQLCipher authors let migrateResult = try scalar("PRAGMA cipher_migrate;") if (migrateResult as? String) != "0" { - // "0" is the result of successfull migration + // "0" is the result of successful migration throw Result.error(message: "Error in cipher migration, result \(migrateResult.debugDescription)", code: 1, statement: nil) } } From 0d905d788e386854effc7133f1faf31fe9a3c9e2 Mon Sep 17 00:00:00 2001 From: "Pongsakorn Onsri(Ken)" Date: Fri, 17 Mar 2023 17:21:31 +0700 Subject: [PATCH 0948/1046] fix Xcode build error --- Sources/SQLite/Core/Connection+Pragmas.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Core/Connection+Pragmas.swift b/Sources/SQLite/Core/Connection+Pragmas.swift index 8f6d854b..2c4f0efb 100644 --- a/Sources/SQLite/Core/Connection+Pragmas.swift +++ b/Sources/SQLite/Core/Connection+Pragmas.swift @@ -7,7 +7,7 @@ public extension Connection { /// See SQLite [PRAGMA user_version](https://sqlite.org/pragma.html#pragma_user_version) var userVersion: UserVersion? { get { - (try? scalar("PRAGMA user_version") as? Int64).map(Int32.init) + (try? scalar("PRAGMA user_version") as? Int64)?.map(Int32.init) } set { _ = try? run("PRAGMA user_version = \(newValue ?? 0)") From 5f96ca46c1843f26c040b467d9f87519f03a0888 Mon Sep 17 00:00:00 2001 From: Stefan Saasen Date: Fri, 7 Apr 2023 15:41:11 +0200 Subject: [PATCH 0949/1046] Make ithe IndexDefinition properties public The [documentation](https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#querying-the-schema) suggests that the index schema information can be queried. That requires the `IndexDefinition` properties to be public. --- Sources/SQLite/Schema/SchemaDefinitions.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 284fc4c3..b314e5c0 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -266,12 +266,12 @@ public struct IndexDefinition: Equatable { orders: indexSQL.flatMap(orders)) } - let table: String - let name: String - let unique: Bool - let columns: [String] - let `where`: String? - let orders: [String: Order]? + public let table: String + public let name: String + public let unique: Bool + public let columns: [String] + public let `where`: String? + public let orders: [String: Order]? enum IndexError: LocalizedError { case tooLong(String, String) From 0718088b7885dfb08f8930111e13aa12fa9a55c6 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 21 Apr 2023 16:10:41 +0200 Subject: [PATCH 0950/1046] Fix GitHub Actions build badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cf72d03..90333b16 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): [SQLite3]: https://www.sqlite.org [SQLite.swift]: https://github.com/stephencelis/SQLite.swift -[GitHubActionBadge]: https://img.shields.io/github/workflow/status/stephencelis/SQLite.swift/Build%20and%20test +[GitHubActionBadge]: https://img.shields.io/github/actions/workflow/status/stephencelis/SQLite.swift/build.yml?branch=master [CocoaPodsVersionBadge]: https://img.shields.io/cocoapods/v/SQLite.swift.svg?style=flat [CocoaPodsVersionLink]: https://cocoapods.org/pods/SQLite.swift From 481b531c6906e11d8efcc7c36ea89b7e572e6ce7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 18 Oct 2022 22:14:04 +0200 Subject: [PATCH 0951/1046] Run on macOS 12 Bump deployment targets Disable watchOS validation CocoaPods/CocoaPods#11558 Adjust deployment targets for Xcode 14 --- .github/workflows/build.yml | 4 ++-- Package.swift | 8 ++++---- SQLite.swift.podspec | 8 ++++---- SQLite.xcodeproj/project.pbxproj | 30 ++++++++++++++++++++++-------- Tests/SPM/Package.swift | 8 ++++---- run-tests.sh | 4 ++-- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1659d9d..beec5ca2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: "15.2" + IOS_VERSION: "16.0" jobs: build: - runs-on: macos-11 + runs-on: macos-12 steps: - uses: actions/checkout@v2 - name: Install diff --git a/Package.swift b/Package.swift index 9664e99a..de40367a 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription let package = Package( name: "SQLite.swift", platforms: [ - .iOS(.v9), - .macOS(.v10_10), - .watchOS(.v3), - .tvOS(.v9) + .iOS(.v11), + .macOS(.v10_13), + .watchOS(.v4), + .tvOS(.v11) ], products: [ .library( diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index f32282d3..4edddd67 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,10 +18,10 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '9.0' - tvos_deployment_target = '9.1' - osx_deployment_target = '10.10' - watchos_deployment_target = '3.0' + ios_deployment_target = '11.0' + tvos_deployment_target = '11.0' + osx_deployment_target = '10.13' + watchos_deployment_target = '4.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 3d62e980..def32855 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1263,6 +1263,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -1284,6 +1285,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -1297,6 +1299,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -1310,6 +1313,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -1333,6 +1337,7 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Debug; }; @@ -1356,6 +1361,7 @@ SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Release; }; @@ -1408,8 +1414,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1417,10 +1423,10 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 3.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Debug; }; @@ -1467,19 +1473,19 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 9.1; + TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 3.0; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Release; }; @@ -1496,6 +1502,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1519,6 +1526,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1533,6 +1541,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1545,6 +1554,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1567,6 +1577,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1591,6 +1602,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1608,6 +1620,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -1623,6 +1636,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 3e329552..51f176f4 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -6,10 +6,10 @@ import PackageDescription let package = Package( name: "test", platforms: [ - .iOS(.v9), - .macOS(.v10_10), - .watchOS(.v3), - .tvOS(.v9) + .iOS(.v11), + .macOS(.v10_13), + .watchOS(.v4), + .tvOS(.v11) ], dependencies: [ // for testing from same repository diff --git a/run-tests.sh b/run-tests.sh index 49330c12..c54300ee 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,9 +8,9 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs --fail-fast + pod lib lint --no-subspecs --fail-fast --platforms=ios,osx,tvos else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=ios,osx,tvos fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From a2897d5512f897e566247808bf7d87b2f033b5e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 12:45:26 +0200 Subject: [PATCH 0952/1046] Run on macos-13 --- .github/workflows/build.yml | 4 ++-- .gitignore | 1 + Package.swift | 2 +- Tests/SPM/Package.swift | 7 ++----- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index beec5ca2..ae806ecc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: iPhone 12 - IOS_VERSION: "16.0" + IOS_VERSION: "16.4" jobs: build: - runs-on: macos-12 + runs-on: macos-13 steps: - uses: actions/checkout@v2 - name: Install diff --git a/.gitignore b/.gitignore index 5882e0cb..b7d43ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ DerivedData # Swift Package Manager .build Packages/ +.swiftpm/ diff --git a/Package.swift b/Package.swift index de40367a..70bde7ef 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7 import PackageDescription let package = Package( diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 51f176f4..3c446ae6 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -18,9 +18,6 @@ let package = Package( // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") ], targets: [ - .target( - name: "test", - dependencies: [.product(name: "SQLite", package: "SQLite.swift")] - ) + .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) ] ) From 85f1d443e9b758aaeea3fc5c9b3abef67675931b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:09:30 +0200 Subject: [PATCH 0953/1046] Automatically download swiftlint / xcbeautifier, lint fixes --- .gitignore | 3 + .swiftlint.yml | 5 +- Makefile | 81 +++++++++++++------ Sources/SQLite/Core/Backup.swift | 2 +- Sources/SQLite/Helpers.swift | 1 + Sources/SQLite/Schema/SchemaReader.swift | 2 +- Sources/SQLite/Typed/Query.swift | 2 +- Tests/.swiftlint.yml | 4 +- Tests/SQLiteTests/Schema/SchemaTests.swift | 2 + .../Typed/CustomFunctionsTests.swift | 16 ++-- 10 files changed, 80 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index b7d43ec9..e7b2ad4d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ DerivedData # Carthage /Carthage/ +# Makefile +bin/ + # Swift Package Manager .build Packages/ diff --git a/.swiftlint.yml b/.swiftlint.yml index d40be28e..e728e191 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -4,6 +4,7 @@ disabled_rules: # rule identifiers to exclude from running - large_tuple - closure_parameter_position - inclusive_language # sqlite_master etc. + - blanket_disable_command included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. - Sources - Tests @@ -26,8 +27,8 @@ identifier_name: - SQLITE_TRANSIENT type_body_length: - warning: 260 - error: 260 + warning: 350 + error: 350 function_body_length: warning: 60 diff --git a/Makefile b/Makefile index 74bf5d18..73b33f97 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,76 @@ -BUILD_TOOL = xcodebuild +XCODEBUILD = xcodebuild BUILD_SCHEME = SQLite Mac -IOS_SIMULATOR = iPhone 12 -IOS_VERSION = 15.0 +IOS_SIMULATOR = iPhone 14 +IOS_VERSION = 16.4 + +# tool settings +SWIFTLINT_VERSION=0.52.2 +SWIFTLINT=bin/swiftlint-$(SWIFTLINT_VERSION) +SWIFTLINT_URL=https://github.com/realm/SwiftLint/releases/download/$(SWIFTLINT_VERSION)/portable_swiftlint.zip +XCBEAUTIFY_VERSION=0.20.0 +XCBEAUTIFY=bin/xcbeautify-$(XCBEAUTIFY_VERSION) +ifeq ($(shell uname), Linux) + XCBEAUTIFY_PLATFORM=x86_64-unknown-linux-gnu.tar.xz +else + XCBEAUTIFY_PLATFORM=universal-apple-macosx.zip +endif +XCBEAUTIFY_URL=https://github.com/tuist/xcbeautify/releases/download/$(XCBEAUTIFY_VERSION)/xcbeautify-$(XCBEAUTIFY_VERSION)-$(XCBEAUTIFY_PLATFORM) +CURL_OPTS=--fail --silent -L --retry 3 + ifeq ($(BUILD_SCHEME),SQLite iOS) BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" -destination "platform=iOS Simulator,name=$(IOS_SIMULATOR),OS=$(IOS_VERSION)" else BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" endif -XCPRETTY := $(shell command -v xcpretty) -TEST_ACTIONS := clean build build-for-testing test-without-building +test: $(XCBEAUTIFY) + set -o pipefail; \ + $(XCODEBUILD) $(BUILD_ARGUMENTS) test | $(XCBEAUTIFY) -default: test +build: $(XCBEAUTIFY) + set -o pipefail; \ + $(XCODEBUILD) $(BUILD_ARGUMENTS) | $(XCBEAUTIFY) -build: - $(BUILD_TOOL) $(BUILD_ARGUMENTS) +lint: $(SWIFTLINT) + $< --strict -lint: - swiftlint --strict -lint-fix: - swiftlint lint fix - -test: -ifdef XCPRETTY - @set -o pipefail && $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) | $(XCPRETTY) -c -else - $(BUILD_TOOL) $(BUILD_ARGUMENTS) $(TEST_ACTIONS) -endif +lint-fix: $(SWIFTLINT) + $< lint fix clean: - $(BUILD_TOOL) $(BUILD_ARGUMENTS) clean + $(XCODEBUILD) $(BUILD_ARGUMENTS) clean repl: - @$(BUILD_TOOL) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ - swift -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' + @$(XCODEBUILD) $(BUILD_ARGUMENTS) -derivedDataPath $(TMPDIR)/SQLite.swift > /dev/null && \ + swift repl -F '$(TMPDIR)/SQLite.swift/Build/Products/Debug' sloc: - @zsh -c "grep -vE '^ *//|^$$' Sources/**/*.{swift,h,m} | wc -l" + @zsh -c "grep -vE '^ *//|^$$' Sources/**/*.{swift,h} | wc -l" + +$(SWIFTLINT): + set -e ; \ + curl $(CURL_OPTS) $(SWIFTLINT_URL) -o swiftlint.zip; \ + unzip -o swiftlint.zip swiftlint; \ + mkdir -p bin; \ + mv swiftlint $@ && rm -f swiftlint.zip + +$(XCBEAUTIFY): + set -e; \ + FILE=$(XCBEAUTIFY_PLATFORM); \ + curl $(CURL_OPTS) $(XCBEAUTIFY_URL) -o $$FILE; \ + case "$${FILE#*.}" in \ + "zip") \ + unzip -o $$FILE xcbeautify; \ + ;; \ + "tar.xz") \ + tar -xvf $$FILE xcbeautify; \ + ;; \ + *) \ + echo "unknown extension $${FILE#*.}!"; \ + exit 1; \ + ;; \ + esac; \ + mkdir -p bin; \ + mv xcbeautify $@ && rm -f $$FILE; .PHONY: test clean repl sloc diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 84ebf1e0..0eebbdd5 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -140,7 +140,7 @@ public final class Backup { /// - Parameter pagesToCopy: The maximal number of pages to copy in one step /// /// - Throws: `Result.Error` if step fails. - // + /// /// See: public func step(pagesToCopy pages: Pages = .all) throws { let status = sqlite3_backup_step(handle, pages.number) diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index e3d37e11..79d057d6 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -38,6 +38,7 @@ public func *(_: Expression?, _: Expression?) -> Expression [ColumnDefinition] { + public func columnDefinitions(table: String) throws -> [ColumnDefinition] { func parsePrimaryKey(column: String) throws -> ColumnDefinition.PrimaryKey? { try createTableSQL(name: table).flatMap { .init(sql: $0) } } diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index feff2f69..c59b728f 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1105,7 +1105,7 @@ extension Connection { return value(try scalar(expression.template, expression.bindings)) } - public func scalar(_ query: Select) throws -> V.ValueType? { + public func scalar(_ query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } return V.fromDatatypeValue(value) diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml index 3537085c..81d6c314 100644 --- a/Tests/.swiftlint.yml +++ b/Tests/.swiftlint.yml @@ -6,8 +6,8 @@ disabled_rules: - identifier_name type_body_length: - warning: 800 - error: 800 + warning: 1000 + error: 1000 function_body_length: warning: 200 diff --git a/Tests/SQLiteTests/Schema/SchemaTests.swift b/Tests/SQLiteTests/Schema/SchemaTests.swift index a7de8bcf..4f4a49d1 100644 --- a/Tests/SQLiteTests/Schema/SchemaTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaTests.swift @@ -72,6 +72,7 @@ class SchemaTests: XCTestCase { } // thoroughness test for ambiguity + // swiftlint:disable:next function_body_length func test_column_compilesColumnDefinitionExpression() { XCTAssertEqual( "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL)", @@ -390,6 +391,7 @@ class SchemaTests: XCTestCase { ) } + // swiftlint:disable:next function_body_length func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { XCTAssertEqual( "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL COLLATE RTRIM)", diff --git a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift index c4eb51e6..0f46b380 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -5,8 +5,8 @@ import SQLite #if !os(Linux) class CustomFunctionNoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = () -> Expression - typealias FunctionResultOptional = () -> Expression + typealias FunctionNoOptional = () -> Expression + typealias FunctionResultOptional = () -> Expression func testFunctionNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { @@ -26,9 +26,9 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { } class CustomFunctionWithOneArgTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression) -> Expression + typealias FunctionNoOptional = (Expression) -> Expression typealias FunctionLeftOptional = (Expression) -> Expression - typealias FunctionResultOptional = (Expression) -> Expression + typealias FunctionResultOptional = (Expression) -> Expression typealias FunctionLeftResultOptional = (Expression) -> Expression func testFunctionNoOptional() throws { @@ -65,12 +65,12 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { } class CustomFunctionWithTwoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression, Expression) -> Expression - typealias FunctionLeftOptional = (Expression, Expression) -> Expression + typealias FunctionNoOptional = (Expression, Expression) -> Expression + typealias FunctionLeftOptional = (Expression, Expression) -> Expression typealias FunctionRightOptional = (Expression, Expression) -> Expression - typealias FunctionResultOptional = (Expression, Expression) -> Expression + typealias FunctionResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightOptional = (Expression, Expression) -> Expression - typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression + typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression typealias FunctionRightResultOptional = (Expression, Expression) -> Expression typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression From 4a0ab2a7445712e71ff803e020b74f9557ae64a8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:14:36 +0200 Subject: [PATCH 0954/1046] Fix simulator version --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae806ecc..ac705e4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: iPhone 12 + IOS_SIMULATOR: "iPhone 14" IOS_VERSION: "16.4" jobs: build: From e4f4ddff5f124652421d7cd99512181bed86b0e7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:22:26 +0200 Subject: [PATCH 0955/1046] Use newer Xcode --- .github/workflows/build.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac705e4e..fbe4ec49 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,13 +8,11 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v2 - - name: Install + - name: "Select Xcode" run: | - gem update bundler - gem install xcpretty --no-document - brew update - brew outdated carthage || brew upgrade carthage - brew outdated swiftlint || brew upgrade swiftlint + xcode-select -p + xcode-select -s /Applications/Xcode-14.3.app/Contents/Developer + xcode-select -p - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 1bc58a5477cc47856d1e6a31b834f1cc0f66c00b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:28:43 +0200 Subject: [PATCH 0956/1046] Fix path --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbe4ec49..8c893c77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: "Select Xcode" run: | xcode-select -p - xcode-select -s /Applications/Xcode-14.3.app/Contents/Developer + xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer xcode-select -p - name: "Lint" run: make lint From 79bab4610b74cc7a8dee118a6b9ebce769aaec61 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 15:33:04 +0200 Subject: [PATCH 0957/1046] sudo --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c893c77..15f325ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - name: "Select Xcode" run: | xcode-select -p - xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer + sudo xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer xcode-select -p - name: "Lint" run: make lint From b028fcdda1489a3bb0c738f8809154f9c4be7f5b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 17:48:05 +0200 Subject: [PATCH 0958/1046] Try Xcode 14.2 --- .github/workflows/build.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15f325ed..242a9021 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: "iPhone 14" - IOS_VERSION: "16.4" + IOS_VERSION: "16.2" jobs: build: runs-on: macos-13 @@ -11,8 +11,7 @@ jobs: - name: "Select Xcode" run: | xcode-select -p - sudo xcode-select -s /Applications/Xcode_14.3.app/Contents/Developer - xcode-select -p + sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 101109848902bb2c7061286c86f15a638d6d5a17 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 22:28:40 +0200 Subject: [PATCH 0959/1046] Update docs --- .github/workflows/build.yml | 2 ++ CHANGELOG.md | 5 +++++ Documentation/Index.md | 31 ++++++++++++++----------------- README.md | 5 +---- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 242a9021..9cc2f56e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,8 @@ jobs: steps: - uses: actions/checkout@v2 - name: "Select Xcode" + # Currently only works with Xcode 14.2: + # https://github.com/CocoaPods/CocoaPods/issues/11839 run: | xcode-select -p sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer diff --git a/CHANGELOG.md b/CHANGELOG.md index fc759118..970b4ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.15.0 (unreleased) +======================================== + +* New minimum deployment targets: iOS/tvOS 11.0, watchOS 4.0 + 0.14.1 (01-11-2022), [diff][diff-0.14.1] ======================================== diff --git a/Documentation/Index.md b/Documentation/Index.md index 583d5def..5d72aa4b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -77,9 +77,6 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 5 (and -> [Xcode 10.2](https://developer.apple.com/xcode/downloads/)) or greater. - ### Swift Package Manager The [Swift Package Manager][] is a tool for managing the distribution of @@ -1142,11 +1139,11 @@ let query = managers chain.with(chain, recursive: true, as: query) // WITH RECURSIVE // "chain" AS ( -// SELECT * FROM "managers" WHERE "id" = 8 -// UNION -// SELECT * from "chain" +// SELECT * FROM "managers" WHERE "id" = 8 +// UNION +// SELECT * from "chain" // JOIN "managers" ON "chain"."manager_id" = "managers"."id" -// ) +// ) // SELECT * FROM "chain" ``` @@ -1156,7 +1153,7 @@ Column names and a materialization hint can optionally be provided. // Add a "level" column to the query representing manager's position in the chain let level = Expression("level") -let queryWithLevel = +let queryWithLevel = managers .select(id, managerId, 0) .where(id == 8) @@ -1166,18 +1163,18 @@ let queryWithLevel = .join(managers, on: chain[managerId] == managers[id]) ) -chain.with(chain, - columns: [id, managerId, level], +chain.with(chain, + columns: [id, managerId, level], recursive: true, hint: .materialize, as: queryWithLevel) // WITH RECURSIVE // "chain" ("id", "manager_id", "level") AS MATERIALIZED ( -// SELECT ("id", "manager_id", 0) FROM "managers" WHERE "id" = 8 -// UNION -// SELECT ("manager"."id", "manager"."manager_id", "level" + 1) FROM "chain" +// SELECT ("id", "manager_id", 0) FROM "managers" WHERE "id" = 8 +// UNION +// SELECT ("manager"."id", "manager"."manager_id", "level" + 1) FROM "chain" // JOIN "managers" ON "chain"."manager_id" = "managers"."id" -// ) +// ) // SELECT * FROM "chain" ``` @@ -1266,7 +1263,7 @@ let count = try db.scalar(users.filter(name != nil).count) We can upsert rows into a table by calling a [query’s](#queries) `upsert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each -joined by the `<-` operator. Upserting is like inserting, except if there is a +joined by the `<-` operator. Upserting is like inserting, except if there is a conflict on the specified column value, SQLite will perform an update on the row instead. ```swift @@ -1957,7 +1954,7 @@ for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` > _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either [implicit or explicit](https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions)) will be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`. -> +> > ```swift > someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") > for row in someObj.statement.bind(kUTTypeImage) { /* ... */ } @@ -2134,7 +2131,7 @@ using the following functions. } } ``` - + - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. diff --git a/README.md b/README.md index 90333b16..e6e5a894 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ API. // Wrap everything in a do...catch to handle errors do { // ... - + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") for email in ["betty@icloud.com", "cathy@icloud.com"] { try stmt.run(email) @@ -119,9 +119,6 @@ interactively, from the Xcode project’s playground. ## Installation -> _Note:_ Version 0.11.6 and later requires Swift 5 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.2) or greater. -> Version 0.11.5 requires Swift 4.2 (and [Xcode](https://developer.apple.com/xcode/downloads/) 10.1) or greater. - ### Swift Package Manager The [Swift Package Manager][] is a tool for managing the distribution of From ab3c598db29a7a2db3e06e9642736cb18e670a0e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sun, 21 May 2023 23:59:35 +0200 Subject: [PATCH 0960/1046] Use shorthand optional binding --- .swiftlint.yml | 2 ++ Sources/SQLite/Core/Connection+Attach.swift | 2 +- Sources/SQLite/Core/Connection.swift | 18 +++++------ Sources/SQLite/Core/Result.swift | 4 +-- Sources/SQLite/Extensions/FTS4.swift | 16 +++++----- Sources/SQLite/Extensions/FTS5.swift | 6 ++-- Sources/SQLite/Helpers.swift | 2 +- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- Sources/SQLite/Schema/SchemaReader.swift | 4 +-- Sources/SQLite/Typed/Coding.swift | 12 ++++---- Sources/SQLite/Typed/CoreFunctions.swift | 30 +++++++++---------- Sources/SQLite/Typed/Operators.swift | 16 +++++----- 12 files changed, 58 insertions(+), 56 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index e728e191..1bb21cd5 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,5 @@ +opt_in_rules: + - shorthand_optional_binding disabled_rules: # rule identifiers to exclude from running - todo - operator_whitespace diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 47e6af5e..8a25e51d 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -13,7 +13,7 @@ extension Connection { #if SQLITE_SWIFT_SQLCIPHER /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#attach public func attach(_ location: Location, as schemaName: String, key: String? = nil) throws { - if let key = key { + if let key { try run("ATTACH DATABASE ? AS ? KEY ?", location.description, schemaName, key) } else { try run("ATTACH DATABASE ? AS ?", location.description, schemaName) diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 26cadea4..f2c3b781 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -414,7 +414,7 @@ public final class Connection { /// times it’s been called for this lock. If it returns `true`, it will /// try again. If it returns `false`, no further attempts will be made. public func busyHandler(_ callback: ((_ tries: Int) -> Bool)?) { - guard let callback = callback else { + guard let callback else { sqlite3_busy_handler(handle, nil, nil) busyHandler = nil return @@ -449,7 +449,7 @@ public final class Connection { @available(watchOS, deprecated: 3.0) @available(tvOS, deprecated: 10.0) fileprivate func trace_v1(_ callback: ((String) -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_trace(handle, nil /* xCallback */, nil /* pCtx */) trace = nil return @@ -458,7 +458,7 @@ public final class Connection { callback(String(cString: pointer.assumingMemoryBound(to: UInt8.self))) } sqlite3_trace(handle, { (context: UnsafeMutableRawPointer?, SQL: UnsafePointer?) in - if let context = context, let SQL = SQL { + if let context, let SQL { unsafeBitCast(context, to: Trace.self)(SQL) } }, @@ -469,7 +469,7 @@ public final class Connection { @available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) fileprivate func trace_v2(_ callback: ((String) -> Void)?) { - guard let callback = callback else { + guard let callback else { // If the X callback is NULL or if the M mask is zero, then tracing is disabled. sqlite3_trace_v2(handle, 0 /* mask */, nil /* xCallback */, nil /* pCtx */) trace = nil @@ -485,7 +485,7 @@ public final class Connection { // callback was invoked. The C argument is a copy of the context pointer. // The P and X arguments are pointers whose meanings depend on T. (_: UInt32, context: UnsafeMutableRawPointer?, pointer: UnsafeMutableRawPointer?, _: UnsafeMutableRawPointer?) in - if let pointer = pointer, + if let pointer, let expandedSQL = sqlite3_expanded_sql(OpaquePointer(pointer)) { unsafeBitCast(context, to: Trace.self)(expandedSQL) sqlite3_free(expandedSQL) @@ -507,7 +507,7 @@ public final class Connection { /// `.Insert`, `.Update`, or `.Delete`), database name, table name, and /// rowid. public func updateHook(_ callback: ((_ operation: Operation, _ db: String, _ table: String, _ rowid: Int64) -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_update_hook(handle, nil, nil) updateHook = nil return @@ -535,7 +535,7 @@ public final class Connection { /// committed. If this callback throws, the transaction will be rolled /// back. public func commitHook(_ callback: (() throws -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_commit_hook(handle, nil, nil) commitHook = nil return @@ -562,7 +562,7 @@ public final class Connection { /// - Parameter callback: A callback invoked when a transaction is rolled /// back. public func rollbackHook(_ callback: (() -> Void)?) { - guard let callback = callback else { + guard let callback else { sqlite3_rollback_hook(handle, nil, nil) rollbackHook = nil return @@ -650,7 +650,7 @@ public final class Connection { try check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, to: UnsafeMutableRawPointer.self), { (callback: UnsafeMutableRawPointer?, _, lhs: UnsafeRawPointer?, _, rhs: UnsafeRawPointer?) in /* xCompare */ - if let lhs = lhs, let rhs = rhs { + if let lhs, let rhs { return unsafeBitCast(callback, to: Collation.self)(lhs, rhs) } else { fatalError("sqlite3_create_collation_v2 callback called with NULL pointer") diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 9fe47ada..ee59e5d1 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -51,13 +51,13 @@ extension Result: CustomStringConvertible { public var description: String { switch self { case let .error(message, errorCode, statement): - if let statement = statement { + if let statement { return "\(message) (\(statement)) (code: \(errorCode))" } else { return "\(message) (code: \(errorCode))" } case let .extendedError(message, extendedCode, statement): - if let statement = statement { + if let statement { return "\(message) (\(statement)) (extended code: \(extendedCode))" } else { return "\(message) (extended code: \(extendedCode))" diff --git a/Sources/SQLite/Extensions/FTS4.swift b/Sources/SQLite/Extensions/FTS4.swift index 8e91e453..c02cfdc1 100644 --- a/Sources/SQLite/Extensions/FTS4.swift +++ b/Sources/SQLite/Extensions/FTS4.swift @@ -98,7 +98,7 @@ public struct Tokenizer { separators: Set = []) -> Tokenizer { var arguments = [String]() - if let removeDiacritics = removeDiacritics { + if let removeDiacritics { arguments.append("remove_diacritics=\(removeDiacritics ? 1 : 0)".quote()) } @@ -208,13 +208,13 @@ open class FTSConfig { func options() -> Options { var options = Options() options.append(formatColumnDefinitions()) - if let tokenizer = tokenizer { + if let tokenizer { options.append("tokenize", value: Expression(literal: tokenizer.description)) } options.appendCommaSeparated("prefix", values: prefixes.sorted().map { String($0) }) if isContentless { options.append("content", value: "") - } else if let externalContentSchema = externalContentSchema { + } else if let externalContentSchema { options.append("content", value: externalContentSchema.tableName()) } return options @@ -306,19 +306,19 @@ open class FTS4Config: FTSConfig { for (column, _) in (columnDefinitions.filter { $0.options.contains(.unindexed) }) { options.append("notindexed", value: column) } - if let languageId = languageId { + if let languageId { options.append("languageid", value: languageId) } - if let compressFunction = compressFunction { + if let compressFunction { options.append("compress", value: compressFunction) } - if let uncompressFunction = uncompressFunction { + if let uncompressFunction { options.append("uncompress", value: uncompressFunction) } - if let matchInfo = matchInfo { + if let matchInfo { options.append("matchinfo", value: matchInfo.rawValue) } - if let order = order { + if let order { options.append("order", value: order.rawValue) } return options diff --git a/Sources/SQLite/Extensions/FTS5.swift b/Sources/SQLite/Extensions/FTS5.swift index 2e4f65fb..3e84e171 100644 --- a/Sources/SQLite/Extensions/FTS5.swift +++ b/Sources/SQLite/Extensions/FTS5.swift @@ -69,13 +69,13 @@ open class FTS5Config: FTSConfig { override func options() -> Options { var options = super.options() - if let contentRowId = contentRowId { + if let contentRowId { options.append("content_rowid", value: contentRowId) } - if let columnSize = columnSize { + if let columnSize { options.append("columnsize", value: Expression(value: columnSize)) } - if let detail = detail { + if let detail { options.append("detail", value: detail.rawValue) } return options diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 79d057d6..adfadc8a 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -109,7 +109,7 @@ extension String { } func transcode(_ literal: Binding?) -> String { - guard let literal = literal else { return "NULL" } + guard let literal else { return "NULL" } switch literal { case let blob as Blob: diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index b314e5c0..2d38e1fb 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -170,7 +170,7 @@ public enum LiteralValue: Equatable, CustomStringConvertible { // swiftlint:enable identifier_name init(_ string: String?) { - guard let string = string else { + guard let string else { self = .NULL return } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 0c02ba9f..58a9b1bd 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -39,10 +39,10 @@ public class SchemaReader { type: ObjectDefinition.ObjectType? = nil, temp: Bool = false) throws -> [ObjectDefinition] { var query: QueryType = SchemaTable.get(for: connection, temp: temp) - if let name = name { + if let name { query = query.where(SchemaTable.nameColumn == name) } - if let type = type { + if let type { query = query.where(SchemaTable.typeColumn == type.rawValue) } return try connection.prepare(query).map { row -> ObjectDefinition in diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index b96bc64e..e7db03fb 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -215,7 +215,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Int?, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer.Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -223,7 +223,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -231,7 +231,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Float?, forKey key: Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -239,7 +239,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: Double?, forKey key: Key) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -247,7 +247,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: String?, forKey key: MyKey) throws { - if let value = value { + if let value { try encode(value, forKey: key) } else if forcingNilValueSetters { encoder.setters.append(Expression(key.stringValue) <- nil) @@ -270,7 +270,7 @@ private class SQLiteEncoder: Encoder { } func encodeIfPresent(_ value: T?, forKey key: Key) throws where T: Swift.Encodable { - guard let value = value else { + guard let value else { guard forcingNilValueSetters else { return } diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index dc6a1044..4429bb5f 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -100,7 +100,7 @@ extension ExpressionType where UnderlyingType == Double { /// /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { - guard let precision = precision else { + guard let precision else { return Function.round.wrap([self]) } return Function.round.wrap([self, Int(precision)]) @@ -120,7 +120,7 @@ extension ExpressionType where UnderlyingType == Double? { /// /// - Returns: A copy of the expression wrapped with the `round` function. public func round(_ precision: Int? = nil) -> Expression { - guard let precision = precision else { + guard let precision else { return Function.round.wrap(self) } return Function.round.wrap([self, Int(precision)]) @@ -250,7 +250,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return "LIKE".infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) @@ -274,7 +274,7 @@ extension ExpressionType where UnderlyingType == String { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return Function.like.infix(self, pattern) } let like: Expression = Function.like.infix(self, pattern, wrap: false) @@ -349,7 +349,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.ltrim.wrap(self) } return Function.ltrim.wrap([self, String(characters)]) @@ -367,7 +367,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.rtrim.wrap(self) } return Function.rtrim.wrap([self, String(characters)]) @@ -385,7 +385,7 @@ extension ExpressionType where UnderlyingType == String { /// /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.trim.wrap([self]) } return Function.trim.wrap([self, String(characters)]) @@ -409,7 +409,7 @@ extension ExpressionType where UnderlyingType == String { } public func substring(_ location: Int, length: Int? = nil) -> Expression { - guard let length = length else { + guard let length else { return Function.substr.wrap([self, location]) } return Function.substr.wrap([self, location, length]) @@ -475,7 +475,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: String, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return Function.like.infix(self, pattern) } return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) @@ -499,7 +499,7 @@ extension ExpressionType where UnderlyingType == String? { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return Function.like.infix(self, pattern) } let like: Expression = Function.like.infix(self, pattern, wrap: false) @@ -574,7 +574,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `ltrim` function. public func ltrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.ltrim.wrap(self) } return Function.ltrim.wrap([self, String(characters)]) @@ -592,7 +592,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `rtrim` function. public func rtrim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.rtrim.wrap(self) } return Function.rtrim.wrap([self, String(characters)]) @@ -610,7 +610,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `trim` function. public func trim(_ characters: Set? = nil) -> Expression { - guard let characters = characters else { + guard let characters else { return Function.trim.wrap(self) } return Function.trim.wrap([self, String(characters)]) @@ -649,7 +649,7 @@ extension ExpressionType where UnderlyingType == String? { /// /// - Returns: A copy of the expression wrapped with the `substr` function. public func substring(_ location: Int, length: Int? = nil) -> Expression { - guard let length = length else { + guard let length else { return Function.substr.wrap([self, location]) } return Function.substr.wrap([self, location, length]) @@ -726,7 +726,7 @@ extension String { /// - Returns: A copy of the expression appended with a `LIKE` query against /// the given pattern. public func like(_ pattern: Expression, escape character: Character? = nil) -> Expression { - guard let character = character else { + guard let character else { return Function.like.infix(self, pattern) } let like: Expression = Function.like.infix(self, pattern, wrap: false) diff --git a/Sources/SQLite/Typed/Operators.swift b/Sources/SQLite/Typed/Operators.swift index 5ffbbceb..1c611cbc 100644 --- a/Sources/SQLite/Typed/Operators.swift +++ b/Sources/SQLite/Typed/Operators.swift @@ -367,14 +367,14 @@ public func ==(lhs: Expression, rhs: V) -> Expression where V Operator.eq.infix(lhs, rhs) } public func ==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS".infix(lhs, Expression(value: nil)) } return Operator.eq.infix(lhs, rhs) } public func ==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { Operator.eq.infix(lhs, rhs) } public func ==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS".infix(Expression(value: nil), rhs) } return Operator.eq.infix(lhs, rhs) } @@ -394,14 +394,14 @@ public func ===(lhs: Expression, rhs: V) -> Expression where "IS".infix(lhs, rhs) } public func ===(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS".infix(lhs, Expression(value: nil)) } return "IS".infix(lhs, rhs) } public func ===(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { "IS".infix(lhs, rhs) } public func ===(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS".infix(Expression(value: nil), rhs) } return "IS".infix(lhs, rhs) } @@ -421,14 +421,14 @@ public func !=(lhs: Expression, rhs: V) -> Expression where V Operator.neq.infix(lhs, rhs) } public func !=(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return Operator.neq.infix(lhs, rhs) } public func !=(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { Operator.neq.infix(lhs, rhs) } public func !=(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return Operator.neq.infix(lhs, rhs) } @@ -448,14 +448,14 @@ public func !==(lhs: Expression, rhs: V) -> Expression where "IS NOT".infix(lhs, rhs) } public func !==(lhs: Expression, rhs: V?) -> Expression where V.Datatype: Equatable { - guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + guard let rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } return "IS NOT".infix(lhs, rhs) } public func !==(lhs: V, rhs: Expression) -> Expression where V.Datatype: Equatable { "IS NOT".infix(lhs, rhs) } public func !==(lhs: V?, rhs: Expression) -> Expression where V.Datatype: Equatable { - guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + guard let lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } return "IS NOT".infix(lhs, rhs) } From 6aab9fcdc27c97f04b4153589908ec871b17ba55 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Mon, 22 May 2023 00:56:52 +0200 Subject: [PATCH 0961/1046] Re-enable watch tests --- run-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index c54300ee..49330c12 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,9 +8,9 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs --fail-fast --platforms=ios,osx,tvos + pod lib lint --no-subspecs --fail-fast else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=ios,osx,tvos + pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From ff93ebc831c01daa0117be6c19e0960347e001a6 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Sat, 27 May 2023 23:09:08 +0200 Subject: [PATCH 0962/1046] Handle FK definitions w/o key references Closes #1199 --- SQLite.xcodeproj/project.pbxproj | 8 ++++ Sources/SQLite/Schema/SchemaDefinitions.swift | 4 +- Sources/SQLite/Schema/SchemaReader.swift | 2 +- .../Schema/SchemaReaderTests.swift | 38 +++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..e6bb4fc9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,6 +211,9 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -340,6 +343,7 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -483,6 +487,7 @@ 19A17B56FBA20E7245BC8AC0 /* Schema */ = { isa = PBXGroup; children = ( + DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */, 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */, 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */, 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */, @@ -998,6 +1003,7 @@ 19A17FACE8E4D54A50BA934E /* FTS5Tests.swift in Sources */, 19A177909023B7B940C5805E /* FTSIntegrationTests.swift in Sources */, 19A17E1DD976D5CE80018749 /* FTS4Tests.swift in Sources */, + DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */, 19A17411403D60640467209E /* ExpressionTests.swift in Sources */, 19A17CA4D7B63D845428A9C5 /* StatementTests.swift in Sources */, 19A17885B646CB0201BE4BD5 /* QueryTests.swift in Sources */, @@ -1119,6 +1125,7 @@ 19A178DA2BB5970778CCAF13 /* FTS5Tests.swift in Sources */, 19A1755C49154C87304C9146 /* FTSIntegrationTests.swift in Sources */, 19A17444861E1443143DEB44 /* FTS4Tests.swift in Sources */, + DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */, 19A17DD33C2E43DD6EE05A60 /* ExpressionTests.swift in Sources */, 19A17D6EC40BC35A5DC81BA8 /* StatementTests.swift in Sources */, 19A17E3F47DA087E2B76D087 /* QueryTests.swift in Sources */, @@ -1199,6 +1206,7 @@ 19A1776BD5127DFDF847FF1F /* FTS5Tests.swift in Sources */, 19A173088B85A7E18E8582A7 /* FTSIntegrationTests.swift in Sources */, 19A178767223229E61C5066F /* FTS4Tests.swift in Sources */, + DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */, 19A1781CBA8968ABD3E00877 /* ExpressionTests.swift in Sources */, 19A17923494236793893BF72 /* StatementTests.swift in Sources */, 19A17A52BF29D27C9AA229E7 /* QueryTests.swift in Sources */, diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 2d38e1fb..9f4c126f 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -107,7 +107,7 @@ public struct ColumnDefinition: Equatable { public struct ForeignKey: Equatable { let table: String let column: String - let primaryKey: String + let primaryKey: String? let onUpdate: String? let onDelete: String? } @@ -365,7 +365,7 @@ extension ColumnDefinition.ForeignKey { ([ "REFERENCES", table.quote(), - "(\(primaryKey.quote()))", + primaryKey.map { "(\($0.quote()))" }, onUpdate.map { "ON UPDATE \($0)" }, onDelete.map { "ON DELETE \($0)" } ] as [String?]).compactMap { $0 } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 58a9b1bd..eb31888b 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -187,7 +187,7 @@ private enum ForeignKeyListTable { static let seqColumn = Expression("seq") static let tableColumn = Expression("table") static let fromColumn = Expression("from") - static let toColumn = Expression("to") + static let toColumn = Expression("to") // when null, use primary key static let onUpdateColumn = Expression("on_update") static let onDeleteColumn = Expression("on_delete") static let matchColumn = Expression("match") diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index dd5ae103..e90578e7 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -135,6 +135,44 @@ class SchemaReaderTests: SQLiteTestCase { ]) } + func test_foreignKeys_references_column() throws { + let sql = """ + CREATE TABLE artist( + artistid INTEGER PRIMARY KEY, + artistname TEXT + ); + CREATE TABLE track( + trackid INTEGER, + trackname TEXT, + trackartist INTEGER REFERENCES artist(artistid) + ); + """ + try db.execute(sql) + let trackColumns = try db.schema.foreignKeys(table: "track") + XCTAssertEqual(trackColumns.map { $0.toSQL() }.joined(separator: "\n"), """ + REFERENCES "artist" ("artistid") + """) + } + + func test_foreignKeys_references_null_column() throws { + let sql = """ + CREATE TABLE artist( + artistid INTEGER PRIMARY KEY, + artistname TEXT + ); + CREATE TABLE track( + trackid INTEGER, + trackname TEXT, + trackartist INTEGER REFERENCES artist + ); + """ + try db.execute(sql) + let trackColumns = try db.schema.foreignKeys(table: "track") + XCTAssertEqual(trackColumns.map { $0.toSQL() }.joined(separator: "\n"), """ + REFERENCES "artist" + """) + } + func test_tableDefinitions() throws { let tables = try schemaReader.tableDefinitions() XCTAssertEqual(tables.count, 1) From 7997c85cbf6c0a1f8caf300b483f04028548e40e Mon Sep 17 00:00:00 2001 From: Stefan Saasen Date: Wed, 7 Jun 2023 15:45:35 +0200 Subject: [PATCH 0963/1046] SchemaReader: return the correct column definition for a composite primary key The `PRAGMA` `table_info` that is used to return the column definitions, returns one row for each defined column. The `pk` column contains: > ... either zero for columns that are not part of the primary key, or the 1-based index of the column within the primary key). See https://www.sqlite.org/pragma.html#pragma_table_info Checking whether the `pk` column equals 1 only detects a single primary key and ignores other columns that are part of a composite primary key. --- Sources/SQLite/Schema/SchemaReader.swift | 2 +- .../Schema/SchemaReaderTests.swift | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 58a9b1bd..2be15f29 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -25,7 +25,7 @@ public class SchemaReader { .map { (row: Row) -> ColumnDefinition in ColumnDefinition( name: row[TableInfoTable.nameColumn], - primaryKey: row[TableInfoTable.primaryKeyColumn] == 1 ? + primaryKey: (row[TableInfoTable.primaryKeyColumn] ?? 0) > 0 ? try parsePrimaryKey(column: row[TableInfoTable.nameColumn]) : nil, type: ColumnDefinition.Affinity(row[TableInfoTable.typeColumn]), nullable: row[TableInfoTable.notNullColumn] == 0, diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index dd5ae103..49871c10 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -90,6 +90,43 @@ class SchemaReaderTests: SQLiteTestCase { ) } + func test_columnDefinitions_composite_primary_keys() throws { + try db.run(""" + CREATE TABLE t ( + col1 INTEGER, + col2 INTEGER, + col3 INTEGER, + PRIMARY KEY (col1, col2) + ); + """) + + XCTAssertEqual( + try schemaReader.columnDefinitions(table: "t"), [ + ColumnDefinition( + name: "col1", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition( + name: "col2", + primaryKey: .init(autoIncrement: false), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition( + name: "col3", + primaryKey: nil, + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ] + ) + } + func test_indexDefinitions_no_index() throws { let indexes = try schemaReader.indexDefinitions(table: "users") XCTAssertTrue(indexes.isEmpty) From 656ca871e357df8ef45b7344c777102cdbf7dfc6 Mon Sep 17 00:00:00 2001 From: Stefan Saasen Date: Tue, 30 May 2023 16:20:29 +0200 Subject: [PATCH 0964/1046] Fix column affinity parsing to match how SQLite determines affinity See https://www.sqlite.org/datatype3.html#determination_of_column_affinity for how SQLite determines column affinity. --- Sources/SQLite/Schema/SchemaDefinitions.swift | 14 +++- .../Schema/SchemaDefinitionsTests.swift | 64 ++++++++++++++++++- .../Schema/SchemaReaderTests.swift | 4 +- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 2d38e1fb..3c6b0523 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -57,7 +57,19 @@ public struct ColumnDefinition: Equatable { } init(_ string: String) { - self = Affinity.allCases.first { $0.rawValue.lowercased() == string.lowercased() } ?? .TEXT + let test = string.uppercased() + // https://sqlite.org/datatype3.html#determination_of_column_affinity + if test.contains("INT") { // Rule 1 + self = .INTEGER + } else if ["CHAR", "CLOB", "TEXT"].first(where: {test.contains($0)}) != nil { // Rule 2 + self = .TEXT + } else if string.contains("BLOB") { // Rule 3 + self = .BLOB + } else if ["REAL", "FLOA", "DOUB"].first(where: {test.contains($0)}) != nil { // Rule 4 + self = .REAL + } else { // Rule 5 + self = .NUMERIC + } } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index ef97b981..8b7e27e3 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -71,8 +71,68 @@ class AffinityTests: XCTestCase { XCTAssertEqual(ColumnDefinition.Affinity("NUMERIC"), .NUMERIC) } - func test_returns_TEXT_for_unknown_type() { - XCTAssertEqual(ColumnDefinition.Affinity("baz"), .TEXT) + // [Determination Of Column Affinity](https://sqlite.org/datatype3.html#determination_of_column_affinity) + // Rule 1 + func testIntegerAffinity() { + let declared = [ + "INT", + "INTEGER", + "TINYINT", + "SMALLINT", + "MEDIUMINT", + "BIGINT", + "UNSIGNED BIG INT", + "INT2", + "INT8" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .INTEGER})) + } + + // Rule 2 + func testTextAffinity() { + let declared = [ + "CHARACTER(20)", + "VARCHAR(255)", + "VARYING CHARACTER(255)", + "NCHAR(55)", + "NATIVE CHARACTER(70)", + "NVARCHAR(100)", + "TEXT", + "CLOB" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .TEXT})) + } + + // Rule 3 + func testBlobAffinity() { + XCTAssertEqual(ColumnDefinition.Affinity("BLOB"), .BLOB) + } + + // Rule 4 + func testRealAffinity() { + let declared = [ + "REAL", + "DOUBLE", + "DOUBLE PRECISION", + "FLOAT" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .REAL})) + } + + // Rule 5 + func testNumericAffinity() { + let declared = [ + "NUMERIC", + "DECIMAL(10,5)", + "BOOLEAN", + "DATE", + "DATETIME" + ] + XCTAssertTrue(declared.allSatisfy({ColumnDefinition.Affinity($0) == .NUMERIC})) + } + + func test_returns_NUMERIC_for_unknown_type() { + XCTAssertEqual(ColumnDefinition.Affinity("baz"), .NUMERIC) } } diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index dd5ae103..b03045de 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -40,7 +40,7 @@ class SchemaReaderTests: SQLiteTestCase { references: nil), ColumnDefinition(name: "admin", primaryKey: nil, - type: .TEXT, + type: .NUMERIC, nullable: false, defaultValue: .numericLiteral("0"), references: nil), @@ -51,7 +51,7 @@ class SchemaReaderTests: SQLiteTestCase { references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, - type: .TEXT, + type: .NUMERIC, nullable: true, defaultValue: .NULL, references: nil) From 3023a1f63336f70c1470bdef59ac2dfded258206 Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Mon, 16 Oct 2023 07:33:54 -0500 Subject: [PATCH 0965/1046] Add optional support for decoding --- Sources/SQLite/Typed/Coding.swift | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index e7db03fb..8c7755c3 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -471,6 +471,54 @@ private class SQLiteDecoder: Decoder { } } + func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? { + try? Float(row.get(Expression(key.stringValue))) + } + + func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? { + try? row.get(Expression(key.stringValue)) + } + + func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? { + try? row.get(Expression(key.stringValue)) + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { + switch type { + case is Data.Type: + let data = try row.get(Expression(key.stringValue)) + return data as? T + case is Date.Type: + let date = try row.get(Expression(key.stringValue)) + return date as? T + case is UUID.Type: + let uuid = try row.get(Expression(key.stringValue)) + return uuid as? T + default: + guard let JSONString = try row.get(Expression(key.stringValue)) else { + throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, + debugDescription: "an unsupported type was found")) + } + guard let data = JSONString.data(using: .utf8) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, + debugDescription: "invalid utf8 data found")) + } + return try JSONDecoder().decode(type, from: data) + } + } + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, From e7c259226373e9469d9166b842e5395dae302b6e Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Thu, 19 Oct 2023 07:13:41 -0500 Subject: [PATCH 0966/1046] Fix copypasta --- Sources/SQLite/Typed/Coding.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 8c7755c3..f62f5b6b 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -495,19 +495,22 @@ private class SQLiteDecoder: Decoder { try? row.get(Expression(key.stringValue)) } - func decode(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { + func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { switch type { case is Data.Type: - let data = try row.get(Expression(key.stringValue)) - return data as? T + if let data = try? row.get(Expression(key.stringValue)) { + return data as? T + } case is Date.Type: - let date = try row.get(Expression(key.stringValue)) - return date as? T + if let date = try? row.get(Expression(key.stringValue)) { + return date as? T + } case is UUID.Type: - let uuid = try row.get(Expression(key.stringValue)) - return uuid as? T + if let uuid = try? row.get(Expression(key.stringValue)) { + return uuid as? T + } default: - guard let JSONString = try row.get(Expression(key.stringValue)) else { + guard let JSONString = try? row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, debugDescription: "an unsupported type was found")) } From 128246f30a396ef864318e3a7ca851e90b3e84a1 Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Thu, 19 Oct 2023 11:04:58 -0500 Subject: [PATCH 0967/1046] Fix missing return --- Sources/SQLite/Typed/Coding.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index f62f5b6b..52612063 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -520,6 +520,8 @@ private class SQLiteDecoder: Decoder { } return try JSONDecoder().decode(type, from: data) } + + return nil } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws From 1c6bf76948de5b6ab1ef7a45629788e3c5c70d0a Mon Sep 17 00:00:00 2001 From: Jacob Hearst Date: Fri, 20 Oct 2023 07:20:58 -0500 Subject: [PATCH 0968/1046] Fix smoothbrain --- Sources/SQLite/Typed/Coding.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index 52612063..bfddc5ee 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -498,17 +498,11 @@ private class SQLiteDecoder: Decoder { func decodeIfPresent(_ type: T.Type, forKey key: Key) throws -> T? where T: Swift.Decodable { switch type { case is Data.Type: - if let data = try? row.get(Expression(key.stringValue)) { - return data as? T - } + return try? row.get(Expression(key.stringValue)) as? T case is Date.Type: - if let date = try? row.get(Expression(key.stringValue)) { - return date as? T - } + return try? row.get(Expression(key.stringValue)) as? T case is UUID.Type: - if let uuid = try? row.get(Expression(key.stringValue)) { - return uuid as? T - } + return try? row.get(Expression(key.stringValue)) as? T default: guard let JSONString = try? row.get(Expression(key.stringValue)) else { throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, @@ -520,8 +514,6 @@ private class SQLiteDecoder: Decoder { } return try JSONDecoder().decode(type, from: data) } - - return nil } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws From fc96d30b72db986b7f930d951eca14c16e32b9b5 Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Sat, 18 Nov 2023 18:30:28 -0800 Subject: [PATCH 0969/1046] Implements built-in window functions --- SQLite.xcodeproj/project.pbxproj | 18 +++ Sources/SQLite/Typed/AggregateFunctions.swift | 6 +- Sources/SQLite/Typed/WindowFunctions.swift | 145 ++++++++++++++++++ .../Typed/QueryIntegrationTests.swift | 95 ++++++++++++ .../Typed/WindowFunctionsTests.swift | 58 +++++++ 5 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 Sources/SQLite/Typed/WindowFunctions.swift create mode 100644 Tests/SQLiteTests/Typed/WindowFunctionsTests.swift diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..95ed6806 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -195,6 +195,13 @@ 49EB68C51F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C61F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; 49EB68C71F7B3CB400D89D40 /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + 64A8EE432B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64A8EE442B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64A8EE452B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64A8EE462B095FBB00F583F7 /* WindowFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */; }; + 64B8E1702B09748000545AFB /* WindowFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */; }; + 64B8E1712B09748000545AFB /* WindowFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */; }; + 64B8E1722B09748000545AFB /* WindowFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */; }; 997DF2AE287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2AF287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; 997DF2B0287FC06D00F8DF95 /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; @@ -335,6 +342,8 @@ 3DF7B79B2884C901005DD8CA /* Planning.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Planning.md; sourceTree = ""; }; 3DFC0B862886C239001C8FC9 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 49EB68C31F7B3CB400D89D40 /* Coding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coding.swift; sourceTree = ""; }; + 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowFunctions.swift; sourceTree = ""; }; + 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowFunctionsTests.swift; sourceTree = ""; }; 997DF2AD287FC06D00F8DF95 /* Query+with.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Query+with.swift"; sourceTree = ""; }; A121AC451CA35C79005A31D1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; @@ -476,6 +485,7 @@ 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */, 19A1709D5BDD2691BA160012 /* SetterTests.swift */, 19A174FE5B47A97937A27276 /* RowTests.swift */, + 64B8E16F2B09748000545AFB /* WindowFunctionsTests.swift */, ); path = Typed; sourceTree = ""; @@ -607,6 +617,7 @@ isa = PBXGroup; children = ( EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */, + 64A8EE422B095FBB00F583F7 /* WindowFunctions.swift */, EE247AFB1C3F06E900AE3E12 /* Collation.swift */, EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */, EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */, @@ -960,6 +971,7 @@ 3DF7B78A28842972005DD8CA /* Connection+Attach.swift in Sources */, 03A65E811C6BB2FB0062603F /* CustomFunctions.swift in Sources */, 03A65E7A1C6BB2F70062603F /* Statement.swift in Sources */, + 64A8EE452B095FBB00F583F7 /* WindowFunctions.swift in Sources */, 03A65E741C6BB2DA0062603F /* Helpers.swift in Sources */, 03A65E831C6BB2FB0062603F /* Operators.swift in Sources */, 03A65E851C6BB2FB0062603F /* Schema.swift in Sources */, @@ -1014,6 +1026,7 @@ 19A17746150A815944A6820B /* SelectTests.swift in Sources */, 19A1766135CE9786B1878603 /* ValueTests.swift in Sources */, 19A177D5C6542E2D572162E5 /* QueryIntegrationTests.swift in Sources */, + 64B8E1722B09748000545AFB /* WindowFunctionsTests.swift in Sources */, 19A178DF5A96CFEFF1E271F6 /* AggregateFunctionsTests.swift in Sources */, 19A17437659BD7FD787D94A6 /* CustomAggregationTests.swift in Sources */, 19A17F907258E524B3CA2FAE /* SetterTests.swift in Sources */, @@ -1057,6 +1070,7 @@ 19A17DC282E36C4F41AA440B /* Errors.swift in Sources */, 19A173668D948AD4DF1F5352 /* DateAndTimeFunctions.swift in Sources */, 19A17DF8D4F13A20F5D2269E /* Result.swift in Sources */, + 64A8EE462B095FBB00F583F7 /* WindowFunctions.swift in Sources */, 19A17DFE05ED8B1F7C45F7EE /* SchemaChanger.swift in Sources */, 19A17D1BEABA610ABF003D67 /* SchemaDefinitions.swift in Sources */, 19A17A33EA026C2E2CEBAF36 /* Connection+Schema.swift in Sources */, @@ -1081,6 +1095,7 @@ EE247B151C3F06E900AE3E12 /* Setter.swift in Sources */, 3DF7B78828842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B101C3F06E900AE3E12 /* CustomFunctions.swift in Sources */, + 64A8EE432B095FBB00F583F7 /* WindowFunctions.swift in Sources */, EE247B091C3F06E900AE3E12 /* FTS4.swift in Sources */, EE247B081C3F06E900AE3E12 /* Value.swift in Sources */, EE247B121C3F06E900AE3E12 /* Operators.swift in Sources */, @@ -1135,6 +1150,7 @@ 19A17F7977364EC8CD33C3C3 /* SelectTests.swift in Sources */, 19A17FD22EF43DF428DD93BA /* ValueTests.swift in Sources */, 19A177AA5922527BBDC77CF9 /* QueryIntegrationTests.swift in Sources */, + 64B8E1702B09748000545AFB /* WindowFunctionsTests.swift in Sources */, 19A179786A6826D58A70F8BC /* AggregateFunctionsTests.swift in Sources */, 19A1793972BDDDB027C113BB /* CustomAggregationTests.swift in Sources */, 19A1773155AC2BF2CA86A473 /* SetterTests.swift in Sources */, @@ -1161,6 +1177,7 @@ 3DF7B78928842972005DD8CA /* Connection+Attach.swift in Sources */, EE247B701C3F3FEC00AE3E12 /* CustomFunctions.swift in Sources */, EE247B691C3F3FEC00AE3E12 /* Statement.swift in Sources */, + 64A8EE442B095FBB00F583F7 /* WindowFunctions.swift in Sources */, EE247B641C3F3FDB00AE3E12 /* Helpers.swift in Sources */, EE247B721C3F3FEC00AE3E12 /* Operators.swift in Sources */, EE247B741C3F3FEC00AE3E12 /* Schema.swift in Sources */, @@ -1215,6 +1232,7 @@ 19A17DE1FCDB5695702AD24D /* SelectTests.swift in Sources */, 19A1726002D24C14F876C8FE /* ValueTests.swift in Sources */, 19A173389E53CB24DFA8CEDD /* QueryIntegrationTests.swift in Sources */, + 64B8E1712B09748000545AFB /* WindowFunctionsTests.swift in Sources */, 19A170C56745F9D722A73D77 /* AggregateFunctionsTests.swift in Sources */, 19A1772EBE65173EDFB1AFCA /* CustomAggregationTests.swift in Sources */, 19A17E0ABA6C415F014CD51C /* SetterTests.swift in Sources */, diff --git a/Sources/SQLite/Typed/AggregateFunctions.swift b/Sources/SQLite/Typed/AggregateFunctions.swift index bf4fb8fc..17fc4a20 100644 --- a/Sources/SQLite/Typed/AggregateFunctions.swift +++ b/Sources/SQLite/Typed/AggregateFunctions.swift @@ -166,7 +166,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// salary.average /// // avg("salary") /// - /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// - Returns: A copy of the expression wrapped with the `avg` aggregate /// function. public var average: Expression { Function.avg.wrap(self) @@ -179,7 +179,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// salary.sum /// // sum("salary") /// - /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// - Returns: A copy of the expression wrapped with the `sum` aggregate /// function. public var sum: Expression { Function.sum.wrap(self) @@ -192,7 +192,7 @@ extension ExpressionType where UnderlyingType: Value, UnderlyingType.Datatype: N /// salary.total /// // total("salary") /// - /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// - Returns: A copy of the expression wrapped with the `total` aggregate /// function. public var total: Expression { Function.total.wrap(self) diff --git a/Sources/SQLite/Typed/WindowFunctions.swift b/Sources/SQLite/Typed/WindowFunctions.swift new file mode 100644 index 00000000..e71e1ada --- /dev/null +++ b/Sources/SQLite/Typed/WindowFunctions.swift @@ -0,0 +1,145 @@ +import Foundation + +// see https://www.sqlite.org/windowfunctions.html#builtins +private enum WindowFunction: String { + // swiftlint:disable identifier_name + case ntile + case row_number + case rank + case dense_rank + case percent_rank + case cume_dist + case lag + case lead + case first_value + case last_value + case nth_value + // swiftlint:enable identifier_name + + func wrap(_ value: Int? = nil) -> Expression { + if let value { + return self.rawValue.wrap(Expression(value: value)) + } + return Expression(literal: "\(rawValue)()") + } + + func over(value: Int? = nil, _ orderBy: Expressible) -> Expression { + return Expression(" ".join([ + self.wrap(value), + Expression("OVER (ORDER BY \(orderBy.expression.template))", orderBy.expression.bindings) + ]).expression) + } + + func over(valueExpr: Expressible, _ orderBy: Expressible) -> Expression { + return Expression(" ".join([ + self.rawValue.wrap(valueExpr), + Expression("OVER (ORDER BY \(orderBy.expression.template))", orderBy.expression.bindings) + ]).expression) + } +} + +extension ExpressionType where UnderlyingType: Value { + /// Builds a copy of the expression with `lag(self, offset, default) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `lag(self, offset, default) OVER (ORDER BY {orderBy})` window function + public func lag(offset: Int = 0, default: Expressible? = nil, _ orderBy: Expressible) -> Expression { + if let defaultExpression = `default` { + return Expression( + "lag(\(template), \(offset), \(defaultExpression.asSQL())) OVER (ORDER BY \(orderBy.expression.template))", + bindings + orderBy.expression.bindings + ) + + } + return Expression("lag(\(template), \(offset)) OVER (ORDER BY \(orderBy.expression.template))", bindings + orderBy.expression.bindings) + } + + /// Builds a copy of the expression with `lead(self, offset, default) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `lead(self, offset, default) OVER (ORDER BY {orderBy})` window function + public func lead(offset: Int = 0, default: Expressible? = nil, _ orderBy: Expressible) -> Expression { + if let defaultExpression = `default` { + return Expression( + "lead(\(template), \(offset), \(defaultExpression.asSQL())) OVER (ORDER BY \(orderBy.expression.template))", + bindings + orderBy.expression.bindings) + + } + return Expression("lead(\(template), \(offset)) OVER (ORDER BY \(orderBy.expression.template))", bindings + orderBy.expression.bindings) + } + + /// Builds a copy of the expression with `first_value(self) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `first_value(self) OVER (ORDER BY {orderBy})` window function + public func firstValue(_ orderBy: Expressible) -> Expression { + WindowFunction.first_value.over(valueExpr: self, orderBy) + } + + /// Builds a copy of the expression with `last_value(self) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `last_value(self) OVER (ORDER BY {orderBy})` window function + public func lastValue(_ orderBy: Expressible) -> Expression { + WindowFunction.last_value.over(valueExpr: self, orderBy) + } + + /// Builds a copy of the expression with `nth_value(self) OVER (ORDER BY {orderBy})` window function + /// + /// - Parameter index: Row N of the window frame to return + /// - Parameter orderBy: Expression to evaluate window order + /// - Returns: An expression returning `nth_value(self) OVER (ORDER BY {orderBy})` window function + public func value(_ index: Int, _ orderBy: Expressible) -> Expression { + Expression("nth_value(\(template), \(index)) OVER (ORDER BY \(orderBy.expression.template))", bindings + orderBy.expression.bindings) + } +} + +/// Builds an expression representing `ntile(size) OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `ntile(size) OVER (ORDER BY {orderBy})` +public func ntile(_ size: Int, _ orderBy: Expressible) -> Expression { +// Expression.ntile(size, orderBy) + + WindowFunction.ntile.over(value: size, orderBy) +} + +/// Builds an expression representing `row_count() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `row_count() OVER (ORDER BY {orderBy})` +public func rowNumber(_ orderBy: Expressible) -> Expression { + WindowFunction.row_number.over(orderBy) +} + +/// Builds an expression representing `rank() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `rank() OVER (ORDER BY {orderBy})` +public func rank(_ orderBy: Expressible) -> Expression { + WindowFunction.rank.over(orderBy) +} + +/// Builds an expression representing `dense_rank() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `dense_rank() OVER ('over')` +public func denseRank(_ orderBy: Expressible) -> Expression { + WindowFunction.dense_rank.over(orderBy) +} + +/// Builds an expression representing `percent_rank() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `percent_rank() OVER (ORDER BY {orderBy})` +public func percentRank(_ orderBy: Expressible) -> Expression { + WindowFunction.percent_rank.over(orderBy) +} + +/// Builds an expression representing `cume_dist() OVER (ORDER BY {orderBy})` +/// +/// - Parameter orderBy: Expression to evaluate window order +/// - Returns: An expression returning `cume_dist() OVER (ORDER BY {orderBy})` +public func cumeDist(_ orderBy: Expressible) -> Expression { + WindowFunction.cume_dist.over(orderBy) +} diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index d8d31a79..aa45cafe 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -322,6 +322,101 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertNotNil(row[name]) XCTAssertNotNil(row[email]) } + + func test_select_ntile_function() throws { + let users = Table("users") + + try insertUser("Joey") + try insertUser("Timmy") + try insertUser("Jimmy") + try insertUser("Billy") + + let bucket = ntile(1, id.asc) + try db.prepare(users.select(id, bucket)).forEach { + XCTAssertEqual($0[bucket], 1) // only 1 window + } + } + + func test_select_cume_dist_function() throws { + let users = Table("users") + + try insertUser("Joey") + try insertUser("Timmy") + try insertUser("Jimmy") + try insertUser("Billy") + + let cumeDist = cumeDist(email) + let results = try db.prepare(users.select(id, cumeDist)).map { + $0[cumeDist] + } + XCTAssertEqual([0.25, 0.5, 0.75, 1], results) + } + + func test_select_window_row_number() throws { + let users = Table("users") + + try insertUser("Billy") + try insertUser("Jimmy") + try insertUser("Joey") + try insertUser("Timmy") + + let rowNumber = rowNumber(email.asc) + var expectedRowNum = 1 + try db.prepare(users.select(id, rowNumber)).forEach { + // should retrieve row numbers in order of INSERT above + XCTAssertEqual($0[rowNumber], expectedRowNum) + expectedRowNum += 1 + } + } + + func test_select_window_ranking() throws { + let users = Table("users") + + try insertUser("Billy") + try insertUser("Jimmy") + try insertUser("Joey") + try insertUser("Timmy") + + let percentRank = percentRank(email) + let actualPercentRank: [Int] = try db.prepare(users.select(id, percentRank)).map { + Int($0[percentRank] * 100) + } + XCTAssertEqual([0, 33, 66, 100], actualPercentRank) + + let rank = rank(email) + let actualRank: [Int] = try db.prepare(users.select(id, rank)).map { + $0[rank] + } + XCTAssertEqual([1, 2, 3, 4], actualRank) + + let denseRank = denseRank(email) + let actualDenseRank: [Int] = try db.prepare(users.select(id, denseRank)).map { + $0[denseRank] + } + XCTAssertEqual([1, 2, 3, 4], actualDenseRank) + } + + func test_select_window_values() throws { + let users = Table("users") + + try insertUser("Billy") + try insertUser("Jimmy") + try insertUser("Joey") + try insertUser("Timmy") + + let firstValue = email.firstValue(email.desc) + try db.prepare(users.select(id, firstValue)).forEach { + XCTAssertEqual($0[firstValue], "Timmy@example.com") // should grab last email alphabetically + } + + let lastValue = email.lastValue(email.asc) + var row = try db.pluck(users.select(id, lastValue))! + XCTAssertEqual(row[lastValue], "Billy@example.com") + + let nthValue = email.value(1, email.asc) + row = try db.pluck(users.select(id, nthValue))! + XCTAssertEqual(row[nthValue], "Billy@example.com") + } } extension Connection { diff --git a/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift new file mode 100644 index 00000000..6ded152b --- /dev/null +++ b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift @@ -0,0 +1,58 @@ +import XCTest +import SQLite + +class WindowFunctionsTests: XCTestCase { + + func test_ntile_wrapsExpressionWithOverClause() { + assertSQL("ntile(1) OVER (ORDER BY \"int\" DESC)", ntile(1, int.desc)) + assertSQL("ntile(20) OVER (ORDER BY \"intOptional\" ASC)", ntile(20, intOptional.asc)) + assertSQL("ntile(20) OVER (ORDER BY \"double\" ASC)", ntile(20, double.asc)) + assertSQL("ntile(1) OVER (ORDER BY \"doubleOptional\" ASC)", ntile(1, doubleOptional.asc)) + assertSQL("ntile(1) OVER (ORDER BY \"int\" DESC)", ntile(1, int.desc)) + } + + func test_row_number_wrapsExpressionWithOverClause() { + assertSQL("row_number() OVER (ORDER BY \"int\" DESC)", rowNumber(int.desc)) + } + + func test_rank_wrapsExpressionWithOverClause() { + assertSQL("rank() OVER (ORDER BY \"int\" DESC)", rank(int.desc)) + } + + func test_dense_rank_wrapsExpressionWithOverClause() { + assertSQL("dense_rank() OVER (ORDER BY \"int\" DESC)", denseRank(int.desc)) + } + + func test_percent_rank_wrapsExpressionWithOverClause() { + assertSQL("percent_rank() OVER (ORDER BY \"int\" DESC)", percentRank(int.desc)) + } + + func test_cume_dist_wrapsExpressionWithOverClause() { + assertSQL("cume_dist() OVER (ORDER BY \"int\" DESC)", cumeDist(int.desc)) + } + + func test_lag_wrapsExpressionWithOverClause() { + assertSQL("lag(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lag(int.desc)) + assertSQL("lag(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 7, int.desc)) + assertSQL("lag(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 1, default: Expression(value: 3), int.desc)) + } + + func test_lead_wrapsExpressionWithOverClause() { + assertSQL("lead(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lead(int.desc)) + assertSQL("lead(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 7, int.desc)) + assertSQL("lead(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 1, default: Expression(value: 3), int.desc)) + } + + func test_firstValue_wrapsExpressionWithOverClause() { + assertSQL("first_value(\"int\") OVER (ORDER BY \"int\" DESC)", int.firstValue(int.desc)) + assertSQL("first_value(\"double\") OVER (ORDER BY \"int\" DESC)", double.firstValue(int.desc)) + } + + func test_lastValue_wrapsExpressionWithOverClause() { + assertSQL("last_value(\"int\") OVER (ORDER BY \"int\" DESC)", int.lastValue(int.desc)) + } + + func test_nth_value_wrapsExpressionWithOverClause() { + assertSQL("nth_value(\"int\", 3) OVER (ORDER BY \"int\" DESC)", int.value(3, int.desc)) + } +} From 2fc62a96f03112f77604285cd0ad24b60558c22a Mon Sep 17 00:00:00 2001 From: Geoff MacDonald Date: Sat, 18 Nov 2023 18:42:41 -0800 Subject: [PATCH 0970/1046] update docs --- Documentation/Index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index 5d72aa4b..a03c26a3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -64,6 +64,7 @@ - [Other Operators](#other-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) + - [Window SQLite Functions](#window-sqlite-functions) - [Date and Time Functions](#date-and-time-functions) - [Custom SQL Functions](#custom-sql-functions) - [Custom Collations](#custom-collations) @@ -1871,6 +1872,11 @@ Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.html) have been surfaced in and type-audited for SQLite.swift. +## Window SQLite Functions + +Most of SQLite's [window functions](https://www.sqlite.org/windowfunctions.html) have been +surfaced in and type-audited for SQLite.swift. Currently only `OVER (ORDER BY ...)` windowing is possible. + ## Date and Time functions SQLite's [date and time](https://www.sqlite.org/lang_datefunc.html) From dedef242d9253e5d6169dfe91d9e67ba4597291b Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 4 Jan 2024 11:22:10 -0800 Subject: [PATCH 0971/1046] Add privacy manifest --- SQLite.xcodeproj/project.pbxproj | 16 ++++++++++++++++ Sources/SQLite/PrivacyInfo.xcprivacy | 14 ++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 Sources/SQLite/PrivacyInfo.xcprivacy diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..8eb1df66 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,6 +211,13 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; + EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -340,6 +347,7 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -546,6 +554,7 @@ EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, + EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */, EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, @@ -892,6 +901,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -899,6 +909,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79828846FED005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -907,6 +918,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -914,6 +926,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -921,6 +934,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79628846FCC005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -929,6 +943,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -936,6 +951,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79928847055005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SQLite/PrivacyInfo.xcprivacy b/Sources/SQLite/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..987771fa --- /dev/null +++ b/Sources/SQLite/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + NSPrivacyTracking + + + From e7c4212a213f8dec5d68ec6e7acadeed8f4fb912 Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Wed, 24 Jan 2024 15:18:34 -0800 Subject: [PATCH 0972/1046] Add visionOS support to Package.swift --- Package.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 70bde7ef..238661ae 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 import PackageDescription let package = Package( @@ -7,7 +7,8 @@ let package = Package( .iOS(.v11), .macOS(.v10_13), .watchOS(.v4), - .tvOS(.v11) + .tvOS(.v11), + .visionOS(.v1) ], products: [ .library( From 240f0802792cce82ebd0c27300cd1748b322e200 Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Wed, 24 Jan 2024 15:18:43 -0800 Subject: [PATCH 0973/1046] Create visionOS target and test plan --- SQLite.xcodeproj/project.pbxproj | 344 +++++++++++++++++- .../xcschemes/SQLite visionOS.xcscheme | 71 ++++ Tests/SQLite visionOS.xctestplan | 24 ++ 3 files changed, 435 insertions(+), 4 deletions(-) create mode 100644 SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme create mode 100644 Tests/SQLite visionOS.xctestplan diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index def32855..857c6fd9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,6 +211,76 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DEB306BA2B61CEF500F9D46B /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DEB306BC2B61CEF500F9D46B /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; + DEB306BD2B61CEF500F9D46B /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; + DEB306BE2B61CEF500F9D46B /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF61C3F06E900AE3E12 /* RTree.swift */; }; + DEB306BF2B61CEF500F9D46B /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; + DEB306C02B61CEF500F9D46B /* URIQueryParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B790288449BA005DD8CA /* URIQueryParameter.swift */; }; + DEB306C12B61CEF500F9D46B /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF71C3F06E900AE3E12 /* Foundation.swift */; }; + DEB306C22B61CEF500F9D46B /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; }; + DEB306C32B61CEF500F9D46B /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFE1C3F06E900AE3E12 /* Expression.swift */; }; + DEB306C42B61CEF500F9D46B /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF81C3F06E900AE3E12 /* Helpers.swift */; }; + DEB306C52B61CEF500F9D46B /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFB1C3F06E900AE3E12 /* Collation.swift */; }; + DEB306C62B61CEF500F9D46B /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B021C3F06E900AE3E12 /* Setter.swift */; }; + DEB306C72B61CEF500F9D46B /* Connection+Attach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF7B78728842972005DD8CA /* Connection+Attach.swift */; }; + DEB306C82B61CEF500F9D46B /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFD1C3F06E900AE3E12 /* CustomFunctions.swift */; }; + DEB306C92B61CEF500F9D46B /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF51C3F06E900AE3E12 /* FTS4.swift */; }; + DEB306CA2B61CEF500F9D46B /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF31C3F06E900AE3E12 /* Value.swift */; }; + DEB306CB2B61CEF500F9D46B /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFF1C3F06E900AE3E12 /* Operators.swift */; }; + DEB306CC2B61CEF500F9D46B /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B011C3F06E900AE3E12 /* Schema.swift */; }; + DEB306CD2B61CEF500F9D46B /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B001C3F06E900AE3E12 /* Query.swift */; }; + DEB306CE2B61CEF500F9D46B /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AF21C3F06E900AE3E12 /* Statement.swift */; }; + DEB306CF2B61CEF500F9D46B /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFA1C3F06E900AE3E12 /* AggregateFunctions.swift */; }; + DEB306D02B61CEF500F9D46B /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; }; + DEB306D12B61CEF500F9D46B /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; }; + DEB306D22B61CEF500F9D46B /* Backup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A43A9722738CF100FEC494 /* Backup.swift */; }; + DEB306D32B61CEF500F9D46B /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1710E73A46D5AC721CDA9 /* Errors.swift */; }; + DEB306D42B61CEF500F9D46B /* DateAndTimeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BA55DABB480F9020C8A /* DateAndTimeFunctions.swift */; }; + DEB306D52B61CEF500F9D46B /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DEB306D62B61CEF500F9D46B /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17E723300E5ED3771DCB5 /* Result.swift */; }; + DEB306D72B61CEF500F9D46B /* Query+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997DF2AD287FC06D00F8DF95 /* Query+with.swift */; }; + DEB306D82B61CEF500F9D46B /* Connection+Aggregation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A175A9CB446640AE6F2200 /* Connection+Aggregation.swift */; }; + DEB306D92B61CEF500F9D46B /* SQLiteFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */; }; + DEB306DA2B61CEF500F9D46B /* SchemaChanger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171B262DDE8718513CFDA /* SchemaChanger.swift */; }; + DEB306DB2B61CEF500F9D46B /* SchemaDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17268AE67B746B96AC125 /* SchemaDefinitions.swift */; }; + DEB306DC2B61CEF500F9D46B /* Connection+Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170A97B51DC5EE365F3C5 /* Connection+Schema.swift */; }; + DEB306DD2B61CEF500F9D46B /* Connection+Pragmas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F285B767BFACD96714B /* Connection+Pragmas.swift */; }; + DEB306DE2B61CEF500F9D46B /* SchemaReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58B21028FB864300F8EEA4 /* SchemaReader.swift */; }; + DEB306E02B61CEF500F9D46B /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9180931C46EA210038162A /* libsqlite3.tbd */; }; + DEB306EB2B61CF9500F9D46B /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; }; + DEB306EC2B61CF9500F9D46B /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; }; + DEB306ED2B61CF9500F9D46B /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; }; + DEB306EE2B61CF9500F9D46B /* SchemaDefinitionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A176174862D0F0139B3987 /* SchemaDefinitionsTests.swift */; }; + DEB306EF2B61CF9500F9D46B /* SchemaChangerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17FDB0B0CFB8987906FD0 /* SchemaChangerTests.swift */; }; + DEB306F02B61CF9500F9D46B /* Connection+SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CA1DF7D0F7C9A94C51C /* Connection+SchemaTests.swift */; }; + DEB306F12B61CF9500F9D46B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B96EBD42C878E609CDC /* FTS5Tests.swift */; }; + DEB306F22B61CF9500F9D46B /* FTSIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17BE1CC4AD4036BAB8EE0 /* FTSIntegrationTests.swift */; }; + DEB306F32B61CF9500F9D46B /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1715904F7B6851FCB5EF6 /* FTS4Tests.swift */; }; + DEB306F42B61CF9500F9D46B /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170F141BF21946D159083 /* ExpressionTests.swift */; }; + DEB306F52B61CF9500F9D46B /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1756EB81E9F7F45B12A78 /* StatementTests.swift */; }; + DEB306F62B61CF9500F9D46B /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171ED017645C8B04DF9F2 /* QueryTests.swift */; }; + DEB306F72B61CF9500F9D46B /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1787E16C8562C09C076F5 /* CipherTests.swift */; }; + DEB306F82B61CF9500F9D46B /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A2ED4E2640F197F48C /* BlobTests.swift */; }; + DEB306F92B61CF9500F9D46B /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17855BD524FF888265B3C /* ConnectionTests.swift */; }; + DEB306FA2B61CF9500F9D46B /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B55F2409B7031443495 /* CoreFunctionsTests.swift */; }; + DEB306FB2B61CF9500F9D46B /* DateAndTimeFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178AEF32ABC8BF2993FB5 /* DateAndTimeFunctionTests.swift */; }; + DEB306FC2B61CF9500F9D46B /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17F2B5959B212A33C383F /* CustomFunctionsTests.swift */; }; + DEB306FD2B61CF9500F9D46B /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17475DCA068453F787613 /* OperatorsTests.swift */; }; + DEB306FE2B61CF9500F9D46B /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1745BE8623D8C6808DB3C /* ResultTests.swift */; }; + DEB306FF2B61CF9500F9D46B /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17162C9861E5C4900455D /* RTreeTests.swift */; }; + DEB307002B61CF9500F9D46B /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A171A7714C6524093255C5 /* SchemaTests.swift */; }; + DEB307012B61CF9500F9D46B /* SelectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17EC0C43015063945D32E /* SelectTests.swift */; }; + DEB307022B61CF9500F9D46B /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17AE284BB1DF31D1B753E /* ValueTests.swift */; }; + DEB307032B61CF9500F9D46B /* QueryIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17CAE60446607E99C22A6 /* QueryIntegrationTests.swift */; }; + DEB307042B61CF9500F9D46B /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17613F00A3F4D4A257A04 /* AggregateFunctionsTests.swift */; }; + DEB307052B61CF9500F9D46B /* CustomAggregationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A177EF5E2D91BA86DA4480 /* CustomAggregationTests.swift */; }; + DEB307062B61CF9500F9D46B /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1709D5BDD2691BA160012 /* SetterTests.swift */; }; + DEB307072B61CF9500F9D46B /* RowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A174FE5B47A97937A27276 /* RowTests.swift */; }; + DEB307082B61CF9500F9D46B /* Connection+PragmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17C60D3464461057E4D63 /* Connection+PragmaTests.swift */; }; + DEB307092B61CF9500F9D46B /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; + DEB3070B2B61CF9500F9D46B /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; + DEB3070D2B61CF9500F9D46B /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -263,6 +333,13 @@ remoteGlobalIDString = 03A65E591C6BB0F50062603F; remoteInfo = "SQLite tvOS"; }; + DEB307142B61D07F00F9D46B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DEB306B82B61CEF500F9D46B; + remoteInfo = "SQLite visionOS"; + }; EE247ADF1C3F04ED00AE3E12 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = EE247ACA1C3F04ED00AE3E12 /* Project object */; @@ -340,6 +417,9 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + DEB306E52B61CEF500F9D46B /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests visionOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DEB307132B61D04500F9D46B /* SQLite visionOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "SQLite visionOS.xctestplan"; path = "Tests/SQLite visionOS.xctestplan"; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -401,6 +481,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306DF2B61CEF500F9D46B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306E02B61CEF500F9D46B /* libsqlite3.tbd in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEB3070A2B61CF9500F9D46B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB3070B2B61CF9500F9D46B /* SQLite.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACF1C3F04ED00AE3E12 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -517,6 +613,7 @@ 3D3C3CCB26E5568800759140 /* SQLite.playground */, EE247AD51C3F04ED00AE3E12 /* SQLite */, EE247AE11C3F04ED00AE3E12 /* SQLiteTests */, + DEB307132B61D04500F9D46B /* SQLite visionOS.xctestplan */, EE247B8A1C3F81D000AE3E12 /* Metadata */, EE247AD41C3F04ED00AE3E12 /* Products */, 3D67B3E41DB2469200A4F4C6 /* Frameworks */, @@ -535,6 +632,8 @@ 03A65E5A1C6BB0F50062603F /* SQLite.framework */, 03A65E631C6BB0F60062603F /* SQLiteTests tvOS.xctest */, A121AC451CA35C79005A31D1 /* SQLite.framework */, + DEB306E52B61CEF500F9D46B /* SQLite.framework */, + DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */, ); name = Products; sourceTree = ""; @@ -679,6 +778,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306B92B61CEF500F9D46B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306BA2B61CEF500F9D46B /* SQLite.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD01C3F04ED00AE3E12 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -752,6 +859,42 @@ productReference = A121AC451CA35C79005A31D1 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; + DEB306B82B61CEF500F9D46B /* SQLite visionOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DEB306E22B61CEF500F9D46B /* Build configuration list for PBXNativeTarget "SQLite visionOS" */; + buildPhases = ( + DEB306B92B61CEF500F9D46B /* Headers */, + DEB306BB2B61CEF500F9D46B /* Sources */, + DEB306DF2B61CEF500F9D46B /* Frameworks */, + DEB306E12B61CEF500F9D46B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "SQLite visionOS"; + productName = SQLite; + productReference = DEB306E52B61CEF500F9D46B /* SQLite.framework */; + productType = "com.apple.product-type.framework"; + }; + DEB306E72B61CF9500F9D46B /* SQLiteTests visionOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DEB3070E2B61CF9500F9D46B /* Build configuration list for PBXNativeTarget "SQLiteTests visionOS" */; + buildPhases = ( + DEB306EA2B61CF9500F9D46B /* Sources */, + DEB3070A2B61CF9500F9D46B /* Frameworks */, + DEB3070C2B61CF9500F9D46B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DEB307152B61D07F00F9D46B /* PBXTargetDependency */, + ); + name = "SQLiteTests visionOS"; + productName = "SQLite tvOSTests"; + productReference = DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; EE247AD21C3F04ED00AE3E12 /* SQLite iOS */ = { isa = PBXNativeTarget; buildConfigurationList = EE247AE71C3F04ED00AE3E12 /* Build configuration list for PBXNativeTarget "SQLite iOS" */; @@ -883,6 +1026,8 @@ 03A65E591C6BB0F50062603F /* SQLite tvOS */, 03A65E621C6BB0F60062603F /* SQLiteTests tvOS */, A121AC441CA35C79005A31D1 /* SQLite watchOS */, + DEB306B82B61CEF500F9D46B /* SQLite visionOS */, + DEB306E72B61CF9500F9D46B /* SQLiteTests visionOS */, ); }; /* End PBXProject section */ @@ -910,6 +1055,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306E12B61CEF500F9D46B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEB3070C2B61CF9500F9D46B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB3070D2B61CF9500F9D46B /* Resources in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247AD11C3F04ED00AE3E12 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1064,6 +1224,86 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DEB306BB2B61CEF500F9D46B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306BC2B61CEF500F9D46B /* CoreFunctions.swift in Sources */, + DEB306BD2B61CEF500F9D46B /* Coding.swift in Sources */, + DEB306BE2B61CEF500F9D46B /* RTree.swift in Sources */, + DEB306BF2B61CEF500F9D46B /* Blob.swift in Sources */, + DEB306C02B61CEF500F9D46B /* URIQueryParameter.swift in Sources */, + DEB306C12B61CEF500F9D46B /* Foundation.swift in Sources */, + DEB306C22B61CEF500F9D46B /* Connection.swift in Sources */, + DEB306C32B61CEF500F9D46B /* Expression.swift in Sources */, + DEB306C42B61CEF500F9D46B /* Helpers.swift in Sources */, + DEB306C52B61CEF500F9D46B /* Collation.swift in Sources */, + DEB306C62B61CEF500F9D46B /* Setter.swift in Sources */, + DEB306C72B61CEF500F9D46B /* Connection+Attach.swift in Sources */, + DEB306C82B61CEF500F9D46B /* CustomFunctions.swift in Sources */, + DEB306C92B61CEF500F9D46B /* FTS4.swift in Sources */, + DEB306CA2B61CEF500F9D46B /* Value.swift in Sources */, + DEB306CB2B61CEF500F9D46B /* Operators.swift in Sources */, + DEB306CC2B61CEF500F9D46B /* Schema.swift in Sources */, + DEB306CD2B61CEF500F9D46B /* Query.swift in Sources */, + DEB306CE2B61CEF500F9D46B /* Statement.swift in Sources */, + DEB306CF2B61CEF500F9D46B /* AggregateFunctions.swift in Sources */, + DEB306D02B61CEF500F9D46B /* FTS5.swift in Sources */, + DEB306D12B61CEF500F9D46B /* Cipher.swift in Sources */, + DEB306D22B61CEF500F9D46B /* Backup.swift in Sources */, + DEB306D32B61CEF500F9D46B /* Errors.swift in Sources */, + DEB306D42B61CEF500F9D46B /* DateAndTimeFunctions.swift in Sources */, + DEB306D52B61CEF500F9D46B /* SQLiteVersion.swift in Sources */, + DEB306D62B61CEF500F9D46B /* Result.swift in Sources */, + DEB306D72B61CEF500F9D46B /* Query+with.swift in Sources */, + DEB306D82B61CEF500F9D46B /* Connection+Aggregation.swift in Sources */, + DEB306D92B61CEF500F9D46B /* SQLiteFeature.swift in Sources */, + DEB306DA2B61CEF500F9D46B /* SchemaChanger.swift in Sources */, + DEB306DB2B61CEF500F9D46B /* SchemaDefinitions.swift in Sources */, + DEB306DC2B61CEF500F9D46B /* Connection+Schema.swift in Sources */, + DEB306DD2B61CEF500F9D46B /* Connection+Pragmas.swift in Sources */, + DEB306DE2B61CEF500F9D46B /* SchemaReader.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DEB306EA2B61CF9500F9D46B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DEB306EB2B61CF9500F9D46B /* TestHelpers.swift in Sources */, + DEB306EC2B61CF9500F9D46B /* FoundationTests.swift in Sources */, + DEB306ED2B61CF9500F9D46B /* Fixtures.swift in Sources */, + DEB306EE2B61CF9500F9D46B /* SchemaDefinitionsTests.swift in Sources */, + DEB306EF2B61CF9500F9D46B /* SchemaChangerTests.swift in Sources */, + DEB306F02B61CF9500F9D46B /* Connection+SchemaTests.swift in Sources */, + DEB306F12B61CF9500F9D46B /* FTS5Tests.swift in Sources */, + DEB306F22B61CF9500F9D46B /* FTSIntegrationTests.swift in Sources */, + DEB306F32B61CF9500F9D46B /* FTS4Tests.swift in Sources */, + DEB306F42B61CF9500F9D46B /* ExpressionTests.swift in Sources */, + DEB306F52B61CF9500F9D46B /* StatementTests.swift in Sources */, + DEB306F62B61CF9500F9D46B /* QueryTests.swift in Sources */, + DEB306F72B61CF9500F9D46B /* CipherTests.swift in Sources */, + DEB306F82B61CF9500F9D46B /* BlobTests.swift in Sources */, + DEB306F92B61CF9500F9D46B /* ConnectionTests.swift in Sources */, + DEB306FA2B61CF9500F9D46B /* CoreFunctionsTests.swift in Sources */, + DEB306FB2B61CF9500F9D46B /* DateAndTimeFunctionTests.swift in Sources */, + DEB306FC2B61CF9500F9D46B /* CustomFunctionsTests.swift in Sources */, + DEB306FD2B61CF9500F9D46B /* OperatorsTests.swift in Sources */, + DEB306FE2B61CF9500F9D46B /* ResultTests.swift in Sources */, + DEB306FF2B61CF9500F9D46B /* RTreeTests.swift in Sources */, + DEB307002B61CF9500F9D46B /* SchemaTests.swift in Sources */, + DEB307012B61CF9500F9D46B /* SelectTests.swift in Sources */, + DEB307022B61CF9500F9D46B /* ValueTests.swift in Sources */, + DEB307032B61CF9500F9D46B /* QueryIntegrationTests.swift in Sources */, + DEB307042B61CF9500F9D46B /* AggregateFunctionsTests.swift in Sources */, + DEB307052B61CF9500F9D46B /* CustomAggregationTests.swift in Sources */, + DEB307062B61CF9500F9D46B /* SetterTests.swift in Sources */, + DEB307072B61CF9500F9D46B /* RowTests.swift in Sources */, + DEB307082B61CF9500F9D46B /* Connection+PragmaTests.swift in Sources */, + DEB307092B61CF9500F9D46B /* Connection+AttachTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; EE247ACE1C3F04ED00AE3E12 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1232,6 +1472,11 @@ target = 03A65E591C6BB0F50062603F /* SQLite tvOS */; targetProxy = 03A65E651C6BB0F60062603F /* PBXContainerItemProxy */; }; + DEB307152B61D07F00F9D46B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DEB306B82B61CEF500F9D46B /* SQLite visionOS */; + targetProxy = DEB307142B61D07F00F9D46B /* PBXContainerItemProxy */; + }; EE247AE01C3F04ED00AE3E12 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = EE247AD21C3F04ED00AE3E12 /* SQLite iOS */; @@ -1365,6 +1610,83 @@ }; name = Release; }; + DEB306E32B61CEF500F9D46B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.14.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = xros; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; + DEB306E42B61CEF500F9D46B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.14.0; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; + PRODUCT_NAME = SQLite; + SDKROOT = xros; + SKIP_INSTALL = YES; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Release; + }; + DEB3070F2B61CF9500F9D46B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; + DEB307102B61CF9500F9D46B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = YES; + INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = xros; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Release; + }; EE247AE51C3F04ED00AE3E12 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1502,7 +1824,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1526,7 +1847,6 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1541,7 +1861,6 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1554,7 +1873,6 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1674,6 +1992,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DEB306E22B61CEF500F9D46B /* Build configuration list for PBXNativeTarget "SQLite visionOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEB306E32B61CEF500F9D46B /* Debug */, + DEB306E42B61CEF500F9D46B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DEB3070E2B61CF9500F9D46B /* Build configuration list for PBXNativeTarget "SQLiteTests visionOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEB3070F2B61CF9500F9D46B /* Debug */, + DEB307102B61CF9500F9D46B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; EE247ACD1C3F04ED00AE3E12 /* Build configuration list for PBXProject "SQLite" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme new file mode 100644 index 00000000..58783573 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/SQLite visionOS.xctestplan b/Tests/SQLite visionOS.xctestplan new file mode 100644 index 00000000..9b4e90ed --- /dev/null +++ b/Tests/SQLite visionOS.xctestplan @@ -0,0 +1,24 @@ +{ + "configurations" : [ + { + "id" : "72B91FD4-441C-4C06-9E92-CAEDCB7325AB", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:SQLite.xcodeproj", + "identifier" : "DEB306E72B61CF9500F9D46B", + "name" : "SQLiteTests visionOS" + } + } + ], + "version" : 1 +} From 9043277c83fcddf18d325192616ec5214c3a50df Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 6 Feb 2024 18:54:57 +0100 Subject: [PATCH 0974/1046] Using macOS 14 to build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cc2f56e..d4fc87d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ env: IOS_VERSION: "16.2" jobs: build: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v2 - name: "Select Xcode" From 623ac53da9a23e6e8d53acdcc07291b8496580c5 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 6 Feb 2024 18:56:12 +0100 Subject: [PATCH 0975/1046] Xcode 15? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d4fc87d8..ec25e7bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: # https://github.com/CocoaPods/CocoaPods/issues/11839 run: | xcode-select -p - sudo xcode-select -s /Applications/Xcode_14.2.app/Contents/Developer + sudo xcode-select -s /Applications/Xcode_15.0.1.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 0c68b99da0e8378a0d43c7ad6911b98e437dd8c1 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Tue, 6 Feb 2024 19:00:02 +0100 Subject: [PATCH 0976/1046] Testing using iOS 17 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec25e7bf..7be89270 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 14" - IOS_VERSION: "16.2" + IOS_SIMULATOR: "iPhone 15" + IOS_VERSION: "17.2" jobs: build: runs-on: macos-14 From 03724bf3d28a16ac0197027d47956b8039c42e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elfred=20Pag=C3=A1n?= Date: Tue, 20 Feb 2024 20:06:37 -0600 Subject: [PATCH 0977/1046] make fromDatatypeValue throw When using custom types, sometimes decoding can fail, say due to changes in the type structure. In this case decoding would fail and the only way to handle it is forcing a crash. This change allows you to use `try row.get()` instead. Givng you the chance to handle the mismatch. --- Sources/SQLite/Core/Value.swift | 2 +- Sources/SQLite/Helpers.swift | 4 ++-- Sources/SQLite/Typed/Query.swift | 12 +++++------ Tests/SQLiteTests/Typed/RowTests.swift | 30 ++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Sources/SQLite/Core/Value.swift b/Sources/SQLite/Core/Value.swift index 9c463f0c..249a7728 100644 --- a/Sources/SQLite/Core/Value.swift +++ b/Sources/SQLite/Core/Value.swift @@ -39,7 +39,7 @@ public protocol Value: Expressible { // extensions cannot have inheritance claus static var declaredDatatype: String { get } - static func fromDatatypeValue(_ datatypeValue: Datatype) -> ValueType + static func fromDatatypeValue(_ datatypeValue: Datatype) throws -> ValueType var datatypeValue: Datatype { get } diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index adfadc8a..c27ccf05 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -121,9 +121,9 @@ func transcode(_ literal: Binding?) -> String { } } -// swiftlint:disable force_cast +// swiftlint:disable force_cast force_try func value(_ binding: Binding) -> A { - A.fromDatatypeValue(binding as! A.Datatype) as! A + try! A.fromDatatypeValue(binding as! A.Datatype) as! A } func value(_ binding: Binding?) -> A { diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index c59b728f..2a83665c 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1097,7 +1097,7 @@ extension Connection { public func scalar(_ query: ScalarQuery) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) + return try V.fromDatatypeValue(value) } public func scalar(_ query: Select) throws -> V { @@ -1108,7 +1108,7 @@ extension Connection { public func scalar(_ query: Select) throws -> V.ValueType? { let expression = query.expression guard let value = try scalar(expression.template, expression.bindings) as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) + return try V.fromDatatypeValue(value) } public func pluck(_ query: QueryType) throws -> Row? { @@ -1200,9 +1200,9 @@ public struct Row { } public func get(_ column: Expression) throws -> V? { - func valueAtIndex(_ idx: Int) -> V? { + func valueAtIndex(_ idx: Int) throws -> V? { guard let value = values[idx] as? V.Datatype else { return nil } - return V.fromDatatypeValue(value) as? V + return try V.fromDatatypeValue(value) as? V } guard let idx = columnNames[column.template] else { @@ -1224,10 +1224,10 @@ public struct Row { similar: columnNames.keys.filter(similar).sorted() ) } - return valueAtIndex(columnNames[firstIndex].value) + return try valueAtIndex(columnNames[firstIndex].value) } - return valueAtIndex(idx) + return try valueAtIndex(idx) } public subscript(column: Expression) -> T { diff --git a/Tests/SQLiteTests/Typed/RowTests.swift b/Tests/SQLiteTests/Typed/RowTests.swift index 506f6b10..14fa373b 100644 --- a/Tests/SQLiteTests/Typed/RowTests.swift +++ b/Tests/SQLiteTests/Typed/RowTests.swift @@ -85,4 +85,34 @@ class RowTests: XCTestCase { } } } + + public func test_get_datatype_throws() { + // swiftlint:disable nesting + struct MyType: Value { + enum MyError: Error { + case failed + } + + public static var declaredDatatype: String { + Blob.declaredDatatype + } + + public static func fromDatatypeValue(_ dataValue: Blob) throws -> Data { + throw MyError.failed + } + + public var datatypeValue: Blob { + return Blob(bytes: []) + } + } + + let row = Row(["\"foo\"": 0], [Blob(bytes: [])]) + XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + if case MyType.MyError.failed = error { + XCTAssertTrue(true) + } else { + XCTFail("unexpected error: \(error)") + } + } + } } From 5bfaddf455a152a276e534b24c98ff08c67d3f63 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 23 Feb 2024 13:50:42 +0100 Subject: [PATCH 0978/1046] fix: json tests (due to a swift upgrade) --- Tests/SQLiteTests/TestHelpers.swift | 10 ++++++++++ Tests/SQLiteTests/Typed/QueryTests.swift | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 65cff8bb..e2da5927 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -105,6 +105,16 @@ func assertSQL(_ expression1: @autoclosure () -> String, _ expression2: @autoclo XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) } +func extractAndReplace(_ value: String, regex: String, with replacement: String) -> (String, String) { + // We cannot use `Regex` because it is not available before iOS 16 :( + let regex = try! NSRegularExpression(pattern: regex) + let valueRange = NSRange(location: 0, length: value.utf16.count) + let match = regex.firstMatch(in: value, options: [], range: valueRange)!.range + let range = Range(match, in: value)! + let extractedValue = String(value[range]) + return (value.replacingCharacters(in: range, with: replacement), extractedValue) +} + let table = Table("table") let qualifiedTable = Table("table", database: "main") let virtualTable = VirtualTable("virtual_table") diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index 82a33808..f698c621 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -364,13 +364,22 @@ class QueryTests: XCTestCase { let insert = try emails.insert(value) let encodedJSON = try JSONEncoder().encode(value1) let encodedJSONString = String(data: encodedJSON, encoding: .utf8)! - assertSQL( + + let expectedSQL = """ INSERT INTO \"emails\" (\"int\", \"string\", \"bool\", \"float\", \"double\", \"date\", \"uuid\", \"optional\", \"sub\") VALUES (1, '2', 1, 3.0, 4.0, '1970-01-01T00:00:00.000', 'E621E1F8-C36C-495A-93FC-0C247A3E6E5F', 'optional', '\(encodedJSONString)') - """.replacingOccurrences(of: "\n", with: ""), - insert + """.replacingOccurrences(of: "\n", with: "") + + // As JSON serialization gives a different result each time, we extract JSON and compare it by deserializing it + // and keep comparing the query but with the json replaced by the `JSON` string + let (expectedQuery, expectedJSON) = extractAndReplace(expectedSQL, regex: "\\{.*\\}", with: "JSON") + let (actualQuery, actualJSON) = extractAndReplace(insert.asSQL(), regex: "\\{.*\\}", with: "JSON") + XCTAssertEqual(expectedQuery, actualQuery) + XCTAssertEqual( + try JSONDecoder().decode(TestCodable.self, from: expectedJSON.data(using: .utf8)!), + try JSONDecoder().decode(TestCodable.self, from: actualJSON.data(using: .utf8)!) ) } #endif From 485b677b8991287bf9b91de2b7d18f3eff21c38e Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Fri, 23 Feb 2024 14:26:22 +0100 Subject: [PATCH 0979/1046] Revert "Add privacy manifest" --- SQLite.xcodeproj/project.pbxproj | 16 ---------------- Sources/SQLite/PrivacyInfo.xcprivacy | 14 -------------- 2 files changed, 30 deletions(-) delete mode 100644 Sources/SQLite/PrivacyInfo.xcprivacy diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 8eb1df66..def32855 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -211,13 +211,6 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; - EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; - EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -347,7 +340,6 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; - EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -554,7 +546,6 @@ EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, - EAE1E1532B473B0C0048B157 /* PrivacyInfo.xcprivacy */, EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, EE247AF91C3F06E900AE3E12 /* Typed */, @@ -901,7 +892,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1582B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -909,7 +899,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1592B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79828846FED005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -918,7 +907,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E15A2B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -926,7 +914,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1542B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -934,7 +921,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1552B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79628846FCC005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -943,7 +929,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1562B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -951,7 +936,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAE1E1572B473B0C0048B157 /* PrivacyInfo.xcprivacy in Resources */, 3DF7B79928847055005DD8CA /* Resources in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SQLite/PrivacyInfo.xcprivacy b/Sources/SQLite/PrivacyInfo.xcprivacy deleted file mode 100644 index 987771fa..00000000 --- a/Sources/SQLite/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,14 +0,0 @@ - - - - - NSPrivacyTrackingDomains - - NSPrivacyCollectedDataTypes - - NSPrivacyAccessedAPITypes - - NSPrivacyTracking - - - From fec58b90b7a340377d16dfcce89027a6b6f81e26 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 23 Feb 2024 14:47:00 +0100 Subject: [PATCH 0980/1046] fix: ios deployment target --- SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/xcschemes/SQLite visionOS.xcscheme | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4edddd67..b8aaaa8f 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '11.0' + ios_deployment_target = '17.0' tvos_deployment_target = '11.0' osx_deployment_target = '10.13' watchos_deployment_target = '4.0' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 857c6fd9..d23b6d64 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1736,7 +1736,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1795,7 +1795,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme index 58783573..c1536b66 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite visionOS.xcscheme @@ -15,7 +15,7 @@ @@ -55,7 +55,7 @@ From ce125ce64c03e9fcd142f18aca254637c223be8c Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 23 Feb 2024 15:37:04 +0100 Subject: [PATCH 0981/1046] fix: deployment targets? --- SQLite.swift.podspec | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b8aaaa8f..2a75f169 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -19,9 +19,9 @@ Pod::Spec.new do |s| s.swift_versions = ['5'] ios_deployment_target = '17.0' - tvos_deployment_target = '11.0' - osx_deployment_target = '10.13' - watchos_deployment_target = '4.0' + tvos_deployment_target = '17.0' + osx_deployment_target = '14.0' + watchos_deployment_target = '10.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target @@ -32,6 +32,11 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.library = 'sqlite3' + + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + ss.osx.deployment_target = osx_deployment_target + ss.watchos.deployment_target = watchos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -51,6 +56,11 @@ Pod::Spec.new do |s| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_SWIFT_STANDALONE=1' } ss.dependency 'sqlite3' + + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + ss.osx.deployment_target = osx_deployment_target + ss.watchos.deployment_target = watchos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -68,6 +78,11 @@ Pod::Spec.new do |s| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' } ss.dependency 'SQLCipher', '>= 4.0.0' + + ss.ios.deployment_target = ios_deployment_target + ss.tvos.deployment_target = tvos_deployment_target + ss.osx.deployment_target = osx_deployment_target + ss.watchos.deployment_target = watchos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' From e7813517c80c5bd79d4e7029ec8a89121f5afe60 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 23 Feb 2024 12:51:28 -0800 Subject: [PATCH 0982/1046] Add privacy manifest - fixes build issue https://github.com/stephencelis/SQLite.swift/actions/runs/8019552944/job/21907598856 --- SQLite.xcodeproj/project.pbxproj | 10 ++++++++++ Sources/SQLite/PrivacyInfo.xcprivacy | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Sources/SQLite/PrivacyInfo.xcprivacy diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 37c8a802..0b246a00 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -221,6 +221,10 @@ DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + EAE5A0372B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; + EAE5A0382B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; + EAE5A0392B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; + EAE5A03A2B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; EE247AD71C3F04ED00AE3E12 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; EE247ADE1C3F04ED00AE3E12 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE247AD31C3F04ED00AE3E12 /* SQLite.framework */; }; EE247B031C3F06E900AE3E12 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; }; @@ -353,6 +357,7 @@ DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; + EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; EE247AD81C3F04ED00AE3E12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -560,6 +565,7 @@ EE247AD61C3F04ED00AE3E12 /* SQLite.h */, EE247AF71C3F06E900AE3E12 /* Foundation.swift */, EE247AF81C3F06E900AE3E12 /* Helpers.swift */, + EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */, EE247AD81C3F04ED00AE3E12 /* Info.plist */, EE247AED1C3F06E900AE3E12 /* Core */, EE247AF41C3F06E900AE3E12 /* Extensions */, @@ -908,6 +914,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A0392B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -923,6 +930,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A03A2B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -930,6 +938,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A0372B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -945,6 +954,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EAE5A0382B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SQLite/PrivacyInfo.xcprivacy b/Sources/SQLite/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..987771fa --- /dev/null +++ b/Sources/SQLite/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + NSPrivacyTracking + + + From e78ae0220e17525a15ac68c697a155eb7a672a8e Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 24 Feb 2024 11:51:41 +0100 Subject: [PATCH 0983/1046] changelogs --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++++-- Documentation/Index.md | 42 ++++++++++++++++++++++++++++++----------- README.md | 4 ++-- SQLite.swift.podspec | 2 +- Tests/SPM/Package.swift | 2 +- 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 970b4ca1..4c5c1fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ -0.15.0 (unreleased) +0.15.0 (24-02-2024), [diff][diff-0.15.0] ======================================== +* Fix incorrect behavior when preparing `SELECT *` preceded by a `WITH` ([#1179][]) +* Adds support for returning extended error codes ([#1178][]) +* Fix typos ([#1182][]) +* fix Xcode build error ([#1192][]) +* Make the IndexDefinition properties public ([#1196][]) +* Fix GitHub Actions build badge ([#1200][]) +* Run CI on macOS 13 ([#1206][]) +* SchemaReader: return the correct column definition for a composite primary key ([#1217][]) +* Add optional support for decoding ([#1224][]) +* make fromDatatypeValue throw ([#1242][]) +* Implements built-in window functions ([#1228][]) +* Fix column affinity parsing to match how SQLite determines affinity ([#1218][]) +* Handle FK definitions w/o key references ([#1210][]) +* Add privacy manifest ([#1245][]) * New minimum deployment targets: iOS/tvOS 11.0, watchOS 4.0 0.14.1 (01-11-2022), [diff][diff-0.14.1] @@ -14,7 +28,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). * Support more complex schema changes and queries ([#1073][], [#1146][] [#1148][]) * Support `ATTACH`/`DETACH` ([#30][], [#1142][]) -* Expose connection flags (via `URIQueryParameter`) to open db ([#1074][])) +* Expose connection flags (via `URIQueryParameter`) to open db ([#1074][]) * Support `WITH` clause ([#1139][]) * Add `Value` conformance for `NSURL` ([#1110][], [#1141][]) * Add decoding for `UUID` ([#1137][]) @@ -140,6 +154,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.13.3]: https://github.com/stephencelis/SQLite.swift/compare/0.13.2...0.13.3 [diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 [diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 +[diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -206,3 +221,17 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1146]: https://github.com/stephencelis/SQLite.swift/pull/1146 [#1148]: https://github.com/stephencelis/SQLite.swift/pull/1148 [#1167]: https://github.com/stephencelis/SQLite.swift/pull/1167 +[#1179]: https://github.com/stephencelis/SQLite.swift/pull/1179 +[#1178]: https://github.com/stephencelis/SQLite.swift/pull/1178 +[#1182]: https://github.com/stephencelis/SQLite.swift/pull/1182 +[#1192]: https://github.com/stephencelis/SQLite.swift/pull/1192 +[#1196]: https://github.com/stephencelis/SQLite.swift/pull/1196 +[#1200]: https://github.com/stephencelis/SQLite.swift/pull/1200 +[#1206]: https://github.com/stephencelis/SQLite.swift/pull/1206 +[#1217]: https://github.com/stephencelis/SQLite.swift/pull/1217 +[#1224]: https://github.com/stephencelis/SQLite.swift/pull/1224 +[#1242]: https://github.com/stephencelis/SQLite.swift/pull/1242 +[#1228]: https://github.com/stephencelis/SQLite.swift/pull/1228 +[#1218]: https://github.com/stephencelis/SQLite.swift/pull/1218 +[#1210]: https://github.com/stephencelis/SQLite.swift/pull/1210 +[#1245]: https://github.com/stephencelis/SQLite.swift/pull/1245 diff --git a/Documentation/Index.md b/Documentation/Index.md index a03c26a3..7f900bc5 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1,21 +1,24 @@ # SQLite.swift Documentation +- [SQLite.swift Documentation](#sqliteswift-documentation) - [Installation](#installation) - [Swift Package Manager](#swift-package-manager) - [Carthage](#carthage) - [CocoaPods](#cocoapods) + - [Requiring a specific version of SQLite](#requiring-a-specific-version-of-sqlite) + - [Using SQLite.swift with SQLCipher](#using-sqliteswift-with-sqlcipher) - [Manual](#manual) - [Getting Started](#getting-started) - [Connecting to a Database](#connecting-to-a-database) - [Read-Write Databases](#read-write-databases) - [Read-Only Databases](#read-only-databases) - - [In a Shared Group Container](#in-a-shared-group-container) + - [In a shared group container](#in-a-shared-group-container) - [In-Memory Databases](#in-memory-databases) - [URI parameters](#uri-parameters) - [Thread-Safety](#thread-safety) - [Building Type-Safe SQL](#building-type-safe-sql) - [Expressions](#expressions) - - [Compound Expressions](#compound-expressions) + - [Compound Expressions](#compound-expressions) - [Queries](#queries) - [Creating a Table](#creating-a-table) - [Create Table Options](#create-table-options) @@ -24,8 +27,11 @@ - [Inserting Rows](#inserting-rows) - [Handling SQLite errors](#handling-sqlite-errors) - [Setters](#setters) + - [Infix Setters](#infix-setters) + - [Postfix Setters](#postfix-setters) - [Selecting Rows](#selecting-rows) - [Iterating and Accessing Values](#iterating-and-accessing-values) + - [Failable iteration](#failable-iteration) - [Plucking Rows](#plucking-rows) - [Building Complex Queries](#building-complex-queries) - [Selecting Columns](#selecting-columns) @@ -34,6 +40,9 @@ - [Table Aliasing](#table-aliasing) - [Filtering Rows](#filtering-rows) - [Filter Operators and Functions](#filter-operators-and-functions) + - [Infix Filter Operators](#infix-filter-operators) + - [Prefix Filter Operators](#prefix-filter-operators) + - [Filtering Functions](#filtering-functions) - [Sorting Rows](#sorting-rows) - [Limiting and Paging Results](#limiting-and-paging-results) - [Recursive and Hierarchical Queries](#recursive-and-hierarchical-queries) @@ -43,13 +52,14 @@ - [Deleting Rows](#deleting-rows) - [Transactions and Savepoints](#transactions-and-savepoints) - [Querying the Schema](#querying-the-schema) + - [Indexes and Columns](#indexes-and-columns) - [Altering the Schema](#altering-the-schema) - [Renaming Tables](#renaming-tables) - [Dropping Tables](#dropping-tables) - [Adding Columns](#adding-columns) - [Added Column Constraints](#added-column-constraints) - - [Schema Changer](#schemachanger) - - [Adding Columns](#adding-columns) + - [SchemaChanger](#schemachanger) + - [Adding Columns](#adding-columns-1) - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - [Renaming/Dropping Tables](#renamingdropping-tables) @@ -61,17 +71,27 @@ - [Date-Time Values](#date-time-values) - [Binary Data](#binary-data) - [Codable Types](#codable-types) + - [Inserting Codable Types](#inserting-codable-types) + - [Updating Codable Types](#updating-codable-types) + - [Retrieving Codable Types](#retrieving-codable-types) + - [Restrictions](#restrictions) - [Other Operators](#other-operators) + - [Other Infix Operators](#other-infix-operators) + - [Other Prefix Operators](#other-prefix-operators) - [Core SQLite Functions](#core-sqlite-functions) - [Aggregate SQLite Functions](#aggregate-sqlite-functions) - [Window SQLite Functions](#window-sqlite-functions) - - [Date and Time Functions](#date-and-time-functions) + - [Date and Time functions](#date-and-time-functions) - [Custom SQL Functions](#custom-sql-functions) + - [Custom Aggregations](#custom-aggregations) - [Custom Collations](#custom-collations) - [Full-text Search](#full-text-search) + - [FTS5](#fts5) - [Executing Arbitrary SQL](#executing-arbitrary-sql) + - [Online Database Backup](#online-database-backup) - [Attaching and detaching databases](#attaching-and-detaching-databases) - [Logging](#logging) + - [Vacuum](#vacuum) [↩]: #sqliteswift-documentation @@ -88,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") ] ``` @@ -109,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.1 + github "stephencelis/SQLite.swift" ~> 0.15.0 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -139,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.1' + pod 'SQLite.swift', '~> 0.15.0' end ``` @@ -153,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.1' + pod 'SQLite.swift/standalone', '~> 0.15.0' end ``` @@ -163,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.14.1' + pod 'SQLite.swift/standalone', '~> 0.15.0' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -179,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.14.1' + pod 'SQLite.swift/SQLCipher', '~> 0.15.0' end ``` diff --git a/README.md b/README.md index e6e5a894..117edb5a 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.14.1 + github "stephencelis/SQLite.swift" ~> 0.15.0 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4edddd67..b691aeab 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.14.1" + s.version = "0.15.0" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 3c446ae6..c82b3b9e 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 20ebe9b334ff557c1166e26ca6c89e94a77a7f2d Mon Sep 17 00:00:00 2001 From: Chris Stockbridge Date: Mon, 4 Mar 2024 20:07:22 -0500 Subject: [PATCH 0984/1046] Adding failing test that includes an optional struct This test passes with release 0.14.1, but fails starting with 0.15 --- .../Typed/QueryIntegrationTests.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index aa45cafe..7ac72d0c 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -152,6 +152,33 @@ class QueryIntegrationTests: SQLiteTestCase { XCTAssertEqual(values.count, 2) } + func test_insert_custom_encodable_type() throws { + struct TestTypeWithOptionalArray: Codable { + var myInt: Int + var myString: String + var myOptionalArray: [Int]? + } + + let table = Table("custom_codable") + try db.run(table.create { builder in + builder.column(Expression("myInt")) + builder.column(Expression("myString")) + builder.column(Expression("myOptionalArray")) + }) + + let customType = TestTypeWithOptionalArray(myInt: 13, myString: "foo", myOptionalArray: [1, 2, 3]) + try db.run(table.insert(customType)) + let rows = try db.prepare(table) + let values: [TestTypeWithOptionalArray] = try rows.map({ try $0.decode() }) + XCTAssertEqual(values.count, 1, "return one optional custom type") + + let customTypeWithNil = TestTypeWithOptionalArray(myInt: 123, myString: "String", myOptionalArray: nil) + try db.run(table.insert(customTypeWithNil)) + let rowsNil = try db.prepare(table) + let valuesNil: [TestTypeWithOptionalArray] = try rowsNil.map({ try $0.decode() }) + XCTAssertEqual(valuesNil.count, 2, "return two custom objects, including one that contains a nil optional") + } + func test_upsert() throws { try XCTSkipUnless(db.satisfiesMinimumVersion(minor: 24)) let fetchAge = { () throws -> Int? in From bad16cb5d1a09e93bbdb40f50df5f222920a10e3 Mon Sep 17 00:00:00 2001 From: Chris Stockbridge Date: Tue, 5 Mar 2024 11:11:50 -0500 Subject: [PATCH 0985/1046] Bugfix: returning nil when decoding an optional should not throw an error --- Sources/SQLite/Typed/Coding.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/SQLite/Typed/Coding.swift b/Sources/SQLite/Typed/Coding.swift index bfddc5ee..d0061851 100644 --- a/Sources/SQLite/Typed/Coding.swift +++ b/Sources/SQLite/Typed/Coding.swift @@ -504,9 +504,8 @@ private class SQLiteDecoder: Decoder { case is UUID.Type: return try? row.get(Expression(key.stringValue)) as? T default: - guard let JSONString = try? row.get(Expression(key.stringValue)) else { - throw DecodingError.typeMismatch(type, DecodingError.Context(codingPath: codingPath, - debugDescription: "an unsupported type was found")) + guard let JSONString = try row.get(Expression(key.stringValue)) else { + return nil } guard let data = JSONString.data(using: .utf8) else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, From cda8a1274a5c426c043bea6d381fddab630dad95 Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 17 Mar 2024 01:10:17 +0800 Subject: [PATCH 0986/1046] Update CoreFunctions.swift fix typo fix lower to upper --- Sources/SQLite/Typed/CoreFunctions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Typed/CoreFunctions.swift b/Sources/SQLite/Typed/CoreFunctions.swift index 4429bb5f..c4359d8b 100644 --- a/Sources/SQLite/Typed/CoreFunctions.swift +++ b/Sources/SQLite/Typed/CoreFunctions.swift @@ -224,7 +224,7 @@ extension ExpressionType where UnderlyingType == String { /// /// let name = Expression("name") /// name.uppercaseString - /// // lower("name") + /// // upper("name") /// /// - Returns: A copy of the expression wrapped with the `upper` function. public var uppercaseString: Expression { From 6d821f83499500cb7bb7f258655fe83ac6f890ed Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Sat, 13 Apr 2024 08:23:49 +0530 Subject: [PATCH 0987/1046] Add dependency on custom cocoapods fork --- Gemfile | 1 + Gemfile.lock | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..2f97c391 --- /dev/null +++ b/Gemfile @@ -0,0 +1 @@ +gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..75374a30 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,116 @@ +GIT + remote: https://github.com/SagarSDagdu/CocoaPods.git + revision: d96f491f79abd2804d1359c5228cce404dd365b7 + tag: 1.15.2.1-sagard + specs: + cocoapods (1.15.2.1.pre.sagard) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.15.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + +GEM + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.1.3.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.2.0) + bigdecimal (3.1.5) + claide (1.1.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + drb (2.2.0) + ruby2_keywords + escape (0.0.4) + ethon (0.16.0) + ffi (>= 1.15.0) + ffi (1.16.3) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (1.14.4) + concurrent-ruby (~> 1.0) + json (2.7.1) + minitest (5.22.0) + molinillo (0.8.0) + mutex_m (0.2.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.1.3) + public_suffix (4.0.7) + rexml (3.2.6) + ruby-macho (2.5.1) + ruby2_keywords (0.0.5) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.24.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + cocoapods! + +BUNDLED WITH + 2.5.4 From 6491e381d7f827ced234e6aa7edca0a92f25eff6 Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Sat, 13 Apr 2024 08:24:03 +0530 Subject: [PATCH 0988/1046] Update deployment targets for Xcode 15 --- SQLite.swift.podspec | 4 ++-- SQLite.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index b691aeab..4a7a390c 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,8 +18,8 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '11.0' - tvos_deployment_target = '11.0' + ios_deployment_target = '12.0' + tvos_deployment_target = '12.0' osx_deployment_target = '10.13' watchos_deployment_target = '4.0' diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 0b246a00..c5215807 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1299,7 +1299,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1321,7 +1321,7 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1335,7 +1335,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -1349,7 +1349,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -1450,7 +1450,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -1459,7 +1459,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -1509,7 +1509,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; @@ -1517,7 +1517,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 11.0; + TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1538,7 +1538,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1562,7 +1562,7 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MARKETING_VERSION = 0.14.0; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; @@ -1577,7 +1577,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1590,7 +1590,7 @@ buildSettings = { GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/SQLiteTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; From c4ec236be4b089984c8239869309bd4c637642b6 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 15:17:15 +0200 Subject: [PATCH 0989/1046] using temp cocoapods (fix xcode 15 build, to be reverted later) --- run-tests.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 49330c12..1bf8de96 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,10 +7,11 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then + bundle install if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - pod lib lint --no-subspecs --fail-fast + bundle exec pod lib lint --no-subspecs --fail-fast else - pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From a6e7697bac187dd6ee5ee8026e7fca91235e5b73 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 15:31:08 +0200 Subject: [PATCH 0990/1046] fixing bundle versions? --- Gemfile.lock | 2 +- run-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 75374a30..159cbf9c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM i18n (1.14.4) concurrent-ruby (~> 1.0) json (2.7.1) - minitest (5.22.0) + minitest (5.22.3) molinillo (0.8.0) mutex_m (0.2.0) nanaimo (0.3.0) diff --git a/run-tests.sh b/run-tests.sh index 1bf8de96..13975f0e 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,7 +7,7 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - bundle install + bundle install --deployment if [ "$VALIDATOR_SUBSPEC" == "none" ]; then bundle exec pod lib lint --no-subspecs --fail-fast else From 9bed2450cbd38352a5a3316b2817bec8baf4f5d3 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 15:38:17 +0200 Subject: [PATCH 0991/1046] adding source for gems as otherwise it does not resolve correctly in CI --- Gemfile | 2 ++ Gemfile.lock | 11 +++++------ run-tests.sh | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 2f97c391..eb6036cd 100644 --- a/Gemfile +++ b/Gemfile @@ -1 +1,3 @@ +source "https://rubygems.org" + gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' diff --git a/Gemfile.lock b/Gemfile.lock index 159cbf9c..a58783ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,7 @@ GIT xcodeproj (>= 1.23.0, < 2.0) GEM + remote: https://rubygems.org/ specs: CFPropertyList (3.0.7) base64 @@ -45,7 +46,7 @@ GEM json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.5) + bigdecimal (3.1.7) claide (1.1.0) cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) @@ -69,8 +70,7 @@ GEM colored2 (3.1.2) concurrent-ruby (1.2.3) connection_pool (2.4.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) @@ -81,18 +81,17 @@ GEM httpclient (2.8.3) i18n (1.14.4) concurrent-ruby (~> 1.0) - json (2.7.1) + json (2.7.2) minitest (5.22.3) molinillo (0.8.0) mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - nkf (0.1.3) + nkf (0.2.0) public_suffix (4.0.7) rexml (3.2.6) ruby-macho (2.5.1) - ruby2_keywords (0.0.5) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) diff --git a/run-tests.sh b/run-tests.sh index 13975f0e..1bf8de96 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -7,7 +7,7 @@ if [ -n "$BUILD_SCHEME" ]; then make test BUILD_SCHEME="$BUILD_SCHEME" fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then - bundle install --deployment + bundle install if [ "$VALIDATOR_SUBSPEC" == "none" ]; then bundle exec pod lib lint --no-subspecs --fail-fast else From 6be8ca943138e3b3d77bb14b74c1b498d75b0cf4 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 16:30:09 +0200 Subject: [PATCH 0992/1046] fix carthage location? --- Tests/Carthage/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Carthage/Makefile b/Tests/Carthage/Makefile index b44d85b9..fe708ab0 100644 --- a/Tests/Carthage/Makefile +++ b/Tests/Carthage/Makefile @@ -1,4 +1,4 @@ -CARTHAGE := /usr/local/bin/carthage +CARTHAGE := $(shell which carthage) CARTHAGE_PLATFORM := iOS CARTHAGE_CONFIGURATION := Release CARTHAGE_DIR := Carthage From a2a550e6266e80ebe4d896d1360cff4ef9ad202a Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 17:18:49 +0200 Subject: [PATCH 0993/1046] bump xcode version + carthage visionOS tests --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7be89270..9fbba1c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: # https://github.com/CocoaPods/CocoaPods/issues/11839 run: | xcode-select -p - sudo xcode-select -s /Applications/Xcode_15.0.1.app/Contents/Developer + sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" @@ -64,6 +64,10 @@ jobs: env: CARTHAGE_PLATFORM: tvOS run: ./run-tests.sh + - name: "Run tests (CARTHAGE_PLATFORM: visionOS)" + env: + CARTHAGE_PLATFORM: visionOS + run: ./run-tests.sh build-linux: runs-on: ubuntu-latest steps: From f0af5e0a2b6917ae38e3019e3456fd0fa9365864 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Sat, 13 Apr 2024 18:55:58 +0200 Subject: [PATCH 0994/1046] Changelogs v0.15.1 --- CHANGELOG.md | 13 +++++++++++++ Documentation/Index.md | 12 ++++++------ README.md | 4 ++-- SQLite.swift.podspec | 2 +- Tests/SPM/Package.swift | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c5c1fba..79085ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +0.15.1 (14-04-2024), [diff][diff-0.15.1] +======================================== + +* Update CoreFunctions.swift fix typo ([#1249][]) +* Fix #1247 support nil case when decoding optionals ([#1248][]) +* Change deployment targets for Xcode 15 and add dependency on custom Cocoapods fork ([#1255][]) +* Add VisionOS support ([#1237][]) + 0.15.0 (24-02-2024), [diff][diff-0.15.0] ======================================== @@ -155,6 +163,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.14.0]: https://github.com/stephencelis/SQLite.swift/compare/0.13.3...0.14.0 [diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 [diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 +[diff-0.15.1]: https://github.com/stephencelis/SQLite.swift/compare/0.15.0...0.15.1 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -235,3 +244,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1218]: https://github.com/stephencelis/SQLite.swift/pull/1218 [#1210]: https://github.com/stephencelis/SQLite.swift/pull/1210 [#1245]: https://github.com/stephencelis/SQLite.swift/pull/1245 +[#1249]: https://github.com/stephencelis/SQLite.swift/pull/1249 +[#1248]: https://github.com/stephencelis/SQLite.swift/pull/1248 +[#1255]: https://github.com/stephencelis/SQLite.swift/pull/1255 +[#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 diff --git a/Documentation/Index.md b/Documentation/Index.md index 7f900bc5..edb9dab3 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") ] ``` @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.0 + github "stephencelis/SQLite.swift" ~> 0.15.1 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.0' + pod 'SQLite.swift', '~> 0.15.1' end ``` @@ -173,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.0' + pod 'SQLite.swift/standalone', '~> 0.15.1' end ``` @@ -183,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.0' + pod 'SQLite.swift/standalone', '~> 0.15.1' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -199,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.0' + pod 'SQLite.swift/SQLCipher', '~> 0.15.1' end ``` diff --git a/README.md b/README.md index 117edb5a..51090e5d 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.0 + github "stephencelis/SQLite.swift" ~> 0.15.1 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 860fe3d4..035fffb0 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.15.0" + s.version = "0.15.1" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index c82b3b9e..5a96daff 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.0") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From fc236997c8e1b21be4ecaaa547496857483dcf6b Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Tue, 16 Apr 2024 05:40:47 +0200 Subject: [PATCH 0995/1046] fix: visionos to cocoapods --- CHANGELOG.md | 6 ++++++ Documentation/Index.md | 12 ++++++------ README.md | 4 ++-- SQLite.swift.podspec | 7 ++++++- Tests/SPM/Package.swift | 2 +- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79085ce3..376c19d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.15.2 (16-04-2024), [diff][diff-0.15.2] +======================================== +* fix: visionos to cocoapods ([#1260][]) + 0.15.1 (14-04-2024), [diff][diff-0.15.1] ======================================== @@ -164,6 +168,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.14.1]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.14.1 [diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 [diff-0.15.1]: https://github.com/stephencelis/SQLite.swift/compare/0.15.0...0.15.1 +[diff-0.15.2]: https://github.com/stephencelis/SQLite.swift/compare/0.15.1...0.15.2 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -248,3 +253,4 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1248]: https://github.com/stephencelis/SQLite.swift/pull/1248 [#1255]: https://github.com/stephencelis/SQLite.swift/pull/1255 [#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 +[#1260]: https://github.com/stephencelis/SQLite.swift/pull/1260 diff --git a/Documentation/Index.md b/Documentation/Index.md index edb9dab3..adbbff8b 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") ] ``` @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.1 + github "stephencelis/SQLite.swift" ~> 0.15.2 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.1' + pod 'SQLite.swift', '~> 0.15.2' end ``` @@ -173,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.1' + pod 'SQLite.swift/standalone', '~> 0.15.2' end ``` @@ -183,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.1' + pod 'SQLite.swift/standalone', '~> 0.15.2' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -199,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.1' + pod 'SQLite.swift/SQLCipher', '~> 0.15.2' end ``` diff --git a/README.md b/README.md index 51090e5d..a87ab937 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.1 + github "stephencelis/SQLite.swift" ~> 0.15.2 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 035fffb0..efed67c1 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.15.1" + s.version = "0.15.2" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC @@ -22,11 +22,13 @@ Pod::Spec.new do |s| tvos_deployment_target = '12.0' osx_deployment_target = '10.13' watchos_deployment_target = '4.0' + visionos_deployment_target = '1.0' s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target s.osx.deployment_target = osx_deployment_target s.watchos.deployment_target = watchos_deployment_target + s.visionos.deployment_target = visionos_deployment_target s.subspec 'standard' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' @@ -37,6 +39,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target + ss.visionos.deployment_target = visionos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -61,6 +64,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target + ss.visionos.deployment_target = visionos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' @@ -83,6 +87,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target + ss.visionos.deployment_target = visionos_deployment_target ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 5a96daff..b73ecd83 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.1") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 8f75f4609221c55901cf60b1438b7a8441af145e Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Tue, 16 Apr 2024 06:06:25 +0200 Subject: [PATCH 0996/1046] gonna fix that later --- SQLite.swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index efed67c1..88713c9e 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -87,7 +87,7 @@ Pod::Spec.new do |s| ss.tvos.deployment_target = tvos_deployment_target ss.osx.deployment_target = osx_deployment_target ss.watchos.deployment_target = watchos_deployment_target - ss.visionos.deployment_target = visionos_deployment_target + #ss.visionos.deployment_target = visionos_deployment_target # Not supported by SQLCipher for now ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' From 15fe07e3d5d0555a7f03966114ce5a9390823af7 Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Thu, 18 Apr 2024 18:57:43 +0530 Subject: [PATCH 0997/1046] Update the marketing version to the version we will be releasing next For recent releases, the `Info.plist` shipped inside the `xcframeworks` do not match the release version. Fixing that to reflect the marketing version we will be releasing next. --- SQLite.xcodeproj/project.pbxproj | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index 79de22e7..a2d7edbe 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -218,6 +218,9 @@ DB7C5DA728D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA828D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; DB7C5DA928D7C9B6006395CF /* SQLiteVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */; }; + DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; + DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; DEB306BA2B61CEF500F9D46B /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = EE247AD61C3F04ED00AE3E12 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; DEB306BC2B61CEF500F9D46B /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AFC1C3F06E900AE3E12 /* CoreFunctions.swift */; }; DEB306BD2B61CEF500F9D46B /* Coding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49EB68C31F7B3CB400D89D40 /* Coding.swift */; }; @@ -288,9 +291,6 @@ DEB307092B61CF9500F9D46B /* Connection+AttachTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A170C08525D3D27CB5F83C /* Connection+AttachTests.swift */; }; DEB3070B2B61CF9500F9D46B /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E5A1C6BB0F50062603F /* SQLite.framework */; }; DEB3070D2B61CF9500F9D46B /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 3DF7B79528846FCC005DD8CA /* Resources */; }; - DBB93D5A2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; - DBB93D5B2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; - DBB93D5C2A22A373009BB96E /* SchemaReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */; }; EAE5A0372B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; EAE5A0382B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; EAE5A0392B893C43007C7EA4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */; }; @@ -433,10 +433,10 @@ DB58B21028FB864300F8EEA4 /* SchemaReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReader.swift; sourceTree = ""; }; DB58B21528FC7C4600F8EEA4 /* SQLiteFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteFeature.swift; sourceTree = ""; }; DB7C5DA528D7C9B6006395CF /* SQLiteVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteVersion.swift; sourceTree = ""; }; + DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; DEB306E52B61CEF500F9D46B /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DEB307112B61CF9500F9D46B /* SQLiteTests visionOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteTests visionOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DEB307132B61D04500F9D46B /* SQLite visionOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "SQLite visionOS.xctestplan"; path = "Tests/SQLite visionOS.xctestplan"; sourceTree = ""; }; - DBB93D592A22A373009BB96E /* SchemaReaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaReaderTests.swift; sourceTree = ""; }; EAE5A0362B893C43007C7EA4 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; EE247AD31C3F04ED00AE3E12 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EE247AD61C3F04ED00AE3E12 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; @@ -1539,6 +1539,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1561,6 +1562,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1612,6 +1614,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1636,6 +1639,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1660,7 +1664,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1685,7 +1689,7 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1862,7 +1866,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1886,7 +1890,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.14.0; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1936,6 +1940,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1961,6 +1966,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; From 5d3db22d9b1e979a3436d199f10bc7dddd4d3a39 Mon Sep 17 00:00:00 2001 From: Sagar Dagdu Date: Thu, 18 Apr 2024 21:26:22 +0530 Subject: [PATCH 0998/1046] Update `podspec` to include privacy manifest Update `podspec` to include privacy manifest --- SQLite.swift.podspec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 88713c9e..e6aa2c6a 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -34,6 +34,7 @@ Pod::Spec.new do |s| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' ss.library = 'sqlite3' + ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } ss.ios.deployment_target = ios_deployment_target ss.tvos.deployment_target = tvos_deployment_target @@ -53,6 +54,7 @@ Pod::Spec.new do |s| s.subspec 'standalone' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.exclude_files = 'Sources/**/Cipher.swift' + ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_STANDALONE', @@ -77,6 +79,8 @@ Pod::Spec.new do |s| s.subspec 'SQLCipher' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' + ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } + ss.xcconfig = { 'OTHER_SWIFT_FLAGS' => '$(inherited) -DSQLITE_SWIFT_SQLCIPHER', 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1 SQLITE_SWIFT_SQLCIPHER=1' From a95fc6df17d108bd99210db5e8a9bac90fe984b8 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Fri, 19 Apr 2024 02:50:43 +0200 Subject: [PATCH 0999/1046] v0.15.3 (oups) --- CHANGELOG.md | 5 +++++ Documentation/Index.md | 12 ++++++------ README.md | 4 ++-- SQLite.swift.podspec | 2 +- Tests/SPM/Package.swift | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 376c19d8..c96724fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +0.15.3 (19-04-2024), [diff][diff-0.15.3] +======================================== +* Update `podspec` to include privacy manifest ([#1265][]) + 0.15.2 (16-04-2024), [diff][diff-0.15.2] ======================================== * fix: visionos to cocoapods ([#1260][]) @@ -254,3 +258,4 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1255]: https://github.com/stephencelis/SQLite.swift/pull/1255 [#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 [#1260]: https://github.com/stephencelis/SQLite.swift/pull/1260 +[#1265]: https://github.com/stephencelis/SQLite.swift/pull/1265 diff --git a/Documentation/Index.md b/Documentation/Index.md index adbbff8b..fcd2d642 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -108,7 +108,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") ] ``` @@ -129,7 +129,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.2 + github "stephencelis/SQLite.swift" ~> 0.15.3 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -159,7 +159,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.2' + pod 'SQLite.swift', '~> 0.15.3' end ``` @@ -173,7 +173,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.2' + pod 'SQLite.swift/standalone', '~> 0.15.3' end ``` @@ -183,7 +183,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.2' + pod 'SQLite.swift/standalone', '~> 0.15.3' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -199,7 +199,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.2' + pod 'SQLite.swift/SQLCipher', '~> 0.15.3' end ``` diff --git a/README.md b/README.md index a87ab937..a4d713b6 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.2 + github "stephencelis/SQLite.swift" ~> 0.15.3 ``` 3. Run `carthage update` and diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index e6aa2c6a..184ce23e 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.15.2" + s.version = "0.15.3" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index b73ecd83..6521211a 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.2") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 363141ac6c070799239593cf6dcad63fe33edd17 Mon Sep 17 00:00:00 2001 From: YiKai Deng Date: Thu, 26 Sep 2024 17:33:15 +0800 Subject: [PATCH 1000/1046] Add `CustomStringConvertible` for Setter --- Documentation/Upgrading.md | 4 +- Sources/SQLite/Typed/Setter.swift | 6 ++ .../Core/Connection+AttachTests.swift | 4 +- .../SQLiteTests/Core/CoreFunctionsTests.swift | 16 ++--- Tests/SQLiteTests/Core/StatementTests.swift | 4 +- Tests/SQLiteTests/Extensions/FTS4Tests.swift | 6 +- .../Extensions/FTSIntegrationTests.swift | 2 +- .../Schema/SchemaChangerTests.swift | 2 +- .../Schema/SchemaReaderTests.swift | 4 +- Tests/SQLiteTests/TestHelpers.swift | 32 ++++----- .../Typed/CustomFunctionsTests.swift | 28 ++++---- Tests/SQLiteTests/Typed/ExpressionTests.swift | 10 +-- Tests/SQLiteTests/Typed/OperatorsTests.swift | 2 +- .../Typed/QueryIntegrationTests.swift | 70 +++++++++---------- Tests/SQLiteTests/Typed/QueryTests.swift | 22 +++--- Tests/SQLiteTests/Typed/RowTests.swift | 22 +++--- Tests/SQLiteTests/Typed/SelectTests.swift | 8 +-- Tests/SQLiteTests/Typed/SetterTests.swift | 3 + .../Typed/WindowFunctionsTests.swift | 4 +- 19 files changed, 130 insertions(+), 119 deletions(-) diff --git a/Documentation/Upgrading.md b/Documentation/Upgrading.md index f2cc2ecb..0e12aacf 100644 --- a/Documentation/Upgrading.md +++ b/Documentation/Upgrading.md @@ -4,6 +4,8 @@ - `Expression.asSQL()` is no longer available. Expressions now implement `CustomStringConvertible`, where `description` returns the SQL. -- `Statement.prepareRowIterator()` is now longer available. Instead, use the methods +- `Statement.prepareRowIterator()` is no longer available. Instead, use the methods of the same name on `Connection`. - `Connection.registerTokenizer` is no longer available to register custom FTS4 tokenizers. +- `Setter.asSQL()` is no longer available. Instead, Setter now implement `CustomStringConvertible`, + where `description` returns the SQL. diff --git a/Sources/SQLite/Typed/Setter.swift b/Sources/SQLite/Typed/Setter.swift index 7910cab8..8dc8a0e0 100644 --- a/Sources/SQLite/Typed/Setter.swift +++ b/Sources/SQLite/Typed/Setter.swift @@ -75,6 +75,12 @@ extension Setter: Expressible { } +extension Setter: CustomStringConvertible { + public var description: String { + asSQL() + } +} + public func <-(column: Expression, value: Expression) -> Setter { Setter(column: column, value: value) } diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index 940a30ca..f37300ca 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -19,7 +19,7 @@ class ConnectionAttachTests: SQLiteTestCase { try db.attach(.inMemory, as: schemaName) let table = Table("attached_users", database: schemaName) - let name = Expression("string") + let name = SQLite.Expression("string") // create a table, insert some data try db.run(table.create { builder in @@ -41,7 +41,7 @@ class ConnectionAttachTests: SQLiteTestCase { try db.attach(.uri(testDb, parameters: [.mode(.readOnly)]), as: schemaName) let table = Table("tests", database: schemaName) - let email = Expression("email") + let email = SQLite.Expression("email") let rows = try db.prepare(table.select(email)).map { $0[email] } XCTAssertEqual(["foo@bar.com"], rows) diff --git a/Tests/SQLiteTests/Core/CoreFunctionsTests.swift b/Tests/SQLiteTests/Core/CoreFunctionsTests.swift index e03e3769..b866c4e9 100644 --- a/Tests/SQLiteTests/Core/CoreFunctionsTests.swift +++ b/Tests/SQLiteTests/Core/CoreFunctionsTests.swift @@ -12,8 +12,8 @@ class CoreFunctionsTests: XCTestCase { } func test_random_generatesExpressionWithRandomFunction() { - assertSQL("random()", Expression.random()) - assertSQL("random()", Expression.random()) + assertSQL("random()", SQLite.Expression.random()) + assertSQL("random()", SQLite.Expression.random()) } func test_length_wrapsStringExpressionWithLengthFunction() { @@ -38,14 +38,14 @@ class CoreFunctionsTests: XCTestCase { assertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) assertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) - assertSQL("(\"string\" LIKE \"a\")", string.like(Expression("a"))) - assertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(Expression("a"))) + assertSQL("(\"string\" LIKE \"a\")", string.like(SQLite.Expression("a"))) + assertSQL("(\"stringOptional\" LIKE \"a\")", stringOptional.like(SQLite.Expression("a"))) - assertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(Expression("a"), escape: "\\")) - assertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(Expression("a"), escape: "\\")) + assertSQL("(\"string\" LIKE \"a\" ESCAPE '\\')", string.like(SQLite.Expression("a"), escape: "\\")) + assertSQL("(\"stringOptional\" LIKE \"a\" ESCAPE '\\')", stringOptional.like(SQLite.Expression("a"), escape: "\\")) - assertSQL("('string' LIKE \"a\")", "string".like(Expression("a"))) - assertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(Expression("a"), escape: "\\")) + assertSQL("('string' LIKE \"a\")", "string".like(SQLite.Expression("a"))) + assertSQL("('string' LIKE \"a\" ESCAPE '\\')", "string".like(SQLite.Expression("a"), escape: "\\")) } func test_glob_buildsExpressionWithGlobOperator() { diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index 5f212505..dbf99d7c 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -27,7 +27,7 @@ class StatementTests: SQLiteTestCase { func test_zero_sized_blob_returns_null() throws { let blobs = Table("blobs") - let blobColumn = Expression("blob_column") + let blobColumn = SQLite.Expression("blob_column") try db.run(blobs.create { $0.column(blobColumn) }) try db.run(blobs.insert(blobColumn <- Blob(bytes: []))) let blobValue = try db.scalar(blobs.select(blobColumn).limit(1, offset: 0)) @@ -38,7 +38,7 @@ class StatementTests: SQLiteTestCase { let names = ["a", "b", "c"] try insertUsers(names) - let emailColumn = Expression("email") + let emailColumn = SQLite.Expression("email") let statement = try db.prepare("SELECT email FROM users") let emails = try statement.prepareRowIterator().map { $0[emailColumn] } diff --git a/Tests/SQLiteTests/Extensions/FTS4Tests.swift b/Tests/SQLiteTests/Extensions/FTS4Tests.swift index 5e595007..f7258fb5 100644 --- a/Tests/SQLiteTests/Extensions/FTS4Tests.swift +++ b/Tests/SQLiteTests/Extensions/FTS4Tests.swift @@ -35,9 +35,9 @@ class FTS4Tests: XCTestCase { } func test_match_onVirtualTableAsExpression_compilesMatchExpression() { - assertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression) - assertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) - assertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) + assertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as SQLite.Expression) + assertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as SQLite.Expression) + assertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as SQLite.Expression) } func test_match_onVirtualTableAsQueryType_compilesMatchExpression() { diff --git a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift index da9ba8dc..1129ae08 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -11,7 +11,7 @@ import SQLite3 @testable import SQLite class FTSIntegrationTests: SQLiteTestCase { - let email = Expression("email") + let email = SQLite.Expression("email") let index = VirtualTable("index") private func createIndex() throws { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 9f8e21f4..2bec6a06 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -92,7 +92,7 @@ class SchemaChangerTests: SQLiteTestCase { } func test_add_column() throws { - let column = Expression("new_column") + let column = SQLite.Expression("new_column") let newColumn = ColumnDefinition(name: "new_column", type: .TEXT, nullable: true, diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index d57ebff7..8c033e41 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -163,7 +163,7 @@ class SchemaReaderTests: SQLiteTestCase { try db.run(linkTable.create(block: { definition in definition.column(idColumn, primaryKey: .autoincrement) - definition.column(testIdColumn, unique: false, check: nil, references: users, Expression("id")) + definition.column(testIdColumn, unique: false, check: nil, references: users, SQLite.Expression("id")) })) let foreignKeys = try schemaReader.foreignKeys(table: "test_links") @@ -238,7 +238,7 @@ class SchemaReaderTests: SQLiteTestCase { } func test_objectDefinitions_indexes() throws { - let emailIndex = users.createIndex(Expression("email"), unique: false, ifNotExists: true) + let emailIndex = users.createIndex(SQLite.Expression("email"), unique: false, ifNotExists: true) try db.run(emailIndex) let indexes = try schemaReader.objectDefinitions(type: .index) diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index e2da5927..56415a59 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -74,29 +74,29 @@ class SQLiteTestCase: XCTestCase { } -let bool = Expression("bool") -let boolOptional = Expression("boolOptional") +let bool = SQLite.Expression("bool") +let boolOptional = SQLite.Expression("boolOptional") -let data = Expression("blob") -let dataOptional = Expression("blobOptional") +let data = SQLite.Expression("blob") +let dataOptional = SQLite.Expression("blobOptional") -let date = Expression("date") -let dateOptional = Expression("dateOptional") +let date = SQLite.Expression("date") +let dateOptional = SQLite.Expression("dateOptional") -let double = Expression("double") -let doubleOptional = Expression("doubleOptional") +let double = SQLite.Expression("double") +let doubleOptional = SQLite.Expression("doubleOptional") -let int = Expression("int") -let intOptional = Expression("intOptional") +let int = SQLite.Expression("int") +let intOptional = SQLite.Expression("intOptional") -let int64 = Expression("int64") -let int64Optional = Expression("int64Optional") +let int64 = SQLite.Expression("int64") +let int64Optional = SQLite.Expression("int64Optional") -let string = Expression("string") -let stringOptional = Expression("stringOptional") +let string = SQLite.Expression("string") +let stringOptional = SQLite.Expression("stringOptional") -let uuid = Expression("uuid") -let uuidOptional = Expression("uuidOptional") +let uuid = SQLite.Expression("uuid") +let uuidOptional = SQLite.Expression("uuidOptional") let testUUIDValue = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! diff --git a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift index 0f46b380..8598b6fb 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -5,8 +5,8 @@ import SQLite #if !os(Linux) class CustomFunctionNoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = () -> Expression - typealias FunctionResultOptional = () -> Expression + typealias FunctionNoOptional = () -> SQLite.Expression + typealias FunctionResultOptional = () -> SQLite.Expression func testFunctionNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { @@ -26,10 +26,10 @@ class CustomFunctionNoArgsTests: SQLiteTestCase { } class CustomFunctionWithOneArgTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression) -> Expression - typealias FunctionLeftOptional = (Expression) -> Expression - typealias FunctionResultOptional = (Expression) -> Expression - typealias FunctionLeftResultOptional = (Expression) -> Expression + typealias FunctionNoOptional = (SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftOptional = (SQLite.Expression) -> SQLite.Expression + typealias FunctionResultOptional = (SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftResultOptional = (SQLite.Expression) -> SQLite.Expression func testFunctionNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a in @@ -65,14 +65,14 @@ class CustomFunctionWithOneArgTests: SQLiteTestCase { } class CustomFunctionWithTwoArgsTests: SQLiteTestCase { - typealias FunctionNoOptional = (Expression, Expression) -> Expression - typealias FunctionLeftOptional = (Expression, Expression) -> Expression - typealias FunctionRightOptional = (Expression, Expression) -> Expression - typealias FunctionResultOptional = (Expression, Expression) -> Expression - typealias FunctionLeftRightOptional = (Expression, Expression) -> Expression - typealias FunctionLeftResultOptional = (Expression, Expression) -> Expression - typealias FunctionRightResultOptional = (Expression, Expression) -> Expression - typealias FunctionLeftRightResultOptional = (Expression, Expression) -> Expression + typealias FunctionNoOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionRightOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftRightOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionRightResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression + typealias FunctionLeftRightResultOptional = (SQLite.Expression, SQLite.Expression) -> SQLite.Expression func testNoOptional() throws { let _: FunctionNoOptional = try db.createFunction("test", deterministic: true) { a, b in diff --git a/Tests/SQLiteTests/Typed/ExpressionTests.swift b/Tests/SQLiteTests/Typed/ExpressionTests.swift index 147d62e9..1b97fe94 100644 --- a/Tests/SQLiteTests/Typed/ExpressionTests.swift +++ b/Tests/SQLiteTests/Typed/ExpressionTests.swift @@ -4,17 +4,17 @@ import XCTest class ExpressionTests: XCTestCase { func test_asSQL_expression_bindings() { - let expression = Expression("foo ? bar", ["baz"]) + let expression = SQLite.Expression("foo ? bar", ["baz"]) XCTAssertEqual(expression.asSQL(), "foo 'baz' bar") } func test_asSQL_expression_bindings_quoting() { - let expression = Expression("foo ? bar", ["'baz'"]) + let expression = SQLite.Expression("foo ? bar", ["'baz'"]) XCTAssertEqual(expression.asSQL(), "foo '''baz''' bar") } func test_expression_custom_string_convertible() { - let expression = Expression("foo ? bar", ["baz"]) + let expression = SQLite.Expression("foo ? bar", ["baz"]) XCTAssertEqual(expression.asSQL(), expression.description) } @@ -24,12 +24,12 @@ class ExpressionTests: XCTestCase { } func test_init_literal() { - let expression = Expression(literal: "literal") + let expression = SQLite.Expression(literal: "literal") XCTAssertEqual(expression.template, "literal") } func test_init_identifier() { - let expression = Expression("identifier") + let expression = SQLite.Expression("identifier") XCTAssertEqual(expression.template, "\"identifier\"") } } diff --git a/Tests/SQLiteTests/Typed/OperatorsTests.swift b/Tests/SQLiteTests/Typed/OperatorsTests.swift index 370b910b..7a5d83da 100644 --- a/Tests/SQLiteTests/Typed/OperatorsTests.swift +++ b/Tests/SQLiteTests/Typed/OperatorsTests.swift @@ -356,7 +356,7 @@ class OperatorsTests: XCTestCase { } func test_precedencePreserved() { - let n = Expression(value: 1) + let n = SQLite.Expression(value: 1) assertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) assertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) } diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 7ac72d0c..6be98ca0 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -12,9 +12,9 @@ import SQLite3 class QueryIntegrationTests: SQLiteTestCase { - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") + let id = SQLite.Expression("id") + let email = SQLite.Expression("email") + let age = SQLite.Expression("age") override func setUpWithError() throws { try super.setUpWithError() @@ -24,7 +24,7 @@ class QueryIntegrationTests: SQLiteTestCase { // MARK: - func test_select() throws { - let managerId = Expression("manager_id") + let managerId = SQLite.Expression("manager_id") let managers = users.alias("managers") let alice = try db.run(users.insert(email <- "alice@example.com")) @@ -39,7 +39,7 @@ class QueryIntegrationTests: SQLiteTestCase { let names = ["a", "b", "c"] try insertUsers(names) - let emailColumn = Expression("email") + let emailColumn = SQLite.Expression("email") let emails = try db.prepareRowIterator(users).map { $0[emailColumn] } XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted()) @@ -55,7 +55,7 @@ class QueryIntegrationTests: SQLiteTestCase { } func test_select_optional() throws { - let managerId = Expression("manager_id") + let managerId = SQLite.Expression("manager_id") let managers = users.alias("managers") let alice = try db.run(users.insert(email <- "alice@example.com")) @@ -69,15 +69,15 @@ class QueryIntegrationTests: SQLiteTestCase { func test_select_codable() throws { let table = Table("codable") try db.run(table.create { builder in - builder.column(Expression("int")) - builder.column(Expression("string")) - builder.column(Expression("bool")) - builder.column(Expression("float")) - builder.column(Expression("double")) - builder.column(Expression("date")) - builder.column(Expression("uuid")) - builder.column(Expression("optional")) - builder.column(Expression("sub")) + builder.column(SQLite.Expression("int")) + builder.column(SQLite.Expression("string")) + builder.column(SQLite.Expression("bool")) + builder.column(SQLite.Expression("float")) + builder.column(SQLite.Expression("double")) + builder.column(SQLite.Expression("date")) + builder.column(SQLite.Expression("uuid")) + builder.column(SQLite.Expression("optional")) + builder.column(SQLite.Expression("sub")) }) let value1 = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, @@ -133,13 +133,13 @@ class QueryIntegrationTests: SQLiteTestCase { func test_insert_many_encodables() throws { let table = Table("codable") try db.run(table.create { builder in - builder.column(Expression("int")) - builder.column(Expression("string")) - builder.column(Expression("bool")) - builder.column(Expression("float")) - builder.column(Expression("double")) - builder.column(Expression("date")) - builder.column(Expression("uuid")) + builder.column(SQLite.Expression("int")) + builder.column(SQLite.Expression("string")) + builder.column(SQLite.Expression("bool")) + builder.column(SQLite.Expression("float")) + builder.column(SQLite.Expression("double")) + builder.column(SQLite.Expression("date")) + builder.column(SQLite.Expression("uuid")) }) let value1 = TestOptionalCodable(int: 5, string: "6", bool: true, float: 7, double: 8, @@ -161,9 +161,9 @@ class QueryIntegrationTests: SQLiteTestCase { let table = Table("custom_codable") try db.run(table.create { builder in - builder.column(Expression("myInt")) - builder.column(Expression("myString")) - builder.column(Expression("myOptionalArray")) + builder.column(SQLite.Expression("myInt")) + builder.column(SQLite.Expression("myString")) + builder.column(SQLite.Expression("myOptionalArray")) }) let customType = TestTypeWithOptionalArray(myInt: 13, myString: "foo", myOptionalArray: [1, 2, 3]) @@ -216,22 +216,22 @@ class QueryIntegrationTests: SQLiteTestCase { let actualIDs = try db.prepare(query1.union(query2)).map { $0[id] } XCTAssertEqual(expectedIDs, actualIDs) - let query3 = users.select(users[*], Expression(literal: "1 AS weight")).filter(email == "sally@example.com") - let query4 = users.select(users[*], Expression(literal: "2 AS weight")).filter(email == "alice@example.com") + let query3 = users.select(users[*], SQLite.Expression(literal: "1 AS weight")).filter(email == "sally@example.com") + let query4 = users.select(users[*], SQLite.Expression(literal: "2 AS weight")).filter(email == "alice@example.com") - let sql = query3.union(query4).order(Expression(literal: "weight")).asSQL() + let sql = query3.union(query4).order(SQLite.Expression(literal: "weight")).asSQL() XCTAssertEqual(sql, """ SELECT "users".*, 1 AS weight FROM "users" WHERE ("email" = 'sally@example.com') UNION \ SELECT "users".*, 2 AS weight FROM "users" WHERE ("email" = 'alice@example.com') ORDER BY weight """) - let orderedIDs = try db.prepare(query3.union(query4).order(Expression(literal: "weight"), email)).map { $0[id] } + let orderedIDs = try db.prepare(query3.union(query4).order(SQLite.Expression(literal: "weight"), email)).map { $0[id] } XCTAssertEqual(Array(expectedIDs.reversed()), orderedIDs) } func test_no_such_column() throws { - let doesNotExist = Expression("doesNotExist") + let doesNotExist = SQLite.Expression("doesNotExist") try insertUser("alice") let row = try db.pluck(users.filter(email == "alice@example.com"))! @@ -272,15 +272,15 @@ class QueryIntegrationTests: SQLiteTestCase { // https://github.com/stephencelis/SQLite.swift/issues/285 func test_order_by_random() throws { try insertUsers(["a", "b", "c'"]) - let result = Array(try db.prepare(users.select(email).order(Expression.random()).limit(1))) + let result = Array(try db.prepare(users.select(email).order(SQLite.Expression.random()).limit(1))) XCTAssertEqual(1, result.count) } func test_with_recursive() throws { let nodes = Table("nodes") - let id = Expression("id") - let parent = Expression("parent") - let value = Expression("value") + let id = SQLite.Expression("id") + let parent = SQLite.Expression("parent") + let value = SQLite.Expression("value") try db.run(nodes.create { builder in builder.column(id) @@ -320,7 +320,7 @@ class QueryIntegrationTests: SQLiteTestCase { /// Verify that `*` is properly expanded in a SELECT statement following a WITH clause. func test_with_glob_expansion() throws { let names = Table("names") - let name = Expression("name") + let name = SQLite.Expression("name") try db.run(names.create { builder in builder.column(email) builder.column(name) diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index f698c621..f018f097 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -13,19 +13,19 @@ import SQLite3 class QueryTests: XCTestCase { let users = Table("users") - let id = Expression("id") - let email = Expression("email") - let age = Expression("age") - let admin = Expression("admin") - let optionalAdmin = Expression("admin") + let id = SQLite.Expression("id") + let email = SQLite.Expression("email") + let age = SQLite.Expression("age") + let admin = SQLite.Expression("admin") + let optionalAdmin = SQLite.Expression("admin") let posts = Table("posts") - let userId = Expression("user_id") - let categoryId = Expression("category_id") - let published = Expression("published") + let userId = SQLite.Expression("user_id") + let categoryId = SQLite.Expression("category_id") + let published = SQLite.Expression("published") let categories = Table("categories") - let tag = Expression("tag") + let tag = SQLite.Expression("tag") func test_select_withExpression_compilesSelectClause() { assertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) @@ -217,7 +217,7 @@ class QueryTests: XCTestCase { } func test_alias_aliasesTable() { - let managerId = Expression("manager_id") + let managerId = SQLite.Expression("manager_id") let managers = users.alias("managers") @@ -422,7 +422,7 @@ class QueryTests: XCTestCase { func test_upsert_encodable() throws { let emails = Table("emails") - let string = Expression("string") + let string = SQLite.Expression("string") let value = TestCodable(int: 1, string: "2", bool: true, float: 3, double: 4, date: Date(timeIntervalSince1970: 0), uuid: testUUIDValue, optional: nil, sub: nil) let insert = try emails.upsert(value, onConflictOf: string) diff --git a/Tests/SQLiteTests/Typed/RowTests.swift b/Tests/SQLiteTests/Typed/RowTests.swift index 14fa373b..1aed00a7 100644 --- a/Tests/SQLiteTests/Typed/RowTests.swift +++ b/Tests/SQLiteTests/Typed/RowTests.swift @@ -5,49 +5,49 @@ class RowTests: XCTestCase { public func test_get_value() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertEqual("value", result) } public func test_get_value_subscript() { let row = Row(["\"foo\"": 0], ["value"]) - let result = row[Expression("foo")] + let result = row[SQLite.Expression("foo")] XCTAssertEqual("value", result) } public func test_get_value_optional() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertEqual("value", result) } public func test_get_value_optional_subscript() { let row = Row(["\"foo\"": 0], ["value"]) - let result = row[Expression("foo")] + let result = row[SQLite.Expression("foo")] XCTAssertEqual("value", result) } public func test_get_value_optional_nil() throws { let row = Row(["\"foo\"": 0], [nil]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertNil(result) } public func test_get_value_optional_nil_subscript() { let row = Row(["\"foo\"": 0], [nil]) - let result = row[Expression("foo")] + let result = row[SQLite.Expression("foo")] XCTAssertNil(result) } public func test_get_type_mismatch_throws_unexpected_null_value() { let row = Row(["\"foo\"": 0], ["value"]) - XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + XCTAssertThrowsError(try row.get(SQLite.Expression("foo"))) { error in if case QueryError.unexpectedNullValue(let name) = error { XCTAssertEqual("\"foo\"", name) } else { @@ -58,13 +58,13 @@ class RowTests: XCTestCase { public func test_get_type_mismatch_optional_returns_nil() throws { let row = Row(["\"foo\"": 0], ["value"]) - let result = try row.get(Expression("foo")) + let result = try row.get(SQLite.Expression("foo")) XCTAssertNil(result) } public func test_get_non_existent_column_throws_no_such_column() { let row = Row(["\"foo\"": 0], ["value"]) - XCTAssertThrowsError(try row.get(Expression("bar"))) { error in + XCTAssertThrowsError(try row.get(SQLite.Expression("bar"))) { error in if case QueryError.noSuchColumn(let name, let columns) = error { XCTAssertEqual("\"bar\"", name) XCTAssertEqual(["\"foo\""], columns) @@ -76,7 +76,7 @@ class RowTests: XCTestCase { public func test_get_ambiguous_column_throws() { let row = Row(["table1.\"foo\"": 0, "table2.\"foo\"": 1], ["value"]) - XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + XCTAssertThrowsError(try row.get(SQLite.Expression("foo"))) { error in if case QueryError.ambiguousColumn(let name, let columns) = error { XCTAssertEqual("\"foo\"", name) XCTAssertEqual(["table1.\"foo\"", "table2.\"foo\""], columns.sorted()) @@ -107,7 +107,7 @@ class RowTests: XCTestCase { } let row = Row(["\"foo\"": 0], [Blob(bytes: [])]) - XCTAssertThrowsError(try row.get(Expression("foo"))) { error in + XCTAssertThrowsError(try row.get(SQLite.Expression("foo"))) { error in if case MyType.MyError.failed = error { XCTAssertTrue(true) } else { diff --git a/Tests/SQLiteTests/Typed/SelectTests.swift b/Tests/SQLiteTests/Typed/SelectTests.swift index 52d5bb6b..5fa3cd30 100644 --- a/Tests/SQLiteTests/Typed/SelectTests.swift +++ b/Tests/SQLiteTests/Typed/SelectTests.swift @@ -24,10 +24,10 @@ class SelectTests: SQLiteTestCase { let usersData = Table("users_name") let users = Table("users") - let name = Expression("name") - let id = Expression("id") - let userID = Expression("user_id") - let email = Expression("email") + let name = SQLite.Expression("name") + let id = SQLite.Expression("id") + let userID = SQLite.Expression("user_id") + let email = SQLite.Expression("email") try insertUser("Joey") try db.run(usersData.insert( diff --git a/Tests/SQLiteTests/Typed/SetterTests.swift b/Tests/SQLiteTests/Typed/SetterTests.swift index 938dd013..05da57a4 100644 --- a/Tests/SQLiteTests/Typed/SetterTests.swift +++ b/Tests/SQLiteTests/Typed/SetterTests.swift @@ -134,4 +134,7 @@ class SetterTests: XCTestCase { assertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) } + func test_setter_custom_string_convertible() { + XCTAssertEqual("\"int\" = \"int\"", (int <- int).description) + } } diff --git a/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift index 6ded152b..01e88297 100644 --- a/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/WindowFunctionsTests.swift @@ -34,13 +34,13 @@ class WindowFunctionsTests: XCTestCase { func test_lag_wrapsExpressionWithOverClause() { assertSQL("lag(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lag(int.desc)) assertSQL("lag(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 7, int.desc)) - assertSQL("lag(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 1, default: Expression(value: 3), int.desc)) + assertSQL("lag(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lag(offset: 1, default: SQLite.Expression(value: 3), int.desc)) } func test_lead_wrapsExpressionWithOverClause() { assertSQL("lead(\"int\", 0) OVER (ORDER BY \"int\" DESC)", int.lead(int.desc)) assertSQL("lead(\"int\", 7) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 7, int.desc)) - assertSQL("lead(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 1, default: Expression(value: 3), int.desc)) + assertSQL("lead(\"int\", 1, 3) OVER (ORDER BY \"int\" DESC)", int.lead(offset: 1, default: SQLite.Expression(value: 3), int.desc)) } func test_firstValue_wrapsExpressionWithOverClause() { From 4a5eda39a5aa63b4151b54e13ac9cd6ff3390a7e Mon Sep 17 00:00:00 2001 From: Cary Clark Date: Fri, 4 Oct 2024 15:54:10 -0700 Subject: [PATCH 1001/1046] Update oldest supported platform versions --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 238661ae..12a2f11c 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription let package = Package( name: "SQLite.swift", platforms: [ - .iOS(.v11), + .iOS(.v12), .macOS(.v10_13), .watchOS(.v4), - .tvOS(.v11), + .tvOS(.v12), .visionOS(.v1) ], products: [ From 748dcba3312485151fa91b551a4564238f2b69b9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 18:35:08 +0200 Subject: [PATCH 1002/1046] Support creating tables in schema changer --- Makefile | 2 +- Sources/SQLite/Schema/SchemaChanger.swift | 49 +++++++++++++++++++ Sources/SQLite/Schema/SchemaDefinitions.swift | 5 +- .../Schema/SchemaChangerTests.swift | 46 +++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 73b33f97..52a25a12 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ lint: $(SWIFTLINT) $< --strict lint-fix: $(SWIFTLINT) - $< lint fix + $< --fix clean: $(XCODEBUILD) $(BUILD_ARGUMENTS) clean diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index af7b5e27..b6ed7312 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -43,19 +43,31 @@ public class SchemaChanger: CustomStringConvertible { public enum Operation { case addColumn(ColumnDefinition) + case addIndex(IndexDefinition) case dropColumn(String) case renameColumn(String, String) case renameTable(String) + case createTable(columns: [ColumnDefinition]) /// Returns non-nil if the operation can be executed with a simple SQL statement func toSQL(_ table: String, version: SQLiteVersion) -> String? { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" + case .addIndex(let definition): + let unique = definition.unique ? "UNIQUE" : "" + let columns = definition.columns.joined(separator: ", ") + let `where` = definition.where.map { " WHERE " + $0 } ?? "" + + return "CREATE \(unique) INDEX \(definition.name) ON \(definition.table) (\(columns)) \(`where`)" case .renameColumn(let from, let to) where SQLiteFeature.renameColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" + case .createTable(let columns): + return "CREATE TABLE \(table.quote()) (" + + columns.map { $0.toSQL() }.joined(separator: ", ") + + ")" default: return nil } } @@ -108,12 +120,39 @@ public class SchemaChanger: CustomStringConvertible { } } + public class CreateTableDefinition { + fileprivate var columnDefinitions: [ColumnDefinition] = [] + fileprivate var indexDefinitions: [IndexDefinition] = [] + + let name: String + + init(name: String) { + self.name = name + } + + public func add(column: ColumnDefinition) { + columnDefinitions.append(column) + } + + public func add(index: IndexDefinition) { + indexDefinitions.append(index) + } + + var operations: [Operation] { + precondition(!columnDefinitions.isEmpty) + return [ + .createTable(columns: columnDefinitions) + ] + indexDefinitions.map { .addIndex($0) } + } + } + private let connection: Connection private let schemaReader: SchemaReader private let version: SQLiteVersion static let tempPrefix = "tmp_" typealias Block = () throws -> Void public typealias AlterTableDefinitionBlock = (AlterTableDefinition) -> Void + public typealias CreateTableDefinitionBlock = (CreateTableDefinition) -> Void struct Options: OptionSet { let rawValue: Int @@ -141,6 +180,15 @@ public class SchemaChanger: CustomStringConvertible { } } + public func create(table: String, ifNotExists: Bool = false, block: CreateTableDefinitionBlock) throws { + let createTableDefinition = CreateTableDefinition(name: table) + block(createTableDefinition) + + for operation in createTableDefinition.operations { + try run(table: table, operation: operation) + } + } + public func drop(table: String, ifExists: Bool = true) throws { try dropTable(table, ifExists: ifExists) } @@ -263,6 +311,7 @@ extension TableDefinition { func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self + case .createTable, .addIndex: fatalError() case .addColumn: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") case .dropColumn(let column): return TableDefinition(name: name, diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 80f9e199..60837244 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -270,12 +270,15 @@ public struct IndexDefinition: Equatable { return memo2 } } + + let orders = indexSQL.flatMap(orders) + self.init(table: table, name: name, unique: unique, columns: columns, where: indexSQL.flatMap(wherePart), - orders: indexSQL.flatMap(orders)) + orders: (orders?.isEmpty ?? false) ? nil : orders) } public let table: String diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 2bec6a06..125ef09e 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -154,4 +154,50 @@ class SchemaChangerTests: SQLiteTestCase { let users_new = Table("users_new") XCTAssertEqual((try db.scalar(users_new.count)) as Int, 1) } + + func test_create_table() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "name", type: .TEXT, nullable: false)) + table.add(column: .init(name: "age", type: .INTEGER)) + + table.add(index: .init(table: table.name, + name: "nameIndex", + unique: true, + columns: ["name"], + where: nil, + orders: nil)) + } + + // make sure new table can be queried + let foo = Table("foo") + XCTAssertEqual((try db.scalar(foo.count)) as Int, 0) + + let columns = try schema.columnDefinitions(table: "foo") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: true, onConflict: nil), + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "name", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + nullable: true, + defaultValue: .NULL, + references: nil) + ]) + + let indexes = try schema.indexDefinitions(table: "foo") + XCTAssertEqual(indexes, [ + IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil) + ]) + } } From 261f98ae268f986ec6e54822d45d9bda263bdd50 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 22:17:45 +0200 Subject: [PATCH 1003/1046] Update docs --- Documentation/Index.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/Index.md b/Documentation/Index.md index fcd2d642..bc62c791 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -63,6 +63,7 @@ - [Renaming Columns](#renaming-columns) - [Dropping Columns](#dropping-columns) - [Renaming/Dropping Tables](#renamingdropping-tables) + - [Creating Tables](#creating-tables) - [Indexes](#indexes) - [Creating Indexes](#creating-indexes) - [Dropping Indexes](#dropping-indexes) @@ -1583,6 +1584,16 @@ try schemaChanger.rename(table: "users", to: "users_new") try schemaChanger.drop(table: "emails", ifExists: false) ``` +#### Creating Tables + +```swift +let schemaChanger = SchemaChanger(connection: db) + +try schemaChanger.create(table: "users") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "name", type: .TEXT, nullable: false)) +} + ### Indexes From e21fde28f0f96cc452151aaa17413b198f3718a9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 22:18:37 +0200 Subject: [PATCH 1004/1046] Revert to standard cocoapods --- Gemfile | 2 +- Gemfile.lock | 93 ++++++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/Gemfile b/Gemfile index eb6036cd..1f2ebb87 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem 'cocoapods', :git => 'https://github.com/SagarSDagdu/CocoaPods.git', tag: '1.15.2.1-sagard' +gem 'cocoapods' diff --git a/Gemfile.lock b/Gemfile.lock index a58783ea..464ef06b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,3 @@ -GIT - remote: https://github.com/SagarSDagdu/CocoaPods.git - revision: d96f491f79abd2804d1359c5228cce404dd365b7 - tag: 1.15.2.1-sagard - specs: - cocoapods (1.15.2.1.pre.sagard) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - GEM remote: https://rubygems.org/ specs: @@ -29,26 +5,47 @@ GEM base64 nkf rexml - activesupport (7.1.3.2) + activesupport (7.2.2.1) base64 + benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.7) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) - cocoapods-core (1.15.2) + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -68,48 +65,52 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.3) - connection_pool (2.4.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.16.3) + ffi (1.17.2) + ffi (1.17.2-arm64-darwin) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.14.4) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.7.2) - minitest (5.22.3) + json (2.12.0) + logger (1.7.0) + minitest (5.25.5) molinillo (0.8.0) - mutex_m (0.2.0) - nanaimo (0.3.0) + mutex_m (0.3.0) + nanaimo (0.4.0) nap (1.1.0) netrc (0.11.0) nkf (0.2.0) public_suffix (4.0.7) - rexml (3.2.6) + rexml (3.4.1) ruby-macho (2.5.1) + securerandom (0.4.1) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.24.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) PLATFORMS arm64-darwin-23 ruby DEPENDENCIES - cocoapods! + cocoapods BUNDLED WITH 2.5.4 From 5ef20d1887e4b8763df890c4f1ffc256b3f79b99 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 22:39:15 +0200 Subject: [PATCH 1005/1046] Don't lint against visionOS --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 1bf8de96..cb68a022 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -11,7 +11,7 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then if [ "$VALIDATOR_SUBSPEC" == "none" ]; then bundle exec pod lib lint --no-subspecs --fail-fast else - bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos fi elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" From 017929080c146c2085b21adcce95ff67b9e82d2e Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 23:14:13 +0200 Subject: [PATCH 1006/1046] Disable visionos just for standalone --- run-tests.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index cb68a022..94f643e4 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -8,15 +8,21 @@ if [ -n "$BUILD_SCHEME" ]; then fi elif [ -n "$VALIDATOR_SUBSPEC" ]; then bundle install - if [ "$VALIDATOR_SUBSPEC" == "none" ]; then - bundle exec pod lib lint --no-subspecs --fail-fast - else - bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos - fi + case "$VALIDATOR_SUBSPEC" in + none) + bundle exec pod lib lint --no-subspecs --fail-fast + ;; + standalone) + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos + ;; + *) + bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast + ;; + esac elif [ -n "$CARTHAGE_PLATFORM" ]; then cd Tests/Carthage && make test CARTHAGE_PLATFORM="$CARTHAGE_PLATFORM" elif [ -n "$SPM" ]; then - cd Tests/SPM && swift ${SPM} + cd Tests/SPM && swift "${SPM}" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift ${PACKAGE_MANAGER_COMMAND} + swift "${PACKAGE_MANAGER_COMMAND}" fi From 023c5039780b1c086f1d4bbea1c6d5a683e3041f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 23:18:21 +0200 Subject: [PATCH 1007/1046] Enable word splitting --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 94f643e4..7d5c26d2 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -24,5 +24,5 @@ elif [ -n "$CARTHAGE_PLATFORM" ]; then elif [ -n "$SPM" ]; then cd Tests/SPM && swift "${SPM}" elif [ -n "${PACKAGE_MANAGER_COMMAND}" ]; then - swift "${PACKAGE_MANAGER_COMMAND}" + swift ${PACKAGE_MANAGER_COMMAND} fi From 673367b772b04a8df0be64e757b2979da8798939 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Tue, 20 May 2025 23:36:41 +0200 Subject: [PATCH 1008/1046] Public init --- Sources/SQLite/Schema/SchemaDefinitions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 60837244..897f8557 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -93,7 +93,7 @@ public struct ColumnDefinition: Equatable { // swiftlint:disable:next force_try static let pattern = try! NSRegularExpression(pattern: "PRIMARY KEY\\s*(?:ASC|DESC)?\\s*(?:ON CONFLICT (\\w+)?)?\\s*(AUTOINCREMENT)?") - init(autoIncrement: Bool = true, onConflict: OnConflict? = nil) { + public init(autoIncrement: Bool = true, onConflict: OnConflict? = nil) { self.autoIncrement = autoIncrement self.onConflict = onConflict } From 5de1420d58a87e082bb3659da9b59a19687cb867 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 14:23:31 +0200 Subject: [PATCH 1009/1046] Support ifNotExists --- Sources/SQLite/Schema/SchemaChanger.swift | 14 +++++++----- .../Schema/SchemaChangerTests.swift | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index b6ed7312..e2e3d5fa 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -47,7 +47,7 @@ public class SchemaChanger: CustomStringConvertible { case dropColumn(String) case renameColumn(String, String) case renameTable(String) - case createTable(columns: [ColumnDefinition]) + case createTable(columns: [ColumnDefinition], ifNotExists: Bool) /// Returns non-nil if the operation can be executed with a simple SQL statement func toSQL(_ table: String, version: SQLiteVersion) -> String? { @@ -64,8 +64,8 @@ public class SchemaChanger: CustomStringConvertible { return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" - case .createTable(let columns): - return "CREATE TABLE \(table.quote()) (" + + case .createTable(let columns, let ifNotExists): + return "CREATE TABLE \(ifNotExists ? " IF NOT EXISTS " : "") \(table.quote()) (" + columns.map { $0.toSQL() }.joined(separator: ", ") + ")" default: return nil @@ -125,9 +125,11 @@ public class SchemaChanger: CustomStringConvertible { fileprivate var indexDefinitions: [IndexDefinition] = [] let name: String + let ifNotExists: Bool - init(name: String) { + init(name: String, ifNotExists: Bool) { self.name = name + self.ifNotExists = ifNotExists } public func add(column: ColumnDefinition) { @@ -141,7 +143,7 @@ public class SchemaChanger: CustomStringConvertible { var operations: [Operation] { precondition(!columnDefinitions.isEmpty) return [ - .createTable(columns: columnDefinitions) + .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) ] + indexDefinitions.map { .addIndex($0) } } } @@ -181,7 +183,7 @@ public class SchemaChanger: CustomStringConvertible { } public func create(table: String, ifNotExists: Bool = false, block: CreateTableDefinitionBlock) throws { - let createTableDefinition = CreateTableDefinition(name: table) + let createTableDefinition = CreateTableDefinition(name: table, ifNotExists: ifNotExists) block(createTableDefinition) for operation in createTableDefinition.operations { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 125ef09e..bba16e08 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -200,4 +200,26 @@ class SchemaChangerTests: SQLiteTestCase { IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil) ]) } + + func test_create_table_if_not_exists() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + + try schemaChanger.create(table: "foo", ifNotExists: true) { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + + XCTAssertThrowsError( + try schemaChanger.create(table: "foo", ifNotExists: false) { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + ) { error in + if case Result.error(_, let code, _) = error { + XCTAssertEqual(code, 1) + } else { + XCTFail("unexpected error: \(error)") + } + } + } } From f2a86841f527f756610797d56b6c49a46c947f41 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 17:24:57 +0200 Subject: [PATCH 1010/1046] Add columns via Expressions --- Sources/SQLite/Schema/SchemaChanger.swift | 25 ++++++++++++++++ .../Schema/SchemaChangerTests.swift | 30 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index e2e3d5fa..d3342032 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -136,6 +136,14 @@ public class SchemaChanger: CustomStringConvertible { columnDefinitions.append(column) } + public func add(expression: Expression) where T: Value { + add(column: .init(name: columnName(for: expression), type: .init(expression: expression), nullable: false)) + } + + public func add(expression: Expression) where T: Value { + add(column: .init(name: columnName(for: expression), type: .init(expression: expression), nullable: true)) + } + public func add(index: IndexDefinition) { indexDefinitions.append(index) } @@ -146,6 +154,13 @@ public class SchemaChanger: CustomStringConvertible { .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) ] + indexDefinitions.map { .addIndex($0) } } + + private func columnName(for expression: Expression) -> String { + switch LiteralValue(expression.template) { + case .stringLiteral(let string): return string + default: fatalError("expression is not a literal string value") + } + } } private let connection: Connection @@ -331,3 +346,13 @@ extension TableDefinition { } } } + +extension ColumnDefinition.Affinity { + init(expression: Expression) where T: Value { + self.init(T.declaredDatatype) + } + + init(expression: Expression) where T: Value { + self.init(T.declaredDatatype) + } +} diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index bba16e08..6b53c9ce 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -201,6 +201,36 @@ class SchemaChangerTests: SQLiteTestCase { ]) } + func test_create_table_add_column_expression() throws { + try schemaChanger.create(table: "foo") { table in + table.add(expression: Expression("name")) + table.add(expression: Expression("age")) + table.add(expression: Expression("salary")) + } + + let columns = try schema.columnDefinitions(table: "foo") + XCTAssertEqual(columns, [ + ColumnDefinition(name: "name", + primaryKey: nil, + type: .TEXT, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "age", + primaryKey: nil, + type: .INTEGER, + nullable: false, + defaultValue: .NULL, + references: nil), + ColumnDefinition(name: "salary", + primaryKey: nil, + type: .REAL, + nullable: true, + defaultValue: .NULL, + references: nil) + ]) + } + func test_create_table_if_not_exists() throws { try schemaChanger.create(table: "foo") { table in table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) From cdaade15b93a43a5db8038da05da1c30c997ea3d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 18:45:26 +0200 Subject: [PATCH 1011/1046] Respect ifNotExists for index --- Sources/SQLite/Schema/SchemaChanger.swift | 12 ++++-------- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index d3342032..13222dab 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -43,7 +43,7 @@ public class SchemaChanger: CustomStringConvertible { public enum Operation { case addColumn(ColumnDefinition) - case addIndex(IndexDefinition) + case addIndex(IndexDefinition, ifNotExists: Bool) case dropColumn(String) case renameColumn(String, String) case renameTable(String) @@ -54,12 +54,8 @@ public class SchemaChanger: CustomStringConvertible { switch self { case .addColumn(let definition): return "ALTER TABLE \(table.quote()) ADD COLUMN \(definition.toSQL())" - case .addIndex(let definition): - let unique = definition.unique ? "UNIQUE" : "" - let columns = definition.columns.joined(separator: ", ") - let `where` = definition.where.map { " WHERE " + $0 } ?? "" - - return "CREATE \(unique) INDEX \(definition.name) ON \(definition.table) (\(columns)) \(`where`)" + case .addIndex(let definition, let ifNotExists): + return definition.toSQL(ifNotExists: ifNotExists) case .renameColumn(let from, let to) where SQLiteFeature.renameColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): @@ -152,7 +148,7 @@ public class SchemaChanger: CustomStringConvertible { precondition(!columnDefinitions.isEmpty) return [ .createTable(columns: columnDefinitions, ifNotExists: ifNotExists) - ] + indexDefinitions.map { .addIndex($0) } + ] + indexDefinitions.map { .addIndex($0, ifNotExists: ifNotExists) } } private func columnName(for expression: Expression) -> String { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 6b53c9ce..02a82267 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -252,4 +252,18 @@ class SchemaChangerTests: SQLiteTestCase { } } } + + func test_create_table_if_not_exists_with_index() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "name", type: .TEXT)) + table.add(index: .init(table: "foo", name: "name_index", unique: true, columns: ["name"], indexSQL: nil)) + } + + // ifNotExists needs to apply to index creation as well + try schemaChanger.create(table: "foo", ifNotExists: true) { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(index: .init(table: "foo", name: "name_index", unique: true, columns: ["name"], indexSQL: nil)) + } + } } From a0136544e9627b941008d7c59836b61feaf1f142 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 22:17:39 +0200 Subject: [PATCH 1012/1046] New visionOS0-compatible pod has been release --- run-tests.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 7d5c26d2..3ffba810 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -12,9 +12,6 @@ elif [ -n "$VALIDATOR_SUBSPEC" ]; then none) bundle exec pod lib lint --no-subspecs --fail-fast ;; - standalone) - bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast --platforms=macos,ios,tvos,watchos - ;; *) bundle exec pod lib lint --subspec="${VALIDATOR_SUBSPEC}" --fail-fast ;; From 77b493e661cff1498892dbaa48aa2205b673ce46 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 22:42:20 +0200 Subject: [PATCH 1013/1046] Use inheritance --- SQLite.swift.podspec | 43 +++++-------------------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 184ce23e..4cbd91bb 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -18,17 +18,11 @@ Pod::Spec.new do |s| s.default_subspec = 'standard' s.swift_versions = ['5'] - ios_deployment_target = '12.0' - tvos_deployment_target = '12.0' - osx_deployment_target = '10.13' - watchos_deployment_target = '4.0' - visionos_deployment_target = '1.0' - - s.ios.deployment_target = ios_deployment_target - s.tvos.deployment_target = tvos_deployment_target - s.osx.deployment_target = osx_deployment_target - s.watchos.deployment_target = watchos_deployment_target - s.visionos.deployment_target = visionos_deployment_target + s.ios.deployment_target = '12.0' + s.tvos.deployment_target = '12.0' + s.osx.deployment_target = '10.13' + s.watchos.deployment_target = '4.0' + s.visionos.deployment_target = '1.0' s.subspec 'standard' do |ss| ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' @@ -36,18 +30,9 @@ Pod::Spec.new do |s| ss.library = 'sqlite3' ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } - ss.ios.deployment_target = ios_deployment_target - ss.tvos.deployment_target = tvos_deployment_target - ss.osx.deployment_target = osx_deployment_target - ss.watchos.deployment_target = watchos_deployment_target - ss.visionos.deployment_target = visionos_deployment_target - ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' - test_spec.ios.deployment_target = ios_deployment_target - test_spec.tvos.deployment_target = tvos_deployment_target - test_spec.osx.deployment_target = osx_deployment_target end end @@ -62,18 +47,9 @@ Pod::Spec.new do |s| } ss.dependency 'sqlite3' - ss.ios.deployment_target = ios_deployment_target - ss.tvos.deployment_target = tvos_deployment_target - ss.osx.deployment_target = osx_deployment_target - ss.watchos.deployment_target = watchos_deployment_target - ss.visionos.deployment_target = visionos_deployment_target - ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' - test_spec.ios.deployment_target = ios_deployment_target - test_spec.tvos.deployment_target = tvos_deployment_target - test_spec.osx.deployment_target = osx_deployment_target end end @@ -87,18 +63,9 @@ Pod::Spec.new do |s| } ss.dependency 'SQLCipher', '>= 4.0.0' - ss.ios.deployment_target = ios_deployment_target - ss.tvos.deployment_target = tvos_deployment_target - ss.osx.deployment_target = osx_deployment_target - ss.watchos.deployment_target = watchos_deployment_target - #ss.visionos.deployment_target = visionos_deployment_target # Not supported by SQLCipher for now - ss.test_spec 'tests' do |test_spec| test_spec.resources = 'Tests/SQLiteTests/Resources/*' test_spec.source_files = 'Tests/SQLiteTests/*.swift' - test_spec.ios.deployment_target = ios_deployment_target - test_spec.tvos.deployment_target = tvos_deployment_target - test_spec.osx.deployment_target = osx_deployment_target end end end From 7309b4337a0649d53d8ebd703f582117c1dc9e91 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 23:04:30 +0200 Subject: [PATCH 1014/1046] Use newer Xcode --- .github/workflows/build.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fbba1c5..5b6b74a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,15 +5,9 @@ env: IOS_VERSION: "17.2" jobs: build: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v2 - - name: "Select Xcode" - # Currently only works with Xcode 14.2: - # https://github.com/CocoaPods/CocoaPods/issues/11839 - run: | - xcode-select -p - sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer - name: "Lint" run: make lint - name: "Run tests (PACKAGE_MANAGER_COMMAND: test)" From 4166f86e844f5d1e87e25264eb9a5194dbaf3b08 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 23:10:36 +0200 Subject: [PATCH 1015/1046] iOS 17.5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b6b74a6..a0274321 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: "iPhone 15" - IOS_VERSION: "17.2" + IOS_VERSION: "17.5" jobs: build: runs-on: macos-15 From f312ab4d12b344f7e541df5cb0363070a0f964ba Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Wed, 21 May 2025 23:29:27 +0200 Subject: [PATCH 1016/1046] Update bundler --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 464ef06b..9f71603b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,7 +67,7 @@ GEM colored2 (3.1.2) concurrent-ruby (1.3.5) connection_pool (2.5.3) - drb (2.2.1) + drb (2.2.3) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) @@ -113,4 +113,4 @@ DEPENDENCIES cocoapods BUNDLED WITH - 2.5.4 + 2.6.9 From 9dc478c9f1f6eaae43f1aa4286e3bf574bb5976d Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 10:02:48 +0200 Subject: [PATCH 1017/1046] SchemaReader: parse and create unique constraints --- Sources/SQLite/Schema/SchemaDefinitions.swift | 27 ++++++++-- Sources/SQLite/Schema/SchemaReader.swift | 52 ++++++++++++++----- .../Schema/SchemaChangerTests.swift | 9 ++-- .../Schema/SchemaReaderTests.swift | 32 ++++++++++-- 4 files changed, 98 insertions(+), 22 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index 897f8557..bb4ce82b 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -128,6 +128,7 @@ public struct ColumnDefinition: Equatable { public let primaryKey: PrimaryKey? public let type: Affinity public let nullable: Bool + public let unique: Bool public let defaultValue: LiteralValue public let references: ForeignKey? @@ -135,12 +136,14 @@ public struct ColumnDefinition: Equatable { primaryKey: PrimaryKey? = nil, type: Affinity, nullable: Bool = true, + unique: Bool = false, defaultValue: LiteralValue = .NULL, references: ForeignKey? = nil) { self.name = name self.primaryKey = primaryKey self.type = type self.nullable = nullable + self.unique = unique self.defaultValue = defaultValue self.references = references } @@ -244,16 +247,18 @@ public struct IndexDefinition: Equatable { public enum Order: String { case ASC, DESC } - public init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, orders: [String: Order]? = nil) { + public init(table: String, name: String, unique: Bool = false, columns: [String], `where`: String? = nil, + orders: [String: Order]? = nil, origin: Origin? = nil) { self.table = table self.name = name self.unique = unique self.columns = columns self.where = `where` self.orders = orders + self.origin = origin } - init (table: String, name: String, unique: Bool, columns: [String], indexSQL: String?) { + init (table: String, name: String, unique: Bool, columns: [String], indexSQL: String?, origin: Origin? = nil) { func wherePart(sql: String) -> String? { IndexDefinition.whereRe.firstMatch(in: sql, options: [], range: NSRange(location: 0, length: sql.count)).map { (sql as NSString).substring(with: $0.range(at: 1)) @@ -278,7 +283,8 @@ public struct IndexDefinition: Equatable { unique: unique, columns: columns, where: indexSQL.flatMap(wherePart), - orders: (orders?.isEmpty ?? false) ? nil : orders) + orders: (orders?.isEmpty ?? false) ? nil : orders, + origin: origin) } public let table: String @@ -287,6 +293,13 @@ public struct IndexDefinition: Equatable { public let columns: [String] public let `where`: String? public let orders: [String: Order]? + public let origin: Origin? + + public enum Origin: String { + case uniqueConstraint = "u" // index created from a "CREATE TABLE (... UNIQUE)" column constraint + case createIndex = "c" // index created explicitly via "CREATE INDEX ..." + case primaryKey = "pk" // index created from a "CREATE TABLE PRIMARY KEY" column constraint + } enum IndexError: LocalizedError { case tooLong(String, String) @@ -300,6 +313,13 @@ public struct IndexDefinition: Equatable { } } + // Indices with names of the form "sqlite_autoindex_TABLE_N" that are used to implement UNIQUE and PRIMARY KEY + // constraints on ordinary tables. + // https://sqlite.org/fileformat2.html#intschema + var isInternal: Bool { + name.starts(with: "sqlite_autoindex_") + } + func validate() throws { if name.count > IndexDefinition.maxIndexLength { throw IndexError.tooLong(name, table) @@ -348,6 +368,7 @@ extension ColumnDefinition { defaultValue.map { "DEFAULT \($0)" }, primaryKey.map { $0.toSQL() }, nullable ? nil : "NOT NULL", + unique ? "UNIQUE" : nil, references.map { $0.toSQL() } ].compactMap { $0 } .joined(separator: " ") diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index 1989ad6f..a67fb94f 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -21,7 +21,7 @@ public class SchemaReader { let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = Dictionary(grouping: try foreignKeys(table: table), by: { $0.column }) - return try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") + let columnDefinitions = try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") .map { (row: Row) -> ColumnDefinition in ColumnDefinition( name: row[TableInfoTable.nameColumn], @@ -29,10 +29,27 @@ public class SchemaReader { try parsePrimaryKey(column: row[TableInfoTable.nameColumn]) : nil, type: ColumnDefinition.Affinity(row[TableInfoTable.typeColumn]), nullable: row[TableInfoTable.notNullColumn] == 0, + unique: false, defaultValue: LiteralValue(row[TableInfoTable.defaultValueColumn]), references: foreignKeys[row[TableInfoTable.nameColumn]]?.first ) } + + let internalIndexes = try indexDefinitions(table: table).filter { $0.isInternal } + return columnDefinitions.map { definition in + if let index = internalIndexes.first(where: { $0.columns.contains(definition.name) }), index.origin == .uniqueConstraint { + + ColumnDefinition(name: definition.name, + primaryKey: definition.primaryKey, + type: definition.type, + nullable: definition.nullable, + unique: true, + defaultValue: definition.defaultValue, + references: definition.references) + } else { + definition + } + } } public func objectDefinitions(name: String? = nil, @@ -66,27 +83,26 @@ public class SchemaReader { .first } - func columns(name: String) throws -> [String] { + func indexInfos(name: String) throws -> [IndexInfo] { try connection.prepareRowIterator("PRAGMA index_info(\(name.quote()))") .compactMap { row in - row[IndexInfoTable.nameColumn] + IndexInfo(name: row[IndexInfoTable.nameColumn], + columnRank: row[IndexInfoTable.seqnoColumn], + columnRankWithinTable: row[IndexInfoTable.cidColumn]) + } } return try connection.prepareRowIterator("PRAGMA index_list(\(table.quote()))") .compactMap { row -> IndexDefinition? in let name = row[IndexListTable.nameColumn] - guard !name.starts(with: "sqlite_") else { - // Indexes SQLite creates implicitly for internal use start with "sqlite_". - // See https://www.sqlite.org/fileformat2.html#intschema - return nil - } return IndexDefinition( table: table, name: name, unique: row[IndexListTable.uniqueColumn] == 1, - columns: try columns(name: name), - indexSQL: try indexSQL(name: name) + columns: try indexInfos(name: name).compactMap { $0.name }, + indexSQL: try indexSQL(name: name), + origin: IndexDefinition.Origin(rawValue: row[IndexListTable.originColumn]) ) } } @@ -123,6 +139,15 @@ public class SchemaReader { objectDefinitions(name: name, type: .table, temp: true) ).compactMap(\.sql).first } + + struct IndexInfo { + let name: String? + // The rank of the column within the index. (0 means left-most.) + let columnRank: Int + // The rank of the column within the table being indexed. + // A value of -1 means rowid and a value of -2 means that an expression is being used + let columnRankWithinTable: Int + } } private enum SchemaTable { @@ -159,11 +184,12 @@ private enum TableInfoTable { private enum IndexInfoTable { // The rank of the column within the index. (0 means left-most.) - static let seqnoColumn = Expression("seqno") + static let seqnoColumn = Expression("seqno") // The rank of the column within the table being indexed. // A value of -1 means rowid and a value of -2 means that an expression is being used. - static let cidColumn = Expression("cid") - // The name of the column being indexed. This columns is NULL if the column is the rowid or an expression. + static let cidColumn = Expression("cid") + // The name of the column being indexed. + // This columns is NULL if the column is the rowid or an expression. static let nameColumn = Expression("name") } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 02a82267..724c2d0c 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -158,7 +158,7 @@ class SchemaChangerTests: SQLiteTestCase { func test_create_table() throws { try schemaChanger.create(table: "foo") { table in table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) - table.add(column: .init(name: "name", type: .TEXT, nullable: false)) + table.add(column: .init(name: "name", type: .TEXT, nullable: false, unique: true)) table.add(column: .init(name: "age", type: .INTEGER)) table.add(index: .init(table: table.name, @@ -179,25 +179,28 @@ class SchemaChangerTests: SQLiteTestCase { primaryKey: .init(autoIncrement: true, onConflict: nil), type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "name", primaryKey: nil, type: .TEXT, nullable: false, + unique: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "age", primaryKey: nil, type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil) ]) - let indexes = try schema.indexDefinitions(table: "foo") + let indexes = try schema.indexDefinitions(table: "foo").filter { !$0.isInternal } XCTAssertEqual(indexes, [ - IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil) + IndexDefinition(table: "foo", name: "nameIndex", unique: true, columns: ["name"], where: nil, orders: nil, origin: .createIndex) ]) } diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 8c033e41..01047c9c 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -18,41 +18,48 @@ class SchemaReaderTests: SQLiteTestCase { primaryKey: .init(autoIncrement: false, onConflict: nil), type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "email", primaryKey: nil, type: .TEXT, nullable: false, + unique: true, defaultValue: .NULL, references: nil), ColumnDefinition(name: "age", primaryKey: nil, type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "salary", primaryKey: nil, type: .REAL, nullable: true, + unique: false, defaultValue: .NULL, references: nil), ColumnDefinition(name: "admin", primaryKey: nil, type: .NUMERIC, nullable: false, + unique: false, defaultValue: .numericLiteral("0"), references: nil), ColumnDefinition(name: "manager_id", primaryKey: nil, type: .INTEGER, nullable: true, + unique: false, defaultValue: .NULL, references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, type: .NUMERIC, nullable: true, + unique: false, defaultValue: .NULL, references: nil) ]) @@ -68,6 +75,24 @@ class SchemaReaderTests: SQLiteTestCase { primaryKey: .init(autoIncrement: true, onConflict: .IGNORE), type: .INTEGER, nullable: true, + unique: false, + defaultValue: .NULL, + references: nil) + ] + ) + } + + func test_columnDefinitions_parses_unique() throws { + try db.run("CREATE TABLE t (name TEXT UNIQUE)") + + let columns = try schemaReader.columnDefinitions(table: "t") + XCTAssertEqual(columns, [ + ColumnDefinition( + name: "name", + primaryKey: nil, + type: .TEXT, + nullable: true, + unique: true, defaultValue: .NULL, references: nil) ] @@ -128,13 +153,13 @@ class SchemaReaderTests: SQLiteTestCase { } func test_indexDefinitions_no_index() throws { - let indexes = try schemaReader.indexDefinitions(table: "users") + let indexes = try schemaReader.indexDefinitions(table: "users").filter { !$0.isInternal } XCTAssertTrue(indexes.isEmpty) } func test_indexDefinitions_with_index() throws { try db.run("CREATE UNIQUE INDEX index_users ON users (age DESC) WHERE age IS NOT NULL") - let indexes = try schemaReader.indexDefinitions(table: "users") + let indexes = try schemaReader.indexDefinitions(table: "users").filter { !$0.isInternal } XCTAssertEqual(indexes, [ IndexDefinition( @@ -143,7 +168,8 @@ class SchemaReaderTests: SQLiteTestCase { unique: true, columns: ["age"], where: "age IS NOT NULL", - orders: ["age": .DESC] + orders: ["age": .DESC], + origin: .createIndex ) ]) } From 374d99da2107656adf12670feea4f167853321d9 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 10:53:36 +0200 Subject: [PATCH 1018/1046] Use cocoapods with watchOS fix --- Gemfile | 2 +- Gemfile.lock | 46 ++++++++++++++++++++++++++-------------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 1f2ebb87..2bb803ca 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem 'cocoapods' +gem 'cocoapods', :git => 'https://github.com/jberkel/CocoaPods.git', branch: 'watchos-fourflusher' diff --git a/Gemfile.lock b/Gemfile.lock index 9f71603b..20ed2aad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,27 @@ +GIT + remote: https://github.com/jberkel/CocoaPods.git + revision: 899f273f298ea20de2378687ea55331004b39371 + branch: watchos-fourflusher + specs: + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (~> 4.1.0) + xcodeproj (>= 1.27.0, < 2.0) + GEM remote: https://rubygems.org/ specs: @@ -27,24 +51,6 @@ GEM benchmark (0.4.0) bigdecimal (3.1.9) claide (1.1.0) - cocoapods (1.16.2) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.16.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.27.0, < 2.0) cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) @@ -91,7 +97,7 @@ GEM nkf (0.2.0) public_suffix (4.0.7) rexml (3.4.1) - ruby-macho (2.5.1) + ruby-macho (4.1.0) securerandom (0.4.1) typhoeus (1.4.1) ethon (>= 0.9.0) @@ -110,7 +116,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods + cocoapods! BUNDLED WITH 2.6.9 From 108431244de611050f25c20efd02bcfe68687b28 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 11:52:52 +0200 Subject: [PATCH 1019/1046] Disable visionOS for SQLCipher --- SQLite.swift.podspec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 4cbd91bb..d0ccb136 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -54,6 +54,13 @@ Pod::Spec.new do |s| end s.subspec 'SQLCipher' do |ss| + # Disable unsupported visionOS + # https://github.com/sqlcipher/sqlcipher/issues/483 + ss.ios.deployment_target = s.deployment_target(:ios) + ss.tvos.deployment_target = s.deployment_target(:tvos) + ss.osx.deployment_target = s.deployment_target(:osx) + ss.watchos.deployment_target = s.deployment_target(:watchos) + ss.source_files = 'Sources/SQLite/**/*.{c,h,m,swift}' ss.resource_bundle = { 'SQLite.swift' => 'Sources/SQLite/PrivacyInfo.xcprivacy' } From 718c7988780f803ede87c03ef2cdce58ea762656 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 12:21:19 +0200 Subject: [PATCH 1020/1046] Add comment --- Gemfile | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 2bb803ca..2770e85d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ source "https://rubygems.org" +# https://github.com/CocoaPods/CocoaPods/pull/12816 gem 'cocoapods', :git => 'https://github.com/jberkel/CocoaPods.git', branch: 'watchos-fourflusher' diff --git a/Gemfile.lock b/Gemfile.lock index 20ed2aad..7dac2c18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/jberkel/CocoaPods.git - revision: 899f273f298ea20de2378687ea55331004b39371 + revision: 32a90c184bc5dc9ec8b7b9b8ad08e98b7253dec2 branch: watchos-fourflusher specs: cocoapods (1.16.2) From c812caf9c432bc196f87648b56551dff2c0017e3 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 13:30:36 +0200 Subject: [PATCH 1021/1046] Make ForeignKey public, rename fields for clarity --- Sources/SQLite/Schema/SchemaDefinitions.swift | 23 ++++++++++--- Sources/SQLite/Schema/SchemaReader.swift | 8 ++--- .../Schema/SchemaChangerTests.swift | 34 +++++++++++++++++++ .../Schema/SchemaDefinitionsTests.swift | 8 ++--- .../Schema/SchemaReaderTests.swift | 4 +-- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaDefinitions.swift b/Sources/SQLite/Schema/SchemaDefinitions.swift index bb4ce82b..d7803999 100644 --- a/Sources/SQLite/Schema/SchemaDefinitions.swift +++ b/Sources/SQLite/Schema/SchemaDefinitions.swift @@ -117,11 +117,24 @@ public struct ColumnDefinition: Equatable { } public struct ForeignKey: Equatable { - let table: String - let column: String - let primaryKey: String? + let fromColumn: String + let toTable: String + // when null, use primary key of "toTable" + let toColumn: String? let onUpdate: String? let onDelete: String? + + public init(toTable: String, toColumn: String? = nil, onUpdate: String? = nil, onDelete: String? = nil) { + self.init(fromColumn: "", toTable: toTable, toColumn: toColumn, onUpdate: onUpdate, onDelete: onDelete) + } + + public init(fromColumn: String, toTable: String, toColumn: String? = nil, onUpdate: String? = nil, onDelete: String? = nil) { + self.fromColumn = fromColumn + self.toTable = toTable + self.toColumn = toColumn + self.onUpdate = onUpdate + self.onDelete = onDelete + } } public let name: String @@ -400,8 +413,8 @@ extension ColumnDefinition.ForeignKey { func toSQL() -> String { ([ "REFERENCES", - table.quote(), - primaryKey.map { "(\($0.quote()))" }, + toTable.quote(), + toColumn.map { "(\($0.quote()))" }, onUpdate.map { "ON UPDATE \($0)" }, onDelete.map { "ON DELETE \($0)" } ] as [String?]).compactMap { $0 } diff --git a/Sources/SQLite/Schema/SchemaReader.swift b/Sources/SQLite/Schema/SchemaReader.swift index a67fb94f..995cd4d7 100644 --- a/Sources/SQLite/Schema/SchemaReader.swift +++ b/Sources/SQLite/Schema/SchemaReader.swift @@ -19,7 +19,7 @@ public class SchemaReader { } let foreignKeys: [String: [ColumnDefinition.ForeignKey]] = - Dictionary(grouping: try foreignKeys(table: table), by: { $0.column }) + Dictionary(grouping: try foreignKeys(table: table), by: { $0.fromColumn }) let columnDefinitions = try connection.prepareRowIterator("PRAGMA table_info(\(table.quote()))") .map { (row: Row) -> ColumnDefinition in @@ -111,9 +111,9 @@ public class SchemaReader { try connection.prepareRowIterator("PRAGMA foreign_key_list(\(table.quote()))") .map { row in ColumnDefinition.ForeignKey( - table: row[ForeignKeyListTable.tableColumn], - column: row[ForeignKeyListTable.fromColumn], - primaryKey: row[ForeignKeyListTable.toColumn], + fromColumn: row[ForeignKeyListTable.fromColumn], + toTable: row[ForeignKeyListTable.tableColumn], + toColumn: row[ForeignKeyListTable.toColumn], onUpdate: row[ForeignKeyListTable.onUpdateColumn] == TableBuilder.Dependency.noAction.rawValue ? nil : row[ForeignKeyListTable.onUpdateColumn], onDelete: row[ForeignKeyListTable.onDeleteColumn] == TableBuilder.Dependency.noAction.rawValue diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 724c2d0c..b08344fc 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -269,4 +269,38 @@ class SchemaChangerTests: SQLiteTestCase { table.add(index: .init(table: "foo", name: "name_index", unique: true, columns: ["name"], indexSQL: nil)) } } + + func test_create_table_with_foreign_key_reference() throws { + try schemaChanger.create(table: "foo") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + } + + try schemaChanger.create(table: "bars") { table in + table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) + table.add(column: .init(name: "foo_id", + type: .INTEGER, + nullable: false, + references: .init(toTable: "foo", toColumn: "id"))) + } + + let barColumns = try schema.columnDefinitions(table: "bars") + + XCTAssertEqual([ + ColumnDefinition(name: "id", + primaryKey: .init(autoIncrement: true, onConflict: nil), + type: .INTEGER, + nullable: true, + unique: false, + defaultValue: .NULL, + references: nil), + + ColumnDefinition(name: "foo_id", + primaryKey: nil, + type: .INTEGER, + nullable: false, + unique: false, + defaultValue: .NULL, + references: .init(fromColumn: "foo_id", toTable: "foo", toColumn: "id", onUpdate: nil, onDelete: nil)) + ], barColumns) + } } diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 8b7e27e3..1c648bd8 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -11,7 +11,7 @@ class ColumnDefinitionTests: XCTestCase { ("\"other_id\" INTEGER NOT NULL REFERENCES \"other_table\" (\"some_id\")", ColumnDefinition(name: "other_id", primaryKey: nil, type: .INTEGER, nullable: false, defaultValue: .NULL, - references: .init(table: "other_table", column: "", primaryKey: "some_id", onUpdate: nil, onDelete: nil))), + references: .init(fromColumn: "", toTable: "other_table", toColumn: "some_id", onUpdate: nil, onDelete: nil))), ("\"text\" TEXT", ColumnDefinition(name: "text", primaryKey: nil, type: .TEXT, nullable: true, defaultValue: .NULL, references: nil)), @@ -245,9 +245,9 @@ class ForeignKeyDefinitionTests: XCTestCase { func test_toSQL() { XCTAssertEqual( ColumnDefinition.ForeignKey( - table: "foo", - column: "bar", - primaryKey: "bar_id", + fromColumn: "bar", + toTable: "foo", + toColumn: "bar_id", onUpdate: nil, onDelete: "SET NULL" ).toSQL(), """ diff --git a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift index 01047c9c..8963070f 100644 --- a/Tests/SQLiteTests/Schema/SchemaReaderTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaReaderTests.swift @@ -54,7 +54,7 @@ class SchemaReaderTests: SQLiteTestCase { nullable: true, unique: false, defaultValue: .NULL, - references: .init(table: "users", column: "manager_id", primaryKey: "id", onUpdate: nil, onDelete: nil)), + references: .init(fromColumn: "manager_id", toTable: "users", toColumn: "id", onUpdate: nil, onDelete: nil)), ColumnDefinition(name: "created_at", primaryKey: nil, type: .NUMERIC, @@ -194,7 +194,7 @@ class SchemaReaderTests: SQLiteTestCase { let foreignKeys = try schemaReader.foreignKeys(table: "test_links") XCTAssertEqual(foreignKeys, [ - .init(table: "users", column: "test_id", primaryKey: "id", onUpdate: nil, onDelete: nil) + .init(fromColumn: "test_id", toTable: "users", toColumn: "id", onUpdate: nil, onDelete: nil) ]) } From 1c440766b22019190a9c78731f0c69b78f5d177b Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:06:41 +0200 Subject: [PATCH 1022/1046] Add drop(index:) --- Sources/SQLite/Schema/SchemaChanger.swift | 10 ++++- .../Schema/SchemaChangerTests.swift | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 13222dab..7ba458c1 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -45,6 +45,7 @@ public class SchemaChanger: CustomStringConvertible { case addColumn(ColumnDefinition) case addIndex(IndexDefinition, ifNotExists: Bool) case dropColumn(String) + case dropIndex(String, ifExists: Bool) case renameColumn(String, String) case renameTable(String) case createTable(columns: [ColumnDefinition], ifNotExists: Bool) @@ -60,6 +61,8 @@ public class SchemaChanger: CustomStringConvertible { return "ALTER TABLE \(table.quote()) RENAME COLUMN \(from.quote()) TO \(to.quote())" case .dropColumn(let column) where SQLiteFeature.dropColumn.isSupported(by: version): return "ALTER TABLE \(table.quote()) DROP COLUMN \(column.quote())" + case .dropIndex(let name, let ifExists): + return "DROP INDEX \(ifExists ? " IF EXISTS " : "") \(name.quote())" case .createTable(let columns, let ifNotExists): return "CREATE TABLE \(ifNotExists ? " IF NOT EXISTS " : "") \(table.quote()) (" + columns.map { $0.toSQL() }.joined(separator: ", ") + @@ -111,6 +114,10 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.dropColumn(column)) } + public func drop(index: String, ifExists: Bool = false) { + operations.append(.dropIndex(index, ifExists: ifExists)) + } + public func rename(column: String, to: String) { operations.append(.renameColumn(column, to)) } @@ -324,8 +331,9 @@ extension TableDefinition { func apply(_ operation: SchemaChanger.Operation?) -> TableDefinition { switch operation { case .none: return self - case .createTable, .addIndex: fatalError() + case .createTable, .addIndex, .dropIndex: fatalError() case .addColumn: fatalError("Use 'ALTER TABLE ADD COLUMN (...)'") + case .dropColumn(let column): return TableDefinition(name: name, columns: columns.filter { $0.name != column }, diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index b08344fc..75331a97 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -124,6 +124,44 @@ class SchemaChangerTests: SQLiteTestCase { } } + func test_drop_index() throws { + try db.execute(""" + CREATE INDEX age_index ON users(age) + """) + + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index") + } + let indexes = try schema.indexDefinitions(table: "users").filter { !$0.isInternal } + XCTAssertEqual(0, indexes.count) + } + + func test_drop_index_if_exists() throws { + try db.execute(""" + CREATE INDEX age_index ON users(age) + """) + + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index") + } + + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index", ifExists: true) + } + + XCTAssertThrowsError( + try schemaChanger.alter(table: "users") { table in + table.drop(index: "age_index", ifExists: false) + } + ) { error in + if case Result.error(let message, _, _) = error { + XCTAssertEqual(message, "no such index: age_index") + } else { + XCTFail("unexpected error \(error)") + } + } + } + func test_drop_table() throws { try schemaChanger.drop(table: "users") XCTAssertThrowsError(try db.scalar(users.count)) { error in From 08bed022dc12289d64278cc5ad2e9a63868fb12f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:23:02 +0200 Subject: [PATCH 1023/1046] Implement add(index:) --- Sources/SQLite/Schema/SchemaChanger.swift | 4 +++ .../Schema/SchemaChangerTests.swift | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 7ba458c1..3badb8d8 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -110,6 +110,10 @@ public class SchemaChanger: CustomStringConvertible { operations.append(.addColumn(column)) } + public func add(index: IndexDefinition, ifNotExists: Bool = false) { + operations.append(.addIndex(index, ifNotExists: ifNotExists)) + } + public func drop(column: String) { operations.append(.dropColumn(column)) } diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index 75331a97..dafdea54 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -124,6 +124,40 @@ class SchemaChangerTests: SQLiteTestCase { } } + func test_add_index() throws { + try schemaChanger.alter(table: "users") { table in + table.add(index: .init(table: table.name, name: "age_index", unique: false, columns: ["age"], indexSQL: nil)) + } + + let indexes = try schema.indexDefinitions(table: "users").filter { !$0.isInternal } + XCTAssertEqual([ + IndexDefinition(table: "users", + name: "age_index", + unique: false, + columns: ["age"], + where: nil, + orders: nil, + origin: .createIndex) + ], indexes) + } + + func test_add_index_if_not_exists() throws { + let index = IndexDefinition(table: "users", name: "age_index", unique: false, columns: ["age"], indexSQL: nil) + try schemaChanger.alter(table: "users") { table in + table.add(index: index) + } + + try schemaChanger.alter(table: "users") { table in + table.add(index: index, ifNotExists: true) + } + + XCTAssertThrowsError( + try schemaChanger.alter(table: "users") { table in + table.add(index: index, ifNotExists: false) + } + ) + } + func test_drop_index() throws { try db.execute(""" CREATE INDEX age_index ON users(age) From 3def7d60f70b109e0f74f6d42bac61d631de56dd Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:45:33 +0200 Subject: [PATCH 1024/1046] Make name public, expose plain SQL run --- Sources/SQLite/Schema/SchemaChanger.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 3badb8d8..3e146df7 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -100,7 +100,7 @@ public class SchemaChanger: CustomStringConvertible { public class AlterTableDefinition { fileprivate var operations: [Operation] = [] - let name: String + public let name: String init(name: String) { self.name = name @@ -223,6 +223,11 @@ public class SchemaChanger: CustomStringConvertible { try connection.run("ALTER TABLE \(table.quote()) RENAME TO \(to.quote())") } + // Runs arbitrary SQL. Should only be used if no predefined operations exist. + public func run(sql: String) throws { + try connection.run(sql) + } + private func run(table: String, operation: Operation) throws { try operation.validate() From 25bd06392f65d7ca75ffc481471de05905ec1c79 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 22 May 2025 17:53:46 +0200 Subject: [PATCH 1025/1046] Better API, test --- Sources/SQLite/Schema/SchemaChanger.swift | 5 +++-- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/SQLite/Schema/SchemaChanger.swift b/Sources/SQLite/Schema/SchemaChanger.swift index 3e146df7..6fae532a 100644 --- a/Sources/SQLite/Schema/SchemaChanger.swift +++ b/Sources/SQLite/Schema/SchemaChanger.swift @@ -224,8 +224,9 @@ public class SchemaChanger: CustomStringConvertible { } // Runs arbitrary SQL. Should only be used if no predefined operations exist. - public func run(sql: String) throws { - try connection.run(sql) + @discardableResult + public func run(_ sql: String, _ bindings: Binding?...) throws -> Statement { + return try connection.run(sql, bindings) } private func run(table: String, operation: Operation) throws { diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index dafdea54..d942fba4 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -375,4 +375,9 @@ class SchemaChangerTests: SQLiteTestCase { references: .init(fromColumn: "foo_id", toTable: "foo", toColumn: "id", onUpdate: nil, onDelete: nil)) ], barColumns) } + + func test_run_arbitrary_sql() throws { + try schemaChanger.run("DROP TABLE users") + XCTAssertEqual(0, try schema.objectDefinitions(name: "users", type: .table).count) + } } From 8938697549dccc21803c5cbef9f2e5fec7ee274a Mon Sep 17 00:00:00 2001 From: ha100 Date: Sun, 8 Jun 2025 05:38:36 +0200 Subject: [PATCH 1026/1046] change CSQLite dep to SwiftToolchainCSQLite to allow swift sdk cross compilation --- Package.swift | 69 +++++++++++-------- Sources/SQLite/Core/Backup.swift | 2 +- .../SQLite/Core/Connection+Aggregation.swift | 2 +- Sources/SQLite/Core/Connection+Attach.swift | 2 +- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- .../Core/Connection+AttachTests.swift | 2 +- .../Core/Connection+PragmaTests.swift | 2 +- Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- Tests/SQLiteTests/Core/ResultTests.swift | 2 +- Tests/SQLiteTests/Core/StatementTests.swift | 2 +- .../Extensions/FTSIntegrationTests.swift | 2 +- .../Typed/CustomAggregationTests.swift | 2 +- .../Typed/QueryIntegrationTests.swift | 2 +- Tests/SQLiteTests/Typed/QueryTests.swift | 2 +- 17 files changed, 56 insertions(+), 45 deletions(-) diff --git a/Package.swift b/Package.swift index 238661ae..690d41e2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,6 +1,38 @@ // swift-tools-version:5.9 import PackageDescription +let deps: [Package.Dependency] = [ + .github("swiftlang/swift-toolchain-sqlite", exact: "1.0.4") +] + +let targets: [Target] = [ + .target( + name: "SQLite", + dependencies: [ + .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite") + ], + exclude: [ + "Info.plist" + ] + ) +] + +let testTargets: [Target] = [ + .testTarget( + name: "SQLiteTests", + dependencies: [ + "SQLite" + ], + path: "Tests/SQLiteTests", + exclude: [ + "Info.plist" + ], + resources: [ + .copy("Resources") + ] + ) +] + let package = Package( name: "SQLite.swift", platforms: [ @@ -16,34 +48,13 @@ let package = Package( targets: ["SQLite"] ) ], - targets: [ - .target( - name: "SQLite", - exclude: [ - "Info.plist" - ] - ), - .testTarget( - name: "SQLiteTests", - dependencies: [ - "SQLite" - ], - path: "Tests/SQLiteTests", - exclude: [ - "Info.plist" - ], - resources: [ - .copy("Resources") - ] - ) - ] + dependencies: deps, + targets: targets + testTargets ) -#if os(Linux) -package.dependencies = [ - .package(url: "https://github.com/stephencelis/CSQLite.git", from: "0.0.3") -] -package.targets.first?.dependencies += [ - .product(name: "CSQLite", package: "CSQLite") -] -#endif +extension Package.Dependency { + + static func github(_ repo: String, exact ver: Version) -> Package.Dependency { + .package(url: "https://github.com/\(repo)", exact: ver) + } +} diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 0eebbdd5..5e741ecd 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -29,7 +29,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index 4eea76c3..bfe253a7 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 8a25e51d..32461468 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index f2c3b781..188ff80b 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -29,7 +29,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index ee59e5d1..2659ad21 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -3,7 +3,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 6cb2e5d3..458e3d9c 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -27,7 +27,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index c27ccf05..00493f21 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -27,7 +27,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index f37300ca..68618596 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -7,7 +7,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index 2bcdb6af..bd29bd98 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -7,7 +7,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 6a1d94ae..61083453 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -8,7 +8,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index d3c8bb1f..b6a373f7 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -7,7 +7,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index dbf99d7c..ceaa8813 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -6,7 +6,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift index 1129ae08..8a34e93b 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 71cbba9c..8b7ec09a 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -8,7 +8,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 6be98ca0..899bf354 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index f018f097..7fc3dcf5 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -4,7 +4,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher #elseif os(Linux) -import CSQLite +import SwiftToolchainCSQLite #else import SQLite3 #endif From fc774a2585965a90f60aa034001e6d69aee42382 Mon Sep 17 00:00:00 2001 From: ha100 Date: Sun, 8 Jun 2025 07:08:47 +0200 Subject: [PATCH 1027/1046] fix macOS build via platform condition --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 690d41e2..48085178 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let targets: [Target] = [ .target( name: "SQLite", dependencies: [ - .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite") + .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows])) ], exclude: [ "Info.plist" From f3cb9105a80d566575c02bb77ac50e74e5895ccc Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 12:15:34 +0200 Subject: [PATCH 1028/1046] Fully qualify Expression --- Tests/SQLiteTests/Schema/SchemaChangerTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift index d942fba4..f5a6de42 100644 --- a/Tests/SQLiteTests/Schema/SchemaChangerTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaChangerTests.swift @@ -278,9 +278,9 @@ class SchemaChangerTests: SQLiteTestCase { func test_create_table_add_column_expression() throws { try schemaChanger.create(table: "foo") { table in - table.add(expression: Expression("name")) - table.add(expression: Expression("age")) - table.add(expression: Expression("salary")) + table.add(expression: SQLite.Expression("name")) + table.add(expression: SQLite.Expression("age")) + table.add(expression: SQLite.Expression("salary")) } let columns = try schema.columnDefinitions(table: "foo") From a7d7e8c0b7529f851fd8927071003095f1e87522 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 12:27:28 +0200 Subject: [PATCH 1029/1046] 17.5 no longer available --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0274321..33c4dba6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build and test on: [push, pull_request] env: IOS_SIMULATOR: "iPhone 15" - IOS_VERSION: "17.5" + IOS_VERSION: "18.0" jobs: build: runs-on: macos-15 From 716394d091618047dfe86b7da5106fb21ede412f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 12:34:42 +0200 Subject: [PATCH 1030/1046] iPhone 16 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33c4dba6..37f75476 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 15" + IOS_SIMULATOR: "iPhone 16" IOS_VERSION: "18.0" jobs: build: From 9831a45edb84d492cd5b68ba6f8d53300808499f Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Thu, 12 Jun 2025 13:17:56 +0200 Subject: [PATCH 1031/1046] Update Index.md --- Documentation/Index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bc62c791..bd2a7a77 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -1592,7 +1592,8 @@ let schemaChanger = SchemaChanger(connection: db) try schemaChanger.create(table: "users") { table in table.add(column: .init(name: "id", primaryKey: .init(autoIncrement: true), type: .INTEGER)) table.add(column: .init(name: "name", type: .TEXT, nullable: false)) -} +} +``` ### Indexes From e3a916637162ece77b0ffd89b9e758669adc62f0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 22:49:43 +0200 Subject: [PATCH 1032/1046] Update CHANGELOG for release --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c96724fd..b77a8b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +0.15.4 (13-06-2025), [diff][diff-0.15.4] +======================================== +* Fix cross compilation for linux on macOS fails ([#1317][]) +* Support creating tables in schema changer ([#1315][]) +* Update oldest supported platform versions ([#1280][]) +* Add CustomStringConvertible for Setter ([#1279][]) + 0.15.3 (19-04-2024), [diff][diff-0.15.3] ======================================== * Update `podspec` to include privacy manifest ([#1265][]) @@ -173,6 +180,8 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [diff-0.15.0]: https://github.com/stephencelis/SQLite.swift/compare/0.14.0...0.15.0 [diff-0.15.1]: https://github.com/stephencelis/SQLite.swift/compare/0.15.0...0.15.1 [diff-0.15.2]: https://github.com/stephencelis/SQLite.swift/compare/0.15.1...0.15.2 +[diff-0.15.3]: https://github.com/stephencelis/SQLite.swift/compare/0.15.2...0.15.3 +[diff-0.15.4]: https://github.com/stephencelis/SQLite.swift/compare/0.15.3...0.15.4 [#30]: https://github.com/stephencelis/SQLite.swift/issues/30 [#142]: https://github.com/stephencelis/SQLite.swift/issues/142 @@ -259,3 +268,7 @@ For breaking changes, see [Upgrading.md](Documentation/Upgrading.md). [#1237]: https://github.com/stephencelis/SQLite.swift/pull/1237 [#1260]: https://github.com/stephencelis/SQLite.swift/pull/1260 [#1265]: https://github.com/stephencelis/SQLite.swift/pull/1265 +[#1279]: https://github.com/stephencelis/SQLite.swift/pull/1279 +[#1280]: https://github.com/stephencelis/SQLite.swift/pull/1280 +[#1315]: https://github.com/stephencelis/SQLite.swift/pull/1315 +[#1317]: https://github.com/stephencelis/SQLite.swift/pull/1317 \ No newline at end of file From 02e055c99c4fa19604a95fea4e82fd085391fdf0 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 22:51:53 +0200 Subject: [PATCH 1033/1046] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a4d713b6..3e782f86 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Swift code. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4") ] ``` @@ -152,7 +152,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.3 + github "stephencelis/SQLite.swift" ~> 0.15.4 ``` 3. Run `carthage update` and @@ -183,7 +183,7 @@ SQLite.swift with CocoaPods: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.14.0' + pod 'SQLite.swift', '~> 0.15.0' end ``` From bbc5212c2ae9905d17ae581927c06e2cbad61ff7 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 22:53:36 +0200 Subject: [PATCH 1034/1046] Project looks dead --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3e782f86..db80fd3a 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,6 @@ Looking for something else? Try another Swift wrapper (or [FMDB][]): - [GRDB](https://github.com/groue/GRDB.swift) - [SQLiteDB](https://github.com/FahimF/SQLiteDB) - - [Squeal](https://github.com/nerdyc/Squeal) [Swift]: https://swift.org/ [SQLite3]: https://www.sqlite.org From 4e289bed6f1ed6737ffa963570beae3359a849e8 Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 23:00:44 +0200 Subject: [PATCH 1035/1046] No longer maintained --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index db80fd3a..0e83504f 100644 --- a/README.md +++ b/README.md @@ -226,8 +226,6 @@ device: ## Communication -[See the planning document] for a roadmap and existing feature requests. - [Read the contributing guidelines][]. The _TL;DR_ (but please; _R_): - Need **help** or have a **general question**? [Ask on Stack @@ -235,7 +233,6 @@ device: - Found a **bug** or have a **feature request**? [Open an issue][]. - Want to **contribute**? [Submit a pull request][]. -[See the planning document]: /Documentation/Planning.md [Read the contributing guidelines]: ./CONTRIBUTING.md#contributing [Ask on Stack Overflow]: https://stackoverflow.com/questions/tagged/sqlite.swift [Open an issue]: https://github.com/stephencelis/SQLite.swift/issues/new From 0f4f2c85c5beddeb70f02e1db36785b5f6e2dccf Mon Sep 17 00:00:00 2001 From: Jan Berkel Date: Fri, 13 Jun 2025 23:12:38 +0200 Subject: [PATCH 1036/1046] Bump version to 0.15.4 --- Documentation/Index.md | 12 ++++++------ SQLite.swift.podspec | 2 +- SQLite.xcodeproj/project.pbxproj | 12 ++---------- Tests/SPM/Package.swift | 2 +- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Documentation/Index.md b/Documentation/Index.md index bd2a7a77..0e8261f2 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -109,7 +109,7 @@ process of downloading, compiling, and linking dependencies. ```swift dependencies: [ - .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") + .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4") ] ``` @@ -130,7 +130,7 @@ install SQLite.swift with Carthage: 2. Update your Cartfile to include the following: ```ruby - github "stephencelis/SQLite.swift" ~> 0.15.3 + github "stephencelis/SQLite.swift" ~> 0.15.4 ``` 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. @@ -160,7 +160,7 @@ install SQLite.swift with Carthage: use_frameworks! target 'YourAppTargetName' do - pod 'SQLite.swift', '~> 0.15.3' + pod 'SQLite.swift', '~> 0.15.4' end ``` @@ -174,7 +174,7 @@ with the OS you can require the `standalone` subspec: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.3' + pod 'SQLite.swift/standalone', '~> 0.15.4' end ``` @@ -184,7 +184,7 @@ dependency to sqlite3 or one of its subspecs: ```ruby target 'YourAppTargetName' do - pod 'SQLite.swift/standalone', '~> 0.15.3' + pod 'SQLite.swift/standalone', '~> 0.15.4' pod 'sqlite3/fts5', '= 3.15.0' # SQLite 3.15.0 with FTS5 enabled end ``` @@ -200,7 +200,7 @@ If you want to use [SQLCipher][] with SQLite.swift you can require the target 'YourAppTargetName' do # Make sure you only require the subspec, otherwise you app might link against # the system SQLite, which means the SQLCipher-specific methods won't work. - pod 'SQLite.swift/SQLCipher', '~> 0.15.3' + pod 'SQLite.swift/SQLCipher', '~> 0.15.4' end ``` diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index d0ccb136..73c6f705 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SQLite.swift" - s.version = "0.15.3" + s.version = "0.15.4" s.summary = "A type-safe, Swift-language layer over SQLite3." s.description = <<-DESC diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index a2d7edbe..ec0423e9 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -1539,7 +1539,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1562,7 +1561,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = appletvos; @@ -1614,7 +1612,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1639,7 +1636,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = watchos; @@ -1664,7 +1660,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1689,7 +1684,6 @@ INFOPLIST_FILE = "$(SRCROOT)/Sources/SQLite/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = xros; @@ -1778,6 +1772,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.4; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; @@ -1837,6 +1832,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 0.15.4; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = ""; SDKROOT = iphoneos; @@ -1866,7 +1862,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1890,7 +1885,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; @@ -1940,7 +1934,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; @@ -1966,7 +1959,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 0.15.3; PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLite; PRODUCT_NAME = SQLite; SDKROOT = macosx; diff --git a/Tests/SPM/Package.swift b/Tests/SPM/Package.swift index 6521211a..a5a4afc4 100644 --- a/Tests/SPM/Package.swift +++ b/Tests/SPM/Package.swift @@ -15,7 +15,7 @@ let package = Package( // for testing from same repository .package(path: "../..") // normally this would be: - // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.3") + // .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4") ], targets: [ .executableTarget(name: "test", dependencies: [.product(name: "SQLite", package: "SQLite.swift")]) From 496a086e5a4d03fb6b0338f63b600dc153be6fa3 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 14 Jul 2025 15:14:14 -0400 Subject: [PATCH 1037/1046] Build `SwiftToolchainCSQLite` for Android --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index cf3a0b60..56925d18 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let targets: [Target] = [ .target( name: "SQLite", dependencies: [ - .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows])) + .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows, .android])) ], exclude: [ "Info.plist" From e8cfef658cbdbdd297ed7f549d197ec481b6b7a9 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Mon, 14 Jul 2025 15:14:34 -0400 Subject: [PATCH 1038/1046] Fix Android build --- Sources/SQLite/Core/Backup.swift | 2 +- Sources/SQLite/Core/Connection+Aggregation.swift | 2 +- Sources/SQLite/Core/Connection+Attach.swift | 2 +- Sources/SQLite/Core/Connection.swift | 2 +- Sources/SQLite/Core/Result.swift | 2 +- Sources/SQLite/Core/Statement.swift | 2 +- Sources/SQLite/Helpers.swift | 2 +- Tests/SQLiteTests/Core/Connection+AttachTests.swift | 2 +- Tests/SQLiteTests/Core/Connection+PragmaTests.swift | 2 +- Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- Tests/SQLiteTests/Core/ResultTests.swift | 2 +- Tests/SQLiteTests/Core/StatementTests.swift | 2 +- Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift | 2 +- Tests/SQLiteTests/Typed/CustomAggregationTests.swift | 2 +- Tests/SQLiteTests/Typed/QueryIntegrationTests.swift | 2 +- Tests/SQLiteTests/Typed/QueryTests.swift | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/SQLite/Core/Backup.swift b/Sources/SQLite/Core/Backup.swift index 5e741ecd..023acc08 100644 --- a/Sources/SQLite/Core/Backup.swift +++ b/Sources/SQLite/Core/Backup.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Connection+Aggregation.swift b/Sources/SQLite/Core/Connection+Aggregation.swift index bfe253a7..a1abb74a 100644 --- a/Sources/SQLite/Core/Connection+Aggregation.swift +++ b/Sources/SQLite/Core/Connection+Aggregation.swift @@ -3,7 +3,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Connection+Attach.swift b/Sources/SQLite/Core/Connection+Attach.swift index 32461468..0c674ee6 100644 --- a/Sources/SQLite/Core/Connection+Attach.swift +++ b/Sources/SQLite/Core/Connection+Attach.swift @@ -3,7 +3,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Connection.swift b/Sources/SQLite/Core/Connection.swift index 188ff80b..57521f83 100644 --- a/Sources/SQLite/Core/Connection.swift +++ b/Sources/SQLite/Core/Connection.swift @@ -28,7 +28,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Result.swift b/Sources/SQLite/Core/Result.swift index 2659ad21..9a72e4c5 100644 --- a/Sources/SQLite/Core/Result.swift +++ b/Sources/SQLite/Core/Result.swift @@ -2,7 +2,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Core/Statement.swift b/Sources/SQLite/Core/Statement.swift index 458e3d9c..82d535b9 100644 --- a/Sources/SQLite/Core/Statement.swift +++ b/Sources/SQLite/Core/Statement.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Sources/SQLite/Helpers.swift b/Sources/SQLite/Helpers.swift index 00493f21..e3c84589 100644 --- a/Sources/SQLite/Helpers.swift +++ b/Sources/SQLite/Helpers.swift @@ -26,7 +26,7 @@ import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/Connection+AttachTests.swift b/Tests/SQLiteTests/Core/Connection+AttachTests.swift index 68618596..0e185da5 100644 --- a/Tests/SQLiteTests/Core/Connection+AttachTests.swift +++ b/Tests/SQLiteTests/Core/Connection+AttachTests.swift @@ -6,7 +6,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift index bd29bd98..d1d4ab04 100644 --- a/Tests/SQLiteTests/Core/Connection+PragmaTests.swift +++ b/Tests/SQLiteTests/Core/Connection+PragmaTests.swift @@ -6,7 +6,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index 61083453..da50974a 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -7,7 +7,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/ResultTests.swift b/Tests/SQLiteTests/Core/ResultTests.swift index b6a373f7..03415f3a 100644 --- a/Tests/SQLiteTests/Core/ResultTests.swift +++ b/Tests/SQLiteTests/Core/ResultTests.swift @@ -6,7 +6,7 @@ import Foundation import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Core/StatementTests.swift b/Tests/SQLiteTests/Core/StatementTests.swift index ceaa8813..3c90d941 100644 --- a/Tests/SQLiteTests/Core/StatementTests.swift +++ b/Tests/SQLiteTests/Core/StatementTests.swift @@ -5,7 +5,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift index 8a34e93b..b3b9e617 100644 --- a/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift +++ b/Tests/SQLiteTests/Extensions/FTSIntegrationTests.swift @@ -3,7 +3,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 8b7ec09a..73a0767f 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -7,7 +7,7 @@ import Dispatch import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift index 899bf354..cde5a0c3 100644 --- a/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift +++ b/Tests/SQLiteTests/Typed/QueryIntegrationTests.swift @@ -3,7 +3,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 diff --git a/Tests/SQLiteTests/Typed/QueryTests.swift b/Tests/SQLiteTests/Typed/QueryTests.swift index 7fc3dcf5..257b5245 100644 --- a/Tests/SQLiteTests/Typed/QueryTests.swift +++ b/Tests/SQLiteTests/Typed/QueryTests.swift @@ -3,7 +3,7 @@ import XCTest import sqlite3 #elseif SQLITE_SWIFT_SQLCIPHER import SQLCipher -#elseif os(Linux) +#elseif canImport(SwiftToolchainCSQLite) import SwiftToolchainCSQLite #else import SQLite3 From 91a30a3535e74767372ef605751569299696d8d1 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Tue, 15 Jul 2025 13:54:04 +0200 Subject: [PATCH 1039/1046] add android ci --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37f75476..f82a9c59 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,3 +76,9 @@ jobs: env: SPM: run run: ./run-tests.sh + build-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run tests + uses: skiptools/swift-android-action@v2 From 22d30c885f6ac1161ff5e88e32f054b4672963d2 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Wed, 16 Jul 2025 15:17:33 +0200 Subject: [PATCH 1040/1046] add SQLite. prefix for Expression in README --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e83504f..a52b7ae5 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ do { let db = try Connection("path/to/db.sqlite3") let users = Table("users") - let id = Expression("id") - let name = Expression("name") - let email = Expression("email") + let id = SQLite.Expression("id") + let name = SQLite.Expression("name") + let email = SQLite.Expression("email") try db.run(users.create { t in t.column(id, primaryKey: true) @@ -83,6 +83,9 @@ do { } ``` +Note that `Expression` should be written as `SQLite.Expression` to avoid +conflicts with the `SwiftUI.Expression` if you are using SwiftUI too. + SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API. From fee88af870ccf4f574607b1dc8f2835cb2aa0b36 Mon Sep 17 00:00:00 2001 From: hawk0620 Date: Thu, 17 Jul 2025 14:11:06 +0800 Subject: [PATCH 1041/1046] create index support array as parameters --- Sources/SQLite/Typed/Schema.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index e162cf34..02f5b102 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -135,8 +135,8 @@ extension Table { } // MARK: - CREATE INDEX - - public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { + + public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .unique : nil, ifNotExists), Expression(literal: "ON"), @@ -146,12 +146,20 @@ extension Table { return " ".join(clauses.compactMap { $0 }).asSQL() } + + public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { + return createIndex(Array(columns), unique: unique, ifNotExists: ifNotExists) + } // MARK: - DROP INDEX - - public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { + + public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { drop("INDEX", indexName(columns), ifExists) } + + public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { + dropIndex(Array(columns), ifExists: ifExists) + } fileprivate func indexName(_ columns: [Expressible]) -> Expressible { let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joined(separator: " ").lowercased() From 98d83a223db0d1a51c2d575f108c66b1b770b725 Mon Sep 17 00:00:00 2001 From: NathanFallet Date: Thu, 17 Jul 2025 13:42:49 +0200 Subject: [PATCH 1042/1046] fix linting --- Sources/SQLite/Typed/Query.swift | 2 +- Sources/SQLite/Typed/Schema.swift | 8 ++++---- Tests/SQLiteTests/TestHelpers.swift | 10 ---------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/Sources/SQLite/Typed/Query.swift b/Sources/SQLite/Typed/Query.swift index 2a83665c..6162fcc7 100644 --- a/Sources/SQLite/Typed/Query.swift +++ b/Sources/SQLite/Typed/Query.swift @@ -1055,7 +1055,7 @@ extension Connection { } func expandGlob(_ namespace: Bool) -> (QueryType) throws -> Void { - { (queryType: QueryType) throws -> Void in + { (queryType: QueryType) throws in var query = type(of: queryType).init(queryType.clauses.from.name, database: queryType.clauses.from.database) query.clauses.select = queryType.clauses.select query.clauses.with = strip(queryType.clauses.with) diff --git a/Sources/SQLite/Typed/Schema.swift b/Sources/SQLite/Typed/Schema.swift index 02f5b102..919042ab 100644 --- a/Sources/SQLite/Typed/Schema.swift +++ b/Sources/SQLite/Typed/Schema.swift @@ -135,7 +135,7 @@ extension Table { } // MARK: - CREATE INDEX - + public func createIndex(_ columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { let clauses: [Expressible?] = [ create("INDEX", indexName(columns), unique ? .unique : nil, ifNotExists), @@ -146,17 +146,17 @@ extension Table { return " ".join(clauses.compactMap { $0 }).asSQL() } - + public func createIndex(_ columns: Expressible..., unique: Bool = false, ifNotExists: Bool = false) -> String { return createIndex(Array(columns), unique: unique, ifNotExists: ifNotExists) } // MARK: - DROP INDEX - + public func dropIndex(_ columns: [Expressible], ifExists: Bool = false) -> String { drop("INDEX", indexName(columns), ifExists) } - + public func dropIndex(_ columns: Expressible..., ifExists: Bool = false) -> String { dropIndex(Array(columns), ifExists: ifExists) } diff --git a/Tests/SQLiteTests/TestHelpers.swift b/Tests/SQLiteTests/TestHelpers.swift index 56415a59..e62b2a23 100644 --- a/Tests/SQLiteTests/TestHelpers.swift +++ b/Tests/SQLiteTests/TestHelpers.swift @@ -164,14 +164,4 @@ struct TestOptionalCodable: Codable, Equatable { let double: Double? let date: Date? let uuid: UUID? - - init(int: Int?, string: String?, bool: Bool?, float: Float?, double: Double?, date: Date?, uuid: UUID?) { - self.int = int - self.string = string - self.bool = bool - self.float = float - self.double = double - self.date = date - self.uuid = uuid - } } From 83c0c9828c0adb80b6ef08d3451fd8c2951398bd Mon Sep 17 00:00:00 2001 From: Nikolay Dzhulay Date: Wed, 13 Aug 2025 15:59:05 +0100 Subject: [PATCH 1043/1046] Attempt to fix tests build for android --- Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift index 1c648bd8..f3a0ba83 100644 --- a/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift +++ b/Tests/SQLiteTests/Schema/SchemaDefinitionsTests.swift @@ -32,7 +32,7 @@ class ColumnDefinitionTests: XCTestCase { defaultValue: .numericLiteral("123.123"), references: nil)) ] - #if !os(Linux) + #if !(os(Linux) || os(Android)) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: ColumnDefinitionTests.self) @@ -183,7 +183,7 @@ class IndexDefinitionTests: XCTestCase { "CREATE INDEX IF NOT EXISTS \"index_tests\" ON \"tests\" (\"test_column\")") ] - #if !os(Linux) + #if !(os(Linux) || os(Android)) override class var defaultTestSuite: XCTestSuite { let suite = XCTestSuite(forTestCaseClass: IndexDefinitionTests.self) From b7b7923708c684d73d378c2a279268c72afae407 Mon Sep 17 00:00:00 2001 From: Nikolay Dzhulay Date: Sun, 17 Aug 2025 20:45:07 +0100 Subject: [PATCH 1044/1046] Disable for Android same tests, which disabled for Linux --- Tests/SQLiteTests/Core/ConnectionTests.swift | 2 +- Tests/SQLiteTests/Typed/CustomAggregationTests.swift | 2 +- Tests/SQLiteTests/Typed/CustomFunctionsTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SQLiteTests/Core/ConnectionTests.swift b/Tests/SQLiteTests/Core/ConnectionTests.swift index da50974a..abdb3e1b 100644 --- a/Tests/SQLiteTests/Core/ConnectionTests.swift +++ b/Tests/SQLiteTests/Core/ConnectionTests.swift @@ -374,7 +374,7 @@ class ConnectionTests: SQLiteTestCase { } // https://github.com/stephencelis/SQLite.swift/issues/1071 - #if !os(Linux) + #if !(os(Linux) || os(Android)) func test_createFunction_withArrayArguments() throws { db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } diff --git a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift index 73a0767f..b052d236 100644 --- a/Tests/SQLiteTests/Typed/CustomAggregationTests.swift +++ b/Tests/SQLiteTests/Typed/CustomAggregationTests.swift @@ -14,7 +14,7 @@ import SQLite3 #endif // https://github.com/stephencelis/SQLite.swift/issues/1071 -#if !os(Linux) +#if !(os(Linux) || os(Android)) class CustomAggregationTests: SQLiteTestCase { override func setUpWithError() throws { diff --git a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift index 8598b6fb..fd9ae6b9 100644 --- a/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift +++ b/Tests/SQLiteTests/Typed/CustomFunctionsTests.swift @@ -2,7 +2,7 @@ import XCTest import SQLite // https://github.com/stephencelis/SQLite.swift/issues/1071 -#if !os(Linux) +#if !(os(Linux) || os(Android)) class CustomFunctionNoArgsTests: SQLiteTestCase { typealias FunctionNoOptional = () -> SQLite.Expression From 4f2fdffc2a5924d807bded85e6fe58378eba3ae6 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 30 Aug 2025 15:10:47 +0200 Subject: [PATCH 1045/1046] Change iOS simulator device to Any iOS Simulator Device --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f82a9c59..d2ef6aa1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "iPhone 16" + IOS_SIMULATOR: "Any iOS Simulator Device" IOS_VERSION: "18.0" jobs: build: From 9c14b66992517fe3c376b8af6ede3f68aabc3577 Mon Sep 17 00:00:00 2001 From: Nathan Fallet Date: Sat, 30 Aug 2025 15:20:21 +0200 Subject: [PATCH 1046/1046] Trying iPhone 16 with iOS 18.4 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2ef6aa1..3c6970f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,8 @@ name: Build and test on: [push, pull_request] env: - IOS_SIMULATOR: "Any iOS Simulator Device" - IOS_VERSION: "18.0" + IOS_SIMULATOR: "iPhone 16" + IOS_VERSION: "18.4" jobs: build: runs-on: macos-15

    J#%XqFCzynZ`pimhll6%#@-RywGYE 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