@uwdata/mosaic-sql 0.10.0 → 0.12.0

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.
Files changed (142) hide show
  1. package/dist/mosaic-sql.js +2242 -1064
  2. package/dist/mosaic-sql.min.js +1 -1
  3. package/dist/types/ast/aggregate.d.ts +70 -0
  4. package/dist/types/ast/between-op.d.ts +46 -0
  5. package/dist/types/ast/binary-op.d.ts +28 -0
  6. package/dist/types/ast/case.d.ts +68 -0
  7. package/dist/types/ast/cast.d.ts +21 -0
  8. package/dist/types/ast/column-param.d.ts +17 -0
  9. package/dist/types/ast/column-ref.d.ts +39 -0
  10. package/dist/types/ast/fragment.d.ts +14 -0
  11. package/dist/types/ast/from.d.ts +21 -0
  12. package/dist/types/ast/function.d.ts +21 -0
  13. package/dist/types/ast/in-op.d.ts +21 -0
  14. package/dist/types/ast/interval.d.ts +21 -0
  15. package/dist/types/ast/literal.d.ts +15 -0
  16. package/dist/types/ast/logical-op.d.ts +46 -0
  17. package/dist/types/ast/node.d.ts +24 -0
  18. package/dist/types/ast/order-by.d.ts +29 -0
  19. package/dist/types/ast/param.d.ts +19 -0
  20. package/dist/types/ast/query.d.ts +268 -0
  21. package/dist/types/ast/sample.d.ts +42 -0
  22. package/dist/types/ast/select.d.ts +22 -0
  23. package/dist/types/ast/table-ref.d.ts +25 -0
  24. package/dist/types/ast/unary-op.d.ts +39 -0
  25. package/dist/types/ast/verbatim.d.ts +9 -0
  26. package/dist/types/ast/window.d.ts +177 -0
  27. package/dist/types/ast/with.d.ts +22 -0
  28. package/dist/types/constants.d.ts +38 -0
  29. package/dist/types/functions/aggregate.d.ts +229 -0
  30. package/dist/types/functions/case.d.ts +15 -0
  31. package/dist/types/functions/cast.d.ts +26 -0
  32. package/dist/types/functions/column.d.ts +9 -0
  33. package/dist/types/functions/datetime.d.ts +44 -0
  34. package/dist/types/functions/literal.d.ts +16 -0
  35. package/dist/types/functions/numeric.d.ts +93 -0
  36. package/dist/types/functions/operators.d.ts +198 -0
  37. package/dist/types/functions/order-by.d.ts +17 -0
  38. package/dist/types/functions/spatial.d.ts +37 -0
  39. package/dist/types/functions/sql-template-tag.d.ts +16 -0
  40. package/dist/types/functions/string.d.ts +55 -0
  41. package/dist/types/functions/table-ref.d.ts +9 -0
  42. package/dist/types/functions/window.d.ts +87 -0
  43. package/dist/types/index-types.d.ts +2 -0
  44. package/dist/types/index.d.ts +53 -0
  45. package/dist/types/load/create.d.ts +8 -0
  46. package/dist/types/load/extension.d.ts +1 -0
  47. package/dist/types/load/load.d.ts +12 -0
  48. package/dist/types/load/sql-from.d.ts +11 -0
  49. package/dist/types/transforms/bin-1d.d.ts +14 -0
  50. package/dist/types/transforms/bin-2d.d.ts +18 -0
  51. package/dist/types/transforms/bin-linear-1d.d.ts +9 -0
  52. package/dist/types/transforms/bin-linear-2d.d.ts +18 -0
  53. package/dist/types/transforms/line-density.d.ts +23 -0
  54. package/dist/types/transforms/m4.d.ts +18 -0
  55. package/dist/types/transforms/scales.d.ts +1 -0
  56. package/dist/types/types.d.ts +59 -0
  57. package/dist/types/util/ast.d.ts +60 -0
  58. package/dist/types/util/function.d.ts +54 -0
  59. package/dist/types/util/string.d.ts +3 -0
  60. package/dist/types/util/type-check.d.ts +18 -0
  61. package/dist/types/visit/recurse.d.ts +28 -0
  62. package/dist/types/visit/rewrite.d.ts +10 -0
  63. package/dist/types/visit/visitors.d.ts +33 -0
  64. package/dist/types/visit/walk.d.ts +7 -0
  65. package/jsconfig.json +11 -0
  66. package/package.json +6 -4
  67. package/src/ast/aggregate.js +164 -0
  68. package/src/ast/between-op.js +75 -0
  69. package/src/ast/binary-op.js +40 -0
  70. package/src/ast/case.js +105 -0
  71. package/src/ast/cast.js +34 -0
  72. package/src/ast/column-param.js +29 -0
  73. package/src/ast/column-ref.js +72 -0
  74. package/src/ast/fragment.js +26 -0
  75. package/src/ast/from.js +40 -0
  76. package/src/ast/function.js +34 -0
  77. package/src/ast/in-op.js +33 -0
  78. package/src/ast/interval.js +33 -0
  79. package/src/ast/literal.js +55 -0
  80. package/src/ast/logical-op.js +67 -0
  81. package/src/ast/node.js +29 -0
  82. package/src/ast/order-by.js +48 -0
  83. package/src/ast/param.js +35 -0
  84. package/src/ast/query.js +578 -0
  85. package/src/ast/sample.js +53 -0
  86. package/src/ast/select.js +44 -0
  87. package/src/ast/table-ref.js +44 -0
  88. package/src/ast/unary-op.js +64 -0
  89. package/src/ast/verbatim.js +26 -0
  90. package/src/ast/window.js +290 -0
  91. package/src/ast/with.js +30 -0
  92. package/src/constants.js +44 -0
  93. package/src/functions/aggregate.js +335 -0
  94. package/src/functions/case.js +21 -0
  95. package/src/functions/cast.js +39 -0
  96. package/src/functions/column.js +20 -0
  97. package/src/functions/datetime.js +65 -0
  98. package/src/functions/literal.js +22 -0
  99. package/src/functions/numeric.js +139 -0
  100. package/src/functions/operators.js +298 -0
  101. package/src/functions/order-by.js +24 -0
  102. package/src/functions/spatial.js +56 -0
  103. package/src/functions/sql-template-tag.js +51 -0
  104. package/src/functions/string.js +82 -0
  105. package/src/functions/table-ref.js +14 -0
  106. package/src/functions/window.js +121 -0
  107. package/src/index-types.ts +2 -0
  108. package/src/index.js +57 -155
  109. package/src/load/create.js +10 -2
  110. package/src/load/load.js +4 -4
  111. package/src/load/sql-from.js +7 -6
  112. package/src/transforms/bin-1d.js +21 -0
  113. package/src/transforms/bin-2d.js +29 -0
  114. package/src/transforms/bin-linear-1d.js +26 -0
  115. package/src/transforms/bin-linear-2d.js +71 -0
  116. package/src/transforms/line-density.js +113 -0
  117. package/src/transforms/m4.js +38 -0
  118. package/src/{scales.js → transforms/scales.js} +31 -17
  119. package/src/types.ts +96 -0
  120. package/src/util/ast.js +96 -0
  121. package/src/util/function.js +78 -0
  122. package/src/util/string.js +16 -0
  123. package/src/util/type-check.js +29 -0
  124. package/src/visit/recurse.js +57 -0
  125. package/src/visit/rewrite.js +32 -0
  126. package/src/visit/visitors.js +108 -0
  127. package/src/visit/walk.js +30 -0
  128. package/tsconfig.json +12 -0
  129. package/src/Query.js +0 -593
  130. package/src/aggregates.js +0 -185
  131. package/src/cast.js +0 -19
  132. package/src/datetime.js +0 -31
  133. package/src/desc.js +0 -13
  134. package/src/expression.js +0 -170
  135. package/src/functions.js +0 -25
  136. package/src/literal.js +0 -6
  137. package/src/operators.js +0 -54
  138. package/src/ref.js +0 -109
  139. package/src/repeat.js +0 -3
  140. package/src/spatial.js +0 -10
  141. package/src/to-sql.js +0 -52
  142. package/src/windows.js +0 -239
package/src/index.js CHANGED
@@ -1,156 +1,58 @@
1
- export {
2
- Ref,
3
- asColumn,
4
- asRelation,
5
- all,
6
- column,
7
- relation
8
- } from './ref.js';
9
-
10
- export {
11
- isSQLExpression,
12
- isParamLike,
13
- sql,
14
- SQLExpression
15
- } from './expression.js';
16
-
17
- export {
18
- desc
19
- } from './desc.js';
20
-
21
- export {
22
- literal
23
- } from './literal.js';
24
-
25
- export {
26
- and,
27
- or,
28
- not,
29
- eq,
30
- neq,
31
- lt,
32
- gt,
33
- lte,
34
- gte,
35
- isBetween,
36
- isNotBetween,
37
- isDistinct,
38
- isNotDistinct,
39
- isNull,
40
- isNotNull
41
- } from './operators.js';
42
-
43
- export {
44
- agg,
45
- argmax,
46
- argmin,
47
- arrayAgg,
48
- avg,
49
- corr,
50
- count,
51
- covariance,
52
- covarPop,
53
- entropy,
54
- first,
55
- kurtosis,
56
- mean,
57
- mad,
58
- max,
59
- median,
60
- min,
61
- mode,
62
- last,
63
- product,
64
- quantile,
65
- regrAvgX,
66
- regrAvgY,
67
- regrCount,
68
- regrIntercept,
69
- regrR2,
70
- regrSXX,
71
- regrSXY,
72
- regrSYY,
73
- regrSlope,
74
- skewness,
75
- stddev,
76
- stddevPop,
77
- stringAgg,
78
- sum,
79
- variance,
80
- varPop
81
- } from './aggregates.js';
82
-
83
- export {
84
- cast,
85
- castDouble,
86
- castInteger
87
- } from './cast.js';
88
-
89
- export {
90
- epoch_ms,
91
- dateBin,
92
- dateMonth,
93
- dateMonthDay,
94
- dateDay
95
- } from './datetime.js';
96
-
97
- export {
98
- regexp_matches,
99
- contains,
100
- prefix,
101
- suffix,
102
- lower,
103
- upper,
104
- length,
105
- isNaN,
106
- isFinite,
107
- isInfinite
108
- } from './functions.js';
109
-
110
- export {
111
- centroid,
112
- centroidX,
113
- centroidY,
114
- geojson,
115
- x,
116
- y
117
- } from './spatial.js';
118
-
119
- export {
120
- row_number,
121
- rank,
122
- dense_rank,
123
- percent_rank,
124
- cume_dist,
125
- ntile,
126
- lag,
127
- lead,
128
- first_value,
129
- last_value,
130
- nth_value
131
- } from './windows.js';
132
-
133
- export {
134
- Query,
135
- isQuery,
136
- isDescribeQuery
137
- } from './Query.js';
138
-
139
- export {
140
- toSQL,
141
- literalToSQL
142
- } from './to-sql.js';
143
-
144
- export {
145
- scaleTransform
146
- } from './scales.js';
147
-
148
- export { create } from './load/create.js';
1
+ export { AggregateNode } from './ast/aggregate.js';
2
+ export { BetweenOpNode, NotBetweenOpNode } from './ast/between-op.js'
3
+ export { BinaryOpNode } from './ast/binary-op.js';
4
+ export { CaseNode, WhenNode } from './ast/case.js';
5
+ export { CastNode } from './ast/cast.js';
6
+ export { ColumnParamNode } from './ast/column-param.js';
7
+ export { ColumnRefNode, ColumnNameRefNode, isColumnRef } from './ast/column-ref.js';
8
+ export { FragmentNode } from './ast/fragment.js';
9
+ export { FromClauseNode } from './ast/from.js';
10
+ export { FunctionNode } from './ast/function.js';
11
+ export { InOpNode } from './ast/in-op.js';
12
+ export { IntervalNode } from './ast/interval.js';
13
+ export { LiteralNode } from './ast/literal.js';
14
+ export { AndNode, OrNode } from './ast/logical-op.js';
15
+ export { SQLNode, ExprNode, isNode } from './ast/node.js';
16
+ export { OrderByNode } from './ast/order-by.js';
17
+ export { ParamNode } from './ast/param.js';
18
+ export { DescribeQuery, Query, SelectQuery, SetOperation, isDescribeQuery, isQuery, isSelectQuery } from './ast/query.js';
19
+ export { SampleClauseNode } from './ast/sample.js';
20
+ export { SelectClauseNode } from './ast/select.js';
21
+ export { TableRefNode, isTableRef } from './ast/table-ref.js';
22
+ export { UnaryOpNode, UnaryPosftixOpNode } from './ast/unary-op.js';
23
+ export { VerbatimNode } from './ast/verbatim.js';
24
+ export { WindowClauseNode, WindowDefNode, WindowFrameNode, WindowFunctionNode, WindowNode } from './ast/window.js';
25
+ export { WithClauseNode } from './ast/with.js';
26
+
27
+ export { argmax, argmin, arrayAgg, avg, corr, count, covariance, covarPop, entropy, first, kurtosis, mad, max, median, min, mode, last, product, quantile, regrAvgX, regrAvgY, regrCount, regrIntercept, regrR2, regrSXX, regrSXY, regrSYY, regrSlope, skewness, stddev, stddevPop, stringAgg, sum, variance, varPop } from './functions/aggregate.js';
28
+ export { cond } from './functions/case.js';
29
+ export { cast, float32, float64, int32 } from './functions/cast.js';
30
+ export { column } from './functions/column.js';
31
+ export { dateBin, dateMonth, dateMonthDay, dateDay, epoch_ms, interval } from './functions/datetime.js';
32
+ export { literal } from './functions/literal.js';
33
+ export { abs, ceil, exp, floor, greatest, isFinite, isInfinite, isNaN, least, ln, log, round, sign, sqrt, trunc } from './functions/numeric.js';
34
+ export { and, or, not, isNull, isNotNull, bitNot, bitAnd, bitOr, bitLeft, bitRight, add, sub, mul, div, idiv, mod, pow, eq, neq, lt, gt, lte, gte, isDistinct, isNotDistinct, isBetween, isNotBetween, isIn } from './functions/operators.js';
35
+ export { asc, desc } from './functions/order-by.js';
36
+ export { geojson, x, y, centroid, centroidX, centroidY } from './functions/spatial.js';
37
+ export { sql } from './functions/sql-template-tag.js';
38
+ export { regexp_matches, contains, prefix, suffix, lower, upper, length } from './functions/string.js';
39
+ export { cume_dist, dense_rank, first_value, lag, last_value, lead, nth_value, ntile, percent_rank, rank, row_number } from './functions/window.js';
40
+
41
+ export { rewrite } from './visit/rewrite.js';
42
+ export { collectAggregates, collectColumns, collectParams, isAggregateExpression } from './visit/visitors.js';
43
+ export { walk } from './visit/walk.js';
44
+
45
+ export { createTable, createSchema } from './load/create.js';
149
46
  export { loadExtension } from './load/extension.js';
150
- export {
151
- loadCSV,
152
- loadJSON,
153
- loadObjects,
154
- loadParquet,
155
- loadSpatial
156
- } from './load/load.js';
47
+ export { loadCSV, loadJSON, loadObjects, loadParquet, loadSpatial } from './load/load.js';
48
+
49
+ export { bin1d } from './transforms/bin-1d.js';
50
+ export { bin2d } from './transforms/bin-2d.js';
51
+ export { binLinear1d } from './transforms/bin-linear-1d.js';
52
+ export { binLinear2d } from './transforms/bin-linear-2d.js';
53
+ export { lineDensity } from './transforms/line-density.js';
54
+ export { m4 } from './transforms/m4.js';
55
+ export { scaleTransform } from './transforms/scales.js';
56
+
57
+ export { asLiteral, asNode, asTableRef, asVerbatim, over } from './util/ast.js';
58
+ export { isParamLike } from './util/type-check.js';
@@ -1,6 +1,6 @@
1
- export function create(name, query, {
1
+ export function createTable(name, query, {
2
2
  replace = false,
3
- temp = true,
3
+ temp = false,
4
4
  view = false
5
5
  } = {}) {
6
6
  return 'CREATE'
@@ -10,3 +10,11 @@ export function create(name, query, {
10
10
  + (replace ? ' ' : ' IF NOT EXISTS ')
11
11
  + name + ' AS ' + query;
12
12
  }
13
+
14
+ export function createSchema(name, {
15
+ strict = false
16
+ } = {}) {
17
+ return 'CREATE SCHEMA '
18
+ + (strict ? '' : 'IF NOT EXISTS ')
19
+ + name;
20
+ }
package/src/load/load.js CHANGED
@@ -1,4 +1,4 @@
1
- import { create } from './create.js';
1
+ import { createTable } from './create.js';
2
2
  import { sqlFrom } from './sql-from.js';
3
3
 
4
4
  export function load(method, tableName, fileName, options = {}, defaults = {}) {
@@ -7,7 +7,7 @@ export function load(method, tableName, fileName, options = {}, defaults = {}) {
7
7
  const read = `${method}('${fileName}'${params ? ', ' + params : ''})`;
8
8
  const filter = where ? ` WHERE ${where}` : '';
9
9
  const query = `SELECT ${select.join(', ')} FROM ${read}${filter}`;
10
- return create(tableName, query, { view, temp, replace });
10
+ return createTable(tableName, query, { view, temp, replace });
11
11
  }
12
12
 
13
13
  export function loadCSV(tableName, fileName, options) {
@@ -15,7 +15,7 @@ export function loadCSV(tableName, fileName, options) {
15
15
  }
16
16
 
17
17
  export function loadJSON(tableName, fileName, options) {
18
- return load('read_json', tableName, fileName, options, { auto_detect: true, json_format: 'auto' });
18
+ return load('read_json', tableName, fileName, options, { auto_detect: true, format: 'auto' });
19
19
  }
20
20
 
21
21
  export function loadParquet(tableName, fileName, options) {
@@ -51,7 +51,7 @@ export function loadObjects(tableName, data, options = {}) {
51
51
  const query = select.length === 1 && select[0] === '*'
52
52
  ? values
53
53
  : `SELECT ${select} FROM ${values}`;
54
- return create(tableName, query, opt);
54
+ return createTable(tableName, query, opt);
55
55
  }
56
56
 
57
57
  function parameters(options) {
@@ -1,11 +1,12 @@
1
- import { literalToSQL } from '../to-sql.js';
1
+ import { asLiteral } from '../util/ast.js';
2
2
 
3
3
  /**
4
4
  * Create a SQL query that embeds the given data for loading.
5
- * @param {*} data The dataset
6
- * @param {object} [options] Loading options
7
- * @param {string[]|object} [options.columns] The columns to include
8
- * @returns {string} SQL query string to load data
5
+ * @param {*} data The dataset as an array of objects.
6
+ * @param {object} [options] Loading options.
7
+ * @param {string[]|object} [options.columns] The columns to include.
8
+ * If not specified, the keys of the first data object are used.
9
+ * @returns {string} SQL query string to load data.
9
10
  */
10
11
  export function sqlFrom(data, {
11
12
  columns = Object.keys(data?.[0] || {})
@@ -22,7 +23,7 @@ export function sqlFrom(data, {
22
23
  }
23
24
  const subq = [];
24
25
  for (const datum of data) {
25
- const sel = keys.map(k => `${literalToSQL(datum[k])} AS "${columns[k]}"`);
26
+ const sel = keys.map(k => `${asLiteral(datum[k])} AS "${columns[k]}"`);
26
27
  subq.push(`(SELECT ${sel.join(', ')})`);
27
28
  }
28
29
  return subq.join(' UNION ALL ');
@@ -0,0 +1,21 @@
1
+ import { float64 } from '../functions/cast.js';
2
+ import { mul, sub } from '../functions/operators.js';
3
+
4
+ /**
5
+ * Compute binned values over a one-dimensional extent.
6
+ * @param {import('../types.js').ExprValue} x The expression to bin.
7
+ * The expression must return numeric values. For example, to bin
8
+ * datetime values, the input expression might map them to numeric
9
+ * values such as milliseconds since the epoch.
10
+ * @param {number} lo The low value of the bin extent.
11
+ * @param {number} hi The high value of the bin extent.
12
+ * @param {number} bins The integer number of bins to use within the
13
+ * defined binning extent.
14
+ * @param {boolean} [reverse] Flag indicating if bins should be
15
+ * produced in reverse order from *hi* to *lo* (default `false`).
16
+ */
17
+ export function bin1d(x, lo, hi, bins, reverse) {
18
+ const diff = reverse ? sub(hi, float64(x)) : sub(float64(x), lo);
19
+ const scale = hi === lo ? 0 : bins / (hi - lo);
20
+ return scale ? mul(diff, float64(scale)) : diff;
21
+ }
@@ -0,0 +1,29 @@
1
+ import { SelectQuery } from '../ast/query.js';
2
+ import { int32 } from '../functions/cast.js';
3
+ import { floor } from '../functions/numeric.js';
4
+ import { add, mul } from '../functions/operators.js';
5
+
6
+ /**
7
+ * Perform aggregation over a binned 2D domain. This method takes expressions
8
+ * for the (non-truncated) x and y bin values; these expressions should be
9
+ * in units of grid indices, but can contain fractional components. The
10
+ * resulting query performs grouping and aggregation over the binned domain,
11
+ * and uses a 2D integer bin index of the form (xbin + num_xbins * ybin).
12
+ * @param {SelectQuery} q The input query. The FROM and WHERE clauses should
13
+ * be added to the query separately, either before or after this method.
14
+ * @param {import('../types.js').ExprValue} xp The x bin expression.
15
+ * @param {import('../types.js').ExprValue} yp The y bin expression.
16
+ * @param {Record<string, import('../types.js').ExprValue>} aggs Named
17
+ * aggregate expressions over bins.
18
+ * @param {number} xn The number of bins along the x dimension
19
+ * @param {string[]} groupby Group by expressions.
20
+ * @returns {SelectQuery} The input query, with binning expressions added.
21
+ */
22
+ export function bin2d(q, xp, yp, aggs, xn, groupby) {
23
+ return q
24
+ .select({
25
+ index: add(int32(floor(xp)), mul(int32(floor(yp)), xn)),
26
+ ...aggs
27
+ })
28
+ .groupby('index', groupby);
29
+ }
@@ -0,0 +1,26 @@
1
+ import { Query } from '../ast/query.js';
2
+ import { sum } from '../functions/aggregate.js';
3
+ import { int32 } from '../functions/cast.js';
4
+ import { floor } from '../functions/numeric.js';
5
+ import { add, mul, neq, sub } from '../functions/operators.js';
6
+
7
+ /**
8
+ * Perform linear binning in one dimension.
9
+ * @param {import('../ast/query.js').SelectQuery} query The base query to bin.
10
+ * @param {import('../types.js').ExprValue} x The expression to bin.
11
+ * @param {import('../types.js').ExprValue} [weight] The expression to weight by.
12
+ * @returns {Query}
13
+ */
14
+ export function binLinear1d(query, x, weight) {
15
+ const w = weight ? (x => mul(x, weight)) : (x => x);
16
+ const p0 = floor(x);
17
+ const p1 = add(p0, 1);
18
+ return Query
19
+ .from(Query.unionAll(
20
+ query.clone().select({ i: int32(p0), w: w(sub(p1, x)) }),
21
+ query.clone().select({ i: int32(p1), w: w(sub(x, p0)) })
22
+ ))
23
+ .select({ index: 'i', density: sum('w') })
24
+ .groupby('index')
25
+ .having(neq('density', 0));
26
+ }
@@ -0,0 +1,71 @@
1
+ import { Query, SelectQuery } from '../ast/query.js';
2
+ import { sum } from '../functions/aggregate.js';
3
+ import { int32 } from '../functions/cast.js';
4
+ import { floor } from '../functions/numeric.js';
5
+ import { add, mul, neq, sub } from '../functions/operators.js';
6
+
7
+ /**
8
+ * Identity function.
9
+ * @template T
10
+ * @param {T} x
11
+ * @returns {T}
12
+ */
13
+ function identity(x) {
14
+ return x;
15
+ }
16
+
17
+ /**
18
+ * Compute densities over a 2D domain using linear binning. The weight of
19
+ * each data point is linearly distributed over adjacent bins, providing
20
+ * a better base for subsequent kernel density estimation. This method takes
21
+ * expressions for the (non-truncated) x and y bin values; these expressions
22
+ * should be in units of grid indices, but can contain fractional components.
23
+ * @param {SelectQuery} q The input query. The FROM and WHERE clauses should
24
+ * be added to the query separately, before this method is invoked.
25
+ * @param {import('../types.js').ExprValue} xp The x grid bin expression
26
+ * @param {import('../types.js').ExprValue} yp The y grid bin expression
27
+ * @param {import('../types.js').ExprValue | undefined} weight Point weights.
28
+ * @param {number} xn The number of x grid bins.
29
+ * @param {string[]} groupby Group by expressions.
30
+ * @returns {SelectQuery} A linear binning query for bin `index` and
31
+ * aggregate `density` columns, in addition to any group by expressions.
32
+ */
33
+ export function binLinear2d(q, xp, yp, weight, xn, groupby) {
34
+
35
+ const w = weight ? x => mul(x, weight) : identity;
36
+
37
+ /**
38
+ * @param {import('../types.js').ExprValue} i
39
+ * @param {import('../types.js').ExprValue} w
40
+ */
41
+ const subq = (i, w) => q.clone().select({ xp, yp, i, w });
42
+ /**
43
+ * @param {import('../types.js').ExprValue} x
44
+ * @param {import('../types.js').ExprValue} y
45
+ */
46
+ const index = (x, y) => add(x, mul(y, xn));
47
+
48
+ const xu = int32(floor(xp));
49
+ const yu = int32(floor(yp));
50
+ const xv = add(xu, 1);
51
+ const yv = add(yu, 1);
52
+ const xpu = sub(xp, xu);
53
+ const xvp = sub(xv, xp);
54
+ const ypu = sub(yp, yu);
55
+ const yvp = sub(yv, yp);
56
+
57
+ return Query
58
+ .from(Query.unionAll(
59
+ // grid[xu + yu * xn] += (xv - xp) * (yv - yp) * wi
60
+ subq(index(xu, yu), w(mul(xvp, yvp))),
61
+ // grid[xu + yv * xn] += (xv - xp) * (yp - yu) * wi
62
+ subq(index(xu, yv), w(mul(xvp, ypu))),
63
+ // grid[xv + yu * xn] += (xp - xu) * (yv - yp) * wi
64
+ subq(index(xv, yu), w(mul(xpu, yvp))),
65
+ // grid[xv + yv * xn] += (xp - xu) * (yp - yu) * wi
66
+ subq(index(xv, yv), w(mul(xpu, ypu)))
67
+ ))
68
+ .select({ index: 'i', density: sum('w') }, groupby)
69
+ .groupby('index', groupby)
70
+ .having(neq('density', 0));
71
+ }
@@ -0,0 +1,113 @@
1
+ import { Query, SelectQuery } from '../ast/query.js';
2
+ import { count, max, sum } from '../functions/aggregate.js';
3
+ import { int32 } from '../functions/cast.js';
4
+ import { abs, floor, greatest, round, sign } from '../functions/numeric.js';
5
+ import { add, div, gt, isNull, lt, lte, mul, or, sub } from '../functions/operators.js';
6
+ import { asc } from '../functions/order-by.js';
7
+ import { sql } from '../functions/sql-template-tag.js';
8
+ import { lead } from '../functions/window.js';
9
+ import { over } from '../util/ast.js';
10
+
11
+ /**
12
+ * Compute line segment densities over a gridded 2D domain. The returned
13
+ * query uses multiple subqueries (CTEs) to identify line segment end point
14
+ * pairs, perform line rasterization in-database, normalize arc lengths,
15
+ * and then sum results for all line series to produce a density map.
16
+ * Based on Moritz and Fisher's work: https://arxiv.org/abs/1808.06019
17
+ * @param {SelectQuery} q The base query over the data.
18
+ * @param {import('../types.js').ExprValue} x Bin expression for x dimension.
19
+ * Provides gridded x coordinates, potentially with a fractional component.
20
+ * @param {import('../types.js').ExprValue} y Bin expression for x dimension.
21
+ * Provides gridded y coordinates, potentially with a fractional component.
22
+ * @param {string[]} z Group by columns that segment data into individual line
23
+ * series. An empty array indicates there is only a single line series.
24
+ * @param {number} xn The number of grid bins for the x dimension.
25
+ * @param {number} yn The number of grid bins for the y dimension.
26
+ * @param {string[]} [groupby] Additional group by expressions. Separate
27
+ * line density maps are created for each of these groups.
28
+ * @param {boolean} [normalize=true] Flag toggling approximate arc-length
29
+ * normalization to improve accuracy and reduce artifacts (default `true`).
30
+ * @returns {SelectQuery}
31
+ */
32
+ export function lineDensity(
33
+ q, x, y, z, xn, yn,
34
+ groupby = [], normalize = true
35
+ ) {
36
+ // select x, y points binned to the grid
37
+ q.select({
38
+ x: int32(floor(x)),
39
+ y: int32(floor(y))
40
+ });
41
+
42
+ // select line segment end point pairs
43
+ // retain only segments within the grid region
44
+ const groups = groupby.concat(z);
45
+ const pairs = Query
46
+ .from(q)
47
+ .select(groups, {
48
+ x0: 'x',
49
+ y0: 'y',
50
+ dx: sub(lead('x').over('sw'), 'x'),
51
+ dy: sub(lead('y').over('sw'), 'y')
52
+ })
53
+ .window({
54
+ sw: over().partitionby(groups).orderby(asc('x'))
55
+ })
56
+ .qualify([
57
+ or(lt('x0', xn), lt(add('x0', 'dx'), xn)),
58
+ or(lt('y0', yn), lt(add('y0', 'dy'), yn)),
59
+ or(gt('x0', 0), gt(add('x0', 'dx'), 0)),
60
+ or(gt('y0', 0), gt(add('y0', 'dy'), 0))
61
+ ]);
62
+
63
+ // create indices to join against for rasterization
64
+ // generate the maximum number of indices needed
65
+ const num = Query
66
+ .select({ x: greatest(max(abs('dx')), max(abs('dy'))) })
67
+ .from('pairs');
68
+ const indices = Query.select({
69
+ i: int32(sql`UNNEST(range((${num})))`)
70
+ });
71
+
72
+ // rasterize line segments
73
+ const raster = Query.unionAll(
74
+ Query
75
+ .select(groups, {
76
+ x: add('x0', 'i'),
77
+ y: add('y0', int32(round(div(mul('i', 'dy'), 'dx'))))
78
+ })
79
+ .from('pairs', 'indices')
80
+ .where([lte(abs('dy'), abs('dx')), lt('i', abs('dx'))]),
81
+ Query
82
+ .select(groups, {
83
+ x: add('x0', int32(round(div(mul(mul(sign('dy'), 'i'), 'dx'), 'dy')))),
84
+ y: add('y0', mul(sign('dy'), 'i'))
85
+ })
86
+ .from('pairs', 'indices')
87
+ .where([gt(abs('dy'), abs('dx')), lt('i', abs('dy'))]),
88
+ Query
89
+ .select(groups, { x: 'x0', y: 'y0' })
90
+ .from('pairs')
91
+ .where(isNull('dx'))
92
+ );
93
+
94
+ // filter raster, normalize columns for each series
95
+ const points = Query
96
+ .from('raster')
97
+ .select(groups, 'x', 'y',
98
+ normalize
99
+ ? { w: div(1, count().partitionby(['x'].concat(groups))) }
100
+ : null
101
+ )
102
+ .where([lte(0, 'x'), lt('x', xn), lte(0, 'y'), lt('y', yn)]);
103
+
104
+ // sum normalized, rasterized series into output grids
105
+ return Query
106
+ .with({ pairs, indices, raster, points })
107
+ .from('points')
108
+ .select(groupby, {
109
+ index: add('x', mul('y', int32(xn))),
110
+ density: normalize ? sum('w') : count()
111
+ })
112
+ .groupby('index', groupby);
113
+ }
@@ -0,0 +1,38 @@
1
+ import { Query } from '../ast/query.js';
2
+ import { argmax, argmin, max, min } from '../functions/aggregate.js';
3
+ import { int32 } from '../functions/cast.js';
4
+ import { floor } from '../functions/numeric.js';
5
+
6
+ /**
7
+ * M4 is an optimization for value-preserving time-series aggregation
8
+ * (https://www.vldb.org/pvldb/vol7/p797-jugel.pdf). This implementation uses
9
+ * an efficient version with a single scan and the aggregate functions
10
+ * argmin and argmax, following https://arxiv.org/pdf/2306.03714.pdf.
11
+ * This method can bin along either the *x* or *y* dimension, as determined
12
+ * by the caller-provided *bin* expression.
13
+ * @param {import('../types.js').FromExpr} input The base query or table.
14
+ * @param {import('../types.js').ExprValue} bin An expression that maps
15
+ * time-series values to fractional pixel positions.
16
+ * @param {string} x The x dimension column name.
17
+ * @param {string} y The y dimension column name.
18
+ * @param {import('../ast/node.js').ExprNode[]} [groups] Additional
19
+ * groupby columns, for example for faceted charts.
20
+ * @returns {Query} The resulting M4 query.
21
+ */
22
+ export function m4(input, bin, x, y, groups = []) {
23
+ const pixel = int32(floor(bin));
24
+
25
+ const q = (sel) => Query
26
+ .from(input)
27
+ .select(sel)
28
+ .groupby(pixel, groups);
29
+
30
+ return Query
31
+ .union(
32
+ q([{ [x]: min(x), [y]: argmin(y, x) }, ...groups]),
33
+ q([{ [x]: max(x), [y]: argmax(y, x) }, ...groups]),
34
+ q([{ [x]: argmin(x, y), [y]: min(y) }, ...groups]),
35
+ q([{ [x]: argmax(x, y), [y]: max(y) }, ...groups])
36
+ )
37
+ .orderby(groups, x);
38
+ }