Line data Source code
1 : #ifndef BIGQUERY_EMULATOR_BACKEND_ENGINE_SEMANTIC_FRAME_STACK_H_
2 : #define BIGQUERY_EMULATOR_BACKEND_ENGINE_SEMANTIC_FRAME_STACK_H_
3 :
4 : // Name-keyed `Value` frame stack shared by the script driver and
5 : // the UDF / TVF invocation surface.
6 : //
7 : // Two contexts in the semantic executor need a stack of name ->
8 : // `Value` frames with case-insensitive identifier matching:
9 : //
10 : // * BigQuery scripting (`DECLARE`, `SET`, `BEGIN ... END`,
11 : // `IF` / `WHILE` / ...). Each `BEGIN ... END` block pushes a
12 : // frame, declarations register fresh bindings in the innermost
13 : // frame, and the innermost matching binding wins for `Set` /
14 : // `Lookup`. Owned by
15 : // `docs/ENGINE_POLICY.md`.
16 : // * SQL UDF / TVF invocation. Each call binds the argument list
17 : // into a fresh frame the body's `ResolvedArgumentRef` /
18 : // `ResolvedRelationArgumentScan` nodes resolve against. Owned by
19 : // `docs/ENGINE_POLICY.md`.
20 : //
21 : // Both contexts share the same primitive: a stack of frames keyed
22 : // on the analyzer-lowered identifier, with `Value` payloads. The
23 : // type lives in `backend/engine/semantic/` (one level above
24 : // `script/`) so the UDF / TVF call sites can depend on it without
25 : // taking a transitive dependency on the script-statement
26 : // translation units.
27 : //
28 : // Thread-safety: a `FrameStack` is owned by exactly one in-flight
29 : // invocation (script run or UDF / TVF call). Callers do not share
30 : // it across threads. The owning driver constructs one per run and
31 : // destroys it when the run ends.
32 :
33 : #include <string>
34 : #include <vector>
35 :
36 : #include "absl/container/flat_hash_map.h"
37 : #include "absl/status/status.h"
38 : #include "absl/status/statusor.h"
39 : #include "absl/strings/string_view.h"
40 : #include "backend/engine/semantic/value.h"
41 :
42 : namespace bigquery_emulator {
43 : namespace backend {
44 : namespace engine {
45 : namespace semantic {
46 :
47 : // A stack of `(name -> Value)` frames. The constructor opens the
48 : // outer (global) frame so callers do not have to special-case
49 : // top-level declarations; `frame_count()` is therefore always >= 1
50 : // once construction returns.
51 : //
52 : // BigQuery identifier comparison is case-insensitive (see the
53 : // BigQuery scripting reference for `DECLARE` and the UDF /
54 : // argument resolution rules); names are lower-cased on `Declare` /
55 : // `Lookup` / `Set` so `DECLARE x` followed by `SET X = 1` (or a
56 : // UDF body referencing `X` when the analyzer canonicalized the
57 : // argument name to `x`) resolves to the same binding.
58 : class FrameStack {
59 : public:
60 : FrameStack();
61 : ~FrameStack();
62 :
63 : FrameStack(const FrameStack&) = delete;
64 : FrameStack& operator=(const FrameStack&) = delete;
65 :
66 : // Open a new frame. Bindings declared in this frame disappear
67 : // when `PopFrame` is called.
68 : void PushFrame();
69 :
70 : // Close the most recent frame. Returns `kFailedPrecondition` if
71 : // there is no frame above the outer (global) frame to pop;
72 : // popping the outer frame is a programmer error (the call stack
73 : // would no longer support `Declare`).
74 : absl::Status PopFrame();
75 :
76 : // Number of frames currently on the stack (always >= 1).
77 4 : size_t frame_count() const {
78 4 : return frames_.size();
79 4 : }
80 :
81 : // Register a fresh binding in the innermost frame. Returns
82 : // `kAlreadyExists` if `name` is already bound in that frame
83 : // (BigQuery rejects `DECLARE x` after a previous `DECLARE x` in
84 : // the same block, and UDF / TVF signatures may not declare the
85 : // same argument name twice).
86 : absl::Status Declare(absl::string_view name, Value value);
87 :
88 : // Update the innermost binding for `name`. Returns `kNotFound`
89 : // when no frame on the stack has a binding for `name`.
90 : absl::Status Set(absl::string_view name, Value value);
91 :
92 : // Read the innermost binding for `name`. Returns `kNotFound`
93 : // when no frame on the stack has a binding for `name`.
94 : absl::StatusOr<Value> Lookup(absl::string_view name) const;
95 :
96 : // Convenience: does any frame on the stack have a binding for
97 : // `name`?
98 : bool Has(absl::string_view name) const;
99 :
100 : // All bindings visible from the current stack position. Inner
101 : // frames shadow outer frames (same resolution order as `Lookup`).
102 : absl::flat_hash_map<std::string, Value> VisibleBindings() const;
103 :
104 : private:
105 : // Each frame is a flat map keyed on the analyzer-lowered name.
106 : // The payload is the current `Value`; updates via `Set`
107 : // overwrite the existing entry in the innermost frame that owns
108 : // the name (rather than allocating a fresh slot in the top
109 : // frame).
110 : using Frame = absl::flat_hash_map<std::string, Value>;
111 : std::vector<Frame> frames_{};
112 : };
113 :
114 : } // namespace semantic
115 : } // namespace engine
116 : } // namespace backend
117 : } // namespace bigquery_emulator
118 :
119 : #endif // BIGQUERY_EMULATOR_BACKEND_ENGINE_SEMANTIC_FRAME_STACK_H_
|