SG rejects newly created document with document ID of deleted document: Channel access missing

I’m referencing this post: Reuse document ID of deleted document @jens

Setup: Couchbase Lite Android 1.4.1 - SG 1.4 - CB Server 4.5

Here are the reproducible steps:

  1. Create document

             document.update(new Document.DocumentUpdater() {
             @Override
             public boolean update(UnsavedRevision newRevision) {
                 Map<String, Object> itemProperties = newRevision.getUserProperties();
                 // add properties is omitted
                 newRevision.setUserProperties(itemProperties);
                 return true;
             }
         });
    

Logs in Android Studio:

D/Sync: [sendAsyncRequest()] POST => http://my_ip/my_database/_revs_diff
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Sync: [sendAsyncRequest()] POST => http://my_ip/my_database/_bulk_docs
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Database: Query view null completed in 9 milliseconds
D/Sync: changeTrackerReceivedChange: {seq=342, id=b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7::item::e1b849f9631f, changes=[{rev=1-66b5105bb81ffabcfbcdfaccf3350563}]}
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8}: adding rev to inbox {b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7::item::e1b849f9631f #1-66b5105bb81ffabcfbcdfaccf3350563 @0}
D/Sync: changeTrackerCaughtUp
D/Database: Query view null completed in 7 milliseconds
D/Sync: processInbox called
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} no new remote revisions to fetch.  add lastInboxSequence (342) to pendingSequences (com.couchbase.lite.support.SequenceMap@af0c4a0)
D/Database: Query view null completed in 11 milliseconds
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55}: saveLastSequence() called. lastSequence: 344 remoteCheckpoint: {_id=_local/81e5533350715facec2fa6384adc6b3dde34943a, _rev=0-72, lastSequence=343}
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55}: start put remote _local document.  checkpointID: 81e5533350715facec2fa6384adc6b3dde34943a body: {_rev=0-72, lastSequence=344, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: [sendAsyncRequest()] PUT => http://my_ip/my_database/_local/81e5533350715facec2fa6384adc6b3dde34943a
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@5c4acff: put remote _local document request finished.  checkpointID: 81e5533350715facec2fa6384adc6b3dde34943a body: {_rev=0-72, lastSequence=344, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@5c4acff: saved remote checkpoint, updating local checkpoint. RemoteCheckpoint: {_rev=0-73, lastSequence=344, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Database: Query view null completed in 52 milliseconds
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8}: saveLastSequence() called. lastSequence: 342 remoteCheckpoint: {_id=_local/086b8f7ce6e606a9accf45792c5237604b08af71, _rev=0-85, lastSequence=341}
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8}: start put remote _local document.  checkpointID: 086b8f7ce6e606a9accf45792c5237604b08af71 body: {_rev=0-85, lastSequence=342, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: [sendAsyncRequest()] PUT => http://my_ip/my_database/_local/086b8f7ce6e606a9accf45792c5237604b08af71
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PullerInternal{http://my_ip/my_database, pull, 086b8}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@a411a1b: put remote _local document request finished.  checkpointID: 086b8f7ce6e606a9accf45792c5237604b08af71 body: {_rev=0-85, lastSequence=342, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@a411a1b: saved remote checkpoint, updating local checkpoint. RemoteCheckpoint: {_rev=0-86, lastSequence=342, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PullerInternal{http://my_ip/my_database, pull, 086b8}
D/Database: Query view null completed in 46 milliseconds

Document ID is: b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7::item::e1b849f9631f
Document in Couchbase Server console:

{
  "_sync": {
    "rev": "1-66b5105bb81ffabcfbcdfaccf3350563",
    "sequence": 342,
    "recent_sequences": [
      342
    ],
    "history": {
      "revs": [
        "1-66b5105bb81ffabcfbcdfaccf3350563"
      ],
      "parents": [
        -1
      ],
      "channels": [
        [
          "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7"
        ]
      ]
    },
    "channels": {
      "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7": null
    },
    "time_saved": "2017-12-21T09:17:08.626827144Z"
  },
  "category": {
    "id": "GENERAL",
    "name": ""
  },
  "channels": "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7",
  "id": "e1b849f9631f",
  "name": "Test1",
  "type": "item",
  "unit": {
    "id": "GENERAL",
    "name": ""
  }
}

Document deletion:

document.delete();

Logs in Android Studio:

D/Sync: [sendAsyncRequest()] POST => http://my_ip/my_database/_revs_diff
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Sync: [sendAsyncRequest()] POST => http://my_ip/my_database/_bulk_docs
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/OpenGLRenderer: endAllStagingAnimators on 0x989d0600 (MenuPopupWindow$MenuDropDownListView) with handle 0x9b7be6d0
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Database: Query view null completed in 17 milliseconds
D/Sync: changeTrackerReceivedChange: {seq=343, id=b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7::item::e1b849f9631f, deleted=true, removed=[b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7], changes=[{rev=2-4ac7fab3f183d0124ecf95a8d991206e}]}
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8}: adding rev to inbox {b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7::item::e1b849f9631f #2-4ac7fab3f183d0124ecf95a8d991206e @0 DEL}
D/Database: Query view null completed in 12 milliseconds
D/Sync: processInbox called
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} no new remote revisions to fetch.  add lastInboxSequence (343) to pendingSequences (com.couchbase.lite.support.SequenceMap@af0c4a0)
D/Database: Query view null completed in 15 milliseconds
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55}: saveLastSequence() called. lastSequence: 345 remoteCheckpoint: {_rev=0-73, lastSequence=344, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55}: start put remote _local document.  checkpointID: 81e5533350715facec2fa6384adc6b3dde34943a body: {_rev=0-73, lastSequence=345, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: [sendAsyncRequest()] PUT => http://my_ip/my_database/_local/81e5533350715facec2fa6384adc6b3dde34943a
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@71dfa57: put remote _local document request finished.  checkpointID: 81e5533350715facec2fa6384adc6b3dde34943a body: {_rev=0-73, lastSequence=345, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@71dfa57: saved remote checkpoint, updating local checkpoint. RemoteCheckpoint: {_rev=0-74, lastSequence=345, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Database: Query view null completed in 25 milliseconds
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8}: saveLastSequence() called. lastSequence: 343 remoteCheckpoint: {_rev=0-86, lastSequence=342, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8}: start put remote _local document.  checkpointID: 086b8f7ce6e606a9accf45792c5237604b08af71 body: {_rev=0-86, lastSequence=343, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: [sendAsyncRequest()] PUT => http://my_ip/my_database/_local/086b8f7ce6e606a9accf45792c5237604b08af71
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PullerInternal{http://my_ip/my_database, pull, 086b8}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@e9de4f3: put remote _local document request finished.  checkpointID: 086b8f7ce6e606a9accf45792c5237604b08af71 body: {_rev=0-86, lastSequence=343, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@e9de4f3: saved remote checkpoint, updating local checkpoint. RemoteCheckpoint: {_rev=0-87, lastSequence=343, _id=_local/086b8f7ce6e606a9accf45792c5237604b08af71}
D/Sync: PullerInternal{http://my_ip/my_database, pull, 086b8} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PullerInternal{http://my_ip/my_database, pull, 086b8}
D/Database: Query view null completed in 54 milliseconds

Document in Couchbase Server console:

{
  "_deleted": true,
  "_sync": {
    "rev": "2-4ac7fab3f183d0124ecf95a8d991206e",
    "flags": 1,
    "sequence": 343,
    "recent_sequences": [
      342,
      343
    ],
    "history": {
      "revs": [
        "2-4ac7fab3f183d0124ecf95a8d991206e",
        "1-66b5105bb81ffabcfbcdfaccf3350563"
      ],
      "parents": [
        1,
        -1
      ],
      "deleted": [
        0
      ],
      "channels": [
        null,
        [
          "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7"
        ]
      ]
    },
    "channels": {
      "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7": {
        "seq": 343,
        "rev": "2-4ac7fab3f183d0124ecf95a8d991206e",
        "del": true
      }
    },
    "time_saved": "2017-12-21T09:23:16.00999601Z"
  }
}	

So far a document was created, synced and then deleted. It’s now marked as deleted on the device and on Couchbase Server. Now I enter the same input and hence create a document with the same ID.

Logs in Android Studio:

D/Sync: [sendAsyncRequest()] POST => http://my_ip/my_database/_revs_diff
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Sync: [sendAsyncRequest()] POST => http://my_ip/my_database/_bulk_docs
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME

Warning is here

W/Sync: {error=forbidden, id=b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7::item::e1b849f9631f, reason=missing channel access, status=403}: _bulk_docs got an error: com.couchbase.lite.replicator.PusherInternal$6@a2735f8
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Database: Query view null completed in 11 milliseconds
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55}: saveLastSequence() called. lastSequence: 346 remoteCheckpoint: {_rev=0-74, lastSequence=345, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55}: start put remote _local document.  checkpointID: 81e5533350715facec2fa6384adc6b3dde34943a body: {_rev=0-74, lastSequence=346, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: [sendAsyncRequest()] PUT => http://my_ip/my_database/_local/81e5533350715facec2fa6384adc6b3dde34943a
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => RESUME
D/Sync: firing trigger: RESUME
D/Sync: State transition: IDLE -> RUNNING (via RESUME).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@b586936: put remote _local document request finished.  checkpointID: 81e5533350715facec2fa6384adc6b3dde34943a body: {_rev=0-74, lastSequence=346, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: com.couchbase.lite.replicator.ReplicationInternal$8@b586936: saved remote checkpoint, updating local checkpoint. RemoteCheckpoint: {_rev=0-75, lastSequence=346, _id=_local/81e5533350715facec2fa6384adc6b3dde34943a}
D/Sync: PusherInternal{http://my_ip/my_database, push, 81e55} [fireTrigger()] => WAITING_FOR_CHANGES
D/Sync: firing trigger: WAITING_FOR_CHANGES
D/Sync: retryReplicationIfError() state=IDLE, error=null, isContinuous()=true, isTransientError()=false
D/Sync: State transition: RUNNING -> IDLE (via WAITING_FOR_CHANGES).  this: PusherInternal{http://my_ip/my_database, push, 81e55}
D/Database: Query view null completed in 25 milliseconds

Sync Gateway function:

{
  "logFilePath": "/home/sync_gateway/logs/sync_gateway_error.log",
  "interface": ":4984",
  "adminInterface": ":4985",
  "log": [
    "CRUD",
    "HTTP",
    "Access",
    "Cache",
    "Changes"
  ],
  "databases": {
    "my_database": {
      "server": "http://localhost:8091",
      "bucket": "my_bucket",
      "revs_limit": 100,
      "allow_empty_password" : true,
      "users": {
        "GUEST": {
          "disabled": true,
          "admin_channels": [
            "*"
          ]
        }
      },
     "sync": `function(doc, oldDoc){
              if(oldDoc){requireAccess(oldDoc.channels);}
              channel (doc.channels) }`
    }
  }
}

How can I create an entirely new document with the document ID of a deleted document and get it to sync?

Here’s simpler code:

Create document:

        view.findViewById(R.id.create).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Map<String, Object> properties = new HashMap<>();
            properties.put("title", "My title");
            properties.put("channels", "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7");
            Document document = CouchbaseUtilv6.getInstance(getActivity()).getDatabase().getDocument("ID-2");
            try {
                document.putProperties(properties);
            } catch (CouchbaseLiteException e) {
                e.printStackTrace();
            }
        }

It’s created, synced and this is in CB Server console:

{
  "_sync": {
    "rev": "1-ef11eb88fd17703d25ddb277a8684cae",
    "sequence": 345,
    "recent_sequences": [
      345
    ],
    "history": {
      "revs": [
        "1-ef11eb88fd17703d25ddb277a8684cae"
      ],
      "parents": [
        -1
      ],
      "channels": [
        [
          "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7"
        ]
      ]
    },
    "channels": {
      "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7": null
    },
    "time_saved": "2017-12-21T11:35:53.871041716Z"
  },
  "channels": "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7",
  "title": "My title"
}

Code to delete document:

    view.findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                Document document = CouchbaseUtilv6.getInstance(getActivity()).getDatabase().getDocument("ID-2");
                document.delete();
            } catch (CouchbaseLiteException e) {
                e.printStackTrace();
            }
        }
    });

It’s synced and in CB Server console:

{
  "_deleted": true,
  "_sync": {
    "rev": "2-6a2118f4a8bfc690ab62f79c295b8875",
    "flags": 1,
    "sequence": 346,
    "recent_sequences": [
      345,
      346
    ],
    "history": {
      "revs": [
        "1-ef11eb88fd17703d25ddb277a8684cae",
        "2-6a2118f4a8bfc690ab62f79c295b8875"
      ],
      "parents": [
        -1,
        0
      ],
      "deleted": [
        1
      ],
      "channels": [
        [
          "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7"
        ],
        null
      ]
    },
    "channels": {
      "b91a8c08-5e1e-4d1b-a163-9f2b2a7ee0a7": {
        "seq": 346,
        "rev": "2-6a2118f4a8bfc690ab62f79c295b8875",
        "del": true
      }
    },
    "time_saved": "2017-12-21T11:36:23.8573285Z"
  }
}

When I create the document again, then this message is in Android Studio logs:

W/Sync: {error=forbidden, id=ID-2, reason=missing channel access, status=403}: _bulk_docs got an error: com.couchbase.lite.replicator.PusherInternal$6@53b59c9

Sync function is as above.

When you recreate the document, the sync function gets called with the deletion revision (the one with _deleted:true) as oldDoc. I think the resulting call to requireAccess(undefined) is failing. You might try changing the line in the sync function to

if(oldDoc && oldDoc.channels){requireAccess(oldDoc.channels);}

Thanks and Happy Holidays!