@uwdata/mosaic-core 0.4.0 → 0.6.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.
- package/README.md +3 -1
- package/dist/mosaic-core.js +895 -484
- package/dist/mosaic-core.min.js +5 -4
- package/package.json +5 -5
- package/src/Catalog.js +1 -0
- package/src/Coordinator.js +9 -2
- package/src/DataCubeIndexer.js +10 -39
- package/src/MosaicClient.js +17 -3
- package/src/QueryConsolidator.js +8 -1
- package/src/connectors/wasm.js +48 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Scalable and extensible linked data views.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mosaic",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"prepublishOnly": "npm run test && npm run lint && npm run build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@duckdb/duckdb-wasm": "^1.28.0",
|
|
32
|
-
"@uwdata/mosaic-sql": "^0.
|
|
33
|
-
"apache-arrow": "^
|
|
31
|
+
"@duckdb/duckdb-wasm": "^1.28.1-dev106.0",
|
|
32
|
+
"@uwdata/mosaic-sql": "^0.6.0",
|
|
33
|
+
"apache-arrow": "^14.0.1"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "51517b28e916e355f4ce0dc6e98aef3a1db3f7b2"
|
|
36
36
|
}
|
package/src/Catalog.js
CHANGED
package/src/Coordinator.js
CHANGED
|
@@ -23,9 +23,13 @@ export function coordinator(instance) {
|
|
|
23
23
|
|
|
24
24
|
export class Coordinator {
|
|
25
25
|
constructor(db = socketConnector(), options = {}) {
|
|
26
|
+
const {
|
|
27
|
+
logger = console,
|
|
28
|
+
manager = QueryManager()
|
|
29
|
+
} = options;
|
|
26
30
|
this.catalog = new Catalog(this);
|
|
27
|
-
this.manager =
|
|
28
|
-
this.logger(
|
|
31
|
+
this.manager = manager;
|
|
32
|
+
this.logger(logger);
|
|
29
33
|
this.configure(options);
|
|
30
34
|
this.databaseConnector(db);
|
|
31
35
|
this.clear();
|
|
@@ -68,6 +72,7 @@ export class Coordinator {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
exec(query, { priority = Priority.Normal } = {}) {
|
|
75
|
+
query = Array.isArray(query) ? query.join(';\n') : query;
|
|
71
76
|
return this.manager.request({ type: 'exec', query }, priority);
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -123,6 +128,7 @@ export class Coordinator {
|
|
|
123
128
|
throw new Error('Client already connected.');
|
|
124
129
|
}
|
|
125
130
|
clients.add(client); // mark as connected
|
|
131
|
+
client.coordinator = this;
|
|
126
132
|
|
|
127
133
|
// retrieve field statistics
|
|
128
134
|
const fields = client.fields();
|
|
@@ -154,5 +160,6 @@ export class Coordinator {
|
|
|
154
160
|
if (!clients.has(client)) return;
|
|
155
161
|
clients.delete(client);
|
|
156
162
|
filterGroups.get(client.filterBy)?.remove(client);
|
|
163
|
+
client.coordinator = null;
|
|
157
164
|
}
|
|
158
165
|
}
|
package/src/DataCubeIndexer.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Query, and,
|
|
1
|
+
import { Query, and, create, isBetween, scaleTransform, sql } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { fnv_hash } from './util/hash.js';
|
|
3
3
|
|
|
4
|
-
const identity = x => x;
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* Build and query optimized indices ("data cubes") for fast computation of
|
|
8
6
|
* groupby aggregate queries over compatible client queries and selections.
|
|
@@ -149,7 +147,7 @@ function getActiveView(clause) {
|
|
|
149
147
|
);
|
|
150
148
|
}
|
|
151
149
|
} else if (type === 'point') {
|
|
152
|
-
predicate =
|
|
150
|
+
predicate = x => x;
|
|
153
151
|
columns = Object.fromEntries(columns.map(col => [col.toString(), col]));
|
|
154
152
|
} else {
|
|
155
153
|
return null; // unsupported type
|
|
@@ -159,42 +157,15 @@ function getActiveView(clause) {
|
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
function binInterval(scale, pixelSize) {
|
|
162
|
-
const {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
case 'log':
|
|
171
|
-
lift = Math.log;
|
|
172
|
-
toSql = c => sql`LN(${asColumn(c)})`;
|
|
173
|
-
break;
|
|
174
|
-
case 'symlog':
|
|
175
|
-
// TODO: support log constants other than 1?
|
|
176
|
-
lift = x => Math.sign(x) * Math.log1p(Math.abs(x));
|
|
177
|
-
toSql = c => (c = asColumn(c), sql`SIGN(${c}) * LN(1 + ABS(${c}))`);
|
|
178
|
-
break;
|
|
179
|
-
case 'sqrt':
|
|
180
|
-
lift = Math.sqrt;
|
|
181
|
-
toSql = c => sql`SQRT(${asColumn(c)})`;
|
|
182
|
-
break;
|
|
183
|
-
case 'utc':
|
|
184
|
-
case 'time':
|
|
185
|
-
lift = x => +x;
|
|
186
|
-
toSql = c => c instanceof Date ? +c : epoch_ms(asColumn(c));
|
|
187
|
-
break;
|
|
160
|
+
const { apply, sqlApply } = scaleTransform(scale);
|
|
161
|
+
if (apply) {
|
|
162
|
+
const { domain, range } = scale;
|
|
163
|
+
const lo = apply(Math.min(...domain));
|
|
164
|
+
const hi = apply(Math.max(...domain));
|
|
165
|
+
const a = (Math.abs(range[1] - range[0]) / (hi - lo)) / pixelSize;
|
|
166
|
+
const s = pixelSize === 1 ? '' : `${pixelSize}::INTEGER * `;
|
|
167
|
+
return value => sql`${s}FLOOR(${a}::DOUBLE * (${sqlApply(value)} - ${lo}::DOUBLE))::INTEGER`;
|
|
188
168
|
}
|
|
189
|
-
return lift ? binFunction(domain, range, pixelSize, lift, toSql) : null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function binFunction(domain, range, pixelSize, lift, toSql) {
|
|
193
|
-
const lo = lift(Math.min(domain[0], domain[1]));
|
|
194
|
-
const hi = lift(Math.max(domain[0], domain[1]));
|
|
195
|
-
const a = (Math.abs(lift(range[1]) - lift(range[0])) / (hi - lo)) / pixelSize;
|
|
196
|
-
const s = pixelSize === 1 ? '' : `${pixelSize}::INTEGER * `;
|
|
197
|
-
return value => sql`${s}FLOOR(${a}::DOUBLE * (${toSql(value)} - ${lo}::DOUBLE))::INTEGER`;
|
|
198
169
|
}
|
|
199
170
|
|
|
200
171
|
const NO_INDEX = { from: NaN };
|
package/src/MosaicClient.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { coordinator } from './Coordinator.js';
|
|
2
1
|
import { throttle } from './util/throttle.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -14,6 +13,21 @@ export class MosaicClient {
|
|
|
14
13
|
constructor(filterSelection) {
|
|
15
14
|
this._filterBy = filterSelection;
|
|
16
15
|
this._requestUpdate = throttle(() => this.requestQuery(), true);
|
|
16
|
+
this._coordinator = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Return this client's connected coordinator.
|
|
21
|
+
*/
|
|
22
|
+
get coordinator() {
|
|
23
|
+
return this._coordinator;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Set this client's connected coordinator.
|
|
28
|
+
*/
|
|
29
|
+
set coordinator(coordinator) {
|
|
30
|
+
this._coordinator = coordinator;
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
/**
|
|
@@ -63,7 +77,7 @@ export class MosaicClient {
|
|
|
63
77
|
|
|
64
78
|
/**
|
|
65
79
|
* Called by the coordinator to return a query result.
|
|
66
|
-
*
|
|
80
|
+
*
|
|
67
81
|
* @param {*} data the query result
|
|
68
82
|
* @returns {this}
|
|
69
83
|
*/
|
|
@@ -86,7 +100,7 @@ export class MosaicClient {
|
|
|
86
100
|
*/
|
|
87
101
|
requestQuery(query) {
|
|
88
102
|
const q = query || this.query(this.filterBy?.predicate(this));
|
|
89
|
-
return
|
|
103
|
+
return this._coordinator.requestQuery(this, q);
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
/**
|
package/src/QueryConsolidator.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { Query, Ref } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { queryResult } from './util/query-result.js';
|
|
3
3
|
|
|
4
|
+
function wait(callback) {
|
|
5
|
+
const method = typeof requestAnimationFrame !== 'undefined'
|
|
6
|
+
? requestAnimationFrame
|
|
7
|
+
: typeof setImmediate !== 'undefined' ? setImmediate : setTimeout;
|
|
8
|
+
return method(callback);
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
/**
|
|
5
12
|
* Create a consolidator to combine structurally compatible queries.
|
|
6
13
|
* @param {*} enqueue Query manager enqueue method
|
|
@@ -30,7 +37,7 @@ export function consolidator(enqueue, cache, record) {
|
|
|
30
37
|
if (entry.request.type === 'arrow') {
|
|
31
38
|
// wait one frame, gather an ordered list of queries
|
|
32
39
|
// only Apache Arrow is supported, so we can project efficiently
|
|
33
|
-
id = id ||
|
|
40
|
+
id = id || wait(() => run());
|
|
34
41
|
pending.push({ entry, priority, index: pending.length });
|
|
35
42
|
} else {
|
|
36
43
|
enqueue(entry, priority);
|
package/src/connectors/wasm.js
CHANGED
|
@@ -1,14 +1,57 @@
|
|
|
1
1
|
import * as duckdb from '@duckdb/duckdb-wasm';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
const
|
|
5
|
-
|
|
3
|
+
export function wasmConnector(options = {}) {
|
|
4
|
+
const { duckdb, connection, ...opts } = options;
|
|
5
|
+
let db = duckdb;
|
|
6
|
+
let con = connection;
|
|
7
|
+
let loadPromise;
|
|
8
|
+
|
|
9
|
+
function load() {
|
|
10
|
+
if (!loadPromise) {
|
|
11
|
+
// use a loading promise to avoid race conditions
|
|
12
|
+
// synchronizes multiple callees on the same load
|
|
13
|
+
loadPromise = (db
|
|
14
|
+
? Promise.resolve(db)
|
|
15
|
+
: initDatabase(opts).then(result => db = result))
|
|
16
|
+
.then(db => db.connect())
|
|
17
|
+
.then(result => con = result);
|
|
18
|
+
}
|
|
19
|
+
return loadPromise;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the backing DuckDB-WASM instance.
|
|
24
|
+
* Will lazily initialize DuckDB-WASM if not already loaded.
|
|
25
|
+
* @returns {duckdb.AsyncDuckDB} The DuckDB-WASM instance.
|
|
26
|
+
*/
|
|
27
|
+
async function getDuckDB() {
|
|
28
|
+
if (!db) await load();
|
|
29
|
+
return db;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the backing DuckDB-WASM connection.
|
|
34
|
+
* Will lazily initialize DuckDB-WASM if not already loaded.
|
|
35
|
+
* @returns {duckdb.AsyncDuckDBConnection} The DuckDB-WASM connection.
|
|
36
|
+
*/
|
|
37
|
+
async function getConnection() {
|
|
38
|
+
if (!con) await load();
|
|
39
|
+
return con;
|
|
40
|
+
}
|
|
6
41
|
|
|
7
42
|
return {
|
|
8
|
-
|
|
9
|
-
|
|
43
|
+
getDuckDB,
|
|
44
|
+
getConnection,
|
|
45
|
+
/**
|
|
46
|
+
* Query the DuckDB-WASM instance.
|
|
47
|
+
* @param {object} query
|
|
48
|
+
* @param {string} [query.type] The query type: 'exec', 'arrow', or 'json'.
|
|
49
|
+
* @param {string} query.sql A SQL query string.
|
|
50
|
+
* @returns the query result
|
|
51
|
+
*/
|
|
10
52
|
query: async query => {
|
|
11
53
|
const { type, sql } = query;
|
|
54
|
+
const con = await getConnection();
|
|
12
55
|
const result = await con.query(sql);
|
|
13
56
|
return type === 'exec' ? undefined
|
|
14
57
|
: type === 'arrow' ? result
|