API
@-Formulas
JavaScript
LotusScript
Reg Exp
Web Design
Notes Client
XPages
 
Filter Out Invalid Documents
In a heavily-used application, with a lot of adding and deleting documents, and a lot of client replication, we found that some of our LotusScript routines were returning invalid documents (deleted documents, for example) from our NotesView.GetAllDocumentsByKey and NotesDatabase.Search methods. Obviously, this could lead to errors (the "Document Has Been Deleted" error was our favorite). The database simply couldn't keep up with all the view index changes, but our LotusScript routines needed to ignore the deleted documents (and conflict documents).

We ended up writing a Filter function that we would use all the time. So instead of:
Set coll = view.GetAllDocumentsByKey(key, True)
our code became:
Set coll = Filter(view.GetAllDocumentsByKey(key, True), "Subject")

It was similar for the NotesDatabase.Search calls that we were using in the application.

This Filter function takes two parameters. The first parameter is the collection you want to filter. The second is either a string or an array of strings. This is the name of the field (or names of all the fields if an array is passed) that must exist on EVERY document in the collection for it to be a valid document. In the example above, we were passing in the single string "Subject", so every document in the returned collection will have a field called "Subject".

One thing nice about the function is that you can be assured that the return value will be a valid collection. It might be empty, but it will be valid. To have bullet-proof code, after getting a collection from a search or through a view, you should really check to see if the collection object is Nothing or not. If the collection is Nothing, then it won't have a Count property and you won't be able to get the first document, so your code will error out with an Object Variable Not Set error. This function always returns a valid collection object.

The function filters out documents that are deleted, documents that are conflicts, and documents that are missing the Form field. In preparation for using this function, you'll want to create a new view called Empty in your database. The view should have a selection formula of SELECT @False. This will assure that there's nothing in the view. If you don't want to add that empty view to your database, you can still use this function but it will be a little slower. We'll talk about that after the code:

Function Filter(coll As NotesDocumentCollection, mustHaveFields As Variant) As NotesDocumentCollection
   Dim view As NotesView
   Dim retColl As NotesDocumentCollection
   Dim doc As NotesDocument
   Dim nextDoc As NotesDocument
   
Notice that the 2nd parameter is defined as a Variant. This is needed so either a string or an array of strings can be passed into the function.

   ' Initialize the return collection to an empty collection
   If coll Is Nothing Then
      Dim session As New NotesSession
      Dim db As NotesDatabase
      Set db = session.CurrentDatabase
      Set view = db.GetView("Empty")
   Else
      Set view = coll.Parent.GetView("Empty")
   End If
   Set retColl = Nothing
   ' Make sure we get a valid object to return
   While retColl Is Nothing
      Set retColl = view.GetAllDocumentsByKey("A key that will not exist", True)
      If retColl Is Nothing Then Call view.Refresh
   Wend

The first bit of code establishes a valid object that will be returned. This means that any calling code does not have to make sure the object is valid before proceeding. It can be assured that it is dealing with a valid object. If you don't want to use the view, then you can perform a NotesDatabase.Search here. Your search string should be SELECT @False (or something where you can guarantee that no documents will be returned). So the retColl statement would be changed to:
      Set retColl = coll.Parent.Search("SELECT @False", Nothing, 0)

Note that doing a search all the time is going to be slower because Notes is going to have to build an index each time instead of using an index that is already built in the view.

   ' Make sure we start out with no documents in the return collection
   If retColl.Count <> 0 Then
      Set doc = retColl.GetFirstDocument
      While Not doc Is Nothing
         Set nextDoc = retColl.GetNextDocument(doc)
         Call retColl.DeleteDocument(doc)
         Set doc = nextDoc
      Wend
   End If

The view should be empty, so the collection should have nothing in it at first. But here we make sure. If there happens to be something in the collection initially, that would throw off our results. So this block removes everything from the collection.

   ' If an invalid object was passed in, then return the empty collection
   If coll Is Nothing Then
      Set Filter = retColl
      Exit Function
   End If

Just in case the first parameter is an invalid object, return a valid object with no documents.

   ' Go through the documents in the original collection. For those that are valid,
   ' put them into the collection to be returned
   Set doc = coll.GetFirstDocument
   While Not doc Is Nothing
      If IsValidDoc(doc, mustHaveFields) Then Call retColl.AddDocument(doc)
      Set doc = coll.GetNextDocument(doc)
   Wend
   ' Return the updated collection
   Set Filter = retColl
End Function

Finally, go through all the documents and find out which ones belong in the collection. This involves calling the "IsValidDoc" function, which is shown next:

Function IsValidDoc(doc As NotesDocument, mustHaveFields As Variant) As Integer
   Dim i As Integer
   
   If doc Is Nothing Then
      IsValidDoc = False
   Elseif doc.IsDeleted Then
      IsValidDoc = False
   Elseif doc.UniversalID = "" Then
      IsValidDoc = False
   Elseif Len(doc.UniversalID) <> 32 Then
      IsValidDoc = False
   Elseif doc.UniversalID = String$(32, "0") Then
      IsValidDoc = False
   Elseif doc.HasItem("$Conflict") Then
      IsValidDoc = False
   Elseif Not doc.HasItem("Form") Then
      IsValidDoc = False
   Else
      IsValidDoc = True
      If Isarray(mustHaveFields) Then
         For i = Lbound(mustHaveFields) To Ubound(mustHaveFields)
            If Not doc.HasItem(Cstr(mustHaveFields(i))) Then IsValidDoc = False
         Next
      Elseif Cstr(mustHaveFields) <> "" Then
         If Not doc.HasItem(Cstr(mustHaveFields)) Then IsValidDoc = False
      End If
   End If
End Function

This function takes a document object and the same 2nd parameter as the Filter function. The function checks all the possibilities that we've found in our years of programming in Notes. So the document must meet all these criteria before being called valid:

If all the above conditions are met, then the document is valid. Note that if you don't care about any additional fields (more than the Form field being present) you can pass in an empty string as the second parameter.

If you want to reuse this code, you'll have to decide if you want to eliminate conflict documents (like our code does) or not. We found that processing the conflict documents ended up causing more problems. For example, if a document should have 3 responses then the document having a coflict (which is a response) ends up throwing the count off. We use other processing to find conflicts and then either report them to our administrators or eliminate the conflict if it doesn't need to be handled manually.