@malloydata/malloy-tests 0.0.312 → 0.0.313

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -21,14 +21,14 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@jest/globals": "^29.4.3",
24
- "@malloydata/db-bigquery": "0.0.312",
25
- "@malloydata/db-duckdb": "0.0.312",
26
- "@malloydata/db-postgres": "0.0.312",
27
- "@malloydata/db-snowflake": "0.0.312",
28
- "@malloydata/db-trino": "0.0.312",
29
- "@malloydata/malloy": "0.0.312",
30
- "@malloydata/malloy-tag": "0.0.312",
31
- "@malloydata/render": "0.0.312",
24
+ "@malloydata/db-bigquery": "0.0.313",
25
+ "@malloydata/db-duckdb": "0.0.313",
26
+ "@malloydata/db-postgres": "0.0.313",
27
+ "@malloydata/db-snowflake": "0.0.313",
28
+ "@malloydata/db-trino": "0.0.313",
29
+ "@malloydata/malloy": "0.0.313",
30
+ "@malloydata/malloy-tag": "0.0.313",
31
+ "@malloydata/render": "0.0.313",
32
32
  "events": "^3.3.0",
33
33
  "jsdom": "^22.1.0",
34
34
  "luxon": "^2.4.0",
@@ -38,5 +38,5 @@
38
38
  "@types/jsdom": "^21.1.1",
39
39
  "@types/luxon": "^2.4.0"
40
40
  },
41
- "version": "0.0.312"
41
+ "version": "0.0.313"
42
42
  }
@@ -421,55 +421,78 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
421
421
  });
422
422
  });
423
423
 
424
- type TL = 'timeLiteral';
425
-
426
424
  describe('temporal filters', () => {
425
+ function tsLit(at: LuxonDateTime): string {
426
+ const typeDef: {type: 'timestamp' | 'date'} = {type: 'timestamp'};
427
+ const node: {node: 'timeLiteral'} = {node: 'timeLiteral'};
428
+ const timeStr = at.toUTC().toFormat(fTimestamp);
429
+ const n = {...node, typeDef, literal: timeStr};
430
+ return db.dialect.sqlLiteralTime({}, n);
431
+ }
427
432
  function lit(t: string, type: 'timestamp' | 'date'): string {
428
433
  const typeDef: {type: 'timestamp' | 'date'} = {type};
429
- const timeLiteral: TL = 'timeLiteral';
430
- const n = {
431
- node: timeLiteral,
432
- typeDef,
433
- literal: t,
434
- };
434
+ const node: {node: 'timeLiteral'} = {node: 'timeLiteral'};
435
+ const n = {...node, typeDef, literal: t};
435
436
  return db.dialect.sqlLiteralTime({}, n);
436
437
  }
437
438
 
438
439
  const fTimestamp = 'yyyy-LL-dd HH:mm:ss';
439
440
  const fDate = 'yyyy-LL-dd';
440
441
 
442
+ const inRange = [{n: 'first'}, {n: 'last'}];
443
+ const notInRange = [{n: 'before'}, {n: 'post-range'}];
444
+
441
445
  /**
442
- * Create a source for testing a range. It will have five rows
446
+ * Create a query for testing temporal filters with better timezone handling.
447
+ * Returns a complete Malloy query string with the filter and timezone in the same segment.
448
+ * Result will have five rows:
443
449
  * { t: 1 second before start, n: 'before' }
444
450
  * { t: start, n: 'first' }
445
451
  * { t: 1 second before end, n: 'last' }
446
452
  * { t: end, n: 'post-range' }
447
453
  * { t: NULL n: 'z-null' }
448
454
  * Use malloyResultMatches(range, inRange) or (range, notInRange)
455
+ *
456
+ * If a timezone is provided then ...
457
+ * - the start and end times are considered to be in that timezone
458
+ * - the query generated will include a timezone: directive
459
+ * - the filter expression is evaluated in that timezone
449
460
  */
450
- const inRange = [{n: 'first'}, {n: 'last'}];
451
- const notInRange = [{n: 'before'}, {n: 'post-range'}];
452
- function mkRange(start: string, end: string) {
453
- const begin = LuxonDateTime.fromFormat(start, fTimestamp);
461
+ function mkRangeQuery(
462
+ filterExpr: string,
463
+ start: string,
464
+ end: string,
465
+ queryTimezone?: string
466
+ ): string {
467
+ const zone = queryTimezone ?? 'UTC';
468
+
469
+ // Convert the civil time to the desired timezone
470
+ const begin = LuxonDateTime.fromFormat(start, fTimestamp, {zone});
471
+ const endTime = LuxonDateTime.fromFormat(end, fTimestamp, {zone});
472
+
454
473
  const b4 = begin.minus({second: 1});
455
- const last = lit(
456
- LuxonDateTime.fromFormat(end, fTimestamp)
457
- .minus({second: 1})
458
- .toFormat(fTimestamp),
459
- 'timestamp'
460
- );
461
- const before = lit(b4.toFormat(fTimestamp), 'timestamp');
462
- const rangeModel = `
463
- query: range is ${dbName}.sql("""
464
- SELECT ${before} AS ${q`t`}, 'before' AS ${q`n`}
465
- UNION ALL SELECT ${lit(start, 'timestamp')}, 'first'
466
- UNION ALL SELECT ${last} , 'last'
467
- UNION ALL SELECT ${lit(end, 'timestamp')}, 'post-range'
474
+ const last = endTime.minus({second: 1});
475
+
476
+ const timezoneClause = queryTimezone
477
+ ? `timezone: '${queryTimezone}';`
478
+ : '';
479
+
480
+ return `
481
+ run: ${dbName}.sql("""
482
+ SELECT ${tsLit(b4)} AS ${q`t`}, 'before' AS ${q`n`}
483
+ UNION ALL SELECT ${tsLit(begin)}, 'first'
484
+ UNION ALL SELECT ${tsLit(last)} , 'last'
485
+ UNION ALL SELECT ${tsLit(endTime)}, 'post-range'
468
486
  UNION ALL SELECT NULL, 'z-null'
469
- """)
470
- -> {select: *; order_by: n}`;
471
- return db.loadModel(rangeModel);
487
+ """) -> {
488
+ ${timezoneClause}
489
+ where: t ~ ${filterExpr}
490
+ select: t, n
491
+ order_by: n
492
+ }
493
+ `;
472
494
  }
495
+
473
496
  function mkDateRange(start: string, end: string) {
474
497
  const begin = LuxonDateTime.fromFormat(start, fDate);
475
498
  const b4 = begin.minus({day: 1});
@@ -499,9 +522,12 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
499
522
  /**
500
523
  * All the relative time tests need a way to set what time it is now
501
524
  */
502
- function nowIs(timeStr: string) {
525
+ function nowIs(nowStr: string, zone = 'UTC') {
503
526
  const spyNow = jest.spyOn(db.dialect, 'sqlNowExpr');
504
- spyNow.mockImplementation(() => lit(timeStr, 'timestamp'));
527
+ spyNow.mockImplementation(() => {
528
+ const utcTime = LuxonDateTime.fromFormat(nowStr, fTimestamp, {zone});
529
+ return tsLit(utcTime);
530
+ });
505
531
  }
506
532
  afterEach(() => jest.restoreAllMocks());
507
533
 
@@ -525,110 +551,144 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
525
551
  });
526
552
  test('2 days ago', async () => {
527
553
  nowIs('2001-01-15 12:00:00');
528
- const range = mkRange('2001-01-13 00:00:00', '2001-01-14 00:00:00');
529
- await expect(`
530
- run: range + { where: t ~ f'2 days ago' }
531
- `).malloyResultMatches(range, inRange);
554
+ const rangeQuery = mkRangeQuery(
555
+ "f'2 days ago'",
556
+ '2001-01-13 00:00:00',
557
+ '2001-01-14 00:00:00'
558
+ );
559
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
532
560
  });
533
561
  test('2 days', async () => {
534
562
  nowIs('2001-01-15 12:00:00');
535
- const range = mkRange('2001-01-14 00:00:00', '2001-01-16 00:00:00');
536
- await expect(`
537
- run: range + { where: t ~ f'2 days' }
538
- `).malloyResultMatches(range, inRange);
563
+ const rangeQuery = mkRangeQuery(
564
+ "f'2 days'",
565
+ '2001-01-14 00:00:00',
566
+ '2001-01-16 00:00:00'
567
+ );
568
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
539
569
  });
540
570
  test('2 days from now', async () => {
541
571
  nowIs('2001-01-15 12:00:00');
542
- const range = mkRange('2001-01-17 00:00:00', '2001-01-18 00:00:00');
543
- await expect(`
544
- run: range + { where: t ~ f'2 days from now' }
545
- `).malloyResultMatches(range, inRange);
572
+ const rangeQuery = mkRangeQuery(
573
+ "f'2 days from now'",
574
+ '2001-01-17 00:00:00',
575
+ '2001-01-18 00:00:00'
576
+ );
577
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
546
578
  });
547
579
  test('2000 to 2001', async () => {
548
- const range = mkRange('2000-01-01 00:00:00', '2001-01-01 00:00:00');
549
- await expect(`
550
- run: range + { where: t ~ f'2000 to 2001' }
551
- `).malloyResultMatches(range, inRange);
580
+ const rangeQuery = mkRangeQuery(
581
+ "f'2000 to 2001'",
582
+ '2000-01-01 00:00:00',
583
+ '2001-01-01 00:00:00'
584
+ );
585
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
552
586
  });
553
587
  test('next 2 days', async () => {
554
588
  nowIs('2001-01-01 12:00:00');
555
- const range = mkRange('2001-01-02 00:00:00', '2001-01-04 00:00:00');
556
- await expect(`
557
- run: range + { where: t ~ f'next 2 days' }
558
- `).malloyResultMatches(range, inRange);
589
+ const rangeQuery = mkRangeQuery(
590
+ "f'next 2 days'",
591
+ '2001-01-02 00:00:00',
592
+ '2001-01-04 00:00:00'
593
+ );
594
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
559
595
  });
560
596
  test('last 2 months', async () => {
561
597
  nowIs('2001-01-01 12:00:00');
562
- const range = mkRange('2000-11-01 00:00:00', '2001-01-01 00:00:00');
563
- await expect(`
564
- run: range + { where: t ~ f'last 2 months' }
565
- `).malloyResultMatches(range, inRange);
598
+ const rangeQuery = mkRangeQuery(
599
+ "f'last 2 months'",
600
+ '2000-11-01 00:00:00',
601
+ '2001-01-01 00:00:00'
602
+ );
603
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
566
604
  });
567
605
  test('before y2k', async () => {
568
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
569
- await expect(`
570
- run: range + { where: t ~ f'before 2001' }
571
- `).malloyResultMatches(range, [{n: 'before'}]);
606
+ const rangeQuery = mkRangeQuery(
607
+ "f'before 2001'",
608
+ '2001-01-01 00:00:00',
609
+ '2002-01-01 00:00:00'
610
+ );
611
+ await expect(rangeQuery).malloyResultMatches(db, [{n: 'before'}]);
572
612
  });
573
613
  test('after y2k', async () => {
574
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
575
- await expect(`
576
- run: range + { where: t ~ f'after 2001' }
577
- `).malloyResultMatches(range, [{n: 'post-range'}]);
614
+ const rangeQuery = mkRangeQuery(
615
+ "f'after 2001'",
616
+ '2001-01-01 00:00:00',
617
+ '2002-01-01 00:00:00'
618
+ );
619
+ await expect(rangeQuery).malloyResultMatches(db, [{n: 'post-range'}]);
578
620
  });
579
621
  test('y2k for 1 minute', async () => {
580
- const range = mkRange('2001-01-01 00:00:00', '2001-01-01 00:01:00');
581
- await expect(`
582
- run: range + { where: t ~ f'2001 for 1 minute' }
583
- `).malloyResultMatches(range, inRange);
622
+ const rangeQuery = mkRangeQuery(
623
+ "f'2001 for 1 minute'",
624
+ '2001-01-01 00:00:00',
625
+ '2001-01-01 00:01:00'
626
+ );
627
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
584
628
  });
585
629
  test('y2k for 2 hour', async () => {
586
- const range = mkRange('2001-01-01 00:00:00', '2001-01-01 02:00:00');
587
- await expect(`
588
- run: range + { where: t ~ f'2001 for 2 hour' }
589
- `).malloyResultMatches(range, inRange);
630
+ const rangeQuery = mkRangeQuery(
631
+ "f'2001 for 2 hour'",
632
+ '2001-01-01 00:00:00',
633
+ '2001-01-01 02:00:00'
634
+ );
635
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
590
636
  });
591
637
  test('y2k for 1 day', async () => {
592
- const range = mkRange('2001-01-01 00:00:00', '2001-01-02 00:00:00');
593
- await expect(`
594
- run: range + { where: t ~ f'2001 for 1 day' }
595
- `).malloyResultMatches(range, inRange);
638
+ const rangeQuery = mkRangeQuery(
639
+ "f'2001 for 1 day'",
640
+ '2001-01-01 00:00:00',
641
+ '2001-01-02 00:00:00'
642
+ );
643
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
596
644
  });
597
645
  test('y2k for 1 week', async () => {
598
- const range = mkRange('2001-01-01 00:00:00', '2001-01-08 00:00:00');
599
- await expect(`
600
- run: range + { where: t ~ f'2001 for 1 week' }
601
- `).malloyResultMatches(range, inRange);
646
+ const rangeQuery = mkRangeQuery(
647
+ "f'2001 for 1 week'",
648
+ '2001-01-01 00:00:00',
649
+ '2001-01-08 00:00:00'
650
+ );
651
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
602
652
  });
603
653
  test('y2k for 1 month', async () => {
604
- const range = mkRange('2001-01-01 00:00:00', '2001-02-01 00:00:00');
605
- await expect(`
606
- run: range + { where: t ~ f'2001 for 1 month' }
607
- `).malloyResultMatches(range, inRange);
654
+ const rangeQuery = mkRangeQuery(
655
+ "f'2001 for 1 month'",
656
+ '2001-01-01 00:00:00',
657
+ '2001-02-01 00:00:00'
658
+ );
659
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
608
660
  });
609
661
  test('y2k for 1 quarter', async () => {
610
- const range = mkRange('2001-01-01 00:00:00', '2001-04-01 00:00:00');
611
- await expect(`
612
- run: range + { where: t ~ f'2001 for 1 quarter' }
613
- `).malloyResultMatches(range, inRange);
662
+ const rangeQuery = mkRangeQuery(
663
+ "f'2001 for 1 quarter'",
664
+ '2001-01-01 00:00:00',
665
+ '2001-04-01 00:00:00'
666
+ );
667
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
614
668
  });
615
669
  test('y2k for 1 year', async () => {
616
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
617
- await expect(`
618
- run: range + { where: t ~ f'2001 for 1 year' }
619
- `).malloyResultMatches(range, inRange);
670
+ const rangeQuery = mkRangeQuery(
671
+ "f'2001 for 1 year'",
672
+ '2001-01-01 00:00:00',
673
+ '2002-01-01 00:00:00'
674
+ );
675
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
620
676
  });
621
677
  test('null', async () => {
622
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
623
- await expect(`
624
- run: range + { where: t ~ f'null' }
625
- `).malloyResultMatches(range, [{n: 'z-null'}]);
678
+ const rangeQuery = mkRangeQuery(
679
+ "f'null'",
680
+ '2001-01-01 00:00:00',
681
+ '2002-01-01 00:00:00'
682
+ );
683
+ await expect(rangeQuery).malloyResultMatches(db, [{n: 'z-null'}]);
626
684
  });
627
685
  test('not null', async () => {
628
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
629
- await expect(`
630
- run: range + { where: t ~ f'not null'; order_by: n }
631
- `).malloyResultMatches(range, [
686
+ const rangeQuery = mkRangeQuery(
687
+ "f'not null'",
688
+ '2001-01-01 00:00:00',
689
+ '2002-01-01 00:00:00'
690
+ );
691
+ await expect(rangeQuery).malloyResultMatches(db, [
632
692
  {n: 'before'},
633
693
  {n: 'first'},
634
694
  {n: 'last'},
@@ -636,11 +696,12 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
636
696
  ]);
637
697
  });
638
698
  test('empty temporal filter', async () => {
639
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
640
- await expect(`
641
- # test.verbose
642
- run: range + { where: t ~ f''; order_by: n }
643
- `).malloyResultMatches(range, [
699
+ const rangeQuery = mkRangeQuery(
700
+ "f''",
701
+ '2001-01-01 00:00:00',
702
+ '2002-01-01 00:00:00'
703
+ );
704
+ await expect(rangeQuery).malloyResultMatches(db, [
644
705
  {n: 'before'},
645
706
  {n: 'first'},
646
707
  {n: 'last'},
@@ -649,46 +710,60 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
649
710
  ]);
650
711
  });
651
712
  test('year literal', async () => {
652
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
653
- await expect(`
654
- run: range + { where: t ~ f'2001' }
655
- `).malloyResultMatches(range, inRange);
713
+ const rangeQuery = mkRangeQuery(
714
+ "f'2001'",
715
+ '2001-01-01 00:00:00',
716
+ '2002-01-01 00:00:00'
717
+ );
718
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
656
719
  });
657
720
  test('not month literal', async () => {
658
- const range = mkRange('2001-06-01 00:00:00', '2001-07-01 00:00:00');
659
- await expect(`
660
- run: range + { where: t ~ f'not 2001-06' }
661
- `).malloyResultMatches(range, notInRange);
721
+ const rangeQuery = mkRangeQuery(
722
+ "f'not 2001-06'",
723
+ '2001-06-01 00:00:00',
724
+ '2001-07-01 00:00:00'
725
+ );
726
+ await expect(rangeQuery).malloyResultMatches(db, notInRange);
662
727
  });
663
728
  test('day literal', async () => {
664
- const range = mkRange('2001-06-15 00:00:00', '2001-06-16 00:00:00');
665
- await expect(`
666
- run: range + { where: t ~ f'2001-06-15' }
667
- `).malloyResultMatches(range, inRange);
729
+ const rangeQuery = mkRangeQuery(
730
+ "f'2001-06-15'",
731
+ '2001-06-15 00:00:00',
732
+ '2001-06-16 00:00:00'
733
+ );
734
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
668
735
  });
669
736
  test('hour literal', async () => {
670
- const range = mkRange('2001-02-03 04:00:00', '2001-02-03 05:00:00');
671
- await expect(`
672
- run: range + { where: t ~ f'2001-02-03 04' }
673
- `).malloyResultMatches(range, inRange);
737
+ const rangeQuery = mkRangeQuery(
738
+ "f'2001-02-03 04'",
739
+ '2001-02-03 04:00:00',
740
+ '2001-02-03 05:00:00'
741
+ );
742
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
674
743
  });
675
744
  test('minute literal', async () => {
676
- const range = mkRange('2001-02-03 04:05:00', '2001-02-03 04:06:00');
677
- await expect(`
678
- run: range + { where: t ~ f'2001-02-03 04:05' }
679
- `).malloyResultMatches(range, inRange);
745
+ const rangeQuery = mkRangeQuery(
746
+ "f'2001-02-03 04:05'",
747
+ '2001-02-03 04:05:00',
748
+ '2001-02-03 04:06:00'
749
+ );
750
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
680
751
  });
681
752
  test('quarter literal', async () => {
682
- const range = mkRange('2001-01-01 00:00:00', '2001-04-01 00:00:00');
683
- await expect(`
684
- run: range + { where: t ~ f'2001-Q1' }
685
- `).malloyResultMatches(range, inRange);
753
+ const rangeQuery = mkRangeQuery(
754
+ "f'2001-Q1'",
755
+ '2001-01-01 00:00:00',
756
+ '2001-04-01 00:00:00'
757
+ );
758
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
686
759
  });
687
760
  test('week literal', async () => {
688
- const range = mkRange('2023-01-01 00:00:00', '2023-01-08 00:00:00');
689
- await expect(`
690
- run: range + { where: t ~ f'2023-01-01-WK' }
691
- `).malloyResultMatches(range, inRange);
761
+ const rangeQuery = mkRangeQuery(
762
+ "f'2023-01-01-WK'",
763
+ '2023-01-01 00:00:00',
764
+ '2023-01-08 00:00:00'
765
+ );
766
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
692
767
  });
693
768
  test('full second literal', async () => {
694
769
  const eqtime = mkEqTime('2023-01-01 01:02:03');
@@ -704,163 +779,250 @@ describe.each(runtimes.runtimeList)('filter expressions %s', (dbName, db) => {
704
779
  });
705
780
  test('today', async () => {
706
781
  nowIs('2001-02-03 12:00:00');
707
- const range = mkRange('2001-02-03 00:00:00', '2001-02-04 00:00:00');
708
- await expect(`
709
- run: range + { where: t ~ f'today' }
710
- `).malloyResultMatches(range, inRange);
782
+ const rangeQuery = mkRangeQuery(
783
+ "f'today'",
784
+ '2001-02-03 00:00:00',
785
+ '2001-02-04 00:00:00'
786
+ );
787
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
711
788
  });
712
789
  test('yesterday', async () => {
713
790
  nowIs('2001-02-03 12:00:00');
714
- const range = mkRange('2001-02-02 00:00:00', '2001-02-03 00:00:00');
715
- await expect(`
716
- run: range + { where: t ~ f'yesterday' }
717
- `).malloyResultMatches(range, inRange);
791
+ const rangeQuery = mkRangeQuery(
792
+ "f'yesterday'",
793
+ '2001-02-02 00:00:00',
794
+ '2001-02-03 00:00:00'
795
+ );
796
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
718
797
  });
719
798
  test('tomorrow', async () => {
720
799
  nowIs('2001-02-03 12:00:00');
721
- const range = mkRange('2001-02-04 00:00:00', '2001-02-05 00:00:00');
722
- await expect(`
723
- run: range + { where: t ~ f'tomorrow' }
724
- `).malloyResultMatches(range, inRange);
800
+ const rangeQuery = mkRangeQuery(
801
+ "f'tomorrow'",
802
+ '2001-02-04 00:00:00',
803
+ '2001-02-05 00:00:00'
804
+ );
805
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
725
806
  });
726
807
  test('this week', async () => {
727
808
  nowIs('2023-01-03 00:00:00');
728
- const range = mkRange('2023-01-01 00:00:00', '2023-01-08 00:00:00');
729
- await expect(`
730
- run: range + { where: t ~ f'this week' }
731
- `).malloyResultMatches(range, inRange);
809
+ const rangeQuery = mkRangeQuery(
810
+ "f'this week'",
811
+ '2023-01-01 00:00:00',
812
+ '2023-01-08 00:00:00'
813
+ );
814
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
732
815
  });
733
816
  test('last month', async () => {
734
817
  nowIs('2001-02-01 12:00:00');
735
- const range = mkRange('2001-01-01 00:00:00', '2001-02-01 00:00:00');
736
- await expect(`
737
- run: range + { where: t ~ f'last month' }
738
- `).malloyResultMatches(range, inRange);
818
+ const rangeQuery = mkRangeQuery(
819
+ "f'last month'",
820
+ '2001-01-01 00:00:00',
821
+ '2001-02-01 00:00:00'
822
+ );
823
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
739
824
  });
740
825
  test('next quarter', async () => {
741
826
  nowIs('2001-01-02 12:00:00');
742
- const range = mkRange('2001-04-01 00:00:00', '2001-07-01 00:00:00');
743
- await expect(`
744
- run: range + { where: t ~ f'next quarter' }
745
- `).malloyResultMatches(range, inRange);
827
+ const rangeQuery = mkRangeQuery(
828
+ "f'next quarter'",
829
+ '2001-04-01 00:00:00',
830
+ '2001-07-01 00:00:00'
831
+ );
832
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
746
833
  });
747
834
  test('this year', async () => {
748
835
  nowIs('2001-01-02 12:00:00');
749
- const range = mkRange('2001-01-01 00:00:00', '2002-01-01 00:00:00');
750
- await expect(`
751
- run: range + { where: t ~ f'this year' }
752
- `).malloyResultMatches(range, inRange);
836
+ const rangeQuery = mkRangeQuery(
837
+ "f'this year'",
838
+ '2001-01-01 00:00:00',
839
+ '2002-01-01 00:00:00'
840
+ );
841
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
753
842
  });
754
843
  // 2023-01-01 is a sunday
755
844
  test('(last) sunday', async () => {
756
845
  nowIs('2023-01-03 00:00:00');
757
- const range = mkRange('2023-01-01 00:00:00', '2023-01-02 00:00:00');
758
- await expect(`
759
- run: range + { where: t ~ f'sunday' }
760
- `).malloyResultMatches(range, inRange);
846
+ const rangeQuery = mkRangeQuery(
847
+ "f'sunday'",
848
+ '2023-01-01 00:00:00',
849
+ '2023-01-02 00:00:00'
850
+ );
851
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
761
852
  });
762
853
  test('last monday', async () => {
763
854
  nowIs('2023-01-03 00:00:00');
764
- const range = mkRange('2023-01-02 00:00:00', '2023-01-03 00:00:00');
765
- await expect(`
766
- run: range + { where: t ~ f'last monday' }
767
- `).malloyResultMatches(range, inRange);
855
+ const rangeQuery = mkRangeQuery(
856
+ "f'last monday'",
857
+ '2023-01-02 00:00:00',
858
+ '2023-01-03 00:00:00'
859
+ );
860
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
768
861
  });
769
862
  test('last-tuesday', async () => {
770
863
  nowIs('2023-01-03 00:00:00');
771
- const range = mkRange('2022-12-27 00:00:00', '2022-12-28 00:00:00');
772
- await expect(`
773
- run: range + { where: t ~ f'tuesday' }
774
- `).malloyResultMatches(range, inRange);
864
+ const rangeQuery = mkRangeQuery(
865
+ "f'tuesday'",
866
+ '2022-12-27 00:00:00',
867
+ '2022-12-28 00:00:00'
868
+ );
869
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
775
870
  });
776
871
  test('last-wednesday', async () => {
777
872
  nowIs('2023-01-03 00:00:00');
778
- const range = mkRange('2022-12-28 00:00:00', '2022-12-29 00:00:00');
779
- await expect(`
780
- run: range + { where: t ~ f'wednesday' }
781
- `).malloyResultMatches(range, inRange);
873
+ const rangeQuery = mkRangeQuery(
874
+ "f'wednesday'",
875
+ '2022-12-28 00:00:00',
876
+ '2022-12-29 00:00:00'
877
+ );
878
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
782
879
  });
783
880
  test('last-thursday', async () => {
784
881
  nowIs('2023-01-03 00:00:00');
785
- const range = mkRange('2022-12-29 00:00:00', '2022-12-30 00:00:00');
786
- await expect(`
787
- run: range + { where: t ~ f'thursday' }
788
- `).malloyResultMatches(range, inRange);
882
+ const rangeQuery = mkRangeQuery(
883
+ "f'thursday'",
884
+ '2022-12-29 00:00:00',
885
+ '2022-12-30 00:00:00'
886
+ );
887
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
789
888
  });
790
889
  test('last-friday', async () => {
791
890
  nowIs('2023-01-03 00:00:00');
792
- const range = mkRange('2022-12-30 00:00:00', '2022-12-31 00:00:00');
793
- await expect(`
794
- run: range + { where: t ~ f'friday' }
795
- `).malloyResultMatches(range, inRange);
891
+ const rangeQuery = mkRangeQuery(
892
+ "f'friday'",
893
+ '2022-12-30 00:00:00',
894
+ '2022-12-31 00:00:00'
895
+ );
896
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
796
897
  });
797
898
  test('last saturday', async () => {
798
899
  nowIs('2023-01-03 00:00:00');
799
- const range = mkRange('2022-12-31 00:00:00', '2023-01-01 00:00:00');
800
- await expect(`
801
- run: range + { where: t ~ f'last saturday' }
802
- `).malloyResultMatches(range, inRange);
900
+ const rangeQuery = mkRangeQuery(
901
+ "f'last saturday'",
902
+ '2022-12-31 00:00:00',
903
+ '2023-01-01 00:00:00'
904
+ );
905
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
803
906
  });
804
907
  test('next sunday', async () => {
805
908
  nowIs('2023-01-03 00:00:00');
806
- const range = mkRange('2023-01-08 00:00:00', '2023-01-09 00:00:00');
807
- await expect(`
808
- run: range + { where: t ~ f'next sunday' }
809
- `).malloyResultMatches(range, inRange);
909
+ const rangeQuery = mkRangeQuery(
910
+ "f'next sunday'",
911
+ '2023-01-08 00:00:00',
912
+ '2023-01-09 00:00:00'
913
+ );
914
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
810
915
  });
811
916
  test('next monday', async () => {
812
917
  nowIs('2023-01-03 00:00:00');
813
- const range = mkRange('2023-01-09 00:00:00', '2023-01-10 00:00:00');
814
- await expect(`
815
- run: range + { where: t ~ f'next monday' }
816
- `).malloyResultMatches(range, inRange);
918
+ const rangeQuery = mkRangeQuery(
919
+ "f'next monday'",
920
+ '2023-01-09 00:00:00',
921
+ '2023-01-10 00:00:00'
922
+ );
923
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
817
924
  });
818
925
  test('next tuesday', async () => {
819
926
  nowIs('2023-01-03 00:00:00');
820
- const range = mkRange('2023-01-10 00:00:00', '2023-01-11 00:00:00');
821
- await expect(`
822
- run: range + { where: t ~ f'next tuesday' }
823
- `).malloyResultMatches(range, inRange);
927
+ const rangeQuery = mkRangeQuery(
928
+ "f'next tuesday'",
929
+ '2023-01-10 00:00:00',
930
+ '2023-01-11 00:00:00'
931
+ );
932
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
824
933
  });
825
934
  test('next wednesday', async () => {
826
935
  nowIs('2023-01-03 00:00:00');
827
- const range = mkRange('2023-01-04 00:00:00', '2023-01-05 00:00:00');
828
- await expect(`
829
- run: range + { where: t ~ f'next wednesday' }
830
- `).malloyResultMatches(range, inRange);
936
+ const rangeQuery = mkRangeQuery(
937
+ "f'next wednesday'",
938
+ '2023-01-04 00:00:00',
939
+ '2023-01-05 00:00:00'
940
+ );
941
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
831
942
  });
832
943
  test('next thursday', async () => {
833
944
  nowIs('2023-01-03 00:00:00');
834
- const range = mkRange('2023-01-05 00:00:00', '2023-01-06 00:00:00');
835
- await expect(`
836
- run: range + { where: t ~ f'next thursday' }
837
- `).malloyResultMatches(range, inRange);
945
+ const rangeQuery = mkRangeQuery(
946
+ "f'next thursday'",
947
+ '2023-01-05 00:00:00',
948
+ '2023-01-06 00:00:00'
949
+ );
950
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
838
951
  });
839
952
  test('next friday', async () => {
840
953
  nowIs('2023-01-03 00:00:00');
841
- const range = mkRange('2023-01-06 00:00:00', '2023-01-07 00:00:00');
842
- await expect(`
843
- run: range + { where: t ~ f'next friday' }
844
- `).malloyResultMatches(range, inRange);
954
+ const rangeQuery = mkRangeQuery(
955
+ "f'next friday'",
956
+ '2023-01-06 00:00:00',
957
+ '2023-01-07 00:00:00'
958
+ );
959
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
845
960
  });
846
961
  test('next saturday', async () => {
847
962
  nowIs('2023-01-03 00:00:00');
848
- const range = mkRange('2023-01-07 00:00:00', '2023-01-08 00:00:00');
849
- await expect(`
850
- run: range + { where: t ~ f'next Saturday' }
851
- `).malloyResultMatches(range, inRange);
963
+ const rangeQuery = mkRangeQuery(
964
+ "f'next Saturday'",
965
+ '2023-01-07 00:00:00',
966
+ '2023-01-08 00:00:00'
967
+ );
968
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
852
969
  });
853
970
  test('temporal filters are case insensitive', async () => {
854
971
  nowIs('2023-01-03 00:00:00');
855
- const range = mkRange('2023-01-04 00:00:00', '2023-01-05 00:00:00');
856
- await expect(`
857
- run: range + {where: t ~ f'Null Or noT aFter TomoRRow'}`).matchesRows(
858
- range,
972
+ const rangeQuery = mkRangeQuery(
973
+ "f'Null Or noT aFter TomoRRow'",
974
+ '2023-01-04 00:00:00',
975
+ '2023-01-05 00:00:00'
976
+ );
977
+ await expect(rangeQuery).matchesRows(
978
+ db,
859
979
  {n: 'before'},
860
980
  {n: 'first'},
861
981
  {n: 'last'},
862
982
  {n: 'z-null'}
863
983
  );
864
984
  });
985
+ const tzTesting = dbName !== 'presto' && dbName !== 'trino';
986
+ describe('query time zone', () => {
987
+ test.when(tzTesting)('day literal in query time zone', async () => {
988
+ const rangeQuery = mkRangeQuery(
989
+ "f'2024-01-01'",
990
+ '2024-01-01 00:00:00',
991
+ '2024-01-02 00:00:00',
992
+ 'America/Mexico_City'
993
+ );
994
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
995
+ });
996
+ test.when(tzTesting)('day literal in query time zone', async () => {
997
+ nowIs('2024-01-15 00:34:56', 'America/Mexico_City');
998
+ const rangeQuery = mkRangeQuery(
999
+ "f'today'",
1000
+ '2024-01-15 00:00:00',
1001
+ '2024-01-16 00:00:00',
1002
+ 'America/Mexico_City'
1003
+ );
1004
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
1005
+ });
1006
+ test.when(tzTesting)('day literal in query time zone', async () => {
1007
+ nowIs('2024-01-01 00:00:00', 'America/Mexico_City');
1008
+ const rangeQuery = mkRangeQuery(
1009
+ "f'next wednesday'",
1010
+ '2024-01-03 00:00:00',
1011
+ '2024-01-04 00:00:00',
1012
+ 'America/Mexico_City'
1013
+ );
1014
+ await expect(rangeQuery).malloyResultMatches(db, inRange);
1015
+ });
1016
+ test.when(tzTesting)('day literal in query time zone', async () => {
1017
+ const exactTimeModel = mkEqTime('2024-01-15 12:00:00');
1018
+ await expect(`
1019
+ run: eqtime -> {
1020
+ timezone: 'America/Mexico_City'
1021
+ where: t ~ f'2024-01-15 06:00:00' // 6 AM Mexico City = Noon UTC
1022
+ select: t, n
1023
+ }
1024
+ `).malloyResultMatches(exactTimeModel, {n: 'exact'});
1025
+ });
1026
+ });
865
1027
  });
866
1028
  });
@@ -57,6 +57,57 @@ function getSplitFunction(db: string) {
57
57
  }[db];
58
58
  }
59
59
 
60
+ function lotsOfNumbersSQLTable(db: string): string | undefined {
61
+ return {
62
+ 'mysql': `
63
+ SELECT
64
+ p0.n
65
+ + p1.n*2
66
+ + p2.n * POWER(2,2)
67
+ + p3.n * POWER(2,3)
68
+ + p4.n * POWER(2,4)
69
+ + p5.n * POWER(2,5)
70
+ + p6.n * POWER(2,6)
71
+ + p7.n * POWER(2,7)
72
+ + p8.n * POWER(2,8)
73
+ + p9.n * POWER(2,9)
74
+ + p10.n * POWER(2,10)
75
+ + p11.n * POWER(2,11)
76
+ + p12.n * POWER(2,12)
77
+ + p13.n * POWER(2,13)
78
+ + p14.n * POWER(2,14)
79
+ + p15.n * POWER(2,15)
80
+ + p16.n * POWER(2,16)
81
+ + p17.n * POWER(2,17)
82
+ + p18.n * POWER(2,18)
83
+ + p19.n * POWER(2,19)
84
+ as n
85
+ FROM
86
+ (SELECT 0 as n UNION SELECT 1) p0,
87
+ (SELECT 0 as n UNION SELECT 1) p1,
88
+ (SELECT 0 as n UNION SELECT 1) p2,
89
+ (SELECT 0 as n UNION SELECT 1) p3,
90
+ (SELECT 0 as n UNION SELECT 1) p4,
91
+ (SELECT 0 as n UNION SELECT 1) p5,
92
+ (SELECT 0 as n UNION SELECT 1) p6,
93
+ (SELECT 0 as n UNION SELECT 1) p7,
94
+ (SELECT 0 as n UNION SELECT 1) p8,
95
+ (SELECT 0 as n UNION SELECT 1) p9,
96
+ (SELECT 0 as n UNION SELECT 1) p10,
97
+ (SELECT 0 as n UNION SELECT 1) p11,
98
+ (SELECT 0 as n UNION SELECT 1) p12,
99
+ (SELECT 0 as n UNION SELECT 1) p13,
100
+ (SELECT 0 as n UNION SELECT 1) p14,
101
+ (SELECT 0 as n UNION SELECT 1) p15,
102
+ (SELECT 0 as n UNION SELECT 1) p16,
103
+ (SELECT 0 as n UNION SELECT 1) p17,
104
+ (SELECT 0 as n UNION SELECT 1) p18,
105
+ (SELECT 0 as n UNION SELECT 1) p19
106
+ `,
107
+ 'duckdb': 'SELECT UNNEST(GENERATE_SERIES(0,1048575,1)) as n',
108
+ }[db];
109
+ }
110
+
60
111
  afterAll(async () => {
61
112
  await runtimes.closeAll();
62
113
  });
@@ -64,6 +115,25 @@ afterAll(async () => {
64
115
  runtimes.runtimeMap.forEach((runtime, databaseName) => {
65
116
  const q = runtime.getQuoter();
66
117
 
118
+ const lotsSQL = lotsOfNumbersSQLTable(databaseName);
119
+ it.when(lotsSQL !== undefined)(
120
+ `big symmetric sum - ${databaseName}`,
121
+ async () => {
122
+ await expect(`
123
+ source: lots_of_numbers is ${databaseName}.sql(""" ${lotsSQL} """) extend {
124
+ measure:
125
+ total_n is n.sum()
126
+ }
127
+ query: two_rows is ${databaseName}.table('malloytest.state_facts') -> {select: state; limit: 2}
128
+ source: b is two_rows extend {
129
+ join_cross: lots_of_numbers
130
+ }
131
+ run: b -> {
132
+ aggregate: lots_of_numbers.total_n
133
+ }
134
+ `).malloyResultMatches(runtime, {total_n: 549755289600});
135
+ }
136
+ );
67
137
  // Issue: #1284
68
138
  it(`parenthesize output field values - ${databaseName}`, async () => {
69
139
  await expect(`
@@ -278,23 +348,27 @@ runtimes.runtimeMap.forEach((runtime, databaseName) => {
278
348
  await expect(`
279
349
  source: a is ${databaseName}.table('malloytest.airports') extend {
280
350
  primary_key: code
281
- dimension: big_elevation is elevation * 100000
351
+ dimension:
352
+ big_elevation is elevation * 100000.0
353
+ little_elevation is elevation / 100000.0
282
354
  measure:
283
355
  total_elevation is elevation.sum()
356
+ total_little_elevation is round(little_elevation.sum(),4)
284
357
  average_elevation is floor(elevation.avg())
285
- total_big_elevation is big_elevation.sum()
358
+ total_big_elevation is round(big_elevation.sum(),0) // mysql is weird
286
359
  average_big_elevation is floor(big_elevation.avg())
287
360
  }
288
361
  query: two_rows is ${databaseName}.table('malloytest.state_facts') -> {select: state; limit: 2}
289
362
  source: b is two_rows extend {
290
- join_cross: a on 1=1
363
+ join_cross: a
291
364
  }
292
365
 
293
- run: b -> {aggregate: a.total_elevation, a.average_elevation, a.total_big_elevation, a.average_big_elevation}
366
+ run: b -> {aggregate: a.total_elevation, a.total_little_elevation, a.average_elevation, a.total_big_elevation, a.average_big_elevation}
294
367
  // run: two_rows
295
368
 
296
369
  `).malloyResultMatches(runtime, {
297
370
  total_elevation: 22629146,
371
+ total_little_elevation: 226.2915,
298
372
  average_elevation: 1143,
299
373
  total_big_elevation: 2262914600000,
300
374
  average_big_elevation: 114329035,