BedquiltDB Spec

Overview

This document specifies a high-level interface to BedquiltDB. The examples will be given in a pseudo-python language.

Connections

The semantics of a "Connection" are left to the client library to decide. In general, the following code example should be considered typical for an imperative language like Python or Ruby:

db = bedquilt.BedquiltClient("localhost/bedquilt_test")
people = db['people']

Because most/all BedquiltDB clients will be wrappers around that languages PostgreSQL library, it is likely that the semantics of creating a connection, including the connection-string specification, will mirror the semantics of that underlying library.

Database Operations

A Database is presumed to correspond to a PostgreSQL Database object.

Create Collection

Create a collection. Does nothing if the collection already exists.

Params:

  • collectionName::String

Returns: Boolean indicating whether the collection was created.

Examples:

db.create_collection("people")

Delete Collection

Delete a collection. Does nothing if the collection does not exist.

Params:

  • collectionName::String

Returns: Boolean indicating whether the collection was deleted.

Examples:

db.delete_collection("people")

List Collections

Get a list of collection names.

Params: None

Returns: List of string names of collections.

Examples:

for collection_name in db.list_collections():
    print collection_name

Collection Exists

Check if a collection exists.

Params:

  • collectionName::String

Examples:

db.collection_exists('people')

List Constraints

Get a list of constraints on a collection. The return value is a list of strings which should be meaningful to humans, but not necessarily meaningful to the BedquiltDB API.

Examples:

coll.list_constraints()

Returns: Boolean indicating whether the collection exists

Collection Operations

A collection is a named bucket in which JSON documents (objects) can be stored. The following assumptions are made about documents within a collection:

  • Each document must have a _id ("underscore eye-dee") field at the top level, whose value is a String.
  • If a document is written to a collection, but does not contain a _id field, then the server will randomly generate one and add it to the document before persisting to storage.
  • Each documents _id field must be unique. Each _id value is presumed to uniquely identify a logical document

In broad terms, the following pseudo-code should store a JSON document in the people collection:

people = db['people']
sarah = {
    '_id': 'abcd',
    'name': 'Sarah',
    'likes': ['icecream', 'code']
}
people.insert(sarah)

Add Constraints

Adds a constraint to the fields of this collection. The spec describes the fields which should be constrained, and how. This will validate all existing documents in the collection before applying. Further writes to this collection will be validated before writing, and will fail (raise an error) if the written document does not satisfy the constraints.

Spec Options: - $required (Boolean) : Enforces that this field must be present in all documents - $notnull (Boolean) : Field must never have a null value - $type (String) : enforce the type of this field, options are "string", "number|double|float", "array", "object"

Params:

  • spec::Map

Returns: Boolean indicating whether any constraints were added or not.

Examples:

coll.add_constraint({"name": {"$required": True,
                              "$notnull": True,
                              "$type": "string",
                              "$unique": False}})

Remove Constraints

Removes constraints, if they exist, on a collection. Removing constraints which don't exist is a no-op. Should use the same constraint spec documents as for creating constraints.

Params:

  • spec::Map

Returns: Boolean indicating whether any constraints were removed or not.

Examples:

coll.remove_constraint({
    "name": {"$required": True}
})

List Constraints

Get a list of constraints on a collection. The return value is a list of strings which should be meaningful to humans, but not necessarily meaningful to the BedquiltDB API.

Examples:

coll.list_constraints()

Insert

Insert a document into the collection. If the document does not contain an _id field, one will be generated and added to the document before insertion. If an _id is supplied and there already exists a document in this collection with the same _id, that is an error.

Params:

  • doc::Map

Returns: String representing the _id field of the document

Examples:

_id = coll.insert({"_id": "sarah@example.com",
                   "name": "Sarah Bingham",
                   "age": 42,
                   "likes": ["icecream", "code", "hockey"]})

Save

Write a document to the collection. If an _id is supplied and there already exists a document in this collection with the same _id, that document will be replaced with this one.

If the document does not contain an _id field, one will be generated and added to the document before insertion as a new document.

Params:

  • doc::Map

Returns: String representing the _id field of the document

Examples:

_id = coll.insert({"_id": "sarah@example.com",
                   "name": "Sarah Bingham",
                   "age": 42,
                   "likes": ["icecream", "code", "hockey"]})
sarah_doc = coll.find_one(_id)
sarah_doc[‘likes’].append("music")
coll.save(sarah_doc)

Find

Retrieve a sequence of documents which match the provided query document. if skip is supplied, that number of documents are skipped. If limit is supplied, the result sequence is limited to that number of documents. The sort parameter is an array of key->integer maps, where the key is the name of the field to sort by and the integer indicates ascending (1) or descending (-1) ordering. If the sort array contains more than one value, the sorts are applied in that order. For example [{age: 1}, {name: 1}] means "sort by age, then by name". Two 'special' sorts are available: $created, and $updated. The $created sort will sort documents by their creation timestamp, while $updated will sort by the time the documents were updated. These sorts should use hidden metadata which is not ordinarily available for querying.

Params:

  • query::Map
  • skip::Integer (optional, default 0)
  • limit::Integer (optional, default null)
  • sort::Array (optional, default null)

Returns: a (possibly empty) sequence of documents.

Examples:

people_who_like_icecream = coll.find(
    {"likes": ["icecream"]}
)

coll.find(
    {"likes": ["icecream"]},
    skip=4,
    limit=2,
    sort=[{"age": 1}]
)

coll.find(
    {"likes": ["icecream"]},
    skip=4,
    limit=2,
    sort=[{"age": 1, "name": -1}]
)

coll.find(
    {"likes": ["icecream"]},
    sort=[{"age": 1, "$updated": -1}]
)

Find One

Retrieve the first document which matches the provided query document from the collection. The filter specifies the structure of the returned document.

Params:

  • query::Map
  • skip::Integer (optional, default 0)
  • sort::Array (optional, default null)

Returns: A single document, or null if none could be found.

Examples:

likes = coll.find_one({"name": "Sarah Bingham"}, {"likes": 1})

Find One By Id

Retrieve the document whose _id field matches the supplied value.

Params:

  • id::String

Returns: A single document, or null if none could be found.

Examples:

likes = coll.find_one_by_id("sarah@example.com")

Find Many By Ids

Retrieve documents whose _id field is in the supplied list of ids.

Params:

  • ids::List[String]

Returns: a (possibly empty) sequence of documents.

Examples:

orders = coll.find_many_by_ids(["X224", "X573", "X248"])

Count

Get a count of documents in a collection, matching a query document.

Params:

  • query::Map

Returns: Integer indicating count of documents matching the query

Examples:

coll.count({"active": True})

Distinct

Get a list of distinct values which exist at some path in a collection. The path is a string representing a dotted-path into the collections documents. For example, to retrieve the list of distinct cities that users live in, the city field, within the address field, within the users collection, would be represented as "address.city".

Params:

  • path::String

Examples:

users = db['users']
users.distinct('address.city')    # => ['Edinburgh', 'Glasgow', ...]
users.distinct('lastName')        # => ['Smith', 'Clarke', ...]

Aside: Advanced Query Operations

Query documents are normally used as a sub-document match, following the semantics of PostgreSQL @> operator. A query document may optionally include Advanced Query Operators, which take the form of key=>value mappings where the key begins with a $ character.

These query operators can be mixed into a query document at any location, and at any level of nesting, and will be filtered out of the query before execution. In this way a match query can be comibined with advanced query operators.

The following operators are supported:

$eq => Any

Asserts that a field value is equal to some specified value. Examples:

collection.find({
    "city": {
        "$eq": "Glasgow"
    }
})

$noteq => Any

Asserts that a field value is not equal to some specified value. Examples:

collection.find({
    "city": {
        "$noteq": "Edinburgh"
    }
}

$gt => Number

Asserts that a value is greater than some specified value. Examples:

collection.find({
    "voteCount": {
        "$gt": 40
    }
})

$gte => Number

Asserts that a value is greater than or equal to some specified value. Examples:

collection.find({
    "voteCount": {
        "$gte": 20
    }
})

$lt => Number

Asserts that a value is less than some specified value. Examples:

collection.find({
    "voteCount": {
        "$lt": 40
    }
})

$lte => Number

Asserts that a value is less than or equal to some specified value. Examples:

collection.find({
    "voteCount": {
        "$lte": 20
    }
})

$in => Array

Asserts that a value is in a specified list of values. Examples:

collection.find({
    "city": {
        "$in": ["Manchester", "Edinburgh"]
    }
})

$notin => Array

Asserts that a value is not in a specified list of values. Examples:

collection.find({
    "city": {
        "$notin": ["London", "Glasgow"]
    }
})

$exists => Boolean

Asserts that the key exists (or doesn't exist). Examples:

collection.find({
    "paymentId": {
        "$exists": true
    }
})
collection.find({
    "paymentId": {
        "$exists": false
    }
})

$type => String

Asserts that the type of a fields value matches the provided type name. Valid types: "object", "string", "boolean", "number", "array", "null" Examples:

collection.find({
    "specification": {
        "$type": "object"
    }
})
collection.find({
    "specification": {
        "$type": "string"
    }
})

$like => String

Asserts that a fields string value is 'like' the specified pattern string, following the semantics of PostgreSQL LIKE operation. Examples:

collection.find({
    "title": {
        "$like": "%Ruby%"
    }
})

$regex => String

Asserts that a fields string value matches the provided regex pattern string, following the semantics of PostgreSQL ~ regex operation. Examples:

collection.find({
    "title": {
        "$regex": "^.*Elixir.*$"
    }
})

As an example of mixing match queries with advanced query operations, the following query should match all documents which live in either Edinburgh or Glasgow, and have logged in at least twice:

users.find({
    "address": {
        "city": {
            "$in": ["Edinburgh", "Glasgow"]
        }
    },
    "loginCount": {
        "$gte": 2
    }
})

Remove

Remove documents matching the query.

Params:

  • query::Map

Returns: Number, representing the number of documents removed

Examples:

removed = coll.remove({"likes": ["pears"]})

Remove One

Remove one document matching the query.

Params:

  • query::Map

Returns: Number, representing the number of documents removed, either one or zero.

Examples:

removed = coll.remove_one({"likes": ["pears"]})

Remove One By Id

Remove one document by its _id field.

Params:

  • id::String

Returns: Number, representing the number of documents removed, either one or zero.

Examples:

removed = coll.remove_one_by_id("abc")

Remove Many By Ids

Remove many documents by their _id fields.

Params:

  • id::Array

Returns: Number, representing the number of documents removed, either one or zero.

Examples:

removed = coll.remove_many_by_ids(["one", "two", "four"])