/Users/deen/code/yugabyte-db/src/yb/common/ql_expr.h
Line | Count | Source (jump to first uncovered line) |
1 | | //-------------------------------------------------------------------------------------------------- |
2 | | // Copyright (c) YugaByte, Inc. |
3 | | // |
4 | | // This module defines the ResultSet that YQL database returns to a query request. |
5 | | //-------------------------------------------------------------------------------------------------- |
6 | | |
7 | | #ifndef YB_COMMON_QL_EXPR_H_ |
8 | | #define YB_COMMON_QL_EXPR_H_ |
9 | | |
10 | | #include <boost/container/small_vector.hpp> |
11 | | |
12 | | #include <boost/optional/optional.hpp> |
13 | | |
14 | | #include "yb/bfql/tserver_opcodes.h" |
15 | | #include "yb/bfpg/tserver_opcodes.h" |
16 | | |
17 | | #include "yb/common/common_fwd.h" |
18 | | #include "yb/common/column_id.h" |
19 | | #include "yb/common/ql_value.h" |
20 | | |
21 | | #include "yb/gutil/casts.h" |
22 | | |
23 | | |
24 | | #include "yb/util/status.h" |
25 | | #include "yb/util/status_format.h" |
26 | | |
27 | | namespace yb { |
28 | | |
29 | | // TODO(neil) |
30 | | // - This should be maping directly from "int32_t" to QLValue. |
31 | | // using ValueMap = std::unordered_map<int32_t, const QLValuePB>; |
32 | | // - We should use shared_ptr for this map as we might multi-threading the execution process. |
33 | | |
34 | | // DocDB is using this map, so its code has to be updated before we can change this. |
35 | | // Slowing down our execution by calling constructor each time is not desired. |
36 | | |
37 | | // Map for easy lookup of column values of a row by the column id. This map is used in tserver |
38 | | // for saving the column values of a selected row to evaluate the WHERE and IF clauses. Since |
39 | | // we use the clauses in protobuf to evaluate, we will maintain the column values in QLValuePB |
40 | | // also to avoid conversion to and from QLValue. |
41 | | struct QLTableColumn { |
42 | | static constexpr int64_t kUninitializedWriteTime = std::numeric_limits<int64_t>::min(); |
43 | | |
44 | | QLValuePB value; |
45 | | int64_t ttl_seconds = 0; |
46 | | int64_t write_time = kUninitializedWriteTime; |
47 | | |
48 | | std::string ToString() const; |
49 | | }; |
50 | | |
51 | | class QLExprResultWriter; |
52 | | |
53 | | class QLExprResult { |
54 | | public: |
55 | | const QLValuePB& Value() const; |
56 | | |
57 | | void MoveToJsonb(common::Jsonb* out); |
58 | | |
59 | | void MoveTo(QLValuePB* out); |
60 | | |
61 | | QLValue& ForceNewValue(); |
62 | | |
63 | | QLExprResultWriter Writer(); |
64 | | |
65 | | bool IsNull() const; |
66 | | |
67 | | private: |
68 | | friend class QLExprResultWriter; |
69 | | |
70 | | QLValue value_; |
71 | | const QLValuePB* existing_value_ = nullptr; |
72 | | }; |
73 | | |
74 | | class QLExprResultWriter { |
75 | | public: |
76 | 282M | explicit QLExprResultWriter(QLExprResult* result) : result_(result) { |
77 | 282M | result_->existing_value_ = nullptr; |
78 | 282M | } |
79 | | |
80 | | void SetNull(); |
81 | | |
82 | | void SetExisting(const QLValuePB* existing_value); |
83 | | |
84 | | QLValue& NewValue(); |
85 | | private: |
86 | | QLExprResult* result_; |
87 | | }; |
88 | | |
89 | | class QLTableRow { |
90 | | public: |
91 | | // Public types. |
92 | | typedef std::shared_ptr<QLTableRow> SharedPtr; |
93 | | typedef std::shared_ptr<const QLTableRow> SharedPtrConst; |
94 | | |
95 | | static const QLTableRow& empty_row(); |
96 | | |
97 | | // Check if row is empty (no column). |
98 | 5.39M | bool IsEmpty() const { return num_assigned_ == 0; } |
99 | | |
100 | | // Get column count. |
101 | | size_t ColumnCount() const; |
102 | | |
103 | | // Clear the row. |
104 | | void Clear(); |
105 | | |
106 | | // Compare column value between two rows. |
107 | | bool MatchColumn(ColumnIdRep col_id, const QLTableRow& source) const; |
108 | 40.2k | bool MatchColumn(const ColumnId& col, const QLTableRow& source) const { |
109 | 40.2k | return MatchColumn(col.rep(), source); |
110 | 40.2k | } |
111 | | |
112 | | // Allocate column in a map to cache its value, ttl, and writetime. |
113 | | QLTableColumn& AllocColumn(ColumnIdRep col_id); |
114 | 265M | QLTableColumn& AllocColumn(const ColumnId& col) { return AllocColumn(col.rep()); } |
115 | | |
116 | | QLTableColumn& AllocColumn(ColumnIdRep col_id, const QLValue& ql_value); |
117 | 21.4M | QLTableColumn& AllocColumn(const ColumnId& col, const QLValue& ql_value) { |
118 | 21.4M | return AllocColumn(col.rep(), ql_value); |
119 | 21.4M | } |
120 | | QLTableColumn& AllocColumn(ColumnIdRep col_id, const QLValuePB& ql_value); |
121 | 5.09M | QLTableColumn& AllocColumn(const ColumnId& col, const QLValuePB& ql_value) { |
122 | 5.09M | return AllocColumn(col.rep(), ql_value); |
123 | 5.09M | } |
124 | | |
125 | | QLTableColumn& AllocColumn(ColumnIdRep col_id, QLValuePB&& ql_value); |
126 | 77.3k | QLTableColumn& AllocColumn(const ColumnId& col, QLValuePB&& ql_value) { |
127 | 77.3k | return AllocColumn(col.rep(), std::move(ql_value)); |
128 | 77.3k | } |
129 | | |
130 | | // Copy column-value from 'source' to the 'col_id' entry in the cached column-map. |
131 | | void CopyColumn(ColumnIdRep col_id, const QLTableRow& source); |
132 | 419 | void CopyColumn(const ColumnId& col, const QLTableRow& source) { |
133 | 419 | return CopyColumn(col.rep(), source); |
134 | 419 | } |
135 | | |
136 | | // Get a column TTL. |
137 | | CHECKED_STATUS GetTTL(ColumnIdRep col_id, int64_t *ttl_seconds) const; |
138 | | |
139 | | // Get a column WriteTime. |
140 | | CHECKED_STATUS GetWriteTime(ColumnIdRep col_id, int64_t *write_time) const; |
141 | | |
142 | | // Copy the column value of the given ID to output parameter "column". |
143 | | CHECKED_STATUS GetValue(ColumnIdRep col_id, QLValue *column) const; |
144 | | CHECKED_STATUS GetValue(const ColumnId& col, QLValue *column) const; |
145 | | boost::optional<const QLValuePB&> GetValue(ColumnIdRep col_id) const; |
146 | 8.95M | boost::optional<const QLValuePB&> GetValue(const ColumnId& col) const { |
147 | 8.95M | return GetValue(col.rep()); |
148 | 8.95M | } |
149 | | |
150 | | // Predicate if given column is specified in the row. |
151 | | // NOTE: This returns true if column is specified even when its value is NULL. |
152 | | bool IsColumnSpecified(ColumnIdRep col_id) const; |
153 | | |
154 | | // Clear the column value. |
155 | | void MarkTombstoned(ColumnIdRep col_id); |
156 | 39 | void MarkTombstoned(const ColumnId& col) { |
157 | 39 | return MarkTombstoned(col.rep()); |
158 | 39 | } |
159 | | |
160 | | // Get the column value in PB format. |
161 | | CHECKED_STATUS ReadColumn(ColumnIdRep col_id, QLExprResultWriter result_writer) const; |
162 | | const QLValuePB* GetColumn(ColumnIdRep col_id) const; |
163 | | CHECKED_STATUS ReadSubscriptedColumn(const QLSubscriptedColPB& subcol, |
164 | | const QLValuePB& index, |
165 | | QLExprResultWriter result_writer) const; |
166 | | |
167 | | // For testing only (no status check). |
168 | 0 | const QLTableColumn& TestValue(ColumnIdRep col_id) const { |
169 | 0 | return *FindColumn(col_id); |
170 | 0 | } |
171 | 0 | const QLTableColumn& TestValue(const ColumnId& col) const { |
172 | 0 | return *FindColumn(col.rep()); |
173 | 0 | } |
174 | | |
175 | | std::string ToString() const; |
176 | | std::string ToString(const Schema& schema) const; |
177 | | |
178 | | private: |
179 | | // Return kInvalidIndex when column index is unknown. |
180 | | size_t ColumnIndex(ColumnIdRep col_id) const; |
181 | | const QLTableColumn* FindColumn(ColumnIdRep col_id) const; |
182 | | Result<const QLTableColumn&> Column(ColumnIdRep col_id) const; |
183 | | // Appends new entry to values_ and assigned_ fields. |
184 | | QLTableColumn& AppendColumn(); |
185 | | |
186 | | // Map from column id to index in values_ and assigned_ vectors. |
187 | | // For columns from [kFirstColumnId; kFirstColumnId + kPreallocatedSize) we don't use |
188 | | // this field and map them directly. |
189 | | // I.e. column with id kFirstColumnId will have index 0 etc. |
190 | | // We are using unsigned int as map value and std::numeric_limits<size_t>::max() as invalid |
191 | | // column. |
192 | | // This way, the compiler would understand that this invalid value could never be stored in the |
193 | | // map and optimize away the comparison with it when inlining the ColumnIndex function call. |
194 | | std::unordered_map<ColumnIdRep, unsigned int> column_id_to_index_; |
195 | | |
196 | | static constexpr size_t kPreallocatedSize = 8; |
197 | | static constexpr ColumnIdRep kFirstNonPreallocatedColumnId = |
198 | | kFirstColumnIdRep + static_cast<ColumnIdRep>(kPreallocatedSize); |
199 | | |
200 | | // The two following vectors will be of the same size. |
201 | | // We use separate fields to achieve the following features: |
202 | | // 1) Fast way to cleanup row, just by setting assigned to false with one call. |
203 | | // 2) Avoid destroying values_, so they would be able to reuse allocated storage during row reuse. |
204 | | boost::container::small_vector<QLTableColumn, kPreallocatedSize> values_; |
205 | | boost::container::small_vector<bool, kPreallocatedSize> assigned_; |
206 | | size_t num_assigned_ = 0; |
207 | | }; |
208 | | |
209 | | class QLExprExecutor { |
210 | | public: |
211 | | // Public types. |
212 | | typedef std::shared_ptr<QLExprExecutor> SharedPtr; |
213 | | typedef std::shared_ptr<const QLExprExecutor> SharedPtrConst; |
214 | | |
215 | | // Constructor. |
216 | | // TODO(neil) Investigate to see if constructor should save some parameters as members since |
217 | | // we pass the same parameter over & over again when calling function recursively. |
218 | 21.5M | QLExprExecutor() { } |
219 | 21.5M | virtual ~QLExprExecutor() { } |
220 | | |
221 | | //------------------------------------------------------------------------------------------------ |
222 | | // CQL Support. |
223 | | |
224 | | // Get TServer opcode. |
225 | | yb::bfql::TSOpcode GetTSWriteInstruction(const QLExpressionPB& ql_expr) const; |
226 | | |
227 | | // Evaluate the given QLExpressionPB. |
228 | | CHECKED_STATUS EvalExpr(const QLExpressionPB& ql_expr, |
229 | | const QLTableRow& table_row, |
230 | | QLExprResultWriter result_writer, |
231 | | const Schema *schema = nullptr); |
232 | | |
233 | | // Evaluate the given QLExpressionPB (if needed) and replace its content with the result. |
234 | | CHECKED_STATUS EvalExpr(QLExpressionPB* ql_expr, |
235 | | const QLTableRow& table_row, |
236 | | const Schema *schema = nullptr); |
237 | | |
238 | | // Read evaluated value from an expression. This is only useful for aggregate function. |
239 | | CHECKED_STATUS ReadExprValue(const QLExpressionPB& ql_expr, |
240 | | const QLTableRow& table_row, |
241 | | QLExprResultWriter result_writer); |
242 | | |
243 | | // Evaluate column reference. |
244 | | virtual CHECKED_STATUS EvalColumnRef(ColumnIdRep col_id, |
245 | | const QLTableRow* table_row, |
246 | | QLExprResultWriter result_writer); |
247 | | |
248 | | // Evaluate call to regular builtin operator. |
249 | | virtual CHECKED_STATUS EvalBFCall(const QLBCallPB& ql_expr, |
250 | | const QLTableRow& table_row, |
251 | | QLValue *result); |
252 | | |
253 | | // Evaluate call to tablet-server builtin operator. |
254 | | virtual CHECKED_STATUS EvalTSCall(const QLBCallPB& ql_expr, |
255 | | const QLTableRow& table_row, |
256 | | QLValue *result, |
257 | | const Schema *schema = nullptr); |
258 | | |
259 | | virtual CHECKED_STATUS ReadTSCallValue(const QLBCallPB& ql_expr, |
260 | | const QLTableRow& table_row, |
261 | | QLExprResultWriter result_writer); |
262 | | |
263 | | // Evaluate a boolean condition for the given row. |
264 | | virtual CHECKED_STATUS EvalCondition(const QLConditionPB& condition, |
265 | | const QLTableRow& table_row, |
266 | | bool* result); |
267 | | virtual CHECKED_STATUS EvalCondition(const QLConditionPB& condition, |
268 | | const QLTableRow& table_row, |
269 | | QLValue *result); |
270 | | |
271 | | //------------------------------------------------------------------------------------------------ |
272 | | // PGSQL Support. |
273 | | |
274 | | // Get TServer opcode. |
275 | | yb::bfpg::TSOpcode GetTSWriteInstruction(const PgsqlExpressionPB& ql_expr) const; |
276 | | |
277 | | // Evaluate the given QLExpressionPB. |
278 | | CHECKED_STATUS EvalExpr(const PgsqlExpressionPB& ql_expr, |
279 | | const QLTableRow* table_row, |
280 | | QLExprResultWriter result_writer, |
281 | | const Schema *schema = nullptr); |
282 | | |
283 | | CHECKED_STATUS EvalExpr(const PgsqlExpressionPB& ql_expr, |
284 | | const QLTableRow& table_row, |
285 | | QLExprResultWriter result_writer, |
286 | | const Schema *schema = nullptr); |
287 | | |
288 | | CHECKED_STATUS EvalExpr(const PgsqlExpressionPB& ql_expr, |
289 | | const QLTableRow& table_row, |
290 | | QLValuePB* result, |
291 | | const Schema *schema = nullptr); |
292 | | |
293 | | // Read evaluated value from an expression. This is only useful for aggregate function. |
294 | | CHECKED_STATUS ReadExprValue(const PgsqlExpressionPB& ql_expr, |
295 | | const QLTableRow& table_row, |
296 | | QLExprResultWriter result_writer); |
297 | | |
298 | | // Evaluate call to regular builtin operator. |
299 | | virtual CHECKED_STATUS EvalBFCall(const PgsqlBCallPB& ql_expr, |
300 | | const QLTableRow& table_row, |
301 | | QLValue *result); |
302 | | |
303 | | // Evaluate call to tablet-server builtin operator. |
304 | | virtual CHECKED_STATUS EvalTSCall(const PgsqlBCallPB& ql_expr, |
305 | | const QLTableRow& table_row, |
306 | | QLValue *result, |
307 | | const Schema *schema = nullptr); |
308 | | |
309 | | virtual CHECKED_STATUS ReadTSCallValue(const PgsqlBCallPB& ql_expr, |
310 | | const QLTableRow& table_row, |
311 | | QLExprResultWriter result_writer); |
312 | | |
313 | | // Evaluate a boolean condition for the given row. |
314 | | virtual CHECKED_STATUS EvalCondition(const PgsqlConditionPB& condition, |
315 | | const QLTableRow& table_row, |
316 | | bool* result); |
317 | | virtual CHECKED_STATUS EvalCondition(const PgsqlConditionPB& condition, |
318 | | const QLTableRow& table_row, |
319 | | QLValue *result); |
320 | | }; |
321 | | |
322 | | template <class Operands> |
323 | | CHECKED_STATUS EvalOperandsHelper( |
324 | 1.10M | QLExprExecutor* executor, const Operands& operands, const QLTableRow& table_row, int index) { |
325 | 1.10M | return Status::OK(); |
326 | 1.10M | } _ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEi Line | Count | Source | 324 | 1.10M | QLExprExecutor* executor, const Operands& operands, const QLTableRow& table_row, int index) { | 325 | 1.10M | return Status::OK(); | 326 | 1.10M | } |
_ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEi Line | Count | Source | 324 | 62 | QLExprExecutor* executor, const Operands& operands, const QLTableRow& table_row, int index) { | 325 | 62 | return Status::OK(); | 326 | 62 | } |
|
327 | | |
328 | | template <class Operands, class... Args> |
329 | | CHECKED_STATUS EvalOperandsHelper( |
330 | | QLExprExecutor* executor, const Operands& operands, const QLTableRow& table_row, int index, |
331 | 2.20M | QLExprResultWriter arg0, Args&&... args) { |
332 | 2.20M | RETURN_NOT_OK(executor->EvalExpr(operands[index], table_row, arg0)); |
333 | 2.20M | return EvalOperandsHelper(executor, operands, table_row, index + 1, std::forward<Args>(args)...); |
334 | 2.20M | } _ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEJEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEiNS_18QLExprResultWriterEDpOT0_ Line | Count | Source | 331 | 1.10M | QLExprResultWriter arg0, Args&&... args) { | 332 | 1.10M | RETURN_NOT_OK(executor->EvalExpr(operands[index], table_row, arg0)); | 333 | 1.10M | return EvalOperandsHelper(executor, operands, table_row, index + 1, std::forward<Args>(args)...); | 334 | 1.10M | } |
_ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEJNS_18QLExprResultWriterEEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEiS6_DpOT0_ Line | Count | Source | 331 | 1.10M | QLExprResultWriter arg0, Args&&... args) { | 332 | 1.10M | RETURN_NOT_OK(executor->EvalExpr(operands[index], table_row, arg0)); | 333 | 1.10M | return EvalOperandsHelper(executor, operands, table_row, index + 1, std::forward<Args>(args)...); | 334 | 1.10M | } |
Unexecuted instantiation: _ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEJNS_18QLExprResultWriterES6_EEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEiS6_DpOT0_ _ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEJEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEiNS_18QLExprResultWriterEDpOT0_ Line | Count | Source | 331 | 62 | QLExprResultWriter arg0, Args&&... args) { | 332 | 62 | RETURN_NOT_OK(executor->EvalExpr(operands[index], table_row, arg0)); | 333 | 62 | return EvalOperandsHelper(executor, operands, table_row, index + 1, std::forward<Args>(args)...); | 334 | 62 | } |
_ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEJNS_18QLExprResultWriterEEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEiS6_DpOT0_ Line | Count | Source | 331 | 62 | QLExprResultWriter arg0, Args&&... args) { | 332 | 62 | RETURN_NOT_OK(executor->EvalExpr(operands[index], table_row, arg0)); | 333 | 62 | return EvalOperandsHelper(executor, operands, table_row, index + 1, std::forward<Args>(args)...); | 334 | 62 | } |
Unexecuted instantiation: _ZN2yb18EvalOperandsHelperIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEJNS_18QLExprResultWriterES6_EEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEiS6_DpOT0_ |
335 | | |
336 | | template <class Operands, class... Args> |
337 | | CHECKED_STATUS EvalOperands( |
338 | | QLExprExecutor* executor, const Operands& operands, const QLTableRow& table_row, |
339 | 1.10M | Args&&... args) { |
340 | 1.10M | if (operands.size() != sizeof...(Args)) { |
341 | 0 | return STATUS_FORMAT(InvalidArgument, "Wrong number of arguments, $0 expected but $1 found", |
342 | 0 | sizeof...(Args), operands.size()); |
343 | 0 | } |
344 | | |
345 | 1.10M | return EvalOperandsHelper(executor, operands, table_row, 0, std::forward<Args>(args)...); |
346 | 1.10M | } Unexecuted instantiation: _ZN2yb12EvalOperandsIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEJNS_18QLExprResultWriterEEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEDpOT0_ _ZN2yb12EvalOperandsIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEJNS_18QLExprResultWriterES6_EEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEDpOT0_ Line | Count | Source | 339 | 1.10M | Args&&... args) { | 340 | 1.10M | if (operands.size() != sizeof...(Args)) { | 341 | 0 | return STATUS_FORMAT(InvalidArgument, "Wrong number of arguments, $0 expected but $1 found", | 342 | 0 | sizeof...(Args), operands.size()); | 343 | 0 | } | 344 | | | 345 | 1.10M | return EvalOperandsHelper(executor, operands, table_row, 0, std::forward<Args>(args)...); | 346 | 1.10M | } |
Unexecuted instantiation: _ZN2yb12EvalOperandsIN6google8protobuf16RepeatedPtrFieldINS_14QLExpressionPBEEEJNS_18QLExprResultWriterES6_S6_EEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEDpOT0_ Unexecuted instantiation: _ZN2yb12EvalOperandsIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEJNS_18QLExprResultWriterEEEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEDpOT0_ _ZN2yb12EvalOperandsIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEJNS_18QLExprResultWriterES6_EEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEDpOT0_ Line | Count | Source | 339 | 62 | Args&&... args) { | 340 | 62 | if (operands.size() != sizeof...(Args)) { | 341 | 0 | return STATUS_FORMAT(InvalidArgument, "Wrong number of arguments, $0 expected but $1 found", | 342 | 0 | sizeof...(Args), operands.size()); | 343 | 0 | } | 344 | | | 345 | 62 | return EvalOperandsHelper(executor, operands, table_row, 0, std::forward<Args>(args)...); | 346 | 62 | } |
Unexecuted instantiation: _ZN2yb12EvalOperandsIN6google8protobuf16RepeatedPtrFieldINS_17PgsqlExpressionPBEEEJNS_18QLExprResultWriterES6_S6_EEENS_6StatusEPNS_14QLExprExecutorERKT_RKNS_10QLTableRowEDpOT0_ |
347 | | |
348 | | } // namespace yb |
349 | | |
350 | | #endif // YB_COMMON_QL_EXPR_H_ |