Skip to content

Commit 24e149f

Browse files
committedJul 31, 2018
Use new record ID representation
refs #255
2 parents a9b1c42 + b705621 commit 24e149f

20 files changed

+613
-221
lines changed
 

‎skygear/src/androidTest/java/io/skygear/skygear/AuthResponseHandlerUnitTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ public void testAuthResponseHandlerSuccessFlow() throws Exception {
3636
AuthResponseHandler authResponseHandler = new AuthResponseHandler() {
3737
@Override
3838
public void onAuthSuccess(Record user) {
39-
assertEquals("user", user.type);
40-
assertEquals("user-id-1", user.id);
39+
assertEquals("user", user.getType());
40+
assertEquals("user-id-1", user.getId());
4141
assertEquals("user1", user.get("username"));
4242
assertEquals("user1@skygear.dev", user.get("email"));
4343

‎skygear/src/androidTest/java/io/skygear/skygear/AuthResponseHandlerWrapperUnitTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public void testAuthResponseHandlerWrapperSuccessFlow() throws Exception {
3737
@Override
3838
public void onAuthSuccess(Record user) {
3939
checkpoints[0] = true;
40-
assertEquals("user", user.type);
41-
assertEquals("user_001", user.id);
40+
assertEquals("user", user.getType());
41+
assertEquals("user_001", user.getId());
4242
}
4343

4444
@Override
@@ -98,8 +98,8 @@ public void testAuthResponseHandlerWrapperAuthResolveFlow() throws Exception {
9898
@Override
9999
public void resolveAuthUser(Record user, String accessToken) {
100100
checkpoints[0] = true;
101-
assertEquals("user", user.type);
102-
assertEquals("user_001", user.id);
101+
assertEquals("user", user.getType());
102+
assertEquals("user_001", user.getId());
103103
assertEquals("my-token", accessToken);
104104

105105
}

‎skygear/src/androidTest/java/io/skygear/skygear/PersistentStoreUnitTest.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ public void testPersistentStoreRestoreUser() throws Exception {
9797
PersistentStore persistentStore = new PersistentStore(instrumentationContext);
9898
Record currentUser = persistentStore.currentUser;
9999

100-
assertEquals("user", currentUser.type);
101-
assertEquals("123", currentUser.id);
100+
assertEquals("user", currentUser.getType());
101+
assertEquals("123", currentUser.getId());
102102
assertEquals("user_123", currentUser.get("username"));
103103
assertEquals("user123@skygear.dev", currentUser.get("email"));
104104

@@ -131,6 +131,8 @@ public void testPersistentStoreSaveUser() throws Exception {
131131
JSONObject currentUserJson = new JSONObject(currentUserString);
132132

133133
assertEquals("user/12345", currentUserJson.getString("_id"));
134+
assertEquals("user", currentUserJson.getString("_recordType"));
135+
assertEquals("12345", currentUserJson.getString("_recordID"));
134136
assertEquals("user_12345", currentUserJson.getString("username"));
135137
assertEquals("user12345@skygear.dev", currentUserJson.getString("email"));
136138
assertEquals("token_12345", pref.getString(PersistentStore.ACCESS_TOKEN_KEY, null));

‎skygear/src/androidTest/java/io/skygear/skygear/RecordDeleteRequestUnitTest.java

+60-23
Original file line numberDiff line numberDiff line change
@@ -54,48 +54,85 @@ public static void tearDownClass() throws Exception {
5454

5555
@Test
5656
public void testRecordDeleteRequestNormalFlow() throws Exception {
57-
Record note1 = new Record("Note");
58-
Record note2 = new Record("Note");
59-
60-
note1.id = "9C0F4536-FEA7-42DB-B8EF-561CCD175E06";
61-
note2.id = "05A2946A-72DC-4F20-99F9-129BD1FCB52A";
57+
Record note1 = new Record("Note", "9C0F4536-FEA7-42DB-B8EF-561CCD175E06");
58+
Record note2 = new Record("Note", "05A2946A-72DC-4F20-99F9-129BD1FCB52A");
6259

6360
RecordDeleteRequest request
6461
= new RecordDeleteRequest(new Record[]{note1, note2}, instrumentationPublicDatabase);
6562
Map<String, Object> data = request.data;
6663
assertEquals("_public", data.get("database_id"));
6764

68-
JSONArray ids = (JSONArray) data.get("ids");
69-
assertEquals(2, ids.length());
65+
JSONArray deprecatedIDs = (JSONArray) data.get("ids");
66+
assertEquals(2, deprecatedIDs.length());
67+
assertEquals("Note/9C0F4536-FEA7-42DB-B8EF-561CCD175E06", deprecatedIDs.getString(0));
68+
assertEquals("Note/05A2946A-72DC-4F20-99F9-129BD1FCB52A", deprecatedIDs.getString(1));
7069

70+
JSONArray recordIdentifiers = (JSONArray) data.get("records");
71+
assertEquals(2, recordIdentifiers.length());
72+
assertEquals(
73+
"Note",
74+
recordIdentifiers.getJSONObject(0).getString("_recordType")
75+
);
76+
assertEquals(
77+
"9C0F4536-FEA7-42DB-B8EF-561CCD175E06",
78+
recordIdentifiers.getJSONObject(0).getString("_recordID")
79+
);
7180
assertEquals(
72-
String.format("%s/%s", note1.getType(), note1.getId()),
73-
ids.get(0)
81+
"Note",
82+
recordIdentifiers.getJSONObject(1).getString("_recordType")
7483
);
7584
assertEquals(
76-
String.format("%s/%s", note2.getType(), note2.getId()),
77-
ids.get(1)
85+
"05A2946A-72DC-4F20-99F9-129BD1FCB52A",
86+
recordIdentifiers.getJSONObject(1).getString("_recordID")
7887
);
7988

8089
request.validate();
8190
}
8291

83-
@Test(expected = InvalidParameterException.class)
84-
public void testRecordSaveRequestNotAllowSaveNoRecords() throws Exception {
85-
RecordDeleteRequest request
86-
= new RecordDeleteRequest(new Record[]{}, instrumentationPublicDatabase);
87-
request.validate();
88-
}
89-
90-
@Test(expected = InvalidParameterException.class)
91-
public void testRecordSaveRequestNotAllowMultiTypeRecords() throws Exception {
92+
@Test
93+
public void testRecordDeleteRequestAcceptRecordIDs() throws Exception {
9294
RecordDeleteRequest request = new RecordDeleteRequest(
93-
new Record[]{
94-
new Record("Note"),
95-
new Record("Comment")
95+
"Note",
96+
new String[]{
97+
"9C0F4536-FEA7-42DB-B8EF-561CCD175E06",
98+
"05A2946A-72DC-4F20-99F9-129BD1FCB52A"
9699
},
97100
instrumentationPublicDatabase
98101
);
102+
Map<String, Object> data = request.data;
103+
assertEquals("_public", data.get("database_id"));
104+
105+
JSONArray deprecatedIDs = (JSONArray) data.get("ids");
106+
assertEquals(2, deprecatedIDs.length());
107+
assertEquals("Note/9C0F4536-FEA7-42DB-B8EF-561CCD175E06", deprecatedIDs.getString(0));
108+
assertEquals("Note/05A2946A-72DC-4F20-99F9-129BD1FCB52A", deprecatedIDs.getString(1));
109+
110+
JSONArray recordIdentifiers = (JSONArray) data.get("records");
111+
assertEquals(2, recordIdentifiers.length());
112+
assertEquals(
113+
"Note",
114+
recordIdentifiers.getJSONObject(0).getString("_recordType")
115+
);
116+
assertEquals(
117+
"9C0F4536-FEA7-42DB-B8EF-561CCD175E06",
118+
recordIdentifiers.getJSONObject(0).getString("_recordID")
119+
);
120+
assertEquals(
121+
"Note",
122+
recordIdentifiers.getJSONObject(1).getString("_recordType")
123+
);
124+
assertEquals(
125+
"05A2946A-72DC-4F20-99F9-129BD1FCB52A",
126+
recordIdentifiers.getJSONObject(1).getString("_recordID")
127+
);
128+
129+
request.validate();
130+
}
131+
132+
@Test(expected = InvalidParameterException.class)
133+
public void testRecordSaveRequestNotAllowSaveNoRecords() throws Exception {
134+
RecordDeleteRequest request
135+
= new RecordDeleteRequest(new Record[]{}, instrumentationPublicDatabase);
99136
request.validate();
100137
}
101138
}

‎skygear/src/androidTest/java/io/skygear/skygear/RecordDeleteResponseHandlerUnitTest.java

+24-14
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828

2929
import static junit.framework.Assert.assertEquals;
30+
import static junit.framework.Assert.assertNull;
3031
import static junit.framework.Assert.assertTrue;
3132
import static junit.framework.Assert.fail;
3233

@@ -35,11 +36,13 @@ public class RecordDeleteResponseHandlerUnitTest {
3536
@Test
3637
public void testRecordDeleteResponseHandlerSuccessFlow() throws Exception {
3738
JSONObject jsonObject1 = new JSONObject();
38-
jsonObject1.put("_id", "Note/48092492-0791-4120-B314-022202AD3970");
39+
jsonObject1.put("_recordType", "Note");
40+
jsonObject1.put("_recordID", "48092492-0791-4120-B314-022202AD3970");
3941
jsonObject1.put("_type", "record");
4042

4143
JSONObject jsonObject2 = new JSONObject();
42-
jsonObject2.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
44+
jsonObject2.put("_recordType", "Note");
45+
jsonObject2.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
4346
jsonObject2.put("_type", "record");
4447

4548
JSONArray result = new JSONArray();
@@ -60,7 +63,7 @@ public void onDeleteSuccess(String[] ids) {
6063
}
6164

6265
@Override
63-
public void onDeletePartialSuccess(String[] ids, Map<String, Error> errors) {
66+
public void onDeletePartialSuccess(String[] ids, Error[] errors) {
6467
fail("Should not get partial success callback");
6568
}
6669

@@ -77,11 +80,13 @@ public void onDeleteFail(Error error) {
7780
@Test
7881
public void testRecordDeleteResponseHandlerPartialSuccessFlow() throws Exception {
7982
JSONObject jsonObject1 = new JSONObject();
80-
jsonObject1.put("_id", "Note/48092492-0791-4120-B314-022202AD3970");
83+
jsonObject1.put("_recordType", "Note");
84+
jsonObject1.put("_recordID", "48092492-0791-4120-B314-022202AD3970");
8185
jsonObject1.put("_type", "record");
8286

8387
JSONObject jsonObject2 = new JSONObject();
84-
jsonObject2.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
88+
jsonObject2.put("_recordType", "Note");
89+
jsonObject2.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
8590
jsonObject2.put("_type", "error");
8691
jsonObject2.put("code", 110);
8792
jsonObject2.put("message", "record not found");
@@ -102,13 +107,16 @@ public void onDeleteSuccess(String[] ids) {
102107
}
103108

104109
@Override
105-
public void onDeletePartialSuccess(String[] ids, Map<String, Error> errors) {
106-
assertEquals(1, ids.length);
107-
assertEquals(1, errors.size());
110+
public void onDeletePartialSuccess(String[] ids, Error[] errors) {
111+
assertEquals(2, ids.length);
112+
assertEquals(2, errors.length);
108113

109114
assertEquals("48092492-0791-4120-B314-022202AD3970", ids[0]);
110-
assertEquals(Error.Code.RESOURCE_NOT_FOUND, errors.get("48092492-0791-4120-B314-022202AD3971").getCode());
111-
assertEquals("record not found", errors.get("48092492-0791-4120-B314-022202AD3971").getDetailMessage());
115+
assertNull(ids[1]);
116+
117+
assertNull(errors[0]);
118+
assertEquals(Error.Code.RESOURCE_NOT_FOUND, errors[1].getCode());
119+
assertEquals("record not found", errors[1].getDetailMessage());
112120

113121
checkpoints[0] = true;
114122
}
@@ -126,14 +134,16 @@ public void onDeleteFail(Error error) {
126134
@Test
127135
public void testRecordDeleteResponseHandlerAllFailFlow() throws Exception {
128136
JSONObject jsonObject1 = new JSONObject();
129-
jsonObject1.put("_id", "Note/48092492-0791-4120-B314-022202AD3970");
137+
jsonObject1.put("_recordType", "Note");
138+
jsonObject1.put("_recordID", "48092492-0791-4120-B314-022202AD3970");
130139
jsonObject1.put("_type", "error");
131140
jsonObject1.put("code", 102);
132141
jsonObject1.put("message", "no permission to delete");
133142
jsonObject1.put("name", "PermissionDenied");
134143

135144
JSONObject jsonObject2 = new JSONObject();
136-
jsonObject2.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
145+
jsonObject2.put("_recordType", "Note");
146+
jsonObject2.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
137147
jsonObject2.put("_type", "error");
138148
jsonObject2.put("code", 110);
139149
jsonObject2.put("message", "record not found");
@@ -154,7 +164,7 @@ public void onDeleteSuccess(String[] ids) {
154164
}
155165

156166
@Override
157-
public void onDeletePartialSuccess(String[] ids, Map<String, Error> errors) {
167+
public void onDeletePartialSuccess(String[] ids, Error[] errors) {
158168
fail("Should not get partial success callback");
159169
}
160170

@@ -180,7 +190,7 @@ public void onDeleteSuccess(String[] ids) {
180190
}
181191

182192
@Override
183-
public void onDeletePartialSuccess(String[] ids, Map<String, Error> errors) {
193+
public void onDeletePartialSuccess(String[] ids, Error[] errors) {
184194
fail("Should not get partial success callback");
185195
}
186196

‎skygear/src/androidTest/java/io/skygear/skygear/RecordSaveRequestUnitTest.java

+13-9
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,17 @@ public void testRecordSaveRequestNormalFlow() throws Exception {
7777
String.format("%s/%s", note1.getType(), note1.getId()),
7878
record1.getString("_id")
7979
);
80+
assertEquals(note1.getType(), record1.getString("_recordType"));
81+
assertEquals(note1.getId(), record1.getString("_recordID"));
8082
assertEquals(1, record1.getInt("identifier"));
8183

8284
JSONObject record2 = (JSONObject) records.get(1);
8385
assertEquals(
8486
String.format("%s/%s", note2.getType(), note2.getId()),
8587
record2.getString("_id")
8688
);
89+
assertEquals(note2.getType(), record2.getString("_recordType"));
90+
assertEquals(note2.getId(), record2.getString("_recordID"));
8791
assertEquals(2, record2.getInt("identifier"));
8892

8993
recordSaveRequest.validate();
@@ -98,15 +102,8 @@ public void testRecordSaveRequestAtomic() throws Exception {
98102
assertTrue((boolean)data.get("atomic"));
99103
}
100104

101-
@Test(expected = InvalidParameterException.class)
102-
public void testRecordSaveRequestNotAllowSaveNoRecords() throws Exception {
103-
RecordSaveRequest recordSaveRequest
104-
= new RecordSaveRequest(new Record[]{}, instrumentationPublicDatabase);
105-
recordSaveRequest.validate();
106-
}
107-
108-
@Test(expected = InvalidParameterException.class)
109-
public void testRecordSaveRequestNotAllowMultiTypeRecords() throws Exception {
105+
@Test
106+
public void testRecordSaveRequestAllowMultiTypeRecords() throws Exception {
110107
RecordSaveRequest recordSaveRequest = new RecordSaveRequest(
111108
new Record[]{
112109
new Record("Note"),
@@ -117,6 +114,13 @@ public void testRecordSaveRequestNotAllowMultiTypeRecords() throws Exception {
117114
recordSaveRequest.validate();
118115
}
119116

117+
@Test(expected = InvalidParameterException.class)
118+
public void testRecordSaveRequestNotAllowSaveNoRecords() throws Exception {
119+
RecordSaveRequest recordSaveRequest
120+
= new RecordSaveRequest(new Record[]{}, instrumentationPublicDatabase);
121+
recordSaveRequest.validate();
122+
}
123+
120124
@Test(expected = InvalidParameterException.class)
121125
public void testRecordSaveRequestNotAllowSavingRecordWithPendingAsset() throws Exception {
122126
Record note = new Record("Note");

‎skygear/src/androidTest/java/io/skygear/skygear/RecordSaveResponseHandlerUnitTest.java

+26-15
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Map;
3030

3131
import static junit.framework.Assert.assertEquals;
32+
import static junit.framework.Assert.assertNull;
3233
import static junit.framework.Assert.assertTrue;
3334
import static junit.framework.Assert.fail;
3435

@@ -37,7 +38,8 @@ public class RecordSaveResponseHandlerUnitTest {
3738
@Test
3839
public void testRecordSaveResponseHandlerSuccessFlow() throws Exception {
3940
JSONObject jsonObject1 = new JSONObject();
40-
jsonObject1.put("_id", "Note/48092492-0791-4120-B314-022202AD3970");
41+
jsonObject1.put("_recordType", "Note");
42+
jsonObject1.put("_recordID", "48092492-0791-4120-B314-022202AD3970");
4143
jsonObject1.put("_created_at", "2016-06-15T07:55:32.342Z");
4244
jsonObject1.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
4345
jsonObject1.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -50,7 +52,8 @@ public void testRecordSaveResponseHandlerSuccessFlow() throws Exception {
5052
jsonObject1.put("abc", 12.345);
5153

5254
JSONObject jsonObject2 = new JSONObject();
53-
jsonObject2.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
55+
jsonObject2.put("_recordType", "Note");
56+
jsonObject2.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
5457
jsonObject2.put("_created_at", "2016-06-15T07:55:32.342Z");
5558
jsonObject2.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
5659
jsonObject2.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -116,7 +119,7 @@ public void onSaveSuccess(Record[] records) {
116119
}
117120

118121
@Override
119-
public void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<String, Error> errors) {
122+
public void onPartiallySaveSuccess(Record[] successRecords, Error[] errors) {
120123
fail("Should not get partial success callback");
121124
}
122125

@@ -133,7 +136,8 @@ public void onSaveFail(Error error) {
133136
@Test
134137
public void testRecordSaveResponseHandlerPartialSuccessFlow() throws Exception {
135138
JSONObject jsonObject1 = new JSONObject();
136-
jsonObject1.put("_id", "Note/48092492-0791-4120-B314-022202AD3970");
139+
jsonObject1.put("_recordType", "Note");
140+
jsonObject1.put("_recordID", "48092492-0791-4120-B314-022202AD3970");
137141
jsonObject1.put("_created_at", "2016-06-15T07:55:32.342Z");
138142
jsonObject1.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
139143
jsonObject1.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -146,7 +150,8 @@ public void testRecordSaveResponseHandlerPartialSuccessFlow() throws Exception {
146150
jsonObject1.put("abc", 12.345);
147151

148152
JSONObject jsonObject2 = new JSONObject();
149-
jsonObject2.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
153+
jsonObject2.put("_recordType", "Note");
154+
jsonObject2.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
150155
jsonObject2.put("_type", "error");
151156
jsonObject2.put("code", 1000);
152157
jsonObject2.put("message", "pq: duplicate key value violates unique constraint \"note__id_key\"");
@@ -167,11 +172,11 @@ public void onSaveSuccess(Record[] records) {
167172
}
168173

169174
@Override
170-
public void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<String, Error> errors) {
171-
assertEquals(1, successRecords.size());
172-
assertEquals(1, errors.size());
175+
public void onPartiallySaveSuccess(Record[] successRecords, Error[] errors) {
176+
assertEquals(2, successRecords.length);
177+
assertEquals(2, errors.length);
173178

174-
Record record1 = successRecords.get("48092492-0791-4120-B314-022202AD3970");
179+
Record record1 = successRecords[0];
175180
assertEquals("Note", record1.getType());
176181
assertEquals("48092492-0791-4120-B314-022202AD3970", record1.getId());
177182
assertEquals(
@@ -189,10 +194,16 @@ public void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<Strin
189194
assertEquals(3, record1.get("foobar"));
190195
assertEquals(12.345, record1.get("abc"));
191196

192-
Error.Code code2 = errors.get("48092492-0791-4120-B314-022202AD3971").getCode();
193-
String reason2 = errors.get("48092492-0791-4120-B314-022202AD3971").getDetailMessage();
194-
assertEquals(Error.Code.UNEXPECTED_ERROR, code2);
195-
assertEquals("pq: duplicate key value violates unique constraint \"note__id_key\"", reason2);
197+
assertNull(successRecords[1]);
198+
199+
assertNull(errors[0]);
200+
201+
Error error2 = errors[1];
202+
assertEquals(Error.Code.UNEXPECTED_ERROR, error2.getCode());
203+
assertEquals(
204+
"pq: duplicate key value violates unique constraint \"note__id_key\"",
205+
error2.getDetailMessage()
206+
);
196207

197208
checkpoints[0] = true;
198209
}
@@ -238,7 +249,7 @@ public void onSaveSuccess(Record[] records) {
238249
}
239250

240251
@Override
241-
public void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<String, Error> errors) {
252+
public void onPartiallySaveSuccess(Record[] successRecords, Error[] errors) {
242253
fail("Should not get partial success callback");
243254
}
244255

@@ -264,7 +275,7 @@ public void onSaveSuccess(Record[] records) {
264275
}
265276

266277
@Override
267-
public void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<String, Error> errors) {
278+
public void onPartiallySaveSuccess(Record[] successRecords, Error[] errors) {
268279
fail("Should not get partial success callback");
269280
}
270281

‎skygear/src/androidTest/java/io/skygear/skygear/RecordSerializerUnitTest.java

+67-12
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public void testRecordDataKeyValidation() throws Exception {
5555
assertFalse(RecordSerializer.isValidKey(""));
5656
assertFalse(RecordSerializer.isValidKey("_id"));
5757
assertFalse(RecordSerializer.isValidKey("_type"));
58+
assertFalse(RecordSerializer.isValidKey("_recordType"));
59+
assertFalse(RecordSerializer.isValidKey("_recordID"));
5860
assertFalse(RecordSerializer.isValidKey("_created_at"));
5961
assertFalse(RecordSerializer.isValidKey("_updated_at"));
6062
assertFalse(RecordSerializer.isValidKey("_ownerID"));
@@ -155,6 +157,8 @@ public void testRecordSerializationNormalFlow() throws Exception {
155157

156158
assertNotNull(jsonObject);
157159
assertEquals("Note/" + aNote.getId(), jsonObject.getString("_id"));
160+
assertEquals("Note", jsonObject.getString("_recordType"));
161+
assertEquals(aNote.getId(), jsonObject.getString("_recordID"));
158162
assertEquals("user123", jsonObject.getString("_ownerID"));
159163
assertEquals("user123", jsonObject.getString("_created_by"));
160164
assertEquals("user456", jsonObject.getString("_updated_by"));
@@ -170,8 +174,10 @@ public void testRecordSerializationNormalFlow() throws Exception {
170174
assertEquals("2016-06-15T07:55:34.342Z", publishDateObject.getString("$date"));
171175

172176
JSONObject commentObject = jsonObject.getJSONObject("comment");
173-
assertEquals("Comment/" + aComment.getId(), commentObject.getString("$id"));
174177
assertEquals("ref", commentObject.getString("$type"));
178+
assertEquals("Comment/" + aComment.getId(), commentObject.getString("$id"));
179+
assertEquals("Comment", commentObject.getString("$recordType"));
180+
assertEquals(aComment.getId(), commentObject.getString("$recordID"));
175181

176182
JSONObject locationObject = jsonObject.getJSONObject("loc");
177183
assertEquals("geo", locationObject.getString("$type"));
@@ -204,6 +210,8 @@ public void testRecordSerializationNormalFlow() throws Exception {
204210

205211
JSONObject commentTransient = transientObject.getJSONObject("comment");
206212
assertEquals("Comment/" + aComment.getId(), commentTransient.getString("_id"));
213+
assertEquals("Comment", commentTransient.getString("_recordType"));
214+
assertEquals(aComment.getId(), commentTransient.getString("_recordID"));
207215
assertEquals("google", commentTransient.getString("okay"));
208216
assertEquals("siri", commentTransient.getString("hey"));
209217
}
@@ -274,13 +282,15 @@ public void testRecordDeserializationNormalFlow() throws Exception {
274282
// prepare reference record data
275283
String referenceRecordId = "7a7873dc-e14b-4b8f-9c51-948da68e924e";
276284
JSONObject referenceRecordData = new JSONObject();
277-
referenceRecordData.put("_id", "Comment/" + referenceRecordId);
285+
referenceRecordData.put("_recordType", "Comment");
286+
referenceRecordData.put("_recordID", referenceRecordId);
278287
referenceRecordData.put("okay", "google");
279288
referenceRecordData.put("hey", "siri");
280289

281290
// prepare record data
282291
JSONObject jsonObject = new JSONObject();
283-
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
292+
jsonObject.put("_recordType", "Note");
293+
jsonObject.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
284294
jsonObject.put("_created_at", "2016-06-15T07:55:32.342Z");
285295
jsonObject.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
286296
jsonObject.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -312,7 +322,8 @@ public void testRecordDeserializationNormalFlow() throws Exception {
312322

313323
JSONObject commentReferenceObject = new JSONObject();
314324
commentReferenceObject.put("$type", "ref");
315-
commentReferenceObject.put("$id", "Comment/" + referenceRecordId);
325+
commentReferenceObject.put("$recordType", "Comment");
326+
commentReferenceObject.put("$recordID", referenceRecordId);
316327
jsonObject.put("comment", commentReferenceObject);
317328

318329
JSONObject unknownValueObject = new JSONObject();
@@ -390,10 +401,49 @@ public void testRecordDeserializationNormalFlow() throws Exception {
390401
assertEquals("siri", commentTransient.get("hey"));
391402
}
392403

393-
@Test(expected = InvalidParameterException.class)
404+
@Test
405+
public void testRecordDeserializationDeprecatedFlow() throws Exception {
406+
JSONObject jsonObject = new JSONObject();
407+
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
408+
jsonObject.put("_created_at", "2016-06-15T07:55:32.342Z");
409+
jsonObject.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
410+
jsonObject.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
411+
jsonObject.put("_updated_at", "2016-06-15T07:55:33.342Z");
412+
jsonObject.put("_updated_by", "5a497b0b-cf93-4720-bea4-14637478cfc2");
413+
jsonObject.put("_access", new JSONArray("[{\"public\":true,\"level\":\"write\"}]"));
414+
415+
jsonObject.put("hello", "world");
416+
jsonObject.put("foobar", 3);
417+
jsonObject.put("abc", 12.345);
418+
419+
Record record = RecordSerializer.deserialize(jsonObject);
420+
421+
assertEquals("Note", record.getType());
422+
assertEquals("48092492-0791-4120-B314-022202AD3971", record.getId());
423+
assertEquals("5a497b0b-cf93-4720-bea4-14637478cfc0", record.getCreatorId());
424+
assertEquals("5a497b0b-cf93-4720-bea4-14637478cfc1", record.getOwnerId());
425+
assertEquals("5a497b0b-cf93-4720-bea4-14637478cfc2", record.getUpdaterId());
426+
427+
assertEquals(
428+
new DateTime(2016, 6, 15, 7, 55, 32, 342, DateTimeZone.UTC).toDate(),
429+
record.getCreatedAt()
430+
);
431+
assertEquals(
432+
new DateTime(2016, 6, 15, 7, 55, 33, 342, DateTimeZone.UTC).toDate(),
433+
record.getUpdatedAt()
434+
);
435+
436+
assertEquals("world", record.get("hello"));
437+
assertEquals(3, record.get("foobar"));
438+
assertEquals(12.345, record.get("abc"));
439+
440+
assertTrue(record.isPublicWritable());
441+
}
442+
443+
@Test(expected = JSONException.class)
394444
public void testRecordDeserializationNotAllowNoId() throws Exception {
395445
JSONObject jsonObject = new JSONObject();
396-
jsonObject.put("_id", "Note");
446+
jsonObject.put("_recordType", "Note");
397447
jsonObject.put("hello", "world");
398448
jsonObject.put("foobar", 3);
399449
jsonObject.put("abc", 12.345);
@@ -405,7 +455,8 @@ public void testRecordDeserializationNotAllowNoId() throws Exception {
405455
/* Regression: https://github.com/SkygearIO/skygear-SDK-Android/issues/23 */
406456
public void testRecordDeserializeJsonObject() throws Exception {
407457
JSONObject jsonObject = new JSONObject();
408-
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
458+
jsonObject.put("_recordType", "Note");
459+
jsonObject.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
409460
jsonObject.put("_created_at", "2016-06-15T07:55:32.342Z");
410461
jsonObject.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
411462
jsonObject.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -423,7 +474,8 @@ public void testRecordDeserializeJsonObject() throws Exception {
423474
/* Regression: https://github.com/SkygearIO/skygear-SDK-Android/issues/23 */
424475
public void testRecordDeserializeArray() throws Exception {
425476
JSONObject jsonObject = new JSONObject();
426-
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
477+
jsonObject.put("_recordType", "Note");
478+
jsonObject.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
427479
jsonObject.put("_created_at", "2016-06-15T07:55:32.342Z");
428480
jsonObject.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
429481
jsonObject.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -443,7 +495,8 @@ public void testRecordDeserializeArray() throws Exception {
443495
/* Regression: https://github.com/SkygearIO/skygear-SDK-Android/issues/44 */
444496
public void testRecordDeserializeWithNullAccess() throws Exception {
445497
JSONObject jsonObject = new JSONObject();
446-
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
498+
jsonObject.put("_recordType", "Note");
499+
jsonObject.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
447500
jsonObject.put("_created_at", "2016-06-15T07:55:32.342Z");
448501
jsonObject.put("_created_by", "5a497b0b-cf93-4720-bea4-14637478cfc0");
449502
jsonObject.put("_ownerID", "5a497b0b-cf93-4720-bea4-14637478cfc1");
@@ -460,7 +513,8 @@ public void testRecordDeserializeWithNullAccess() throws Exception {
460513
/* Regression: https://github.com/SkygearIO/skygear-SDK-Android/issues/45 */
461514
public void testRecordDeserializeNotRequiredMetaData() throws Exception {
462515
JSONObject jsonObject = new JSONObject();
463-
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
516+
jsonObject.put("_recordType", "Note");
517+
jsonObject.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
464518
jsonObject.put("foo", "bar");
465519

466520
RecordSerializer.deserialize(jsonObject);
@@ -470,7 +524,8 @@ public void testRecordDeserializeNotRequiredMetaData() throws Exception {
470524
/* Regression: https://github.com/SkygearIO/skygear-SDK-Android/issues/61 */
471525
public void testRecordDeserializeNull() throws Exception {
472526
JSONObject jsonObject = new JSONObject();
473-
jsonObject.put("_id", "Note/48092492-0791-4120-B314-022202AD3971");
527+
jsonObject.put("_recordType", "Note");
528+
jsonObject.put("_recordID", "48092492-0791-4120-B314-022202AD3971");
474529
jsonObject.put("null-value-key", JSONObject.NULL);
475530

476531
Record aNote = RecordSerializer.deserialize(jsonObject);
@@ -483,7 +538,7 @@ public void testRecordSerializeAndDeserialize() throws Exception {
483538
JSONObject jsonObject = RecordSerializer.serialize(note);
484539
Record record = RecordSerializer.deserialize(jsonObject);
485540

486-
assertEquals(note.id, record.id);
541+
assertEquals(note.getId(), record.getId());
487542
assertEquals(note.ownerId, record.ownerId);
488543
assertEquals(note.createdAt, record.createdAt);
489544
assertEquals(note.creatorId, record.creatorId);

‎skygear/src/androidTest/java/io/skygear/skygear/ReferenceSerializerUnitTest.java

+45
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.junit.runner.RunWith;
2525

2626
import static junit.framework.Assert.assertEquals;
27+
import static junit.framework.Assert.assertTrue;
28+
import static junit.framework.Assert.assertFalse;
2729

2830
@RunWith(AndroidJUnit4.class)
2931
public class ReferenceSerializerUnitTest {
@@ -34,10 +36,24 @@ public void testReferenceSerializationNormalFlow() throws Exception {
3436

3537
assertEquals("ref", jsonObject.getString("$type"));
3638
assertEquals("Note/c71c6ce4-a3c7-4b7d-833b-46b7df8cec03", jsonObject.getString("$id"));
39+
assertEquals("Note", jsonObject.getString("$recordType"));
40+
assertEquals("c71c6ce4-a3c7-4b7d-833b-46b7df8cec03", jsonObject.getString("$recordID"));
3741
}
3842

3943
@Test
4044
public void testReferenceDeserializationNormalFlow() throws Exception {
45+
JSONObject jsonObject = new JSONObject();
46+
jsonObject.put("$type", "ref");
47+
jsonObject.put("$recordType", "Note");
48+
jsonObject.put("$recordID", "c71c6ce4-a3c7-4b7d-833b-46b7df8cec03");
49+
50+
Reference ref = ReferenceSerializer.deserialize(jsonObject);
51+
assertEquals("Note", ref.getType());
52+
assertEquals("c71c6ce4-a3c7-4b7d-833b-46b7df8cec03", ref.getId());
53+
}
54+
55+
@Test
56+
public void testReferenceDeserializationDeprecatedFlow() throws Exception {
4157
JSONObject jsonObject = new JSONObject();
4258
jsonObject.put("$type", "ref");
4359
jsonObject.put("$id", "Note/c71c6ce4-a3c7-4b7d-833b-46b7df8cec03");
@@ -46,4 +62,33 @@ public void testReferenceDeserializationNormalFlow() throws Exception {
4662
assertEquals("Note", ref.getType());
4763
assertEquals("c71c6ce4-a3c7-4b7d-833b-46b7df8cec03", ref.getId());
4864
}
65+
66+
@Test
67+
public void testReferenceFormatChecking() throws Exception {
68+
assertFalse(ReferenceSerializer.isReferenceFormat(null));
69+
assertFalse(ReferenceSerializer.isReferenceFormat(new Object()));
70+
assertFalse(ReferenceSerializer.isReferenceFormat(new JSONObject("{}")));
71+
assertFalse(ReferenceSerializer.isReferenceFormat(
72+
new JSONObject("{\"$type\": \"not-ref\"}")
73+
));
74+
assertFalse(ReferenceSerializer.isReferenceFormat(
75+
new JSONObject("{\"$type\": \"ref\", \"$recordType\": \"Note\"}")
76+
));
77+
assertFalse(ReferenceSerializer.isReferenceFormat(
78+
new JSONObject("{\"$type\": \"ref\", \"$recordID\": \"some-id\"}")
79+
));
80+
assertFalse(ReferenceSerializer.isReferenceFormat(
81+
new JSONObject("{\"$type\": \"ref\", \"$id\": \"some-id\"}")
82+
));
83+
assertTrue(ReferenceSerializer.isReferenceFormat(
84+
new JSONObject("{" +
85+
"\"$type\": \"ref\", " +
86+
"\"$recordType\": \"some-id\", " +
87+
"\"$recordID\": \"some-id\"" +
88+
"}")
89+
));
90+
assertTrue(ReferenceSerializer.isReferenceFormat(
91+
new JSONObject("{\"$type\": \"ref\", \"$id\": \"Note/some-id\"}")
92+
));
93+
}
4994
}

‎skygear/src/androidTest/java/io/skygear/skygear/ValueSerializerUnitTest.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,14 @@ public void testSerializeReference() throws Exception {
147147
JSONObject jsonValue = (JSONObject)ValueSerializer.serialize((Object)value);
148148

149149
assertEquals("ref", jsonValue.getString("$type"));
150-
assertEquals("Comment/7a7873dc-e14b-4b8f-9c51-948da68e924e", jsonValue.getString("$id"));
150+
assertEquals("Comment", jsonValue.getString("$recordType"));
151+
assertEquals("7a7873dc-e14b-4b8f-9c51-948da68e924e",
152+
jsonValue.getString("$recordID")
153+
);
154+
assertEquals(
155+
"Comment/7a7873dc-e14b-4b8f-9c51-948da68e924e",
156+
jsonValue.getString("$id")
157+
);
151158
}
152159

153160
@Test
@@ -188,7 +195,18 @@ public void testSerializeRecord() throws Exception {
188195
JSONObject jsonValue = (JSONObject)ValueSerializer.serialize((Object)value);
189196

190197
assertEquals("record", jsonValue.getString("$type"));
191-
assertEquals("note/48092492-0791-4120-b314-022202ad3971", jsonValue.getJSONObject("$record").getString("_id"));
198+
assertEquals(
199+
"note/48092492-0791-4120-b314-022202ad3971",
200+
jsonValue.getJSONObject("$record").getString("_id")
201+
);
202+
assertEquals(
203+
"note",
204+
jsonValue.getJSONObject("$record").getString("_recordType")
205+
);
206+
assertEquals(
207+
"48092492-0791-4120-b314-022202ad3971",
208+
jsonValue.getJSONObject("$record").getString("_recordID")
209+
);
192210
}
193211

194212
@Test

‎skygear/src/main/java/io/skygear/skygear/Database.java

+25-10
Original file line numberDiff line numberDiff line change
@@ -269,16 +269,6 @@ public void onFailure(Error error) {
269269
});
270270
}
271271

272-
/**
273-
* Save a record atomically.
274-
*
275-
* @param record the record
276-
* @param handler the response handler
277-
*/
278-
public void saveAtomically(Record record, RecordSaveResponseHandler handler) {
279-
this.saveAtomically(new Record[]{ record }, handler);
280-
}
281-
282272
/**
283273
* Save multiple records atomically.
284274
*
@@ -341,6 +331,31 @@ public void delete(Record[] records, RecordDeleteResponseHandler handler) {
341331
this.getContainer().sendRequest(request);
342332
}
343333

334+
/**
335+
* Delete a record.
336+
*
337+
* @param recordType the record type
338+
* @param recordID the record ID
339+
* @param handler the response handler
340+
*/
341+
public void delete(String recordType, String recordID, RecordDeleteResponseHandler handler) {
342+
this.delete(recordType, new String[] { recordID }, handler);
343+
}
344+
345+
/**
346+
* Delete multiple records.
347+
*
348+
* @param recordType the record type
349+
* @param recordIDs the record IDs
350+
* @param handler the response handler
351+
*/
352+
public void delete(String recordType, String[] recordIDs, RecordDeleteResponseHandler handler) {
353+
RecordDeleteRequest request = new RecordDeleteRequest(recordType, recordIDs, this);
354+
request.responseHandler = handler;
355+
356+
this.getContainer().sendRequest(request);
357+
}
358+
344359
/**
345360
* Upload asset.
346361
*

‎skygear/src/main/java/io/skygear/skygear/ErrorSerializer.java

+15-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
*/
2828
public class ErrorSerializer {
2929

30+
private static final String ErrorSerializationCodeKey = "code";
31+
private static final String ErrorSerializationNameKey = "name";
32+
private static final String ErrorSerializationMessageKey = "message";
33+
private static final String ErrorSerializationExtraInfoKey = "info";
34+
3035
/**
3136
* Serializes an error object
3237
*
@@ -36,13 +41,14 @@ public class ErrorSerializer {
3641
public static JSONObject serialize(Error errorObject) {
3742
try {
3843
JSONObject jsonObject = new JSONObject();
39-
jsonObject.put("message", errorObject.getDetailMessage());
40-
jsonObject.put("code", errorObject.getCodeValue());
41-
jsonObject.put("name", errorObject.getName());
44+
45+
jsonObject.put(ErrorSerializationCodeKey, errorObject.getCodeValue());
46+
jsonObject.put(ErrorSerializationNameKey, errorObject.getName());
47+
jsonObject.put(ErrorSerializationMessageKey, errorObject.getDetailMessage());
4248

4349
JSONObject infoObject = errorObject.getInfo();
4450
if (infoObject != null) {
45-
jsonObject.put("info", infoObject);
51+
jsonObject.put(ErrorSerializationExtraInfoKey, infoObject);
4652
}
4753

4854
return jsonObject;
@@ -59,10 +65,11 @@ public static JSONObject serialize(Error errorObject) {
5965
* @throws JSONException the json exception
6066
*/
6167
public static Error deserialize(JSONObject jsonObject) throws JSONException {
62-
String errorString = jsonObject.optString("message");
63-
int errorCodeValue = jsonObject.getInt("code");
64-
String errorName = jsonObject.optString("name");
65-
JSONObject errorInfo = jsonObject.optJSONObject("info");
68+
69+
int errorCodeValue = jsonObject.getInt(ErrorSerializationCodeKey);
70+
String errorName = jsonObject.optString(ErrorSerializationNameKey);
71+
String errorString = jsonObject.optString(ErrorSerializationMessageKey);
72+
JSONObject errorInfo = jsonObject.optJSONObject(ErrorSerializationExtraInfoKey);
6673

6774
return new Error(errorCodeValue, errorName, errorString, errorInfo);
6875
}

‎skygear/src/main/java/io/skygear/skygear/RecordDeleteRequest.java

+60-19
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,35 @@
1717

1818
package io.skygear.skygear;
1919

20+
import android.util.Log;
21+
2022
import org.json.JSONArray;
23+
import org.json.JSONException;
24+
import org.json.JSONObject;
2125

2226
import java.security.InvalidParameterException;
2327
import java.util.ArrayList;
24-
import java.util.Arrays;
2528
import java.util.HashMap;
26-
import java.util.HashSet;
2729
import java.util.List;
28-
import java.util.Set;
30+
31+
import static io.skygear.skygear.RecordSerializer.RecordIdentifier;
2932

3033
/**
3134
* The Skygear Record Delete Request.
3235
*/
3336
public class RecordDeleteRequest extends Request {
37+
private static final String TAG = "Skygear SDK";
38+
3439
private String databaseId;
35-
private List<Record> records;
40+
private List<RecordIdentifier> recordIdentifiers;
3641

3742
/**
3843
* Instantiates a new record delete request with default properties.
3944
*/
4045
RecordDeleteRequest() {
4146
super("record:delete");
4247
this.data = new HashMap<>();
43-
this.records = new ArrayList<>();
48+
this.recordIdentifiers = new ArrayList<>();
4449
}
4550

4651
/**
@@ -52,17 +57,62 @@ public class RecordDeleteRequest extends Request {
5257
public RecordDeleteRequest(Record[] records, Database database) {
5358
this();
5459
this.databaseId = database.getName();
55-
this.records = Arrays.asList(records);
60+
61+
for (Record perRecord : records) {
62+
this.recordIdentifiers.add(
63+
new RecordIdentifier(perRecord.type, perRecord.id)
64+
);
65+
}
66+
67+
this.updateData();
68+
}
69+
70+
/**
71+
* Instantiates a new record delete request.
72+
*
73+
* @param recordType the record type
74+
* @param recordIDs the record IDs
75+
* @param database the database
76+
*/
77+
public RecordDeleteRequest(String recordType, String[] recordIDs, Database database) {
78+
this();
79+
this.databaseId = database.getName();
80+
81+
for (String perRecordID: recordIDs) {
82+
this.recordIdentifiers.add(
83+
new RecordIdentifier(recordType, perRecordID)
84+
);
85+
}
86+
5687
this.updateData();
5788
}
5889

5990
private void updateData() {
60-
JSONArray recordArray = new JSONArray();
61-
for (Record perRecord : this.records) {
62-
recordArray.put(String.format("%s/%s", perRecord.getType(), perRecord.getId()));
91+
JSONArray deprecatedIDs = new JSONArray();
92+
JSONArray recordIdentifiers = new JSONArray();
93+
94+
try {
95+
for (RecordIdentifier perRecordIdentifier : this.recordIdentifiers) {
96+
String perRecordDeprecatedID = String.format(
97+
"%s/%s",
98+
perRecordIdentifier.type,
99+
perRecordIdentifier.id
100+
);
101+
102+
deprecatedIDs.put(perRecordDeprecatedID);
103+
104+
JSONObject perRecordIdentifierData = new JSONObject();
105+
perRecordIdentifierData.put("_recordType", perRecordIdentifier.type);
106+
perRecordIdentifierData.put("_recordID", perRecordIdentifier.id);
107+
108+
recordIdentifiers.put(perRecordIdentifierData);
109+
}
110+
} catch(JSONException e) {
111+
Log.w(TAG, "Fail to serialize record identifiers");
63112
}
64113

65-
this.data.put("ids", recordArray);
114+
this.data.put("ids", deprecatedIDs);
115+
this.data.put("records", recordIdentifiers);
66116
this.data.put("database_id", this.databaseId);
67117
}
68118

@@ -74,14 +124,5 @@ protected void validate() throws Exception {
74124
if (ids.length() == 0) {
75125
throw new InvalidParameterException("No records to be processed");
76126
}
77-
78-
Set<String> typeSet = new HashSet<>();
79-
for (Record perRecord : this.records) {
80-
typeSet.add(perRecord.type);
81-
}
82-
83-
if (typeSet.size() > 1) {
84-
throw new InvalidParameterException("Only records in the same type are allowed");
85-
}
86127
}
87128
}

‎skygear/src/main/java/io/skygear/skygear/RecordDeleteResponseHandler.java

+25-14
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ public abstract class RecordDeleteResponseHandler implements ResponseHandler {
4141
/**
4242
* Partially delete success callback.
4343
*
44-
* @param ids the deleted record ids
45-
* @param errors the errors (recordId to error)
44+
* @param ids the deleted record ids (null when fail to delete the corresponding record)
45+
* @param errors the errors (null when the corresponding record is deleted successfully)
4646
*/
47-
public abstract void onDeletePartialSuccess(String[] ids, Map<String, Error> errors);
47+
public abstract void onDeletePartialSuccess(String[] ids, Error[] errors);
4848

4949
/**
5050
* Delete fail callback.
@@ -58,19 +58,27 @@ public void onSuccess(JSONObject result) {
5858
try {
5959
JSONArray results = result.getJSONArray("result");
6060
List<String> successList = new LinkedList<>();
61-
Map<String, Error> errorMap = new TreeMap<>();
61+
List<Error> errorList = new LinkedList<>();
62+
63+
boolean hasSuccess = false;
64+
boolean hasError = false;
6265

6366
for (int idx = 0; idx < results.length(); idx++) {
6467
JSONObject perResult = results.getJSONObject(idx);
65-
String perResultId = perResult.getString("_id").split("/", 2)[1];
68+
String perResultId = perResult.getString("_recordID");
6669
String perResultType = perResult.getString("_type");
6770

6871
switch (perResultType) {
69-
case "record":
72+
case "record": {
73+
hasSuccess = true;
7074
successList.add(perResultId);
75+
errorList.add(null);
7176
break;
77+
}
7278
case "error": {
73-
errorMap.put(perResultId, ErrorSerializer.deserialize(perResult));
79+
hasError = true;
80+
successList.add(null);
81+
errorList.add(ErrorSerializer.deserialize(perResult));
7482
break;
7583
}
7684
default: {
@@ -84,15 +92,18 @@ public void onSuccess(JSONObject result) {
8492
}
8593
}
8694

87-
if (errorMap.size() == 0) {
88-
// all success
89-
this.onDeleteSuccess(successList.toArray(new String[]{}));
90-
} else if (successList.size() == 0) {
95+
if (hasSuccess && hasError) {
96+
// partial success
97+
this.onDeletePartialSuccess(
98+
successList.toArray(new String[]{}),
99+
errorList.toArray(new Error[]{})
100+
);
101+
} else if (hasError) {
91102
// all fail
92-
this.onDeleteFail(errorMap.values().iterator().next());
103+
this.onFail(errorList.get(0));
93104
} else {
94-
// partial success
95-
this.onDeletePartialSuccess(successList.toArray(new String[]{}), errorMap);
105+
// all success
106+
this.onDeleteSuccess(successList.toArray(new String[]{}));
96107
}
97108
} catch (JSONException e) {
98109
this.onDeleteFail(new Error("Malformed server response"));

‎skygear/src/main/java/io/skygear/skygear/RecordSaveRequest.java

+1-8
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public boolean getAtomic() {
6464

6565
public void setAtomic(boolean atomic) {
6666
this.atomic = atomic;
67-
this.updateData();
67+
this.data.put("atomic", this.atomic);
6868
}
6969

7070
private void updateData() {
@@ -88,19 +88,12 @@ protected void validate() throws Exception {
8888
throw new InvalidParameterException("No records to be processed");
8989
}
9090

91-
Set<String> typeSet = new HashSet<>();
9291
for (Record perRecord : this.records) {
93-
typeSet.add(perRecord.type);
94-
9592
for (Object perRecordPerValue : perRecord.getData().values()) {
9693
if (perRecordPerValue instanceof Asset && ((Asset) perRecordPerValue).isPendingUpload()) {
9794
throw new InvalidParameterException("Cannot save records with pending upload asset");
9895
}
9996
}
10097
}
101-
102-
if (typeSet.size() > 1) {
103-
throw new InvalidParameterException("Only records in the same type are allowed");
104-
}
10598
}
10699
}

‎skygear/src/main/java/io/skygear/skygear/RecordSaveResponseHandler.java

+33-20
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.json.JSONException;
2222
import org.json.JSONObject;
2323

24+
import java.util.LinkedList;
25+
import java.util.List;
2426
import java.util.Map;
2527
import java.util.TreeMap;
2628

@@ -39,10 +41,11 @@ public abstract class RecordSaveResponseHandler implements ResponseHandler {
3941
/**
4042
* partially save success callback.
4143
*
42-
* @param successRecords the successfully saved record map (recordId to record)
43-
* @param errors the errors (recordId to error)
44+
* @param successRecords the successfully saved record
45+
* (null when fail to save the corresponding record)
46+
* @param errors the errors (null when the corresponding record is saved successfully)
4447
*/
45-
public abstract void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<String, Error> errors);
48+
public abstract void onPartiallySaveSuccess(Record[] successRecords, Error[] errors);
4649

4750
/**
4851
* Save fail callback.
@@ -55,20 +58,30 @@ public abstract class RecordSaveResponseHandler implements ResponseHandler {
5558
public void onSuccess(JSONObject result) {
5659
try {
5760
JSONArray results = result.getJSONArray("result");
58-
Map<String, Record> recordMap = new TreeMap<>();
59-
Map<String, Error> errorMap = new TreeMap<>();
6061

61-
for (int idx = 0; idx < results.length(); idx++) {
62+
int resultSize = results.length();
63+
64+
List<Record> recordList = new LinkedList<>();
65+
List<Error> errorList = new LinkedList<>();
66+
67+
boolean hasSuccess = false;
68+
boolean hasError = false;
69+
70+
for (int idx = 0; idx < resultSize; idx++) {
6271
JSONObject perResult = results.getJSONObject(idx);
63-
String perResultId = perResult.getString("_id").split("/", 2)[1];
6472
String perResultType = perResult.getString("_type");
6573

6674
switch (perResultType) {
67-
case "record":
68-
recordMap.put(perResultId, Record.fromJson(perResult));
75+
case "record": {
76+
hasSuccess = true;
77+
recordList.add(Record.fromJson(perResult));
78+
errorList.add(null);
6979
break;
80+
}
7081
case "error":{
71-
errorMap.put(perResultId, ErrorSerializer.deserialize(perResult));
82+
hasError = true;
83+
recordList.add(null);
84+
errorList.add(ErrorSerializer.deserialize(perResult));
7285
break;
7386
}
7487
default: {
@@ -82,18 +95,18 @@ public void onSuccess(JSONObject result) {
8295
}
8396
}
8497

85-
if (errorMap.size() == 0) {
86-
// all success
87-
Record[] records = new Record[recordMap.size()];
88-
recordMap.values().toArray(records);
89-
90-
this.onSaveSuccess(records);
91-
} else if (recordMap.size() == 0) {
98+
if (hasSuccess && hasError) {
99+
// partial success
100+
this.onPartiallySaveSuccess(
101+
recordList.toArray(new Record[resultSize]),
102+
errorList.toArray(new Error[resultSize])
103+
);
104+
} else if (hasError) {
92105
// all fail
93-
this.onSaveFail(errorMap.values().iterator().next());
106+
this.onSaveFail(errorList.get(0));
94107
} else {
95-
// partial success
96-
this.onPartiallySaveSuccess(recordMap, errorMap);
108+
// all success
109+
this.onSaveSuccess(recordList.toArray(new Record[resultSize]));
97110
}
98111
} catch (JSONException e) {
99112
this.onSaveFail(new Error("Malformed server response"));

‎skygear/src/main/java/io/skygear/skygear/RecordSerializer.java

+106-41
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,32 @@
4141
* The Skygear Record Serializer.
4242
*/
4343
public class RecordSerializer {
44+
45+
private static final String RecordSerializationDeprecatedIDKey = "_id";
46+
private static final String RecordSerializationResponseTypeKey = "_type";
47+
private static final String RecordSerializationRecordTypeKey = "_recordType";
48+
private static final String RecordSerializationRecordIDKey = "_recordID";
49+
private static final String RecordSerializationCreationDatetimeKey = "_created_at";
50+
private static final String RecordSerializationUpdateDatetimeKey = "_updated_at";
51+
private static final String RecordSerializationOwnerIDKey = "_ownerID";
52+
private static final String RecordSerializationCreatorIDKey = "_created_by";
53+
private static final String RecordSerializationUpdaterIDKey = "_updated_by";
54+
private static final String RecordSerializationAccessKey = "_access";
55+
private static final String RecordSerializationTransientKey = "_transient";
56+
4457
private static final String TAG = "Skygear SDK";
4558
private static List<String> ReservedKeys = Arrays.asList(
46-
"_id",
47-
"_type",
48-
"_created_at",
49-
"_updated_at",
50-
"_ownerID",
51-
"_created_by",
52-
"_updated_by",
53-
"_access",
54-
"_transient"
59+
RecordSerializationDeprecatedIDKey,
60+
RecordSerializationResponseTypeKey,
61+
RecordSerializationRecordTypeKey,
62+
RecordSerializationRecordIDKey,
63+
RecordSerializationCreationDatetimeKey,
64+
RecordSerializationUpdateDatetimeKey,
65+
RecordSerializationOwnerIDKey,
66+
RecordSerializationCreatorIDKey,
67+
RecordSerializationUpdaterIDKey,
68+
RecordSerializationAccessKey,
69+
RecordSerializationTransientKey
5570
);
5671

5772
private static Set<? extends Class> CompatibleValueClasses = new HashSet<>(Arrays.asList(
@@ -161,25 +176,40 @@ public static JSONObject serialize(Record record) {
161176
try {
162177
JSONObject jsonObject = RecordSerializer.serialize(record.data);
163178

164-
jsonObject.put("_id", String.format("%s/%s", record.type, record.id));
179+
jsonObject.put(RecordSerializationDeprecatedIDKey, String.format(
180+
"%s/%s",
181+
record.getType(),
182+
record.getId()
183+
));
184+
jsonObject.put(RecordSerializationRecordTypeKey, record.getType());
185+
jsonObject.put(RecordSerializationRecordIDKey, record.getId());
165186
if (record.createdAt != null) {
166-
jsonObject.put("_created_at", DateSerializer.stringFromDate(record.createdAt));
187+
jsonObject.put(
188+
RecordSerializationCreationDatetimeKey,
189+
DateSerializer.stringFromDate(record.createdAt)
190+
);
167191
}
168192
if (record.updatedAt != null) {
169-
jsonObject.put("_updated_at", DateSerializer.stringFromDate(record.updatedAt));
193+
jsonObject.put(
194+
RecordSerializationUpdateDatetimeKey,
195+
DateSerializer.stringFromDate(record.updatedAt)
196+
);
170197
}
171198
if (record.creatorId != null) {
172-
jsonObject.put("_created_by", record.creatorId);
199+
jsonObject.put(RecordSerializationCreatorIDKey, record.creatorId);
173200
}
174201
if (record.updaterId != null) {
175-
jsonObject.put("_updated_by", record.updaterId);
202+
jsonObject.put(RecordSerializationUpdaterIDKey, record.updaterId);
176203
}
177204
if (record.ownerId != null) {
178-
jsonObject.put("_ownerID", record.ownerId);
205+
jsonObject.put(RecordSerializationOwnerIDKey, record.ownerId);
179206
}
180207

181208
if (record.getAccess() != null) {
182-
jsonObject.put("_access", AccessControlSerializer.serialize(record.getAccess()));
209+
jsonObject.put(
210+
RecordSerializationAccessKey,
211+
AccessControlSerializer.serialize(record.getAccess())
212+
);
183213
}
184214

185215
// handle _transient
@@ -195,7 +225,7 @@ public static JSONObject serialize(Record record) {
195225
}
196226
}
197227

198-
jsonObject.put("_transient", transientObject);
228+
jsonObject.put(RecordSerializationTransientKey, transientObject);
199229
}
200230

201231
return jsonObject;
@@ -214,49 +244,41 @@ public static JSONObject serialize(Record record) {
214244
* @throws JSONException the JSON exception
215245
*/
216246
public static Record deserialize(JSONObject jsonObject) throws JSONException {
217-
String typedId = jsonObject.optString("_id");
218-
String[] split = typedId.split("/", 2);
219-
220-
if (split.length < 2 || split[1].length() == 0) {
221-
throw new InvalidParameterException("_id field is malformed");
222-
}
223-
224-
// handle _id
225-
Record record = new Record(split[0]);
226-
record.id = split[1];
247+
RecordIdentifier identifier = RecordSerializer.deserializeRecordIdentifer(jsonObject);
248+
Record record = new Record(identifier.type, identifier.id);
227249

228250
// handle _create_at
229-
if (jsonObject.has("_created_at")) {
230-
String createdAtString = jsonObject.getString("_created_at");
251+
if (jsonObject.has(RecordSerializationCreationDatetimeKey)) {
252+
String createdAtString = jsonObject.getString(RecordSerializationCreationDatetimeKey);
231253
record.createdAt = DateSerializer.dateFromString(createdAtString);
232254
}
233255

234256
// handle _updated_at
235-
if (jsonObject.has("_updated_at")) {
236-
String updatedAtString = jsonObject.getString("_updated_at");
257+
if (jsonObject.has(RecordSerializationUpdateDatetimeKey)) {
258+
String updatedAtString = jsonObject.getString(RecordSerializationUpdateDatetimeKey);
237259
record.updatedAt = DateSerializer.dateFromString(updatedAtString);
238260
}
239261

240262
// handle _created_by, _updated_by, _ownerID
241-
if (jsonObject.has("_created_by")) {
242-
record.creatorId = jsonObject.getString("_created_by");
263+
if (jsonObject.has(RecordSerializationCreatorIDKey)) {
264+
record.creatorId = jsonObject.getString(RecordSerializationCreatorIDKey);
243265
}
244-
if (jsonObject.has("_updated_by")) {
245-
record.updaterId = jsonObject.getString("_updated_by");
266+
if (jsonObject.has(RecordSerializationUpdaterIDKey)) {
267+
record.updaterId = jsonObject.getString(RecordSerializationUpdaterIDKey);
246268
}
247-
if (jsonObject.has("_ownerID")) {
248-
record.ownerId = jsonObject.getString("_ownerID");
269+
if (jsonObject.has(RecordSerializationOwnerIDKey)) {
270+
record.ownerId = jsonObject.getString(RecordSerializationOwnerIDKey);
249271
}
250272

251273
// handle _access
252274
JSONArray accessJsonArray = null;
253-
if (!jsonObject.isNull("_access")) {
254-
accessJsonArray = jsonObject.getJSONArray("_access");
275+
if (!jsonObject.isNull(RecordSerializationAccessKey)) {
276+
accessJsonArray = jsonObject.getJSONArray(RecordSerializationAccessKey);
255277
}
256278

257279
// handle _transient
258-
if (!jsonObject.isNull("_transient")) {
259-
JSONObject transientObject = jsonObject.getJSONObject("_transient");
280+
if (!jsonObject.isNull(RecordSerializationTransientKey)) {
281+
JSONObject transientObject = jsonObject.getJSONObject(RecordSerializationTransientKey);
260282
Iterator<String> transientKeys = transientObject.keys();
261283

262284
while(transientKeys.hasNext()) {
@@ -303,4 +325,47 @@ public static Record deserialize(JSONObject jsonObject) throws JSONException {
303325

304326
return record;
305327
}
328+
329+
/**
330+
* RecordIdentifier is a data class representing the identifier of a record, which includes
331+
* the type and the ID of the record.
332+
*
333+
* RecordIdentifier is only used within this package.
334+
*/
335+
static class RecordIdentifier {
336+
final String type;
337+
final String id;
338+
339+
RecordIdentifier(String type, String id) {
340+
this.type = type;
341+
this.id = id;
342+
}
343+
}
344+
345+
static RecordIdentifier deserializeRecordIdentifer(JSONObject jsonObject)
346+
throws JSONException
347+
{
348+
String recordType;
349+
String recordID;
350+
try {
351+
recordType = jsonObject.getString(RecordSerializationRecordTypeKey);
352+
recordID = jsonObject.getString(RecordSerializationRecordIDKey);
353+
} catch (JSONException e) {
354+
String typedId = jsonObject.getString(RecordSerializationDeprecatedIDKey);
355+
String[] split = typedId.split("/", 2);
356+
357+
if (split.length != 2 || split[0].length() == 0 || split[1].length() == 0) {
358+
throw new JSONException(String.format(
359+
"%s and / or %s is malformed",
360+
RecordSerializationRecordTypeKey,
361+
RecordSerializationRecordIDKey
362+
));
363+
}
364+
365+
recordType = split[0];
366+
recordID = split[1];
367+
}
368+
369+
return new RecordIdentifier(recordType, recordID);
370+
}
306371
}

‎skygear/src/main/java/io/skygear/skygear/ReferenceSerializer.java

+65-14
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,22 @@
2020
import org.json.JSONException;
2121
import org.json.JSONObject;
2222

23+
import static io.skygear.skygear.RecordSerializer.RecordIdentifier;
24+
2325
/**
2426
* The Skygear Record Reference Serializer.
2527
* <p>
2628
* This class converts between record reference object and JSON object in Skygear defined format.
2729
*/
2830
public class ReferenceSerializer {
2931

32+
private static final String ReferenceSerializationTypeKey = "$type";
33+
private static final String ReferenceSerializationDeprecatedIDKey = "$id";
34+
private static final String ReferenceSerializationRecordTypeKey = "$recordType";
35+
private static final String ReferenceSerializationRecordIDKey = "$recordID";
36+
37+
private static final String ReferenceSerializationTypeValue = "ref";
38+
3039
/**
3140
* Serializes a record reference
3241
*
@@ -36,8 +45,13 @@ public class ReferenceSerializer {
3645
public static JSONObject serialize(Reference reference) {
3746
try {
3847
JSONObject jsonObject = new JSONObject();
39-
jsonObject.put("$type", "ref");
40-
jsonObject.put("$id", reference.getType() + '/' + reference.getId());
48+
jsonObject.put(ReferenceSerializationTypeKey, ReferenceSerializationTypeValue);
49+
jsonObject.put(
50+
ReferenceSerializationDeprecatedIDKey,
51+
reference.getType() + '/' + reference.getId()
52+
);
53+
jsonObject.put(ReferenceSerializationRecordTypeKey, reference.getType());
54+
jsonObject.put(ReferenceSerializationRecordIDKey, reference.getId());
4155

4256
return jsonObject;
4357
} catch (JSONException e) {
@@ -53,16 +67,11 @@ public static JSONObject serialize(Reference reference) {
5367
* @throws JSONException the json exception
5468
*/
5569
public static Reference deserialize(JSONObject jsonObject) throws JSONException {
56-
String typeValue = jsonObject.getString("$type");
57-
if (typeValue.equals("ref")) {
58-
String typedId = jsonObject.getString("$id");
59-
String[] split = typedId.split("/", 2);
60-
61-
if (split.length < 2 || split[1].length() == 0) {
62-
throw new JSONException("$id field is malformed");
63-
}
64-
65-
return new Reference(split[0], split[1]);
70+
String typeValue = jsonObject.getString(ReferenceSerializationTypeKey);
71+
if (typeValue.equals(ReferenceSerializationTypeValue)) {
72+
RecordIdentifier identifier
73+
= ReferenceSerializer.deserializeRecordIdentifer(jsonObject);
74+
return new Reference(identifier.type, identifier.id);
6675
}
6776

6877
throw new JSONException("Invalid $type value: " + typeValue);
@@ -77,12 +86,54 @@ public static Reference deserialize(JSONObject jsonObject) throws JSONException
7786
public static boolean isReferenceFormat(Object object) {
7887
try {
7988
JSONObject jsonObject = (JSONObject) object;
80-
return jsonObject.getString("$type").equals("ref") &&
81-
!jsonObject.isNull("$id");
89+
if (jsonObject == null) {
90+
return false;
91+
}
92+
93+
if (!jsonObject.getString(ReferenceSerializationTypeKey)
94+
.equals(ReferenceSerializationTypeValue)
95+
) {
96+
return false;
97+
}
98+
99+
try {
100+
RecordIdentifier identifier
101+
= ReferenceSerializer.deserializeRecordIdentifer(jsonObject);
102+
return identifier.type.length() > 0 && identifier.id.length() > 0;
103+
} catch (JSONException e) {
104+
return false;
105+
}
82106
} catch (ClassCastException e) {
83107
return false;
84108
} catch (JSONException e) {
85109
return false;
86110
}
87111
}
112+
113+
static RecordIdentifier deserializeRecordIdentifer(JSONObject jsonObject)
114+
throws JSONException
115+
{
116+
String recordType;
117+
String recordID;
118+
try {
119+
recordType = jsonObject.getString(ReferenceSerializationRecordTypeKey);
120+
recordID = jsonObject.getString(ReferenceSerializationRecordIDKey);
121+
} catch (JSONException e) {
122+
String typedId = jsonObject.getString(ReferenceSerializationDeprecatedIDKey);
123+
String[] split = typedId.split("/", 2);
124+
125+
if (split.length != 2 || split[0].length() == 0 || split[1].length() == 0) {
126+
throw new JSONException(String.format(
127+
"%s and / or %s are malformed",
128+
ReferenceSerializationRecordTypeKey,
129+
ReferenceSerializationRecordIDKey
130+
));
131+
}
132+
133+
recordType = split[0];
134+
recordID = split[1];
135+
}
136+
137+
return new RecordIdentifier(recordType, recordID);
138+
}
88139
}

‎skygear_example/src/main/java/io/skygear/skygear_example/RecordCreateActivity.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ public void onSaveSuccess(Record[] records) {
308308
}
309309

310310
@Override
311-
public void onPartiallySaveSuccess(Map<String, Record> successRecords, Map<String, Error> errors) {
311+
public void onPartiallySaveSuccess(Record[] successRecords, Error[] errors) {
312312
failDialog.setMessage("Unexpected Error");
313313
failDialog.show();
314314
}
@@ -366,7 +366,7 @@ public void onDeleteSuccess(String[] ids) {
366366
}
367367

368368
@Override
369-
public void onDeletePartialSuccess(String[] ids, Map<String, Error> errors) {
369+
public void onDeletePartialSuccess(String[] ids, Error[] errors) {
370370
failDialog.setMessage("Unexpected Error");
371371
failDialog.show();
372372
}

‎skygear_example/src/main/java/io/skygear/skygear_example/RecordQueryActivity.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,24 @@ public void onDeleteSuccess(String[] ids) {
242242
}
243243

244244
@Override
245-
public void onDeletePartialSuccess(String[] ids, Map<String, Error> errors) {
245+
public void onDeletePartialSuccess(String[] ids, Error[] errors) {
246246
RecordQueryActivity.this.records = null;
247+
int successCount = 0;
248+
for (String eachId: ids) {
249+
if (eachId != null) {
250+
successCount++;
251+
}
252+
}
253+
254+
int errorCount = 0;
255+
for (Error eachError: errors) {
256+
if (eachError != null) {
257+
errorCount++;
258+
}
259+
}
260+
247261
partiallySuccessDialog.setMessage(
248-
String.format("%d successes\n%d fails", ids.length, errors.size())
262+
String.format("%d successes\n%d fails", successCount, errorCount)
249263
);
250264
partiallySuccessDialog.show();
251265
}

0 commit comments

Comments
 (0)
Please sign in to comment.