@smartive/graphql-magic 22.2.1 → 22.3.0-next.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/CHANGELOG.md +2 -3
- package/dist/bin/gqm.cjs +190 -33
- package/dist/cjs/index.cjs +190 -33
- package/dist/esm/migrations/generate.d.ts +4 -0
- package/dist/esm/migrations/generate.js +180 -35
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +1 -0
- package/package.json +2 -2
- package/src/migrations/generate.ts +220 -35
- package/src/models/model-definitions.ts +1 -0
|
@@ -160,35 +160,27 @@ export class MigrationGenerator {
|
|
|
160
160
|
);
|
|
161
161
|
|
|
162
162
|
// Update fields
|
|
163
|
-
const
|
|
163
|
+
const rawExistingFields = model.fields.filter((field) => {
|
|
164
|
+
if (!field.generateAs) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
164
168
|
const col = this.getColumn(model.name, field.kind === 'relation' ? `${field.name}Id` : field.name);
|
|
165
169
|
if (!col) {
|
|
166
170
|
return false;
|
|
167
171
|
}
|
|
168
172
|
|
|
169
|
-
if (
|
|
173
|
+
if (col.generation_expression !== field.generateAs) {
|
|
170
174
|
return true;
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
|
|
174
|
-
if (field.type === 'Int') {
|
|
175
|
-
if (col.data_type !== 'integer') {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (field.type === 'Float') {
|
|
180
|
-
if (field.double) {
|
|
181
|
-
if (col.data_type !== 'double precision') {
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
} else if (col.data_type !== 'numeric') {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return false;
|
|
177
|
+
return this.hasChanged(model, field);
|
|
191
178
|
});
|
|
179
|
+
if (rawExistingFields.length) {
|
|
180
|
+
this.updateFieldsRaw(model, rawExistingFields, up, down);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const existingFields = model.fields.filter((field) => !field.generateAs && this.hasChanged(model, field));
|
|
192
184
|
this.updateFields(model, existingFields, up, down);
|
|
193
185
|
}
|
|
194
186
|
|
|
@@ -375,6 +367,10 @@ export class MigrationGenerator {
|
|
|
375
367
|
for (const field of fields) {
|
|
376
368
|
alter.push(() => this.column(field, { setNonNull: field.defaultValue !== undefined }));
|
|
377
369
|
|
|
370
|
+
if (field.generateAs) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
|
|
378
374
|
// If the field is not nullable but has no default, write placeholder code
|
|
379
375
|
if (field.nonNull && field.defaultValue === undefined) {
|
|
380
376
|
updates.push(() => this.writer.write(`${field.name}: 'TODO',`).newLine());
|
|
@@ -405,13 +401,63 @@ export class MigrationGenerator {
|
|
|
405
401
|
|
|
406
402
|
down.push(() => {
|
|
407
403
|
this.alterTable(model.name, () => {
|
|
408
|
-
for (const { kind, name } of fields) {
|
|
404
|
+
for (const { kind, name } of fields.toReversed()) {
|
|
409
405
|
this.dropColumn(kind === 'relation' ? `${name}Id` : name);
|
|
410
406
|
}
|
|
411
407
|
});
|
|
412
408
|
});
|
|
413
409
|
}
|
|
414
410
|
|
|
411
|
+
private updateFieldsRaw(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
|
|
412
|
+
if (!fields.length) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
up.push(() => {
|
|
417
|
+
this.alterTableRaw(model.name, () => {
|
|
418
|
+
for (const [index, field] of fields.entries()) {
|
|
419
|
+
this.columnRaw(field, { alter: true }, index);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
down.push(() => {
|
|
425
|
+
this.alterTableRaw(model.name, () => {
|
|
426
|
+
for (const [index, field] of fields.entries()) {
|
|
427
|
+
this.columnRaw(field, { alter: true }, index);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (isUpdatableModel(model)) {
|
|
433
|
+
const updatableFields = fields.filter(isUpdatableField);
|
|
434
|
+
if (!updatableFields.length) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
up.push(() => {
|
|
439
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
440
|
+
for (const [index, field] of updatableFields.entries()) {
|
|
441
|
+
this.columnRaw(field, { alter: true }, index);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
down.push(() => {
|
|
447
|
+
this.alterTable(`${model.name}Revision`, () => {
|
|
448
|
+
for (const [index, field] of updatableFields.entries()) {
|
|
449
|
+
this.columnRaw(
|
|
450
|
+
field,
|
|
451
|
+
{ alter: true },
|
|
452
|
+
index,
|
|
453
|
+
summonByName(this.columns[model.name], field.kind === 'relation' ? `${field.name}Id` : field.name),
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
415
461
|
private updateFields(model: EntityModel, fields: EntityField[], up: Callbacks, down: Callbacks) {
|
|
416
462
|
if (!fields.length) {
|
|
417
463
|
return;
|
|
@@ -572,6 +618,12 @@ export class MigrationGenerator {
|
|
|
572
618
|
.blankLine();
|
|
573
619
|
}
|
|
574
620
|
|
|
621
|
+
private alterTableRaw(table: string, block: () => void) {
|
|
622
|
+
this.writer.write(`await knex.raw('ALTER TABLE "${table}"`);
|
|
623
|
+
block();
|
|
624
|
+
this.writer.write(`');`).newLine().blankLine();
|
|
625
|
+
}
|
|
626
|
+
|
|
575
627
|
private alterTable(table: string, block: () => void) {
|
|
576
628
|
return this.writer
|
|
577
629
|
.write(`await knex.schema.alterTable('${table}', (table) => `)
|
|
@@ -605,29 +657,125 @@ export class MigrationGenerator {
|
|
|
605
657
|
return value;
|
|
606
658
|
}
|
|
607
659
|
|
|
660
|
+
private columnRaw(
|
|
661
|
+
{ name, ...field }: EntityField,
|
|
662
|
+
{ setNonNull = true, alter = false } = {},
|
|
663
|
+
index: number,
|
|
664
|
+
toColumn?: Column,
|
|
665
|
+
) {
|
|
666
|
+
const nonNull = () => {
|
|
667
|
+
if (setNonNull) {
|
|
668
|
+
if (toColumn) {
|
|
669
|
+
if (toColumn.is_nullable) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
if (field.nonNull) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const kind = field.kind;
|
|
683
|
+
if (field.generateAs) {
|
|
684
|
+
let type = '';
|
|
685
|
+
switch (kind) {
|
|
686
|
+
case undefined:
|
|
687
|
+
case 'primitive':
|
|
688
|
+
switch (field.type) {
|
|
689
|
+
case 'Float':
|
|
690
|
+
type = `decimal(${field.precision ?? 'undefined'}, ${field.scale ?? 'undefined'})`;
|
|
691
|
+
break;
|
|
692
|
+
default:
|
|
693
|
+
throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
|
|
694
|
+
}
|
|
695
|
+
break;
|
|
696
|
+
default:
|
|
697
|
+
throw new Error(`Generated columns of kind ${kind} are not supported yet.`);
|
|
698
|
+
}
|
|
699
|
+
if (index) {
|
|
700
|
+
this.writer.write(`,`);
|
|
701
|
+
}
|
|
702
|
+
if (alter) {
|
|
703
|
+
this.writer.write(` ALTER COLUMN "${name}" TYPE ${type}`);
|
|
704
|
+
if (setNonNull) {
|
|
705
|
+
if (nonNull()) {
|
|
706
|
+
this.writer.write(`, ALTER COLUMN "${name}" SET NOT NULL`);
|
|
707
|
+
} else {
|
|
708
|
+
this.writer.write(`, ALTER COLUMN "${name}" DROP NOT NULL`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
this.writer.write(`, ALTER COLUMN "${name}" SET EXPRESSION AS (${field.generateAs})`);
|
|
712
|
+
} else {
|
|
713
|
+
this.writer.write(
|
|
714
|
+
`${alter ? 'ALTER' : 'ADD'} COLUMN "${name}" ${type}${nonNull() ? ' not null' : ''} GENERATED ALWAYS AS (${field.generateAs}) STORED`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
throw new Error(`Only generated columns can be created with columnRaw`);
|
|
722
|
+
}
|
|
723
|
+
|
|
608
724
|
private column(
|
|
609
725
|
{ name, primary, list, ...field }: EntityField,
|
|
610
726
|
{ setUnique = true, setNonNull = true, alter = false, foreign = true, setDefault = true } = {},
|
|
611
727
|
toColumn?: Column,
|
|
612
728
|
) {
|
|
613
|
-
const
|
|
614
|
-
if (what) {
|
|
615
|
-
this.writer.write(what);
|
|
616
|
-
}
|
|
729
|
+
const nonNull = () => {
|
|
617
730
|
if (setNonNull) {
|
|
618
731
|
if (toColumn) {
|
|
619
732
|
if (toColumn.is_nullable) {
|
|
620
|
-
|
|
621
|
-
} else {
|
|
622
|
-
this.writer.write('.notNullable()');
|
|
623
|
-
}
|
|
624
|
-
} else {
|
|
625
|
-
if (field.nonNull) {
|
|
626
|
-
this.writer.write(`.notNullable()`);
|
|
627
|
-
} else {
|
|
628
|
-
this.writer.write('.nullable()');
|
|
733
|
+
return false;
|
|
629
734
|
}
|
|
735
|
+
|
|
736
|
+
return true;
|
|
630
737
|
}
|
|
738
|
+
if (field.nonNull) {
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
const kind = field.kind;
|
|
746
|
+
if (field.generateAs) {
|
|
747
|
+
let type = '';
|
|
748
|
+
switch (kind) {
|
|
749
|
+
case undefined:
|
|
750
|
+
case 'primitive':
|
|
751
|
+
switch (field.type) {
|
|
752
|
+
case 'Float':
|
|
753
|
+
type = `decimal(${field.precision ?? 'undefined'}, ${field.scale ?? 'undefined'})`;
|
|
754
|
+
break;
|
|
755
|
+
default:
|
|
756
|
+
throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
default:
|
|
760
|
+
throw new Error(`Generated columns of kind ${kind} are not supported yet.`);
|
|
761
|
+
}
|
|
762
|
+
this.writer.write(
|
|
763
|
+
`table.specificType('${name}', '${type}${nonNull() ? ' not null' : ''} GENERATED ALWAYS AS (${field.generateAs}) STORED')`,
|
|
764
|
+
);
|
|
765
|
+
if (alter) {
|
|
766
|
+
this.writer.write('.alter()');
|
|
767
|
+
}
|
|
768
|
+
this.writer.write(';').newLine();
|
|
769
|
+
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const col = (what?: string) => {
|
|
774
|
+
if (what) {
|
|
775
|
+
this.writer.write(what);
|
|
776
|
+
}
|
|
777
|
+
if (setNonNull) {
|
|
778
|
+
this.writer.write(nonNull() ? '.notNullable()' : '.nullable()');
|
|
631
779
|
}
|
|
632
780
|
if (setDefault && field.defaultValue !== undefined) {
|
|
633
781
|
this.writer.write(`.defaultTo(${this.value(field.defaultValue)})`);
|
|
@@ -642,7 +790,6 @@ export class MigrationGenerator {
|
|
|
642
790
|
}
|
|
643
791
|
this.writer.write(';').newLine();
|
|
644
792
|
};
|
|
645
|
-
const kind = field.kind;
|
|
646
793
|
switch (kind) {
|
|
647
794
|
case undefined:
|
|
648
795
|
case 'primitive':
|
|
@@ -716,6 +863,44 @@ export class MigrationGenerator {
|
|
|
716
863
|
private getColumn(tableName: string, columnName: string) {
|
|
717
864
|
return this.columns[tableName].find((col) => col.name === columnName);
|
|
718
865
|
}
|
|
866
|
+
|
|
867
|
+
private hasChanged(model: EntityModel, field: EntityField) {
|
|
868
|
+
const col = this.getColumn(model.name, field.kind === 'relation' ? `${field.name}Id` : field.name);
|
|
869
|
+
if (!col) {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (field.generateAs) {
|
|
874
|
+
if (col.generation_expression !== field.generateAs) {
|
|
875
|
+
throw new Error(
|
|
876
|
+
`Column ${col.name} has specific type ${col.generation_expression} but expected ${field.generateAs}`,
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if ((!field.nonNull && !col.is_nullable) || (field.nonNull && col.is_nullable)) {
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (!field.kind || field.kind === 'primitive') {
|
|
886
|
+
if (field.type === 'Int') {
|
|
887
|
+
if (col.data_type !== 'integer') {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (field.type === 'Float') {
|
|
892
|
+
if (field.double) {
|
|
893
|
+
if (col.data_type !== 'double precision') {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
} else if (col.data_type !== 'numeric') {
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
719
904
|
}
|
|
720
905
|
|
|
721
906
|
export const getMigrationDate = () => {
|