Skip to content

Commit 5fab57c

Browse files
api: support schema introspection
This patch add `crud.schema` introspection handle, which allows to inspect which spaces are available and what format do they have. The feature covers two use cases: - when application users want to know what data they can manipulate, - when crud integration tools want to know data format for pre-processing (for example, CRUD HTTP API).
1 parent 6b20c8c commit 5fab57c

File tree

11 files changed

+464
-10
lines changed

11 files changed

+464
-10
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## Unreleased
99

10+
### Added
11+
* Space schema introspection API `crud.schema` (#380).
12+
1013
### Changed
1114
* `deps.sh` installs the `vshard` instead of the `cartridge` by default (#364).
1215
You could to specify an environment variable `CARTIRDGE_VERSION` to install

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,82 @@ end
17111711
rv:close()
17121712
```
17131713

1714+
### Schema
1715+
1716+
`crud` routers provide API to introspect spaces schema.
1717+
1718+
```lua
1719+
local schema, err = crud.update(space_name, opts)
1720+
```
1721+
1722+
where:
1723+
1724+
* `space_name` (`?string`) - name of the space (if `nil`, provides info for all spaces)
1725+
* `opts`:
1726+
* `timeout` (`?number`) - `vshard.call` timeout and vshard master
1727+
discovery timeout (in seconds), default value is 2
1728+
* `vshard_router` (`?string|table`) - Cartridge vshard group name or
1729+
vshard router instance. Set this parameter if your space is not
1730+
a part of the default vshard cluster
1731+
1732+
Returns space schema (or spaces schema map), error.
1733+
1734+
Beware that schema info is not exactly the same as underlying storage spaces schema.
1735+
The reason is that `crud` generates `bucket_id`, if it isn't provided,
1736+
so this field is actually nullable for a `crud` user. We also do not expose
1737+
`bucket_id` index info since it's a vshard utility and do not related
1738+
to application logic.
1739+
1740+
**Example:**
1741+
1742+
```lua
1743+
crud.schema('customers')
1744+
---
1745+
- format:
1746+
- name: id
1747+
type: unsigned
1748+
- name: bucket_id
1749+
type: unsigned
1750+
is_nullable: true
1751+
- name: name
1752+
type: string
1753+
- name: age
1754+
type: number
1755+
indexes:
1756+
0:
1757+
unique: true
1758+
parts:
1759+
- fieldno: 1
1760+
type: unsigned
1761+
exclude_null: false
1762+
is_nullable: false
1763+
id: 0
1764+
type: TREE
1765+
name: primary_index
1766+
2:
1767+
unique: false
1768+
parts:
1769+
- fieldno: 4
1770+
type: number
1771+
exclude_null: false
1772+
is_nullable: false
1773+
id: 2
1774+
type: TREE
1775+
name: age
1776+
...
1777+
```
1778+
1779+
```lua
1780+
crud.schema()
1781+
---
1782+
- customers:
1783+
format: ...
1784+
indexes: ...
1785+
shops:
1786+
format: ...
1787+
indexes: ...
1788+
```
1789+
17141790
## Cartridge roles
17151791

17161792
`cartridge.roles.crud-storage` is a Tarantool Cartridge role that depends on the

crud.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ local sharding_metadata = require('crud.common.sharding.sharding_metadata')
2121
local utils = require('crud.common.utils')
2222
local stats = require('crud.stats')
2323
local readview = require('crud.readview')
24+
local schema = require('crud.schema')
2425

2526
local crud = {}
2627

@@ -152,6 +153,10 @@ crud.storage_info = utils.storage_info
152153
-- @function readview
153154
crud.readview = readview.new
154155

156+
-- @refer schema.call
157+
-- @function schema
158+
crud.schema = schema.call
159+
155160
--- Initializes crud on node
156161
--
157162
-- Exports all functions that are used for calls

crud/common/schema.lua

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,7 @@ function schema.wrap_func_reload(vshard_router, func, ...)
113113
return res, err
114114
end
115115

116-
local function get_space_schema_hash(space)
117-
if space == nil then
118-
return ''
119-
end
120-
116+
schema.get_normalized_space_schema = function(space)
121117
local indexes_info = {}
122118
for i = 0, table.maxn(space.index) do
123119
local index = space.index[i]
@@ -133,12 +129,19 @@ local function get_space_schema_hash(space)
133129
end
134130
end
135131

136-
local space_info = {
132+
return {
137133
format = space:format(),
138134
indexes = indexes_info,
139135
}
136+
end
137+
138+
local function get_space_schema_hash(space)
139+
if space == nil then
140+
return ''
141+
end
140142

141-
return digest.murmur(msgpack.encode(space_info))
143+
local sch = schema.get_normalized_space_schema(space)
144+
return digest.murmur(msgpack.encode(sch))
142145
end
143146

144147
function schema.filter_obj_fields(obj, field_names)

crud/common/utils.lua

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ local function get_replicaset_by_replica_uuid(replicasets, uuid)
110110
return nil
111111
end
112112

113-
function utils.get_space(space_name, vshard_router, timeout, replica_uuid)
113+
function utils.get_spaces(vshard_router, timeout, replica_uuid)
114114
local replicasets, replicaset
115115
timeout = timeout or const.DEFAULT_VSHARD_CALL_TIMEOUT
116116
local deadline = fiber.clock() + timeout
@@ -160,9 +160,17 @@ function utils.get_space(space_name, vshard_router, timeout, replica_uuid)
160160
return nil, GetSpaceError:new(error_msg)
161161
end
162162

163-
local space = replicaset.master.conn.space[space_name]
163+
return replicaset.master.conn.space, nil, replicaset.master.conn.schema_version
164+
end
165+
166+
function utils.get_space(space_name, vshard_router, timeout, replica_uuid)
167+
local spaces, err, schema_version = utils.get_spaces(vshard_router, timeout, replica_uuid)
168+
169+
if spaces == nil then
170+
return nil, err
171+
end
164172

165-
return space, nil, replicaset.master.conn.schema_version
173+
return spaces[space_name], err, schema_version
166174
end
167175

168176
function utils.get_space_format(space_name, vshard_router)

crud/schema.lua

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
local checks = require('checks')
2+
local errors = require('errors')
3+
4+
local SchemaError = errors.new_class('SchemaError', {capture_stack = false})
5+
6+
local schema_module = require('crud.common.schema')
7+
local utils = require('crud.common.utils')
8+
9+
local schema = {}
10+
11+
local system_spaces = {
12+
-- https://github.com/tarantool/tarantool/blob/3240201a2f5bac3bddf8a74015db9b351954e0b5/src/box/schema_def.h#L77-L127
13+
['_vinyl_deferred_delete'] = true,
14+
['_schema'] = true,
15+
['_collation'] = true,
16+
['_vcollation'] = true,
17+
['_space'] = true,
18+
['_vspace'] = true,
19+
['_sequence'] = true,
20+
['_sequence_data'] = true,
21+
['_vsequence'] = true,
22+
['_index'] = true,
23+
['_vindex'] = true,
24+
['_func'] = true,
25+
['_vfunc'] = true,
26+
['_user'] = true,
27+
['_vuser'] = true,
28+
['_priv'] = true,
29+
['_vpriv'] = true,
30+
['_cluster'] = true,
31+
['_trigger'] = true,
32+
['_truncate'] = true,
33+
['_space_sequence'] = true,
34+
['_vspace_sequence'] = true,
35+
['_fk_constraint'] = true,
36+
['_ck_constraint'] = true,
37+
['_func_index'] = true,
38+
['_session_settings'] = true,
39+
-- https://github.com/tarantool/vshard/blob/b3c27b32637863e9a03503e641bb7c8c69779a00/vshard/storage/init.lua#L752
40+
['_bucket'] = true,
41+
-- https://github.com/tarantool/ddl/blob/b55d0ff7409f32e4d527e2d25444d883bce4163b/test/set_sharding_metadata_test.lua#L92-L98
42+
['_ddl_sharding_key'] = true,
43+
['_ddl_sharding_func'] = true,
44+
}
45+
46+
local function get_crud_schema(space)
47+
local sch = schema_module.get_normalized_space_schema(space)
48+
49+
-- bucket_id is not nullable for a storage, yet
50+
-- it is optional for a crud user.
51+
for _, v in ipairs(sch.format) do
52+
if v.name == 'bucket_id' then
53+
v.is_nullable = true
54+
end
55+
end
56+
57+
for id, v in pairs(sch.indexes) do
58+
-- There is no reason for a user to know about
59+
-- bucket_id index.
60+
if v.name == 'bucket_id' then
61+
sch.indexes[id] = nil
62+
end
63+
end
64+
65+
return sch
66+
end
67+
68+
schema.call = function(space_name, opts)
69+
checks('?string', {
70+
vshard_router = '?string|table',
71+
timeout = '?number',
72+
})
73+
74+
opts = opts or {}
75+
76+
local vshard_router, err = utils.get_vshard_router_instance(opts.vshard_router)
77+
if err ~= nil then
78+
return nil, SchemaError:new(err)
79+
end
80+
81+
local _, err = schema_module.reload_schema(vshard_router)
82+
if err ~= nil then
83+
return nil, SchemaError:new(err)
84+
end
85+
86+
local spaces, err = utils.get_spaces(vshard_router, opts.timeout)
87+
if err ~= nil then
88+
return nil, SchemaError:new(err)
89+
end
90+
91+
if space_name ~= nil then
92+
local space = spaces[space_name]
93+
if space == nil then
94+
return nil, SchemaError:new("Space %q doesn't exist", space_name)
95+
end
96+
return get_crud_schema(space)
97+
else
98+
local resp = {}
99+
100+
for name, space in pairs(spaces) do
101+
-- Can be indexed by space id and space name,
102+
-- so we need to be careful with duplicates.
103+
if type(name) == 'string' and system_spaces[name] == nil then
104+
resp[name] = get_crud_schema(space)
105+
end
106+
end
107+
108+
return resp
109+
end
110+
end
111+
112+
return schema
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env tarantool
2+
3+
require('strict').on()
4+
_G.is_initialized = function() return false end
5+
6+
local log = require('log')
7+
local errors = require('errors')
8+
local cartridge = require('cartridge')
9+
10+
if package.setsearchroot ~= nil then
11+
package.setsearchroot()
12+
else
13+
package.path = package.path .. debug.sourcedir() .. "/?.lua;"
14+
end
15+
16+
package.preload['customers-storage'] = function()
17+
return {
18+
role_name = 'customers-storage',
19+
init = require('storage_init')
20+
}
21+
end
22+
23+
local ok, err = errors.pcall('CartridgeCfgError', cartridge.cfg, {
24+
advertise_uri = 'localhost:3301',
25+
http_port = 8081,
26+
bucket_count = 3000,
27+
roles = {
28+
'cartridge.roles.crud-router',
29+
'cartridge.roles.crud-storage',
30+
'customers-storage',
31+
}}
32+
)
33+
34+
if not ok then
35+
log.error('%s', err)
36+
os.exit(1)
37+
end
38+
39+
_G.is_initialized = cartridge.is_healthy
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
return function()
2+
if box.info.ro == true then
3+
return
4+
end
5+
6+
local engine = os.getenv('ENGINE') or 'memtx'
7+
8+
local customers_space = box.schema.space.create('customers', {
9+
format = {
10+
{name = 'id', type = 'unsigned'},
11+
{name = 'bucket_id', type = 'unsigned'},
12+
{name = 'name', type = 'string'},
13+
{name = 'age', type = 'number'},
14+
},
15+
if_not_exists = true,
16+
engine = engine,
17+
})
18+
customers_space:create_index('id', {
19+
parts = { {field = 'id'} },
20+
if_not_exists = true,
21+
})
22+
customers_space:create_index('bucket_id', {
23+
parts = { {field = 'bucket_id'} },
24+
unique = false,
25+
if_not_exists = true,
26+
})
27+
28+
local shops_space = box.schema.space.create('shops', {
29+
format = {
30+
{name = 'registry_id', type = 'unsigned'},
31+
{name = 'bucket_id', type = 'unsigned'},
32+
{name = 'name', type = 'string'},
33+
{name = 'address', type = 'string'},
34+
{name = 'owner', type = 'string', is_nullable = true},
35+
},
36+
if_not_exists = true,
37+
engine = engine,
38+
})
39+
shops_space:create_index('registry', {
40+
parts = { {field = 'registry_id'} },
41+
if_not_exists = true,
42+
})
43+
shops_space:create_index('bucket_id', {
44+
parts = { {field = 'bucket_id'} },
45+
unique = false,
46+
if_not_exists = true,
47+
})
48+
shops_space:create_index('address', {
49+
parts = { {field = 'address'} },
50+
unique = true,
51+
if_not_exists = true,
52+
})
53+
end

0 commit comments

Comments
 (0)