1#![cfg_attr(not(feature = "omit_metadata"), doc = include_str!("../../../docs/expression_gen.md"))]
159
160use std::borrow::Cow;
161use std::collections::HashMap;
162
163use serde::{Deserialize, Serialize};
164use ts_rs::TS;
165
166#[derive(Deserialize, Clone, PartialEq, Debug)]
167#[serde(untagged)]
168pub enum ExpressionsDeserde {
169 Array(Vec<String>),
170 Map(HashMap<String, String>),
171}
172
173#[derive(Deserialize, Serialize, Clone, PartialEq, Debug, Default, TS)]
174#[serde(from = "ExpressionsDeserde")]
175pub struct Expressions(pub HashMap<String, String>);
176
177impl std::ops::Deref for Expressions {
178 type Target = HashMap<String, String>;
179
180 fn deref(&self) -> &Self::Target {
181 &self.0
182 }
183}
184
185impl std::ops::DerefMut for Expressions {
186 fn deref_mut(&mut self) -> &mut Self::Target {
187 &mut self.0
188 }
189}
190
191fn upgrade_legacy_format(expressions: &[String]) -> HashMap<String, String> {
192 tracing::debug!("Legacy `expressions` format: {:?}", expressions);
193 expressions
194 .iter()
195 .map(|s| {
196 if let Some((name, expression)) = s.split_once('\n') {
197 if !expression.is_empty() && name.starts_with("//") {
198 (name.split_at(2).1.trim().to_owned(), expression.to_owned())
199 } else {
200 (s.to_owned(), s.to_owned())
201 }
202 } else {
203 (s.to_owned(), s.to_owned())
204 }
205 })
206 .collect::<HashMap<_, _>>()
207}
208
209impl From<ExpressionsDeserde> for Expressions {
210 fn from(value: ExpressionsDeserde) -> Self {
211 match value {
212 ExpressionsDeserde::Array(arr) => Self(upgrade_legacy_format(&arr)),
213 ExpressionsDeserde::Map(map) => Self(map),
214 }
215 }
216}
217
218#[derive(Clone, Debug, PartialEq, TS)]
219pub struct Expression<'a> {
220 pub name: Cow<'a, str>,
221 pub expression: Cow<'a, str>,
222}
223
224impl<'a> Expression<'a> {
225 pub fn new(name: Option<Cow<'a, str>>, expression: Cow<'a, str>) -> Self {
227 Self {
228 name: name.unwrap_or_else(|| expression.clone()),
229 expression,
230 }
231 }
232}
233
234impl<'a> FromIterator<Expression<'a>> for Expressions {
235 fn from_iter<T: IntoIterator<Item = Expression<'a>>>(iter: T) -> Self {
236 Self(
237 iter.into_iter()
238 .map(|x| (x.name.as_ref().to_owned(), x.expression.as_ref().to_owned()))
239 .collect(),
240 )
241 }
242}
243
244impl Expressions {
245 pub fn insert(&mut self, expr: &Expression) {
246 self.0.insert(
247 expr.name.as_ref().to_owned(),
248 expr.expression.as_ref().to_owned(),
249 );
250 }
251}
252
253#[doc(hidden)]
254#[derive(Serialize, Clone, Copy)]
255pub struct CompletionItemSuggestion {
256 pub label: &'static str,
257 pub insert_text: &'static str,
258 pub documentation: &'static str,
259}
260
261#[doc(hidden)]
262pub static COMPLETIONS: [CompletionItemSuggestion; 77] = [
263 CompletionItemSuggestion {
264 label: "var",
265 insert_text: "var ${1:x := 1}",
266 documentation: "Declare a new local variable",
267 },
268 CompletionItemSuggestion {
269 label: "abs",
270 insert_text: "abs(${1:x})",
271 documentation: "Absolute value of x",
272 },
273 CompletionItemSuggestion {
274 label: "avg",
275 insert_text: "avg(${1:x})",
276 documentation: "Average of all inputs",
277 },
278 CompletionItemSuggestion {
279 label: "bucket",
280 insert_text: "bucket(${1:x}, ${2:y})",
281 documentation: "Bucket x by y",
282 },
283 CompletionItemSuggestion {
284 label: "ceil",
285 insert_text: "ceil(${1:x})",
286 documentation: "Smallest integer >= x",
287 },
288 CompletionItemSuggestion {
289 label: "exp",
290 insert_text: "exp(${1:x})",
291 documentation: "Natural exponent of x (e ^ x)",
292 },
293 CompletionItemSuggestion {
294 label: "floor",
295 insert_text: "floor(${1:x})",
296 documentation: "Largest integer <= x",
297 },
298 CompletionItemSuggestion {
299 label: "frac",
300 insert_text: "frac(${1:x})",
301 documentation: "Fractional portion (after the decimal) of x",
302 },
303 CompletionItemSuggestion {
304 label: "iclamp",
305 insert_text: "iclamp(${1:x})",
306 documentation: "Inverse clamp x within a range",
307 },
308 CompletionItemSuggestion {
309 label: "inrange",
310 insert_text: "inrange(${1:x})",
311 documentation: "Returns whether x is within a range",
312 },
313 CompletionItemSuggestion {
314 label: "log",
315 insert_text: "log(${1:x})",
316 documentation: "Natural log of x",
317 },
318 CompletionItemSuggestion {
319 label: "log10",
320 insert_text: "log10(${1:x})",
321 documentation: "Base 10 log of x",
322 },
323 CompletionItemSuggestion {
324 label: "log1p",
325 insert_text: "log1p(${1:x})",
326 documentation: "Natural log of 1 + x where x is very small",
327 },
328 CompletionItemSuggestion {
329 label: "log2",
330 insert_text: "log2(${1:x})",
331 documentation: "Base 2 log of x",
332 },
333 CompletionItemSuggestion {
334 label: "logn",
335 insert_text: "logn(${1:x}, ${2:N})",
336 documentation: "Base N log of x where N >= 0",
337 },
338 CompletionItemSuggestion {
339 label: "max",
340 insert_text: "max(${1:x})",
341 documentation: "Maximum value of all inputs",
342 },
343 CompletionItemSuggestion {
344 label: "min",
345 insert_text: "min(${1:x})",
346 documentation: "Minimum value of all inputs",
347 },
348 CompletionItemSuggestion {
349 label: "mul",
350 insert_text: "mul(${1:x})",
351 documentation: "Product of all inputs",
352 },
353 CompletionItemSuggestion {
354 label: "percent_of",
355 insert_text: "percent_of(${1:x})",
356 documentation: "Percent y of x",
357 },
358 CompletionItemSuggestion {
359 label: "pow",
360 insert_text: "pow(${1:x}, ${2:y})",
361 documentation: "x to the power of y",
362 },
363 CompletionItemSuggestion {
364 label: "root",
365 insert_text: "root(${1:x}, ${2:N})",
366 documentation: "N-th root of x where N >= 0",
367 },
368 CompletionItemSuggestion {
369 label: "round",
370 insert_text: "round(${1:x})",
371 documentation: "Round x to the nearest integer",
372 },
373 CompletionItemSuggestion {
374 label: "sgn",
375 insert_text: "sgn(${1:x})",
376 documentation: "Sign of x: -1, 1, or 0",
377 },
378 CompletionItemSuggestion {
379 label: "sqrt",
380 insert_text: "sqrt(${1:x})",
381 documentation: "Square root of x",
382 },
383 CompletionItemSuggestion {
384 label: "sum",
385 insert_text: "sum(${1:x})",
386 documentation: "Sum of all inputs",
387 },
388 CompletionItemSuggestion {
389 label: "trunc",
390 insert_text: "trunc(${1:x})",
391 documentation: "Integer portion of x",
392 },
393 CompletionItemSuggestion {
394 label: "acos",
395 insert_text: "acos(${1:x})",
396 documentation: "Arc cosine of x in radians",
397 },
398 CompletionItemSuggestion {
399 label: "acosh",
400 insert_text: "acosh(${1:x})",
401 documentation: "Inverse hyperbolic cosine of x in radians",
402 },
403 CompletionItemSuggestion {
404 label: "asin",
405 insert_text: "asin(${1:x})",
406 documentation: "Arc sine of x in radians",
407 },
408 CompletionItemSuggestion {
409 label: "asinh",
410 insert_text: "asinh(${1:x})",
411 documentation: "Inverse hyperbolic sine of x in radians",
412 },
413 CompletionItemSuggestion {
414 label: "atan",
415 insert_text: "atan(${1:x})",
416 documentation: "Arc tangent of x in radians",
417 },
418 CompletionItemSuggestion {
419 label: "atanh",
420 insert_text: "atanh(${1:x})",
421 documentation: "Inverse hyperbolic tangent of x in radians",
422 },
423 CompletionItemSuggestion {
424 label: "cos",
425 insert_text: "cos(${1:x})",
426 documentation: "Cosine of x",
427 },
428 CompletionItemSuggestion {
429 label: "cosh",
430 insert_text: "cosh(${1:x})",
431 documentation: "Hyperbolic cosine of x",
432 },
433 CompletionItemSuggestion {
434 label: "cot",
435 insert_text: "cot(${1:x})",
436 documentation: "Cotangent of x",
437 },
438 CompletionItemSuggestion {
439 label: "sin",
440 insert_text: "sin(${1:x})",
441 documentation: "Sine of x",
442 },
443 CompletionItemSuggestion {
444 label: "sinc",
445 insert_text: "sinc(${1:x})",
446 documentation: "Sine cardinal of x",
447 },
448 CompletionItemSuggestion {
449 label: "sinh",
450 insert_text: "sinh(${1:x})",
451 documentation: "Hyperbolic sine of x",
452 },
453 CompletionItemSuggestion {
454 label: "tan",
455 insert_text: "tan(${1:x})",
456 documentation: "Tangent of x",
457 },
458 CompletionItemSuggestion {
459 label: "tanh",
460 insert_text: "tanh(${1:x})",
461 documentation: "Hyperbolic tangent of x",
462 },
463 CompletionItemSuggestion {
464 label: "deg2rad",
465 insert_text: "deg2rad(${1:x})",
466 documentation: "Convert x from degrees to radians",
467 },
468 CompletionItemSuggestion {
469 label: "deg2grad",
470 insert_text: "deg2grad(${1:x})",
471 documentation: "Convert x from degrees to gradians",
472 },
473 CompletionItemSuggestion {
474 label: "rad2deg",
475 insert_text: "rad2deg(${1:x})",
476 documentation: "Convert x from radians to degrees",
477 },
478 CompletionItemSuggestion {
479 label: "grad2deg",
480 insert_text: "grad2deg(${1:x})",
481 documentation: "Convert x from gradians to degrees",
482 },
483 CompletionItemSuggestion {
484 label: "concat",
485 insert_text: "concat(${1:x}, ${2:y})",
486 documentation: "Concatenate string columns and string literals, such \
487 as:\nconcat(\"State\" ', ', \"City\")",
488 },
489 CompletionItemSuggestion {
490 label: "order",
491 insert_text: "order(${1:input column}, ${2:value}, ...)",
492 documentation: "Generates a sort order for a string column based on the input order of \
493 the parameters, such as:\norder(\"State\", 'Texas', 'New York')",
494 },
495 CompletionItemSuggestion {
496 label: "upper",
497 insert_text: "upper(${1:x})",
498 documentation: "Uppercase of x",
499 },
500 CompletionItemSuggestion {
501 label: "lower",
502 insert_text: "lower(${1:x})",
503 documentation: "Lowercase of x",
504 },
505 CompletionItemSuggestion {
506 label: "hour_of_day",
507 insert_text: "hour_of_day(${1:x})",
508 documentation: "Return a datetime's hour of the day as a string",
509 },
510 CompletionItemSuggestion {
511 label: "month_of_year",
512 insert_text: "month_of_year(${1:x})",
513 documentation: "Return a datetime's month of the year as a string",
514 },
515 CompletionItemSuggestion {
516 label: "day_of_week",
517 insert_text: "day_of_week(${1:x})",
518 documentation: "Return a datetime's day of week as a string",
519 },
520 CompletionItemSuggestion {
521 label: "now",
522 insert_text: "now()",
523 documentation: "The current datetime in local time",
524 },
525 CompletionItemSuggestion {
526 label: "today",
527 insert_text: "today()",
528 documentation: "The current date in local time",
529 },
530 CompletionItemSuggestion {
531 label: "is_null",
532 insert_text: "is_null(${1:x})",
533 documentation: "Whether x is a null value",
534 },
535 CompletionItemSuggestion {
536 label: "is_not_null",
537 insert_text: "is_not_null(${1:x})",
538 documentation: "Whether x is not a null value",
539 },
540 CompletionItemSuggestion {
541 label: "not",
542 insert_text: "not(${1:x})",
543 documentation: "not x",
544 },
545 CompletionItemSuggestion {
546 label: "true",
547 insert_text: "true",
548 documentation: "Boolean value true",
549 },
550 CompletionItemSuggestion {
551 label: "false",
552 insert_text: "false",
553 documentation: "Boolean value false",
554 },
555 CompletionItemSuggestion {
556 label: "if",
557 insert_text: "if (${1:condition}) {} else if (${2:condition}) {} else {}",
558 documentation: "An if/else conditional, which evaluates a condition such as:\n if \
559 (\"Sales\" > 100) { true } else { false }",
560 },
561 CompletionItemSuggestion {
562 label: "for",
563 insert_text: "for (${1:expression}) {}",
564 documentation: "A for loop, which repeatedly evaluates an incrementing expression such \
565 as:\nvar x := 0; var y := 1; for (x < 10; x += 1) { y := x + y }",
566 },
567 CompletionItemSuggestion {
568 label: "string",
569 insert_text: "string(${1:x})",
570 documentation: "Converts the given argument to a string",
571 },
572 CompletionItemSuggestion {
573 label: "integer",
574 insert_text: "integer(${1:x})",
575 documentation: "Converts the given argument to a 32-bit integer. If the result \
576 over/under-flows, null is returned",
577 },
578 CompletionItemSuggestion {
579 label: "float",
580 insert_text: "float(${1:x})",
581 documentation: "Converts the argument to a float",
582 },
583 CompletionItemSuggestion {
584 label: "date",
585 insert_text: "date(${1:year}, ${1:month}, ${1:day})",
586 documentation: "Given a year, month (1-12) and day, create a new date",
587 },
588 CompletionItemSuggestion {
589 label: "datetime",
590 insert_text: "datetime(${1:timestamp})",
591 documentation: "Given a POSIX timestamp of milliseconds since epoch, create a new datetime",
592 },
593 CompletionItemSuggestion {
594 label: "boolean",
595 insert_text: "boolean(${1:x})",
596 documentation: "Converts the given argument to a boolean",
597 },
598 CompletionItemSuggestion {
599 label: "random",
600 insert_text: "random()",
601 documentation: "Returns a random float between 0 and 1, inclusive.",
602 },
603 CompletionItemSuggestion {
604 label: "match",
605 insert_text: "match(${1:string}, ${2:pattern})",
606 documentation: "Returns True if any part of string matches pattern, and False otherwise.",
607 },
608 CompletionItemSuggestion {
609 label: "match_all",
610 insert_text: "match_all(${1:string}, ${2:pattern})",
611 documentation: "Returns True if the whole string matches pattern, and False otherwise.",
612 },
613 CompletionItemSuggestion {
614 label: "search",
615 insert_text: "search(${1:string}, ${2:pattern})",
616 documentation: "Returns the substring that matches the first capturing group in pattern, \
617 or null if there are no capturing groups in the pattern or if there are \
618 no matches.",
619 },
620 CompletionItemSuggestion {
621 label: "indexof",
622 insert_text: "indexof(${1:string}, ${2:pattern}, ${3:output_vector})",
623 documentation: "Writes into index 0 and 1 of output_vector the start and end indices of \
624 the substring that matches the first capturing group in \
625 pattern.\n\nReturns true if there is a match and output was written, or \
626 false if there are no capturing groups in the pattern, if there are no \
627 matches, or if the indices are invalid.",
628 },
629 CompletionItemSuggestion {
630 label: "substring",
631 insert_text: "substring(${1:string}, ${2:start_idx}, ${3:length})",
632 documentation: "Returns a substring of string from start_idx with the given length. If \
633 length is not passed in, returns substring from start_idx to the end of \
634 the string. Returns null if the string or any indices are invalid.",
635 },
636 CompletionItemSuggestion {
637 label: "replace",
638 insert_text: "replace(${1:string}, ${2:pattern}, ${3:replacer})",
639 documentation: "Replaces the first match of pattern in string with replacer, or return \
640 the original string if no replaces were made.",
641 },
642 CompletionItemSuggestion {
643 label: "replace_all",
644 insert_text: "replace_all(${1:string}, ${2:pattern}, ${3:replacer})",
645 documentation: "Replaces all non-overlapping matches of pattern in string with replacer, \
646 or return the original string if no replaces were made.",
647 },
648 CompletionItemSuggestion {
649 label: "index",
650 insert_text: "index()",
651 documentation: "Looks up the index value of the current row",
652 },
653 CompletionItemSuggestion {
654 label: "col",
655 insert_text: "col(${1:string})",
656 documentation: "Looks up a column value by name",
657 },
658 CompletionItemSuggestion {
659 label: "vlookup",
660 insert_text: "vlookup(${1:string}, ${2:uint64})",
661 documentation: "Looks up a value in another column by index",
662 },
663];
664
665#[test]
666fn test_completions_insert_text_matches_label() {
667 for comp in COMPLETIONS {
668 let label = comp.label;
669 let insert_text = comp.insert_text;
670 assert!(
671 insert_text.starts_with(label),
672 "insert_text for label {label} does not start with {label}:\n {insert_text}"
673 );
674 }
675}