@malloydata/malloy 0.0.308 → 0.0.309

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.
@@ -1,6 +1,6 @@
1
- import type { Sampling, AtomicTypeDef, TimeDeltaExpr, TypecastExpr, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode } from '../../model/malloy_types';
1
+ import type { Sampling, AtomicTypeDef, TimeDeltaExpr, TypecastExpr, MeasureTimeExpr, BasicAtomicTypeDef, RecordLiteralNode, ArrayLiteralNode, TimeExtractExpr } from '../../model/malloy_types';
2
2
  import type { DialectFunctionOverloadDef } from '../functions';
3
- import type { DialectFieldList, FieldReferenceType, QueryInfo } from '../dialect';
3
+ import { type DialectFieldList, type FieldReferenceType, type QueryInfo } from '../dialect';
4
4
  import { PostgresBase } from '../pg_impl';
5
5
  export declare class PostgresDialect extends PostgresBase {
6
6
  name: string;
@@ -66,4 +66,5 @@ export declare class PostgresDialect extends PostgresBase {
66
66
  validateTypeName(sqlType: string): boolean;
67
67
  sqlLiteralRecord(lit: RecordLiteralNode): string;
68
68
  sqlLiteralArray(lit: ArrayLiteralNode): string;
69
+ sqlTimeExtractExpr(qi: QueryInfo, from: TimeExtractExpr): string;
69
70
  }
@@ -26,6 +26,7 @@ exports.PostgresDialect = void 0;
26
26
  const utils_1 = require("../../model/utils");
27
27
  const malloy_types_1 = require("../../model/malloy_types");
28
28
  const functions_1 = require("../functions");
29
+ const dialect_1 = require("../dialect");
29
30
  const pg_impl_1 = require("../pg_impl");
30
31
  const dialect_functions_1 = require("./dialect_functions");
31
32
  const function_overrides_1 = require("./function_overrides");
@@ -80,7 +81,7 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
80
81
  this.udfPrefix = 'pg_temp.__udf';
81
82
  this.hasFinalStage = true;
82
83
  this.divisionIsInteger = true;
83
- this.supportsSumDistinctFunction = false;
84
+ this.supportsSumDistinctFunction = true;
84
85
  this.unnestWithNumbers = false;
85
86
  this.defaultSampling = { rows: 50000 };
86
87
  this.supportUnnestArrayAgg = true;
@@ -242,14 +243,33 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
242
243
  }
243
244
  throw new Error(`Unknown or unhandled postgres time unit: ${df.units}`);
244
245
  }
246
+ // This looks like a partial implementation of a method similar to how DuckDB
247
+ // does symmetric aggregates, which was abandoned. Leaving it in here for now
248
+ // in case the original author wants to pick it up again.
249
+ // sqlSumDistinct(key: string, value: string, funcName: string): string {
250
+ // // return `sum_distinct(list({key:${key}, val: ${value}}))`;
251
+ // return `(
252
+ // SELECT ${funcName}((a::json->>'f2')::DOUBLE PRECISION) as value
253
+ // FROM (
254
+ // SELECT UNNEST(array_agg(distinct row_to_json(row(${key},${value}))::text)) a
255
+ // ) a
256
+ // )`;
257
+ // }
245
258
  sqlSumDistinct(key, value, funcName) {
246
- // return `sum_distinct(list({key:${key}, val: ${value}}))`;
247
- return `(
248
- SELECT ${funcName}((a::json->>'f2')::DOUBLE PRECISION) as value
249
- FROM (
250
- SELECT UNNEST(array_agg(distinct row_to_json(row(${key},${value}))::text)) a
251
- ) a
252
- )`;
259
+ const hashKey = this.sqlSumDistinctHashedKey(key);
260
+ // PostgreSQL requires CAST to NUMERIC before ROUND, which is different
261
+ // than the generic implementation of sqlSumDistinct, but is OK in
262
+ // PostgreSQL because NUMERIC has arbitrary precision.
263
+ const roundedValue = `ROUND(CAST(COALESCE(${value}, 0) AS NUMERIC), 9)`;
264
+ const sumSQL = `SUM(DISTINCT ${roundedValue} + ${hashKey}) - SUM(DISTINCT ${hashKey})`;
265
+ const ret = `CAST(${sumSQL} AS DOUBLE PRECISION)`;
266
+ if (funcName === 'SUM') {
267
+ return ret;
268
+ }
269
+ else if (funcName === 'AVG') {
270
+ return `(${ret})/NULLIF(COUNT(DISTINCT CASE WHEN ${value} IS NOT NULL THEN ${key} END), 0)`;
271
+ }
272
+ throw new Error(`Unknown Symmetric Aggregate function ${funcName}`);
253
273
  }
254
274
  // TODO this does not preserve the types of the arguments, meaning we have to hack
255
275
  // around this in the definitions of functions that use this to cast back to the correct
@@ -339,6 +359,20 @@ class PostgresDialect extends pg_impl_1.PostgresBase {
339
359
  const array = lit.kids.values.map(val => val.sql);
340
360
  return 'JSONB_BUILD_ARRAY(' + array.join(',') + ')';
341
361
  }
362
+ sqlTimeExtractExpr(qi, from) {
363
+ const units = pg_impl_1.timeExtractMap[from.units] || from.units;
364
+ let extractFrom = from.e.sql;
365
+ if (malloy_types_1.TD.isTimestamp(from.e.typeDef)) {
366
+ const tz = (0, dialect_1.qtz)(qi);
367
+ if (tz) {
368
+ extractFrom = `(${extractFrom}::TIMESTAMPTZ AT TIME ZONE '${tz}')`;
369
+ }
370
+ }
371
+ // PostgreSQL before 14 returns a double precision for EXTRACT, cast to integer
372
+ // since it is common to pass an extraction to mod ( like in fitler expressions )
373
+ const extracted = `EXTRACT(${units} FROM ${extractFrom})::integer`;
374
+ return from.units === 'day_of_week' ? `(${extracted}+1)` : `(${extracted})`;
375
+ }
342
376
  }
343
377
  exports.PostgresDialect = PostgresDialect;
344
378
  //# sourceMappingURL=postgres.js.map
@@ -33,16 +33,15 @@ function sqlSumDistinct(dialect, sqlExp, sqlDistintKey) {
33
33
  const precision = 9;
34
34
  const uniqueInt = dialect.sqlSumDistinctHashedKey(sqlDistintKey);
35
35
  const multiplier = 10 ** (precision - NUMERIC_DECIMAL_PRECISION);
36
- const sumSQL = `
37
- (
38
- SUM(DISTINCT
39
- (CAST(ROUND(COALESCE(${sqlExp},0)*(${multiplier}*1.0), ${NUMERIC_DECIMAL_PRECISION}) AS ${dialect.defaultDecimalType}) +
40
- ${uniqueInt}
41
- ))
42
- -
43
- SUM(DISTINCT ${uniqueInt})
36
+ // Ensure value is numeric and handle nulls
37
+ const safeValue = `CAST(COALESCE(${sqlExp}, 0) AS ${dialect.defaultDecimalType})`;
38
+ // Scale and round to eliminate floating point differences
39
+ const roundedValue = `ROUND(${safeValue}*${multiplier}, ${NUMERIC_DECIMAL_PRECISION})`;
40
+ const sumSQL = `(
41
+ SUM(DISTINCT ${roundedValue} + ${uniqueInt})
42
+ - SUM(DISTINCT ${uniqueInt})
44
43
  )`;
45
- let ret = `(${sumSQL}/(${multiplier}*1.0))`;
44
+ let ret = `(${sumSQL}/${multiplier})`;
46
45
  ret = `CAST(${ret} AS ${dialect.defaultNumberType})`;
47
46
  return ret;
48
47
  }
@@ -33,4 +33,5 @@ export declare class TemporalFilterCompiler {
33
33
  mod7(n: string): string;
34
34
  private moment;
35
35
  private isIn;
36
+ private weekdayMoment;
36
37
  }
@@ -521,51 +521,20 @@ class TemporalFilterCompiler {
521
521
  return this.lastUnit(m.units);
522
522
  case 'next':
523
523
  return this.nextUnit(m.units);
524
+ case 'sunday':
525
+ return this.weekdayMoment(1, m.which);
524
526
  case 'monday':
527
+ return this.weekdayMoment(2, m.which);
525
528
  case 'tuesday':
529
+ return this.weekdayMoment(3, m.which);
526
530
  case 'wednesday':
531
+ return this.weekdayMoment(4, m.which);
527
532
  case 'thursday':
533
+ return this.weekdayMoment(5, m.which);
528
534
  case 'friday':
535
+ return this.weekdayMoment(6, m.which);
529
536
  case 'saturday':
530
- case 'sunday': {
531
- const destDay = [
532
- 'sunday',
533
- 'monday',
534
- 'tuesday',
535
- 'wednesday',
536
- 'thursday',
537
- 'friday',
538
- 'saturday',
539
- ].indexOf(m.moment);
540
- const dow = this.dayofWeek(this.nowExpr()).sql;
541
- if (m.which === 'next') {
542
- const nForwards = `${this.mod7(`${destDay}-(${dow}-1)+6`)}+1`;
543
- const begin = this.delta(this.thisUnit('day').begin, '+', nForwards, 'day');
544
- const end = this.delta(this.thisUnit('day').begin, '+', `${nForwards}+1`, 'day');
545
- // console.log(
546
- // `SELECT ${
547
- // this.nowExpr().sql
548
- // } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nForwards} as nForwards,\n ${
549
- // begin.sql
550
- // } as begin,\n ${end.sql} as end`
551
- // );
552
- return { begin, end: end.sql };
553
- }
554
- // dacks back = mod((daw0 - dst) + 6, 7) + 1;
555
- // dacks back = mod(((daw - 1) - dst) + 6, 7) + 1;
556
- // dacks back = mod(((daw) - dst) + 7, 7) + 1;
557
- const nBack = `${this.mod7(`(${dow}-1)-${destDay}+6`)}+1`;
558
- const begin = this.delta(this.thisUnit('day').begin, '-', nBack, 'day');
559
- const end = this.delta(this.thisUnit('day').begin, '-', `(${nBack})-1`, 'day');
560
- // console.log(
561
- // `SELECT ${
562
- // this.nowExpr().sql
563
- // } as now,\n ${destDay} as destDay,\n ${dow} as dow,\n ${nBack} as nBack,\n ${
564
- // begin.sql
565
- // } as begin,\n ${end.sql} as end`
566
- // );
567
- return { begin, end: end.sql };
568
- }
537
+ return this.weekdayMoment(7, m.which);
569
538
  }
570
539
  }
571
540
  isIn(notIn, begin, end) {
@@ -581,6 +550,27 @@ class TemporalFilterCompiler {
581
550
  end = this.time(end);
582
551
  return `${this.expr} ${begOp} ${begin} ${joinOp} ${this.expr} ${endOp} ${end}`;
583
552
  }
553
+ weekdayMoment(destDay, which) {
554
+ const direction = which || 'last';
555
+ const dow = this.dayofWeek(this.nowExpr());
556
+ const todayBegin = this.thisUnit('day').begin;
557
+ // destDay comes in as 1-7 (Malloy format), convert to 0-6
558
+ const destDayZeroBased = destDay - 1;
559
+ // dow is 1-7, convert to 0-6 for the arithmetic
560
+ const dowZeroBased = `(${dow.sql}-1)`;
561
+ let daysOffset;
562
+ if (direction === 'next') {
563
+ // Days forward: ((destDay - dow + 6) % 7) + 1
564
+ daysOffset = `${this.mod7(`${destDayZeroBased}-${dowZeroBased}+6`)}+1`;
565
+ }
566
+ else {
567
+ // Days back: ((dow - destDay + 6) % 7) + 1
568
+ daysOffset = `${this.mod7(`${dowZeroBased}-${destDayZeroBased}+6`)}+1`;
569
+ }
570
+ const begin = this.delta(todayBegin, direction === 'next' ? '+' : '-', daysOffset, 'day');
571
+ const end = this.delta(begin, '+', '1', 'day');
572
+ return { begin, end: end.sql };
573
+ }
584
574
  }
585
575
  exports.TemporalFilterCompiler = TemporalFilterCompiler;
586
576
  //# sourceMappingURL=filter_compilers.js.map
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.308";
1
+ export declare const MALLOY_VERSION = "0.0.309";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.308';
5
+ exports.MALLOY_VERSION = '0.0.309';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.308",
3
+ "version": "0.0.309",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -41,9 +41,9 @@
41
41
  "generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@malloydata/malloy-filter": "0.0.308",
45
- "@malloydata/malloy-interfaces": "0.0.308",
46
- "@malloydata/malloy-tag": "0.0.308",
44
+ "@malloydata/malloy-filter": "0.0.309",
45
+ "@malloydata/malloy-interfaces": "0.0.309",
46
+ "@malloydata/malloy-tag": "0.0.309",
47
47
  "antlr4ts": "^0.5.0-alpha.4",
48
48
  "assert": "^2.0.0",
49
49
  "jaro-winkler": "^0.2.8",