Skip to content

Commit

Permalink
Merge pull request #65 from AnyhowStep/tpt-update-and-fetch-one-by-ca…
Browse files Browse the repository at this point in the history
…ndidate-key

Tpt update and fetch one by candidate key
  • Loading branch information
AnyhowStep authored Dec 15, 2019
2 parents 34264df + b22504b commit 89f39a6
Show file tree
Hide file tree
Showing 20 changed files with 1,121 additions and 116 deletions.
16 changes: 16 additions & 0 deletions src/column/array-util/constructor/from-column-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,19 @@ export function fromColumnMap<
}
return result as FromColumnMap<ColumnMapT>;
}

export function fromColumnMapArray<
ColumnMapT extends ColumnMap
> (
columnMapArr : readonly ColumnMapT[]
) : (
FromColumnMap<ColumnMapT>
) {
const result : IColumn[] = [];
for (const columnMap of columnMapArr) {
for (const columnAlias of Object.keys(columnMap)) {
result.push(columnMap[columnAlias]);
}
}
return result as FromColumnMap<ColumnMapT>;
}
25 changes: 23 additions & 2 deletions src/design-pattern-table-per-type/table-per-type-impl.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {ITablePerType, TablePerTypeData, InsertableTablePerType} from "./table-per-type";
import * as TablePerTypeUtil from "./util";
import {TableWithPrimaryKey} from "../table";
import {SelectConnection, ExecutionUtil, IsolableInsertOneConnection} from "../execution";
import {SelectConnection, ExecutionUtil, IsolableInsertOneConnection, IsolableUpdateConnection} from "../execution";
import {WhereDelegate} from "../where-clause";
import {OnlyKnownProperties} from "../type-util";
import {OnlyKnownProperties, StrictUnion} from "../type-util";
import {CandidateKey_NonUnion} from "../candidate-key";

export class TablePerType<DataT extends TablePerTypeData> implements ITablePerType<DataT> {
readonly childTable : DataT["childTable"];
Expand Down Expand Up @@ -79,4 +80,24 @@ export class TablePerType<DataT extends TablePerTypeData> implements ITablePerTy
row
);
}

updateAndFetchOneByCandidateKey<
CandidateKeyT extends StrictUnion<CandidateKey_NonUnion<this["childTable"]>>,
AssignmentMapT extends TablePerTypeUtil.CustomAssignmentMap<this>
> (
connection : IsolableUpdateConnection,
/**
* @todo Try and recall why I wanted `AssertNonUnion<>`
* I didn't write compile-time tests for it...
*/
candidateKey : CandidateKeyT,// & AssertNonUnion<CandidateKeyT>,
assignmentMapDelegate : TablePerTypeUtil.AssignmentMapDelegate<this, AssignmentMapT>
) : Promise<TablePerTypeUtil.UpdateAndFetchOneReturnType<this, AssignmentMapT>> {
return TablePerTypeUtil.updateAndFetchOneByCandidateKey(
this,
connection,
candidateKey,
assignmentMapDelegate
);
}
}
77 changes: 77 additions & 0 deletions src/design-pattern-table-per-type/util/execution/absorb-row.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {BuiltInExprUtil} from "../../../built-in-expr";
import {DataTypeUtil} from "../../../data-type";
import {ITable} from "../../../table";

/**
* @todo Better name
*
* Adds properties from `row` to `result`.
*
* If a property from `row` already exists on `result`,
* we use `table` to check if the values on both objects are equal.
*
* If they are not equal, an `Error` is thrown.
*/
export function absorbRow (
result : Record<string, unknown>,
table : ITable,
row : Record<string, unknown>
) {
for (const columnAlias of Object.keys(row)) {
/**
* This is guaranteed to be a value expression.
*/
const newValue = row[columnAlias];

if (Object.prototype.hasOwnProperty.call(result, columnAlias)) {
/**
* This `curValue` could be a non-value expression.
* We only want value expressions.
*/
const curValue = result[columnAlias];

if (BuiltInExprUtil.isAnyNonValueExpr(curValue)) {
/**
* Add this new value to the `result`
* so we can use it to update rows of tables
* further down the inheritance hierarchy.
*/
result[columnAlias] = newValue;
continue;
}

if (curValue === newValue) {
/**
* They are equal, do nothing.
*/
continue;
}
/**
* We need some custom equality checking logic
*/
if (!DataTypeUtil.isNullSafeEqual(
table.columns[columnAlias],
/**
* This may throw
*/
table.columns[columnAlias].mapper(
`${table.alias}.${columnAlias}`,
curValue
),
newValue
)) {
/**
* @todo Custom `Error` type
*/
throw new Error(`All columns with the same name in an inheritance hierarchy must have the same value; mismatch found for ${table.alias}.${columnAlias}`);
}
} else {
/**
* Add this new value to the `result`
* so we can use it to update rows of tables
* further down the inheritance hierarchy.
*/
result[columnAlias] = newValue;
}
}
}
2 changes: 2 additions & 0 deletions src/design-pattern-table-per-type/util/execution/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./fetch-one";
export * from "./insert-and-fetch";
export * from "./insert-row";
export * from "./update-and-fetch-one-by-candidate-key";
export * from "./update-and-fetch-one-impl";
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import {IsolableInsertOneConnection, ExecutionUtil} from "../../../execution";
import {Identity, OnlyKnownProperties, omitOwnEnumerable} from "../../../type-util";
import {ColumnAlias, ColumnType, implicitAutoIncrement, generatedColumnAliases, findTableWithGeneratedColumnAlias} from "../query";
import {CustomExprUtil} from "../../../custom-expr";
import {DataTypeUtil} from "../../../data-type";
import {BuiltInExprUtil} from "../../../built-in-expr";
import {TableUtil} from "../../../table";
import {expr} from "../../../expr";
import {UsedRefUtil} from "../../../used-ref";
import {absorbRow} from "./absorb-row";

export type InsertAndFetchRow<
TptT extends InsertableTablePerType
Expand Down Expand Up @@ -117,63 +116,7 @@ export async function insertAndFetch<
connection,
result as never
);
for (const columnAlias of Object.keys(fetchedRow)) {
/**
* This is guaranteed to be a value expression.
*/
const newValue = fetchedRow[columnAlias];

if (Object.prototype.hasOwnProperty.call(result, columnAlias)) {
/**
* This `curValue` could be a non-value expression.
* We only want value expressions.
*/
const curValue = result[columnAlias];

if (BuiltInExprUtil.isAnyNonValueExpr(curValue)) {
/**
* Add this new value to the `rawInsertRow`
* so we can use it to insert rows to tables
* further down the inheritance hierarchy.
*/
result[columnAlias] = newValue;
continue;
}

if (curValue === newValue) {
/**
* They are equal, do nothing.
*/
continue;
}
/**
* We need some custom equality checking logic
*/
if (!DataTypeUtil.isNullSafeEqual(
table.columns[columnAlias],
/**
* This may throw
*/
table.columns[columnAlias].mapper(
`${table.alias}.${columnAlias}`,
curValue
),
newValue
)) {
/**
* @todo Custom `Error` type
*/
throw new Error(`All columns with the same name in an inheritance hierarchy must have the same value; mismatch found for ${table.alias}.${columnAlias}`);
}
} else {
/**
* Add this new value to the `rawInsertRow`
* so we can use it to insert rows to tables
* further down the inheritance hierarchy.
*/
result[columnAlias] = newValue;
}
}
absorbRow(result, table, fetchedRow);
}

return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {ITablePerType} from "../../table-per-type";
import {CustomAssignmentMap, AssignmentMapDelegate} from "./update-and-fetch-one-by-candidate-key";
import {IsolableSelectConnection, ExecutionUtil} from "../../../execution";
import {ColumnRefUtil} from "../../../column-ref";
import {ColumnUtil, ColumnArrayUtil} from "../../../column";
import {from, From} from "../execution-impl";
import {WhereDelegate} from "../../../where-clause";
import {isMutableColumnAlias, columnMapper} from "../query";
import {BuiltInExprUtil} from "../../../built-in-expr";
import {expr, ExprUtil} from "../../../expr";
import {DataTypeUtil} from "../../../data-type";

/**
* Not meant to be called externally.
*
* @todo Better name
*/
export async function invokeAssignmentDelegate<
TptT extends ITablePerType,
AssignmentMapT extends CustomAssignmentMap<TptT>
> (
tpt : TptT,
connection : IsolableSelectConnection,
whereDelegate : WhereDelegate<From<TptT>["fromClause"]>,
assignmentMapDelegate : AssignmentMapDelegate<TptT, AssignmentMapT>
) : Promise<Record<string, unknown>> {
const columns = ColumnRefUtil.fromColumnArray<
ColumnUtil.FromColumnMap<
| TptT["childTable"]["columns"]
| TptT["parentTables"][number]["columns"]
>[]
>(
ColumnArrayUtil.fromColumnMapArray<
| TptT["childTable"]["columns"]
| TptT["parentTables"][number]["columns"]
>(
[
tpt.childTable.columns,
...tpt.parentTables.map(parentTable => parentTable.columns)
]
)
);
/**
* May contain extra properties that are not mutable columns,
* or even columns at all.
*/
const rawAssignmentMap = assignmentMapDelegate(columns);

const columnAliasArr = Object.keys(rawAssignmentMap);
if (columnAliasArr.length == 0) {
return {};
}

const query = from(tpt)
.where(whereDelegate as any)
.select(() => columnAliasArr
.filter(columnAlias => isMutableColumnAlias(tpt, columnAlias))
.map(columnAlias => {
const customExpr = rawAssignmentMap[columnAlias as keyof typeof rawAssignmentMap] as any;
if (BuiltInExprUtil.isAnyNonValueExpr(customExpr)) {
/**
* We have a non-value expression
*/
return expr(
{
mapper : DataTypeUtil.intersect(
columnMapper(tpt, columnAlias),
BuiltInExprUtil.mapper(customExpr)
),
usedRef : BuiltInExprUtil.usedRef(customExpr),
},
BuiltInExprUtil.buildAst(customExpr)
).as(columnAlias);
} else {
/**
* We have a value expression
*/
return ExprUtil.fromRawExprNoUsedRefInput(
columnMapper(tpt, columnAlias),
customExpr
).as(columnAlias);
}
}) as any
);
/**
* Should only contain value expressions now.
*/
return ExecutionUtil.fetchOne(
query as any,
connection
) as Promise<Record<string, unknown>>;
}
Loading

0 comments on commit 89f39a6

Please sign in to comment.