Add support for range operators (#938)
This commit is contained in:
committed by
Joe Nelson
parent
c72bc37630
commit
59a320fd44
@@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #885, Postgres COMMENTs on SCHEMA/TABLE/COLUMN are used for OpenAPI - @ldesgoui
|
- #885, Postgres COMMENTs on SCHEMA/TABLE/COLUMN are used for OpenAPI - @ldesgoui
|
||||||
- #907, Ability to embed using a specific relation when there are multiple between tables - @ruslantalpa
|
- #907, Ability to embed using a specific relation when there are multiple between tables - @ruslantalpa
|
||||||
- #930, Split table comment on newline to get OpenAPI operation summary and description - @daurnimator
|
- #930, Split table comment on newline to get OpenAPI operation summary and description - @daurnimator
|
||||||
|
- #938, Support for range operators - @russelldavies
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #924, Improve relations initialization time - @9too
|
- #924, Improve relations initialization time - @9too
|
||||||
- #927, Treat blank pre-request as missing - @begriffs
|
- #927, Treat blank pre-request as missing - @begriffs
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #938, Deprecate symbol operators with mnemonic names. - @russelldavies
|
||||||
|
|
||||||
## [0.4.2.0] - 2017-06-11
|
## [0.4.2.0] - 2017-06-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -417,7 +417,9 @@ pgFmtFilter table (Filter fld (Operation hasNot_ ex)) = notOp <> " " <> case ex
|
|||||||
(op, VText val) -> pgFmtFieldOp op <> " " <> case op of
|
(op, VText val) -> pgFmtFieldOp op <> " " <> case op of
|
||||||
"like" -> unknownLiteral (T.map star val)
|
"like" -> unknownLiteral (T.map star val)
|
||||||
"ilike" -> unknownLiteral (T.map star val)
|
"ilike" -> unknownLiteral (T.map star val)
|
||||||
|
-- TODO: The '@@' was deprecated, remove in v0.5.0.0
|
||||||
"@@" -> "to_tsquery(" <> unknownLiteral val <> ") "
|
"@@" -> "to_tsquery(" <> unknownLiteral val <> ") "
|
||||||
|
"fts" -> "to_tsquery(" <> unknownLiteral val <> ") "
|
||||||
"is" -> whiteList val
|
"is" -> whiteList val
|
||||||
"isnot" -> whiteList val
|
"isnot" -> whiteList val
|
||||||
_ -> unknownLiteral val
|
_ -> unknownLiteral val
|
||||||
|
|||||||
@@ -161,6 +161,16 @@ operators = M.fromList [
|
|||||||
("notin", "NOT IN"),
|
("notin", "NOT IN"),
|
||||||
("isnot", "IS NOT"),
|
("isnot", "IS NOT"),
|
||||||
("is", "IS"),
|
("is", "IS"),
|
||||||
|
("fts", "@@"),
|
||||||
|
("cs", "@>"),
|
||||||
|
("cd", "<@"),
|
||||||
|
("ov", "&&"),
|
||||||
|
("sl", "<<"),
|
||||||
|
("sr", ">>"),
|
||||||
|
("nxr", "&<"),
|
||||||
|
("nxl", "&>"),
|
||||||
|
("adj", "-|-"),
|
||||||
|
-- TODO: these are deprecated and should be removed in v0.5.0.0
|
||||||
("@@", "@@"),
|
("@@", "@@"),
|
||||||
("@>", "@>"),
|
("@>", "@>"),
|
||||||
("<@", "<@")]
|
("<@", "<@")]
|
||||||
|
|||||||
@@ -70,20 +70,66 @@ spec =
|
|||||||
it "can handle is" $
|
it "can handle is" $
|
||||||
get "/entities?and=(name.is.null,arr.is.null)&select=id" `shouldRespondWith`
|
get "/entities?and=(name.is.null,arr.is.null)&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{ "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
it "can handle @@" $
|
it "can handle fts" $ do
|
||||||
|
get "/entities?or=(text_search_vector.fts.bar,text_search_vector.fts.baz)&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/entities?or=(text_search_vector.@@.bar,text_search_vector.@@.baz)&select=id" `shouldRespondWith`
|
get "/entities?or=(text_search_vector.@@.bar,text_search_vector.@@.baz)&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
it "can handle @> and <@" $
|
it "can handle cs and cd" $ do
|
||||||
|
get "/entities?or=(arr.cs.{1,2,3},arr.cd.{1})&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 },{ "id": 3 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/entities?or=(arr.@>.{1,2,3},arr.<@.{1})&select=id" `shouldRespondWith`
|
get "/entities?or=(arr.@>.{1,2,3},arr.<@.{1})&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 1 },{ "id": 3 }]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{ "id": 1 },{ "id": 3 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "can handle range operators" $ do
|
||||||
|
get "/ranges?range=eq.[1,3]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=neq.[1,3]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 2 }, { "id": 3 }, { "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=lt.[1,10]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=gt.[8,11]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=lte.[1,3]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=gte.[2,3]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 2 }, { "id": 3 }, { "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=cs.[1,2]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=cd.[1,6]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=ov.[0,4]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=sl.[9,10]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=sr.[3,4]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 3 }, { "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=nxr.[4,7]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }, { "id": 2 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=nxl.[4,7]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 3 }, { "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/ranges?range=adj.(3,10]&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
context "operators with not" $ do
|
context "operators with not" $ do
|
||||||
it "eq, @>, like can be negated" $
|
it "eq, cs, like can be negated" $ do
|
||||||
|
get "/entities?and=(arr.not.cs.{1,2,3},and(id.not.eq.2,name.not.like.*3))&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/entities?and=(arr.not.@>.{1,2,3},and(id.not.eq.2,name.not.like.*3))&select=id" `shouldRespondWith`
|
get "/entities?and=(arr.not.@>.{1,2,3},and(id.not.eq.2,name.not.like.*3))&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 1}]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{ "id": 1}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
it "in, is, @@ can be negated" $
|
it "in, is, fts can be negated" $ do
|
||||||
|
get "/entities?and=(id.not.in.(1,3),and(name.not.is.null,text_search_vector.not.fts.foo))&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 2}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/entities?and=(id.not.in.(1,3),and(name.not.is.null,text_search_vector.not.@@.foo))&select=id" `shouldRespondWith`
|
get "/entities?and=(id.not.in.(1,3),and(name.not.is.null,text_search_vector.not.@@.foo))&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 2}]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{ "id": 2}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
it "lt, gte, <@ can be negated" $
|
it "lt, gte, cd can be negated" $ do
|
||||||
|
get "/entities?and=(arr.not.cd.{1},or(id.not.lt.1,id.not.gte.3))&select=id" `shouldRespondWith`
|
||||||
|
[json|[{"id": 2}, {"id": 3}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/entities?and=(arr.not.<@.{1},or(id.not.lt.1,id.not.gte.3))&select=id" `shouldRespondWith`
|
get "/entities?and=(arr.not.<@.{1},or(id.not.lt.1,id.not.gte.3))&select=id" `shouldRespondWith`
|
||||||
[json|[{"id": 2}, {"id": 3}]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{"id": 2}, {"id": 3}]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
it "gt, lte, ilike can be negated" $
|
it "gt, lte, ilike can be negated" $
|
||||||
|
|||||||
+31
-11
@@ -98,14 +98,28 @@ spec = do
|
|||||||
it "matches with ilike using not operator" $
|
it "matches with ilike using not operator" $
|
||||||
get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]"
|
get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]"
|
||||||
|
|
||||||
it "matches with tsearch @@" $
|
it "matches with tsearch fts" $ do
|
||||||
get "/tsearch?text_search_vector=@@.foo" `shouldRespondWith`
|
get "/tsearch?text_search_vector=fts.impossible" `shouldRespondWith`
|
||||||
[json| [{"text_search_vector":"'bar':2 'foo':1"}] |]
|
[json| [{"text_search_vector": "'fun':5 'imposs':9 'kind':3" }] |]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/tsearch?text_search_vector=fts.possible" `shouldRespondWith`
|
||||||
|
[json| [{"text_search_vector": "'also':2 'fun':3 'possibl':8" }] |]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
|
get "/tsearch?text_search_vector=@@.impossible" `shouldRespondWith`
|
||||||
|
[json| [{"text_search_vector": "'fun':5 'imposs':9 'kind':3" }] |]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/tsearch?text_search_vector=@@.possible" `shouldRespondWith`
|
||||||
|
[json| [{"text_search_vector": "'also':2 'fun':3 'possibl':8" }] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "matches with tsearch @@ using not operator" $
|
it "matches with tsearch fts using not operator" $ do
|
||||||
get "/tsearch?text_search_vector=not.@@.foo" `shouldRespondWith`
|
get "/tsearch?text_search_vector=not.fts.impossible" `shouldRespondWith`
|
||||||
[json| [{"text_search_vector":"'baz':1 'qux':2"}] |]
|
[json| [{"text_search_vector": "'also':2 'fun':3 'possibl':8" }] |]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
|
get "/tsearch?text_search_vector=not.@@.impossible" `shouldRespondWith`
|
||||||
|
[json| [{"text_search_vector": "'also':2 'fun':3 'possibl':8" }] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "matches with computed column" $
|
it "matches with computed column" $
|
||||||
@@ -129,11 +143,17 @@ spec = do
|
|||||||
get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith`
|
get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith`
|
||||||
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1,"name":"Design w7"}]},{"id":2,"tasks":[{"id":3,"name":"Design w10"}]}]},{"id":2,"projects":[{"id":3,"tasks":[{"id":5,"name":"Design IOS"}]},{"id":4,"tasks":[{"id":7,"name":"Design OSX"}]}]}]|]
|
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1,"name":"Design w7"}]},{"id":2,"tasks":[{"id":3,"name":"Design w10"}]}]},{"id":2,"projects":[{"id":3,"tasks":[{"id":5,"name":"Design IOS"}]},{"id":4,"tasks":[{"id":7,"name":"Design OSX"}]}]}]|]
|
||||||
|
|
||||||
it "matches with @> operator" $
|
it "matches with cs operator" $ do
|
||||||
|
get "/complex_items?select=id&arr_data=cs.{2}" `shouldRespondWith`
|
||||||
|
[str|[{"id":2},{"id":3}]|]
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith`
|
get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith`
|
||||||
[str|[{"id":2},{"id":3}]|]
|
[str|[{"id":2},{"id":3}]|]
|
||||||
|
|
||||||
it "matches with <@ operator" $
|
it "matches with cd operator" $ do
|
||||||
|
get "/complex_items?select=id&arr_data=cd.{1,2,4}" `shouldRespondWith`
|
||||||
|
[str|[{"id":1},{"id":2}]|]
|
||||||
|
-- TODO: remove in 0.5.0 as deprecated
|
||||||
get "/complex_items?select=id&arr_data=<@.{1,2,4}" `shouldRespondWith`
|
get "/complex_items?select=id&arr_data=<@.{1,2,4}" `shouldRespondWith`
|
||||||
[str|[{"id":1},{"id":2}]|]
|
[str|[{"id":1},{"id":2}]|]
|
||||||
|
|
||||||
@@ -257,15 +277,15 @@ spec = do
|
|||||||
it "requesting children 2 levels" $
|
it "requesting children 2 levels" $
|
||||||
get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith`
|
get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith`
|
||||||
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||||
|
|
||||||
it "requesting children 2 levels (with relation path fixed)" $
|
it "requesting children 2 levels (with relation path fixed)" $
|
||||||
get "/clients?id=eq.1&select=id,projects:projects.client_id{id,tasks{id}}" `shouldRespondWith`
|
get "/clients?id=eq.1&select=id,projects:projects.client_id{id,tasks{id}}" `shouldRespondWith`
|
||||||
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||||
|
|
||||||
it "requesting many<->many relation" $
|
it "requesting many<->many relation" $
|
||||||
get "/tasks?select=id,users{id}" `shouldRespondWith`
|
get "/tasks?select=id,users{id}" `shouldRespondWith`
|
||||||
[str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
[str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||||
|
|
||||||
it "requesting many<->many relation (with relation path fixed)" $
|
it "requesting many<->many relation (with relation path fixed)" $
|
||||||
get "/tasks?select=id,users:users.users_tasks{id}" `shouldRespondWith`
|
get "/tasks?select=id,users:users.users_tasks{id}" `shouldRespondWith`
|
||||||
[str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
[str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||||
|
|||||||
Vendored
+8
-2
@@ -246,8 +246,8 @@ INSERT INTO nullable_integer VALUES (NULL);
|
|||||||
--
|
--
|
||||||
|
|
||||||
TRUNCATE TABLE tsearch CASCADE;
|
TRUNCATE TABLE tsearch CASCADE;
|
||||||
INSERT INTO tsearch VALUES ('''bar'':2 ''foo'':1');
|
INSERT INTO tsearch VALUES (to_tsvector('It''s kind of fun to do the impossible'));
|
||||||
INSERT INTO tsearch VALUES ('''baz'':1 ''qux'':2');
|
INSERT INTO tsearch VALUES (to_tsvector('But also fun to do what is possible'));
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
@@ -315,6 +315,12 @@ INSERT INTO grandchild_entities VALUES (3, 'grandchild entity 3', 2, null, null,
|
|||||||
INSERT INTO grandchild_entities VALUES (4, '(grandchild,entity,4)', 2, null, null, '{"a": {"b":"foo"}}');
|
INSERT INTO grandchild_entities VALUES (4, '(grandchild,entity,4)', 2, null, null, '{"a": {"b":"foo"}}');
|
||||||
INSERT INTO grandchild_entities VALUES (5, '(grandchild,entity,5)', 2, null, null, '{"b":"bar"}');
|
INSERT INTO grandchild_entities VALUES (5, '(grandchild,entity,5)', 2, null, null, '{"b":"bar"}');
|
||||||
|
|
||||||
|
TRUNCATE TABLE ranges CASCADE;
|
||||||
|
INSERT INTO ranges VALUES (1, '[1,3]');
|
||||||
|
INSERT INTO ranges VALUES (2, '[3,6]');
|
||||||
|
INSERT INTO ranges VALUES (3, '[6,9]');
|
||||||
|
INSERT INTO ranges VALUES (4, '[9,12]');
|
||||||
|
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|||||||
Vendored
+1
@@ -56,6 +56,7 @@ GRANT ALL ON TABLE
|
|||||||
, entities
|
, entities
|
||||||
, child_entities
|
, child_entities
|
||||||
, grandchild_entities
|
, grandchild_entities
|
||||||
|
, ranges
|
||||||
TO postgrest_test_anonymous;
|
TO postgrest_test_anonymous;
|
||||||
|
|
||||||
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
||||||
|
|||||||
Vendored
+8
@@ -1214,6 +1214,14 @@ create table grandchild_entities (
|
|||||||
jsonb_col jsonb
|
jsonb_col jsonb
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Table used for testing range operators
|
||||||
|
|
||||||
|
create table ranges (
|
||||||
|
id integer primary key,
|
||||||
|
range numrange
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
-- OpenAPI description tests
|
-- OpenAPI description tests
|
||||||
|
|
||||||
comment on table child_entities is 'child_entities comment';
|
comment on table child_entities is 'child_entities comment';
|
||||||
|
|||||||
Reference in New Issue
Block a user