@peter.prib/renderengine 0.2.10 → 0.2.11

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/REFERENCE.md CHANGED
@@ -875,6 +875,12 @@ types — see `ARCHITECTURE.md`):
875
875
  a comment where one cannot be made; `COMMENT ON`s for definitions (`comments: false` leaves them out). Names SQL
876
876
  would not take bare are quoted. ANSI SQL as PostgreSQL reads it.
877
877
 
878
+ A table's name may be schema-qualified (`sales.customer`, as a database lists its tables): the DDL names it in its
879
+ schema (`sales.customer`, each part quoted as it needs, `SqlDdl.tableName()`), and a reference to it is
880
+ `sales.customer` or `sales.customer.id`. The check and the DDL read a reference the one way,
881
+ `LevelSchema.resolveReference(ref)`: the whole text as a table (by id or name, any case), else what is before its
882
+ last dot as the table and the rest as its column — `{ entity, column }`, or null.
883
+
878
884
  **A dimensional scheme.** At the logical and physical levels an entity may have a role (`EntityRole`, registered as
879
885
  relationship kinds are): a **fact** (`FactRole`, `role: 'fact'`: what is measured, at a stated `grain`, its
880
886
  attributes' `measure` additive, semi-additive or non-additive) or a **dimension** (`DimensionRole`: its `scdType`,
@@ -44885,6 +44885,29 @@
44885
44885
  return this.entities.find(e => e.id === id) ?? null;
44886
44886
  }
44887
44887
 
44888
+ /** An entity by id, else by name (any case), or null. */
44889
+ named(text) {
44890
+ const s = String(text ?? '').trim();
44891
+ return this.entity(s) ?? this.entities.find(e => String(e.name ?? '').toLowerCase() === s.toLowerCase()) ?? null;
44892
+ }
44893
+
44894
+ /**
44895
+ * What a foreign key's `references` names: `{ entity, column }` (column
44896
+ * null when it names the table alone), or null. `table` or `table.column`,
44897
+ * the table by id or name; a table's own name may hold dots (a
44898
+ * schema-qualified `sales.customer`), so the whole text is tried as a
44899
+ * table first, then what is before its last dot, the rest its column.
44900
+ */
44901
+ resolveReference(ref) {
44902
+ const text = String(ref ?? '').trim();
44903
+ if (!text) return null;
44904
+ const whole = this.named(text);
44905
+ if (whole) return { entity: whole, column: null };
44906
+ const dot = text.lastIndexOf('.');
44907
+ const entity = dot > 0 ? this.named(text.slice(0, dot)) : null;
44908
+ return entity ? { entity, column: text.slice(dot + 1).trim() || null } : null;
44909
+ }
44910
+
44888
44911
  /** Add an entity (its id unique at this level). */
44889
44912
  addEntity(entity) {
44890
44913
  if (!entity?.id) throw new TypeError('An entity needs an id');
@@ -45325,17 +45348,13 @@
45325
45348
  foreignKeys() {
45326
45349
  return ATTRIBUTED.flatMap(level => {
45327
45350
  const schema = this.model.level(level);
45328
- const find = (ref) => {
45329
- const [name] = String(ref).split('.');
45330
- return schema.entity(name) ?? schema.entities.find(e => e.name.toLowerCase() === name.trim().toLowerCase()) ?? null;
45331
- };
45332
45351
  const related = (a, b) => schema.relationships.some(r => (r.source === a && r.target === b) || (r.source === b && r.target === a));
45333
45352
  return schema.entities.flatMap(e => (e.attributes ?? []).filter(a => a.isFK).flatMap(a => {
45334
45353
  if (!a.references) {
45335
45354
  return schema.relationships.some(r => r.source === e.id || r.target === e.id) ? []
45336
45355
  : [this._entity(level, e, 'foreign-key-unrelated', 'info', `"${e.name}.${a.name}" is a foreign key, but "${e.name}" has no relationship`)];
45337
45356
  }
45338
- const target = find(a.references);
45357
+ const target = schema.resolveReference(a.references)?.entity;
45339
45358
  if (!target) return [this._entity(level, e, 'reference-missing', 'warning', `"${e.name}.${a.name}" references "${a.references}", which is not at the ${level} level`)];
45340
45359
  return related(e.id, target.id) ? []
45341
45360
  : [this._entity(level, e, 'foreign-key-without-relationship', 'warning', `"${e.name}.${a.name}" references "${target.name}", but they have no relationship`)];
@@ -48277,6 +48296,15 @@
48277
48296
  return /^[a-z_][a-z0-9_]*$/.test(s) && !RESERVED.has(s) ? s : `"${s.replace(/"/g, '""')}"`;
48278
48297
  }
48279
48298
 
48299
+ /**
48300
+ * A table's name as SQL takes it: a schema-qualified one (`sales.customer`)
48301
+ * each part as name() has it, so it names the table in its schema rather
48302
+ * than one table called "sales.customer".
48303
+ */
48304
+ static tableName(text) {
48305
+ return String(text).split('.').map(SqlDdl.name).join('.');
48306
+ }
48307
+
48280
48308
  /** A text as an SQL string literal. */
48281
48309
  static literal(text) {
48282
48310
  return `'${String(text).replace(/'/g, "''")}'`;
@@ -48286,11 +48314,7 @@
48286
48314
  write(model) {
48287
48315
  const schema = model.level('physical');
48288
48316
  const tables = schema.entities;
48289
- const find = (ref) => {
48290
- const [table, column] = String(ref).split('.').map(s => s.trim());
48291
- const target = schema.entity(table) ?? tables.find(t => t.name.toLowerCase() === table.toLowerCase());
48292
- return target ? { target, column } : null;
48293
- };
48317
+ const table = SqlDdl.tableName;
48294
48318
  const out = [`-- ${model.name || 'Data model'}: the physical level, ${tables.length} table${tables.length === 1 ? '' : 's'}`, ''];
48295
48319
  for (const t of tables) {
48296
48320
  const columns = t.attributes ?? [];
@@ -48300,28 +48324,28 @@
48300
48324
  ...(keys.length ? [` PRIMARY KEY (${keys.join(', ')})`] : []),
48301
48325
  ...columns.filter(c => c.isUnique && !c.isPK).map(c => ` UNIQUE (${SqlDdl.name(c.name)})`),
48302
48326
  ];
48303
- out.push(`CREATE TABLE ${SqlDdl.name(t.name)} (`, lines.join(',\n'), ');', '');
48327
+ out.push(`CREATE TABLE ${table(t.name)} (`, lines.join(',\n'), ');', '');
48304
48328
  }
48305
48329
  for (const t of tables) {
48306
48330
  for (const c of (t.attributes ?? []).filter(a => a.isFK && a.references)) {
48307
- const ref = find(c.references);
48331
+ const ref = schema.resolveReference(c.references);
48308
48332
  if (!ref) {
48309
48333
  out.push(`-- ${t.name}.${c.name} references ${c.references}, which is not a table here`);
48310
48334
  continue;
48311
48335
  }
48312
- const theirs = ref.column ? [ref.column] : (ref.target.attributes ?? []).filter(a => a.isPK).map(a => a.name);
48336
+ const theirs = ref.column ? [ref.column] : (ref.entity.attributes ?? []).filter(a => a.isPK).map(a => a.name);
48313
48337
  if (!theirs.length) {
48314
- out.push(`-- ${t.name}.${c.name} references ${ref.target.name}, which has no primary key`);
48338
+ out.push(`-- ${t.name}.${c.name} references ${ref.entity.name}, which has no primary key`);
48315
48339
  continue;
48316
48340
  }
48317
- out.push(`ALTER TABLE ${SqlDdl.name(t.name)} ADD CONSTRAINT ${SqlDdl.name(`fk_${t.name}_${c.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, '_'))}`
48318
- + ` FOREIGN KEY (${SqlDdl.name(c.name)}) REFERENCES ${SqlDdl.name(ref.target.name)} (${theirs.map(SqlDdl.name).join(', ')});`);
48341
+ out.push(`ALTER TABLE ${table(t.name)} ADD CONSTRAINT ${SqlDdl.name(`fk_${t.name}_${c.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, '_'))}`
48342
+ + ` FOREIGN KEY (${SqlDdl.name(c.name)}) REFERENCES ${table(ref.entity.name)} (${theirs.map(SqlDdl.name).join(', ')});`);
48319
48343
  }
48320
48344
  }
48321
48345
  if (this.comments) {
48322
48346
  const notes = tables.flatMap(t => [
48323
- ...(t.description ? [`COMMENT ON TABLE ${SqlDdl.name(t.name)} IS ${SqlDdl.literal(t.description)};`] : []),
48324
- ...(t.attributes ?? []).filter(c => c.description).map(c => `COMMENT ON COLUMN ${SqlDdl.name(t.name)}.${SqlDdl.name(c.name)} IS ${SqlDdl.literal(c.description)};`),
48347
+ ...(t.description ? [`COMMENT ON TABLE ${table(t.name)} IS ${SqlDdl.literal(t.description)};`] : []),
48348
+ ...(t.attributes ?? []).filter(c => c.description).map(c => `COMMENT ON COLUMN ${table(t.name)}.${SqlDdl.name(c.name)} IS ${SqlDdl.literal(c.description)};`),
48325
48349
  ]);
48326
48350
  if (notes.length) out.push('', ...notes);
48327
48351
  }
@@ -41279,6 +41279,29 @@
41279
41279
  return this.entities.find(e => e.id === id) ?? null;
41280
41280
  }
41281
41281
 
41282
+ /** An entity by id, else by name (any case), or null. */
41283
+ named(text) {
41284
+ const s = String(text ?? '').trim();
41285
+ return this.entity(s) ?? this.entities.find(e => String(e.name ?? '').toLowerCase() === s.toLowerCase()) ?? null;
41286
+ }
41287
+
41288
+ /**
41289
+ * What a foreign key's `references` names: `{ entity, column }` (column
41290
+ * null when it names the table alone), or null. `table` or `table.column`,
41291
+ * the table by id or name; a table's own name may hold dots (a
41292
+ * schema-qualified `sales.customer`), so the whole text is tried as a
41293
+ * table first, then what is before its last dot, the rest its column.
41294
+ */
41295
+ resolveReference(ref) {
41296
+ const text = String(ref ?? '').trim();
41297
+ if (!text) return null;
41298
+ const whole = this.named(text);
41299
+ if (whole) return { entity: whole, column: null };
41300
+ const dot = text.lastIndexOf('.');
41301
+ const entity = dot > 0 ? this.named(text.slice(0, dot)) : null;
41302
+ return entity ? { entity, column: text.slice(dot + 1).trim() || null } : null;
41303
+ }
41304
+
41282
41305
  /** Add an entity (its id unique at this level). */
41283
41306
  addEntity(entity) {
41284
41307
  if (!entity?.id) throw new TypeError('An entity needs an id');
@@ -41719,17 +41742,13 @@
41719
41742
  foreignKeys() {
41720
41743
  return ATTRIBUTED.flatMap(level => {
41721
41744
  const schema = this.model.level(level);
41722
- const find = (ref) => {
41723
- const [name] = String(ref).split('.');
41724
- return schema.entity(name) ?? schema.entities.find(e => e.name.toLowerCase() === name.trim().toLowerCase()) ?? null;
41725
- };
41726
41745
  const related = (a, b) => schema.relationships.some(r => (r.source === a && r.target === b) || (r.source === b && r.target === a));
41727
41746
  return schema.entities.flatMap(e => (e.attributes ?? []).filter(a => a.isFK).flatMap(a => {
41728
41747
  if (!a.references) {
41729
41748
  return schema.relationships.some(r => r.source === e.id || r.target === e.id) ? []
41730
41749
  : [this._entity(level, e, 'foreign-key-unrelated', 'info', `"${e.name}.${a.name}" is a foreign key, but "${e.name}" has no relationship`)];
41731
41750
  }
41732
- const target = find(a.references);
41751
+ const target = schema.resolveReference(a.references)?.entity;
41733
41752
  if (!target) return [this._entity(level, e, 'reference-missing', 'warning', `"${e.name}.${a.name}" references "${a.references}", which is not at the ${level} level`)];
41734
41753
  return related(e.id, target.id) ? []
41735
41754
  : [this._entity(level, e, 'foreign-key-without-relationship', 'warning', `"${e.name}.${a.name}" references "${target.name}", but they have no relationship`)];
@@ -44671,6 +44690,15 @@
44671
44690
  return /^[a-z_][a-z0-9_]*$/.test(s) && !RESERVED.has(s) ? s : `"${s.replace(/"/g, '""')}"`;
44672
44691
  }
44673
44692
 
44693
+ /**
44694
+ * A table's name as SQL takes it: a schema-qualified one (`sales.customer`)
44695
+ * each part as name() has it, so it names the table in its schema rather
44696
+ * than one table called "sales.customer".
44697
+ */
44698
+ static tableName(text) {
44699
+ return String(text).split('.').map(SqlDdl.name).join('.');
44700
+ }
44701
+
44674
44702
  /** A text as an SQL string literal. */
44675
44703
  static literal(text) {
44676
44704
  return `'${String(text).replace(/'/g, "''")}'`;
@@ -44680,11 +44708,7 @@
44680
44708
  write(model) {
44681
44709
  const schema = model.level('physical');
44682
44710
  const tables = schema.entities;
44683
- const find = (ref) => {
44684
- const [table, column] = String(ref).split('.').map(s => s.trim());
44685
- const target = schema.entity(table) ?? tables.find(t => t.name.toLowerCase() === table.toLowerCase());
44686
- return target ? { target, column } : null;
44687
- };
44711
+ const table = SqlDdl.tableName;
44688
44712
  const out = [`-- ${model.name || 'Data model'}: the physical level, ${tables.length} table${tables.length === 1 ? '' : 's'}`, ''];
44689
44713
  for (const t of tables) {
44690
44714
  const columns = t.attributes ?? [];
@@ -44694,28 +44718,28 @@
44694
44718
  ...(keys.length ? [` PRIMARY KEY (${keys.join(', ')})`] : []),
44695
44719
  ...columns.filter(c => c.isUnique && !c.isPK).map(c => ` UNIQUE (${SqlDdl.name(c.name)})`),
44696
44720
  ];
44697
- out.push(`CREATE TABLE ${SqlDdl.name(t.name)} (`, lines.join(',\n'), ');', '');
44721
+ out.push(`CREATE TABLE ${table(t.name)} (`, lines.join(',\n'), ');', '');
44698
44722
  }
44699
44723
  for (const t of tables) {
44700
44724
  for (const c of (t.attributes ?? []).filter(a => a.isFK && a.references)) {
44701
- const ref = find(c.references);
44725
+ const ref = schema.resolveReference(c.references);
44702
44726
  if (!ref) {
44703
44727
  out.push(`-- ${t.name}.${c.name} references ${c.references}, which is not a table here`);
44704
44728
  continue;
44705
44729
  }
44706
- const theirs = ref.column ? [ref.column] : (ref.target.attributes ?? []).filter(a => a.isPK).map(a => a.name);
44730
+ const theirs = ref.column ? [ref.column] : (ref.entity.attributes ?? []).filter(a => a.isPK).map(a => a.name);
44707
44731
  if (!theirs.length) {
44708
- out.push(`-- ${t.name}.${c.name} references ${ref.target.name}, which has no primary key`);
44732
+ out.push(`-- ${t.name}.${c.name} references ${ref.entity.name}, which has no primary key`);
44709
44733
  continue;
44710
44734
  }
44711
- out.push(`ALTER TABLE ${SqlDdl.name(t.name)} ADD CONSTRAINT ${SqlDdl.name(`fk_${t.name}_${c.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, '_'))}`
44712
- + ` FOREIGN KEY (${SqlDdl.name(c.name)}) REFERENCES ${SqlDdl.name(ref.target.name)} (${theirs.map(SqlDdl.name).join(', ')});`);
44735
+ out.push(`ALTER TABLE ${table(t.name)} ADD CONSTRAINT ${SqlDdl.name(`fk_${t.name}_${c.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, '_'))}`
44736
+ + ` FOREIGN KEY (${SqlDdl.name(c.name)}) REFERENCES ${table(ref.entity.name)} (${theirs.map(SqlDdl.name).join(', ')});`);
44713
44737
  }
44714
44738
  }
44715
44739
  if (this.comments) {
44716
44740
  const notes = tables.flatMap(t => [
44717
- ...(t.description ? [`COMMENT ON TABLE ${SqlDdl.name(t.name)} IS ${SqlDdl.literal(t.description)};`] : []),
44718
- ...(t.attributes ?? []).filter(c => c.description).map(c => `COMMENT ON COLUMN ${SqlDdl.name(t.name)}.${SqlDdl.name(c.name)} IS ${SqlDdl.literal(c.description)};`),
44741
+ ...(t.description ? [`COMMENT ON TABLE ${table(t.name)} IS ${SqlDdl.literal(t.description)};`] : []),
44742
+ ...(t.attributes ?? []).filter(c => c.description).map(c => `COMMENT ON COLUMN ${table(t.name)}.${SqlDdl.name(c.name)} IS ${SqlDdl.literal(c.description)};`),
44719
44743
  ]);
44720
44744
  if (notes.length) out.push('', ...notes);
44721
44745
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peter.prib/renderengine",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "ArchiMate modeller, diagram editor and chart library for the browser, with no runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -53,17 +53,13 @@ export class DataModelChecker extends ModelChecker {
53
53
  foreignKeys() {
54
54
  return ATTRIBUTED.flatMap(level => {
55
55
  const schema = this.model.level(level);
56
- const find = (ref) => {
57
- const [name] = String(ref).split('.');
58
- return schema.entity(name) ?? schema.entities.find(e => e.name.toLowerCase() === name.trim().toLowerCase()) ?? null;
59
- };
60
56
  const related = (a, b) => schema.relationships.some(r => (r.source === a && r.target === b) || (r.source === b && r.target === a));
61
57
  return schema.entities.flatMap(e => (e.attributes ?? []).filter(a => a.isFK).flatMap(a => {
62
58
  if (!a.references) {
63
59
  return schema.relationships.some(r => r.source === e.id || r.target === e.id) ? []
64
60
  : [this._entity(level, e, 'foreign-key-unrelated', 'info', `"${e.name}.${a.name}" is a foreign key, but "${e.name}" has no relationship`)];
65
61
  }
66
- const target = find(a.references);
62
+ const target = schema.resolveReference(a.references)?.entity;
67
63
  if (!target) return [this._entity(level, e, 'reference-missing', 'warning', `"${e.name}.${a.name}" references "${a.references}", which is not at the ${level} level`)];
68
64
  return related(e.id, target.id) ? []
69
65
  : [this._entity(level, e, 'foreign-key-without-relationship', 'warning', `"${e.name}.${a.name}" references "${target.name}", but they have no relationship`)];
@@ -27,6 +27,29 @@ export class LevelSchema {
27
27
  return this.entities.find(e => e.id === id) ?? null;
28
28
  }
29
29
 
30
+ /** An entity by id, else by name (any case), or null. */
31
+ named(text) {
32
+ const s = String(text ?? '').trim();
33
+ return this.entity(s) ?? this.entities.find(e => String(e.name ?? '').toLowerCase() === s.toLowerCase()) ?? null;
34
+ }
35
+
36
+ /**
37
+ * What a foreign key's `references` names: `{ entity, column }` (column
38
+ * null when it names the table alone), or null. `table` or `table.column`,
39
+ * the table by id or name; a table's own name may hold dots (a
40
+ * schema-qualified `sales.customer`), so the whole text is tried as a
41
+ * table first, then what is before its last dot, the rest its column.
42
+ */
43
+ resolveReference(ref) {
44
+ const text = String(ref ?? '').trim();
45
+ if (!text) return null;
46
+ const whole = this.named(text);
47
+ if (whole) return { entity: whole, column: null };
48
+ const dot = text.lastIndexOf('.');
49
+ const entity = dot > 0 ? this.named(text.slice(0, dot)) : null;
50
+ return entity ? { entity, column: text.slice(dot + 1).trim() || null } : null;
51
+ }
52
+
30
53
  /** Add an entity (its id unique at this level). */
31
54
  addEntity(entity) {
32
55
  if (!entity?.id) throw new TypeError('An entity needs an id');
@@ -27,6 +27,15 @@ export class SqlDdl {
27
27
  return /^[a-z_][a-z0-9_]*$/.test(s) && !RESERVED.has(s) ? s : `"${s.replace(/"/g, '""')}"`;
28
28
  }
29
29
 
30
+ /**
31
+ * A table's name as SQL takes it: a schema-qualified one (`sales.customer`)
32
+ * each part as name() has it, so it names the table in its schema rather
33
+ * than one table called "sales.customer".
34
+ */
35
+ static tableName(text) {
36
+ return String(text).split('.').map(SqlDdl.name).join('.');
37
+ }
38
+
30
39
  /** A text as an SQL string literal. */
31
40
  static literal(text) {
32
41
  return `'${String(text).replace(/'/g, "''")}'`;
@@ -36,11 +45,7 @@ export class SqlDdl {
36
45
  write(model) {
37
46
  const schema = model.level('physical');
38
47
  const tables = schema.entities;
39
- const find = (ref) => {
40
- const [table, column] = String(ref).split('.').map(s => s.trim());
41
- const target = schema.entity(table) ?? tables.find(t => t.name.toLowerCase() === table.toLowerCase());
42
- return target ? { target, column } : null;
43
- };
48
+ const table = SqlDdl.tableName;
44
49
  const out = [`-- ${model.name || 'Data model'}: the physical level, ${tables.length} table${tables.length === 1 ? '' : 's'}`, ''];
45
50
  for (const t of tables) {
46
51
  const columns = t.attributes ?? [];
@@ -50,28 +55,28 @@ export class SqlDdl {
50
55
  ...(keys.length ? [` PRIMARY KEY (${keys.join(', ')})`] : []),
51
56
  ...columns.filter(c => c.isUnique && !c.isPK).map(c => ` UNIQUE (${SqlDdl.name(c.name)})`),
52
57
  ];
53
- out.push(`CREATE TABLE ${SqlDdl.name(t.name)} (`, lines.join(',\n'), ');', '');
58
+ out.push(`CREATE TABLE ${table(t.name)} (`, lines.join(',\n'), ');', '');
54
59
  }
55
60
  for (const t of tables) {
56
61
  for (const c of (t.attributes ?? []).filter(a => a.isFK && a.references)) {
57
- const ref = find(c.references);
62
+ const ref = schema.resolveReference(c.references);
58
63
  if (!ref) {
59
64
  out.push(`-- ${t.name}.${c.name} references ${c.references}, which is not a table here`);
60
65
  continue;
61
66
  }
62
- const theirs = ref.column ? [ref.column] : (ref.target.attributes ?? []).filter(a => a.isPK).map(a => a.name);
67
+ const theirs = ref.column ? [ref.column] : (ref.entity.attributes ?? []).filter(a => a.isPK).map(a => a.name);
63
68
  if (!theirs.length) {
64
- out.push(`-- ${t.name}.${c.name} references ${ref.target.name}, which has no primary key`);
69
+ out.push(`-- ${t.name}.${c.name} references ${ref.entity.name}, which has no primary key`);
65
70
  continue;
66
71
  }
67
- out.push(`ALTER TABLE ${SqlDdl.name(t.name)} ADD CONSTRAINT ${SqlDdl.name(`fk_${t.name}_${c.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, '_'))}`
68
- + ` FOREIGN KEY (${SqlDdl.name(c.name)}) REFERENCES ${SqlDdl.name(ref.target.name)} (${theirs.map(SqlDdl.name).join(', ')});`);
72
+ out.push(`ALTER TABLE ${table(t.name)} ADD CONSTRAINT ${SqlDdl.name(`fk_${t.name}_${c.name}`.toLowerCase().replace(/[^a-z0-9_]+/g, '_'))}`
73
+ + ` FOREIGN KEY (${SqlDdl.name(c.name)}) REFERENCES ${table(ref.entity.name)} (${theirs.map(SqlDdl.name).join(', ')});`);
69
74
  }
70
75
  }
71
76
  if (this.comments) {
72
77
  const notes = tables.flatMap(t => [
73
- ...(t.description ? [`COMMENT ON TABLE ${SqlDdl.name(t.name)} IS ${SqlDdl.literal(t.description)};`] : []),
74
- ...(t.attributes ?? []).filter(c => c.description).map(c => `COMMENT ON COLUMN ${SqlDdl.name(t.name)}.${SqlDdl.name(c.name)} IS ${SqlDdl.literal(c.description)};`),
78
+ ...(t.description ? [`COMMENT ON TABLE ${table(t.name)} IS ${SqlDdl.literal(t.description)};`] : []),
79
+ ...(t.attributes ?? []).filter(c => c.description).map(c => `COMMENT ON COLUMN ${table(t.name)}.${SqlDdl.name(c.name)} IS ${SqlDdl.literal(c.description)};`),
75
80
  ]);
76
81
  if (notes.length) out.push('', ...notes);
77
82
  }