Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tpt update and fetch one by candidate key #65

Merged
merged 6 commits into from
Dec 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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