// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import { DataType } from './type'; export class Schema { public readonly fields: Field[]; public readonly metadata: Map; public readonly dictionaries: Map; constructor(fields: Field[] = [], metadata?: Map | null, dictionaries?: Map | null) { this.fields = (fields || []) as Field[]; this.metadata = metadata || new Map(); if (!dictionaries) { dictionaries = generateDictionaryMap(fields); } this.dictionaries = dictionaries; } public get [Symbol.toStringTag]() { return 'Schema'; } public toString() { return `Schema<{ ${this.fields.map((f, i) => `${i}: ${f}`).join(', ')} }>`; } public select(...columnNames: K[]) { const names = columnNames.reduce((xs, x) => (xs[x] = true) && xs, Object.create(null)); return new Schema<{ [P in K]: T[P] }>(this.fields.filter((f) => names[f.name]), this.metadata); } public selectAt(...columnIndices: number[]) { return new Schema<{ [key: string]: K }>(columnIndices.map((i) => this.fields[i]).filter(Boolean), this.metadata); } public assign(schema: Schema): Schema; public assign(...fields: (Field | Field[])[]): Schema; public assign(...args: (Schema | Field | Field[])[]) { const other = (args[0] instanceof Schema ? args[0] as Schema : Array.isArray(args[0]) ? new Schema([]> args[0]) : new Schema([]> args)); const curFields = [...this.fields] as Field[]; const metadata = mergeMaps(mergeMaps(new Map(), this.metadata), other.metadata); const newFields = other.fields.filter((f2) => { const i = curFields.findIndex((f) => f.name === f2.name); return ~i ? (curFields[i] = f2.clone({ metadata: mergeMaps(mergeMaps(new Map(), curFields[i].metadata), f2.metadata) })) && false : true; }) as Field[]; const newDictionaries = generateDictionaryMap(newFields, new Map()); return new Schema( [...curFields, ...newFields], metadata, new Map([...this.dictionaries, ...newDictionaries]) ); } } export class Field { public static new(props: { name: string | number; type: T; nullable?: boolean; metadata?: Map | null }): Field; public static new(name: string | number | Field, type: T, nullable?: boolean, metadata?: Map | null): Field; /** @nocollapse */ public static new(...args: any[]) { let [name, type, nullable, metadata] = args; if (args[0] && typeof args[0] === 'object') { ({ name } = args[0]); (type === undefined) && (type = args[0].type); (nullable === undefined) && (nullable = args[0].nullable); (metadata === undefined) && (metadata = args[0].metadata); } return new Field(`${name}`, type, nullable, metadata); } public readonly type: T; public readonly name: string; public readonly nullable: boolean; public readonly metadata: Map; constructor(name: string, type: T, nullable = false, metadata?: Map | null) { this.name = name; this.type = type; this.nullable = nullable; this.metadata = metadata || new Map(); } public get typeId() { return this.type.typeId; } public get [Symbol.toStringTag]() { return 'Field'; } public toString() { return `${this.name}: ${this.type}`; } public clone(props: { name?: string | number; type?: R; nullable?: boolean; metadata?: Map | null }): Field; public clone(name?: string | number | Field, type?: R, nullable?: boolean, metadata?: Map | null): Field; public clone(...args: any[]) { let [name, type, nullable, metadata] = args; (!args[0] || typeof args[0] !== 'object') ? ([name = this.name, type = this.type, nullable = this.nullable, metadata = this.metadata] = args) : ({name = this.name, type = this.type, nullable = this.nullable, metadata = this.metadata} = args[0]); return Field.new(name, type, nullable, metadata); } } /** @ignore */ function mergeMaps(m1?: Map | null, m2?: Map | null): Map { return new Map([...(m1 || new Map()), ...(m2 || new Map())]); } /** @ignore */ function generateDictionaryMap(fields: Field[], dictionaries = new Map()): Map { for (let i = -1, n = fields.length; ++i < n;) { const field = fields[i]; const type = field.type; if (DataType.isDictionary(type)) { if (!dictionaries.has(type.id)) { dictionaries.set(type.id, type.dictionary); } else if (dictionaries.get(type.id) !== type.dictionary) { throw new Error(`Cannot create Schema containing two different dictionaries with the same Id`); } } if (type.children && type.children.length > 0) { generateDictionaryMap(type.children, dictionaries); } } return dictionaries; } // Add these here so they're picked up by the externs creator // in the build, and closure-compiler doesn't minify them away (Schema.prototype as any).fields = null; (Schema.prototype as any).metadata = null; (Schema.prototype as any).dictionaries = null; (Field.prototype as any).type = null; (Field.prototype as any).name = null; (Field.prototype as any).nullable = null; (Field.prototype as any).metadata = null;