Skip to content

Instantly share code, notes, and snippets.

@Kittoes0124
Last active January 20, 2021 08:21
Show Gist options
  • Save Kittoes0124/a8050730f4c700f8c6a69517519e5b2d to your computer and use it in GitHub Desktop.
Save Kittoes0124/a8050730f4c700f8c6a69517519e5b2d to your computer and use it in GitHub Desktop.
/*
Known Issues:
0) Joining to the same table results in circular references.
1) Joining to any table results in the loss of all type information.
*/
interface Array<T> {
toBtSqlTable(keySelector: IndexKeySelector<T>): TableWithOperators<T>;
}
type Action<T> = UnaryProjection<T, void>;
type BinaryPredicate<TFirst, TSecond> = BinaryProjection<TFirst, TSecond, boolean>;
type BinaryProjection<TFirst, TSecond, TResult> = (first: Readonly<TFirst>, second: Readonly<TSecond>) => TResult;
type IndexKey = (number | string);
type IndexKeySelector<T> = UnaryProjection<T, IndexKey>;
type TableWithOperators<TEntity> = {
forEach(action: Action<TEntity>): void;
join<TOther>(
other: TableWithOperators<TOther>,
...predicates: ReadonlyArray<BinaryPredicate<TEntity, TOther>>
): TableWithOperators<any>;
leftJoin<TOther>(
other: TableWithOperators<TOther>,
...predicates: ReadonlyArray<BinaryPredicate<TEntity, TOther>>
): TableWithOperators<any>;
select<TProjection>(
a: UnaryProjection<TEntity, TProjection>,
): TableWithOperators<TProjection>;
select<A, TProjection>(
a: UnaryProjection<TEntity, A>,
b: UnaryProjection<A, TProjection>,
): TableWithOperators<TProjection>;
select<A, B, TProjection>(
a: UnaryProjection<TEntity, A>,
b: UnaryProjection<A, B>,
c: UnaryProjection<B, TProjection>,
): TableWithOperators<TProjection>;
select<A, B, C, TProjection>(
a: UnaryProjection<TEntity, A>,
b: UnaryProjection<A, B>,
c: UnaryProjection<B, C>,
d: UnaryProjection<C, TProjection>,
): TableWithOperators<TProjection>;
select<A, B, C, D, TProjection>(
a: UnaryProjection<TEntity, A>,
b: UnaryProjection<A, B>,
c: UnaryProjection<B, C>,
d: UnaryProjection<C, D>,
e: UnaryProjection<D, TProjection>,
): TableWithOperators<TProjection>;
select(...projections: ReadonlyArray<UnaryProjection<any, any>>): TableWithOperators<any>;
toArray(): Array<TEntity>;
where(...predicates: ReadonlyArray<UnaryPredicate<TEntity>>): TableWithOperators<TEntity>;
};
type UnaryPredicate<T> = UnaryProjection<T, boolean>;
type UnaryProjection<TInput, TResult> = (input: Readonly<TInput>) => TResult;
module BtSql {
Object.defineProperty(Array.prototype, "toBtSqlTable", {
value: function<T>(keySelector: IndexKeySelector<any>) {
return buildTableCore<T>(this, keySelector);
},
});
type CircularReferenceHandler = (
parentKey: Readonly<IndexKey>,
parentValue: Dictionary<IndexKey, any>,
childKey: Readonly<IndexKey>,
childValue: ImmutableDictionary<IndexKey, any>,
) => void;
type Dictionary<TKey extends IndexKey, TValue> = Record<TKey, TValue>;
type Immutable<T> = { readonly [P in keyof T]: Readonly<T[P]>; };
type ImmutableDictionary<TKey extends IndexKey, TValue> = Immutable<Dictionary<TKey, TValue>>;
type ImmutableTable<TEntity, TKey extends IndexKey> = Immutable<Table<TKey, TEntity>>;
type Table<TKey extends IndexKey, TEntity> = ({
index: Array<Readonly<TKey>>;
values: Dictionary<TKey, TEntity>;
j: Array<TableRelation<any, any>>,
s: Array<UnaryProjection<any, any>>,
w: Array<UnaryPredicate<TEntity>>,
});
type TableRelation<T, U> = {
other: TableWithOperators<U>,
isOuterJoin?: boolean,
predicates?: ReadonlyArray<BinaryPredicate<T, U>>,
};
const ERROR_TAG_DUPLICATE_KEY = "[Duplicate Key]";
const ERROR_TAG_NOT_SUPPORTED = "[Not Supported]";
const ERROR_MESSAGE_TABLE_BUILD_DUPLICATE_KEY = `${ERROR_TAG_DUPLICATE_KEY} Unable to build table; the index key must be unique within the data set.`;
const ERROR_MESSAGE_TABLE_JOIN_PROJECTION_NOT_SUPPORTED = `${ERROR_TAG_NOT_SUPPORTED} Unable to process join predicates; the feature is under review.`;
const assignObject = Object.assign;
const brandAndFreezeTable = <TEntity,>(
table: Table<IndexKey, TEntity>,
): TableWithOperators<TEntity> => {
freezeObject(table.index);
freezeObject(table.values);
brandedTables.add(table);
return freezeObject(table as any);
};
const buildTableCore = <TEntity,>(
entities: ReadonlyArray<TEntity>,
keySelector: IndexKeySelector<TEntity>,
): TableWithOperators<TEntity> => brandAndFreezeTable(entities.reduce(
(t, e) => {
const k = keySelector(e);
if (t.values[k]) {
throw newError(ERROR_MESSAGE_TABLE_BUILD_DUPLICATE_KEY);
}
if (isObject(e)) {
processCircularReferences((e as any), defaultCircularReferenceHandler);
}
freezeEntityAndInsertIntoTable(cloneObjectShallow(e), k, t);
return t;
},
newEmptyTableObject<TEntity>()
));
const cloneObjectShallow = <T,>(
t: T,
): T => assignObject({}, t);
const defaultCircularReferenceHandler = (
_parentKey: Readonly<IndexKey>,
parentValue: Dictionary<IndexKey, any>,
childKey: Readonly<IndexKey>,
_childValue: ImmutableDictionary<IndexKey, any>,
): void => {
parentValue[childKey] = undefined;
};
const freezeEntityAndInsertIntoTable = <TEntity,>(
entity: TEntity,
key: IndexKey,
target: Table<IndexKey, TEntity>,
): void => {
target.index.push(key);
target.values[key] = freezeObject(entity);
};
const freezeObject = Object.freeze;
const getAliasedTableValue = (
charCode: number,
value: any,
): ImmutableDictionary<string, any> => ({
[String.fromCharCode(charCode)]: (isObject(value) ? cloneObjectShallow(value) : { value, }),
});
const getObjectProperties = Object.entries;
const identity = <T,>(input: T) => input;
const isObject = <T,>(input: T) => (input && ("object" === typeof input));
const newArray = <T,>() => new Array<T>();
const newError = (message?: string) => new Error(message);
const newEmptyTableObject = <TEntity,>(
): Table<IndexKey, TEntity> => {
let index = newArray<IndexKey>();
let values = ({} as Dictionary<IndexKey, TEntity>);
let table = ({
j: newArray<TableRelation<any, any>>(),
s: newArray<UnaryProjection<any, any>>(),
w: newArray<UnaryPredicate<any>>(),
index,
values,
forEach: function(action: Action<any>) {
for (const key of index) {
const value = projectMany(this.s)(values[key]);
if (projectMany(this.w)(value)) {
if (!this.j.length) {
action(value);
}
else {
let aliasCode = 97;
let isMatch = false;
let result = getAliasedTableValue(aliasCode, value);
for (const relation of this.j) {
++aliasCode;
const joinPredicates = relation.predicates;
const otherTable = ((relation.other as any) as Table<IndexKey, any>);
if (!joinPredicates?.length) {
let otherValue = otherTable.values[key];
if ((otherValue && projectMany(otherTable.w)(otherValue)) || relation.isOuterJoin) {
isMatch = true;
otherValue = {};
}
assignObject(result, getAliasedTableValue(aliasCode, otherValue));
}
else {
throw newError(ERROR_MESSAGE_TABLE_JOIN_PROJECTION_NOT_SUPPORTED);
}
}
if (isMatch) {
action(result);
}
}
}
}
},
join: function(other: TableWithOperators<any>, ..._predicates: ReadonlyArray<BinaryPredicate<any, any>>) {
this.j.push({ other: other, });
return this;
},
leftJoin: function(other: TableWithOperators<any>, ..._predicates: ReadonlyArray<BinaryPredicate<any, any>>) {
this.j.push({
isOuterJoin: true,
other: other,
});
return this;
},
select: function(...p: ReadonlyArray<UnaryProjection<any, any>>) {
return brandAndFreezeTable(assignObject({ s: this.s.concat(p), }, this));
},
toArray: function(){
const result = newArray<any>();
this.forEach(value => result.push(value));
return result;
},
where: function(...p: ReadonlyArray<UnaryPredicate<any>>) {
return brandAndFreezeTable(assignObject({ w: this.w.concat(p), }, this));
},
} as (Table<IndexKey, TEntity> & TableWithOperators<TEntity>));
return table;
};
const newWeakSet = <T extends object>() => new WeakSet<T>();
const processCircularReferences = <T extends object>(
input: T,
handler: CircularReferenceHandler,
initialPathValue = "o",
): T => {
const objectStack = [{ k: initialPathValue, v: (input as ImmutableDictionary<IndexKey, any>), },];
const objectTracker = newWeakSet<object>();
objectTracker.add(input);
while (objectStack.length) {
const { k: parentKey, v: parentValue } = objectStack.shift()!;
for (const [childKey, childValue] of getObjectProperties(parentValue)) {
if (isObject(childValue)) {
if (objectTracker.has(childValue)) {
handler(parentKey, parentValue, childKey, childValue);
}
else {
objectStack.push({ k: `${parentKey}.${childKey}`, v: childValue, });
objectTracker.add(childValue);
}
}
}
}
return input;
}
const projectMany = (
projections: Array<UnaryProjection<any, any>>,
): UnaryProjection<any, any> => {
if (0 === projections.length) {
return (identity as UnaryProjection<any, any>);
}
if (1 === projections.length) {
return projections[0];
}
return (input: any): any => {
let result = input;
for (const projection of projections) {
const temp = projection(result);
if (terminationToken === temp) {
break;
}
result = temp;
}
return result;
}
};
const brandedTables = newWeakSet<ImmutableTable<any, IndexKey>>();
const terminationToken = {};
export function act<T>(action: UnaryProjection<T, void>) {
return (input: T) => {
action(input);
return input;
};
}
export function terminate<T>() {
return (((_input: T) => {
return terminationToken;
}) as UnaryProjection<T, any>);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment