pg-mem
Version:
A memory version of postgres
444 lines • 16 kB
TypeScript
import { TableConstraint, CreateColumnDef, NodeLocation, DataTypeDef, FunctionArgumentMode, BinaryOperator, Statement } from 'pgsql-ast-parser';
import { MigrationParams } from './migrate/migrate-interfaces';
export type nil = undefined | null;
export type Schema = {
name: string;
fields: SchemaField[];
constraints?: TableConstraint[];
};
export interface SchemaField extends Omit<CreateColumnDef, 'dataType' | 'kind' | 'name'> {
type: IType | DataType;
name: string;
serial?: boolean;
}
export interface IType {
/** Data type */
readonly primary: DataType;
readonly name: string;
toString(): string;
/** Create an array type of this type */
asArray(): IType;
}
export declare enum DataType {
inet = "inet",
record = "record",
uuid = "uuid",
text = "text",
citext = "citext",
array = "array",
list = "list",
bigint = "bigint",
float = "float",
decimal = "decimal",
integer = "integer",
jsonb = "jsonb",
regtype = "regtype",
regclass = "regclass",
json = "json",
bytea = "bytea",
interval = "interval",
timestamp = "timestamp",
timestamptz = "timestamptz",
date = "date",
time = "time",
timetz = "timetz",
null = "null",
bool = "bool",
point = "point",
line = "line",
lseg = "lseg",
box = "box",
path = "path",
polygon = "polygon",
circle = "circle"
}
export interface MemoryDbOptions {
/**
* If set to true, pg-mem will stop embbeding info about the SQL statement
* that has failed in exception messages.
*/
noErrorDiagnostic?: boolean;
/**
* If set to true, then the query runner will not check that no AST part
* has been left behind when parsing the request.
*
* ... so setting it to true could lead to unnoticed ignored query parts.
*
* (advice: only set it to true as a workaround while an issue on https://github.com/oguimbal/pg-mem is being fixed... )
*/
noAstCoverageCheck?: boolean;
/**
* If set to true, this will throw an exception if
* you try to use an unsupported index type
* (only BTREE is supported at time of writing)
*/
noIgnoreUnsupportedIndices?: boolean;
/**
* When set to true, this will auto create an index on foreign table when adding a foreign key.
* 👉 Recommanded when using Typeorm .synchronize(), which creates foreign keys but not indices !
**/
readonly autoCreateForeignKeyIndices?: boolean;
}
export interface IMemoryDb {
/**
* Adapters to create wrappers of this db compatible with known libraries
*/
readonly adapters: LibAdapters;
/**
* The default 'public' schema
*/
readonly public: ISchema;
/**
* Get an existing schema
*/
getSchema(db?: string | null): ISchema;
/**
* Create a schema in this database
*/
createSchema(name: string): ISchema;
/**
* Get a table to inspect it (in the public schema... this is a shortcut for db.public.getTable())
*/
getTable<T = any>(table: string): IMemoryTable<T>;
getTable<T = any>(table: string, nullIfNotFound?: boolean): IMemoryTable<T> | null;
/** Subscribe to a global event */
on(event: 'query', handler: (query: string) => any): ISubscription;
on(event: GlobalEvent, handler: () => any): ISubscription;
on(event: GlobalEvent, handler: () => any): ISubscription;
/** Subscribe to an event on all tables */
on(event: TableEvent, handler: (table: string) => any): ISubscription;
/**
* Creates a restore point.
* 👉 This operation is O(1) (instantaneous, even with millions of records).
* */
backup(): IBackup;
/**
* Registers an extension (that can be installed using the 'create extension' statement)
* @param name Extension name
* @param install How to install this extension on a given schema
*/
registerExtension(name: string, install: (schema: ISchema) => void): this;
/** Registers a new language, usable in 'DO' blocks, or in 'CREATE FUNCTION' blocks */
registerLanguage(languageName: string, compiler: LanguageCompiler): this;
}
export type QueryInterceptor = (query: string) => any[] | nil;
export type ArgDef = DataType | IType | ArgDefDetails;
export interface ArgDefDetails {
/** Argument type */
type: IType;
/** Optional argument name */
name?: string;
/**
* Arguments are 'in' by default, but you can change that.
*/
mode?: FunctionArgumentMode;
}
export type LanguageCompiler = (options: ToCompile) => CompiledFunction;
export interface ToCompile {
/** Function being compiled (null for "DO" statements compilations) */
functionName?: string | nil;
/** Code to compile */
code: string;
/** Schema against which this compilation is performed */
schema: ISchema;
/** Expected arguments */
args: ArgDefDetails[];
/** Expected return type (if any) */
returns?: IType | nil;
}
export declare class AdvancedResult {
readonly result: any;
constructor(result: any, outArgs: any[]);
}
export type CompiledFunction = (...inArguments: any[]) => AdvancedResult | PlainResult;
export type PlainResult = Object | number | Date | null | void;
export interface CompiledFunctionResult {
/** The function result, if function "returns" something */
result?: any;
/** The functions out arguments, as indexed in the `args` passed to your language compiler function */
outArgs?: any[];
}
export interface IBackup {
/**
* Restores data to the state when this backup has been performed.
* 👉 This operation is O(1).
* 👉 Schema must not have been changed since then !
**/
restore(): void;
}
export interface LibAdapters {
/** Create a PG module that will be equivalent to require('pg') */
createPg(queryLatency?: number): {
Pool: any;
Client: any;
};
/** Create a pg-promise instance bound to this db */
createPgPromise(queryLatency?: number): any;
/** Create a slonik pool bound to this db */
createSlonik(opts?: SlonikAdapterOptions): Promise<any>;
/** Create a pg-native instance bound to this db */
createPgNative(queryLatency?: number): any;
/** Create a Typeorm connection bound to this db
* @deprecated Use `createTypeormDataSource` instead. See https://github.com/oguimbal/pg-mem/pull/238.
*/
createTypeormConnection(typeOrmConnection: any, queryLatency?: number): any;
/** Create a Typeorm data source bound to this db */
createTypeormDataSource(typeOrmConnection: any, queryLatency?: number): any;
/** Create a Knex.js instance bound to this db */
createKnex(queryLatency?: number, knexConfig?: object): any;
/** Create a Kysely instance bound to this db */
createKysely(queryLatency?: number, kyselyConfig?: object): any;
/** Create a mikro-orm instance bound to this db */
createMikroOrm(mikroOrmOptions: any, queryLatency?: number): Promise<any>;
/** Creates a Postres.js `sql` tag bound to this db */
createPostgresJsTag(queryLatency?: number): any;
/** Binds a server to this instance */
bindServer(opts?: BindServerOptions): Promise<BindServerResult>;
}
export interface BindServerResult {
postgresConnectionString: string;
connectionSettings: {
host: string;
port: number;
};
close(): void;
}
export interface BindServerOptions {
/** defaults to a random port */
port?: number;
/** defaults to 'localhost' */
host?: string;
}
export interface SlonikAdapterOptions {
queryLatency?: number;
/** options you would give to the createPool() function */
createPoolOptions?: any;
/** add zod validation interceptor to create pool options, like with https://github.com/gajus/slonik?tab=readme-ov-file#result-parser-interceptor */
zodValidation?: boolean;
/** old versions of slonik, this be passed as client configuration */
clientConfigurationInput?: any;
}
export type QueryOrAst = string | Statement | Statement[];
export interface IPreparedQuery {
describe(): QueryDescription;
bind(args?: any[]): IBoundQuery;
}
export interface QueryDescription {
parameters: ParameterInfo[];
result: FieldInfo[];
}
export interface ParameterInfo {
type: DataType;
typeId: number;
}
export interface IBoundQuery {
/** Executes all statements */
executeAll(): QueryResult;
/**
* Progressively executes a query that contains multiple statements, yielding results until the end of enumeration (or an exception)
*/
iterate(): IterableIterator<QueryResult>;
}
export interface ISchema {
/**
* Another way to create tables (equivalent to "create table" queries")
*/
declareTable(table: Schema): IMemoryTable<any>;
/**
* Execute a query and return many results
*/
many(query: QueryOrAst): any[];
/**
* Execute a query without results
*/
none(query: QueryOrAst): void;
/**
* Execute a query with a single result
*/
one(query: QueryOrAst): any;
/**
* Execute a query that has no argument, and returns the latest query result
* (shortcut for .prepare(cmd).bind().executeAll())
*/
query(text: QueryOrAst): QueryResult;
/**
* Progressively executes a query that has no argument, yielding results until the end of enumeration (or an exception)
* (shortcut for .prepare(cmd).bind().iterate())
*/
queries(text: QueryOrAst): Iterable<QueryResult>;
/**
* Prepare a query
*/
prepare(text: QueryOrAst): IPreparedQuery;
/**
* Get a table in this db to inspect it
*/
getTable(table: string): IMemoryTable<unknown>;
getTable(table: string, nullIfNotFound?: boolean): IMemoryTable<unknown> | null;
/**
* List all tables in this schema
*/
listTables(): Iterable<IMemoryTable<unknown>>;
/** Register a function */
registerFunction(fn: FunctionDefinition, orReplace?: boolean): this;
/** Register a binary operator */
registerOperator(fn: OperatorDefinition): this;
/** Register a simple type, which is equivalent to another */
registerEquivalentType(type: IEquivalentType): IType;
/** Register a simple type, which is equivalent to another */
registerEquivalentSizableType(type: IEquivalentType): IType;
/** Get an existing type */
getType(name: DataType): IType;
/**
* Registers an enum type on this schema
* @param name Enum name
* @param values Possible values
*/
registerEnum(name: string, values: string[]): void;
/**
* Database migration, node-sqlite flavor
* ⚠ Only working when runnin nodejs !
*/
migrate(config?: MigrationParams): Promise<void>;
/**
* Intecept queries.
* If your interceptor returns an array, then the query will not be executed.
* The given result will be returned instead.
*/
interceptQueries(interceptor: QueryInterceptor): ISubscription;
}
export interface FunctionDefinition {
/** Function name (casing doesnt matter) */
name: string;
/** Expected arguments */
args?: ArgDef[] | nil;
/** Other arguments type (variadic arguments) */
argsVariadic?: DataType | IType | nil;
/** Returned data type */
returns?: DataType | IType | nil;
/**
* If the function is marked as impure, it will not be simplified
* (ex: "select myFn(1) from myTable" will call myFn() for each row in myTable, even if it does not depend on its result) */
impure?: boolean;
/** If true, the function will also be called when passing null arguments */
allowNullArguments?: boolean;
/** Actual implementation of the function */
implementation: CompiledFunction;
}
export interface OperatorDefinition {
/** Function name (casing doesnt matter) */
operator: BinaryOperator;
/** Expected left argument */
left: DataType | IType;
/** Expected right argument */
right: DataType | IType;
/** True if the operator is commutative (if left & right can be inverted) */
commutative?: boolean;
/** Returned data type */
returns: DataType | IType;
/**
* If the function is marked as impure, it will not be simplified
* (ex: "select myFn(1) from myTable" will call myFn() for each row in myTable, even if it does not depend on its result) */
impure?: boolean;
/** If true, the function will also be called when passing null arguments */
allowNullArguments?: boolean;
/** Actual implementation of the function */
implementation: CompiledFunction;
}
export interface QueryResult {
/** Last command that has been executed */
command: string;
rowCount: number;
fields: Array<FieldInfo>;
rows: any[];
/** Location of the last ";" prior to this statement */
location: NodeLocation;
}
export interface FieldInfo {
name: string;
type: DataType;
typeId: number;
}
export type TableEvent = 'seq-scan';
export type GlobalEvent = 'query' | 'query-failed' | 'catastrophic-join-optimization' | 'schema-change' | 'create-extension';
export interface IMemoryTable<T> {
readonly name: string;
readonly primaryIndex: IndexDef | nil;
/** List columns in this table */
getColumns(): Iterable<ColumnDef>;
/** Subscribe to an event on this table */
on(event: TableEvent, handler: () => any): ISubscription;
/** List existing indices defined on this table */
listIndices(): IndexDef[];
/**
* Inserts a raw item into this table.
* ⚠ Neither the record you provided, nor the returned value are the actual item stored. You wont be able to mutate internal state.
* @returns A copy of the inserted item (with assigned defaults)
*/
insert(item: Partial<T>): T | null;
/** Find all items matching a specific template */
find<keys extends (keyof T)[]>(template?: Partial<T> | nil, columns?: keys): Pick<T, ArrayToUnionOr<keys, keyof T>>[];
}
type ArrayToUnionOr<T, Or> = T extends (infer U)[] ? U : Or;
export interface ColumnDef {
readonly name: string;
readonly type: IType;
readonly nullable: boolean;
}
export interface ISubscription {
unsubscribe(): void;
}
export interface IndexDef {
readonly name: string;
readonly expressions: string[];
}
export declare class NotSupported extends Error {
constructor(what?: string);
static never(value: never, msg?: string): NotSupported;
}
interface ErrorData {
readonly error: string;
readonly details?: string;
readonly hint?: string;
readonly code?: string;
}
export declare class QueryError extends Error {
readonly data: ErrorData;
readonly code: string | undefined;
constructor(err: string | ErrorData, code?: string);
}
export declare class CastError extends QueryError {
constructor(from: string | DataType | IType, to: string | DataType | IType, inWhat?: string);
}
export declare class ColumnNotFound extends QueryError {
constructor(col: string);
}
export declare class AmbiguousColumn extends QueryError {
constructor(col: string);
}
export declare class RelationNotFound extends QueryError {
constructor(tableName: string);
}
export declare class TypeNotFound extends QueryError {
constructor(t: string | number | DataTypeDef);
}
export declare class RecordExists extends QueryError {
constructor();
}
export declare class PermissionDeniedError extends QueryError {
constructor(what?: string);
}
export declare function typeDefToStr(t: DataTypeDef): string;
/** A type definition that is equivalent to another type */
export interface IEquivalentType {
/** Type name */
readonly name: string;
/** Which underlying type is it equivalent to ? */
readonly equivalentTo: DataType | IType;
/**
* Is this value valid ?
*/
isValid(value: any): boolean;
}
export {};
//# sourceMappingURL=interfaces.d.ts.map