Skip to content

Commit 3f74e87

Browse files
committed
fix: fix inconsistencies in how schema id is handled.
1 parent 888d5a9 commit 3f74e87

File tree

1 file changed

+120
-68
lines changed

1 file changed

+120
-68
lines changed

core/database/foxx/api/schema_router.js

Lines changed: 120 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,54 @@ function fixSchOwnNmAr(a_sch) {
4343
}
4444
}
4545

46+
/**
47+
* Validates and parses a schema ID string. Returns the bare ID and version number.
48+
* If no version suffix is present, version is null.
49+
*
50+
* @param {string} schId - Schema ID, optionally with ":version" suffix
51+
* @returns {{ id: string, ver: number|null }}
52+
*/
53+
function parseSchemaId(schId) {
54+
const colonCount = (schId.match(/:/g) || []).length;
55+
56+
if (colonCount > 1) {
57+
throw [error.ERR_INVALID_PARAM, "Schema ID contains multiple colons: '" + schId + "'"];
58+
}
59+
60+
if (colonCount === 0) {
61+
return { id: schId, ver: null };
62+
}
63+
64+
const idx = schId.indexOf(":");
65+
const verStr = schId.substr(idx + 1);
66+
const ver = Number(verStr);
67+
68+
if (verStr.length === 0) {
69+
throw [error.ERR_INVALID_PARAM, "Schema ID has trailing colon with no version: '" + schId + "'"];
70+
}
71+
72+
if (!Number.isInteger(ver)) {
73+
throw [error.ERR_INVALID_PARAM, "Schema ID version suffix is not a valid integer: '" + verStr + "'"];
74+
}
75+
76+
return { id: schId.substr(0, idx), ver: ver };
77+
}
78+
79+
/**
80+
* Strips version suffix from a schema ID field if present.
81+
* Handles the case where procInputParam composites "id:ver" into obj.id.
82+
*
83+
* @param {object} obj - Object with an id field to clean
84+
*/
85+
function stripSchemaIdVersion(obj) {
86+
if (obj.id) {
87+
var idx = obj.id.indexOf(":");
88+
if (idx >= 0) {
89+
obj.id = obj.id.substr(0, idx);
90+
}
91+
}
92+
}
93+
4694
// Find all references (internal and external), load them, then place in refs param (object)
4795
// This allows preloading schema dependencies for schema processing on client side
4896
function _resolveDeps(a_sch_id, a_refs) {
@@ -123,9 +171,18 @@ router
123171
obj.own_nm = client.name;
124172
}
125173

174+
const parsed = parseSchemaId(req.body.id);
175+
176+
if (parsed.ver !== null && parsed.ver !== 0) {
177+
throw [error.ERR_INVALID_PARAM, "Schema ID version must be 0 for creation, got: " + parsed.ver];
178+
}
179+
126180
g_lib.procInputParam(req.body, "_sch_id", false, obj);
127181
g_lib.procInputParam(req.body, "desc", false, obj);
128182

183+
// Strip version suffix that procInputParam composited into obj.id
184+
stripSchemaIdVersion(obj);
185+
129186
sch = g_db.sch.save(obj, {
130187
returnNew: true,
131188
}).new;
@@ -210,16 +267,12 @@ router
210267
waitForSync: true,
211268
action: function () {
212269
const client = g_lib.getUserFromClientID(req.queryParams.client);
213-
var idx = req.queryParams.id.indexOf(":");
214-
if (idx < 0) {
270+
271+
const parsed = parseSchemaId(req.queryParams.id);
272+
if (parsed.ver === null) {
215273
throw [error.ERR_INVALID_PARAM, "Schema ID missing version number suffix."];
216274
}
217-
var sch_id = req.queryParams.id.substr(0, idx),
218-
sch_ver = parseInt(req.queryParams.id.substr(idx + 1)),
219-
sch_old = g_db.sch.firstExample({
220-
id: sch_id,
221-
ver: sch_ver,
222-
});
275+
let sch_old = g_db.sch.firstExample({ id: parsed.id, ver: parsed.ver });
223276

224277
if (!sch_old) {
225278
throw [
@@ -272,6 +325,7 @@ router
272325
}
273326

274327
g_lib.procInputParam(req.body, "_sch_id", true, obj);
328+
stripSchemaIdVersion(obj);
275329

276330
if (
277331
obj.id &&
@@ -382,16 +436,11 @@ router
382436
waitForSync: true,
383437
action: function () {
384438
const client = g_lib.getUserFromClientID(req.queryParams.client);
385-
var idx = req.queryParams.id.indexOf(":");
386-
if (idx < 0) {
439+
const parsed = parseSchemaId(req.queryParams.id);
440+
if (parsed.ver === null) {
387441
throw [error.ERR_INVALID_PARAM, "Schema ID missing version number suffix."];
388442
}
389-
var sch_id = req.queryParams.id.substr(0, idx),
390-
sch_ver = parseInt(req.queryParams.id.substr(idx + 1)),
391-
sch = g_db.sch.firstExample({
392-
id: sch_id,
393-
ver: sch_ver,
394-
});
443+
let sch = g_db.sch.firstExample({ id: parsed.id, ver: parsed.ver });
395444

396445
if (!sch)
397446
throw [
@@ -540,57 +589,64 @@ router
540589
description: `Delete schema. Schema ID: ${req.queryParams.id}`,
541590
});
542591

543-
const client = g_lib.getUserFromClientID(req.queryParams.client);
544-
var idx = req.queryParams.id.indexOf(":");
545-
if (idx < 0) {
546-
throw [error.ERR_INVALID_PARAM, "Schema ID missing version number suffix."];
547-
}
548-
var sch_id = req.queryParams.id.substr(0, idx),
549-
sch_ver = parseInt(req.queryParams.id.substr(idx + 1));
592+
g_db._executeTransaction({
593+
collections: {
594+
read: ["u", "uuid", "accn", "sch_dep", "sch_ver"],
595+
write: ["sch"],
596+
},
597+
waitForSync: true,
598+
action: function () {
550599

551-
sch_old = g_db.sch.firstExample({
552-
id: sch_id,
553-
ver: sch_ver,
554-
});
600+
const client = g_lib.getUserFromClientID(req.queryParams.client);
601+
const parsed = parseSchemaId(req.queryParams.id);
602+
if (parsed.ver === null) {
603+
throw [error.ERR_INVALID_PARAM, "Schema ID missing version number suffix."];
604+
}
605+
sch_old = g_db.sch.firstExample({ id: parsed.id, ver: parsed.ver });
555606

556-
if (!sch_old)
557-
throw [error.ERR_NOT_FOUND, "Schema '" + req.queryParams.id + "' not found."];
607+
if (!sch_old)
608+
throw [error.ERR_NOT_FOUND, "Schema '" + req.queryParams.id + "' not found."];
558609

559-
if (sch_old.own_id != client._id && !client.is_admin) throw error.ERR_PERM_DENIED;
610+
if (sch_old.own_id != client._id && !client.is_admin) throw error.ERR_PERM_DENIED;
560611

561-
// Cannot delete schemas that are in use
562-
if (sch_old.cnt) {
563-
throw [
564-
error.ERR_PERM_DENIED,
565-
"Schema is associated with data records - cannot update.",
566-
];
567-
}
612+
// Cannot delete schemas that are in use
613+
if (sch_old.cnt) {
614+
throw [
615+
error.ERR_PERM_DENIED,
616+
"Schema is associated with data records - cannot delete.",
617+
];
618+
}
568619

569-
// Cannot delete schemas references by other schemas
570-
if (
571-
g_db.sch_dep.firstExample({
572-
_to: sch_old._id,
573-
})
574-
) {
575-
throw [
576-
error.ERR_PERM_DENIED,
577-
"Schema is referenced by another schema - cannot update.",
578-
];
579-
}
620+
// Cannot delete schemas references by other schemas
621+
if (
622+
g_db.sch_dep.firstExample({
623+
_to: sch_old._id,
624+
})
625+
) {
626+
throw [
627+
error.ERR_PERM_DENIED,
628+
"Schema is referenced by another schema - cannot delete.",
629+
];
630+
}
580631

581-
// Only allow deletion of oldest and newest revisions of schemas
582-
if (
583-
g_db.sch_ver.firstExample({
584-
_from: sch_old._id,
585-
}) &&
586-
g_db.sch_ver.firstExample({
587-
_to: sch_old._id,
588-
})
589-
) {
590-
throw [error.ERR_PERM_DENIED, "Cannot delete intermediate schema revisions."];
591-
}
632+
// Only allow deletion of oldest and newest revisions of schemas
633+
if (
634+
g_db.sch_ver.firstExample({
635+
_from: sch_old._id,
636+
}) &&
637+
g_db.sch_ver.firstExample({
638+
_to: sch_old._id,
639+
})
640+
) {
641+
throw [error.ERR_PERM_DENIED, "Cannot delete intermediate schema revisions."];
642+
}
643+
644+
g_graph.sch.remove(sch_old._id);
645+
646+
res.send();
647+
},
648+
});
592649

593-
g_graph.sch.remove(sch_old._id);
594650
logger.logRequestSuccess({
595651
client: req.queryParams?.client,
596652
correlationId: req.headers["x-correlation-id"],
@@ -632,16 +688,12 @@ router
632688
description: `View schema. Schema ID: ${req.queryParams.id}`,
633689
});
634690
const client = g_lib.getUserFromClientID(req.queryParams.client);
635-
var idx = req.queryParams.id.indexOf(":");
636-
if (idx < 0) {
691+
692+
const parsed = parseSchemaId(req.queryParams.id);
693+
if (parsed.ver === null) {
637694
throw [error.ERR_INVALID_PARAM, "Schema ID missing version number suffix."];
638695
}
639-
var sch_id = req.queryParams.id.substr(0, idx),
640-
sch_ver = parseInt(req.queryParams.id.substr(idx + 1));
641-
sch = g_db.sch.firstExample({
642-
id: sch_id,
643-
ver: sch_ver,
644-
});
696+
sch = g_db.sch.firstExample({ id: parsed.id, ver: parsed.ver });
645697

646698
if (!sch) throw [error.ERR_NOT_FOUND, "Schema '" + req.queryParams.id + "' not found."];
647699

0 commit comments

Comments
 (0)