Collection is the set of documents stored in a database. We know the document can be presented as JSON string or IObject.
Each collection has it’s name — the string of alphanumeric characters and _.
The only sign the object belongs to the collection is existence in the object the field named “collection_nameID”. This field contains the unique identifier of the object in this collection.
For example, if the document is in the “forms” collections, it has the field “formsID”. The value of the field is the unique identifier of this document in the collection “forms”.
The document can be in multiple collections at the same time. It has multiple ID fields in such case.
You should do the following to perform a database query.
Resolve IoC dependency to take command object which interacts with DB.
ITask task = IOC.resolve( Keys.getOrAdd("command_name"), // each command has it's own unique name connection, // object - connection to the DB "collection_name", // each document belongs to the collection other comma-separated parameters // the set of parameters depends on the command );
If the dependency cannot be resolved, IOC.resolve throws ResolveDependencyException.
Execute the task.
task.execute();
If the command cannot be executed, task.execute() throws TaskExecutionException.
Creates the collection in the database to be used in the following database queries. Use this task on an initialization step, to create all necessary collections on the first server start.
Command name: db.collection.create
Additional parameters:
ID of the document is always the primary key, so GetById always use index. You can specify additional indexes to do Search more effectively as options.
The example of options object with indexes:
{ "ordered": [ "a", "b" ], "datetime": "date", "tags": "tags", "fulltext": "text", "language": "english" }
You should define a field or array of fields for each possible index type in the options. Also some additional parameters may be required.
Available index types:
Ordered, datetime and tags indexes are created for each field independently, so queries for all specified fields can be done in any combination. However, fulltext index is only one for the collection, the texts from all specified fields are concatenated for the indexing.
Adds the new object to the collection or updates the existed object.
Command name: db.collection.upsert
Additional parameters:
The Upsert command checks the existence of value of the “collection_nameID” field, if the ID field is absent it inserts the document, otherwise it updates the document. When the document is successfully inserted, the command adds the field “collection_nameID” to the document.
Adds new object to the collection.
Command name: db.collection.insert
If the document contains the field “collection_nameID” the command throws a successor of TaskPrepareException. In other cases it’s behavior is the same as of Upsert command.
It’s recommended to use Upsert if there is no strong necessity to use Insert.
Deletes the object from the collection.
Command name: db.collection.delete
Additional parameters:
The object must have the field “collection_nameID” which contains the unique identifier of the document in the collection.
When this field is present in the document, it’s be tried to delete the object from the collection, the field “collection_nameID” is deleted from the document.
If the document is absent in the collection, no error appears because the absence of the document with the specified id is the target postcondition, the field “collection_nameID” is deleted from the in-memory document.
Takes the document by it’s id.
Command name: db.collection.getbyid
Additional parameters:
If the document with such id does not exist, the TaskExecutionException is thrown.
ITask task = IOC.resolve( Keys.getOrAdd("db.collection.getbyid"), connection, collectionName, documentiId, (IAction<IObject>) foundDoc -> { try { System.out.println("Found by id"); System.out.println((String) doc.serialize()); } catch (SerializeException e) { throw new ActionExecuteException(e); } } ); task.execute();
Searching of the document in the collection.
Command name: db.collection.search
Additional parameters:
If no documents for the specified criteria were found, the callback function receives empty array.
The search criteria is the complex IObject which contains three parts: filter, pagination control and sorting order. For example, it may look like this.
{ "filter": { "$or": [ { "a": { "$eq": "b" } }, { "b": { "$gt": 42 } } ] }, "page": { "size": 50, "number": 2 }, "sort": [ { "a": "asc" }, { "b": "desc" } ] }
Filter is the criterion to filter the resulting documents. It’s the equivalent of SQL WHERE clause.
The filter is the set of conditions and operators. Conditions join operators together. Operators match the specified document field against the specified criteria.
Available conditions:
Available operators:
It’s possible to check nested fields using dot-separated syntax.
{ "filter": { "a.b.c": { "$eq": 123 } } }
Multiple conditions for the same field can be defined. It implies AND relations between then, all conditions must be satisfied.
{ "filter": { "finished": { "$gt": 15, "$lt": 20 } } }
Also multiple conditions for different fields can be ANDed implicitly too.
{ "filter": { "status": { "$eq": "A" }, "age": { "$lt": 30 } } }
Fulltext search has a special, more complex syntax.
It doesn’t require the document field because the search is done over pre-indexed fields defined during the collection creation. So, in the simplest form the fulltext filter may look like this.
{ "filter": { "$fulltext": "term1 term2" } }
However, it’s better to define the language for the fulltext query explicitly.
{ "filter": { "$fulltext": { "query": "term1 term2", "language": "english" } } }
The query can be more complex to join search terms with different conditions.
{ "filter": { "$fulltext": { "query": { "$or": [ "term1", "term2" ] }, "language": "english" } } }
Page criterion is used for pagination. You may define the page size and page number.
This is equivalent of SQL LIMIT and OFFSET clauses. However, here you must work in terms of pages, while SQL works in terms of rows to skip.
If the pagination is not defined, only first 100 documents from the collection are returned, i.e. page number 1 of size 100. Also if the page size is more than 1000, it’s limited to 1000.
ITask task = IOC.resolve( Keys.getOrAdd("db.collection.search"), connection, collectionName, new DSObject(String.format( "{ " + "\"filter\": { \"%1$s\": { \"$eq\": \"new value\" } }," + "\"page\": { \"size\": 2, \"number\": 2 }," + "\"sort\": [ { \"%1$s\": \"asc\" } ]" + "}", testField.toString())), (IAction<IObject[]>) docs -> { try { for (IObject doc : docs) { System.out.println("Found by " + testField); System.out.println((String) doc.serialize()); } } catch (SerializeException e) { throw new ActionExecuteException(e); } } ); task.execute();
Counts the number of documents in the collection matching specified criteria.
Command name: db.collection.count
Additional parameters:
Search criteria are the same as for Search command described above. However, only filter part of it is taken.
If no documents for the specified criteria were found, the callback function receives zero.
It’s recommended to avoid to use this task because the counting can be as slow as selecting the same documents using Search task. Try to store and update the desired counter separately and explicitly.
Get the document by id.
public interface IGetDocumentMessage { CollectionName collectionName(); string id(); void document(IObject doc); }; public class MessageHandler { void Handle(final IGetDocumentMessage mes) { IPool pool = IOC.resolve(Keys.getOrAdd("DatabaseConnectionPool")); try (PoolGuard guard = new PoolGuard(pool)) { ITask task = IOC.resolve( Keys.getOrAdd("db.collection.getbyid"), guard.getObject(), mes.collectionName(), mes.id(), (doc) -> { mes.document(doc); } ); task.execute(); } }
Also see the sample server implementation for details.
Implementation details: