/
context.ts
127 lines (111 loc) · 4.18 KB
/
context.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { _ISchema, _ISelection, _IDb, OnStatementExecuted, nil, QueryError, _IStatement, IValue, Parameter } from '../interfaces-private.ts';
class StackOf<T> {
readonly stack: T[] = [];
constructor(private name: string) {
}
usingValue = <ret>(value: T, act: () => ret): ret => {
this.stack.push(value);
try {
return act();
} finally {
this.stack.pop();
}
}
get current(): T {
if (!this.stack.length) {
throw new Error(`No ${this.name} available`);
}
return this.stack[this.stack.length - 1];
}
get currentOrNil(): T | nil {
return this.stack[this.stack.length - 1];
}
}
const _selectionStack = new StackOf<_ISelection>('build context');
const _statementStack = new StackOf<_IStatement>('execution statement');
const _tempBindings = new StackOf<Map<string, _ISelection | 'no returning'>>('binding context');
const _parametersStack = new StackOf<Parameter[]>('parameter context');
const _nameResolver = new StackOf<INameResolver>('name resolver');
export interface INameResolver {
/** Try to resolve a name */
resolve(name: string): IValue | nil;
/** True if is isolated... meaning that one cannot fetch values from its parent */
readonly isolated: boolean;
}
interface IBuildContext {
readonly selection: _ISelection;
readonly db: _IDb;
readonly schema: _ISchema;
readonly onFinishExecution: (callback: OnStatementExecuted) => void
readonly getTempBinding: (name: string) => _ISelection | nil;
readonly setTempBinding: (name: string, boundTo: _ISelection) => void;
readonly getParameter: (nameOrPosition: string | number) => IValue | nil;
}
class Context implements IBuildContext {
get selection(): _ISelection {
return _selectionStack.current;
}
get db(): _IDb {
return _selectionStack.current.db;
}
get schema(): _ISchema {
// remove the concept of selection schema ?
// (does not make much sens, if you think about it)
return _selectionStack.current.ownerSchema;
}
onFinishExecution = (callback: OnStatementExecuted) => {
_statementStack.current.onExecuted(callback);
};
getTempBinding = (name: string) => {
const ret = _tempBindings.currentOrNil?.get(name);
if (ret === 'no returning') {
throw new QueryError(`WITH query "${name}" does not have a RETURNING clause`);
}
return ret;
};
setTempBinding = (name: string, boundTo: _ISelection) => {
if (_tempBindings.current.has(name)) {
throw new QueryError(`WITH query name "${name}" specified more than once`);
}
_tempBindings.current.set(name, boundTo.isExecutionWithNoResult ? 'no returning' : boundTo);
};
getParameter = (nameOrPosition: string | number) => {
const params = _parametersStack.currentOrNil;
if (!params) {
return null;
}
if (typeof nameOrPosition === 'number') {
const ret = params[nameOrPosition]?.value;
if (!ret) {
// not ideal... (duplicated error message)
throw new QueryError(`bind message supplies ${params.length} parameters, but prepared statement "" requires ${nameOrPosition}`, '08P01');
}
return ret;
}
return params.find(p => p.value.id === nameOrPosition)?.value;
}
}
const _buildCtx = new Context();
export function buildCtx(): IBuildContext {
return _buildCtx;
}
export const withSelection = _selectionStack.usingValue;
export const withStatement = _statementStack.usingValue;
export function withBindingScope<T>(act: () => T): T {
return _tempBindings.usingValue(new Map(), act);
}
export const withParameters = _parametersStack.usingValue;
export const withNameResolver = _nameResolver.usingValue;
export function resolveName(name: string): IValue | null {
for (let i = _nameResolver.stack.length - 1; i >= 0; i--) {
const resolver = _nameResolver.stack[i];
const found = resolver.resolve(name);
if (found) {
return found;
}
if (resolver.isolated) {
return null;
}
}
return null;
}