~bigbes/tarantool

tarantool-protobuf

ref: 0b6e1bc52ef675919ba245e012538e615d17afa1 tarantool-protobuf/examples/dynamic/load_from_space.lua -rw-r--r-- 3.0 KiB
0b6e1bc5 — Eugene Blikh bench: add map_bench.lua, close ch2 (intervention regresses) 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
-- 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)