Class: Tina4::SessionHandlers::MongoWireClient
- Inherits:
-
Object
- Object
- Tina4::SessionHandlers::MongoWireClient
- Defined in:
- lib/tina4/session_handlers/mongo_wire_client.rb
Overview
Zero-dependency synchronous MongoDB client speaking the wire protocol (OP_MSG, opcode 2013) over a TCP socket, with a minimal BSON codec.
WHY IT EXISTS (session_contract.json #6, ADR-0024). Python, PHP and Node
have always shipped a raw OP_MSG fallback for the mongodb SESSION backend;
Ruby alone hard-required the mongo gem and RAISED without it. The same
.env therefore worked in three frameworks and failed in the fourth, which
breaks the zero-dependency promise ASYMMETRICALLY - worse than breaking it
consistently, because nothing about the configuration hints at it. This is
the missing fourth transport, ported from the proven Python master
(tina4_python/session_handlers/mongodb_handler.py) rather than invented.
It is deliberately NOT a general Mongo driver. It implements exactly the
four operations the session handler performs - find_one by _id, upsert by
_id, delete_one by _id, and delete_many for gc - and it mirrors the SHAPE
of the mongo gem's Collection for those four so the handler's read,
write, destroy and gc bodies are IDENTICAL on both transports. That is not
cosmetic: a branch inside each operation is a branch that can be wrong on
one path only, and this fallback would then ship untested behind a
condition nobody exercises.
HARD-WON DETAILS, preserved from the three siblings:
* The COMMAND NAME comes FIRST in the document and `$db` LAST. Reversed,
the server reads `$db` as the command name and answers CommandNotFound.
* BSON is little-endian throughout, string lengths are BYTE counts (not
character counts), and a Time is encoded as BSON UTC datetime (0x09,
int64 milliseconds) so a document written here is byte-identical in
shape to one the gem writes.
* An OP_MSG reply can arrive split across TCP segments, so the body is
read by its exact declared byte count rather than one recv().
NO NETWORK I/O IN A CONSTRUCTOR (ADR-0021, session_contract.json #4): the socket is opened on the FIRST command, inside the log-loud-and-degrade policy, never here.
Constant Summary collapse
- OP_MSG =
2013- TYPE_DOUBLE =
BSON element type bytes, named so the codec reads as the spec does.
0x01- TYPE_STRING =
0x02- TYPE_DOCUMENT =
0x03- TYPE_ARRAY =
0x04- TYPE_BINARY =
0x05- TYPE_OBJECT_ID =
0x07- TYPE_BOOLEAN =
0x08- TYPE_DATETIME =
0x09- TYPE_NULL =
0x0A- TYPE_INT32 =
0x10- TYPE_TIMESTAMP =
0x11- TYPE_INT64 =
0x12- INT32_MIN =
-2_147_483_648
- INT32_MAX =
2_147_483_647
Instance Method Summary collapse
- #close ⇒ Object
- #delete_many(filter) ⇒ Object
- #delete_one(filter) ⇒ Object
-
#find(filter) ⇒ Object
Documents matching
filter, capped at one. -
#initialize(host:, port:, database:, collection:, timeout: 10) ⇒ MongoWireClient
constructor
A new instance of MongoWireClient.
-
#update_one(filter, update, upsert: false) ⇒ Object
Apply
update(an operator document such as{"$set" => {...}}, or a full replacement) to the single document matchingfilter.
Constructor Details
#initialize(host:, port:, database:, collection:, timeout: 10) ⇒ MongoWireClient
Returns a new instance of MongoWireClient.
66 67 68 69 70 71 72 73 74 |
# File 'lib/tina4/session_handlers/mongo_wire_client.rb', line 66 def initialize(host:, port:, database:, collection:, timeout: 10) @host = host @port = port @database = database @collection = collection @timeout = timeout @socket = nil @request_id = 0 end |
Instance Method Details
#close ⇒ Object
104 105 106 107 108 109 110 |
# File 'lib/tina4/session_handlers/mongo_wire_client.rb', line 104 def close @socket&.close rescue StandardError nil # already closed / never opened - nothing to do ensure @socket = nil end |
#delete_many(filter) ⇒ Object
100 101 102 |
# File 'lib/tina4/session_handlers/mongo_wire_client.rb', line 100 def delete_many(filter) delete(filter, 0) end |
#delete_one(filter) ⇒ Object
96 97 98 |
# File 'lib/tina4/session_handlers/mongo_wire_client.rb', line 96 def delete_one(filter) delete(filter, 1) end |
#find(filter) ⇒ Object
Documents matching filter, capped at one.
Returns an ARRAY so the caller can write find(...).first exactly as it
does against the gem's Collection, which answers a lazy view. One shared
call site, two transports.
81 82 83 84 |
# File 'lib/tina4/session_handlers/mongo_wire_client.rb', line 81 def find(filter) reply = command("find" => @collection, "filter" => filter, "limit" => 1, "$db" => @database) reply.dig("cursor", "firstBatch") || [] end |
#update_one(filter, update, upsert: false) ⇒ Object
Apply update (an operator document such as {"$set" => {...}}, or a
full replacement) to the single document matching filter.
88 89 90 91 92 93 94 |
# File 'lib/tina4/session_handlers/mongo_wire_client.rb', line 88 def update_one(filter, update, upsert: false) command( "update" => @collection, "updates" => [{ "q" => filter, "u" => update, "upsert" => upsert }], "$db" => @database ) end |