Skip to content

Commit ea85b57

Browse files
committed
Allow constraints on tables via value types
1 parent b754e09 commit ea85b57

7 files changed

Lines changed: 147 additions & 85 deletions

File tree

libs/execution/src/lib/constraints/constraint-executor.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,39 @@ import {
1111
ERROR_TYPEGUARD,
1212
type InternalErrorValueRepresentation,
1313
type InternalValidValueRepresentation,
14-
type ValueTypeProperty,
1514
type ValueTypeConstraintInlineDefinition,
1615
evaluateExpression,
16+
isConstraintDefinition,
1717
} from '@jvalue/jayvee-language-server';
1818

1919
import { type ExecutionContext } from '../execution-context';
2020

21-
export class ConstraintExecutor<
22-
T extends ConstraintDefinition | ValueTypeConstraintInlineDefinition,
23-
> implements AstNodeWrapper<T>
21+
export class ConstraintExecutor
22+
implements
23+
AstNodeWrapper<ConstraintDefinition | ValueTypeConstraintInlineDefinition>
2424
{
25-
constructor(public readonly astNode: T) {}
25+
constructor(
26+
public readonly astNode:
27+
| ConstraintDefinition
28+
| ValueTypeConstraintInlineDefinition,
29+
) {}
2630

2731
isValid(
28-
value: InternalValidValueRepresentation | InternalErrorValueRepresentation,
32+
values: Map<
33+
string,
34+
InternalValidValueRepresentation | InternalErrorValueRepresentation
35+
>,
2936
context: ExecutionContext,
30-
properties: T extends ValueTypeConstraintInlineDefinition
31-
? ValueTypeProperty[]
32-
: void,
3337
): boolean {
3438
const expression = this.astNode.expression;
3539

36-
if (properties === undefined) {
40+
if (isConstraintDefinition(this.astNode)) {
41+
const value = values.get('value');
42+
assert(value !== undefined);
3743
context.evaluationContext.setValueForValueKeyword(value);
3844
} else {
39-
const assignmentForTypeSystem: ValueTypeProperty[] = properties;
40-
for (const property of assignmentForTypeSystem) {
41-
context.evaluationContext.setValueForReference(property.name, value);
45+
for (const [name, value] of values) {
46+
context.evaluationContext.setValueForReference(name, value);
4247
}
4348
}
4449

@@ -57,15 +62,6 @@ export class ConstraintExecutor<
5762
),
5863
);
5964

60-
if (properties === undefined) {
61-
context.evaluationContext.deleteValueForValueKeyword();
62-
} else {
63-
const assignment_for_type_system: ValueTypeProperty[] = properties;
64-
for (const property of assignment_for_type_system) {
65-
context.evaluationContext.deleteValueForReference(property.name);
66-
}
67-
}
68-
6965
return result;
7066
}
7167
}

libs/execution/src/lib/types/io-types/table.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
import { ValueTypeProvider } from '@jvalue/jayvee-language-server';
66

7-
import { Table } from './table';
7+
import { Table, type TableColumn } from './table';
88

99
describe('Table', () => {
1010
let table: Table;
1111
let valueTypeProvider: ValueTypeProvider;
1212

1313
beforeEach(() => {
14-
table = new Table();
14+
table = new Table(0, new Map<string, TableColumn>(), []);
1515
valueTypeProvider = new ValueTypeProvider();
1616
});
1717

libs/execution/src/lib/types/io-types/table.ts

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
type IOTypeImplementation,
2323
type IoTypeVisitor,
2424
} from './io-type-implementation';
25+
import { ConstraintExecutor } from '../../constraints';
26+
import { type ExecutionContext } from '../../execution-context';
2527

2628
export interface TableColumn<
2729
T extends InternalValidValueRepresentation = InternalValidValueRepresentation,
@@ -30,7 +32,7 @@ export interface TableColumn<
3032
valueType: ValueType;
3133
}
3234

33-
export type TableRow = Record<
35+
export type TableRow = Map<
3436
string,
3537
InternalValidValueRepresentation | InternalErrorValueRepresentation
3638
>;
@@ -42,12 +44,14 @@ export type TableRow = Record<
4244
export class Table implements IOTypeImplementation<IOType.TABLE> {
4345
public readonly ioType = IOType.TABLE;
4446

45-
private numberOfRows = 0;
46-
47-
private columns = new Map<string, TableColumn>();
48-
49-
public constructor(numberOfRows = 0) {
50-
this.numberOfRows = numberOfRows;
47+
public constructor(
48+
private numberOfRows: number,
49+
private columns: Map<string, TableColumn>,
50+
private constraints: ConstraintExecutor[],
51+
) {
52+
assert(this.numberOfRows !== undefined);
53+
assert(this.columns !== undefined);
54+
assert(this.constraints !== undefined);
5155
}
5256

5357
addColumn(name: string, column: TableColumn): void {
@@ -60,32 +64,44 @@ export class Table implements IOTypeImplementation<IOType.TABLE> {
6064
* NOTE: This method will only add the row if the table has at least one column!
6165
* @param row data of this row for each column
6266
*/
63-
addRow(row: TableRow): void {
64-
const rowLength = Object.keys(row).length;
67+
addRow(
68+
row: Record<
69+
string,
70+
InternalValidValueRepresentation | InternalErrorValueRepresentation
71+
>,
72+
): void;
73+
addRow(row: TableRow): void;
74+
addRow(
75+
row:
76+
| TableRow
77+
| Record<
78+
string,
79+
InternalValidValueRepresentation | InternalErrorValueRepresentation
80+
>,
81+
): void {
82+
const rowLength = row instanceof Map ? row.size : Object.keys(row).length;
6583
assert(
6684
rowLength === this.columns.size,
6785
`Added row has the wrong dimension (expected: ${this.columns.size}, actual: ${rowLength})`,
6886
);
69-
if (rowLength === 0) {
70-
return;
87+
88+
if (rowLength > 0) {
89+
this.numberOfRows++;
7190
}
72-
assert(
73-
Object.keys(row).every((x) => this.hasColumn(x)),
74-
'Added row does not fit the columns in the table',
75-
);
7691

77-
Object.entries(row).forEach(([columnName, value]) => {
92+
const rowValues =
93+
row instanceof Map ? [...row.entries()] : Object.entries(row);
94+
95+
for (const [columnName, cellValue] of rowValues) {
7896
const column = this.columns.get(columnName);
79-
assert(column !== undefined);
97+
assert(column !== undefined, 'All added rows fit columns in the table');
8098

8199
assert(
82-
ERROR_TYPEGUARD(value) ||
83-
column.valueType.isInternalValidValueRepresentation(value),
100+
ERROR_TYPEGUARD(cellValue) ||
101+
column.valueType.isInternalValidValueRepresentation(cellValue),
84102
);
85-
column.values.push(value);
86-
});
87-
88-
this.numberOfRows++;
103+
column.values.push(cellValue);
104+
}
89105
}
90106

91107
getNumberOfRows(): number {
@@ -108,12 +124,7 @@ export class Table implements IOTypeImplementation<IOType.TABLE> {
108124
return this.columns.get(name);
109125
}
110126

111-
getRow(
112-
rowId: number,
113-
): Map<
114-
string,
115-
InternalValidValueRepresentation | InternalErrorValueRepresentation
116-
> {
127+
getRow(rowId: number): TableRow {
117128
const numberOfRows = this.getNumberOfRows();
118129
if (rowId >= numberOfRows) {
119130
throw new Error(
@@ -133,6 +144,26 @@ export class Table implements IOTypeImplementation<IOType.TABLE> {
133144
return row;
134145
}
135146

147+
findUnfullfilledRows(
148+
executionContext: ExecutionContext,
149+
onInvalidRow?: (rowIndex: number, row: TableRow) => void,
150+
): void {
151+
for (let rowIdx = 0; rowIdx < this.numberOfRows; rowIdx++) {
152+
const row = this.getRow(rowIdx);
153+
154+
const allConstraintsFulfilled = this.constraints.every((constraint) =>
155+
constraint.isValid(row, executionContext),
156+
);
157+
if (allConstraintsFulfilled) {
158+
continue;
159+
}
160+
161+
if (onInvalidRow !== undefined) {
162+
onInvalidRow(rowIdx, row);
163+
}
164+
}
165+
}
166+
136167
static generateDropTableStatement(tableName: string): string {
137168
return `DROP TABLE IF EXISTS "${tableName}";`;
138169
}
@@ -186,16 +217,19 @@ export class Table implements IOTypeImplementation<IOType.TABLE> {
186217
}
187218

188219
clone(): Table {
189-
const cloned = new Table();
190-
cloned.numberOfRows = this.numberOfRows;
191-
[...this.columns.entries()].forEach(([columnName, column]) => {
192-
cloned.addColumn(columnName, {
220+
const copiedColumns = new Map<string, TableColumn>();
221+
[...this.columns.entries()].map(([columnName, column]) => {
222+
copiedColumns.set(columnName, {
193223
values: cloneInternalValue(column.values),
194224
valueType: column.valueType,
195225
});
196226
});
197227

198-
return cloned;
228+
const copiedConstraints = this.constraints.map(
229+
(constraint) => new ConstraintExecutor(constraint.astNode),
230+
);
231+
232+
return new Table(this.numberOfRows, copiedColumns, copiedConstraints);
199233
}
200234

201235
acceptVisitor<R>(visitor: IoTypeVisitor<R>): R {

libs/execution/src/lib/types/value-types/value-representation-validity.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
ValueTypeVisitor,
2323
type ValuetypeAssignmentValuetype,
2424
type ValuetypeDefinitionValuetype,
25-
isConstraintDefinition,
25+
type InternalErrorValueRepresentation,
2626
} from '@jvalue/jayvee-language-server';
2727

2828
import { ConstraintExecutor } from '../../constraints';
@@ -33,13 +33,27 @@ export function isValidValueRepresentation(
3333
valueType: ValueType,
3434
context: ExecutionContext,
3535
): boolean {
36-
const visitor = new ValueRepresentationValidityVisitor(value, context);
36+
const values = new Map<string, InternalValidValueRepresentation>();
37+
values.set('value', value);
38+
const visitor = new ValueRepresentationValidityVisitor(values, context);
3739
return valueType.acceptVisitor(visitor);
3840
}
3941

42+
export function allValueRepresentationsValid(
43+
values: Map<string, InternalValidValueRepresentation>,
44+
atomicValueType: AtomicValueType,
45+
context: ExecutionContext,
46+
): boolean {
47+
const visitor = new ValueRepresentationValidityVisitor(values, context);
48+
return atomicValueType.acceptVisitor(visitor);
49+
}
50+
4051
class ValueRepresentationValidityVisitor extends ValueTypeVisitor<boolean> {
4152
constructor(
42-
private value: InternalValidValueRepresentation,
53+
private values: Map<
54+
string,
55+
InternalValidValueRepresentation | InternalErrorValueRepresentation
56+
>,
4357
private context: ExecutionContext,
4458
) {
4559
super();
@@ -58,13 +72,9 @@ class ValueRepresentationValidityVisitor extends ValueTypeVisitor<boolean> {
5872
for (const constraint of valueType.getConstraints()) {
5973
this.context.enterNode(constraint);
6074

61-
const valueFulfilledConstraint = isConstraintDefinition(constraint)
62-
? new ConstraintExecutor(constraint).isValid(this.value, this.context)
63-
: new ConstraintExecutor(constraint).isValid(
64-
this.value,
65-
this.context,
66-
valueType.getProperties(),
67-
);
75+
const valueFulfilledConstraint = new ConstraintExecutor(
76+
constraint,
77+
).isValid(this.values, this.context);
6878

6979
this.context.exitNode(constraint);
7080

@@ -125,6 +135,8 @@ class ValueRepresentationValidityVisitor extends ValueTypeVisitor<boolean> {
125135
}
126136

127137
private isValidForPrimitiveValuetype(valueType: PrimitiveValueType): boolean {
128-
return valueType.isInternalValidValueRepresentation(this.value);
138+
const value = this.values.get('value');
139+
assert(value !== undefined);
140+
return valueType.isInternalValidValueRepresentation(value);
129141
}
130142
}

libs/execution/test/utils/test-infrastructure-util.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export function constructTable(
8989
columns: TableColumnDefinition[],
9090
numberOfRows: number,
9191
): Table {
92-
const table = new Table(numberOfRows);
93-
columns.forEach((col) => table.addColumn(col.columnName, col.column));
94-
return table;
92+
const columnMap = new Map<string, TableColumn>();
93+
columns.forEach((col) => columnMap.set(col.columnName, col.column));
94+
return new Table(numberOfRows, columnMap, []);
9595
}

0 commit comments

Comments
 (0)