-- Load .proto schemas from a Tarantool space at runtime. -- -- Pattern: store the .proto source text in a space, parse it on demand, -- cache the resulting module. Schema updates land in the space; readers -- pick them up after a cache invalidation. -- -- Run with: -- LUA_PATH="./runtime/?/init.lua;./runtime/?.lua;;" tarantool examples/dynamic/load_from_space.lua -- Ephemeral instance; state in /tmp so the example doesn't litter the -- working directory with .snap / .xlog files. local data_dir = '/tmp/tarantool-protobuf-dynamic-example' os.execute('mkdir -p ' .. data_dir) box.cfg{ listen = nil, memtx_dir = data_dir, wal_dir = data_dir, log = data_dir .. '/tarantool.log', } local pb = require('pb') -- 1. Bootstrap a space that holds proto schemas keyed by name. box.once('init_schemas', function() box.schema.space.create('proto_schemas') box.space.proto_schemas:format({ {name = 'name', type = 'string'}, {name = 'version', type = 'unsigned'}, {name = 'source', type = 'string'}, }) box.space.proto_schemas:create_index('pk', {parts = {'name'}}) end) -- 2. Insert a schema. In production this lands via your app's -- admin / migration path; here we just write it inline. box.space.proto_schemas:replace{'demo.user.v1', 1, [[ syntax = "proto3"; package demo.user.v1; message User { int32 id = 1; string name = 2; repeated string tags = 3; } ]]} -- 3. Cache parsed modules by (schema_name, version). Invalidate by -- bumping the version row when you update the source. local cache = {} local function get_module(name) local row = box.space.proto_schemas:get(name) if row == nil then error(("schema %q not found"):format(name), 0) end local key = name .. '@' .. tostring(row.version) local mod = cache[key] if mod == nil then mod = pb.parse(row.source) cache[key] = mod end return mod end -- 4. Use it like any generated module. local demo = get_module('demo.user.v1') local bytes = demo.User_encode({ id = 42, name = 'Alice', tags = {'admin', 'active'}, }) print(('encoded %d bytes'):format(#bytes)) local user = demo.User_decode(bytes) print(('decoded: id=%d name=%s tags=%s,%s'):format( user.id, user.name, user.tags[1], user.tags[2])) -- 5. Schema upgrade: bump version + source, next get_module rebuilds. box.space.proto_schemas:replace{'demo.user.v1', 2, [[ syntax = "proto3"; package demo.user.v1; message User { int32 id = 1; string name = 2; repeated string tags = 3; string email = 4; } ]]} local demo_v2 = get_module('demo.user.v1') -- v1-encoded bytes still decode (new field absent). local user_v1 = demo_v2.User_decode(bytes) print(('v2 reads v1 bytes: email=%s'):format(tostring(user_v1.email))) -- v2-encoded bytes carry the new field. local bytes_v2 = demo_v2.User_encode({ id = 42, name = 'Alice', tags = {'admin'}, email = 'alice@x', }) print(('v2 encoded %d bytes; email round-trips: %s') :format(#bytes_v2, demo_v2.User_decode(bytes_v2).email)) os.exit(0)