{"id":20842805,"url":"https://github.com/mudge/oplogjam","last_synced_at":"2025-05-08T22:42:31.499Z","repository":{"id":46088801,"uuid":"93876666","full_name":"mudge/oplogjam","owner":"mudge","description":"An experiment in writing a \"safe\" MongoDB oplog tailer in Ruby.","archived":false,"fork":false,"pushed_at":"2020-06-03T15:46:45.000Z","size":101,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-08T22:42:26.622Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/oplogjam","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mudge.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-06-09T16:18:04.000Z","updated_at":"2017-09-14T09:43:15.000Z","dependencies_parsed_at":"2022-08-21T00:50:37.891Z","dependency_job_id":null,"html_url":"https://github.com/mudge/oplogjam","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudge%2Foplogjam","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudge%2Foplogjam/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudge%2Foplogjam/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mudge%2Foplogjam/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mudge","download_url":"https://codeload.github.com/mudge/oplogjam/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253160727,"owners_count":21863624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-18T01:25:33.661Z","updated_at":"2025-05-08T22:42:31.479Z","avatar_url":"https://github.com/mudge.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Oplogjam [![Build Status](https://travis-ci.org/mudge/oplogjam.svg)](https://travis-ci.org/mudge/oplogjam)\n\n**Current version:** 0.1.1  \n**Supported Ruby versions:** 2.0, 2.1, 2.2  \n**Supported MongoDB versions:** 2.4, 2.6, 3.0, 3.2, 3.4  \n**Supported PostgreSQL versions:** 9.5, 9.6\n\nAn experiment in writing a \"safe\" MongoDB oplog tailer that converts documents to PostgreSQL JSONB in Ruby.\n\nBased on experiences running [Stripe's now deprecated MoSQL project](https://github.com/stripe/mosql) in production, this project provides a core library which stores all MongoDB documents in the same standard table schema in PostgreSQL but leaves all configuration and orchestration to the user. This means that this library can be used to _power_ an end-to-end MoSQL replacement but does not provide all functionality itself.\n\nAt its heart, the library connects to a [MongoDB replica set oplog](https://docs.mongodb.com/manual/core/replica-set-oplog/) and provides an abstraction to users so they can iterate over the operations in the oplog and transform those into equivalent PostgreSQL SQL statements.\n\n```ruby\nDB = Sequel.connect('postgres:///acme')\nmongo = Mongo::Client.new('mongodb://localhost')\n\nOplogjam::Oplog.new(mongo).operations.each do |operation|\n  operation.apply('acme.widgets' =\u003e DB[:widgets], 'acme.anvils' =\u003e DB[:anvils])\nend\n```\n\n## Requirements\n\n* A [MongoDB replica set](https://docs.mongodb.com/manual/replication/);\n* [PostgreSQL 9.5](https://www.postgresql.org/docs/9.5/static/release-9-5.html) or newer (for [`INSERT ON CONFLICT`](https://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT) and [`jsonb_set`](https://www.postgresql.org/docs/9.5/static/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE) support);\n* A PostgreSQL database with the [`uuid-ossp` extension](https://www.postgresql.org/docs/current/static/uuid-ossp.html).\n\n## Why does `apply` take a mapping?\n\nThis library expects to replay operations on MongoDB collections on equivalent PostgreSQL tables. As the MongoDB oplog contains _all_ operations on a replica set in a single collection, you must provide a _mapping_ between MongoDB namespaces (e.g. a database and collection name such as `foo.bar` for a collection `bar` in the database `foo`) and PostgreSQL tables (represented by [Sequel datasets](https://github.com/jeremyevans/sequel/blob/master/doc/dataset_basics.rdoc)). Any operations for namespaces not included in the mapping will be ignored.\n\nFor example, if we only want to replay operations on `foo.bar` to a table `foo_bar` in PostgreSQL, we might have a mapping like so:\n\n```ruby\nDB = Sequel.connect('postgres:///oplogjam_test')\nmapping = { 'foo.bar' =\u003e DB[:foo_bar] }\n```\n\nThen we can pass this mapping when we call `apply` on an operation, e.g.\n\n```ruby\noplog.operations.each do |operation|\n  operation.apply(mapping)\nend\n```\n\nIn order for this to work, the PostgreSQL table `foo_bar` must have the following schema:\n\n```\n                             Table \"public.foo_bar\"\n   Column   |            Type             |              Modifiers\n------------+-----------------------------+-------------------------------------\n uuid       | uuid                        | not null default uuid_generate_v1()\n id         | jsonb                       | not null\n document   | jsonb                       | not null\n created_at | timestamp without time zone | not null\n updated_at | timestamp without time zone | not null\n deleted_at | timestamp without time zone |\nIndexes:\n    \"foo_bar_pkey\" PRIMARY KEY, btree (uuid)\n    \"foo_bar_id_deleted_at_key\" UNIQUE CONSTRAINT, btree (id, deleted_at)\n    \"foo_bar_id_index\" UNIQUE, btree (id) WHERE deleted_at IS NULL\n```\n\nWe can create this ourselves or use [`Oplogjam::Schema`](#oplogjamschema) to do it for us:\n\n```ruby\nschema = Oplogjam::Schema.new(DB)\nschema.create_table(:foo_bar)\nschema.import(collection, :foo_bar) # Optionally import data from a MongoDB collection\nschema.add_indexes(:foo_bar)\n```\n\n## Why does this project exist?\n\nSince maintenance of MoSQL by Stripe was ended, there have been several major changes that affect anyone designing a system to replay a MongoDB oplog in PostgreSQL:\n\n* The MongoDB driver ecosystem was overhauled and the Ruby driver API changed significantly;\n* PostgreSQL 9.5 introduced new JSONB operations such as `jsonb_set` for updating fields in JSONB objects;\n* PostgreSQL 9.5 also introduced `INSERT ON CONFLICT` for effectively \"upserting\" duplicate records on `INSERT`.\n\nRunning MoSQL in production also revealed that we didn't need its rich support for transforming MongoDB documents into typical relational schema with typed columns but instead relied entirely on its JSONB support: effectively mirroring the MongoDB document by storing it in a single JSONB column.\n\nWith that specific use case in mind, I wanted to explore whether a library to _safely_ transform arbitrary MongoDB operations into SQL could be done in Ruby and remain somewhat idiomatic.\n\n## Why doesn't this project come with some sort of executable?\n\nWhile the library is more opinionated about the data schema of PostgreSQL, it doesn't attempt to make any decisions about how you decide which collections you want to replicate and where they should be replicated to. Similarly, connecting to databases, logging, etc. are all left up to the user as something that can differ wildly.\n\nWhile in future I may add an executable, for now this is up to the user to manage.\n\n## API Documentation\n\n* [`Oplogjam::Oplog`](#oplogjamoplog)\n  * [`Oplogjam::Oplog.new(client)`](#oplogjamoplognewclient)\n  * [`Oplogjam::Oplog#operations([query])`](#oplogjamoplogoperationsquery)\n* [`Oplogjam::Schema`](#oplogjamschema)\n  * [`Oplogjam::Schema.new(db)`](#oplogjamschemadb)\n  * [`Oplogjam::Schema#create_table(name)`](#oplogjamschemacreate_tablename)\n  * [`Oplogjam::Schema#add_indexes(name)`](#oplogjamschemaadd_indexesname)\n  * [`Oplogjam::Schema#import(collection, name, batch_size = 100)`](#oplogjamschemaimportcollection-name-batch_size)\n* [`Oplogjam::Operation`](#oplogjamoperation)\n  * [`Oplogjam::Operation.from(bson)`](#oplogjamoperationfrombson)\n* [`Oplogjam::Noop`](#oplogjamnoop)\n  * [`Oplogjam::Noop.from(bson)`](#oplogjamnoopfrombson)\n  * [`Oplogjam::Noop#message`](#oplogjamnoopmessage)\n  * [`Oplogjam::Noop#id`](#oplogjamnoopid)\n  * [`Oplogjam::Noop#timestamp`](#oplogjamnooptimestamp)\n  * [`Oplogjam::Noop#ts`](#oplogjamnoopts)\n  * [`Oplogjam::Noop#==(other)`](#oplogjamnoopother)\n  * [`Oplogjam::Noop#apply(mapping)`](#oplogjamnoopapplymapping)\n* [`Oplogjam::Insert`](#oplogjaminsert)\n  * [`Oplogjam::Insert.from(bson)`](#oplogjaminsertfrombson)\n  * [`Oplogjam::Insert#id`](#oplogjaminsertid)\n  * [`Oplogjam::Insert#namespace`](#oplogjaminsertnamespace)\n  * [`Oplogjam::Insert#document`](#oplogjaminsertdocument)\n  * [`Oplogjam::Insert#timestamp`](#oplogjaminserttimestamp)\n  * [`Oplogjam::Insert#ts`](#oplogjaminsertts)\n  * [`Oplogjam::Insert#==(other)`](#oplogjaminsertother)\n  * [`Oplogjam::Insert#apply(mapping)`](#oplogjaminsertapplymapping)\n* [`Oplogjam::Update`](#oplogjamupdate)\n  * [`Oplogjam::Update.from(bson)`](#oplogjamupdatefrombson)\n  * [`Oplogjam::Update#id`](#oplogjamupdateid)\n  * [`Oplogjam::Update#namespace`](#oplogjamupdatenamespace)\n  * [`Oplogjam::Update#update`](#oplogjamupdateupdate)\n  * [`Oplogjam::Update#query`](#oplogjamupdatequery)\n  * [`Oplogjam::Update#timestamp`](#oplogjamupdatetimestamp)\n  * [`Oplogjam::Update#ts`](#oplogjamupdatets)\n  * [`Oplogjam::Update#==(other)`](#oplogjamupdateother)\n  * [`Oplogjam::Update#apply(mapping)`](#oplogjamupdateapplymapping)\n* [`Oplogjam::Delete`](#oplogjamdelete)\n  * [`Oplogjam::Delete.from(bson)`](#oplogjamdeletefrombson)\n  * [`Oplogjam::Delete#id`](#oplogjamdeleteid)\n  * [`Oplogjam::Delete#namespace`](#oplogjamdeletenamespace)\n  * [`Oplogjam::Delete#query`](#oplogjamdeletequery)\n  * [`Oplogjam::Delete#timestamp`](#oplogjamdeletetimestamp)\n  * [`Oplogjam::Delete#ts`](#oplogjamdeletets)\n  * [`Oplogjam::Delete#==(other)`](#oplogjamdeleteother)\n  * [`Oplogjam::Delete#apply(mapping)`](#oplogjamdeleteapplymapping)\n* [`Oplogjam::ApplyOps`](#oplogjamapplyops)\n  * [`Oplogjam::ApplyOps.from(bson)`](#oplogjamapplyopsfrombson)\n  * [`Oplogjam::ApplyOps#id`](#oplogjamapplyopsid)\n  * [`Oplogjam::ApplyOps#namespace`](#oplogjamapplyopsnamespace)\n  * [`Oplogjam::ApplyOps#operations`](#oplogjamapplyopsoperations)\n  * [`Oplogjam::ApplyOps#timestamp`](#oplogjamapplyopstimestamp)\n  * [`Oplogjam::ApplyOps#ts`](#oplogjamapplyopsts)\n  * [`Oplogjam::ApplyOps#==(other)`](#oplogjamapplyopsother)\n  * [`Oplogjam::ApplyOps#apply(mapping)`](#oplogjamapplyopsapplymapping)\n* [`Oplogjam::Command`](#oplogjamcommand)\n  * [`Oplogjam::Command.from(bson)`](#oplogjamcommandfrombson)\n  * [`Oplogjam::Command#id`](#oplogjamcommandid)\n  * [`Oplogjam::Command#namespace`](#oplogjamcommandnamespace)\n  * [`Oplogjam::Command#command`](#oplogjamcommandcommand)\n  * [`Oplogjam::Command#timestamp`](#oplogjamcommandtimestamp)\n  * [`Oplogjam::Command#ts`](#oplogjamcommandts)\n  * [`Oplogjam::Command#==(other)`](#oplogjamcommandother)\n  * [`Oplogjam::Command#apply(mapping)`](#oplogjamcommandapplymapping)\n\n### `Oplogjam::Oplog`\n\nAn object representing a MongoDB oplog.\n\n#### `Oplogjam::Oplog.new(client)`\n\n```ruby\nmongo = Mongo::Client.new('mongodb://localhost')\nOplogjam::Oplog.new(mongo)\n```\n\nReturn a new [`Oplogjam::Oplog`](#oplogjamoplog) for the given [`Mongo::Client`](http://api.mongodb.com/ruby/current/Mongo/Client.html) `client` connected to a replica set.\n\n#### `Oplogjam::Oplogjam#operations([query])`\n\n```ruby\noplog.operations.each do |operation|\n  # Do something with operation\nend\n\noplog.operations('ts' =\u003e { '$gt' =\u003e BSON::Timestamp.new(123456, 1) })\n```\n\nReturn an infinite `Enumerator` yielding [`Operation`](#oplogjamoperation)s from the [`Oplog`](#oplogjamoplog) with an optional MongoDB `query` which will affect the results from the underlying oplog.\n\n### `Oplogjam::Schema`\n\nA class to manage the PostgreSQL schema used by Oplogjam (e.g. creating tables, importing data, etc.).\n\n#### `Oplogjam::Schema.new(db)`\n\n```ruby\nDB = Sequel.connect('postgres:///oplogjam_test')\nschema = Oplogjam::Schema.new(DB)\n```\n\nReturn a new [`Oplogjam::Schema`](#oplogjamschema) for the given [Sequel database connection](http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html).\n\n#### `Oplogjam::Schema#create_table(name)`\n\n```ruby\nschema.create_table(:foo_bar)\n```\n\nAttempt to create a table for Oplogjam's use in PostgreSQL with the given `name` if it doesn't already exist. Note that the `name` may be a single `String`, `Symbol` or a [Sequel qualified identifier](https://github.com/jeremyevans/sequel/blob/master/doc/sql.rdoc#identifiers) if you're using [PostgreSQL schema](https://www.postgresql.org/docs/current/static/ddl-schemas.html).\n\nA table will be created with the following schema:\n\n* `uuid`: a UUID v1 primary key (v1 so that they are sequential);\n* `id`: a `jsonb` representation of the primary key of the MongoDB document;\n* `document`: a `jsonb` representation of the entire MongoDB document;\n* `created_at`: the `timestamp` when this row was created by Oplogjam (_not_ by MongoDB);\n* `updated_at`: the `timestamp` when this row was last updated by Oplogjam (_not_ by MongoDB);\n* `deleted_at`: the `timestamp` when this row was deleted by Oplogjam (_not_ by MongoDB).\n\nIf the table already exists, the method will do nothing.\n\n#### `Oplogjam::Schema#add_indexes(name)`\n\n```ruby\nschema.add_indexes(name)\n```\n\nAdd the following indexes and constraints to the table with the given `name` if they don't already exist:\n\n* A unique index on `id` and `deleted_at` so no two records can have the same MongoDB ID and deletion time;\n* A partial unique index on `id` where `deleted_at` is `NULL` so no two records can have the same ID and not be deleted.\n\nNote that the `name` may be a single `String`, `Symbol` or a [Sequel qualified identifier](https://github.com/jeremyevans/sequel/blob/master/doc/sql.rdoc#identifiers) if you're using PostgreSQL schema.\n\nIf the indexes already exist on the given table, the method will do nothing.\n\n#### `Oplogjam::Schema#import(collection, name[, batch_size = 100])`\n\n```ruby\nschema.import(mongo[:bar], :foo_bar)\n```\n\nImport all existing documents from a given [`Mongo::Collection`](http://api.mongodb.com/ruby/current/Mongo/Collection.html) `collection` into the PostgreSQL table with the given `name` in batches of `batch_size` (defaults to 100). Note that the `name` may be a single `String`, `Symbol` or [Sequel qualified identifier](https://github.com/jeremyevans/sequel/blob/master/doc/sql.rdoc#identifiers) if you're using PostgreSQL schema.\n\nFor performance, it's better to import existing data _before_ adding indexes to the table (hence the separate [`create_table`](#oplogjamschemacreate_tablename) and [`add_indexes`](#oplogjamschemaadd_indexesname) methods).\n\n### `Oplogjam::Operation`\n\nA class representing a single MongoDB oplog operation.\n\n#### `Oplogjam::Operation.from(bson)`\n\n```ruby\nOplogjam::Operation.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog operation into a corresponding Ruby object:\n\n* `Oplogjam::Noop`\n* `Oplogjam::Insert`\n* `Oplogjam::Update`\n* `Oplogjam::Delete`\n* `Oplogjam::ApplyOps`\n* `Oplogjam::Command`\n\nRaises a `Oplogjam::InvalidOperation` if the type of operation is not recognised.\n\n### `Oplogjam::Noop`\n\nA class representing a MongoDB no-op.\n\n#### `Oplogjam::Noop.from(bson)`\n\n```ruby\nOplogjam::Noop.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog no-op into an `Oplogjam::Noop` instance.\n\nRaises a `Oplogjam::InvalidNoop` error if the given document is not a valid no-op.\n\n#### `Oplogjam::Noop#message`\n\n```ruby\nnoop.message\n#=\u003e \"initiating set\"\n```\n\nReturn the internal message of the no-op.\n\n#### `Oplogjam::Noop#id`\n\n```ruby\nnoop.id\n#=\u003e -2135725856567446411\n```\n\nReturn the internal, unique identifier for the no-op.\n\n#### `Oplogjam::Noop#timestamp`\n\n```ruby\nnoop.timestamp\n#=\u003e 2017-09-09 16:11:18 +0100\n```\n\nReturn the time of the no-op as a `Time`.\n\n#### `Oplogjam::Noop#ts`\n\n```ruby\nnoop.ts\n#=\u003e #\u003cBSON::Timestamp:0x007fcadfa44500 @increment=1, @seconds=1479419535\u003e\n```\n\nReturn the raw, underlying BSON Timestamp of the no-op.\n\n#### `Oplogjam::Noop#==(other)`\n\n```ruby\nnoop == other_noop\n#=\u003e false\n```\n\nCompares the identifiers of two no-ops and returns true if they are equal.\n\n#### `Oplogjam::Noop#apply(mapping)`\n\n```ruby\nnoop.apply('foo.bar' =\u003e DB[:bar])\n```\n\nApply this no-op to a mapping of MongoDB namespaces (e.g. `foo.bar`) to Sequel datasets representing PostgreSQL tables. As no-ops do nothing, this performs no operation.\n\n### `Oplogjam::Insert`\n\nA class representing a MongoDB insert.\n\n#### `Oplogjam::Insert.from(bson)`\n\n```ruby\nOplogjam::Insert.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog insert into an `Oplogjam::Insert` instance.\n\nRaises a `Oplogjam::InvalidInsert` error if the given document is not a valid insert.\n\n#### `Oplogjam::Insert#id`\n\n```ruby\ninsert.id\n#=\u003e -2135725856567446411\n```\n\nReturn the internal, unique identifier for the insert.\n\n#### `Oplogjam::Insert#namespace`\n\n```ruby\ninsert.namespace\n#=\u003e \"foo.bar\"\n```\n\nReturn the namespace the insert affects. This will be a `String` of the form `database.collection`, e.g. `foo.bar`.\n\n#### `Oplogjam::Insert#document`\n\n```ruby\ninsert.document\n#=\u003e {\"_id\"=\u003e1}\n```\n\nReturn the `BSON::Document` being inserted.\n\n#### `Oplogjam::Insert#timestamp`\n\n```ruby\ninsert.timestamp\n#=\u003e 2017-09-09 16:11:18 +0100\n```\n\nReturn the time of the insert as a `Time`.\n\n#### `Oplogjam::Insert#ts`\n\n```ruby\ninsert.ts\n#=\u003e #\u003cBSON::Timestamp:0x007fcadfa44500 @increment=1, @seconds=1479419535\u003e\n```\n\nReturn the raw, underlying BSON Timestamp of the insert.\n\n#### `Oplogjam::Insert#==(other)`\n\n```ruby\ninsert == other_insert\n#=\u003e false\n```\n\nCompares the identifiers of two inserts and returns true if they are equal.\n\n#### `Oplogjam::Insert#apply(mapping)`\n\n```ruby\ninsert.apply('foo.bar' =\u003e DB[:bar])\n```\n\nApply this insert to a mapping of MongoDB namespaces (e.g. `foo.bar`) to Sequel datasets representing PostgreSQL tables. If the namespace of the insert maps to a dataset in the mapping, insert this document into the dataset with the following values:\n\n* A unique UUID v1 identifier;\n* The value of the document's `_id` stored as a JSONB value;\n* The entire document stored as a JSONB value;\n* The current time as `created_at`;\n* The current time as `updated_at`.\n\n### `Oplogjam::Update`\n\nA class representing a MongoDB update.\n\n#### `Oplogjam::Update.from(bson)`\n\n```ruby\nOplogjam::Update.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog update into an `Oplogjam::Update` instance.\n\nRaises a `Oplogjam::InvalidUpdate` error if the given document is not a valid update.\n\n#### `Oplogjam::Update#id`\n\n```ruby\nupdate.id\n#=\u003e -2135725856567446411\n```\n\nReturn the internal, unique identifier for the update.\n\n#### `Oplogjam::Update#namespace`\n\n```ruby\nupdate.namespace\n#=\u003e \"foo.bar\"\n```\n\nReturn the namespace the update affects. This will be a `String` of the form `database.collection`, e.g. `foo.bar`.\n\n#### `Oplogjam::Update#update`\n\n```ruby\nupdate.update\n#=\u003e {\"$set\"=\u003e{\"name\"=\u003e\"Alice\"}}\n```\n\nReturn the update to be applied as a `BSON::Document`.\n\n#### `Oplogjam::Update#query`\n\n```ruby\nupdate.query\n#=\u003e {\"_id\"=\u003e1}\n```\n\nReturn the query identifying which documents should be updated as a `BSON::Document`.\n\n#### `Oplogjam::Update#timestamp`\n\n```ruby\nupdate.timestamp\n#=\u003e 2017-09-09 16:11:18 +0100\n```\n\nReturn the time of the update as a `Time`.\n\n#### `Oplogjam::Update#ts`\n\n```ruby\nupdate.ts\n#=\u003e #\u003cBSON::Timestamp:0x007fcadfa44500 @increment=1, @seconds=1479419535\u003e\n```\n\nReturn the raw, underlying BSON Timestamp of the update.\n\n#### `Oplogjam::Update#==(other)`\n\n```ruby\nupdate == other_update\n#=\u003e false\n```\n\nCompares the identifiers of two updates and returns true if they are equal.\n\n#### `Oplogjam::Update#apply(mapping)`\n\n```ruby\nupdate.apply('foo.bar' =\u003e DB[:bar])\n```\n\nApply this update to a mapping of MongoDB namespaces (e.g. `foo.bar`) to Sequel datasets representing PostgreSQL tables. If the namespace of the update maps to a dataset in the mapping, perform the update by finding the relevant row based on the query and transforming the MongoDB update into an equivalent PostgreSQL update.\n\nThis will also update the `updated_at` column of the target document to the current time.\n\n### `Oplogjam::Delete`\n\nA class representing a MongoDB deletion.\n\n#### `Oplogjam::Delete.from(bson)`\n\n```ruby\nOplogjam::Delete.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog delete into an `Oplogjam::Delete` instance.\n\nRaises a `Oplogjam::InvalidDelete` error if the given document is not a valid delete.\n\n#### `Oplogjam::Delete#id`\n\n```ruby\ndelete.id\n#=\u003e -2135725856567446411\n```\n\nReturn the internal, unique identifier for the delete.\n\n#### `Oplogjam::Delete#namespace`\n\n```ruby\ndelete.namespace\n#=\u003e \"foo.bar\"\n```\n\nReturn the namespace the delete affects. This will be a `String` of the form `database.collection`, e.g. `foo.bar`.\n\n#### `Oplogjam::Delete#query`\n\n```ruby\ndelete.query\n#=\u003e {\"_id\"=\u003e1}\n```\n\nReturn the query identifying which documents should be deleted as a `BSON::Document`.\n\n#### `Oplogjam::Delete#timestamp`\n\n```ruby\ndelete.timestamp\n#=\u003e 2017-09-09 16:11:18 +0100\n```\n\nReturn the time of the delete as a `Time`.\n\n#### `Oplogjam::Delete#ts`\n\n```ruby\ndelete.ts\n#=\u003e #\u003cBSON::Timestamp:0x007fcadfa44500 @increment=1, @seconds=1479419535\u003e\n```\n\nReturn the raw, underlying BSON Timestamp of the delete.\n\n#### `Oplogjam::Delete#==(other)`\n\n```ruby\ndelete == other_delete\n#=\u003e false\n```\n\nCompares the identifiers of two deletes and returns true if they are equal.\n\n#### `Oplogjam::Delete#apply(mapping)`\n\n```ruby\ndelete.apply('foo.bar' =\u003e DB[:bar])\n```\n\nApply this delete to a mapping of MongoDB namespaces (e.g. `foo.bar`) to Sequel datasets representing PostgreSQL tables. If the namespace of the delete maps to a dataset in the mapping, perform a soft deletion by finding the relevant row based on the query and setting `deleted_at` to the current time.\n\nThis will also update the `updated_at` column of the target document to the current time.\n\n### `Oplogjam::ApplyOps`\n\nA class representing a series of MongoDB operations in a single operation.\n\n#### `Oplogjam::ApplyOps.from(bson)`\n\n```ruby\nOplogjam::ApplyOps.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog apply ops into an `Oplogjam::ApplyOps` instance.\n\nRaises a `Oplogjam::InvalidApplyOps` error if the given document is not a valid apply ops.\n\n#### `Oplogjam::ApplyOps#id`\n\n```ruby\napply_ops.id\n#=\u003e -2135725856567446411\n```\n\nReturn the internal, unique identifier for the apply ops.\n\n#### `Oplogjam::ApplyOps#namespace`\n\n```ruby\napply_ops.namespace\n#=\u003e \"foo.bar\"\n```\n\nReturn the namespace the apply ops affects. This will be a `String` of the form `database.collection`, e.g. `foo.bar`.\n\n#### `Oplogjam::ApplyOps#operations`\n\n```ruby\napply_ops.operations\n#=\u003e [#\u003cOplogjam::Insert ...\u003e]\n```\n\nReturn the operations within the apply ops as Oplogjam operations of the appropriate type.\n\n#### `Oplogjam::ApplyOps#timestamp`\n\n```ruby\napply_ops.timestamp\n#=\u003e 2017-09-09 16:11:18 +0100\n```\n\nReturn the time of the apply ops as a `Time`.\n\n#### `Oplogjam::ApplyOps#ts`\n\n```ruby\napply_ops.ts\n#=\u003e #\u003cBSON::Timestamp:0x007fcadfa44500 @increment=1, @seconds=1479419535\u003e\n```\n\nReturn the raw, underlying BSON Timestamp of the apply ops.\n\n#### `Oplogjam::ApplyOps#==(other)`\n\n```ruby\napply_ops == other_apply_ops\n#=\u003e false\n```\n\nCompares the identifiers of two apply ops and returns true if they are equal.\n\n#### `Oplogjam::ApplyOps#apply(mapping)`\n\n```ruby\napply_ops.apply('foo.bar' =\u003e DB[:bar])\n```\n\nApply all of the operations inside this apply ops to a mapping of MongoDB namespaces (e.g. `foo.bar`) to Sequel datasets representing PostgreSQL tables. If the namespace of the operations map to a dataset in the mapping, apply them as described in each operation type's `apply` method.\n\n### `Oplogjam::Command`\n\nA class representing a MongoDB command.\n\n#### `Oplogjam::Command.from(bson)`\n\n```ruby\nOplogjam::Command.from(document)\n```\n\nConvert a BSON document representing a MongoDB oplog command into an `Oplogjam::Command` instance.\n\nRaises a `Oplogjam::InvalidCommand` error if the given document is not a valid command.\n\n#### `Oplogjam::Command#id`\n\n```ruby\ncommand.id\n#=\u003e -2135725856567446411\n```\n\nReturn the internal, unique identifier for the command.\n\n#### `Oplogjam::Command#namespace`\n\n```ruby\ncommand.namespace\n#=\u003e \"foo.bar\"\n```\n\nReturn the namespace the command affects. This will be a `String` of the form `database.collection`, e.g. `foo.bar`.\n\n#### `Oplogjam::Command#command`\n\n```ruby\ncommand.command\n#=\u003e {\"create\"=\u003e\"bar\"}\n```\n\nReturn the contents of the command as a `BSON::Document`.\n\n#### `Oplogjam::Command#timestamp`\n\n```ruby\ncommand.timestamp\n#=\u003e 2017-09-09 16:11:18 +0100\n```\n\nReturn the time of the command as a `Time`.\n\n#### `Oplogjam::Command#ts`\n\n```ruby\ncommand.ts\n#=\u003e #\u003cBSON::Timestamp:0x007fcadfa44500 @increment=1, @seconds=1479419535\u003e\n```\n\nReturn the raw, underlying BSON Timestamp of the command.\n\n#### `Oplogjam::Command#==(other)`\n\n```ruby\ncommand == other_command\n#=\u003e false\n```\n\nCompares the identifiers of two commands and returns true if they are equal.\n\n#### `Oplogjam::Command#apply(mapping)`\n\n```ruby\ncommand.apply('foo.bar' =\u003e DB[:bar])\n```\n\nApply this command to a mapping of MongoDB namespaces (e.g. `foo.bar`) to Sequel datasets representing PostgreSQL tables. As commands have no equivalent in PostgreSQL, this performs no operation.\n\n## Acknowledgements\n\n* [Stripe's MoSQL](https://github.com/stripe/mosql)\n* [Stripe's Mongoriver](https://github.com/stripe/mongoriver/)\n\n## License\n\nCopyright © 2017 Paul Mucur.\n\nDistributed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmudge%2Foplogjam","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmudge%2Foplogjam","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmudge%2Foplogjam/lists"}