pg-mem
Version:
A memory version of postgres
604 lines • 24.1 kB
TypeScript
import { IMemoryDb, IMemoryTable, DataType, IType, TableEvent, GlobalEvent, ISchema, SchemaField, MemoryDbOptions, nil, Schema, ISubscription, LanguageCompiler, ArgDefDetails, QueryResult, IBoundQuery, IPreparedQuery } from './interfaces';
import { Expr, SelectedColumn, CreateColumnDef, AlterColumn, LimitStatement, OrderByStatement, TableConstraint, AlterSequenceChange, CreateSequenceOptions, QName, DataTypeDef, ExprRef, Name, BinaryOperator, CreateExtensionStatement, DropFunctionStatement, ExprCall } from 'pgsql-ast-parser';
import { Map as ImMap, Record, Set as ImSet } from 'immutable';
export * from './interfaces';
export declare const GLOBAL_VARS: unique symbol;
export declare function getId(item: any): string;
export declare function setId<T = any>(item: T, id: string): T;
export type RegClass = string | number;
export type RegType = string | number;
export type TypeQuery = DataTypeDef | DataType | number | _IType;
export interface _ISchema extends ISchema {
readonly name: string;
readonly db: _IDb;
readonly dualTable: _ITable;
/** @deprecated for debug purposes */
lastSelect?: _ISelection;
/** If the given name refers to another schema, then get it. Else, get this */
getThisOrSiblingFor(name: QName): _ISchema;
executeCreateExtension(p: CreateExtensionStatement): void;
dropFunction(fn: DropFunctionStatement): void;
explainSelect(sql: string): _SelectExplanation;
explainLastSelect(): _SelectExplanation | undefined;
getTable(table: string): _ITable;
getTable(table: string, nullIfNotFound?: boolean): _ITable;
tablesCount(t: _Transaction): number;
listTables(t?: _Transaction): Iterable<_ITable>;
declareTable(table: Schema, noSchemaChange?: boolean): _ITable;
createSequence(t: _Transaction, opts: CreateSequenceOptions | nil, name: QName | nil): _ISequence;
/** Get functions matching this overload */
resolveFunction(name: string | QName, args: IValue[], forceOwn?: boolean): _FunctionDefinition | nil;
/** Get an exact function def from its signature (do not use that to resolve overload) */
getFunction(name: string, args: _IType[]): _FunctionDefinition | nil;
/** Get operator matching this overload */
resolveOperator(name: BinaryOperator, left: IValue, right: IValue, forceOwn?: boolean): _OperatorDefinition | nil;
getObject(p: QName): _IRelation;
getObject(p: QName, opts: BeingCreated): _IRelation;
getObject(p: QName, opts?: QueryObjOpts): _IRelation | null;
getOwnObject(name: string): _IRelation | null;
parseType(t: string): _IType;
getType(t: TypeQuery): _IType;
getType(_t: TypeQuery, opts?: QueryObjOpts): _IType | null;
getOwnType(name: DataTypeDef): _IType | null;
getObjectByRegClassId(reg: number): _IRelation;
getObjectByRegClassId(reg: number, opts?: QueryObjOpts): _IRelation | null;
getOwnObjectByRegClassId(reg: number): _IRelation | null;
getObjectByRegOrName(reg: RegClass): _IRelation;
getObjectByRegOrName(reg: RegClass, opts?: QueryObjOpts): _IRelation | null;
setReadonly(): void;
_registerTypeSizeable(name: string, type: (sz?: number) => _IType): this;
_registerType(type: _IType): this;
_unregisterType(type: _IType): this;
_reg_register(rel: _IRelation): Reg;
_reg_unregister(rel: _IRelation): void;
_reg_rename(rel: _IRelation, oldName: string, newName: string): void;
}
export interface _IStatement {
readonly schema: _ISchema;
onExecuted(callback: OnStatementExecuted): void;
}
export interface _IStatementExecutor {
execute(t: _Transaction): StatementResult;
}
export interface StatementResult {
result: QueryResult;
state: _Transaction;
}
export type OnStatementExecuted = (t: _Transaction) => void;
export interface QueryObjOpts extends Partial<BeingCreated> {
/** Returns null instead of throwing error if not found */
nullIfNotFound?: boolean;
/** Will only search in the current schema, or in the targeted schema (not in search path) */
skipSearch?: boolean;
}
export interface BeingCreated {
beingCreated: _IRelation;
}
export interface _FunctionDefinition {
name: string;
args: _ArgDefDetails[];
argsVariadic?: _IType | nil;
returns?: _IType | nil;
impure?: boolean;
allowNullArguments?: boolean;
implementation: (...args: any[]) => any;
}
export interface _OperatorDefinition extends _FunctionDefinition {
commutative: boolean;
left: _IType;
right: _IType;
returns: _IType;
}
export type _ArgDefDetails = ArgDefDetails & {
type: _IType;
default?: IValue;
};
export interface _Transaction {
readonly isChild: boolean;
/** Create a new transaction within this transaction */
fork(): _Transaction;
/** Commit this transaction (returns the parent transaction) */
commit(): _Transaction;
/** Commits this transaction and all underlying transactions */
fullCommit(): _Transaction;
rollback(): _Transaction;
delete(identity: symbol): void;
/** Set data persisted in this transaction */
set<T>(identity: symbol, data: T): T;
/** Get data persisted in this transaction */
get<T>(identity: symbol): T;
getMap<T extends ImMap<any, any>>(identity: symbol): T;
getSet<T>(identity: symbol): ImSet<T>;
/** Get transient data, which will only exist within the scope of the current statement */
setTransient<T>(identity: symbol, data: T): T;
/** Set transient data, which will only exist within the scope of the current statement */
getTransient<T>(identity: symbol): T;
clearTransientData(): void;
}
export interface Stats {
/** Returns this selection size, or null if it cannot be computed without iteration */
count: number;
}
export interface _IAggregation {
checkIfIsKey(got: IValue): IValue;
getAggregation(name: string, call: ExprCall): IValue;
}
export type Row = any;
export interface _ISelection extends _IAlias {
readonly debugId?: string;
readonly ownerSchema: _ISchema;
readonly db: _IDb;
/** Tells if this statement is an execution without any meaningful result ("update" with no "returning", etc...) */
readonly isExecutionWithNoResult: boolean;
/** Column list (those visible when select *) */
readonly columns: ReadonlyArray<IValue>;
/** True when this is an aggregation being built */
isAggregation(): this is _IAggregation;
/** Statistical measure of how many items will be returned by this selection */
entropy(t: _Transaction): number;
enumerate(t: _Transaction): Iterable<Row>;
/** Returns true if the given value is present in this */
hasItem(value: Row, t: _Transaction): boolean;
stats(t: _Transaction): Stats | null;
/** Gets the index associated with this value (or returns null) */
getIndex(...forValue: IValue[]): _IIndex | nil;
/** All columns. A bit like .columns`, but including records selections */
listSelectableIdentities(): Iterable<IValue>;
filter(where: Expr | nil): _ISelection;
limit(limit: LimitStatement): _ISelection;
orderBy(orderBy: OrderByStatement[] | nil): _ISelection;
groupBy(grouping: Expr[] | nil): _ISelection;
distinct(select?: Expr[]): _ISelection;
union(right: _ISelection): _ISelection;
getColumn(column: string | ExprRef): IValue;
getColumn(column: string | ExprRef, nullIfNotFound?: boolean): IValue | nil;
setAlias(alias?: string): _ISelection;
isOriginOf(a: IValue): boolean;
explain(e: _Explainer): _SelectExplanation;
/** Select a specific subset */
select(select: (string | SelectedColumn)[]): _ISelection;
/** Limit selection to a specific alias (in joins) */
selectAlias(alias: string): _IAlias | nil;
}
export interface _IAlias {
listColumns(): Iterable<IValue>;
}
export interface _Explainer {
readonly transaction: _Transaction;
idFor(sel: _ISelection): string | number;
}
export type _SelectExplanation = {
/** A jointure */
id: string | number;
_: 'join';
/** The restrictive table (the one which MUST have a matched elemnt) */
restrictive: _SelectExplanation;
/** The joined table */
joined: _SelectExplanation;
inner: boolean;
on: {
/** 'with' will have to be scanned with this expression */
seqScan: _ExprExplanation;
} | {
/** Which seq id will be iterated (could be either 'join' or 'with' when there is an inner join) */
iterate: string | number;
/** Which iteration side has been chosen (always 'restrictive' for non inner joins) */
iterateSide: 'joined' | 'restrictive';
/** the index table on the other table that can be used to lookup corresponding item(s) */
joinIndex: _IndexExplanation;
/** It will be matched with this expression (computable from the other table) */
matches: _ExprExplanation;
/** True if there is a predicate filter that is also applied (happens when there are 'ANDs' in join condition) */
filtered?: boolean;
};
} | {
/** A selection transformation */
id: string | number;
_: 'map';
select?: {
what: _ExprExplanation;
as: string;
}[];
of: _SelectExplanation;
} | {
id: string | number;
_: 'orderBy';
of: _SelectExplanation;
} | {
/** A selection transformation */
id: string | number;
_: 'limit';
take?: _ExprExplanation;
skip?: _ExprExplanation;
on: _SelectExplanation;
} | {
/** A selection transformation */
id: string | number;
_: 'distinct';
of: _SelectExplanation;
} | {
/** A table */
_: 'table';
table: string;
} | {
/** An AND filter */
id: string | number;
_: 'and';
enumerate: _SelectExplanation;
andCheck: _SelectExplanation[];
} | {
/** A raw array definition */
id: string | number;
_: 'constantSet';
rawArrayLen: number;
} | {
/** One of the following operators on an index:
* - (NOT) IN
* - (NOT) LIKE
* - (NOT) BETWEEN
* - < > <= >= = !=
*
* (against constants) */
id: string | number;
_: 'eq' | 'ineq' | 'neq' | 'inside' | 'outside';
entropy: number;
/** The index that will be used to check equality */
on: _IndexExplanation;
} | {
/** An empty set */
id: string | number;
_: 'empty';
} | {
/** An union set */
id: string | number;
_: 'union';
union: _SelectExplanation[];
} | {
/** A seq-scan filter of another set */
id: string | number;
_: 'seqFilter';
filtered: _SelectExplanation;
} | {
id: string | number;
_: 'aggregate';
aggregator: {
/** aggregation will iterate the whole lot */
seqScan: _ExprExplanation;
} | {
/** aggregation uses an index items which already contains required aggregations */
index: _IndexExplanation;
} | {
/** aggregation is trivial (select count(*) from table) */
trivial: _ISelection;
};
};
export type _IndexExplanation = {
/** BTree index on expression */
_: 'btree';
onTable: string;
btree: string[];
} | {
_: 'indexMap';
of: _IndexExplanation;
} | {
_: 'indexRestriction';
/** This index will receive a lookup for each item of "for" collection */
lookup: _IndexExplanation;
/** Enumerated collection */
for: _SelectExplanation;
} | {
/** Uses an index of a column propagated by a join */
_: 'indexOnJoin';
/** The in propagated column that is used */
index: _IndexExplanation;
/** How elements from the other table will be joined */
strategy: _IndexExplanation | 'catastrophic';
};
export type _ExprExplanation = {
constant: true;
} | {
/** ID of the origin of this selection */
on: string | number;
col: string;
};
export interface _IDb extends IMemoryDb {
readonly options: MemoryDbOptions;
readonly public: _ISchema;
readonly data: _Transaction;
readonly searchPath: ReadonlyArray<string>;
createSchema(db: string): _ISchema;
getSchema(db?: string | null, nullIfNotFound?: false): _ISchema;
getSchema(db: string, nullIfNotFound: true): _ISchema | null;
raiseTable(table: string, event: TableEvent): void;
raiseGlobal(event: GlobalEvent, ...args: any[]): void;
listSchemas(): _ISchema[];
onSchemaChange(): void;
getTable(name: string, nullIfNotExists?: boolean): _ITable;
getExtension(name: string): (schema: ISchema) => void;
/** Get functions matching this overload */
resolveFunction(name: string | QName, types: IValue[]): _FunctionDefinition | nil;
/** Get operators matching this overload */
resolveOperator(name: BinaryOperator, left: IValue, right: IValue): _OperatorDefinition | nil;
getLanguage(name: string): LanguageCompiler;
}
export type OnConflictHandler = {
ignore: 'all' | _IIndex;
} | {
onIndex: _IIndex;
update: (item: any, excluded: any, t: _Transaction) => void;
};
export type DropHandler = (t: _Transaction, cascade: boolean) => void;
export type TruncateHandler = (t: _Transaction, opts: TruncateOpts) => void;
export type IndexHandler = (act: 'create' | 'drop', idx: _INamedIndex) => void;
export interface _RelationBase {
readonly name: string;
readonly reg: Reg;
readonly ownerSchema?: _ISchema;
}
export interface Reg {
readonly typeId: number;
readonly classId: number;
}
export interface ChangeOpts {
onConflict?: OnConflictHandler | nil;
overriding?: 'user' | 'system' | nil;
}
export interface _ITable extends IMemoryTable<any>, _RelationBase {
readonly type: 'table';
readonly hidden: boolean;
readonly db: _IDb;
readonly selection: _ISelection;
readonly ownerSchema: _ISchema;
doInsert(t: _Transaction, toInsert: Row, opts?: ChangeOpts): Row | nil | void;
setHidden(): this;
setReadonly(): this;
delete(t: _Transaction, toDelete: Row): void;
update(t: _Transaction, toUpdate: Row): Row | never;
createIndex(t: _Transaction, expressions: CreateIndexDef): _IConstraint | nil;
createIndex(t: _Transaction, expressions: Name[], type: 'primary' | 'unique', indexName?: string | nil): _IConstraint;
setReadonly(): this;
/** Create a column */
addColumn(column: SchemaField | CreateColumnDef, t: _Transaction): _Column;
/** Get a column to modify it */
getColumnRef(column: string): _Column;
getColumnRef(column: string, nullIfNotFound?: boolean): _Column | nil;
rename(to: string): this;
getConstraint(constraint: string): _IConstraint | nil;
addConstraint(constraint: TableConstraint, t: _Transaction): _IConstraint | nil;
getIndex(...forValues: IValue[]): _IIndex | nil;
dropIndex(t: _Transaction, name: string): void;
drop(t: _Transaction, cascade: boolean): void;
/** Will be executed when one of the given columns is affected (update/delete) */
onBeforeChange(columns: (string | _Column)[], check: ChangeHandler): ISubscription;
/** Will be executed once all 'onBeforeChange' handlers have ran (coherency checks) */
onCheckChange(columns: 'all' | (string | _Column)[], check: ChangeHandler): ISubscription;
onDrop(sub: DropHandler): ISubscription;
onIndex(sub: IndexHandler): ISubscription;
onTruncate(sub: TruncateHandler): ISubscription;
truncate(t: _Transaction, truncateOpts?: TruncateOpts): void;
}
export interface TruncateOpts {
restartIdentity?: boolean;
cascade?: boolean;
}
export interface _IView extends _RelationBase {
readonly type: 'view';
readonly db: _IDb;
readonly selection: _ISelection;
drop(t: _Transaction): void;
}
export interface _IConstraint {
readonly name: string | nil;
uninstall(t: _Transaction): void;
}
export type ChangeHandler = (old: Row | null, neu: Row | null, t: _Transaction, opts: ChangeOpts) => void;
export interface _Column {
readonly notNull: boolean;
readonly default: IValue | nil;
readonly expression: IValue;
readonly usedInIndexes: ReadonlySet<_IIndex>;
readonly table: _ITable;
readonly name: string;
alter(alter: AlterColumn, t: _Transaction): this;
rename(to: string, t: _Transaction): this;
drop(t: _Transaction): void;
onDrop(sub: DropHandler): ISubscription;
}
export interface CreateIndexDef {
ifNotExists?: boolean;
columns: CreateIndexColDef[];
indexName?: string;
unique?: boolean;
notNull?: boolean;
primary?: boolean;
predicate?: IValue;
}
export interface CreateIndexColDef {
value: IValue;
nullsLast?: boolean;
desc?: boolean;
}
export interface _IType<TRaw = any> extends IType, _RelationBase {
readonly type: 'type';
/** Data type */
readonly primary: DataType;
readonly primaryName: string;
/** Reg type name */
readonly name: string;
readonly reg: Reg;
toString(): string;
equals(a: TRaw, b: TRaw): boolean | null;
gt(a: TRaw, b: TRaw): boolean | null;
ge(a: TRaw, b: TRaw): boolean | null;
lt(a: TRaw, b: TRaw): boolean | null;
le(a: TRaw, b: TRaw): boolean | null;
canConvertImplicit(to: _IType<TRaw>): boolean | nil;
canCast(to: _IType<TRaw>): boolean | nil;
cast<T = any>(value: IValue<TRaw>, to: _IType<T>): IValue;
convertImplicit<T = any>(value: IValue<TRaw>, to: _IType<T>): IValue;
prefer(type: _IType<any>, stricterType?: boolean): _IType | nil;
/** Build an array type for this type */
asArray(): _IType<TRaw[]>;
asList(): _IType<TRaw[]>;
/** Get an unicity hash */
hash(value: TRaw): string | number | null;
drop(t: _Transaction): void;
}
export interface Parameter {
readonly index: number;
readonly value: IValue | nil;
inferedType?: _IType | nil;
}
export interface IValue<TRaw = any> {
/** Columns used in this expression (if any) */
readonly usedColumns: ReadonlySet<IValue>;
readonly type: _IType<TRaw>;
/** is 'any()' call ? */
readonly isAny: boolean;
/** Is a constant... i.e. not dependent on columns. ex: (2+2) or NOW() */
readonly isConstant: boolean;
/** Is REAL constant (i.e. 2+2, not varying expressions like NOW()) */
readonly isConstantReal: boolean;
/** Is a literal constant ? (constant not defined as an operation) */
readonly isConstantLiteral: boolean;
/** Will be set if there is an index on this value */
readonly index: _IIndex | nil;
/** Originates from this selection */
readonly origin: _ISelection | nil;
/** Column ID, or null */
readonly id: string | nil;
/** Hash of this value (used to identify indexed expressions) */
readonly hash: string;
/** Get value if is a constant */
get(): any;
/** Get value if is NOT a constant */
get(raw: TRaw, t?: _Transaction | nil): any;
setId(newId: string): IValue;
canCast(to: _IType): boolean;
cast<T = any>(to: _IType<T>): IValue;
convertImplicit<T = any>(to: _IType<T>): IValue;
/**
* Creates a copy of this column that can
**/
setWrapper<TNew>(newOrigin: _ISelection, unwrap: (val: TRaw) => TNew, newType: _IType<TNew>): IValue<TNew>;
setWrapper(newOrigin: _ISelection, unwrap: (val: TRaw) => TRaw): IValue<TRaw>;
map(unwrap: (val: TRaw) => TRaw): IValue<TRaw>;
map<TNew>(unwrap: (val: TRaw) => TNew, newType: _IType<TNew>): IValue<TNew>;
setOrigin(origin: _ISelection): IValue<TRaw>;
clone(): IValue;
explain(e: _Explainer): _ExprExplanation;
}
export type IndexKey = unknown[];
export interface IndexExpression {
readonly hash: string;
readonly type: _IType;
}
export interface _INamedIndex extends _IIndex, _RelationBase {
readonly type: 'index';
readonly onTable: _ITable;
drop(t: _Transaction): void;
}
export interface _IIndex {
readonly unique?: boolean;
readonly expressions: IndexExpression[];
/** Returns a measure of how many items will be returned by this op */
entropy(t: IndexOp): number;
/** Returns this selection stats, or null if it cannot be computed without iteration */
stats(t: _Transaction, key?: IndexKey): Stats | null;
/** Get values equating the given key */
eqFirst(rawKey: IndexKey, t: _Transaction): Row | null;
enumerate(op: IndexOp): Iterable<Row>;
explain(e: _Explainer): _IndexExplanation;
iterateKeys(t: _Transaction): Iterable<IndexKey> | null;
}
export type IndexOp = {
type: 'eq' | 'neq' | 'gt' | 'lt' | 'ge' | 'le';
key: IndexKey;
t: _Transaction;
matchNull?: boolean;
} | {
type: 'inside' | 'outside';
lo: IndexKey;
hi: IndexKey;
t: _Transaction;
} | {
type: 'nin';
keys: IndexKey[];
t: _Transaction;
};
export interface TableRecordDef {
readonly?: boolean;
hidden?: boolean;
name?: string;
dataId?: symbol;
serials: ImMap<string, number>;
it: number;
indexByHash: ImMap<string, _IIndex>;
indexByName: ImMap<string, _IIndex>;
columnsByName: ImMap<string, CR>;
}
export interface TableColumnRecordDef {
default: IValue;
notNull: boolean;
usedInIndexes: ImSet<_IIndex>;
type: _IType;
name: string;
}
export type TR = Record<TableRecordDef>;
export type CR = Record<TableColumnRecordDef>;
export declare const EmtpyTable: Record.Factory<TableRecordDef>;
export declare const NewColumn: Record.Factory<TableColumnRecordDef>;
export type _IRelation = _ITable | _ISequence | _INamedIndex | _IType | _IView;
export declare function asIndex(o: _IRelation): _INamedIndex;
export declare function asIndex(o: _IRelation | null): _INamedIndex | null;
export declare function asType(o: _IRelation): _IType;
export declare function asType(o: _IRelation | null): _IType | null;
export declare function asSeq(o: _IRelation): _ISequence;
export declare function asSeq(o: _IRelation | null): _ISequence | null;
export declare function asTable(o: _IRelation): _ITable;
export declare function asTable(o: _IRelation | null): _ITable | null;
export declare function asTable(o: _IRelation | null, nullIfNotType?: boolean): _ITable | null;
export type _ISelectable = _ITable | _IView;
export declare function asSelectable(o: _IRelation): _ISelectable;
export declare function asSelectable(o: _IRelation | null): _ISelectable | null;
export declare function asSelectable(o: _IRelation | null, nullIfNotType?: boolean): _ISelectable | null;
export declare function asView(o: _IRelation): _IView;
export declare function asView(o: _IRelation | null): _IView | null;
export declare function asView(o: _IRelation | null, nullIfNotType?: boolean): _IView | null;
export interface _ISequence extends _RelationBase {
readonly type: 'sequence';
alter(t: _Transaction, opts: CreateSequenceOptions | AlterSequenceChange): this;
nextValue(t: _Transaction): number;
restart(t: _Transaction): void;
setValue(t: _Transaction, value: number, increase?: boolean): void;
currentValue(t: _Transaction): number;
drop(t: _Transaction): void;
}
export interface AggregationComputer<TRet = any> {
readonly type: _IType;
/** Compute from index (ex: count(*) with a group-by) */
computeFromIndex?(key: IndexKey, index: _IIndex, t: _Transaction): TRet | undefined;
/** Compute out of nowhere when there is no group
* (ex: when there is no grouping, count(*) on a table or count(xxx) when there is an index on xxx) */
computeNoGroup?(t: _Transaction): TRet | undefined;
/** When iterating, each new group will have its computer */
createGroup(t: _Transaction): AggregationGroupComputer<TRet>;
}
export interface AggregationGroupComputer<TRet = any> {
/** When iterating, this will be called for each item in this group */
feedItem(item: any): void;
/** Finish computation (sets aggregation on result) */
finish(): TRet | nil;
}
export interface _IPreparedQuery extends IPreparedQuery {
bind(args?: any[]): _IBoundQuery;
executed?: () => void;
failed?: (e: any) => void;
}
export interface _IBoundQuery extends IBoundQuery {
executeAll(tx?: _Transaction): _QueryResult;
}
export interface _QueryResult extends QueryResult {
state?: _Transaction;
}
//# sourceMappingURL=interfaces-private.d.ts.map