@metaobjectsdev/metadata 0.8.1 → 0.9.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attr-schema-validate.d.ts.map +1 -1
- package/dist/attr-schema-validate.js +49 -1
- package/dist/attr-schema-validate.js.map +1 -1
- package/dist/core/field/field-constants.d.ts +27 -2
- package/dist/core/field/field-constants.d.ts.map +1 -1
- package/dist/core/field/field-constants.js +30 -2
- package/dist/core/field/field-constants.js.map +1 -1
- package/dist/core/field/field-schema.d.ts +9 -1
- package/dist/core/field/field-schema.d.ts.map +1 -1
- package/dist/core/field/field-schema.js +34 -3
- package/dist/core/field/field-schema.js.map +1 -1
- package/dist/core/field/meta-field.d.ts +10 -0
- package/dist/core/field/meta-field.d.ts.map +1 -1
- package/dist/core/field/meta-field.js +36 -1
- package/dist/core/field/meta-field.js.map +1 -1
- package/dist/core/field/validate-field-readonly.d.ts +9 -0
- package/dist/core/field/validate-field-readonly.d.ts.map +1 -0
- package/dist/core/field/validate-field-readonly.js +116 -0
- package/dist/core/field/validate-field-readonly.js.map +1 -0
- package/dist/core/object/meta-object-aware.d.ts +11 -0
- package/dist/core/object/meta-object-aware.d.ts.map +1 -0
- package/dist/core/object/meta-object-aware.js +15 -0
- package/dist/core/object/meta-object-aware.js.map +1 -0
- package/dist/core/object/meta-object.d.ts +21 -0
- package/dist/core/object/meta-object.d.ts.map +1 -1
- package/dist/core/object/meta-object.js +43 -2
- package/dist/core/object/meta-object.js.map +1 -1
- package/dist/core/object/object-class-registry.d.ts +22 -0
- package/dist/core/object/object-class-registry.d.ts.map +1 -0
- package/dist/core/object/object-class-registry.js +38 -0
- package/dist/core/object/object-class-registry.js.map +1 -0
- package/dist/core/object/object-constants.d.ts +7 -0
- package/dist/core/object/object-constants.d.ts.map +1 -1
- package/dist/core/object/object-constants.js +14 -0
- package/dist/core/object/object-constants.js.map +1 -1
- package/dist/core/object/object-schema.d.ts.map +1 -1
- package/dist/core/object/object-schema.js +24 -1
- package/dist/core/object/object-schema.js.map +1 -1
- package/dist/core/object/validate-discriminator.d.ts +4 -0
- package/dist/core/object/validate-discriminator.d.ts.map +1 -0
- package/dist/core/object/validate-discriminator.js +145 -0
- package/dist/core/object/validate-discriminator.js.map +1 -0
- package/dist/core/object/value-object.d.ts +23 -0
- package/dist/core/object/value-object.d.ts.map +1 -0
- package/dist/core/object/value-object.js +51 -0
- package/dist/core/object/value-object.js.map +1 -0
- package/dist/core/query/query-constants.d.ts.map +1 -1
- package/dist/core/query/query-constants.js +4 -0
- package/dist/core/query/query-constants.js.map +1 -1
- package/dist/core-types.d.ts.map +1 -1
- package/dist/core-types.js +16 -4
- package/dist/core-types.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +23 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +26 -1
- package/dist/loader/meta-data-loader.js.map +1 -1
- package/dist/loader/validation-passes.d.ts +1 -0
- package/dist/loader/validation-passes.d.ts.map +1 -1
- package/dist/loader/validation-passes.js +122 -2
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +7 -6
- package/dist/naming.js.map +1 -1
- package/dist/persistence/db/db-constants.d.ts +23 -0
- package/dist/persistence/db/db-constants.d.ts.map +1 -1
- package/dist/persistence/db/db-constants.js +31 -0
- package/dist/persistence/db/db-constants.js.map +1 -1
- package/dist/persistence/db/db-provider.js +3 -3
- package/dist/persistence/db/db-provider.js.map +1 -1
- package/dist/persistence/db/db-schema.d.ts +8 -0
- package/dist/persistence/db/db-schema.d.ts.map +1 -1
- package/dist/persistence/db/db-schema.js +17 -1
- package/dist/persistence/db/db-schema.js.map +1 -1
- package/dist/persistence/source/meta-source.d.ts +15 -1
- package/dist/persistence/source/meta-source.d.ts.map +1 -1
- package/dist/persistence/source/meta-source.js +55 -3
- package/dist/persistence/source/meta-source.js.map +1 -1
- package/dist/persistence/source/source-constants.d.ts +20 -1
- package/dist/persistence/source/source-constants.d.ts.map +1 -1
- package/dist/persistence/source/source-constants.js +36 -1
- package/dist/persistence/source/source-constants.js.map +1 -1
- package/dist/persistence/source/source-schema.d.ts.map +1 -1
- package/dist/persistence/source/source-schema.js +65 -4
- package/dist/persistence/source/source-schema.js.map +1 -1
- package/dist/persistence/source/validate-source-parameter-ref.d.ts +4 -0
- package/dist/persistence/source/validate-source-parameter-ref.d.ts.map +1 -0
- package/dist/persistence/source/validate-source-parameter-ref.js +96 -0
- package/dist/persistence/source/validate-source-parameter-ref.js.map +1 -0
- package/dist/persistence/source/validate-source-physical-names.d.ts +9 -0
- package/dist/persistence/source/validate-source-physical-names.d.ts.map +1 -0
- package/dist/persistence/source/validate-source-physical-names.js +79 -0
- package/dist/persistence/source/validate-source-physical-names.js.map +1 -0
- package/dist/serializer-json.d.ts.map +1 -1
- package/dist/serializer-json.js +43 -0
- package/dist/serializer-json.js.map +1 -1
- package/dist/template/template-constants.d.ts +9 -0
- package/dist/template/template-constants.d.ts.map +1 -1
- package/dist/template/template-constants.js +20 -0
- package/dist/template/template-constants.js.map +1 -1
- package/dist/template/template-schema.d.ts.map +1 -1
- package/dist/template/template-schema.js +42 -3
- package/dist/template/template-schema.js.map +1 -1
- package/package.json +1 -1
- package/src/attr-schema-validate.ts +70 -0
- package/src/core/field/field-constants.ts +37 -2
- package/src/core/field/field-schema.ts +43 -2
- package/src/core/field/meta-field.ts +39 -0
- package/src/core/field/validate-field-readonly.ts +142 -0
- package/src/core/object/meta-object-aware.ts +27 -0
- package/src/core/object/meta-object.ts +47 -2
- package/src/core/object/object-class-registry.ts +48 -0
- package/src/core/object/object-constants.ts +17 -0
- package/src/core/object/object-schema.ts +29 -1
- package/src/core/object/validate-discriminator.ts +195 -0
- package/src/core/object/value-object.ts +65 -0
- package/src/core/query/query-constants.ts +5 -0
- package/src/core-types.ts +17 -4
- package/src/errors.ts +23 -0
- package/src/index.ts +10 -0
- package/src/loader/meta-data-loader.ts +31 -1
- package/src/loader/validation-passes.ts +161 -0
- package/src/naming.ts +6 -5
- package/src/persistence/db/db-constants.ts +40 -0
- package/src/persistence/db/db-provider.ts +3 -3
- package/src/persistence/db/db-schema.ts +20 -0
- package/src/persistence/source/meta-source.ts +64 -2
- package/src/persistence/source/source-constants.ts +40 -1
- package/src/persistence/source/source-schema.ts +78 -3
- package/src/persistence/source/validate-source-parameter-ref.ts +143 -0
- package/src/persistence/source/validate-source-physical-names.ts +123 -0
- package/src/serializer-json.ts +50 -0
- package/src/template/template-constants.ts +23 -0
- package/src/template/template-schema.ts +49 -2
- package/dist/constants.d.ts +0 -208
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -419
- package/dist/constants.js.map +0 -1
- package/dist/core/file-meta-data-loader.d.ts +0 -18
- package/dist/core/file-meta-data-loader.d.ts.map +0 -1
- package/dist/core/file-meta-data-loader.js +0 -81
- package/dist/core/file-meta-data-loader.js.map +0 -1
- package/dist/core/file-source.d.ts +0 -12
- package/dist/core/file-source.d.ts.map +0 -1
- package/dist/core/file-source.js +0 -46
- package/dist/core/file-source.js.map +0 -1
- package/dist/core-attr-schemas.d.ts +0 -22
- package/dist/core-attr-schemas.d.ts.map +0 -1
- package/dist/core-attr-schemas.js +0 -324
- package/dist/core-attr-schemas.js.map +0 -1
- package/dist/db/db-attr-schemas.d.ts +0 -8
- package/dist/db/db-attr-schemas.d.ts.map +0 -1
- package/dist/db/db-attr-schemas.js +0 -26
- package/dist/db/db-attr-schemas.js.map +0 -1
- package/dist/db/db-provider.d.ts +0 -3
- package/dist/db/db-provider.d.ts.map +0 -1
- package/dist/db/db-provider.js +0 -28
- package/dist/db/db-provider.js.map +0 -1
- package/dist/meta/find-reference.d.ts +0 -22
- package/dist/meta/find-reference.d.ts.map +0 -1
- package/dist/meta/find-reference.js +0 -29
- package/dist/meta/find-reference.js.map +0 -1
- package/dist/meta/meta-attr.d.ts +0 -8
- package/dist/meta/meta-attr.d.ts.map +0 -1
- package/dist/meta/meta-attr.js +0 -17
- package/dist/meta/meta-attr.js.map +0 -1
- package/dist/meta/meta-data.d.ts +0 -107
- package/dist/meta/meta-data.d.ts.map +0 -1
- package/dist/meta/meta-data.js +0 -302
- package/dist/meta/meta-data.js.map +0 -1
- package/dist/meta/meta-field.d.ts +0 -48
- package/dist/meta/meta-field.d.ts.map +0 -1
- package/dist/meta/meta-field.js +0 -94
- package/dist/meta/meta-field.js.map +0 -1
- package/dist/meta/meta-identity.d.ts +0 -71
- package/dist/meta/meta-identity.d.ts.map +0 -1
- package/dist/meta/meta-identity.js +0 -129
- package/dist/meta/meta-identity.js.map +0 -1
- package/dist/meta/meta-layout.d.ts +0 -23
- package/dist/meta/meta-layout.d.ts.map +0 -1
- package/dist/meta/meta-layout.js +0 -45
- package/dist/meta/meta-layout.js.map +0 -1
- package/dist/meta/meta-object.d.ts +0 -40
- package/dist/meta/meta-object.d.ts.map +0 -1
- package/dist/meta/meta-object.js +0 -81
- package/dist/meta/meta-object.js.map +0 -1
- package/dist/meta/meta-origin.d.ts +0 -32
- package/dist/meta/meta-origin.d.ts.map +0 -1
- package/dist/meta/meta-origin.js +0 -55
- package/dist/meta/meta-origin.js.map +0 -1
- package/dist/meta/meta-relationship.d.ts +0 -11
- package/dist/meta/meta-relationship.d.ts.map +0 -1
- package/dist/meta/meta-relationship.js +0 -27
- package/dist/meta/meta-relationship.js.map +0 -1
- package/dist/meta/meta-root.d.ts +0 -12
- package/dist/meta/meta-root.d.ts.map +0 -1
- package/dist/meta/meta-root.js +0 -24
- package/dist/meta/meta-root.js.map +0 -1
- package/dist/meta/meta-source.d.ts +0 -18
- package/dist/meta/meta-source.d.ts.map +0 -1
- package/dist/meta/meta-source.js +0 -31
- package/dist/meta/meta-source.js.map +0 -1
- package/dist/meta/meta-validator.d.ts +0 -29
- package/dist/meta/meta-validator.d.ts.map +0 -1
- package/dist/meta/meta-validator.js +0 -49
- package/dist/meta/meta-validator.js.map +0 -1
- package/dist/meta/meta-view.d.ts +0 -4
- package/dist/meta/meta-view.d.ts.map +0 -1
- package/dist/meta/meta-view.js +0 -8
- package/dist/meta/meta-view.js.map +0 -1
- package/dist/persistence/db/db-attr-schemas.d.ts +0 -8
- package/dist/persistence/db/db-attr-schemas.d.ts.map +0 -1
- package/dist/persistence/db/db-attr-schemas.js +0 -28
- package/dist/persistence/db/db-attr-schemas.js.map +0 -1
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
// DB concern constants — physical DB column attr keys.
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
FIELD_SUBTYPE_STRING,
|
|
5
|
+
FIELD_SUBTYPE_TIMESTAMP,
|
|
6
|
+
} from "../../core/field/field-constants.js";
|
|
7
|
+
|
|
3
8
|
/** Column name override on a field (maps to @column in metadata). */
|
|
4
9
|
export const FIELD_ATTR_COLUMN = "column";
|
|
5
10
|
/** When true, suppress the @filterable-without-index Loader warning (Project D drift check). */
|
|
6
11
|
export const FIELD_ATTR_DB_INDEXED = "db.indexed";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* R6 Plan 2b: `@dbColumnType` — physical DB column-type override on a field.
|
|
15
|
+
* Selects the DB column type WITHOUT changing the logical field type or its
|
|
16
|
+
* native binding (ADR-0013 — the canonical physical escape hatch). Registered
|
|
17
|
+
* by the dbProvider, validated as a (logical subtype × value) pairing.
|
|
18
|
+
*/
|
|
19
|
+
export const FIELD_ATTR_DB_COLUMN_TYPE = "dbColumnType";
|
|
20
|
+
|
|
21
|
+
/** `@dbColumnType: uuid` — native Postgres `uuid` column (legal on field.string). */
|
|
22
|
+
export const DB_COLUMN_TYPE_UUID = "uuid";
|
|
23
|
+
/** `@dbColumnType: jsonb` — genuinely-open `jsonb` column (legal on field.string). */
|
|
24
|
+
export const DB_COLUMN_TYPE_JSONB = "jsonb";
|
|
25
|
+
/** `@dbColumnType: timestamp_with_tz` — `timestamp with time zone` (legal on field.timestamp). */
|
|
26
|
+
export const DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ = "timestamp_with_tz";
|
|
27
|
+
|
|
28
|
+
/** The closed set of legal `@dbColumnType` values (raw-dialect passthrough deferred). */
|
|
29
|
+
export const DB_COLUMN_TYPE_VALUES = [
|
|
30
|
+
DB_COLUMN_TYPE_UUID,
|
|
31
|
+
DB_COLUMN_TYPE_JSONB,
|
|
32
|
+
DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ,
|
|
33
|
+
] as const;
|
|
34
|
+
export type DbColumnTypeValue = (typeof DB_COLUMN_TYPE_VALUES)[number];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Legal `@dbColumnType` value → the field subtypes it may be applied to (ADR-0013,
|
|
38
|
+
* R6 Plan 2b). Any other (subtype × value) pairing — or an unrecognized value — is
|
|
39
|
+
* an ERR_BAD_ATTR_VALUE. Keyed by value; the value-set is enforced by membership in
|
|
40
|
+
* this map. Subtype names are the bare FIELD_SUBTYPE_* constants (string / timestamp).
|
|
41
|
+
*/
|
|
42
|
+
export const DB_COLUMN_TYPE_LEGAL_SUBTYPES: Readonly<Record<DbColumnTypeValue, readonly string[]>> = {
|
|
43
|
+
[DB_COLUMN_TYPE_UUID]: [FIELD_SUBTYPE_STRING],
|
|
44
|
+
[DB_COLUMN_TYPE_JSONB]: [FIELD_SUBTYPE_STRING],
|
|
45
|
+
[DB_COLUMN_TYPE_TIMESTAMP_WITH_TZ]: [FIELD_SUBTYPE_TIMESTAMP],
|
|
46
|
+
} as const;
|
|
@@ -8,18 +8,18 @@ import type { TypeRegistry } from "../../registry.js";
|
|
|
8
8
|
import { TYPE_FIELD, TYPE_SOURCE } from "../../shared/base-types.js";
|
|
9
9
|
import { FIELD_SUBTYPES } from "../../core/field/field-constants.js";
|
|
10
10
|
import { SOURCE_SUBTYPE_RDB } from "../source/source-constants.js";
|
|
11
|
-
import { columnSchema, dbIndexedSchema } from "./db-schema.js";
|
|
11
|
+
import { columnSchema, dbIndexedSchema, dbColumnTypeSchema } from "./db-schema.js";
|
|
12
12
|
import { sourceRdbAttrs } from "../source/source-schema.js";
|
|
13
13
|
|
|
14
14
|
export const dbProvider: MetaDataTypeProvider = {
|
|
15
15
|
id: "metaobjects-db",
|
|
16
16
|
dependencies: ["metaobjects-core-types"],
|
|
17
17
|
description:
|
|
18
|
-
"DB-domain attributes — @column / @db.indexed on fields, @table/@kind/@role/@schema on source.rdb.",
|
|
18
|
+
"DB-domain attributes — @column / @db.indexed / @dbColumnType on fields, @table/@kind/@role/@schema on source.rdb.",
|
|
19
19
|
registerTypes(registry: TypeRegistry): void {
|
|
20
20
|
for (const subType of FIELD_SUBTYPES) {
|
|
21
21
|
registry.extend(TYPE_FIELD, subType, {
|
|
22
|
-
attributes: [columnSchema, dbIndexedSchema],
|
|
22
|
+
attributes: [columnSchema, dbIndexedSchema, dbColumnTypeSchema],
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
// source.rdb — @table/@kind/@role/@schema attrs.
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
FIELD_ATTR_COLUMN,
|
|
11
11
|
FIELD_ATTR_DB_INDEXED,
|
|
12
|
+
FIELD_ATTR_DB_COLUMN_TYPE,
|
|
13
|
+
DB_COLUMN_TYPE_VALUES,
|
|
12
14
|
} from "./db-constants.js";
|
|
13
15
|
|
|
14
16
|
/** `@column` — column-name override on every field subtype (source.rdb). */
|
|
@@ -28,3 +30,21 @@ export const dbIndexedSchema: AttrSchema = {
|
|
|
28
30
|
description:
|
|
29
31
|
"When true, suppress the @filterable-without-index Loader warning (the field is indexed by other means).",
|
|
30
32
|
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* R6 Plan 2b: `@dbColumnType` — physical DB column-type override on every field
|
|
36
|
+
* subtype. The legal value depends on the field's logical subtype, so the
|
|
37
|
+
* subtype × value pairing is validated own-only by the loader
|
|
38
|
+
* (validateAttrSchema → ERR_BAD_ATTR_VALUE), not by a flat `allowedValues` set.
|
|
39
|
+
* The string valueType keeps the schema's type check intact.
|
|
40
|
+
*/
|
|
41
|
+
export const dbColumnTypeSchema: AttrSchema = {
|
|
42
|
+
name: FIELD_ATTR_DB_COLUMN_TYPE,
|
|
43
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
44
|
+
required: false,
|
|
45
|
+
description:
|
|
46
|
+
"Physical DB column-type override (ADR-0013 escape hatch). Legal values are " +
|
|
47
|
+
`${DB_COLUMN_TYPE_VALUES.join(" | ")}, each legal only on a specific logical ` +
|
|
48
|
+
"field subtype (uuid/jsonb on field.string, timestamp_with_tz on field.timestamp). " +
|
|
49
|
+
"The logical field type and its native binding are unchanged.",
|
|
50
|
+
};
|
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
// MetaSource — concrete node class for type=source nodes.
|
|
2
2
|
// Declares where an object's data lives (Project E).
|
|
3
|
-
// source.rdb uses
|
|
3
|
+
// source.rdb uses kind-aware physical-name aliases + @kind/@role/@schema;
|
|
4
|
+
// read-only-ness is derived from @kind.
|
|
4
5
|
//
|
|
5
6
|
// Extends MetaData directly: no model wrapper, no metaOf() indirection.
|
|
6
7
|
|
|
7
8
|
import { MetaData } from "../../shared/meta-data.js";
|
|
9
|
+
import { pluralize, toSnakeCase } from "../../naming.js";
|
|
8
10
|
import {
|
|
9
11
|
SOURCE_ATTR_TABLE,
|
|
12
|
+
SOURCE_ATTR_VIEW,
|
|
13
|
+
SOURCE_ATTR_MATERIALIZED_VIEW,
|
|
14
|
+
SOURCE_ATTR_PROC,
|
|
15
|
+
SOURCE_ATTR_FUNCTION,
|
|
10
16
|
SOURCE_ATTR_KIND,
|
|
11
17
|
SOURCE_ATTR_ROLE,
|
|
12
18
|
SOURCE_READ_ONLY_KINDS,
|
|
13
19
|
DEFAULT_SOURCE_KIND,
|
|
14
20
|
DEFAULT_SOURCE_ROLE,
|
|
21
|
+
PHYSICAL_NAME_ATTR_BY_KIND,
|
|
15
22
|
} from "./source-constants.js";
|
|
16
23
|
|
|
24
|
+
/** Every kind-aware physical-name alias key, ordered for deterministic
|
|
25
|
+
* iteration (matches the SOURCE_RDB_KINDS order). */
|
|
26
|
+
const ALL_PHYSICAL_NAME_ALIASES = [
|
|
27
|
+
SOURCE_ATTR_TABLE,
|
|
28
|
+
SOURCE_ATTR_VIEW,
|
|
29
|
+
SOURCE_ATTR_MATERIALIZED_VIEW,
|
|
30
|
+
SOURCE_ATTR_PROC,
|
|
31
|
+
SOURCE_ATTR_FUNCTION,
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
17
34
|
export class MetaSource extends MetaData {
|
|
18
|
-
/** Physical SQL table/view name from @table
|
|
35
|
+
/** Physical SQL table/view name from the legacy @table attr. Kept as a
|
|
36
|
+
* back-compat accessor that reads ONLY the @table slot — callers should
|
|
37
|
+
* use physicalName for the FR-016 four-step rule. */
|
|
19
38
|
get tableName(): string | undefined {
|
|
20
39
|
const v = this.ownAttr(SOURCE_ATTR_TABLE);
|
|
21
40
|
return typeof v === "string" && v !== "" ? v : undefined;
|
|
@@ -48,4 +67,47 @@ export class MetaSource extends MetaData {
|
|
|
48
67
|
isWritable(): boolean {
|
|
49
68
|
return !this.isReadOnly();
|
|
50
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolved physical SQL name for this source, following the FR-016 / ADR-0018
|
|
73
|
+
* four-step rule:
|
|
74
|
+
* 1. Kind-matching alias (e.g. @proc when @kind: "storedProc").
|
|
75
|
+
* 2. Legacy @table for non-table kind (pre-1.0 fallback).
|
|
76
|
+
* 3. Source's bare structural `name` via snake_case.
|
|
77
|
+
* 4. Owning entity's name via pluralize(snake_case).
|
|
78
|
+
*
|
|
79
|
+
* Callers needing the legacy raw @table slot only should use `tableName`;
|
|
80
|
+
* codegen / migrate / runtime should use `physicalName`.
|
|
81
|
+
*/
|
|
82
|
+
get physicalName(): string {
|
|
83
|
+
const kind = this.effectiveKind;
|
|
84
|
+
|
|
85
|
+
// Step 1: kind-matching alias.
|
|
86
|
+
const canonicalAttr = PHYSICAL_NAME_ATTR_BY_KIND.get(kind);
|
|
87
|
+
if (canonicalAttr !== undefined) {
|
|
88
|
+
const v = this.ownAttr(canonicalAttr);
|
|
89
|
+
if (typeof v === "string" && v !== "") return v;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Step 2: legacy @table for non-table kind.
|
|
93
|
+
if (canonicalAttr !== SOURCE_ATTR_TABLE) {
|
|
94
|
+
const legacy = this.ownAttr(SOURCE_ATTR_TABLE);
|
|
95
|
+
if (typeof legacy === "string" && legacy !== "") return legacy;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Step 3: source's structural `name` via snake_case (no pluralization —
|
|
99
|
+
// the source's name IS the logical name).
|
|
100
|
+
if (this.name !== "") {
|
|
101
|
+
return toSnakeCase(this.name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Step 4: owning entity's name via pluralize(snake_case). The MetaData
|
|
105
|
+
// parent of a source is always the entity that declared it.
|
|
106
|
+
const owner = this.parent;
|
|
107
|
+
if (owner !== undefined && owner.name !== "") {
|
|
108
|
+
return pluralize(toSnakeCase(owner.name));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
51
113
|
}
|
|
@@ -30,13 +30,40 @@ export const DEFAULT_DB_SCHEMA_POSTGRES = "public";
|
|
|
30
30
|
// Source v2 (ADR-0007) — rdb paradigm: kind, role, physical table name.
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// FR-016 / ADR-0007 point 2: the source's "logical name" is the structural
|
|
34
|
+
// `name` field every MetaData node already carries — accessed via `source.name`,
|
|
35
|
+
// NOT an @-attr. (An @-attr would conflict with the ERR_RESERVED_ATTR rule for
|
|
36
|
+
// the reserved structural key `name`.) See the four-step physical-name
|
|
37
|
+
// resolution rule on MetaSource.physicalName.
|
|
38
|
+
|
|
39
|
+
// --- Per-kind physical-name aliases (FR-016 / ADR-0018) -----------------------
|
|
40
|
+
// One canonical attr key per rdb @kind. The single internal physical-name
|
|
41
|
+
// "slot" on the source is filled by whichever alias matches @kind; the
|
|
42
|
+
// canonical serializer emits the kind-matching alias regardless of which
|
|
43
|
+
// spelling was on input.
|
|
44
|
+
/** Physical SQL table name. Canonical attr for @kind: "table" (default). */
|
|
34
45
|
export const SOURCE_ATTR_TABLE = "table";
|
|
46
|
+
/** Physical SQL view name. Canonical attr for @kind: "view". */
|
|
47
|
+
export const SOURCE_ATTR_VIEW = "view";
|
|
48
|
+
/** Physical SQL materialized-view name. Canonical attr for @kind: "materializedView". */
|
|
49
|
+
export const SOURCE_ATTR_MATERIALIZED_VIEW = "materializedView";
|
|
50
|
+
/** Physical SQL stored-procedure name. Canonical attr for @kind: "storedProc". */
|
|
51
|
+
export const SOURCE_ATTR_PROC = "proc";
|
|
52
|
+
/** Physical SQL table-function name. Canonical attr for @kind: "tableFunction". */
|
|
53
|
+
export const SOURCE_ATTR_FUNCTION = "function";
|
|
54
|
+
|
|
35
55
|
/** Object kind within the rdb paradigm; read-only-ness is derived from it. */
|
|
36
56
|
export const SOURCE_ATTR_KIND = "kind";
|
|
37
57
|
/** Multi-source role; exactly one primary per object. */
|
|
38
58
|
export const SOURCE_ATTR_ROLE = "role";
|
|
39
59
|
|
|
60
|
+
/** FR-015: reference to an object.value describing the input shape of a callable
|
|
61
|
+
* source. Required for @kind: "storedProc" | "tableFunction" that take args;
|
|
62
|
+
* ignored for non-callable kinds. Wire-format symmetric with template.@payloadRef
|
|
63
|
+
* (FR-004) — the typed-input pattern reuses object.value rather than minting a
|
|
64
|
+
* new parameter.* node type. */
|
|
65
|
+
export const SOURCE_ATTR_PARAMETER_REF = "parameterRef";
|
|
66
|
+
|
|
40
67
|
export const SOURCE_KIND_TABLE = "table";
|
|
41
68
|
export const SOURCE_KIND_VIEW = "view";
|
|
42
69
|
export const SOURCE_KIND_MATERIALIZED_VIEW = "materializedView";
|
|
@@ -52,6 +79,18 @@ export const SOURCE_RDB_KINDS = [
|
|
|
52
79
|
] as const;
|
|
53
80
|
export type SourceRdbKind = (typeof SOURCE_RDB_KINDS)[number];
|
|
54
81
|
|
|
82
|
+
/** Map @kind → canonical kind-aware physical-name attr key (FR-016 / ADR-0018).
|
|
83
|
+
* Drives the four-step physical-name resolution rule and the canonical-serializer
|
|
84
|
+
* per-kind rewrite. The single internal physical-name "slot" on a source is the
|
|
85
|
+
* value of whichever alias matches the source's @kind. */
|
|
86
|
+
export const PHYSICAL_NAME_ATTR_BY_KIND: ReadonlyMap<string, string> = new Map([
|
|
87
|
+
[SOURCE_KIND_TABLE, SOURCE_ATTR_TABLE],
|
|
88
|
+
[SOURCE_KIND_VIEW, SOURCE_ATTR_VIEW],
|
|
89
|
+
[SOURCE_KIND_MATERIALIZED_VIEW, SOURCE_ATTR_MATERIALIZED_VIEW],
|
|
90
|
+
[SOURCE_KIND_STORED_PROC, SOURCE_ATTR_PROC],
|
|
91
|
+
[SOURCE_KIND_TABLE_FUNCTION, SOURCE_ATTR_FUNCTION],
|
|
92
|
+
]);
|
|
93
|
+
|
|
55
94
|
/** rdb @kind default when omitted (writable table). */
|
|
56
95
|
export const DEFAULT_SOURCE_KIND = SOURCE_KIND_TABLE;
|
|
57
96
|
|
|
@@ -5,20 +5,67 @@ import type { AttrSchema } from "../../registry.js";
|
|
|
5
5
|
import { ATTR_SUBTYPE_STRING } from "../../core/attr/attr-constants.js";
|
|
6
6
|
import {
|
|
7
7
|
SOURCE_ATTR_TABLE,
|
|
8
|
+
SOURCE_ATTR_VIEW,
|
|
9
|
+
SOURCE_ATTR_MATERIALIZED_VIEW,
|
|
10
|
+
SOURCE_ATTR_PROC,
|
|
11
|
+
SOURCE_ATTR_FUNCTION,
|
|
8
12
|
SOURCE_ATTR_KIND,
|
|
9
13
|
SOURCE_ATTR_ROLE,
|
|
10
14
|
SOURCE_ATTR_SCHEMA,
|
|
15
|
+
SOURCE_ATTR_PARAMETER_REF,
|
|
11
16
|
SOURCE_RDB_KINDS,
|
|
12
17
|
SOURCE_ROLES,
|
|
13
18
|
} from "./source-constants.js";
|
|
14
19
|
|
|
15
|
-
/** `@table` — physical SQL table
|
|
20
|
+
/** `@table` — physical SQL table name for @kind: "table" (default). FR-016: one
|
|
21
|
+
* of five kind-aware physical-name aliases; all write to the same internal slot.
|
|
22
|
+
* Pre-1.0 legacy: also accepted with non-table @kind (canonical-serializer
|
|
23
|
+
* rewrites; loader emits WARN_LEGACY_PHYSICAL_NAME_ALIAS). */
|
|
16
24
|
const tableSchema: AttrSchema = {
|
|
17
25
|
name: SOURCE_ATTR_TABLE,
|
|
18
26
|
valueType: ATTR_SUBTYPE_STRING,
|
|
19
27
|
required: false,
|
|
20
28
|
description:
|
|
21
|
-
"Physical SQL table
|
|
29
|
+
"Physical SQL table name for source.rdb @kind: \"table\" (default). FR-016: " +
|
|
30
|
+
"Defaults from the source's bare structural `name` via the project's columnNamingStrategy " +
|
|
31
|
+
"when omitted, then from the owning entity's name. Pre-1.0 legacy spelling for " +
|
|
32
|
+
"view/materializedView/storedProc/tableFunction kinds during the transition; " +
|
|
33
|
+
"canonical-serializer rewrites to the kind-matching alias.",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** `@view` — physical SQL view name for @kind: "view" (FR-016 / ADR-0018). */
|
|
37
|
+
const viewSchema: AttrSchema = {
|
|
38
|
+
name: SOURCE_ATTR_VIEW,
|
|
39
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
40
|
+
required: false,
|
|
41
|
+
description: "Physical SQL view name for source.rdb @kind: \"view\". Same internal slot as @table.",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** `@materializedView` — physical SQL materialized-view name for @kind: "materializedView". */
|
|
45
|
+
const materializedViewSchema: AttrSchema = {
|
|
46
|
+
name: SOURCE_ATTR_MATERIALIZED_VIEW,
|
|
47
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
48
|
+
required: false,
|
|
49
|
+
description:
|
|
50
|
+
"Physical SQL materialized-view name for source.rdb @kind: \"materializedView\". Same internal slot as @table.",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** `@proc` — physical SQL stored-procedure name for @kind: "storedProc". */
|
|
54
|
+
const procSchema: AttrSchema = {
|
|
55
|
+
name: SOURCE_ATTR_PROC,
|
|
56
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
57
|
+
required: false,
|
|
58
|
+
description:
|
|
59
|
+
"Physical SQL stored-procedure name for source.rdb @kind: \"storedProc\". Same internal slot as @table.",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** `@function` — physical SQL table-function name for @kind: "tableFunction". */
|
|
63
|
+
const functionSchema: AttrSchema = {
|
|
64
|
+
name: SOURCE_ATTR_FUNCTION,
|
|
65
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
66
|
+
required: false,
|
|
67
|
+
description:
|
|
68
|
+
"Physical SQL table-function name for source.rdb @kind: \"tableFunction\". Same internal slot as @table.",
|
|
22
69
|
};
|
|
23
70
|
|
|
24
71
|
/** `@kind` — object kind within the rdb paradigm; drives read-only derivation. */
|
|
@@ -50,5 +97,33 @@ const schemaSchema: AttrSchema = {
|
|
|
50
97
|
"Optional database schema name (e.g. 'catalog', 'public'). Postgres defaults to 'public'; SQLite rejects any non-default value.",
|
|
51
98
|
};
|
|
52
99
|
|
|
100
|
+
/** `@parameterRef` — name or FQN of an object.value describing the input shape
|
|
101
|
+
* of a callable source (FR-015). Required when @kind is "storedProc" or
|
|
102
|
+
* "tableFunction" and the proc takes args; ignored for non-callable kinds.
|
|
103
|
+
* Mirrors template.@payloadRef from FR-004. */
|
|
104
|
+
const parameterRefSchema: AttrSchema = {
|
|
105
|
+
name: SOURCE_ATTR_PARAMETER_REF,
|
|
106
|
+
valueType: ATTR_SUBTYPE_STRING,
|
|
107
|
+
required: false,
|
|
108
|
+
description:
|
|
109
|
+
"FR-015: name or FQN of an object.value describing the input shape of " +
|
|
110
|
+
"this source's callable interface. Permitted on @kind: \"storedProc\" / " +
|
|
111
|
+
"\"tableFunction\"; rejected on non-callable kinds (table / view / " +
|
|
112
|
+
"materializedView). Field children of the referenced object.value become " +
|
|
113
|
+
"the call-site parameter list in declaration order. Symmetric with " +
|
|
114
|
+
"template.@payloadRef in FR-004 — the typed-input pattern reuses " +
|
|
115
|
+
"object.value rather than minting a new parameter.* node type.",
|
|
116
|
+
};
|
|
117
|
+
|
|
53
118
|
/** All attr schemas for source.rdb, to be registered via registry.extend. */
|
|
54
|
-
export const sourceRdbAttrs: AttrSchema[] = [
|
|
119
|
+
export const sourceRdbAttrs: AttrSchema[] = [
|
|
120
|
+
tableSchema,
|
|
121
|
+
viewSchema,
|
|
122
|
+
materializedViewSchema,
|
|
123
|
+
procSchema,
|
|
124
|
+
functionSchema,
|
|
125
|
+
kindSchema,
|
|
126
|
+
roleSchema,
|
|
127
|
+
schemaSchema,
|
|
128
|
+
parameterRefSchema,
|
|
129
|
+
];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Validation pass: source.rdb @parameterRef typed-input rules (FR-015).
|
|
2
|
+
//
|
|
3
|
+
// Codes:
|
|
4
|
+
// ERR_PARAMETER_REF_UNRESOLVED — @parameterRef names a non-existent object.
|
|
5
|
+
// ERR_PARAMETER_REF_NOT_VALUE_OBJECT — @parameterRef points at an object.entity
|
|
6
|
+
// instead of object.value.
|
|
7
|
+
// ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND
|
|
8
|
+
// — @parameterRef set with @kind: "table" /
|
|
9
|
+
// "view" / "materializedView". Only the
|
|
10
|
+
// callable kinds (storedProc, tableFunction)
|
|
11
|
+
// accept parameters.
|
|
12
|
+
// ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH
|
|
13
|
+
// — a parameter field uses origin.passthrough
|
|
14
|
+
// @from: "Entity.field" but the parameter's
|
|
15
|
+
// subtype does not match the referenced
|
|
16
|
+
// field's subtype.
|
|
17
|
+
|
|
18
|
+
import type { MetaData } from "../../shared/meta-data.js";
|
|
19
|
+
import { ParseError } from "../../errors.js";
|
|
20
|
+
import {
|
|
21
|
+
TYPE_OBJECT,
|
|
22
|
+
TYPE_SOURCE,
|
|
23
|
+
TYPE_FIELD,
|
|
24
|
+
TYPE_ORIGIN,
|
|
25
|
+
} from "../../shared/base-types.js";
|
|
26
|
+
import {
|
|
27
|
+
OBJECT_SUBTYPE_VALUE,
|
|
28
|
+
OBJECT_SUBTYPE_ENTITY,
|
|
29
|
+
} from "../../core/object/object-constants.js";
|
|
30
|
+
import { PACKAGE_SEPARATOR } from "../../shared/structural.js";
|
|
31
|
+
import { MetaSource } from "./meta-source.js";
|
|
32
|
+
import {
|
|
33
|
+
SOURCE_ATTR_PARAMETER_REF,
|
|
34
|
+
SOURCE_SUBTYPE_RDB,
|
|
35
|
+
SOURCE_KIND_STORED_PROC,
|
|
36
|
+
SOURCE_KIND_TABLE_FUNCTION,
|
|
37
|
+
} from "./source-constants.js";
|
|
38
|
+
import {
|
|
39
|
+
ORIGIN_SUBTYPE_PASSTHROUGH,
|
|
40
|
+
ORIGIN_PASSTHROUGH_ATTR_FROM,
|
|
41
|
+
} from "../../persistence/origin/origin-constants.js";
|
|
42
|
+
|
|
43
|
+
const CALLABLE_KINDS = new Set<string>([
|
|
44
|
+
SOURCE_KIND_STORED_PROC,
|
|
45
|
+
SOURCE_KIND_TABLE_FUNCTION,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export function validateSourceParameterRef(root: MetaData): ParseError[] {
|
|
49
|
+
const errors: ParseError[] = [];
|
|
50
|
+
|
|
51
|
+
// Pre-index every object by name AND fqn so resolution costs O(1) per source.
|
|
52
|
+
const objectIndex = new Map<string, MetaData>();
|
|
53
|
+
for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
|
|
54
|
+
objectIndex.set(obj.name, obj);
|
|
55
|
+
const fqn = obj.package !== undefined && obj.package !== ""
|
|
56
|
+
? `${obj.package}${PACKAGE_SEPARATOR}${obj.name}`
|
|
57
|
+
: obj.name;
|
|
58
|
+
objectIndex.set(fqn, obj);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
|
|
62
|
+
for (const source of obj.ownChildren().filter(
|
|
63
|
+
(c): c is MetaSource =>
|
|
64
|
+
c.type === TYPE_SOURCE && c.subType === SOURCE_SUBTYPE_RDB && c instanceof MetaSource,
|
|
65
|
+
)) {
|
|
66
|
+
const ref = source.ownAttr(SOURCE_ATTR_PARAMETER_REF);
|
|
67
|
+
if (typeof ref !== "string" || ref === "") continue;
|
|
68
|
+
|
|
69
|
+
// ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND — checked even before resolution
|
|
70
|
+
// so authoring mistakes on the wrong kind surface immediately.
|
|
71
|
+
if (!CALLABLE_KINDS.has(source.effectiveKind)) {
|
|
72
|
+
errors.push(
|
|
73
|
+
new ParseError(
|
|
74
|
+
`source.rdb on object "${obj.name}" has @parameterRef but @kind is "${source.effectiveKind}"; ` +
|
|
75
|
+
`only "storedProc" or "tableFunction" accept parameters`,
|
|
76
|
+
{ code: "ERR_PARAMETER_REF_ON_NON_CALLABLE_KIND", source: source.source },
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const target = objectIndex.get(ref);
|
|
83
|
+
if (target === undefined) {
|
|
84
|
+
errors.push(
|
|
85
|
+
new ParseError(
|
|
86
|
+
`source.rdb on object "${obj.name}" @parameterRef = "${ref}" does not resolve to any known object`,
|
|
87
|
+
{ code: "ERR_PARAMETER_REF_UNRESOLVED", source: source.source },
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (target.subType !== OBJECT_SUBTYPE_VALUE) {
|
|
94
|
+
const reason = target.subType === OBJECT_SUBTYPE_ENTITY
|
|
95
|
+
? "an object.entity (entities have identity; parameter shapes are value-objects)"
|
|
96
|
+
: `an object.${target.subType}`;
|
|
97
|
+
errors.push(
|
|
98
|
+
new ParseError(
|
|
99
|
+
`source.rdb on object "${obj.name}" @parameterRef = "${ref}" resolves to ${reason}; ` +
|
|
100
|
+
`use an object.value`,
|
|
101
|
+
{ code: "ERR_PARAMETER_REF_NOT_VALUE_OBJECT", source: source.source },
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH — every parameter field
|
|
108
|
+
// with origin.passthrough must have a subtype matching the referenced
|
|
109
|
+
// field. The origin path validation pass checks the from-path resolves;
|
|
110
|
+
// here we just check the subtype alignment.
|
|
111
|
+
for (const paramField of target.ownChildren().filter((c) => c.type === TYPE_FIELD)) {
|
|
112
|
+
const passthrough = paramField.ownChildren().find(
|
|
113
|
+
(c) => c.type === TYPE_ORIGIN && c.subType === ORIGIN_SUBTYPE_PASSTHROUGH,
|
|
114
|
+
);
|
|
115
|
+
if (passthrough === undefined) continue;
|
|
116
|
+
const from = passthrough.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
|
|
117
|
+
if (typeof from !== "string" || from === "") continue;
|
|
118
|
+
const dot = from.indexOf(".");
|
|
119
|
+
if (dot < 0) continue;
|
|
120
|
+
const targetEntityName = from.slice(0, dot);
|
|
121
|
+
const targetFieldName = from.slice(dot + 1);
|
|
122
|
+
const targetEntity = objectIndex.get(targetEntityName);
|
|
123
|
+
if (targetEntity === undefined) continue; // origin-paths pass surfaces this
|
|
124
|
+
const targetField = targetEntity.ownChildren().find(
|
|
125
|
+
(c) => c.type === TYPE_FIELD && c.name === targetFieldName,
|
|
126
|
+
);
|
|
127
|
+
if (targetField === undefined) continue;
|
|
128
|
+
if (paramField.subType !== targetField.subType) {
|
|
129
|
+
errors.push(
|
|
130
|
+
new ParseError(
|
|
131
|
+
`parameter field "${paramField.name}" (field.${paramField.subType}) on @parameterRef ` +
|
|
132
|
+
`"${ref}" uses origin.passthrough @from: "${from}", but ` +
|
|
133
|
+
`${targetEntity.name}.${targetFieldName} is field.${targetField.subType}; types must match`,
|
|
134
|
+
{ code: "ERR_PARAMETER_REF_PASSTHROUGH_TYPE_MISMATCH", source: paramField.source },
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return errors;
|
|
143
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Validation pass: per-kind physical-name aliases on source.rdb (FR-016 / ADR-0018).
|
|
2
|
+
//
|
|
3
|
+
// Each source.rdb may declare at most one of @table / @view / @materializedView /
|
|
4
|
+
// @proc / @function. The chosen alias must match the source's @kind, with one
|
|
5
|
+
// pre-1.0 legacy exception: @table is also accepted for non-table kinds (e.g.
|
|
6
|
+
// @kind: "storedProc" + @table: "fn_x"), which emits a WARN_LEGACY_PHYSICAL_NAME_ALIAS.
|
|
7
|
+
//
|
|
8
|
+
// Codes:
|
|
9
|
+
// ERR_PHYSICAL_NAME_MULTIPLE — two or more kind-aware aliases on one source.
|
|
10
|
+
// ERR_PHYSICAL_NAME_KIND_MISMATCH — alias other than @table set with a non-matching @kind.
|
|
11
|
+
// WARN_LEGACY_PHYSICAL_NAME_ALIAS — @table set with a non-table @kind (legacy spelling).
|
|
12
|
+
|
|
13
|
+
import type { MetaData } from "../../shared/meta-data.js";
|
|
14
|
+
import { ParseError } from "../../errors.js";
|
|
15
|
+
import type { LoaderWarning } from "../../source.js";
|
|
16
|
+
import { TYPE_OBJECT, TYPE_SOURCE } from "../../shared/base-types.js";
|
|
17
|
+
import { MetaSource } from "./meta-source.js";
|
|
18
|
+
import {
|
|
19
|
+
SOURCE_ATTR_TABLE,
|
|
20
|
+
SOURCE_ATTR_VIEW,
|
|
21
|
+
SOURCE_ATTR_MATERIALIZED_VIEW,
|
|
22
|
+
SOURCE_ATTR_PROC,
|
|
23
|
+
SOURCE_ATTR_FUNCTION,
|
|
24
|
+
SOURCE_SUBTYPE_RDB,
|
|
25
|
+
PHYSICAL_NAME_ATTR_BY_KIND,
|
|
26
|
+
} from "./source-constants.js";
|
|
27
|
+
|
|
28
|
+
const ALL_PHYSICAL_NAME_ALIASES = [
|
|
29
|
+
SOURCE_ATTR_TABLE,
|
|
30
|
+
SOURCE_ATTR_VIEW,
|
|
31
|
+
SOURCE_ATTR_MATERIALIZED_VIEW,
|
|
32
|
+
SOURCE_ATTR_PROC,
|
|
33
|
+
SOURCE_ATTR_FUNCTION,
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
export interface PhysicalNameValidationResult {
|
|
37
|
+
errors: ParseError[];
|
|
38
|
+
warnings: LoaderWarning[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function validateSourcePhysicalNames(root: MetaData): PhysicalNameValidationResult {
|
|
42
|
+
const errors: ParseError[] = [];
|
|
43
|
+
const warnings: LoaderWarning[] = [];
|
|
44
|
+
|
|
45
|
+
for (const obj of root.ownChildren().filter((c) => c.type === TYPE_OBJECT)) {
|
|
46
|
+
const sources = obj
|
|
47
|
+
.ownChildren()
|
|
48
|
+
.filter(
|
|
49
|
+
(c): c is MetaSource =>
|
|
50
|
+
c.type === TYPE_SOURCE && c.subType === SOURCE_SUBTYPE_RDB && c instanceof MetaSource,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
for (const source of sources) {
|
|
54
|
+
// Empty-string check first — explicit "" is meaningless and an
|
|
55
|
+
// authoring error regardless of which alias was used.
|
|
56
|
+
for (const attr of ALL_PHYSICAL_NAME_ALIASES) {
|
|
57
|
+
const v = source.ownAttr(attr);
|
|
58
|
+
if (typeof v === "string" && v === "") {
|
|
59
|
+
errors.push(
|
|
60
|
+
new ParseError(
|
|
61
|
+
`source.rdb on object "${obj.name}" sets @${attr} to an empty string; physical name attrs require a non-empty value`,
|
|
62
|
+
{ code: "ERR_BAD_ATTR_VALUE", source: source.source },
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const setAliases = ALL_PHYSICAL_NAME_ALIASES.filter((attr) => {
|
|
69
|
+
const v = source.ownAttr(attr);
|
|
70
|
+
return typeof v === "string" && v !== "";
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (setAliases.length > 1) {
|
|
74
|
+
errors.push(
|
|
75
|
+
new ParseError(
|
|
76
|
+
`source.rdb on object "${obj.name}" declares multiple physical-name aliases (${setAliases
|
|
77
|
+
.map((a) => `@${a}`)
|
|
78
|
+
.join(", ")}); set exactly one`,
|
|
79
|
+
{ code: "ERR_PHYSICAL_NAME_MULTIPLE", source: source.source },
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (setAliases.length === 0) continue;
|
|
86
|
+
|
|
87
|
+
const chosenAlias = setAliases[0]!;
|
|
88
|
+
const expectedAlias = PHYSICAL_NAME_ATTR_BY_KIND.get(source.effectiveKind);
|
|
89
|
+
|
|
90
|
+
if (chosenAlias === expectedAlias) continue;
|
|
91
|
+
|
|
92
|
+
// Legacy: @table is permitted for non-table kinds with a warning.
|
|
93
|
+
if (chosenAlias === SOURCE_ATTR_TABLE) {
|
|
94
|
+
warnings.push({
|
|
95
|
+
code: "WARN_LEGACY_PHYSICAL_NAME_ALIAS",
|
|
96
|
+
message:
|
|
97
|
+
`source.rdb on object "${obj.name}" uses @table with @kind: "${source.effectiveKind}"; ` +
|
|
98
|
+
`prefer the kind-matching alias @${expectedAlias} (ADR-0018)`,
|
|
99
|
+
source: source.source,
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Any other mismatch is a hard error.
|
|
105
|
+
errors.push(
|
|
106
|
+
new ParseError(
|
|
107
|
+
`source.rdb on object "${obj.name}" uses @${chosenAlias} with @kind: "${source.effectiveKind}"; ` +
|
|
108
|
+
`@${chosenAlias} is only valid for @kind: "${kindForAlias(chosenAlias)}"`,
|
|
109
|
+
{ code: "ERR_PHYSICAL_NAME_KIND_MISMATCH", source: source.source },
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { errors, warnings };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function kindForAlias(alias: string): string {
|
|
119
|
+
for (const [kind, attr] of PHYSICAL_NAME_ATTR_BY_KIND.entries()) {
|
|
120
|
+
if (attr === alias) return kind;
|
|
121
|
+
}
|
|
122
|
+
return "(unknown)";
|
|
123
|
+
}
|