Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite persistent normalized cache #79

Merged
merged 76 commits into from
Apr 24, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
ddee83a
Placeholder SqliteNormalizedCache
Apr 4, 2017
55e8b8d
Add SQLite.swift dependency to Apollo.podspec
Apr 4, 2017
647884e
Create Example project to test Cocoapods installation
Apr 4, 2017
3a094a1
Move tests from Apollo project to ApolloExample project (since they w…
Apr 5, 2017
6384fb4
Workaround to tests not running due to Apollo.framework not being in …
Apr 5, 2017
d4349f5
Crude implementation of SqliteNormalizedCache
Apr 5, 2017
e34dd89
More progress on crude implementation of SqliteNormalizedCache
Apr 6, 2017
9f8b5fb
Temporary workaround for JSON type conversion bug
Apr 7, 2017
135ac3f
Replace temporary workaround with better fix
Apr 7, 2017
76cdf91
Remove in-memory initializer from SqliteNormalizedCache
Apr 7, 2017
9680d22
Avoid duplicating overwriting updated records in sqlite normalized cache
Apr 7, 2017
8cba5f5
Better organize code in SqliteNormalizedCache merge method
Apr 7, 2017
829157d
Fix unit tests by setting NSAppTransportSecurity to enable arbitrary …
Apr 10, 2017
deeab16
SqliteNormalizedCache fulfills loadRecords(forKeys:) promise with [ni…
Apr 10, 2017
6f5192b
Store record references in SQLite as JSON objects with a single key (…
Apr 10, 2017
43b2cb5
Update FetchQueryTests to use both in-memory and sqlite caches
Apr 10, 2017
1ae3e37
Update LoadQueryFromStoreTests to use both in-memory and sqlite caches
Apr 10, 2017
6d9ef48
Update StarWarsServerCachingRoundtripTests to use both in-memory and …
Apr 10, 2017
abaa650
Update StarWarsServerTests to use both in-memory and sqlite caches
Apr 10, 2017
589096f
Update StoreTransactionTests to use both in-memory and sqlite caches
Apr 10, 2017
20fa038
Update WatchQueryTests to use both in-memory and sqlite caches
Apr 10, 2017
26a2625
Remove ApolloClient and ApolloStore convenience initializers that ass…
Apr 10, 2017
3e7a0ee
Add a test to CachePersistenceTests, verifying that a query response …
Apr 11, 2017
5245bf8
Add SqliteNormalizedCache subspec (which is not supported on tvos)
Apr 11, 2017
da1f9e7
Make “ApolloExample” and “TestHost iOS” shared schemes
Apr 11, 2017
b802c7b
Move SqliteNormalizedCache file into subspec
Apr 11, 2017
ae5b4c0
Fix persistence unit test. The problem was that data wasn’t already c…
Apr 11, 2017
35fed37
Fix Apollo.xcodeproj building (without sqlite cache)
Apr 11, 2017
acbddf2
Update Podfile with Core subspec, so consumers can choose to avoid SQ…
Apr 11, 2017
82990f0
Pod install
Apr 11, 2017
1c95de2
Add back line in WatchQueryTests.swift that was accidentally removed …
Apr 12, 2017
8409057
Move codegen script resource under ‘Core’ target in Apollo.podspec
Apr 12, 2017
4a4457d
Make InMemoryNormalizedCache public, since it will need to be instant…
Apr 12, 2017
1d195f7
Make SqliteNormalizedCache public, since it will need to be instantia…
Apr 12, 2017
2a6ec2d
Split podspec into Apollo.podspec and ApolloSQLite.podspec
Apr 13, 2017
91af7ce
Restructure project into two separate projects (Apollo and ApolloSQLi…
Apr 14, 2017
adbfc27
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
a8d94b2
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
0a92c5d
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
c2a25c2
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
b18b616
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
859936b
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
cd130e5
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
25a72bb
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
ded68d7
(continued) Restructure project into two separate projects (Apollo an…
Apr 17, 2017
17f19db
Revert "Split podspec into Apollo.podspec and ApolloSQLite.podspec"
Apr 17, 2017
4dca165
Project version comes from a Version.xcconfig file, and set-version.s…
Apr 17, 2017
ef49021
Fix Apollo.podspec, and don’t let SQLite.swift get above 0.11.2 since…
Apr 17, 2017
1c30841
Wrestling with submodules
Apr 18, 2017
4853f4d
Wrestling with submodules, continued
Apr 18, 2017
4a517a7
Prepare to try submodules again
Apr 18, 2017
41b02d0
Prepare to try submodules again, continued
Apr 18, 2017
9931574
Some Apollo xcodeproj cleanup
Apr 18, 2017
d536c17
Naming tweak: “Sqlite” —> “SQLite”
Apr 18, 2017
f14048c
Explanatory coments for NormalizedCache methods
Apr 18, 2017
4a07f40
Make SQLiteNormalizedCache’s loadRecords(forKeys:) adhere to the cont…
Apr 18, 2017
aa12af9
Revert bumping version to 0.5.7
Apr 18, 2017
69d124d
Add comment to Apollo.podspec explaining reasoning behind including S…
Apr 18, 2017
ca6209d
Remove separate test schemes and share framework schemes in ApolloSQLite
martijnwalraven Apr 19, 2017
ec80581
Convert ApolloSQLite to universal framework
martijnwalraven Apr 19, 2017
5118469
Update README.md with concise installation instructions for Cocoapods…
Apr 19, 2017
97b6ce5
Avoid overwriting xcconfig-specified deployment targets in ApolloSQLi…
Apr 19, 2017
92fb642
Better organize directory structure
Apr 19, 2017
31ad83d
Replace TestHost iOS scheme with ApolloPerformanceTests
martijnwalraven Apr 20, 2017
e5dac42
Clean up Promise construction in SQLiteNormalizedCache
martijnwalraven Apr 20, 2017
1f344e0
Avoid JSONSerializationFormat in SQLiteNormalizedCache and clarify ty…
martijnwalraven Apr 20, 2017
eb67621
Use $reference when serializing Reference to avoid conflicts with use…
martijnwalraven Apr 20, 2017
6ee21ba
Add ApolloSQLite jobs to .travis.yml
martijnwalraven Apr 20, 2017
9f9d17f
Minor cleanup and clarity fixes
Apr 20, 2017
c029a48
Write SQLite database to recommended MacOS directory to hopefully avo…
Apr 21, 2017
28feedf
Temporarily disable all tests except ApolloSQLite, for the purpose of…
Apr 21, 2017
76de545
Keep trying things to prevent CI crash
Apr 21, 2017
cab7b64
Keep trying things to prevent CI crash
Apr 21, 2017
95cd1af
Keep trying things to prevent CI crash
Apr 21, 2017
5e4e3b5
Revert "Temporarily disable all tests except ApolloSQLite, for the pu…
Apr 21, 2017
998c1d2
Disable unit tests on iOS 9.3 for now, since that simulator doesn’t s…
Apr 21, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Apollo/JSONStandardTypeConversions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,13 @@ extension Dictionary: JSONEncodable {
public var jsonObject: JSONObject {
var jsonObject = JSONObject(minimumCapacity: count)
for (key, value) in self {
print("key: \(key), \(type(of: key))")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be removed?

print("value: \(value), \(type(of: value))")
if case let (key as String, value as JSONEncodable) = (key, value) {
if !isNil(value) {
jsonObject[key] = value.jsonValue
}
}
else {
print("key: \(type(of: key))")
print("value: \(type(of: value))")
} else {
fatalError("Dictionary is only JSONEncodable if Value is (and if Key is String)")
}
}
Expand Down
9 changes: 6 additions & 3 deletions Apollo/Record.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ public typealias CacheKey = String
/// A cache record.
public struct Record {
public let key: CacheKey
public private(set) var fields: JSONObject

public init(key: CacheKey, _ fields: JSONObject = [:]) {
public typealias Value = Any
public typealias Fields = [CacheKey: Value]
public private(set) var fields: Fields

public init(key: CacheKey, _ fields: Fields = [:]) {
self.key = key
self.fields = fields
}

public subscript(key: CacheKey) -> JSONValue? {
public subscript(key: CacheKey) -> Value? {
get {
return fields[key]
}
Expand Down
2 changes: 1 addition & 1 deletion Apollo/RecordSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public struct RecordSet {
}

extension RecordSet: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (CacheKey, JSONObject)...) {
public init(dictionaryLiteral elements: (CacheKey, Record.Fields)...) {
self.init(records: elements.map { Record(key: $0.0, $0.1) })
}
}
Expand Down
56 changes: 26 additions & 30 deletions ApolloSQLite/SQLiteNormalizedCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,60 +83,56 @@ public final class SQLiteNormalizedCache: NormalizedCache {
throw SQLiteNormalizedCacheError.invalidRecordEncoding(record: record)
}

let recordJSON = try SQLiteSerialization.deserialize(data: recordData)
return Record(key: row[key], recordJSON)
let fields = try SQLiteSerialization.deserialize(data: recordData)
return Record(key: row[key], fields)
}
}

private let serializedReferenceKey = "reference"

final class SQLiteSerialization {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think this should probably be private.

static func serialize(fields: JSONObject) throws -> Data {
static func serialize(fields: Record.Fields) throws -> Data {
var objectToSerialize = JSONObject()
for (key, value) in fields {
objectToSerialize[key] = try serialize(value: value)
}
return try JSONSerializationFormat.serialize(value: objectToSerialize)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup in this commit is great! Perhaps JSONSerializationFormat should have a more descriptive name, to make its purpose more distinct than the system-provided JSONSerialization?

return try JSONSerialization.data(withJSONObject: objectToSerialize, options: [])
}

private static func serialize(value: Record.Value) throws -> JSONValue {
switch value {
case let reference as Reference:
return [serializedReferenceKey: reference.key]
case let array as [Record.Value]:
return try array.map { try serialize(value: $0) }
default:
return value
}
}

static func deserialize(data: Data) throws -> JSONObject {
let object = try JSONSerializationFormat.deserialize(data: data)
static func deserialize(data: Data) throws -> Record.Fields {
let object = try JSONSerialization.jsonObject(with: data, options: [])
guard let jsonObject = object as? JSONObject else {
throw SQLiteNormalizedCacheError.invalidRecordShape(object: object)
}
var deserializedObject = JSONObject()
var fields = Record.Fields()
for (key, value) in jsonObject {
deserializedObject[key] = try deserialize(valueJSON: value)
fields[key] = try deserialize(jsonValue: value)
}
return deserializedObject
return fields
}

private static func deserialize(valueJSON: Any) throws -> Any {
switch valueJSON {
private static func deserialize(jsonValue: JSONValue) throws -> Record.Value {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this for the naming of the serialize and deserialize method parameters, for even greater clarity?

  • serialize(fieldValue:)
  • serialize(fieldJSON:)

switch jsonValue {
case let dictionary as JSONObject:
guard let reference = dictionary[serializedReferenceKey] as? String else {
throw SQLiteNormalizedCacheError.invalidRecordValue(value: valueJSON)
throw SQLiteNormalizedCacheError.invalidRecordValue(value: jsonValue)
}
return Reference(key: reference)
case let array as NSArray:
return try array.map { try deserialize(valueJSON: $0) }
case let array as [JSONValue]:
return try array.map { try deserialize(jsonValue: $0) }
default:
return valueJSON
}
}

private static func serialize(value: Any) throws -> Any {
switch value {
case let reference as Reference:
return [serializedReferenceKey: reference.key]
case let array as NSArray:
return try array.map { try serialize(value: $0) }
case let string as NSString:
return string as String
case let number as NSNumber:
return number.doubleValue
default:
return value
return jsonValue
}
}
}