divillysausages.com

Getting all the collection names with MongoDB and AS3

At work I've been making on a quick tool to modify objects in MongoDB for our upcoming game. As I remembered reading about some AS3 drivers that allow you to directly connect to a Mongo database rather than need to use a server script, I decided to take a look. And immediately hit a roadblock.

In the main libs I found; @s9tpepper's MongoAS3 lib, and JMCNet's full-mongo-flex-driver; there was no built-in way to get the collections in a database. If you've worked with Mongo before, you'll know that it's slightly important to know that.

Both libs provided access to the Mongo runCommand() command, but no combination of keys seemed to do the job. Of course, you could always just keep a static Array of Strings, but where's the fun in that?

Other languages and drivers usually have a function along the lines of getCollectionNames() and with the help of @dun4n's google-fu, we found out how to do it. Mmmm, smells like a hack!

How it works

So, for a database that looks like this:

Inital test DB setup

the basic gist is that you query the system.namespaces collection for all their entries. That'll returning you something like this:

{ name:test_db.system.indexes}
{ name:test_db.Foo}
{ name:test_db.Foo.$_id_}
{ name:test_db.Bar}
{ name:test_db.Bar.$_id_}

Once you have that, you need to strip out the name value (the test_db.Foo part), and ignore:

Anything that's left is your collections.

So for both libs, a complete solution:

MongoAS3

The latest SWC didn't work for me - I kept getting an error with the system.namespaces call - saying that there was a "." in the collection name. Using the code directly instead worked fine, though this means you'll have to have both the AS3Signals and AS3CoreLib in your classpath.

private var m_dbConn:Mongo 		= null;	// our db connection
private var m_collections:Array	= [];	// our array of collection names

// connects to our db
private function _connect():void
{
	// create our mongo object
	this.m_dbConn = new Mongo( "localhost", 27017 );
	
	// connect to our db
	this.m_dbConn.db( "test_db" ).connected.add( this._onConnect );
	this.m_dbConn.db( "test_db" ).connect();
}

// called when we're connected to the db
private function _onConnect( db:DB ):void
{
	trace( "Connected to the db, getting the collections" );
	this.m_dbConn.db( "test_db" ).collection( "system.namespaces" ).find( new Document, null ).add( this._onCollections );
}

// called when we get a response from our system query
private function _onCollections( cursor:Cursor ):void
{
	// get the first document
	var doc:Object = cursor.getNextDocument();
	
	// go through them
	while ( doc != null )
	{
		// store our name and get the next doc
		var name:String = doc["name"] as String;
		doc 			= cursor.getNextDocument();
		
		// we ignore anything with a $ in it, as though are internal mongo collections.
		// likewise, we ignore anything with .fs. or .system. in it
		if ( name.lastIndexOf( "$" ) != -1 || name.indexOf( "fs." ) != -1 || name.indexOf( "system." ) != -1 )
			continue;
			
		// it's a good name, but it's still in the form
		// dbName.collectionName, so strip off the db name
		var index:int = name.indexOf( "." );
		if ( index != -1 )
			name = name.substring( index + 1 );
			
		// add the name to our collection
		this.m_collections.push( name );
	}
	
	// check for more (can have a large number of collections)
	if ( cursor.hasMore() )
		cursor.getMore().add( this._onCollections );
	else
	{
		// we're finished
		trace( "Finished getting collections (" + this.m_collections.length + "):" );
		this.m_collections.sort();
		for each( var s:String in this.m_collections )
			trace( "\t" + s );
	}
}

JMCNet

private var m_dbConn:JMCNetMongoDBDriver 	= null;	// our db connection
private var m_collections:Array				= [];	// our array of collection names

// connects to our db
private function _connect():void
{
	// create our db connection
	this.m_dbConn 				= new JMCNetMongoDBDriver;
	this.m_dbConn.hostname		= "localhost";
	this.m_dbConn.port			= 27017;
	this.m_dbConn.databaseName	= "test_db";
	
	// add our event listener
	this.m_dbConn.addEventListener( JMCNetMongoDBDriver.EVT_CONNECTOK, this._onConnect );
	this.m_dbConn.connect();
}

// called when we're connected to the db
private function _onConnect( event:EventMongoDB ):void
{
	trace( "Connected to the db, getting the collections" );
	
	// query the system namespace
	this.m_dbConn.queryDoc( "system.namespaces", new MongoDocumentQuery, new MongoResponder( this._onCollections ) );
}

// called when we get the results of our system namespaces query
private function _onCollections( response:MongoDocumentResponse, token:* ):void
{
	// go through the response documents
	if ( response.documents != null && response.documents.length > 0 )
	{
		for each( var doc:MongoDocument in response.documents )
		{
			var name:String = doc.getValue( "name" ) as String;
			
			// we ignore anything with a $ in it, as though are internal mongo collections.
			// likewise, we ignore anything with .fs. or .system. in it
			if ( name.lastIndexOf( "$" ) != -1 || name.indexOf( "fs." ) != -1 || name.indexOf( "system." ) != -1 )
				continue;
				
			// it's a good name, but it's still in the form
			// dbName.collectionName, so strip off the db name
			var index:int = name.indexOf( "." );
			if ( index != -1 )
				name = name.substring( index + 1 );
				
			// add the name to our collection
			this.m_collections.push( name );
		}
	}
	
	// check if we have more
	var cursor:Cursor = response.cursorID;
	if ( cursor.isset() )
		this.m_dbConn.getMoreDoc( "system.namespaces", cursor, new MongoResponder( this._onCollections ) );
	else
	{
		// we're finished
		trace( "Finished getting collections (" + this.m_collections.length + "):" );
		this.m_collections.sort();
		for each( var s:String in this.m_collections )
			trace( "\t" + s );
	}
}

Files

I've put together a quick FlashDevelop project showing both libs connecting and recovering the collection names. You can download it here.

Flash test getting the collection names from a MongoDB database

Comments

Anonymous

Hi, im trying to use JMC library to connect Mongo to AIR, is there anyway to use it in pure ASĀ£ projects without all the mx stuff ?

Thank you in advance for interest.

Damian Connolly

If you check out the attached project, it's a pure AS3 project; the MX classes in the lib don't stop you using it, or force you to make a Flex project. If you want to completely remove it anyway, it should be possible to download the code from http://jmcnet-full-mongo-flex-driver.googlecode.com/svn/trunk/ and make the relevant substitutions, but I haven't looked into it.

Anonymous

Hi, i used your poejct as refernce and it works fine, still dont understand clearly why :D thank you

Submit a comment

* indicates required