Skip to content

Commit

Permalink
fix(control): reliably sort schema control declaration by dependency …
Browse files Browse the repository at this point in the history
…order
  • Loading branch information
uladkasach committed Jun 9, 2024
1 parent 10e5d69 commit 286e62d
Show file tree
Hide file tree
Showing 10 changed files with 513 additions and 129 deletions.
292 changes: 244 additions & 48 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,15 @@
"chalk": "2.4.2",
"change-case": "4.1.1",
"domain-objects": "0.20.0",
"domain-objects-metadata": "0.5.1",
"domain-objects-metadata": "0.7.1",
"fast-glob": "3.2.2",
"joi": "17.4.0",
"lodash.omit": "4.5.0",
"oclif": "4.11.3",
"shelljs": "0.8.5",
"simple-async-tasks": "1.4.2",
"ts-node": "10.9.2",
"type-fns": "0.8.1",
"type-fns": "1.15.0",
"uuid": "9.0.0",
"yaml": "1.6.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { DomainEntity } from 'domain-objects';
import { AsyncTask, AsyncTaskStatus } from 'simple-async-tasks';

/**
* an async task which predicts station congestion by comparing expected arrival time with estimated arrival time
*/
export interface AsyncTaskPredictStationCongestion extends AsyncTask {
id?: number;
uuid?: string;
createdAt?: Date;
updatedAt?: Date;
status: AsyncTaskStatus;

/**
* the station to run this prediction for
*/
stationUuid: string;

/**
* the event to run the prediction on
*
* note
* - this has a nested dependency on a train, which we use to test sql-schema-control order
*/
trainLocatedEventUuid: string;
}
export class AsyncTaskPredictStationCongestion
extends DomainEntity<AsyncTaskPredictStationCongestion>
implements AsyncTaskPredictStationCongestion
{
public static unique = ['stationUuid', 'trainLocatedEventUuid'];
public static updatable = ['status'];
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './AsyncTaskPredictStationCongestion'
export * from './Carriage';
export * from './Certificate';
export * from './Engineer';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`defineDependentReferenceAvailableProvisionOrder should work on the example project 1`] = `
{
"depth": 3,
"order": [
"Carriage",
"Certificate",
"Geocode",
"Locomotive",
"Price",
"TrainEngineer",
"TrainStation",
"InvoiceLineItem",
"Train",
"TrainLocatedEvent",
"AsyncTaskPredictStationCongestion",
"Invoice",
],
"reason": {
"AsyncTaskPredictStationCongestion": [
"TrainStation",
"TrainLocatedEvent",
],
"Carriage": [],
"Certificate": [],
"Geocode": [],
"Invoice": [
"InvoiceLineItem",
"Price",
],
"InvoiceLineItem": [
"Price",
],
"Locomotive": [],
"Price": [],
"Train": [
"Geocode",
"Locomotive",
"Carriage",
"TrainEngineer",
],
"TrainEngineer": [
"Certificate",
],
"TrainLocatedEvent": [
"Train",
"Geocode",
],
"TrainStation": [
"Geocode",
],
},
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,6 @@ GeneratedCodeFile {
- type: resource
path: ./functions/upsert_certificate.sql
# train_engineer
- type: resource
path: ./tables/train_engineer.sql
- type: resource
path: ./tables/train_engineer_version.sql
- type: resource
path: ./tables/train_engineer_version_to_certificate.sql
- type: resource
path: ./tables/train_engineer_version_to_license_uuid.sql
- type: resource
path: ./tables/train_engineer_cvp.sql
- type: resource
path: ./views/view_train_engineer_current.sql
- type: resource
path: ./functions/upsert_train_engineer.sql
# geocode
- type: resource
path: ./tables/geocode.sql
Expand All @@ -48,11 +32,27 @@ GeneratedCodeFile {
- type: resource
path: ./functions/upsert_locomotive.sql
# train_located_event
# price
- type: resource
path: ./tables/train_located_event.sql
path: ./tables/price.sql
- type: resource
path: ./functions/upsert_train_located_event.sql
path: ./functions/upsert_price.sql
# train_engineer
- type: resource
path: ./tables/train_engineer.sql
- type: resource
path: ./tables/train_engineer_version.sql
- type: resource
path: ./tables/train_engineer_version_to_certificate.sql
- type: resource
path: ./tables/train_engineer_version_to_license_uuid.sql
- type: resource
path: ./tables/train_engineer_cvp.sql
- type: resource
path: ./views/view_train_engineer_current.sql
- type: resource
path: ./functions/upsert_train_engineer.sql
# train_station
- type: resource
Expand All @@ -66,32 +66,12 @@ GeneratedCodeFile {
- type: resource
path: ./functions/upsert_train_station.sql
# price
- type: resource
path: ./tables/price.sql
- type: resource
path: ./functions/upsert_price.sql
# invoice_line_item
- type: resource
path: ./tables/invoice_line_item.sql
- type: resource
path: ./functions/upsert_invoice_line_item.sql
# invoice
- type: resource
path: ./tables/invoice.sql
- type: resource
path: ./tables/invoice_version.sql
- type: resource
path: ./tables/invoice_version_to_invoice_line_item.sql
- type: resource
path: ./tables/invoice_cvp.sql
- type: resource
path: ./views/view_invoice_current.sql
- type: resource
path: ./functions/upsert_invoice.sql
# train
- type: resource
path: ./tables/train.sql
Expand All @@ -108,7 +88,39 @@ GeneratedCodeFile {
- type: resource
path: ./views/view_train_current.sql
- type: resource
path: ./functions/upsert_train.sql",
path: ./functions/upsert_train.sql
# train_located_event
- type: resource
path: ./tables/train_located_event.sql
- type: resource
path: ./functions/upsert_train_located_event.sql
# async_task_predict_station_congestion
- type: resource
path: ./tables/async_task_predict_station_congestion.sql
- type: resource
path: ./tables/async_task_predict_station_congestion_version.sql
- type: resource
path: ./tables/async_task_predict_station_congestion_cvp.sql
- type: resource
path: ./views/view_async_task_predict_station_congestion_current.sql
- type: resource
path: ./functions/upsert_async_task_predict_station_congestion.sql
# invoice
- type: resource
path: ./tables/invoice.sql
- type: resource
path: ./tables/invoice_version.sql
- type: resource
path: ./tables/invoice_version_to_invoice_line_item.sql
- type: resource
path: ./tables/invoice_cvp.sql
- type: resource
path: ./views/view_invoice_current.sql
- type: resource
path: ./functions/upsert_invoice.sql",
"relpath": "./domain.control.yml",
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { introspect } from 'domain-objects-metadata';

import { defineSqlSchemaRelationshipsForDomainObjects } from '../sqlSchemaRelationship/defineSqlSchemaRelationshipsForDomainObjects';
import { defineDependentReferenceAvailableProvisionOrder } from './defineDependentReferenceAvailableProvisionOrder';
import { defineSqlSchemaControlCodeFilesForDomainObjects } from './defineSqlSchemaControlCodeFilesForDomainObjects';

describe('defineDependentReferenceAvailableProvisionOrder', () => {
it('should work on the example project', () => {
const domainObjects = introspect(
`${__dirname}/../../__test_assets__/exampleProject/src/domain/objects/index.ts`,
);
const sqlSchemaRelationships = defineSqlSchemaRelationshipsForDomainObjects(
{ domainObjects },
);

const { order, reason, depth } =
defineDependentReferenceAvailableProvisionOrder({
sqlSchemaRelationships,
});
console.log({ order, reason, depth });

expect({ order, reason, depth }).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { UnexpectedCodePathError } from '@ehmpathy/error-fns';
import { isPresent } from 'type-fns';

import { SqlSchemaToDomainObjectRelationship } from '../../../domain';

/**
* .what = defines the order in which schemas must be provisioned to ensure their dependent references are available
* .why =
* - guarantees that if this order is followed, there will be no schema declaration issues
*/
export const defineDependentReferenceAvailableProvisionOrder = ({
sqlSchemaRelationships,
}: {
sqlSchemaRelationships: SqlSchemaToDomainObjectRelationship[];
}): { order: string[]; reason: Record<string, string[]>; depth: number } => {
// create a map of DobjName -> ReferencedDobjName[]
const dependencyMap = sqlSchemaRelationships.reduce(
(summary, thisRelationship) => {
const key = thisRelationship.name.domainObject;
const value = [
...new Set(
thisRelationship.properties
.map((property) => property.sqlSchema.reference?.of.name)
.filter(isPresent),
),
];
return {
...summary,
[key]: value,
};
},
{} as Record<string, string[]>,
);

// loop through each until its dependencies are in the sorted array
const dobjNamesToOrder = Object.keys(dependencyMap).sort();
const order: string[] = [];
let iterations = 0;
while (order.length < dobjNamesToOrder.length) {
// increment the iterations; fail fast if we've iterated more than 21 times. there should not be a reason to have a reference depth of 21+ times, meaning there's probably a cyclical reference
iterations++;
if (iterations > 21)
throw new UnexpectedCodePathError(
'attempted to resolve reference order for more than 21 iterations. is there a cyclical import?',
{ dependencyMap: dependencyMap },
);

// loop through each dobj
for (const dobjName of dobjNamesToOrder) {
// if already ordered, no need to try again
if (order.includes(dobjName)) continue;

// if a dependency is not found yet, continue until it is
const dependencies = dependencyMap[dobjName];
if (!dependencies)
throw new UnexpectedCodePathError(
'could not find dependencies for dobj. how is that possible?',
{ dobjName, dependencies },
);
const hasSomeDependencyMissed = dependencies.some(
(dependency) => !order.includes(dependency),
);
if (hasSomeDependencyMissed) continue;

// otherwise, add this dobj to the order
order.push(dobjName);
}
}

return {
order: order,
reason: dependencyMap,
depth: iterations,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('defineSqlSchemaControlCodeFilesForDomainObjects', () => {
const sqlSchemaRelationships = defineSqlSchemaRelationshipsForDomainObjects(
{ domainObjects },
);

const file = defineSqlSchemaControlCodeFilesForDomainObjects({
domainObjects,
sqlSchemaRelationships,
Expand Down
Loading

0 comments on commit 286e62d

Please sign in to comment.