Skip to content
Snippets Groups Projects
Commit 11a3aa16 authored by David Sehnal's avatar David Sehnal
Browse files

added database type

parent 40a35dea
Branches
Tags
No related merge requests found
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Database from './database/database'
import Table from './database/table'
import Column from './database/column'
import * as ColumnHelpers from './database/column-helpers'
export { Database, Table, Column, ColumnHelpers }
\ No newline at end of file
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ColumnHelpers from '../column-helpers'
import Column from '../column'
import Table from '../table'
describe('column', () => {
const cc = Column.ofConst(10, 2, Column.Type.int);
const arr = Column.ofArray({ array: [1, 2, 3, 4], type: Column.Type.int });
const arrWindow = Column.window(arr, 1, 3);
const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), type: Column.Type.int });
const typedWindow = Column.window(typed, 1, 3);
const numStr = Column.ofArray({ array: [1, 2] as any, type: Column.Type.str });
it('constant', () => {
expect(cc.rowCount).toBe(2);
expect(cc.value(0)).toBe(10);
});
it('arr', () => {
expect(arr.rowCount).toBe(4);
expect(arr.value(1)).toBe(2);
expect(arrWindow.value(0)).toBe(2);
expect(arrWindow.rowCount).toBe(2);
});
it('typed', () => {
expect(typedWindow.value(0)).toBe(2);
expect(typedWindow.rowCount).toBe(2);
expect(ColumnHelpers.isTypedArray(typedWindow.toArray())).toBe(true);
});
it('numStr', () => {
expect(numStr.value(0)).toBe('1');
expect(numStr.toArray()).toEqual(['1', '2']);
});
it('view', () => {
expect(Column.view(arr, [1, 0, 3, 2]).toArray()).toEqual([2, 1, 4, 3]);
expect(Column.view(arr, [1, 3]).toArray()).toEqual([2, 4]);
});
it('map to array', () => {
expect(Column.mapToArray(arrWindow, x => x + 1)).toEqual([3, 4]);
});
})
describe('table', () => {
const schema = {
x: Column.Type.int,
n: Column.Type.str
};
it('ofRows', () => {
const t = Table.ofRows(schema, [
{ x: 10, n: 'row1' },
{ x: -1, n: 'row2' },
]);
expect(t.x.toArray()).toEqual([10, -1]);
expect(t.n.toArray()).toEqual(['row1', 'row2']);
});
it('ofColumns', () => {
const t = Table.ofColumns(schema, {
x: Column.ofArray({ array: [10, -1], type: Column.Type.int }),
n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }),
});
expect(t.x.toArray()).toEqual([10, -1]);
expect(t.n.toArray()).toEqual(['row1', 'row2']);
});
it('ofArrays', () => {
const t = Table.ofArrays(schema, {
x: [10, -1],
n: ['row1', 'row2'],
});
expect(t.x.toArray()).toEqual([10, -1]);
expect(t.n.toArray()).toEqual(['row1', 'row2']);
});
it('pickColumns', () => {
const t = Table.ofColumns(schema, {
x: Column.ofArray({ array: [10, -1], type: Column.Type.int }),
n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }),
});
const s = { x: Column.Type.int, y: Column.Type.int };
const picked = Table.pickColumns(s, t, { y: Column.ofArray({ array: [3, 4], type: Column.Type.int })});
expect(picked._columns).toEqual(['x', 'y']);
expect(picked._rowCount).toEqual(2);
expect(picked.x.toArray()).toEqual([10, -1]);
expect(picked.y.toArray()).toEqual([3, 4]);
});
it('view', () => {
const t = Table.ofColumns(schema, {
x: Column.ofArray({ array: [10, -1], type: Column.Type.int }),
n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }),
});
const s = { x: Column.Type.int };
const view = Table.view(t, s, [1]);
expect(view._columns).toEqual(['x']);
expect(view._rowCount).toEqual(1);
expect(view.x.toArray()).toEqual([-1]);
});
it('sort', () => {
const t = Table.ofColumns<typeof schema>(schema, {
x: Column.ofArray({ array: [10, -1], type: Column.Type.int }),
n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }),
});
const { x } = t;
const sorted = Table.sort(t, (i, j) => x.value(i) - x.value(j))
expect(sorted.x.toArray()).toEqual([-1, 10]);
expect(sorted.n.toArray()).toEqual(['row2', 'row1']);
});
});
\ No newline at end of file
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Column from './column'
export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams<any>) {
const start = params && typeof params.start !== 'undefined' ? Math.max(Math.min(params.start, rowCount - 1), 0) : 0;
const end = params && typeof params.end !== 'undefined' ? Math.min(params.end, rowCount) : rowCount;
return { start, end };
}
export function createArray(rowCount: number, params?: Column.ToArrayParams<any>) {
const c = params && typeof params.array !== 'undefined' ? params.array : Array;
const { start, end } = getArrayBounds(rowCount, params);
return { array: new c(end - start) as any[], start, end };
}
export function fillArrayValues(value: (row: number) => any, target: any[], start: number) {
for (let i = 0, _e = target.length; i < _e; i++) target[i] = value(start + i);
return target;
}
export function createAndFillArray(rowCount: number, value: (row: number) => any, params?: Column.ToArrayParams<any>) {
const { array, start } = createArray(rowCount, params);
return fillArrayValues(value, array, start);
}
export function isTypedArray(data: any): boolean {
return !!data.buffer && typeof data.byteLength === 'number' && typeof data.BYTES_PER_ELEMENT === 'number';
}
export function typedArrayWindow(data: any, params?: Column.ToArrayParams<any>): ReadonlyArray<number> {
const { constructor, buffer, length, byteOffset, BYTES_PER_ELEMENT } = data;
const { start, end } = getArrayBounds(length, params);
if (start === 0 && end === length) return data;
return new constructor(buffer, byteOffset + BYTES_PER_ELEMENT * start, Math.min(length, end - start));
}
\ No newline at end of file
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ColumnHelpers from './column-helpers'
interface Column<T> {
readonly '@type': Column.Type,
readonly '@array': ArrayLike<any> | undefined,
readonly isDefined: boolean,
readonly rowCount: number,
value(row: number): T,
valueKind(row: number): Column.ValueKind,
toArray(params?: Column.ToArrayParams<T>): ReadonlyArray<T>,
areValuesEqual(rowA: number, rowB: number): boolean
}
namespace Column {
export type Type<T = any> = Type.Str | Type.Int | Type.Float | Type.Vector | Type.Matrix | Type.Aliased<T>
export type ArrayCtor<T> = { new(size: number): ArrayLike<T> }
export namespace Type {
export type Str = { T: string, kind: 'str' }
export type Int = { T: number, kind: 'int' }
export type Float = { T: number, kind: 'float' }
export type Vector = { T: number[], dim: number, kind: 'vector' };
export type Matrix = { T: number[][], rows: number, cols: number, kind: 'matrix' };
export type Aliased<T> = { T: T } & { kind: 'str' | 'int' | 'float' }
export const str: Str = { T: '', kind: 'str' };
export const int: Int = { T: 0, kind: 'int' };
export const float: Float = { T: 0, kind: 'float' };
export function vector(dim: number): Vector { return { T: [] as number[], dim, kind: 'vector' }; }
export function matrix(rows: number, cols: number): Matrix { return { T: [] as number[][], rows, cols, kind: 'matrix' }; }
export function aliased<T>(t: Type): Aliased<T> { return t as any as Aliased<T>; }
}
export interface ToArrayParams<T> {
array?: ArrayCtor<T>,
start?: number,
/** Last row (exclusive) */
end?: number
}
export interface LambdaSpec<T extends Type> {
value: (row: number) => T['T'],
rowCount: number,
type: T,
valueKind?: (row: number) => ValueKind,
}
export interface ArraySpec<T extends Type> {
array: ArrayLike<T['T']>,
type: T,
valueKind?: (row: number) => ValueKind
}
export interface MapSpec<S extends Type, T extends Type> {
f: (v: S['T']) => T['T'],
type: T,
valueKind?: (row: number) => ValueKind,
}
export const enum ValueKind { Present = 0, NotPresent = 1, Unknown = 2 }
export function Undefined<T extends Type>(rowCount: number, type: T): Column<T['T']> {
return constColumn(type['T'], rowCount, type, ValueKind.NotPresent);
}
export function ofConst<T extends Type>(v: T['T'], rowCount: number, type: T): Column<T['T']> {
return constColumn(v, rowCount, type, ValueKind.Present);
}
export function ofLambda<T extends Type>(spec: LambdaSpec<T>): Column<T['T']> {
return lambdaColumn(spec);
}
export function ofArray<T extends Column.Type>(spec: Column.ArraySpec<T>): Column<T['T']> {
return arrayColumn(spec);
}
export function ofIntArray(array: ArrayLike<number>) {
return arrayColumn({ array, type: Type.int });
}
export function ofFloatArray(array: ArrayLike<number>) {
return arrayColumn({ array, type: Type.float });
}
export function ofStringArray(array: ArrayLike<string>) {
return arrayColumn({ array, type: Type.str });
}
export function window<T>(column: Column<T>, start: number, end: number) {
return windowColumn(column, start, end);
}
export function view<T>(column: Column<T>, indices: ArrayLike<number>, checkIndentity = true) {
return columnView(column, indices, checkIndentity);
}
/** A map of the 1st occurence of each value. */
export function createFirstIndexMap<T>(column: Column<T>) {
return createFirstIndexMapOfColumn(column);
}
export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> {
return mapToArrayImpl(column, f, ctor || Array);
}
export function areEqual<T>(a: Column<T>, b: Column<T>) {
return areColumnsEqual(a, b);
}
/** Makes the column backned by an array. Useful for columns that accessed often. */
export function asArrayColumn<T>(c: Column<T>, array?: ArrayCtor<T>): Column<T> {
if (c['@array']) return c;
if (!c.isDefined) return Undefined(c.rowCount, c['@type']) as any as Column<T>;
return arrayColumn({ array: c.toArray({ array }), type: c['@type'] as any, valueKind: c.valueKind });
}
}
export default Column;
function createFirstIndexMapOfColumn<T>(c: Column<T>): Map<T, number> {
const map = new Map<T, number>();
for (let i = 0, _i = c.rowCount; i < _i; i++) {
const v = c.value(i);
if (!map.has(v)) return map.set(c.value(i), i);
}
return map;
}
function constColumn<T extends Column.Type>(v: T['T'], rowCount: number, type: T, valueKind: Column.ValueKind): Column<T['T']> {
const value: Column<T['T']>['value'] = row => v;
return {
'@type': type,
'@array': void 0,
isDefined: valueKind === Column.ValueKind.Present,
rowCount,
value,
valueKind: row => valueKind,
toArray: params => {
const { array } = ColumnHelpers.createArray(rowCount, params);
for (let i = 0, _i = array.length; i < _i; i++) array[i] = v;
return array;
},
areValuesEqual: (rowA, rowB) => true
}
}
function lambdaColumn<T extends Column.Type>({ value, valueKind, rowCount, type }: Column.LambdaSpec<T>): Column<T['T']> {
return {
'@type': type,
'@array': void 0,
isDefined: true,
rowCount,
value,
valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
toArray: params => {
const { array, start } = ColumnHelpers.createArray(rowCount, params);
for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start);
return array;
},
areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB)
}
}
function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.ArraySpec<T>): Column<T['T']> {
const rowCount = array.length;
const value: Column<T['T']>['value'] = type.kind === 'str'
? row => { const v = array[row]; return typeof v === 'string' ? v : '' + v; }
: row => array[row];
const isTyped = ColumnHelpers.isTypedArray(array);
return {
'@type': type,
'@array': array,
isDefined: true,
rowCount,
value,
valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
toArray: type.kind === 'str'
? params => {
const { start, end } = ColumnHelpers.getArrayBounds(rowCount, params);
const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any;
for (let i = 0, _i = end - start; i < _i; i++) {
const v = array[start + i];
ret[i] = typeof v === 'string' ? v : '' + v;
}
return ret;
}
: isTyped
? params => ColumnHelpers.typedArrayWindow(array, params) as any as ReadonlyArray<T>
: params => {
const { start, end } = ColumnHelpers.getArrayBounds(rowCount, params);
if (start === 0 && end === array.length) return array as ReadonlyArray<T['T']>;
const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any;
for (let i = 0, _i = end - start; i < _i; i++) ret[i] = array[start + i];
return ret;
},
areValuesEqual: (rowA, rowB) => array[rowA] === array[rowB]
}
}
function windowColumn<T>(column: Column<T>, start: number, end: number) {
if (!column.isDefined) return Column.Undefined(end - start, column['@type']);
if (!!column['@array'] && ColumnHelpers.isTypedArray(column['@array'])) return windowTyped(column, start, end);
return windowFull(column, start, end);
}
function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> {
const array = ColumnHelpers.typedArrayWindow(c['@array'], { start, end });
return arrayColumn({ array, type: c['@type'], valueKind: c.valueKind }) as any;
}
function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> {
const v = c.value, vk = c.valueKind, ave = c.areValuesEqual;
const value: Column<T>['value'] = start === 0 ? v : row => v(row + start);
const rowCount = end - start;
return {
'@type': c['@type'],
'@array': void 0,
isDefined: c.isDefined,
rowCount,
value,
valueKind: start === 0 ? vk : row => vk(row + start),
toArray: params => {
const { array } = ColumnHelpers.createArray(rowCount, params);
for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start);
return array;
},
areValuesEqual: start === 0 ? ave : (rowA, rowB) => ave(rowA + start, rowB + start)
};
}
function isIdentity(map: ArrayLike<number>, rowCount: number) {
if (map.length !== rowCount) return false;
for (let i = 0, _i = map.length; i < _i; i++) {
if (map[i] !== i) return false;
}
return true;
}
function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> {
if (!c.isDefined) return c;
if (checkIdentity && isIdentity(map, c.rowCount)) return c;
if (!!c['@array']) return arrayView(c, map);
return viewFull(c, map);
}
function arrayView<T>(c: Column<T>, map: ArrayLike<number>): Column<T> {
const array = c['@array']!;
const ret = new (array as any).constructor(map.length);
for (let i = 0, _i = map.length; i < _i; i++) ret[i] = array[map[i]];
return arrayColumn({ array: ret, type: c['@type'], valueKind: c.valueKind });
}
function viewFull<T>(c: Column<T>, map: ArrayLike<number>): Column<T> {
const v = c.value, vk = c.valueKind, ave = c.areValuesEqual;
const value: Column<T>['value'] = row => v(map[row]);
const rowCount = map.length;
return {
'@type': c['@type'],
'@array': void 0,
isDefined: c.isDefined,
rowCount,
value,
valueKind: row => vk(map[row]),
toArray: params => {
const { array } = ColumnHelpers.createArray(rowCount, params);
for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(map[i]);
return array;
},
areValuesEqual: (rowA, rowB) => ave(map[rowA], map[rowB])
};
}
function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCtor<S>): ArrayLike<S> {
const ret = new ctor(c.rowCount) as any;
for (let i = 0, _i = c.rowCount; i < _i; i++) ret[i] = f(c.value(i));
return ret;
}
function areColumnsEqual(a: Column<any>, b: Column<any>) {
if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a['@type'].kind !== b['@type'].kind) return false;
if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b);
return areValuesEqual(a, b);
}
function areArraysEqual(a: Column<any>, b: Column<any>) {
const xs = a['@array']!, ys = b['@array']!;
for (let i = 0, _i = a.rowCount; i < _i; i++) {
if (xs[i] !== ys[i]) return false;
}
return true;
}
function areValuesEqual(a: Column<any>, b: Column<any>) {
const va = a.value, vb = b.value;
for (let i = 0, _i = a.rowCount; i < _i; i++) {
if (va(i) !== vb(i)) return false;
}
return true;
}
\ No newline at end of file
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Table from './table'
/** A collection of tables */
type Database<Schema extends Database.Schema> = {
readonly _name: string,
readonly _tableNames: string[],
readonly _schema: Schema
} & Database.Tables<Schema>
namespace Database {
export type Tables<S extends Schema> = { [T in keyof Schema]: Table<S[T]> }
export type Schema = { [table: string]: Table.Schema }
export function ofTables<S extends Schema, Db = Database<S>>(name: string, schema: Schema, tables: Tables<S>): Db {
const keys = Object.keys(tables);
const ret = Object.create(null);
const tableNames: string[] = [];
ret._name = name;
ret._tableNames = tableNames;
ret._schema = schema;
for (const k of keys) {
if (!Table.is(tables[k])) continue;
ret[k] = tables[k];
tableNames[tableNames.length] = k;
}
return ret;
}
}
export default Database
\ No newline at end of file
/**
* Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import Column from './column'
import { sortArray } from '../sort'
/** A collection of columns */
type Table<Schema extends Table.Schema> = {
readonly _rowCount: number,
readonly _columns: ReadonlyArray<string>,
readonly _schema: Schema
} & Table.Columns<Schema>
/** An immutable table */
namespace Table {
export type Schema = { [field: string]: Column.Type }
export type Columns<S extends Schema> = { [C in keyof S]: Column<S[C]['T']> }
export type Row<S extends Schema> = { [C in keyof S]: S[C]['T'] }
export type Arrays<S extends Schema> = { [C in keyof S]: ArrayLike<S[C]['T']> }
export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & { [C in keyof S]?: Column<S[C]['T']> }
export function is(t: any): t is Table<any> {
return t && typeof t._rowCount === 'number' && !!t._columns && !!t._schema;
}
export function pickColumns<S extends Schema>(schema: S, table: PartialTable<S>, guard: Partial<Columns<S>> = {}): Table<S> {
const ret = Object.create(null);
const keys = Object.keys(schema);
ret._rowCount = table._rowCount;
ret._columns = keys;
ret._schema = schema;
for (const k of keys) {
if (!!table[k]) ret[k] = table[k];
else if (!!guard[k]) ret[k] = guard[k];
else throw Error(`Cannot find column '${k}'.`);
}
return ret;
}
export function ofColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, columns: Columns<S>): R {
const _columns = Object.keys(columns);
const _rowCount = columns[_columns[0]].rowCount;
return { _rowCount, _columns, _schema: schema, ...(columns as any) };
}
export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Row<S>>): R {
const ret = Object.create(null);
const rowCount = rows.length;
const columns = Object.keys(schema);
ret._rowCount = rowCount;
ret._columns = columns;
ret._schema = schema;
for (const k of columns) {
(ret as any)[k] = Column.ofLambda({
rowCount,
type: schema[k],
value: r => rows[r][k],
valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present
})
}
return ret as R;
}
export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R {
const ret = Object.create(null);
const columns = Object.keys(schema);
ret._rowCount = arrays[columns[0]].length;
ret._columns = columns;
ret._schema = schema;
for (const k of columns) {
(ret as any)[k] = Column.ofArray({ array: arrays[k], type: schema[k] })
}
return ret as R;
}
export function view<S extends R, R extends Schema>(table: Table<S>, schema: R, view: ArrayLike<number>) {
const ret = Object.create(null);
const columns = Object.keys(schema);
ret._rowCount = view.length;
ret._columns = columns;
ret._schema = schema;
for (const k of columns) {
(ret as any)[k] = Column.view(table[k], view);
}
return ret as Table<R>;
}
export function columnToArray<S extends Schema>(table: Table<S>, name: keyof S, array?: Column.ArrayCtor<any>) {
table[name] = Column.asArrayColumn(table[name], array);
}
/** Sort and return a new table */
export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) {
const indices = new Int32Array(table._rowCount);
for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i;
sortArray(indices, (_, i, j) => cmp(i, j));
let isIdentity = true;
for (let i = 0, _i = indices.length; i < _i; i++) {
if (indices[i] !== i) {
isIdentity = false;
break;
}
}
if (isIdentity) return table;
const ret = Object.create(null);
ret._rowCount = table._rowCount;
ret._columns = table._columns;
ret._schema = table._schema;
for (const c of table._columns) {
ret[c] = Column.view((table as any)[c], indices, false);
}
return ret;
}
export function areEqual<T extends Table<Schema>>(a: T, b: T) {
if (a._rowCount !== b._rowCount) return false;
if (a._columns.length !== b._columns.length) return false;
for (const c of a._columns) {
if (!b[c]) return false;
}
for (const c of a._columns) {
if (!Column.areEqual(a[c], b[c])) return false;
}
return true;
}
}
export default Table
\ No newline at end of file
- Make a writer that takes a database and produces a CIF/BinaryCIF file.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment