MongoDB API

Description

NoSQL document databases are popular for certain types of applications. The Xbasic MongoDB class makes it easy to query, update and manage a MongoDB database from Xbasic. Learn how to perform some of the more common operations on a MongoDB database in this guide to the MongoDB API.

Creating Document Collection

If you already have a Mongo database server, creating new documents in a Mongo database is very easy. If the database doesn't already exist, and you have permission to create a database, a new database will automatically be created for you when you try to add a collection to the database. The same goes for collections. The creation won't occur until you execute your first command against the data.

Inserting Documents

To add records to a Mongo database, simple call the AddRecord() method with a properly formatted JSON string. If the record was added, the value of the unique _id that was assigned to the document is returned.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","MyNewDatabase","MyNewTable")
? Mongo.AddRecord(<<%str%
#{ "Firstname" : "John" , "Lastname" : "Public" , "Date" : "10/8/1988" } #%str%) = "53ac89b37116e1d426bd8c95"
? Mongo.AddRecord(<<%str%
#{ "Firstname" : "Jane" , "Lastname" : "Doe" , "Date" : "10/8/1988" }
#%str%)
= "53ac89c17116e1d426bd8c96"

? json_reformat( Mongo.GetAllRecords() , .t. )
= [
    {
        "_id": "53ac89b37116e1d426bd8c95",
        "Firstname": "John",
        "Lastname": "Public",
        "Date": "10/8/1988"
    },
    {
        "_id": "53ac89c17116e1d426bd8c96",
        "Firstname": "Jane",
        "Lastname": "Doe",
        "Date": "10/8/1988"
    }
]

Updating Documents

If you want to modify the contents of an existing document, you will need its ID and the new content.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","MyNewDatabase","MyNewTable")
Mongo.UpdateRecord("53ac89c17116e1d426bd8c96",<<%str%
#{ "Firstname" : "Janet" , "Lastname" : "Doe" , "Date" : "11/5/1976" }
#%str%)

? json_reformat( Mongo.GetAllRecords() , .t. )
= [
    {
        "_id": "53ac89b37116e1d426bd8c95",
        "Firstname": "John",
        "Lastname": "Public",
        "Date": "10/8/1988"
    },
    {
        "_id": "53ac89c17116e1d426bd8c96",
        "Firstname": "Janet",
        "Lastname": "Doe",
        "Date": "11/5/1976"
    }
]

Deleting Documents

Document deletion merely requires calling the DeleteRecord() method passing the documents unique id.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","MyNewDatabase","MyNewTable")
Mongo.DeleteRecord("53ac89c17116e1d426bd8c96")
? json_reformat( Mongo.GetAllRecords() , .t. )
= [
    {
        "_id": "53ac89b37116e1d426bd8c95",
        "Firstname": "John",
        "Lastname": "Public",
        "Date": "10/8/1988"
    }
]

Retrieving data

Getting All documents using in a Mongo db collection is done by calling the GetAllRecords(), which is a method common to Mongo and couch client classes.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? json_reformat( Mongo.GetAllRecords() , .t. )
= [
    {
        "_id": "5399ddd502681b9c258dc5f7",
        "Id": "00000000",
        "Picture": "=filename_decode(\"Hires\\allium.jpg\")",
        "Imagedate": "07/30/2001",
        "Keywords": "allium white"
    },
    {
        "_id": "5399ddd502681b9c258dc5f8",
        "Id": "00000001",
        "Picture": "=filename_decode(\"Hires\\alyssum purple.jpg\")",
        "Imagedate": "06/05/2002",
        "Keywords": "alyssum purple"
    },
    .....

Getting a single document is another common method, which requires you pass the internal "id" for a record.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")

? json_reformat( Mongo.GetRecord("5399ddd502681b9c258dc5f8") , .t. )
= {
    "_id": "5399ddd502681b9c258dc5f8",
    "Id": "00000001",
    "Picture": "=filename_decode(\"Hires\\alyssum purple.jpg\")",
    "Imagedate": "06/05/2002",
    "Keywords": "alyssum purple"
}

Retrieving documents using criteria, a subset of the fields in the document, or retrieving the documents require using the Mongo-specific GetRecords() function.

If you omit the criteria, you will get the same results as if you had called the GetAllRecords() method.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? json_reformat( Mongo.GetRecords("") , .t. ) ' GetRecords with no criteria..
= [
    {
        "_id": "5399ddd502681b9c258dc5f7",
        "Id": "00000000",
        "Picture": "=filename_decode(\"Hires\\allium.jpg\")",
        "Imagedate": "07/30/2001",
        "Keywords": "allium white"
    },
    {
        "_id": "5399ddd502681b9c258dc5f8",
        "Id": "00000001",
        "Picture": "=filename_decode(\"Hires\\alyssum purple.jpg\")",
        "Imagedate": "06/05/2002",
        "Keywords": "alyssum purple"
    },
    ...

The format for the criteria is a description of data we are looking for , for example if we wanted to search the flowers collection for documents that had an imagedate field that was equal to "05/02/2003" we would need to pass the JSON string { "Imagedate": "05/02/2003" } to the GetRecords() function.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? json_reformat( Mongo.GetRecords(<<%str%
#{ "Imagedate": "05/02/2003" }
#%str%), .t. )
= [
    {
        "_id": "5399ddd602681b9c258dc606",
        "Id": "00000015",
        "Picture": "=filename_decode(\"Hires\\daffy_050103.jpg\")",
        "Imagedate": "05/02/2003",
        "Keywords": "daffodil white pale yellow"
    },
    {
        "_id": "5399ddd602681b9c258dc607",
        "Id": "00000016",
        "Picture": "=filename_decode(\"Hires\\daffy_050103a.jpg\")",
        "Imagedate": "05/02/2003",
        "Keywords": "daffodil white orange"
    }
]

Sorting of documents is accomplished by using the second parameter of the GetRecords(), and passing a JSON definition of all the fields we want to sort on, providing the direction of the sort (1 for ascending, -1 for descending).

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? json_reformat( Mongo.GetRecords("",<<%str%
#{ "Keywords": 1 }
#%str%), .t. )
= [
    {
        "_id": "5399ddd502681b9c258dc5f7",
        "Id": "00000000",
        "Picture": "=filename_decode(\"Hires\\allium.jpg\")",
        "Imagedate": "07/30/2001",
        "Keywords": "allium white"
    },
    {
        "_id": "5399ddd502681b9c258dc5f8",
        "Id": "00000001",
        "Picture": "=filename_decode(\"Hires\\alyssum purple.jpg\")",
        "Imagedate": "06/05/2002",
        "Keywords": "alyssum purple"
    },
    ....

? json_reformat( Mongo.GetRecords("",<<%str%
#{ "Keywords": -1 }
#%str%), .t. )
= [
    {
        "_id": "5399ddd602681b9c258dc5fb",
        "Id": "00000004",
        "Picture": "=filename_decode(\"Hires\\boltonia.jpg\")",
        "Imagedate": "10/08/2002",
        "Keywords": "white boltonia daisy"
    },
    {
        "_id": "5399ddd602681b9c258dc61f",
        "Id": "00000043",
        "Picture": "=filename_decode(\"Hires\\magnolia 2.jpg\")",
        "Imagedate": "04/14/2002",
        "Keywords": "star magnolia white"
    },
    ....

If we want to restrict the fields that are returned, we use the third optional JSON string to describe the data we expect to retrieve. It should be noted that the _id field gets included by default.

Mongo = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? json_reformat( Mongo.GetRecords("","",<<%str%
#{ "Keywords": "" }
#%str%), .t. )
= [
    {
        "_id": "5399ddd502681b9c258dc5f7",
        "Keywords": "allium white"
    },
    {
        "_id": "5399ddd502681b9c258dc5f8",
        "Keywords": "alyssum purple"
    },
    {
        "_id": "5399ddd602681b9c258dc5f9",
        "Keywords": "anenome white"
    },

Data Retrieval with Ranges

In addition to getting exact matches with the criteria, you can a tree of json operators to get items that match a set of conditions.

In the following example, instead of a value to match for "Keywords", we have an object with a "$gt" and "$lt" property - these are the value to match Keywords that are greater that "d" and also less than "i". It is worth noting that this is a case sensitive search, and there are no documents in this collection with Keyword fields that are lexically between "D" and "I".

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? strtran(Mongo.GetRecords(<<%str%
#{
#   "Keywords" : {
#      "$gt" : "d" , "$lt" : "i"
#   }
#}
#%str%,"{ \"Keywords\" : 1 }","{ \"Keywords\" : \"\" }"),"}","}"+crlf())
= [{"_id":"5399ddd602681b9c258dc608","Keywords":"daffodil mixture"}
,{"_id":"5399ddd602681b9c258dc607","Keywords":"daffodil white orange"}
,{"_id":"5399ddd602681b9c258dc606","Keywords":"daffodil white pale yellow"}
,{"_id":"5399ddd602681b9c258dc605","Keywords":"daffodil white yellow"}
,{"_id":"5399ddd602681b9c258dc604","Keywords":"daffodil yellow"}
,{"_id":"5399ddd602681b9c258dc609","Keywords":"daisy shasta"}
,{"_id":"5399ddd602681b9c258dc61d","Keywords":"daylily stella doro yellow"}
,{"_id":"5399ddd602681b9c258dc60c","Keywords":"dianthus purple"}
,{"_id":"5399ddd602681b9c258dc60d","Keywords":"dogwood pale green"}
,{"_id":"5399ddd602681b9c258dc610","Keywords":"erigeron purple"}
,{"_id":"5399ddd602681b9c258dc611","Keywords":"flowering almond white pink"}
,{"_id":"5399ddd602681b9c258dc613","Keywords":"floxglove white"}
,{"_id":"5399ddd602681b9c258dc612","Keywords":"forsythia white"}
,{"_id":"5399ddd602681b9c258dc614","Keywords":"gallardia red yellow"}
,{"_id":"5399ddd602681b9c258dc615","Keywords":"hyacinth pink"}
]

Advanced data Retrieval with Map/Reduce

In addition to the GetRecords function, there is a MapReduce() function on the Mongo object that provides the means to filter and compose data using server-side Javascript functions.

In this example, we get arrays of flowers grouped by date.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")

? json_reformat( Mongo.MapReduce("function() {emit(this.Imagedate,this.Keywords);}","") , .t. )
= [
    {
        "_id": "07/30/2001",
        "value": [
            "allium white",
            "anenome white",
            "coreopsis zagreb yellow",
            "crocus purple",
            "kolkwitzia white",
            "oenethera yellow closeup"
        ]
    },
    {
        "_id": "06/05/2002",
        "value": [
            "alyssum purple"
        ]
    },
    {
        "_id": "05/24/2002",
        "value": [
            "anenome white",
            "rhododendron chionides pink"
        ]
    },
    {
    ....

The reduce function gets called for any keys that have more than one match. In the following example, we edit the values for these to only include the first entry, and a suffix that includes the count.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")

? json_reformat( Mongo.MapReduce("function() {emit(this.Imagedate,this.Keywords);}","function(key,values) { return [ values[0] + ' plus '+ (values.length-1) + ' others' ]; }") , .t. )
= [
    {
        "_id": "07/30/2001",
        "value": [
            "allium white plus 5 others"
        ]
    },
    {
        "_id": "06/05/2002",
        "value": [
            "alyssum purple"
        ]
    },
    {
        "_id": "05/24/2002",
        "value": [
            "anenome white plus 1 others"
        ]
    },
    {
        "_id": "10/08/2002",
        "value": [
            "white boltonia daisy plus 1 others"
        ]
    },

The third option is the finalize function definition. This occurs for all values, regardless of whether reduce gets called for them.

In this example we use the finalize to count the number of values we found for each key.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
? json_reformat( Mongo.MapReduce("function() {emit(this.Imagedate,this.Keywords);}","","function(key,values) { return values.length;}") , .t. )
= [
    {
        "_id": "07/30/2001",
        "value": 6
    },
    {
        "_id": "06/05/2002",
        "value": 14
    },
    {
        "_id": "05/24/2002",
        "value": 2
    },
    {
        "_id": "10/08/2002",
        "value": 2
    },
    ....

Optimizing Searches with Indexes

Just as SQL servers allows application developers to indicate which columns should be indexed, Mongo provides the means to index fields in collections. While having index means the server will need to do more work whenever any documents change for tbe indexed collection, the tradeoff is that using any field that an index exists for in the critia for a GetRecords() call will be much faster.

To see what indexes already exist, call the GetIndexes() method.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
Mongo.formatJson = .t.
? Mongo.GetIndexes()
= {
    "_id_": [
        [
            "_id",
            1
        ]
    ]
}

To add a new index, call the createIndex() method, passing a format that resembles the 'sorting' parameter discussed in GetRecords().

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
Mongo.formatJson = .t.

Mongo.CreateIndex(<<%str%
#{
#   "Keywords" : 1
#}
#%str%,"")
? Mongo.GetIndexes()
= {
    "_id_": [
        [
            "_id",
            1
        ]
    ],
    "Keywords_1": [
        [
            "Keywords",
            1
        ]
    ]
}

To smarter index add function exists if you want to have the function first test to see if the index already exists, this function is called 'ensureIndex' .

In the example below, notice that the first EnsureIndex() call does nothing, which the second call adds an index in the "Id" field because no index exists for the field "Id".

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
Mongo.formatJson = .t.
Mongo.CreateIndex(<<%str%
#{
#   "Keywords" : 1
#}
#%str%,"")
Mongo.EnsureIndex(<<%str%
#{
#   "Keywords" : 1
#}
#%str%,"")
Mongo.EnsureIndex(<<%str%
#{
#   "Id" : 1
#}
#%str%,"")

? Mongo.GetIndexes()
= {
    "_id_": [
        [
            "_id",
            1
        ]
    ],
    "Keywords_1": [
        [
            "Keywords",
            1
        ]
    ],
    "Id_1": [
        [
            "Id",
            1
        ]
    ]
}

If you want to delete indexes, there is a DropIndex() method that gets called with the tag name.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
Mongo.formatJson = .t.

Mongo.DropIndex("Keywords_1")
? Mongo.GetIndexes()
= {
    "_id_": [
        [
            "_id",
            1
        ]
    ]
}

Managing Databases and Collections

There are a number of utility functions for managing databases and collections. To get a List of all the databases in a Mongo server instance, call the ListDatabases() function.

To delete a databases (and all collections in the database) call the DropDatabaseFunction().

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","MyNewDatabase","temp_table")
Mongo.formatJson = .t.

Mongo.AddRecord("{ \"Id\" : \"0001\" }")
? Mongo.ListDatabases()
= {
    "databases": [
        {
            "name": "admin",
            "sizeOnDisk": 83886080,
            "empty": false
        },
        {
            "name": "MyNewDatabase",
            "sizeOnDisk": 83886080,
            "empty": false
        },
    ],
    "totalSize": 587202560,
    "ok": 1
}

Mongo.DropDatabase()
? Mongo.ListDatabases()
= {
    "databases": [
        {
            "name": "admin",
            "sizeOnDisk": 83886080,
            "empty": false
        },
    ],
    "totalSize": 503316480,
    "ok": 1
}

Like databases, collections within a database can be enumerated and deleted. To get a list of all the collections in a databases, call the ListCollections() method. To Remove a collection (and all its documents) call the DropCollection() method.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","MyNewDatabase","temp_table")
Mongo.formatJson = .t.
Mongo.AddRecord("{ \"Id\" : \"0001\" }")

? Mongo.ListCollections()
= [
    "temp_table",
    "system.indexes"
]

Mongo.DropCollection("temp_table")
? Mongo.ListCollections()
= [
    "system.indexes"
]

Collections can be renamed using the collectionRename() method.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","MyNewDatabase","temp_table")
Mongo.formatJson = .t.

? Mongo.ListCollections()
= [
    "system.indexes",
    "temp_table"
]

Mongo.RenameCollection("temp_table","customer")
? Mongo.ListCollections()
= [
    "system.indexes",
    "customer"
]

Changing Collection on a Mongo Object

The Mongo object has a collection property that can be changed after a collection object is created to point at a different collection.

In addition, the database property allows you to change the database being used.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","AppServerDemo","flowers")
Mongo.formatJson = .t.
Mongo.query_limit = 2
? Mongo.GetAllRecords()
= [
    {
        "_id": "5399ddd502681b9c258dc5f7",
        "Id": "00000000",
        "Picture": "=filename_decode(\"Hires\\allium.jpg\")",
        "Imagedate": "07/30/2001",
        "Keywords": "allium white"
    },
    {
        "_id": "5399ddd502681b9c258dc5f8",
        "Id": "00000001",
        "Picture": "=filename_decode(\"Hires\\alyssum purple.jpg\")",
        "Imagedate": "06/05/2002",
        "Keywords": "alyssum purple"
    }
]
Mongo.database = "northwind"
Mongo.collection = "Customers"
? Mongo.GetAllRecords()
= [
    {
        "_id": "5399d22302681b9c258dc5f6",
        "firstname": "John",
        "middlename": "Q",
        "lastname": "Public"
    }
]

Functions

Mongo databases can have saved functions, which are similar to stored procedures is SQL server.

Functions can be used in map/reduce and view definitions, but their use is generally cautioned against, as they can slow down the server.

In the below example, a helper function returns a full name given a record that contains parts of a name.

dim Mongo as extension::MongoDB = extension::MongoDB::Create("mongodb://localhost:27017","northwind","Customers")

? Mongo.ListFunctions()
= "[]"

Mongo.FunctionAdd("makefullname","function(doc) { return doc.firstname+' '+doc.middlename+' '+doc.lastname; }")
? Mongo.ListFunctions()
= ["makefullname"]


? Mongo.MapReduce("function() { emit(makefullname(this)); }")
= [{"_id":"John Q Public","value":[null]}]

CriteriaToJavascript Helper

Mongo has a view definition syntax for selecting records, Alpha Anywhere includes a helper function that allows a developer to create the javascript that returns the same records as a view definition would.

This function is provided primarily so that the client (i.e. browser) and for other document databases implementations that don't support Mongos query format so that they can consume the mongo query definition.

' Simple exact match
dim view as c = <<%str%
#{ "person": "jim" }
#%str%

? extension::MongoDB::CriteriaToJavascript(view)
= doc.person === "jim"


' Comparisons
dim view as c = <<%str%
#{"$or":[{"price":{"$gt":10}},{"qty":{"$gt":1}}]}
#%str%
? extension::MongoDB::CriteriaToJavascript(view)
= "(doc.price > 10 || doc.qty > 1)"

' Test against multiple entries
dim view as c = <<%str%
#{ "person": { "$in": ["jim", "john"] } }
#%str%

? extension::MongoDB::CriteriaToJavascript(view)
= ["jim","john"].indexOf( doc.person) > -1

See Also