@thi.ng/column-store 0.1.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/table.js ADDED
@@ -0,0 +1,176 @@
1
+ import { isArray } from "@thi.ng/checks/is-array";
2
+ import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
3
+ import {
4
+ FLAG_BITMAP,
5
+ FLAG_DICT,
6
+ FLAG_RLE,
7
+ FLAG_UNIQUE
8
+ } from "./api.js";
9
+ import { ArrayColumn } from "./columns/array.js";
10
+ import { DictArrayColumn } from "./columns/dict-array.js";
11
+ import { DictColumn } from "./columns/dict.js";
12
+ import { PlainColumn } from "./columns/plain.js";
13
+ import { TypedArrayColumn } from "./columns/typedarray.js";
14
+ import { __columnError } from "./internal/checks.js";
15
+ import { Query } from "./query.js";
16
+ class Table {
17
+ opts;
18
+ schema = {};
19
+ columns = {};
20
+ length = 0;
21
+ static load(serialized, opts) {
22
+ const table = new Table(serialized.schema, opts);
23
+ table.length = serialized.length;
24
+ for (let id in table.columns) {
25
+ table.columns[id].load(serialized.columns[id]);
26
+ }
27
+ return table;
28
+ }
29
+ constructor(schema, opts) {
30
+ this.opts = { ...opts };
31
+ for (let id in schema) this.addColumn(id, schema[id]);
32
+ }
33
+ query(terms) {
34
+ return new Query(this, terms);
35
+ }
36
+ addColumn(id, spec) {
37
+ if (this.columns[id]) __columnError(id, "already exists");
38
+ const $spec = {
39
+ cardinality: [1, 1],
40
+ flags: 0,
41
+ ...spec
42
+ };
43
+ this.validateColumnSpec(id, $spec);
44
+ this.schema[id] = $spec;
45
+ this.columns[id] = COLUMN_TYPES[spec.type].impl(this, id, $spec);
46
+ }
47
+ removeColumn(id) {
48
+ if (this.columns[id]) return false;
49
+ delete this.columns[id];
50
+ delete this.schema[id];
51
+ return true;
52
+ }
53
+ *[Symbol.iterator]() {
54
+ for (let i = 0; i < this.length; i++) yield this.getRow(i);
55
+ }
56
+ reindex() {
57
+ for (let id in this.columns) this.columns[id].reindex();
58
+ }
59
+ addRow(row) {
60
+ this.validateRow(row);
61
+ const { columns, length: rowID } = this;
62
+ for (let id in columns) {
63
+ columns[id].setRow(rowID, row[id]);
64
+ }
65
+ this.length++;
66
+ }
67
+ addRows(rows) {
68
+ for (let row of rows) this.addRow(row);
69
+ }
70
+ updateRow(i, row) {
71
+ if (i < 0 || i >= this.length) illegalArgs(`row ID: ${i}`);
72
+ this.validateRow(row);
73
+ for (let id in this.columns) {
74
+ this.columns[id].setRow(i, row[id]);
75
+ }
76
+ }
77
+ removeRow(i) {
78
+ if (i < 0 || i >= this.length) illegalArgs(`row ID: ${i}`);
79
+ for (let id in this.columns) {
80
+ this.columns[id].removeRow(i);
81
+ }
82
+ this.length--;
83
+ }
84
+ getRow(i, safe = true) {
85
+ if (safe && (i < 0 || i >= this.length)) return;
86
+ const row = {};
87
+ for (let id in this.columns) {
88
+ row[id] = this.columns[id].getRow(i);
89
+ }
90
+ return row;
91
+ }
92
+ getPartialRow(i, columns, safe = true) {
93
+ if (safe && (i < 0 || i >= this.length)) return;
94
+ const row = {};
95
+ for (let id of columns) {
96
+ row[id] = this.columns[id]?.getRow(i);
97
+ }
98
+ return row;
99
+ }
100
+ validateRow(row) {
101
+ const { columns } = this;
102
+ for (let id in columns) {
103
+ if (!columns[id].validate(row[id]))
104
+ __columnError(id, `invalid value`);
105
+ }
106
+ }
107
+ validateColumnSpec(id, spec) {
108
+ const def = COLUMN_TYPES[spec.type];
109
+ if (!def) __columnError(id, `unknown type: ${spec.type}`);
110
+ if (spec.flags & ~(def.flags ?? 0))
111
+ __columnError(
112
+ id,
113
+ `unsupported flags for column type: ${spec.type}`
114
+ );
115
+ const [min, max] = spec.cardinality;
116
+ if (max < min)
117
+ __columnError(id, `wrong cardinality: ${spec.cardinality}`);
118
+ if (min < def.cardinality[0] || max > def.cardinality[1])
119
+ __columnError(id, `wrong cardimality`);
120
+ if (def.required && min === 0 && spec.default == null)
121
+ __columnError(id, `missing default value`);
122
+ if (spec.default != null) {
123
+ if (max > 1 !== isArray(spec.default))
124
+ __columnError(id, `default value`);
125
+ }
126
+ }
127
+ toJSON() {
128
+ return {
129
+ schema: this.schema,
130
+ columns: this.columns,
131
+ length: this.length
132
+ };
133
+ }
134
+ }
135
+ const $typed = {
136
+ impl: (table, id) => new TypedArrayColumn(id, table),
137
+ flags: FLAG_BITMAP | FLAG_RLE,
138
+ cardinality: [0, 1],
139
+ required: true
140
+ };
141
+ const $float = { ...$typed, flags: FLAG_BITMAP };
142
+ const $untyped = {
143
+ impl: (table, id, { flags, cardinality: [min, max], default: d }) => {
144
+ const isDict = flags & FLAG_DICT;
145
+ if (flags & FLAG_RLE) {
146
+ if (!isDict || max > 1 || min === 0 && d == null) {
147
+ __columnError(id, `RLE encoding not supported`);
148
+ }
149
+ }
150
+ return max > 1 ? new (isDict ? DictArrayColumn : ArrayColumn)(id, table) : new (isDict ? DictColumn : PlainColumn)(id, table);
151
+ },
152
+ flags: FLAG_BITMAP | FLAG_DICT | FLAG_UNIQUE | FLAG_RLE,
153
+ cardinality: [0, -1 >>> 0]
154
+ };
155
+ const COLUMN_TYPES = {
156
+ u8: $typed,
157
+ i8: $typed,
158
+ u16: $typed,
159
+ i16: $typed,
160
+ u32: $typed,
161
+ i32: $typed,
162
+ f32: $float,
163
+ f64: $float,
164
+ num: $untyped,
165
+ str: $untyped
166
+ };
167
+ const registerColumnType = (type, spec) => {
168
+ if (COLUMN_TYPES[type])
169
+ illegalArgs(`column type ${type} already registered`);
170
+ COLUMN_TYPES[type] = spec;
171
+ };
172
+ export {
173
+ COLUMN_TYPES,
174
+ Table,
175
+ registerColumnType
176
+ };