/// <summary> /// /// </summary> /// <param name="connection"></param> /// <param name="vertexObject"></param> /// <param name="edgeDocId"></param> /// <param name="isReverse"></param> /// <param name="newEdgeObject"></param> public static void UpdateEdgeProperty( GraphViewCommand command, JObject vertexObject, string edgeDocId, // Can be null bool isReverse, JObject newEdgeObject // With all metadata (including id, partition, srcV/sinkV, edgeId) ) { bool tooLarge; string srcOrSinkVInEdgeObject = isReverse ? KW_EDGE_SRCV : KW_EDGE_SINKV; if (edgeDocId == null) { JArray edgeContainer = (JArray)vertexObject[isReverse ? KW_VERTEX_REV_EDGE : KW_VERTEX_EDGE]; // Don't use JToken.Replace() here. // Make sure the currently modified edge is the last child of edgeContainer, which // garantees the newly created edge-document won't be too large. // // NOTE: The following line applies for both incomming and outgoing edge. edgeContainer.Children <JObject>().First( e => (string)e[KW_EDGE_ID] == (string)newEdgeObject[KW_EDGE_ID] && (string)e[srcOrSinkVInEdgeObject] == (string)newEdgeObject[srcOrSinkVInEdgeObject] ).Remove(); edgeContainer.Add(newEdgeObject); UploadOne(command, (string)vertexObject[KW_DOC_ID], vertexObject, false, out tooLarge); if (tooLarge) { // Handle this situation: The updated edge is too large to be filled into the vertex-document string existEdgeDocId, newEdgeDocId; bool? spillReverse = null; EdgeDocumentHelper.SpillVertexEdgesToDocument(command, vertexObject, ref spillReverse, out existEdgeDocId, out newEdgeDocId); } } else { // Large vertex JObject edgeDocObject = command.Connection.RetrieveDocumentById(edgeDocId, command.Connection.GetDocumentPartition(vertexObject), command); edgeDocObject[KW_EDGEDOC_EDGE].Children <JObject>().First( e => (string)e[KW_EDGE_ID] == (string)newEdgeObject[KW_EDGE_ID] && (string)e[srcOrSinkVInEdgeObject] == (string)newEdgeObject[srcOrSinkVInEdgeObject] ).Remove(); ((JArray)edgeDocObject[KW_EDGEDOC_EDGE]).Add(newEdgeObject); UploadOne(command, edgeDocId, edgeDocObject, false, out tooLarge); if (tooLarge) { if (command.Connection.EdgeSpillThreshold == 1) { throw new GraphViewException("The edge is too large to be stored in one document!"); } // Handle this situation: The modified edge is too large to be filled into the original edge-document // Remove the edgeObject added just now, and upload the original edge-document ((JArray)edgeDocObject[KW_EDGEDOC_EDGE]).Last.Remove(); UploadOne(command, edgeDocId, edgeDocObject, false, out tooLarge); Debug.Assert(!tooLarge); // Insert the edgeObject to one of the vertex's edge-documents InsertEdgeObjectInternal(command, vertexObject, null, newEdgeObject, isReverse, out edgeDocId); } } }
/// <summary> /// Insert edgeObject to one a vertex. /// NOTE: vertex-document and edge-document(s) are uploaded. /// NOTE: If changing _edge/_reverse_edge field from JArray to JObject, the "EdgeDocId" of existing /// edges in VertexCache are updated (from null to the newly created edge-document's id) /// NOTE: Adding the newly created edge into VertexCache is not operated by this function. Actually, /// if called by <see cref="UpdateEdgeProperty"/>, VertexCache should be updated by setting an /// edge's property, but not adding a new edge. /// </summary> /// <param name="command"></param> /// <param name="vertexObject"></param> /// <param name="vertexField">Can be null if we already know edgeContainer is JObject</param> /// <param name="edgeObject"></param> /// <param name="isReverse"></param> /// <param name="newEdgeDocId"></param> internal static void InsertEdgeObjectInternal( GraphViewCommand command, JObject vertexObject, VertexField vertexField, JObject edgeObject, bool isReverse, out string newEdgeDocId) { bool tooLarge; JArray edgeContainer = (JArray)vertexObject[isReverse ? KW_VERTEX_REV_EDGE : KW_VERTEX_EDGE]; // JArray or JObject bool isSpilled = IsSpilledVertex(vertexObject, isReverse); // // This graph is compatible only, thus add an edge-document directly // if (command.Connection.GraphType != GraphType.GraphAPIOnly || command.Connection.EdgeSpillThreshold == 1) { Debug.Assert(command.Connection.EdgeSpillThreshold == 1); // Create a new edge-document to store the edge. JObject edgeDocObject = new JObject { [KW_DOC_ID] = GraphViewConnection.GenerateDocumentId(), [KW_EDGEDOC_ISREVERSE] = isReverse, [KW_EDGEDOC_VERTEXID] = (string)vertexObject[KW_DOC_ID], [KW_EDGEDOC_VERTEX_LABEL] = (string)vertexObject[KW_VERTEX_LABEL], [KW_EDGEDOC_EDGE] = new JArray(edgeObject), [KW_EDGEDOC_IDENTIFIER] = (JValue)true, }; if (command.Connection.PartitionPathTopLevel != null) { // This may be KW_DOC_PARTITION, maybe not edgeDocObject[command.Connection.PartitionPathTopLevel] = vertexObject[command.Connection.PartitionPathTopLevel]; } // Upload the edge-document bool dummyTooLarge; UploadOne(command, (string)edgeDocObject[KW_DOC_ID], edgeDocObject, true, out dummyTooLarge); Debug.Assert(!dummyTooLarge); newEdgeDocId = (string)edgeDocObject[KW_DOC_ID]; return; } if (isSpilled) { // Now it is a large-degree vertex, and contains at least 1 edge-document JArray edgeDocumentsArray = edgeContainer; Debug.Assert(edgeDocumentsArray != null, "edgeDocumentsArray != null"); Debug.Assert(edgeDocumentsArray.Count == 1, "edgeDocumentsArray.Count == 1"); string lastEdgeDocId = (string)edgeDocumentsArray.Last[KW_DOC_ID]; JObject edgeDocument = command.Connection.RetrieveDocumentById(lastEdgeDocId, vertexField.Partition, command); Debug.Assert(((string)edgeDocument[KW_DOC_ID]).Equals(lastEdgeDocId), $"((string)edgeDocument[{KW_DOC_ID}]).Equals(lastEdgeDocId)"); Debug.Assert((bool)edgeDocument[KW_EDGEDOC_ISREVERSE] == isReverse, $"(bool)edgeDocument['{KW_EDGEDOC_ISREVERSE}'] == isReverse"); Debug.Assert((string)edgeDocument[KW_EDGEDOC_VERTEXID] == (string)vertexObject[KW_DOC_ID], $"(string)edgeDocument['{KW_EDGEDOC_VERTEXID}'] == (string)vertexObject['{KW_DOC_ID}']"); JArray edgesArray = (JArray)edgeDocument[KW_EDGEDOC_EDGE]; Debug.Assert(edgesArray != null, "edgesArray != null"); Debug.Assert(edgesArray.Count > 0, "edgesArray.Count > 0"); if (command.Connection.EdgeSpillThreshold == 0) { // Don't spill an edge-document until it is too large edgesArray.Add(edgeObject); tooLarge = false; } else { // Explicitly specified a threshold Debug.Assert(command.Connection.EdgeSpillThreshold > 0, "connection.EdgeSpillThreshold > 0"); if (edgesArray.Count >= command.Connection.EdgeSpillThreshold) { // The threshold is reached! tooLarge = true; } else { // The threshold is not reached edgesArray.Add(edgeObject); tooLarge = false; } } // If the edge-document is not too large (reach the threshold), try to // upload the edge into the document if (!tooLarge) { UploadOne(command, lastEdgeDocId, edgeDocument, false, out tooLarge); } if (tooLarge) { // The edge is too large to be filled into the last edge-document // or the threashold is reached: // Create a new edge-document to store the edge. JObject edgeDocObject = new JObject { [KW_DOC_ID] = GraphViewConnection.GenerateDocumentId(), [KW_EDGEDOC_ISREVERSE] = isReverse, [KW_EDGEDOC_VERTEXID] = (string)vertexObject[KW_DOC_ID], [KW_EDGEDOC_VERTEX_LABEL] = (string)vertexObject[KW_VERTEX_LABEL], [KW_EDGEDOC_EDGE] = new JArray(edgeObject), [KW_EDGEDOC_IDENTIFIER] = (JValue)true, }; if (command.Connection.PartitionPathTopLevel != null) { // This may be KW_DOC_PARTITION, maybe not edgeDocObject[command.Connection.PartitionPathTopLevel] = vertexObject[command.Connection.PartitionPathTopLevel]; } lastEdgeDocId = command.Connection.CreateDocumentAsync(edgeDocObject, command).Result; // Replace the newly created edge-document to vertexObject Debug.Assert(edgeDocumentsArray.Count == 1); edgeDocumentsArray[0][KW_DOC_ID] = lastEdgeDocId; } newEdgeDocId = lastEdgeDocId; // Upload the vertex documention (at least, its _nextXxx is changed) bool dummyTooLarge; UploadOne(command, (string)vertexObject[KW_DOC_ID], vertexObject, false, out dummyTooLarge); Debug.Assert(!dummyTooLarge); } else { // This vertex is not spilled bool?spillReverse; ((JArray)edgeContainer).Add(edgeObject); if (command.Connection.EdgeSpillThreshold == 0) { // Don't spill an edge-document until it is too large tooLarge = false; spillReverse = null; } else { // Explicitly specified a threshold Debug.Assert(command.Connection.EdgeSpillThreshold > 0, "connection.EdgeSpillThreshold > 0"); tooLarge = (((JArray)edgeContainer).Count > command.Connection.EdgeSpillThreshold); spillReverse = isReverse; } if (!tooLarge) { UploadOne(command, (string)vertexObject[KW_DOC_ID], vertexObject, false, out tooLarge); } if (tooLarge) { string existEdgeDocId; // The vertex object is uploaded in SpillVertexEdgesToDocument EdgeDocumentHelper.SpillVertexEdgesToDocument(command, vertexObject, ref spillReverse, out existEdgeDocId, out newEdgeDocId); // the edges are spilled into two ducuments. // one stores old edges(docId = existEdgeDocId), the other one stores the new edge. // Because the new edge is not in the vertexCache, hence we can set all edges' docId as existEdgeDocId Debug.Assert(spillReverse != null); Debug.Assert(vertexField != null); if (spillReverse.Value) { vertexField.RevAdjacencyList.ResetFetchedEdgesDocId(existEdgeDocId); } else { vertexField.AdjacencyList.ResetFetchedEdgesDocId(existEdgeDocId); } } else { newEdgeDocId = null; } } }