feat: expose unique columns and many-to-many markers in OpenAPI

Add unique constraint and many-to-many relationship metadata to the
generated OpenAPI spec so clients can render them.

- Store unique constraints on Table as tableUniqueCols (mirroring
  tablePKCols) instead of denormalizing them onto each Column.
- Compute unique constraints via a per-table tbl_unique_cols CTE in
  tablesSqlQuery.
- Annotate unique columns and composite unique constraints in property
  descriptions, and emit m2m markers in table descriptions.
This commit is contained in:
2026-08-20 18:03:43 +02:00
parent ce7ea53a57
commit 77ab8f83ac
8 changed files with 169 additions and 9 deletions
+82 -1
View File
@@ -285,10 +285,91 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Foreign Key to `second.id`.<fk table='second' column='id'/>"
"description": "Note:\nThis is a Unique column.<unique/>\nThis is a Foreign Key to `second.id`.<fk table='second' column='id'/>"
}
|]
it "includes a unique description for a column with a unique constraint" $ do
r <- simpleBody <$> get "/"
let uniqueKey = r ^? key "definitions" . key "single_unique" . key "properties" . key "unique_key"
liftIO $
uniqueKey `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Unique column.<unique/>"
}
|]
it "includes the column list of a composite unique constraint" $ do
r <- simpleBody <$> get "/"
let compoundKey1 = r ^? key "definitions" . key "compound_unique" . key "properties" . key "key1"
compoundKey2 = r ^? key "definitions" . key "compound_unique" . key "properties" . key "key2"
liftIO $ do
compoundKey1 `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is part of a composite unique constraint.<unique cols='key1,key2'/>"
}
|]
compoundKey2 `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is part of a composite unique constraint.<unique cols='key1,key2'/>"
}
|]
it "includes the column list for mixed single and composite unique constraints" $ do
r <- simpleBody <$> get "/"
let uniqueCol = r ^? key "definitions" . key "mixed_unique" . key "properties" . key "id"
compoundKey1 = r ^? key "definitions" . key "mixed_unique" . key "properties" . key "key1"
compoundKey2 = r ^? key "definitions" . key "mixed_unique" . key "properties" . key "key2"
liftIO $ do
uniqueCol `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is a Unique column.<unique/>"
}
|]
compoundKey1 `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is part of a composite unique constraint.<unique cols='key1,key2'/>"
}
|]
compoundKey2 `shouldBe` Just
[aesonQQ|
{
"format": "int32",
"type": "integer",
"description": "Note:\nThis is part of a composite unique constraint.<unique cols='key1,key2'/>"
}
|]
it "includes m2m relationship markers in the table description" $ do
r <- simpleBody <$> get "/"
let beingDescription = r ^? key "definitions" . key "being" . key "description"
liftIO $
beingDescription `shouldBe` Just
[aesonQQ|"<m2m table='part' junction='being_part' source='being' target='part'/>"|]
describe "Foreign table" $
it "includes foreign table properties" $ do
+9
View File
@@ -1451,6 +1451,15 @@ create table test.compound_unique(
unique(key1, key2)
);
create table test.mixed_unique(
id integer not null,
key1 integer not null,
key2 integer not null,
value text,
unique(id),
unique(key1, key2)
);
create table test.family_tree (
id text not null primary key,
name text not null,