├── .deno ├── .vscode │ └── settings.json ├── adapters │ ├── adapters.ts │ ├── index.ts │ └── pg-socket-adapter.ts ├── column.ts ├── constraints │ ├── foreign-key.ts │ ├── generated.ts │ ├── index-cst.ts │ ├── subscription.ts │ └── wrapped.ts ├── datatypes │ ├── datatype-base.ts │ ├── datatypes-geometric.ts │ ├── datatypes.ts │ ├── index.ts │ ├── t-custom-enum.ts │ ├── t-equivalent.ts │ ├── t-inet.ts │ ├── t-interval.ts │ ├── t-jsonb.ts │ ├── t-record.ts │ ├── t-regclass.ts │ ├── t-regtype.ts │ ├── t-time.ts │ └── t-timestamp.ts ├── db.ts ├── evaluator.ts ├── execution │ ├── clean-results.ts │ ├── exec-utils.ts │ ├── records-mutations │ │ ├── deletion.ts │ │ ├── insert.ts │ │ ├── mutation-base.ts │ │ ├── truncate-table.ts │ │ └── update.ts │ ├── schema-amends │ │ ├── alter-enum.ts │ │ ├── alter-sequence.ts │ │ ├── alter.ts │ │ ├── create-enum.ts │ │ ├── create-function.ts │ │ ├── create-index.ts │ │ ├── create-materialized-view.ts │ │ ├── create-schema.ts │ │ ├── create-sequence.ts │ │ ├── create-table.ts │ │ ├── create-view.ts │ │ ├── do.ts │ │ ├── drop-index.ts │ │ ├── drop-sequence.ts │ │ ├── drop-table.ts │ │ └── drop-type.ts │ ├── select.ts │ ├── set.ts │ ├── show.ts │ ├── statement-exec.ts │ └── transaction-statements.ts ├── functions │ ├── date.ts │ ├── index.ts │ ├── numbers.ts │ ├── sequence-fns.ts │ ├── string.ts │ ├── subquery.ts │ └── system.ts ├── index.ts ├── interfaces-private.ts ├── interfaces.ts ├── migrate │ ├── migrate-interfaces.ts │ └── migrate.ts ├── misc │ ├── buffer-deno.ts │ ├── buffer-node.ts │ ├── pg-escape.ts │ └── pg-utils.ts ├── mod.ts ├── parser │ ├── context.ts │ ├── expression-builder.ts │ ├── function-call.ts │ └── parse-cache.ts ├── readme.md ├── schema │ ├── btree-index.ts │ ├── consts.ts │ ├── custom-index.ts │ ├── function-call-table.ts │ ├── information-schema │ │ ├── columns-list.ts │ │ ├── constraint-column-usage.ts │ │ ├── index.ts │ │ ├── key-column-usage.ts │ │ ├── table-constraints.ts │ │ └── table-list.ts │ ├── overload-resolver.ts │ ├── pg-catalog │ │ ├── binary-operators.ts │ │ ├── index.ts │ │ ├── pg-attribute-list.ts │ │ ├── pg-class.ts │ │ ├── pg-constraints-list.ts │ │ ├── pg-database.ts │ │ ├── pg-enum-list.ts │ │ ├── pg-index-list.ts │ │ ├── pg-namespace-list.ts │ │ ├── pg-proc.ts │ │ ├── pg-range.ts │ │ ├── pg-sequences-list.ts │ │ ├── pg-type-list.ts │ │ ├── pg-user-list.ts │ │ ├── pg_statio_user_tables.ts │ │ └── sql-function-language.ts │ ├── prepared-intercepted.ts │ ├── prepared.ts │ ├── readonly-table.ts │ ├── schema.ts │ ├── sequence.ts │ ├── table-index.ts │ ├── values-table.ts │ └── view.ts ├── table.ts ├── transaction.ts ├── transforms │ ├── aggregation.ts │ ├── aggregations │ │ ├── array_agg.ts │ │ ├── avg.ts │ │ ├── bool-aggregs.ts │ │ ├── count.ts │ │ ├── json_aggs.ts │ │ ├── max-min.ts │ │ └── sum.ts │ ├── alias.ts │ ├── and-filter.ts │ ├── array-filter.ts │ ├── between-filter.ts │ ├── build-filter.ts │ ├── distinct.ts │ ├── eq-filter.ts │ ├── false-filter.ts │ ├── in-filter.ts │ ├── ineq-filter.ts │ ├── join.ts │ ├── limit.ts │ ├── not-in-filter.ts │ ├── or-filter.ts │ ├── order-by.ts │ ├── restrictive-index.ts │ ├── selection.ts │ ├── seq-scan.ts │ ├── startswith-filter.ts │ ├── transform-base.ts │ └── union.ts └── utils.ts ├── .env ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pg_mem.png └── workflows │ ├── codeql-analysis.yml │ └── main.yml ├── .gitignore ├── .nvmrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── bun.lockb ├── package.json ├── playground ├── error.tsx ├── grid.tsx ├── index.css ├── index.html ├── index.tsx ├── tsconfig.json └── value.tsx ├── readme.md ├── samples ├── knex │ └── knex.ts ├── kysely │ └── kysely.ts ├── mikro-orm │ └── simple.ts ├── sequelize │ └── sequelize.ts └── typeorm │ ├── joins.ts │ └── simple.ts ├── src ├── adapters │ ├── adapters.ts │ ├── index.ts │ └── pg-socket-adapter.ts ├── column.ts ├── constraints │ ├── foreign-key.ts │ ├── generated.ts │ ├── index-cst.ts │ ├── subscription.ts │ └── wrapped.ts ├── datatypes │ ├── datatype-base.ts │ ├── datatypes-geometric.spec.ts │ ├── datatypes-geometric.ts │ ├── datatypes.ts │ ├── index.ts │ ├── t-custom-enum.ts │ ├── t-equivalent.ts │ ├── t-inet.ts │ ├── t-interval.ts │ ├── t-jsonb.ts │ ├── t-record.ts │ ├── t-regclass.ts │ ├── t-regtype.ts │ ├── t-time.ts │ └── t-timestamp.ts ├── db.ts ├── evaluator.ts ├── execution │ ├── clean-results.ts │ ├── exec-utils.ts │ ├── records-mutations │ │ ├── deletion.ts │ │ ├── insert.ts │ │ ├── mutation-base.ts │ │ ├── truncate-table.ts │ │ └── update.ts │ ├── schema-amends │ │ ├── alter-enum.ts │ │ ├── alter-sequence.ts │ │ ├── alter.ts │ │ ├── create-enum.ts │ │ ├── create-function.ts │ │ ├── create-index.ts │ │ ├── create-materialized-view.ts │ │ ├── create-schema.ts │ │ ├── create-sequence.ts │ │ ├── create-table.ts │ │ ├── create-view.ts │ │ ├── do.ts │ │ ├── drop-index.ts │ │ ├── drop-sequence.ts │ │ ├── drop-table.ts │ │ └── drop-type.ts │ ├── select.ts │ ├── set.ts │ ├── show.ts │ ├── statement-exec.ts │ └── transaction-statements.ts ├── functions │ ├── date.ts │ ├── index.ts │ ├── numbers.ts │ ├── sequence-fns.ts │ ├── string.ts │ ├── subquery.ts │ └── system.ts ├── index.ts ├── interfaces-private.ts ├── interfaces.ts ├── migrate │ ├── migrate-interfaces.ts │ ├── migrate.ts │ └── readme.md ├── misc │ ├── buffer-deno.ts │ ├── buffer-node.ts │ ├── pg-escape.ts │ └── pg-utils.ts ├── parser │ ├── context.ts │ ├── expression-builder.ts │ ├── function-call.ts │ └── parse-cache.ts ├── schema │ ├── btree-index.ts │ ├── consts.ts │ ├── custom-index.ts │ ├── function-call-table.ts │ ├── information-schema │ │ ├── columns-list.ts │ │ ├── constraint-column-usage.ts │ │ ├── index.ts │ │ ├── key-column-usage.ts │ │ ├── table-constraints.ts │ │ └── table-list.ts │ ├── overload-resolver.ts │ ├── pg-catalog │ │ ├── binary-operators.ts │ │ ├── index.ts │ │ ├── pg-attribute-list.ts │ │ ├── pg-class.ts │ │ ├── pg-constraints-list.ts │ │ ├── pg-database.ts │ │ ├── pg-enum-list.ts │ │ ├── pg-index-list.ts │ │ ├── pg-namespace-list.ts │ │ ├── pg-proc.ts │ │ ├── pg-range.ts │ │ ├── pg-sequences-list.ts │ │ ├── pg-type-list.ts │ │ ├── pg-user-list.ts │ │ ├── pg_statio_user_tables.ts │ │ └── sql-function-language.ts │ ├── prepared-intercepted.ts │ ├── prepared.ts │ ├── readonly-table.ts │ ├── schema.ts │ ├── sequence.ts │ ├── table-index.ts │ ├── values-table.ts │ └── view.ts ├── table.ts ├── tests │ ├── aggregations.spec.ts │ ├── alter-table.queries.spec.ts │ ├── binary-operators.spec.ts │ ├── constraints.spec.ts │ ├── conversions.spec.ts │ ├── count.spec.ts │ ├── cross-join.spec.ts │ ├── custom-functions.spec.ts │ ├── custom-types.spec.ts │ ├── datatypes.spec.ts │ ├── delete.queries.spec.ts │ ├── distinct.spec.ts │ ├── drizzle-requests.spec.ts │ ├── drop.spec.ts │ ├── extensions.spec.ts │ ├── foreign-keys.spec.ts │ ├── fork.spec.ts │ ├── function-calls.spec.ts │ ├── group-by.spec.ts │ ├── indexes.spec.ts │ ├── insert.queries.spec.ts │ ├── invalid-syntaxes.spec.ts │ ├── irl-tests │ │ ├── carlosfaria94.spec.ts │ │ ├── cbadger85.spec.ts │ │ ├── mirrorbytes.spec.ts │ │ └── objection-knexsnakecase.js │ ├── issues.spec.ts │ ├── join.spec.ts │ ├── knex-real.spec.ts │ ├── kysely-real.spec.ts │ ├── limit.queries.spec.ts │ ├── migrate │ │ ├── 001-initial.sql │ │ ├── 002-some-feature.sql │ │ ├── 003-test-cert.sql │ │ ├── 004-no-down.sql │ │ └── migrate.spec.ts │ ├── mikro-orm-real.spec.ts │ ├── naming.spec.ts │ ├── nulls.spec.ts │ ├── operators.queries.spec.ts │ ├── order-by.queries.spec.ts │ ├── pg-promise.spec.ts │ ├── pg.spec.ts │ ├── postgres.js-real.spec.ts │ ├── prepare.spec.ts │ ├── publicapi.spec.ts │ ├── regclass.spec.ts │ ├── regtype.spec.ts │ ├── schema-manipulation.spec.ts │ ├── select.queries.spec.ts │ ├── sequelize-real.spec.ts │ ├── sequelize-requests.spec.ts │ ├── sequence.spec.ts │ ├── simple-queries.spec.ts │ ├── slonik.spec.ts │ ├── subqueries.spec.ts │ ├── test-utils.spec.ts │ ├── test-utils.ts │ ├── transactions.queries.spec.ts │ ├── typeorm-real.spec.ts │ ├── typeorm-requests.spec.ts │ ├── union.queries.spec.ts │ ├── update.queries.spec.ts │ ├── various-expressions.spec.ts │ ├── views.spec.ts │ ├── where.queries.spec.ts │ └── with.queries.spec.ts ├── transaction.ts ├── transforms │ ├── aggregation.ts │ ├── aggregations │ │ ├── array_agg.ts │ │ ├── avg.ts │ │ ├── bool-aggregs.ts │ │ ├── count.ts │ │ ├── json_aggs.ts │ │ ├── max-min.ts │ │ └── sum.ts │ ├── alias.ts │ ├── and-filter.ts │ ├── array-filter.ts │ ├── between-filter.ts │ ├── build-filter.ts │ ├── distinct.ts │ ├── eq-filter.ts │ ├── false-filter.ts │ ├── in-filter.ts │ ├── ineq-filter.ts │ ├── join.ts │ ├── limit.ts │ ├── not-in-filter.ts │ ├── or-filter.ts │ ├── order-by.ts │ ├── restrictive-index.ts │ ├── selection.ts │ ├── seq-scan.ts │ ├── startswith-filter.ts │ ├── transform-base.ts │ └── union.ts └── utils.ts ├── testbind.ts ├── tools ├── deno-test.ts ├── deno-transpile.js ├── playground.webpack.config.js └── webpack.config.js ├── tsconfig.deno.json └── tsconfig.json /.deno/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true 3 | } -------------------------------------------------------------------------------- /.deno/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adapters.ts'; 2 | -------------------------------------------------------------------------------- /.deno/constraints/index-cst.ts: -------------------------------------------------------------------------------- 1 | import { _IConstraint, _IIndex, _ITable, _Transaction } from '../interfaces-private.ts'; 2 | 3 | export class IndexConstraint implements _IConstraint { 4 | 5 | constructor(readonly name: string, readonly index: _IIndex, private table: _ITable) { 6 | } 7 | 8 | uninstall(t: _Transaction): void { 9 | this.table.dropIndex(t, this.name); 10 | } 11 | } -------------------------------------------------------------------------------- /.deno/constraints/subscription.ts: -------------------------------------------------------------------------------- 1 | import { _IConstraint, _Transaction } from '../interfaces-private.ts'; 2 | 3 | export class SubscriptionConstraint implements _IConstraint { 4 | constructor(readonly name: string, readonly uninstall: (t: _Transaction) => void) { 5 | } 6 | } -------------------------------------------------------------------------------- /.deno/constraints/wrapped.ts: -------------------------------------------------------------------------------- 1 | import { _IConstraint, _Transaction } from '../interfaces-private.ts'; 2 | 3 | export class ConstraintWrapper implements _IConstraint { 4 | constructor(private refs: Map, private inner: _IConstraint) { 5 | if (inner.name) { 6 | refs.set(inner.name, this); 7 | } 8 | } 9 | get name() { 10 | return this.inner.name; 11 | } 12 | uninstall(t: _Transaction): void { 13 | this.inner.uninstall(t); 14 | if (this.name) { 15 | this.refs.delete(this.name); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.deno/datatypes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './datatypes.ts'; 2 | -------------------------------------------------------------------------------- /.deno/datatypes/t-custom-enum.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from '../evaluator.ts'; 2 | import { TypeBase } from './datatype-base.ts'; 3 | import { DataType, nil, QueryError } from '../interfaces.ts'; 4 | import { _IRelation, _ISchema, _IType, _Transaction } from '../interfaces-private.ts'; 5 | 6 | export function asEnum(o: _IRelation | null): CustomEnumType { 7 | if (o && o.type === 'type' && o instanceof CustomEnumType) { 8 | return o; 9 | } 10 | throw new QueryError(`"${o?.name}" is not a enum`); 11 | } 12 | export class CustomEnumType extends TypeBase { 13 | 14 | get primary(): DataType { 15 | return this.name as any; 16 | } 17 | 18 | get name(): string { 19 | return this._name; 20 | } 21 | 22 | constructor(readonly schema: _ISchema 23 | , private readonly _name: string 24 | , readonly values: string[]) { 25 | super(null); 26 | } 27 | 28 | install() { 29 | this.schema._registerType(this); 30 | } 31 | 32 | doCanCast(to: _IType) { 33 | return to.primary === DataType.text; 34 | } 35 | 36 | doCast(value: Evaluator, to: _IType): Evaluator | nil { 37 | return value; 38 | } 39 | 40 | prefer(type: _IType): _IType | nil { 41 | return this; 42 | } 43 | 44 | doCanBuildFrom(from: _IType): boolean | nil { 45 | return from.primary === DataType.text; 46 | } 47 | 48 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 49 | return value 50 | .setConversion((raw: string) => { 51 | if (!this.values.includes(raw)) { 52 | throw new QueryError(`invalid input value for enum ${this.name}: "${raw}"`); 53 | } 54 | return raw; 55 | } 56 | , conv => ({ conv, toCenum: this.name })) 57 | } 58 | 59 | 60 | drop(t: _Transaction): void { 61 | this.schema._unregisterType(this); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.deno/datatypes/t-equivalent.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from '../evaluator.ts'; 2 | import { TypeBase } from './datatype-base.ts'; 3 | import { CastError, DataType, IEquivalentType, IType, nil, QueryError, typeDefToStr } from '../interfaces.ts'; 4 | import { _ISchema, _IType } from '../interfaces-private.ts'; 5 | import { Types } from './datatypes.ts'; 6 | 7 | export class EquivalentType extends TypeBase { 8 | 9 | private equiv: _IType; 10 | 11 | constructor(private def: IEquivalentType) { 12 | super(null); 13 | if (typeof def.equivalentTo === 'string') { 14 | let eq = (Types as any)[def.equivalentTo]; 15 | if (typeof eq === 'function') { 16 | eq = eq(); 17 | } 18 | this.equiv = eq; 19 | } else { 20 | this.equiv = def.equivalentTo as _IType; 21 | } 22 | 23 | if (!this.equiv) { 24 | throw new Error(`Invalid equilvalent type`); 25 | } 26 | } 27 | 28 | get primary(): DataType { 29 | return this.equiv.primary; 30 | } 31 | 32 | get primaryName(): string { 33 | return this.def.name; 34 | } 35 | 36 | get name(): string { 37 | return this.def.name; 38 | } 39 | 40 | doCanCast(to: _IType) { 41 | return to.primary === this.equiv.primary; 42 | } 43 | 44 | doCast(value: Evaluator, to: _IType): Evaluator | nil { 45 | return value; 46 | } 47 | 48 | prefer(type: _IType): _IType | nil { 49 | return this; 50 | } 51 | 52 | doCanBuildFrom(from: _IType): boolean | nil { 53 | // return from.canCast(this.equiv); 54 | return this.equiv.canCast(from); 55 | } 56 | 57 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 58 | return value 59 | .setConversion(x => { 60 | if (!this.def.isValid(x)) { 61 | throw new QueryError(`invalid input syntax for type ${typeDefToStr(this)}: ${x}`); 62 | } 63 | return x; 64 | }, val => ({ val, to: this.equiv.primary })); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.deno/datatypes/t-inet.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from '../evaluator.ts'; 2 | import { TypeBase } from './datatype-base.ts'; 3 | import { DataType, nil, QueryError } from '../interfaces.ts'; 4 | import { _ISchema, _IType } from '../interfaces-private.ts'; 5 | 6 | // https://www.postgresql.org/docs/13/datatype-net-types.html#DATATYPE-INET 7 | 8 | export class INetType extends TypeBase { 9 | 10 | get primary(): DataType { 11 | return DataType.inet 12 | } 13 | 14 | doCanCast(to: _IType) { 15 | return to.primary === DataType.text; 16 | } 17 | 18 | doCast(value: Evaluator, to: _IType): Evaluator | nil { 19 | return value; 20 | } 21 | 22 | prefer(type: _IType): _IType | nil { 23 | return this; 24 | } 25 | 26 | doCanBuildFrom(from: _IType): boolean | nil { 27 | return from.primary === DataType.text; 28 | } 29 | 30 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 31 | return value 32 | .setConversion(x => { 33 | const [_, a, b, c, d, __, m] = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(\/(\d+))?$/.exec(x) ?? [] 34 | if ([a, b, c, d].some(notByte) || notMask(m)) { 35 | throw new QueryError(`invalid input syntax for type inet: ${x}`); 36 | } 37 | return x; 38 | }, toInet => ({ toInet })); 39 | } 40 | } 41 | 42 | function notByte(b: string) { 43 | return !b 44 | || b.length > 1 && b[0] === '0' 45 | || parseInt(b, 10) > 255; 46 | } 47 | 48 | function notMask(b: string) { 49 | return b 50 | && (b.length > 1 && b[0] === '0' 51 | || parseInt(b, 10) > 32); 52 | } -------------------------------------------------------------------------------- /.deno/datatypes/t-interval.ts: -------------------------------------------------------------------------------- 1 | import { DataType, nil, _IType } from '../interfaces-private.ts'; 2 | import { Interval, normalizeInterval, parseIntervalLiteral } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { TypeBase } from './datatype-base.ts'; 4 | import { Evaluator } from '../evaluator.ts'; 5 | import { intervalToSec } from '../utils.ts'; 6 | 7 | export class IntervalType extends TypeBase { 8 | 9 | get primary(): DataType { 10 | return DataType.interval; 11 | } 12 | 13 | doCanBuildFrom(from: _IType) { 14 | switch (from.primary) { 15 | case DataType.text: 16 | return true; 17 | } 18 | return false; 19 | } 20 | 21 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 22 | switch (from.primary) { 23 | case DataType.text: 24 | return value 25 | .setConversion(str => { 26 | const conv = normalizeInterval(parseIntervalLiteral(str)); 27 | return conv; 28 | } 29 | , toInterval => ({ toInterval })); 30 | } 31 | return null; 32 | } 33 | 34 | doEquals(a: Interval, b: Interval): boolean { 35 | return intervalToSec(a) === intervalToSec(b); 36 | } 37 | doGt(a: Interval, b: Interval): boolean { 38 | return intervalToSec(a) > intervalToSec(b); 39 | } 40 | doLt(a: Interval, b: Interval): boolean { 41 | return intervalToSec(a) < intervalToSec(b); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.deno/execution/exec-utils.ts: -------------------------------------------------------------------------------- 1 | import { QName, Statement, NodeLocation, toSql } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 2 | import { _ISchema, QueryError, _Transaction, _IDb } from '../interfaces-private.ts'; 3 | 4 | export function checkExistence(schema: _ISchema, name: QName, ifNotExists: boolean | undefined, act: () => void): boolean { 5 | // check if object exists 6 | const exists = schema.getObject(name, { 7 | skipSearch: true, 8 | nullIfNotFound: true 9 | }); 10 | if (exists) { 11 | if (ifNotExists) { 12 | return false; 13 | } 14 | throw new QueryError(`relation "${name.name}" already exists`); 15 | } 16 | 17 | // else, perform operation 18 | act(); 19 | return true; 20 | } 21 | 22 | 23 | 24 | export function locOf(p: Statement): NodeLocation { 25 | return p._location ?? { start: 0, end: 0 }; 26 | } 27 | 28 | export abstract class ExecHelper { 29 | constructor(private statement: Statement) { 30 | } 31 | 32 | protected noData(t: _Transaction, name?: string) { 33 | return { 34 | result: { 35 | command: name ?? this.statement.type.toUpperCase(), 36 | fields: [], 37 | rowCount: 0, 38 | rows: [], 39 | location: locOf(this.statement), 40 | }, 41 | state: t, 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.deno/execution/records-mutations/deletion.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _Transaction, IValue, _Explainer, _ISchema, asTable, _ISelection, _IIndex, _IStatement } from '../../interfaces-private.ts'; 2 | import { DeleteStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { MutationDataSourceBase } from './mutation-base.ts'; 4 | import { buildCtx } from '../../parser/context.ts'; 5 | 6 | export class Deletion extends MutationDataSourceBase { 7 | 8 | 9 | constructor(ast: DeleteStatement) { 10 | const { schema } = buildCtx(); 11 | const table = asTable(schema.getObject(ast.from)); 12 | const mutatedSel = table 13 | .selection 14 | .filter(ast.where); 15 | 16 | super(table, mutatedSel, ast); 17 | } 18 | 19 | protected performMutation(t: _Transaction): any[] { 20 | // perform deletion 21 | const rows = []; 22 | for (const item of this.mutatedSel.enumerate(t)) { 23 | this.table.delete(t, item); 24 | rows.push(item); 25 | } 26 | return rows; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.deno/execution/records-mutations/truncate-table.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, SchemaField, NotSupported, _ITable, _IStatementExecutor, asTable, StatementResult, _IStatement, TruncateOpts } from '../../interfaces-private.ts'; 2 | import { TruncateTableStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { buildCtx } from '../../parser/context.ts'; 5 | 6 | export class TruncateTable extends ExecHelper implements _IStatementExecutor { 7 | private table: _ITable; 8 | private opts: TruncateOpts; 9 | 10 | constructor(statement: TruncateTableStatement) { 11 | super(statement); 12 | if (statement.tables.length !== 1) { 13 | throw new NotSupported('Multiple truncations'); 14 | } 15 | this.opts = { 16 | cascade: statement.cascade === 'cascade', 17 | restartIdentity: statement.identity === 'restart', 18 | }; 19 | const { schema } = buildCtx(); 20 | this.table = asTable(schema.getObject(statement.tables[0])); 21 | } 22 | 23 | execute(t: _Transaction): StatementResult { 24 | this.table.truncate(t, this.opts); 25 | return this.noData(t, 'TRUNCATE'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/alter-enum.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _IStatementExecutor, _IStatement } from '../../interfaces-private.ts'; 2 | import { AlterEnumType } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore } from '../../utils.ts'; 5 | import { asEnum, CustomEnumType } from '../../datatypes/t-custom-enum.ts'; 6 | 7 | export class AlterEnum extends ExecHelper implements _IStatementExecutor { 8 | private onSchema: _ISchema; 9 | private originalEnum: CustomEnumType; 10 | constructor({ schema }: _IStatement, private p: AlterEnumType) { 11 | super(p); 12 | this.onSchema = schema.getThisOrSiblingFor(p.name); 13 | this.originalEnum = asEnum(schema.getObject(p.name)) 14 | if (!this.onSchema) { 15 | ignore(this.p) 16 | } 17 | } 18 | 19 | execute(t: _Transaction) { 20 | // commit pending data before making changes 21 | // (because the index sequence creation does support further rollbacks) 22 | t = t.fullCommit(); 23 | const enumValues = this.originalEnum.values 24 | 25 | switch (this.p.change.type) { 26 | case 'add value': 27 | enumValues.push(this.p.change.add.value) 28 | break; 29 | case 'rename': 30 | this.originalEnum.drop(t) 31 | this.onSchema.registerEnum(this.p.change.to.name, enumValues) 32 | break; 33 | } 34 | 35 | // new implicit transaction 36 | t = t.fork(); 37 | 38 | return this.noData(t, 'ALTER'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/alter-sequence.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq } from '../../interfaces-private.ts'; 2 | import { AlterSequenceStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore } from '../../utils.ts'; 5 | 6 | export class AlterSequence extends ExecHelper implements _IStatementExecutor { 7 | private seq: _ISequence | null; 8 | 9 | 10 | constructor({ schema }: _IStatement, private p: AlterSequenceStatement) { 11 | super(p); 12 | 13 | this.seq = asSeq(schema.getObject(p.name, { 14 | nullIfNotFound: p.ifExists, 15 | })); 16 | if (!this.seq) { 17 | ignore(this.p); 18 | } 19 | } 20 | 21 | execute(t: _Transaction) { 22 | // commit pending data before making changes 23 | // (because the index sequence creation does support further rollbacks) 24 | t = t.fullCommit(); 25 | 26 | // alter the sequence 27 | this.seq?.alter(t, this.p.change); 28 | 29 | // new implicit transaction 30 | t = t.fork(); 31 | 32 | return this.noData(t, 'ALTER'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/create-enum.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, _ISchema, NotSupported, CreateIndexColDef, _ITable, CreateIndexDef, _IStatement, _IStatementExecutor } from '../../interfaces-private.ts'; 2 | import { CreateEnumType } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | 5 | export class CreateEnum extends ExecHelper implements _IStatementExecutor { 6 | private onSchema: _ISchema; 7 | private values: string[]; 8 | private name: string; 9 | 10 | constructor({ schema }: _IStatement, st: CreateEnumType) { 11 | super(st); 12 | this.onSchema = schema.getThisOrSiblingFor(st.name); 13 | this.values = st.values.map(x => x.value); 14 | this.name = st.name.name; 15 | } 16 | 17 | execute(t: _Transaction) { 18 | // commit pending data before making changes 19 | // (because does not support further rollbacks) 20 | t = t.fullCommit(); 21 | 22 | // register enum 23 | this.onSchema 24 | .registerEnum(this.name, this.values); 25 | 26 | // new implicit transaction 27 | t = t.fork(); 28 | return this.noData(t, 'CREATE'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/create-materialized-view.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, asTable, _ISchema, NotSupported, CreateIndexColDef, _ITable, CreateIndexDef, _IStatement, _IStatementExecutor, asView, _IView, QueryError } from '../../interfaces-private.ts'; 2 | import { CreateMaterializedViewStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { View } from '../../schema/view.ts'; 5 | import { buildSelect } from '../select.ts'; 6 | 7 | export class CreateMaterializedView extends ExecHelper implements _IStatementExecutor { 8 | private schema: _ISchema; 9 | private toRegister?: View; 10 | 11 | 12 | constructor(st: _IStatement, p: CreateMaterializedViewStatement) { 13 | super(p); 14 | this.schema = st.schema.getThisOrSiblingFor(p.name); 15 | // check existence 16 | const existing = this.schema.getObject(p.name, { nullIfNotFound: true }); 17 | if (existing) { 18 | if (p.ifNotExists) { 19 | return; 20 | } 21 | throw new QueryError(`Name already exists: ${p.name.name}`); 22 | } 23 | 24 | const view = buildSelect(p.query); 25 | 26 | // hack: materialized views are implemented as simple views :/ (todo ?) 27 | this.toRegister = new View(this.schema, p.name.name, view); 28 | } 29 | 30 | 31 | execute(t: _Transaction) { 32 | if (!this.toRegister) { 33 | return this.noData(t, 'CREATE'); 34 | } 35 | 36 | // commit pending data before making changes 37 | // (because does not support further rollbacks) 38 | t = t.fullCommit(); 39 | 40 | // view creation 41 | this.toRegister.register(); 42 | 43 | // new implicit transaction 44 | t = t.fork(); 45 | return this.noData(t, 'CREATE'); 46 | } 47 | } -------------------------------------------------------------------------------- /.deno/execution/schema-amends/create-schema.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, asTable, _ISchema, NotSupported, CreateIndexColDef, _ITable, CreateIndexDef, _IStatement, _IStatementExecutor, QueryError } from '../../interfaces-private.ts'; 2 | import { CreateSchemaStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore } from '../../utils.ts'; 5 | 6 | export class CreateSchema extends ExecHelper implements _IStatementExecutor { 7 | private toCreate?: string; 8 | 9 | constructor(private st: _IStatement, p: CreateSchemaStatement) { 10 | super(p); 11 | const sch = this.st.schema.db.getSchema(p.name.name, true); 12 | if (!p.ifNotExists && sch) { 13 | throw new QueryError('schema already exists! ' + p.name); 14 | } 15 | if (sch) { 16 | ignore(p); 17 | } else { 18 | this.toCreate = p.name.name; 19 | } 20 | } 21 | 22 | execute(t: _Transaction) { 23 | // commit pending data before making changes 24 | // (because does not support further rollbacks) 25 | t = t.fullCommit(); 26 | 27 | // create schema 28 | if (this.toCreate) { 29 | this.st.schema.db.createSchema(this.toCreate); 30 | } 31 | 32 | // new implicit transaction 33 | t = t.fork(); 34 | return this.noData(t, 'CREATE'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/create-sequence.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, NotSupported, _ISequence, _IStatementExecutor } from '../../interfaces-private.ts'; 2 | import { QName, CreateSequenceStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { Sequence } from '../../schema/sequence.ts'; 4 | import { checkExistence, ExecHelper } from '../exec-utils.ts'; 5 | 6 | export class ExecuteCreateSequence extends ExecHelper implements _IStatementExecutor { 7 | schema: _ISchema; 8 | constructor(inSchema: _ISchema, private p: CreateSequenceStatement, private acceptTempSequences: boolean) { 9 | super(p); 10 | const name: QName = p.name; 11 | this.schema = inSchema.getThisOrSiblingFor(name); 12 | } 13 | 14 | execute(t: _Transaction) { 15 | // commit pending data before making changes 16 | // (because the index sequence creation does support further rollbacks) 17 | t = t.fullCommit(); 18 | 19 | // create the sequence 20 | this.createSeq(t); 21 | 22 | // new implicit transaction 23 | t = t.fork(); 24 | return this.noData(t, 'CREATE'); 25 | } 26 | 27 | createSeq(t: _Transaction) { 28 | const p = this.p; 29 | const name: QName = p.name; 30 | // const ret = this.simple('CREATE', p); 31 | 32 | let ret: _ISequence | null = null; 33 | checkExistence(this.schema, name, p.ifNotExists, () => { 34 | if (p.temp && !this.acceptTempSequences) { 35 | throw new NotSupported('temp sequences'); 36 | } 37 | ret = new Sequence(name.name, this.schema) 38 | .alter(t, p.options); 39 | this.schema.db.onSchemaChange(); 40 | }); 41 | return ret; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/do.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult, _IStatement, CompiledFunction } from '../../interfaces-private.ts'; 2 | import { DoStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../../execution/exec-utils.ts'; 4 | 5 | export class DoStatementExec extends ExecHelper implements _IStatementExecutor { 6 | private compiled: CompiledFunction; 7 | 8 | constructor({ schema }: _IStatement, st: DoStatement) { 9 | super(st); 10 | const lang = schema.db.getLanguage(st.language?.name ?? 'plpgsql'); 11 | this.compiled = lang({ 12 | args: [], 13 | code: st.code, 14 | schema: schema, 15 | }); 16 | } 17 | 18 | execute(t: _Transaction): StatementResult { 19 | this.compiled(); 20 | return this.noData(t, 'DO'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/drop-index.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq, asIndex, _INamedIndex } from '../../interfaces-private.ts'; 2 | import { DropStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore, notNil } from '../../utils.ts'; 5 | 6 | export class DropIndex extends ExecHelper implements _IStatementExecutor { 7 | private idx: _INamedIndex[]; 8 | 9 | 10 | constructor({ schema }: _IStatement, statement: DropStatement) { 11 | super(statement); 12 | 13 | this.idx = notNil(statement.names.map(x => asIndex(schema.getObject(x, { 14 | nullIfNotFound: statement.ifExists, 15 | })))); 16 | 17 | if (this.idx.length) { 18 | ignore(statement.concurrently); 19 | } else { 20 | ignore(statement); 21 | } 22 | } 23 | 24 | execute(t: _Transaction) { 25 | // commit pending data before making changes 26 | // (because the index sequence creation does support further rollbacks) 27 | t = t.fullCommit(); 28 | 29 | // alter the sequence 30 | for (const idx of this.idx) { 31 | idx.onTable.dropIndex(t, idx.name); 32 | } 33 | 34 | // new implicit transaction 35 | t = t.fork(); 36 | 37 | return this.noData(t, 'DROP'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/drop-sequence.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq } from '../../interfaces-private.ts'; 2 | import { DropStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore, notNil } from '../../utils.ts'; 5 | 6 | export class DropSequence extends ExecHelper implements _IStatementExecutor { 7 | private seqs: _ISequence[]; 8 | 9 | constructor({ schema }: _IStatement, statement: DropStatement) { 10 | super(statement); 11 | 12 | this.seqs = notNil(statement.names.map(x => asSeq(schema.getObject(x, { 13 | nullIfNotFound: statement.ifExists, 14 | })))); 15 | if (!this.seqs.length) { 16 | ignore(statement); 17 | } 18 | } 19 | 20 | execute(t: _Transaction) { 21 | // commit pending data before making changes 22 | // (because the index sequence creation does support further rollbacks) 23 | t = t.fullCommit(); 24 | 25 | // drop the sequence 26 | for (const seq of this.seqs) { 27 | seq.drop(t); 28 | } 29 | 30 | // new implicit transaction 31 | t = t.fork(); 32 | 33 | return this.noData(t, 'DROP'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/drop-table.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq, asIndex, _INamedIndex, _ITable, asTable } from '../../interfaces-private.ts'; 2 | import { DropStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore, notNil } from '../../utils.ts'; 5 | 6 | export class DropTable extends ExecHelper implements _IStatementExecutor { 7 | private tables: _ITable[]; 8 | private cascade: boolean; 9 | 10 | 11 | constructor({ schema }: _IStatement, statement: DropStatement) { 12 | super(statement); 13 | 14 | this.tables = notNil(statement.names.map(x => asTable(schema.getObject(x, { 15 | nullIfNotFound: statement.ifExists, 16 | })))); 17 | 18 | this.cascade = statement.cascade === 'cascade'; 19 | 20 | if (!this.tables.length) { 21 | ignore(statement); 22 | } 23 | } 24 | 25 | execute(t: _Transaction) { 26 | // commit pending data before making changes 27 | // (because it does not support further rollbacks) 28 | t = t.fullCommit(); 29 | 30 | // drop table 31 | for (const table of this.tables) { 32 | table.drop(t, this.cascade); 33 | } 34 | 35 | // new implicit transaction 36 | t = t.fork(); 37 | 38 | return this.noData(t, 'DROP'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.deno/execution/schema-amends/drop-type.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq, asType, _IType } from '../../interfaces-private.ts'; 2 | import { DropStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ExecHelper } from '../exec-utils.ts'; 4 | import { ignore, notNil } from '../../utils.ts'; 5 | 6 | export class DropType extends ExecHelper implements _IStatementExecutor { 7 | private types: _IType[]; 8 | 9 | constructor({ schema }: _IStatement, statement: DropStatement) { 10 | super(statement); 11 | 12 | this.types = notNil(statement.names.map(x => asType(schema.getObject(x, { 13 | nullIfNotFound: statement.ifExists, 14 | })))); 15 | if (!this.types.length) { 16 | ignore(statement); 17 | } 18 | } 19 | 20 | execute(t: _Transaction) { 21 | // commit pending data before making changes 22 | // (because the index sequence creation does support further rollbacks) 23 | t = t.fullCommit(); 24 | 25 | // drop the sequence 26 | for (const seq of this.types) { 27 | seq.drop(t); 28 | } 29 | 30 | // new implicit transaction 31 | t = t.fork(); 32 | 33 | return this.noData(t, 'DROP'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.deno/execution/set.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult, GLOBAL_VARS, QueryError } from '../interfaces-private.ts'; 2 | import { SetGlobalStatement, SetTimezone, SetNames } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { ignore } from '../utils.ts'; 4 | import { ExecHelper } from './exec-utils.ts'; 5 | 6 | export class SetExecutor extends ExecHelper implements _IStatementExecutor { 7 | 8 | constructor(private p: SetGlobalStatement | SetTimezone | SetNames) { 9 | super(p); 10 | // todo handle set statements timezone ? 11 | // They are just ignored as of today (in order to handle pg_dump exports) 12 | ignore(p); 13 | } 14 | 15 | execute(t: _Transaction): StatementResult { 16 | const p = this.p; 17 | if (p.type === 'set' && p.set.type === 'value') { 18 | t.set(GLOBAL_VARS, t.getMap(GLOBAL_VARS) 19 | .set(p.variable.name, p.set.value)); 20 | } 21 | return this.noData(t, 'SET'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.deno/execution/show.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult, GLOBAL_VARS, QueryError } from '../interfaces-private.ts'; 2 | import { ShowStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { locOf } from './exec-utils.ts'; 4 | 5 | export class ShowExecutor implements _IStatementExecutor { 6 | constructor(private statement: ShowStatement) { } 7 | 8 | execute(t: _Transaction): StatementResult { 9 | const p = this.statement; 10 | const got = t.getMap(GLOBAL_VARS); 11 | if (!got.has(p.variable.name)) { 12 | throw new QueryError(`unrecognized configuration parameter "${p.variable.name}"`); 13 | } 14 | return { 15 | state: t, 16 | result: { 17 | rows: [{ [p.variable.name]: got.get(p.variable.name) }], 18 | rowCount: 1, 19 | command: 'SHOW', 20 | fields: [], 21 | location: locOf(p), 22 | }, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.deno/execution/transaction-statements.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult } from '../interfaces-private.ts'; 2 | import { ExecHelper } from './exec-utils.ts'; 3 | import { CommitStatement, RollbackStatement, StartTransactionStatement, BeginStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 4 | import { ignore } from '../utils.ts'; 5 | 6 | export class CommitExecutor extends ExecHelper implements _IStatementExecutor { 7 | 8 | constructor(statement: CommitStatement) { 9 | super(statement) 10 | } 11 | 12 | execute(t: _Transaction): StatementResult { 13 | t = t.commit(); 14 | // recreate an implicit transaction if we're at root 15 | // (I can see how its usfull, but this is dubious...) 16 | if (!t.isChild) { 17 | t = t.fork(); 18 | } 19 | return this.noData(t, 'COMMIT'); 20 | } 21 | 22 | } 23 | 24 | export class RollbackExecutor extends ExecHelper implements _IStatementExecutor { 25 | constructor(statement: RollbackStatement) { 26 | super(statement); 27 | ignore(statement); 28 | } 29 | 30 | execute(t: _Transaction): StatementResult { 31 | t = t.rollback(); 32 | return this.noData(t, 'ROLLBACK'); 33 | } 34 | } 35 | 36 | 37 | export class BeginStatementExec extends ExecHelper implements _IStatementExecutor { 38 | constructor(statement: BeginStatement | StartTransactionStatement) { 39 | super(statement); 40 | ignore(statement); 41 | } 42 | 43 | execute(t: _Transaction): StatementResult { 44 | t = t.fork(); 45 | return this.noData(t, 'BEGIN'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.deno/functions/date.ts: -------------------------------------------------------------------------------- 1 | import { FunctionDefinition } from '../interfaces.ts'; 2 | import moment from 'https://deno.land/x/momentjs@2.29.1-deno/mod.ts'; 3 | import { DataType, QueryError } from '../interfaces-private.ts'; 4 | import { nullIsh } from '../utils.ts'; 5 | 6 | 7 | export const dateFunctions: FunctionDefinition[] = [ 8 | { 9 | name: 'to_date', 10 | args: [DataType.text, DataType.text], 11 | returns: DataType.date, 12 | implementation: (data, format) => { 13 | if (nullIsh(data) || nullIsh(format)) { 14 | return null; // if one argument is null => null 15 | } 16 | const ret = moment.utc(data, format); 17 | if (!ret.isValid()) { 18 | throw new QueryError(`The text '${data}' does not match the date format ${format}`); 19 | } 20 | return ret.toDate(); 21 | } 22 | }, 23 | { 24 | name: 'now', 25 | returns: DataType.timestamptz, 26 | impure: true, 27 | implementation: () => new Date(), 28 | }, 29 | ]; -------------------------------------------------------------------------------- /.deno/functions/index.ts: -------------------------------------------------------------------------------- 1 | import { stringFunctions } from './string.ts'; 2 | import { dateFunctions } from './date.ts'; 3 | import { systemFunctions } from './system.ts'; 4 | import { sequenceFunctions } from './sequence-fns.ts'; 5 | import { numberFunctions } from './numbers.ts'; 6 | import { subqueryFunctions } from './subquery.ts'; 7 | 8 | 9 | export const allFunctions = [ 10 | ...stringFunctions 11 | , ... dateFunctions 12 | , ... systemFunctions 13 | , ... sequenceFunctions 14 | , ... numberFunctions 15 | , ... subqueryFunctions 16 | ] 17 | -------------------------------------------------------------------------------- /.deno/functions/numbers.ts: -------------------------------------------------------------------------------- 1 | import { DataType, FunctionDefinition } from '../interfaces.ts'; 2 | 3 | export const numberFunctions: FunctionDefinition[] = [ 4 | { 5 | name: 'greatest', 6 | args: [DataType.integer], 7 | argsVariadic: DataType.integer, 8 | returns: DataType.integer, 9 | implementation: (...args: number[]) => Math.max(...args), 10 | }, 11 | { 12 | name: 'least', 13 | args: [DataType.integer], 14 | argsVariadic: DataType.integer, 15 | returns: DataType.integer, 16 | implementation: (...args: number[]) => Math.min(...args), 17 | }, 18 | ] -------------------------------------------------------------------------------- /.deno/functions/string.ts: -------------------------------------------------------------------------------- 1 | import { DataType, FunctionDefinition } from '../interfaces-private.ts'; 2 | 3 | export const stringFunctions: FunctionDefinition[] = [ 4 | { 5 | name: 'lower', 6 | args: [DataType.text], 7 | returns: DataType.text, 8 | implementation: (x: string) => x?.toLowerCase(), 9 | }, 10 | { 11 | name: 'upper', 12 | args: [DataType.text], 13 | returns: DataType.text, 14 | implementation: (x: string) => x?.toUpperCase(), 15 | }, 16 | { 17 | name: 'concat', 18 | args: [DataType.text], 19 | argsVariadic: DataType.text, 20 | returns: DataType.text, 21 | allowNullArguments: true, 22 | implementation: (...x: string[]) => x?.join(''), 23 | }, 24 | { 25 | name: 'concat_ws', 26 | args: [DataType.text], 27 | argsVariadic: DataType.text, 28 | returns: DataType.text, 29 | allowNullArguments: true, 30 | implementation: (separator: string, ...x: string[]) => x?.join(separator), 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /.deno/functions/subquery.ts: -------------------------------------------------------------------------------- 1 | import { FunctionDefinition } from '../interfaces.ts'; 2 | import { DataType } from '../interfaces-private.ts'; 3 | 4 | export const subqueryFunctions: FunctionDefinition[] = [ 5 | { 6 | name: 'exists', 7 | args: [DataType.integer], 8 | argsVariadic: DataType.integer, 9 | returns: DataType.bool, 10 | allowNullArguments: true, 11 | impure: true, 12 | implementation: (...items: number[]) => items?.some?.(Boolean) ?? false, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /.deno/functions/system.ts: -------------------------------------------------------------------------------- 1 | import { Types } from '../datatypes/index.ts'; 2 | import { FunctionDefinition } from '../interfaces.ts'; 3 | 4 | export const systemFunctions: FunctionDefinition[] = [ 5 | { 6 | // ugly hack... 7 | name: 'current_schema', 8 | returns: Types.text(), 9 | implementation: () => 'public', 10 | }, 11 | { 12 | name: 'obj_description', 13 | args: [Types.regclass, Types.text()], 14 | returns: Types.null, 15 | implementation: () => null 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /.deno/index.ts: -------------------------------------------------------------------------------- 1 | export { newDb } from './db.ts'; 2 | export { enableStatementLocationTracking } from './parser/parse-cache.ts'; 3 | export { replaceQueryArgs$ } from './adapters/index.ts'; 4 | export * from './interfaces.ts'; 5 | -------------------------------------------------------------------------------- /.deno/migrate/migrate-interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface MigrationParams { 2 | /** 3 | * If true, will force the migration API to rollback and re-apply the latest migration over 4 | * again each time when Node.js app launches. 5 | */ 6 | force?: boolean 7 | /** 8 | * Migrations table name. Default is 'migrations' 9 | */ 10 | table?: string 11 | /** 12 | * Path to the migrations folder. Default is `path.join(process.cwd(), 'migrations')` 13 | */ 14 | migrationsPath?: string 15 | /** 16 | * Migration data read from migrations folder. `migrationsPath` will be ignored if this is 17 | * provided. 18 | */ 19 | migrations?: readonly MigrationData[] 20 | } 21 | 22 | export interface MigrationFile { 23 | id: number 24 | name: string 25 | filename: string 26 | } 27 | 28 | export interface MigrationData { 29 | id: number 30 | name: string 31 | up: string 32 | down: string 33 | } 34 | -------------------------------------------------------------------------------- /.deno/misc/buffer-deno.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* 5 | ⛔⛔⛔ WARN ⛔⛔⛔ 6 | 7 | This file is built for Deno. 8 | Dont be surprised if it yields errors in Node, it's not meant to be used there. 9 | 10 | (transpilation replaces buffer-node.ts by buffer-node.ts when building deno version) 11 | 12 | */ 13 | 14 | export type TBuffer = Uint8Array; 15 | 16 | 17 | export function bufToString(buf: TBuffer): string { 18 | // @ts-ignore 19 | const decoder = new TextDecoder() 20 | return decoder.decode(buf); 21 | } 22 | 23 | export function bufCompare(a: TBuffer, b: TBuffer) { 24 | if (a === b) { 25 | return 0; 26 | } 27 | if (a.length > b.length) { 28 | return 1; 29 | } 30 | for (let i = 0; i < a.length; i++) { 31 | const d = a[i] - b[i]; 32 | if (d === 0) { 33 | continue; 34 | } 35 | return d < 0 ? -1 : 1; 36 | } 37 | return 0; 38 | } 39 | 40 | export function bufFromString(str: string) { 41 | // @ts-ignore 42 | const encoder = new TextEncoder() 43 | const buffer = encoder.encode(str); 44 | return buffer; 45 | } 46 | 47 | export function isBuf(v: any): v is TBuffer { 48 | return v instanceof Uint8Array; 49 | } 50 | 51 | export function bufClone(buf: TBuffer): TBuffer { 52 | return new Uint8Array(buf); 53 | } 54 | -------------------------------------------------------------------------------- /.deno/misc/buffer-node.ts: -------------------------------------------------------------------------------- 1 | export type TBuffer = Buffer; 2 | 3 | 4 | export function bufToString(buf: TBuffer): string { 5 | return buf?.toString('utf-8'); 6 | } 7 | 8 | export function bufCompare(a: TBuffer, b: TBuffer) { 9 | return Buffer.compare(a, b); 10 | } 11 | 12 | export function bufFromString(str: string) { 13 | return Buffer.from(str); 14 | } 15 | 16 | export function isBuf(v: any): v is TBuffer { 17 | return Buffer.isBuffer(v); 18 | } 19 | 20 | export function bufClone(buf: TBuffer): TBuffer { 21 | const bufcopy = Buffer.alloc(buf.length); 22 | buf.copy(bufcopy); 23 | return bufcopy; 24 | } -------------------------------------------------------------------------------- /.deno/misc/pg-escape.ts: -------------------------------------------------------------------------------- 1 | // stolen from https://github.com/segmentio/pg-escape/blob/master/index.js 2 | 3 | export function literal(val: any) { 4 | if (null == val) return 'NULL'; 5 | if (Array.isArray(val)) { 6 | var vals: any[] = val.map(literal) 7 | return "(" + vals.join(", ") + ")" 8 | } 9 | var backslash = ~val.indexOf('\\'); 10 | var prefix = backslash ? 'E' : ''; 11 | val = val.replace(/'/g, "''"); 12 | val = val.replace(/\\/g, '\\\\'); 13 | return prefix + "'" + val + "'"; 14 | }; 15 | -------------------------------------------------------------------------------- /.deno/mod.ts: -------------------------------------------------------------------------------- 1 | export * from './index.ts'; -------------------------------------------------------------------------------- /.deno/parser/parse-cache.ts: -------------------------------------------------------------------------------- 1 | 2 | import { QueryError } from '../interfaces.ts'; 3 | import LRUCache from 'https://deno.land/x/lru_cache@6.0.0-deno.4/mod.ts'; 4 | import hash from 'https://deno.land/x/object_hash@2.0.3.1/mod.ts'; 5 | import { Expr, parse, Statement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 6 | import { errorMessage } from '../utils.ts'; 7 | 8 | 9 | const astCache: LRUCache = new LRUCache({ 10 | max: 1000, 11 | }); 12 | 13 | let locationTracking = false; 14 | export function enableStatementLocationTracking() { 15 | locationTracking = true; 16 | astCache.reset(); 17 | } 18 | 19 | 20 | /** Parse an AST from SQL */ 21 | export function parseSql(sql: string): Statement[]; 22 | export function parseSql(sql: string, entry: 'expr'): Expr; 23 | export function parseSql(sql: string, entry?: string): any { 24 | // when 'entry' is not specified, lets cache parsings 25 | // => better perf on repetitive requests 26 | const key = !entry && hash(sql); 27 | if (!entry) { 28 | const cached = astCache.get(key); 29 | if (cached) { 30 | return cached; 31 | } 32 | } 33 | 34 | try { 35 | 36 | let ret = parse(sql, { 37 | entry, 38 | locationTracking, 39 | }); 40 | 41 | // cache result 42 | if (!entry) { 43 | astCache.set(key, ret); 44 | } 45 | return ret; 46 | 47 | } catch (e) { 48 | const msg = errorMessage(e); 49 | if (!/Syntax error/.test(msg)) { 50 | throw e; 51 | } 52 | 53 | 54 | // throw a nice parsing error. 55 | throw new QueryError(`💔 Your query failed to parse. 56 | This is most likely due to a SQL syntax error. However, you might also have hit a bug, or an unimplemented feature of pg-mem. 57 | If this is the case, please file an issue at https://github.com/oguimbal/pg-mem along with a query that reproduces this syntax error. 58 | 59 | 👉 Failed query: 60 | 61 | ${sql} 62 | 63 | 💀 ${msg}`); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /.deno/schema/consts.ts: -------------------------------------------------------------------------------- 1 | export const SCHEMA_NAMESPACE = 11; 2 | export const MAIN_NAMESPACE = 2200; 3 | 4 | type OidType = 'table' | 'index'; 5 | export function makeOid(type: OidType, id: string) { 6 | return `oid:${type}:${id}`; 7 | } 8 | 9 | export function parseOid(oid: string): { type: OidType; id: string } { 10 | const [_, type, id] = /^oid:([^:]+):([^:]+)$/.exec(oid) ?? []; 11 | return { 12 | type: type as OidType, 13 | id 14 | } 15 | } -------------------------------------------------------------------------------- /.deno/schema/function-call-table.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, IValue, _Explainer, _IIndex, _SelectExplanation, Stats } from '../interfaces-private.ts'; 2 | import { RecordCol } from '../datatypes/index.ts'; 3 | import { buildCtx } from '../parser/context.ts'; 4 | import { DataSourceBase } from '../transforms/transform-base.ts'; 5 | import { columnEvaluator } from '../transforms/selection.ts'; 6 | import { colByName, fromEntries } from '../utils.ts'; 7 | 8 | export class FunctionCallTable extends DataSourceBase { 9 | readonly columns: readonly IValue[]; 10 | private readonly colsByName: Map; 11 | private symbol = Symbol(); 12 | 13 | get isExecutionWithNoResult(): boolean { 14 | return false; 15 | } 16 | 17 | constructor(cols: readonly RecordCol[], private evaluator: IValue) { 18 | super(buildCtx().schema); 19 | this.columns = cols.map(c => columnEvaluator(this, c.name, c.type).setOrigin(this)); 20 | this.colsByName = fromEntries(this.columns.map(c => [c.id!, c])); 21 | } 22 | 23 | entropy(t: _Transaction): number { 24 | return 0; 25 | } 26 | 27 | enumerate(t: _Transaction): Iterable { 28 | const results = this.evaluator.get(null, t); 29 | for (const result of results ?? []) { 30 | result[this.symbol] = true; 31 | } 32 | return results; 33 | } 34 | 35 | hasItem(value: any, t: _Transaction): boolean { 36 | return !!(value as any)[this.symbol]; 37 | } 38 | 39 | getColumn(column: string, nullIfNotFound?: boolean | undefined): IValue { 40 | return colByName(this.colsByName, column, nullIfNotFound)!; 41 | } 42 | 43 | getIndex(forValue: IValue): _IIndex | null | undefined { 44 | return null; 45 | } 46 | 47 | isOriginOf(value: IValue): boolean { 48 | return value.origin === this; 49 | } 50 | 51 | 52 | explain(e: _Explainer): _SelectExplanation { 53 | throw new Error('Method not implemented.'); 54 | } 55 | 56 | stats(t: _Transaction): Stats | null { 57 | throw new Error('Method not implemented.'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.deno/schema/information-schema/constraint-column-usage.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | export class ConstraintColumnUsage extends ReadOnlyTable implements _ITable { 7 | 8 | 9 | _schema: Schema = { 10 | name: 'constraint_column_usage', 11 | fields: [ 12 | { name: 'constraint_catalog', type: Types.text() } 13 | , { name: 'constraint_schema', type: Types.text() } 14 | , { name: 'constraint_name', type: Types.text() } 15 | 16 | , { name: 'table_catalog', type: Types.text() } 17 | , { name: 'table_schema', type: Types.text() } 18 | , { name: 'table_name', type: Types.text() } 19 | 20 | , { name: 'column_name', type: Types.text() } 21 | ] 22 | }; 23 | 24 | 25 | entropy(): number { 26 | return 0; 27 | } 28 | 29 | *enumerate() { 30 | } 31 | 32 | 33 | hasItem(value: any): boolean { 34 | return false; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /.deno/schema/information-schema/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { _IDb, _ISchema } from '../../interfaces-private.ts'; 3 | import { ColumnsListSchema } from './columns-list.ts'; 4 | import { TablesSchema } from './table-list.ts'; 5 | import { TableConstraints } from './table-constraints.ts'; 6 | import { KeyColumnUsage } from './key-column-usage.ts'; 7 | import { ConstraintColumnUsage } from './constraint-column-usage.ts'; 8 | 9 | export function setupInformationSchema(db: _IDb) { 10 | const schema: _ISchema = db.createSchema('information_schema'); 11 | 12 | // SELECT * FROM "information_schema"."tables" WHERE ("table_schema" = 'public' AND "table_name" = 'user') 13 | new TablesSchema(schema).register(); 14 | new ColumnsListSchema(schema).register(); 15 | new TableConstraints(schema).register(); 16 | new KeyColumnUsage(schema).register(); 17 | new ConstraintColumnUsage(schema).register(); 18 | 19 | schema.setReadonly(); 20 | } -------------------------------------------------------------------------------- /.deno/schema/information-schema/key-column-usage.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | 7 | export class KeyColumnUsage extends ReadOnlyTable implements _ITable { 8 | 9 | 10 | _schema: Schema = { 11 | name: 'key_column_usage', 12 | fields: [ 13 | { name: 'constraint_catalog', type: Types.text() } 14 | , { name: 'constraint_schema', type: Types.text() } 15 | , { name: 'constraint_name', type: Types.text() } 16 | , { name: 'table_catalog', type: Types.text() } 17 | , { name: 'table_schema', type: Types.text() } 18 | , { name: 'table_name', type: Types.text() } 19 | , { name: 'column_name', type: Types.text() } 20 | , { name: 'ordinal_position', type: Types.integer } 21 | , { name: 'position_in_unique_constraint', type: Types.integer } 22 | ] 23 | }; 24 | 25 | 26 | entropy(): number { 27 | return 0; 28 | } 29 | 30 | *enumerate() { 31 | } 32 | 33 | 34 | hasItem(value: any): boolean { 35 | return false; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /.deno/schema/information-schema/table-constraints.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | // https://www.postgresql.org/docs/13/catalog-pg-range.html 7 | export class TableConstraints extends ReadOnlyTable implements _ITable { 8 | 9 | 10 | _schema: Schema = { 11 | name: 'table_constraints', 12 | fields: [ 13 | { name: 'constraint_catalog', type: Types.text() } 14 | , { name: 'constraint_schema', type: Types.text() } 15 | , { name: 'constraint_name', type: Types.text() } 16 | , { name: 'table_catalog', type: Types.text() } 17 | , { name: 'table_schema', type: Types.text() } 18 | , { name: 'table_name', type: Types.text() } 19 | , { name: 'constraint_type', type: Types.text() } 20 | , { name: 'is_deferrable', type: Types.bool } 21 | , { name: 'initially_deferred', type: Types.bool } 22 | , { name: 'enforced', type: Types.bool } 23 | ] 24 | }; 25 | 26 | 27 | entropy(): number { 28 | return 0; 29 | } 30 | 31 | *enumerate() { 32 | } 33 | 34 | 35 | hasItem(value: any): boolean { 36 | return false; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-database.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema, _Transaction, setId } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | // https://www.postgresql.org/docs/12/catalog-pg-class.html 7 | 8 | const IS_SCHEMA = Symbol('_is_pg_database'); 9 | export class PgDatabaseTable extends ReadOnlyTable implements _ITable { 10 | 11 | get ownSymbol() { 12 | return IS_SCHEMA; 13 | } 14 | 15 | 16 | _schema: Schema = { 17 | name: 'pg_database', 18 | fields: [ 19 | { name: 'oid', type: Types.integer } // hidden oid column 20 | , { name: 'datname', type: Types.text() } 21 | , { name: 'datdba', type: Types.integer } 22 | , { name: 'encoding', type: Types.integer } 23 | , { name: 'datcollate', type: Types.text() } 24 | , { name: 'datctype', type: Types.text() } 25 | , { name: 'datistemplate', type: Types.bool } 26 | , { name: 'datlowconn', type: Types.bool } 27 | , { name: 'datconlimit', type: Types.integer } 28 | , { name: 'datlastsysoid', type: Types.integer } 29 | , { name: 'datfrozenxid', type: Types.integer } 30 | , { name: 'datminmxid', type: Types.integer } 31 | , { name: 'dattablespace', type: Types.integer } 32 | , { name: 'datacl', type: Types.jsonb } 33 | ] 34 | }; 35 | 36 | entropy(t: _Transaction): number { 37 | return this.db.listSchemas().length; 38 | } 39 | 40 | *enumerate() { 41 | // this is 💩, whaterver... 42 | let i = 48593; 43 | for (const t of this.db.listSchemas()) { 44 | const ret = { 45 | oid: ++i, 46 | datname: t.name, 47 | [IS_SCHEMA]: true, 48 | }; 49 | yield setId(ret, '/schema/pg_database/' + t.name); 50 | } 51 | } 52 | 53 | 54 | hasItem(value: any): boolean { 55 | return !!value?.[IS_SCHEMA]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-enum-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | export class PgEnumTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_enum', 10 | fields: [ 11 | { name: 'oid', type: Types.integer } 12 | , { name: 'enumtypid', type: Types.integer } 13 | , { name: 'enumsortorder', type: Types.integer } 14 | , { name: 'enumlabel', type: Types.text() } 15 | ] 16 | }; 17 | 18 | entropy(): number { 19 | return 0; 20 | } 21 | 22 | *enumerate() { 23 | } 24 | 25 | hasItem(value: any): boolean { 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-index-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | export class PgIndexTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_index', 10 | fields: [ 11 | { name: 'indexrelid', type: Types.integer } // oid 12 | , { name: 'indrelid', type: Types.integer } // oid 13 | , { name: 'indnatts', type: Types.integer } 14 | , { name: 'indnkyatts', type: Types.integer } 15 | , { name: 'indisunique', type: Types.bool } 16 | , { name: 'indisprimary', type: Types.bool } 17 | , { name: 'indisxclusion', type: Types.bool } 18 | , { name: 'indimmediate', type: Types.bool } 19 | , { name: 'indisclustered', type: Types.bool } 20 | , { name: 'indisvalid', type: Types.bool } 21 | , { name: 'indcheckxmin', type: Types.bool } 22 | , { name: 'indisready', type: Types.bool } 23 | , { name: 'indisliv', type: Types.bool } 24 | , { name: 'indisreplident', type: Types.bool } 25 | , { name: 'indkey', type: Types.integer.asArray() } // int2vector 26 | , { name: 'indcollation', type: Types.integer.asArray() } // oidvector 27 | , { name: 'indclass', type: Types.integer.asArray() } // oidvector 28 | , { name: 'indoption', type: Types.integer.asArray() } // int2vector 29 | , { name: 'indeexprs', type: Types.jsonb } // pg_node_tree 30 | , { name: 'indpred', type: Types.jsonb } // pg_node_tree 31 | ] 32 | }; 33 | 34 | entropy(): number { 35 | return 0; 36 | } 37 | 38 | *enumerate() { 39 | } 40 | 41 | 42 | 43 | hasItem(value: any): boolean { 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-namespace-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | export class PgNamespaceTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_namespace', 10 | fields: [ 11 | { name: 'oid', type: Types.integer } // hidden oid column 12 | , { name: 'nspname', type: Types.text() } 13 | , { name: 'nspowner', type: Types.integer } // oid 14 | , { name: 'nspacl', type: Types.jsonb } // aclitem[] 15 | ] 16 | }; 17 | 18 | 19 | entropy(): number { 20 | return 0; 21 | } 22 | 23 | *enumerate() { 24 | 25 | // yield { 26 | // oid: MAIN_NAMESPACE, 27 | // nspname: 'public', 28 | // nspowner: null, 29 | // nspacl: null, 30 | // }; 31 | // yield { 32 | // oid: MAIN_NAMESPACE, 33 | // nspname: 'public', 34 | // nspowner: null, 35 | // nspacl: null, 36 | // }; 37 | } 38 | 39 | 40 | 41 | hasItem(value: any): boolean { 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-proc.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | // https://www.postgresql.org/docs/13/catalog-pg-range.html 7 | export class PgProc extends ReadOnlyTable implements _ITable { 8 | 9 | 10 | _schema: Schema = { 11 | name: 'pg_range', 12 | fields: [ 13 | { name: 'rngtypid', type: Types.integer } // oid 14 | , { name: 'rngsubtype', type: Types.integer } // oid 15 | , { name: 'rngcollation', type: Types.integer } // oid 16 | , { name: 'rngsubopc', type: Types.integer } // oid 17 | , { name: 'rngcanonical', type: Types.integer } // oid 18 | , { name: 'rngsubdiff', type: Types.integer } // oid 19 | ] 20 | }; 21 | 22 | 23 | entropy(): number { 24 | return 0; 25 | } 26 | 27 | *enumerate() { 28 | } 29 | 30 | 31 | 32 | hasItem(value: any): boolean { 33 | return false; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-sequences-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | export class PgSequencesTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_sequences', 10 | fields: [ 11 | { name: 'schemaname', type: Types.text() } 12 | , { name: 'sequencename', type: Types.text() } 13 | , { name: 'sequenceowner', type: Types.integer } 14 | , { name: 'data_type', type: Types.text() } 15 | , { name: 'start_value', type: Types.integer } 16 | , { name: 'min_value', type: Types.integer } 17 | , { name: 'max_value', type: Types.integer } 18 | , { name: 'increment_by', type: Types.integer } 19 | , { name: 'cycle', type: Types.bool } 20 | , { name: 'cache_size', type: Types.integer } 21 | , { name: 'last_value', type: Types.integer } 22 | ] 23 | }; 24 | 25 | entropy(): number { 26 | return 0; 27 | } 28 | 29 | *enumerate() { 30 | } 31 | 32 | hasItem(value: any): boolean { 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg-user-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | export class PgUserTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_user', 10 | fields: [ 11 | { name: 'usename', type: Types.text() } 12 | , { name: 'usesysid', type: Types.integer } 13 | , { name: 'usecreatedb', type: Types.bool } 14 | , { name: 'usesuper', type: Types.bool } 15 | , { name: 'usecatupd', type: Types.bool } 16 | , { name: 'userepl', type: Types.bool } 17 | , { name: 'usebypassrls', type: Types.bool } 18 | , { name: 'passwd', type: Types.text() } 19 | , { name: 'valuntil', type: Types.timestamptz() } 20 | , { name: 'useconfig', type: Types.jsonb } 21 | ] 22 | }; 23 | 24 | entropy(): number { 25 | return 0; 26 | } 27 | 28 | *enumerate() { 29 | } 30 | 31 | hasItem(value: any): boolean { 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.deno/schema/pg-catalog/pg_statio_user_tables.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private.ts'; 2 | import { Schema } from '../../interfaces.ts'; 3 | import { Types } from '../../datatypes/index.ts'; 4 | import { ReadOnlyTable } from '../readonly-table.ts'; 5 | 6 | // https://www.postgresql.org/docs/13/catalog-pg-range.html 7 | const IS_SCHEMA = Symbol('_is_pg_statio_user_tables'); 8 | export class PgStatioUserTables extends ReadOnlyTable implements _ITable { 9 | 10 | 11 | _schema: Schema = { 12 | name: 'pg_statio_user_tables', 13 | fields: [ 14 | { name: 'relid', type: Types.integer } // oid 15 | , { name: 'schemaname', type: Types.text() } 16 | , { name: 'relname', type: Types.text() } 17 | , { name: 'heap_blks_read', type: Types.integer } 18 | , { name: 'heap_blks_hit', type: Types.integer } 19 | , { name: 'idx_blks_read', type: Types.integer } 20 | , { name: 'idx_blks_hit', type: Types.integer } 21 | , { name: 'toast_blks_read', type: Types.integer } 22 | , { name: 'toast_blks_hit', type: Types.integer } 23 | , { name: 'tidx_blks_read', type: Types.integer } 24 | , { name: 'tidx_blks_hit', type: Types.integer } 25 | 26 | ] 27 | }; 28 | 29 | 30 | entropy(): number { 31 | return 0; 32 | } 33 | 34 | *enumerate() { 35 | for (const t of this.db.public.listTables()) { 36 | yield { 37 | relid: t.reg.typeId, 38 | schemaname: 'public', 39 | relname: t.name, 40 | heap_blks_read: 0, 41 | heap_blks_hit: 0, 42 | idx_blks_read: 0, 43 | idx_blks_hit: 0, 44 | toast_blks_read: 0, 45 | toast_blks_hit: 0, 46 | tidx_blks_read: 0, 47 | tidx_blks_hit: 0, 48 | [IS_SCHEMA]: true, 49 | }; 50 | } 51 | } 52 | 53 | 54 | 55 | hasItem(value: any): boolean { 56 | return value[IS_SCHEMA]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.deno/schema/view.ts: -------------------------------------------------------------------------------- 1 | import { QueryError, Reg, _Explainer, _ISchema, _ISelection, _IView, _Transaction } from '../interfaces-private.ts'; 2 | import { DataSourceBase, FilterBase } from '../transforms/transform-base.ts'; 3 | 4 | export class View extends FilterBase implements _IView { 5 | get type(): 'view' { 6 | return 'view'; 7 | } 8 | 9 | private _reg?: Reg; 10 | get reg(): Reg { 11 | if (!this._reg) { 12 | throw new QueryError(`relation "${this.name}" does not exist`); 13 | } 14 | return this._reg; 15 | } 16 | 17 | constructor(readonly ownerSchema: _ISchema, readonly name: string, readonly selection: _ISelection) { 18 | super(selection); 19 | } 20 | 21 | 22 | enumerate(t: _Transaction): Iterable { 23 | return this.selection.enumerate(t); 24 | } 25 | 26 | hasItem(value: any, t: _Transaction): boolean { 27 | return this.selection.hasItem(value, t); 28 | } 29 | 30 | explain(e: _Explainer) { 31 | return this.selection.explain(e); 32 | } 33 | 34 | stats(t: _Transaction) { 35 | return this.selection.stats(t); 36 | } 37 | 38 | 39 | register() { 40 | // once fields registered, 41 | // then register the table 42 | // (column registrations need it not to be registered yet) 43 | this._reg = this.ownerSchema._reg_register(this); 44 | return this; 45 | } 46 | 47 | drop(t: _Transaction): void { 48 | throw new Error('Method not implemented.'); 49 | } 50 | } -------------------------------------------------------------------------------- /.deno/transforms/aggregations/array_agg.ts: -------------------------------------------------------------------------------- 1 | import { AggregationComputer, AggregationGroupComputer, IValue, nil, QueryError, _ISelection, _IType, _Transaction } from '../../interfaces-private.ts'; 2 | import { ExprCall } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { buildValue } from '../../parser/expression-builder.ts'; 4 | import { Types } from '../../datatypes/index.ts'; 5 | import { withSelection } from '../../parser/context.ts'; 6 | 7 | 8 | class ArrayAggExpr implements AggregationComputer { 9 | 10 | constructor(private exp: IValue) { 11 | } 12 | 13 | get type(): _IType { 14 | return Types.integer.asArray(); 15 | } 16 | 17 | createGroup(t: _Transaction): AggregationGroupComputer { 18 | let val: any[] = []; 19 | return { 20 | feedItem: (item) => { 21 | const value = this.exp.get(item, t); 22 | val = [...val, value]; 23 | }, 24 | finish: () => val, 25 | } 26 | } 27 | } 28 | 29 | export function buildArrayAgg(this: void, base: _ISelection, call: ExprCall) { 30 | return withSelection(base, () => { 31 | const args = call.args; 32 | if (args.length !== 1) { 33 | throw new QueryError('ARRAY_AGG expects one argument, given ' + args.length); 34 | } 35 | 36 | const what = buildValue(args[0]); 37 | return new ArrayAggExpr(what); 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /.deno/transforms/aggregations/avg.ts: -------------------------------------------------------------------------------- 1 | import { AggregationComputer, AggregationGroupComputer, IValue, nil, QueryError, _ISelection, _IType, _Transaction } from '../../interfaces-private.ts'; 2 | import { ExprCall } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { buildValue } from '../../parser/expression-builder.ts'; 4 | import { Types } from '../../datatypes/index.ts'; 5 | import { nullIsh, sum } from '../../utils.ts'; 6 | import { withSelection } from '../../parser/context.ts'; 7 | 8 | 9 | class AvgExpr implements AggregationComputer { 10 | 11 | constructor(private exp: IValue) { 12 | } 13 | 14 | get type(): _IType { 15 | return Types.bigint; 16 | } 17 | 18 | createGroup(t: _Transaction): AggregationGroupComputer { 19 | let full: number[] = []; 20 | return { 21 | feedItem: (item) => { 22 | const value = this.exp.get(item, t); 23 | if (!nullIsh(value)) { 24 | full.push(value); 25 | } 26 | }, 27 | finish: () => full.length === 0 ? null : sum(full) / full.length, 28 | } 29 | } 30 | } 31 | 32 | 33 | export function buildAvg(this: void, base: _ISelection, call: ExprCall) { 34 | return withSelection(base, () => { 35 | const args = call.args; 36 | if (args.length !== 1) { 37 | throw new QueryError('AVG expects one argument, given ' + args.length); 38 | } 39 | 40 | const what = buildValue(args[0]); 41 | return new AvgExpr(what); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /.deno/transforms/aggregations/bool-aggregs.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, QueryError, AggregationComputer, IValue, _IType, _Transaction, AggregationGroupComputer } from '../../interfaces-private.ts'; 2 | import { ExprCall } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { withSelection } from '../../parser/context.ts'; 4 | import { buildValue } from '../../parser/expression-builder.ts'; 5 | import { nullIsh } from '../../utils.ts'; 6 | import { Types } from '../../datatypes/index.ts'; 7 | 8 | 9 | class BoolAgg implements AggregationComputer { 10 | 11 | constructor(private exp: IValue, private isOr: boolean) { 12 | } 13 | 14 | get type() { 15 | return Types.bool; 16 | } 17 | 18 | createGroup(t: _Transaction): AggregationGroupComputer { 19 | let result: boolean | null = null; 20 | return { 21 | feedItem: (item) => { 22 | if (result === this.isOr) { 23 | // no need to compute it further 24 | return; 25 | } 26 | const value = this.exp.get(item, t); 27 | if (nullIsh(value)) { 28 | return; 29 | } 30 | result = !!value; 31 | }, 32 | finish: () => result, 33 | } 34 | } 35 | } 36 | 37 | 38 | 39 | export function buildBoolAgg(this: void, base: _ISelection, call: ExprCall, fn: 'bool_and' | 'bool_or') { 40 | return withSelection(base, () => { 41 | const args = call.args; 42 | if (args.length !== 1) { 43 | throw new QueryError(fn + ' expects one argument, given ' + args.length); 44 | } 45 | const what = buildValue(args[0]).cast(Types.bool); 46 | return new BoolAgg(what, fn === 'bool_or'); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /.deno/transforms/aggregations/json_aggs.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, QueryError, AggregationComputer, IValue, _IType, _Transaction, AggregationGroupComputer } from '../../interfaces-private.ts'; 2 | import { ExprCall } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { withSelection } from '../../parser/context.ts'; 4 | import { buildValue } from '../../parser/expression-builder.ts'; 5 | import { nullIsh } from '../../utils.ts'; 6 | import { Types } from '../../datatypes/index.ts'; 7 | 8 | 9 | class JsonAggExpr implements AggregationComputer { 10 | 11 | constructor(private exp: IValue, readonly type: _IType) { 12 | } 13 | 14 | createGroup(t: _Transaction): AggregationGroupComputer { 15 | let full: any[][] = []; 16 | return { 17 | feedItem: (item) => { 18 | const value = this.exp.get(item, t); 19 | if (!nullIsh(value)) { 20 | full.push(value); 21 | } 22 | }, 23 | finish: () => full.length === 0 ? null : full, 24 | } 25 | } 26 | } 27 | 28 | 29 | export function buildJsonAgg(this: void, base: _ISelection, call: ExprCall, fn: 'json_agg' | 'jsonb_agg') { 30 | return withSelection(base, () => { 31 | const args = call.args; 32 | if (args.length !== 1) { 33 | throw new QueryError(fn + ' expects one argument, given ' + args.length); 34 | } 35 | const type = fn === 'json_agg' ? Types.json : Types.jsonb; 36 | const what = buildValue(args[0]); 37 | return new JsonAggExpr(what, type); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /.deno/transforms/aggregations/sum.ts: -------------------------------------------------------------------------------- 1 | import { AggregationComputer, AggregationGroupComputer, IValue, nil, QueryError, _ISelection, _IType, _Transaction } from '../../interfaces-private.ts'; 2 | import { ExprCall } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 3 | import { buildValue } from '../../parser/expression-builder.ts'; 4 | import { Types } from '../../datatypes/index.ts'; 5 | import { nullIsh } from '../../utils.ts'; 6 | import { withSelection } from '../../parser/context.ts'; 7 | 8 | class SumExpr implements AggregationComputer { 9 | 10 | constructor(private exp: IValue) { 11 | } 12 | 13 | get type(): _IType { 14 | return Types.bigint; 15 | } 16 | 17 | createGroup(t: _Transaction): AggregationGroupComputer { 18 | let val: number | nil = null; 19 | return { 20 | feedItem: (item) => { 21 | const value = this.exp.get(item, t); 22 | if (!nullIsh(value)) { 23 | val = nullIsh(val) ? value : val + value; 24 | } 25 | }, 26 | finish: () => val, 27 | } 28 | } 29 | } 30 | 31 | export function buildSum(this: void, base: _ISelection, call: ExprCall) { 32 | return withSelection(base, () => { 33 | const args = call.args; 34 | if (args.length !== 1) { 35 | throw new QueryError('SUM expects one argument, given ' + args.length); 36 | } 37 | 38 | const what = buildValue(args[0]); 39 | return new SumExpr(what); 40 | 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /.deno/transforms/array-filter.ts: -------------------------------------------------------------------------------- 1 | import { FilterBase } from './transform-base.ts'; 2 | import { _ISelection, _Explainer, _SelectExplanation, _Transaction, Stats, Row } from '../interfaces-private.ts'; 3 | 4 | export class ArrayFilter extends FilterBase { 5 | 6 | get index() { 7 | return null; 8 | } 9 | 10 | entropy() { 11 | return this.rows.length; 12 | } 13 | 14 | hasItem(raw: Row): boolean { 15 | return this.rows.includes(raw); 16 | } 17 | 18 | getIndex() { 19 | return null; 20 | } 21 | 22 | constructor(fromTable: _ISelection, public rows: Row[]) { 23 | super(fromTable); 24 | } 25 | 26 | enumerate(): Iterable { 27 | return this.rows; 28 | } 29 | 30 | stats(t: _Transaction): Stats | null { 31 | return { 32 | count: this.rows.length, 33 | }; 34 | } 35 | 36 | explain(e: _Explainer): _SelectExplanation { 37 | return { 38 | id: e.idFor(this), 39 | _: 'constantSet', 40 | rawArrayLen: this.rows.length, 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /.deno/transforms/between-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _IIndex, _ITable, _Transaction, _Explainer, _SelectExplanation, IndexOp, Stats, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | import { nullIsh } from '../utils.ts'; 4 | 5 | export class BetweenFilter extends FilterBase { 6 | 7 | private opDef: IndexOp; 8 | 9 | 10 | entropy(t: _Transaction) { 11 | return this.onValue.index!.entropy({ ...this.opDef, t }); 12 | } 13 | 14 | constructor(private onValue: IValue 15 | , private lo: any 16 | , private hi: any 17 | , private op: 'inside' | 'outside') { 18 | super(onValue.origin!); 19 | if (onValue.index!.expressions[0]?.hash !== onValue.hash) { 20 | throw new Error('Between index misuse'); 21 | } 22 | this.opDef = { 23 | type: op, 24 | hi: [hi], 25 | lo: [lo], 26 | t: null as any, 27 | } 28 | } 29 | 30 | hasItem(value: Row, t: _Transaction): boolean { 31 | const v = this.onValue.get(value, t); 32 | if (nullIsh(v)) { 33 | return false; 34 | } 35 | if (this.op === 'inside') { 36 | return !!this.onValue.type.ge(v, this.lo) 37 | && !!this.onValue.type.le(v, this.hi); 38 | } 39 | return !!this.onValue.type.lt(v, this.lo) 40 | || !!this.onValue.type.gt(v, this.lo); 41 | } 42 | 43 | enumerate(t: _Transaction): Iterable { 44 | return this.onValue.index!.enumerate({ ...this.opDef, t }); 45 | } 46 | 47 | 48 | stats(t: _Transaction): Stats | null { 49 | return null; 50 | } 51 | 52 | explain(e: _Explainer): _SelectExplanation { 53 | return { 54 | id: e.idFor(this), 55 | _: this.op, 56 | entropy: this.entropy(e.transaction), 57 | on: this.onValue.index!.explain(e), 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.deno/transforms/false-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _Explainer, _SelectExplanation, _Transaction, Stats, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | 4 | export class FalseFilter extends FilterBase { 5 | 6 | get index() { 7 | return null; 8 | } 9 | 10 | entropy() { 11 | return 0; 12 | } 13 | 14 | hasItem() { 15 | return false; 16 | } 17 | 18 | enumerate(): Iterable { 19 | return []; 20 | } 21 | 22 | stats(t: _Transaction): Stats | null { 23 | return { 24 | count: 0, 25 | } 26 | } 27 | 28 | explain(e: _Explainer): _SelectExplanation { 29 | return { 30 | id: e.idFor(this), 31 | _: 'empty', 32 | }; 33 | } 34 | } -------------------------------------------------------------------------------- /.deno/transforms/ineq-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _IIndex, _ITable, _Transaction, _Explainer, _SelectExplanation, IndexOp, Stats, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | import { nullIsh } from '../utils.ts'; 4 | 5 | export class IneqFilter extends FilterBase { 6 | 7 | private index: _IIndex; 8 | private opDef: IndexOp; 9 | 10 | entropy(t: _Transaction) { 11 | return this.onValue.index!.entropy({ ...this.opDef, t }); 12 | } 13 | 14 | hasItem(item: Row, t: _Transaction) { 15 | const val = this.onValue.get(item, t); 16 | if (nullIsh(val)) { 17 | return false; 18 | } 19 | return !!this.onValue.type[this.op](val, this.than); 20 | } 21 | 22 | constructor(private onValue: IValue 23 | , private op: 'gt' | 'ge' | 'lt' | 'le' 24 | , private than: any) { 25 | super(onValue.origin!); 26 | 27 | this.index = this.onValue.index!; 28 | this.opDef = { 29 | type: op, 30 | key: [than], 31 | t: null as any, 32 | } 33 | } 34 | 35 | 36 | stats(t: _Transaction): Stats | null { 37 | return null; 38 | } 39 | 40 | *enumerate(t: _Transaction): Iterable { 41 | for (const item of this.index.enumerate({ ...this.opDef, t })) { 42 | if (!this.hasItem(item, t)) { 43 | break; 44 | } 45 | yield item; 46 | } 47 | } 48 | 49 | 50 | explain(e: _Explainer): _SelectExplanation { 51 | return { 52 | id: e.idFor(this), 53 | _: 'ineq', 54 | entropy: this.entropy(e.transaction), 55 | on: this.onValue.index!.explain(e), 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /.deno/transforms/limit.ts: -------------------------------------------------------------------------------- 1 | import { IValue, _ISelection, _Transaction, _Explainer, _SelectExplanation, Stats, nil, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | import { LimitStatement } from 'https://deno.land/x/pgsql_ast_parser@12.0.1/mod.ts'; 4 | import { buildValue } from '../parser/expression-builder.ts'; 5 | import { withSelection } from '../parser/context.ts'; 6 | 7 | export function buildLimit(on: _ISelection, limit: LimitStatement) { 8 | return withSelection(on, () => { 9 | const l = limit.limit && buildValue(limit.limit); 10 | const o = limit.offset && buildValue(limit.offset); 11 | return new LimitFilter(on, l, o); 12 | }); 13 | } 14 | 15 | class LimitFilter extends FilterBase { 16 | 17 | get index() { 18 | return null; 19 | } 20 | 21 | entropy(t: _Transaction) { 22 | return this.selection.entropy(t); 23 | } 24 | 25 | hasItem(raw: Row, t: _Transaction): boolean { 26 | return this.base.hasItem(raw, t); 27 | } 28 | 29 | constructor(private selection: _ISelection, private take: IValue | nil, private skip: IValue | nil) { 30 | super(selection); 31 | } 32 | 33 | 34 | stats(t: _Transaction): Stats | null { 35 | return null; 36 | } 37 | 38 | *enumerate(t: _Transaction): Iterable { 39 | let skip = this.skip?.get(null, t) ?? 0; 40 | let take = this.take?.get(null, t) ?? Number.MAX_SAFE_INTEGER; 41 | if (take <= 0) { 42 | return; 43 | } 44 | for (const raw of this.selection.enumerate(t)) { 45 | if (skip > 0) { 46 | skip--; 47 | continue; 48 | } 49 | yield raw; 50 | take--; 51 | if (!take) { 52 | return; 53 | } 54 | } 55 | } 56 | 57 | 58 | 59 | explain(e: _Explainer): _SelectExplanation { 60 | return { 61 | id: e.idFor(this), 62 | _: 'limit', 63 | take: this.take?.explain(e), 64 | skip: this.skip?.explain(e), 65 | on: this.selection.explain(e), 66 | }; 67 | } 68 | } -------------------------------------------------------------------------------- /.deno/transforms/or-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, _IIndex, _ITable, getId, _Transaction, _Explainer, _SelectExplanation, Stats, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | 4 | 5 | export class OrFilter extends FilterBase { 6 | 7 | entropy(t: _Transaction) { 8 | return this.left.entropy(t) + this.right.entropy(t); 9 | } 10 | 11 | hasItem(value: Row, t: _Transaction): boolean { 12 | return this.left.hasItem(value, t) || this.right.hasItem(value, t); 13 | } 14 | 15 | constructor(private left: _ISelection, private right: _ISelection) { 16 | super(left); 17 | if (left.columns !== right.columns) { // istanbul ignore next 18 | throw new Error('Column set mismatch'); 19 | } 20 | } 21 | 22 | stats(t: _Transaction): Stats | null { 23 | return null; 24 | } 25 | 26 | *enumerate(t: _Transaction): Iterable { 27 | const yielded = new Set(); 28 | for (const item of this.left.enumerate(t)) { 29 | yield item; 30 | yielded.add(getId(item)); 31 | } 32 | for (const item of this.right.enumerate(t)) { 33 | const id = getId(item); 34 | if (!yielded.has(id)) { 35 | yield item; 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | explain(e: _Explainer): _SelectExplanation { 43 | return { 44 | id: e.idFor(this), 45 | _: 'union', 46 | union: [ 47 | this.left.explain(e), 48 | this.right.explain(e), 49 | ], 50 | }; 51 | } 52 | } -------------------------------------------------------------------------------- /.deno/transforms/restrictive-index.ts: -------------------------------------------------------------------------------- 1 | import { _IIndex, IValue, IndexExpression, _Transaction, IndexKey, _Explainer, _IndexExplanation, IndexOp, _ISelection, Stats, Row } from '../interfaces-private.ts'; 2 | 3 | export class RestrictiveIndex implements _IIndex { 4 | constructor(private base: _IIndex, readonly filter: _ISelection) { 5 | } 6 | 7 | private match(raw: Row, t: _Transaction) { 8 | return this.filter.hasItem(raw, t); 9 | } 10 | 11 | get expressions(): IndexExpression[] { 12 | return this.base.expressions; 13 | } 14 | 15 | stats(t: _Transaction, key?: IndexKey): Stats | null { 16 | // cannot comput without iterating 17 | return null; 18 | } 19 | 20 | iterateKeys() { 21 | // cannot comput without iterating 22 | // (we know underlying keys, but we dont know which have items that match our filter) 23 | return null; 24 | } 25 | 26 | eqFirst(rawKey: IndexKey, t: _Transaction) { 27 | for (const i of this.base.enumerate({ 28 | key: rawKey, 29 | t: t, 30 | type: 'eq', 31 | })) { 32 | if (this.match(i, t)) { 33 | return i; 34 | } 35 | } 36 | return null; 37 | } 38 | 39 | 40 | entropy(t: IndexOp): number { 41 | return this.base.entropy(t); 42 | } 43 | 44 | *enumerate(op: IndexOp): Iterable { 45 | for (const i of this.base.enumerate(op)) { 46 | if (this.match(i, op.t)) { 47 | yield i; 48 | } 49 | } 50 | } 51 | 52 | explain(e: _Explainer): _IndexExplanation { 53 | return { 54 | _: 'indexRestriction', 55 | lookup: this.base.explain(e), 56 | for: this.filter.explain(e), 57 | // criteria: this.restrict.explain(e), 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /.deno/transforms/seq-scan.ts: -------------------------------------------------------------------------------- 1 | import { IValue, _ISelection, _Transaction, _Explainer, _SelectExplanation, Stats, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | import { Types } from '../datatypes/index.ts'; 4 | 5 | export class SeqScanFilter extends FilterBase { 6 | 7 | get index() { 8 | return null; 9 | } 10 | 11 | entropy(t: _Transaction) { 12 | // boost source entropy (in case an index has the same items count) 13 | return this.selection.entropy(t) * 1.5; 14 | } 15 | 16 | hasItem(raw: Row, t: _Transaction): boolean { 17 | return !!this.getter.get(raw, t); 18 | } 19 | 20 | constructor(private selection: _ISelection, private getter: IValue) { 21 | super(selection); 22 | this.getter = getter.cast(Types.bool); 23 | } 24 | 25 | 26 | stats(t: _Transaction): Stats | null { 27 | return null; 28 | } 29 | 30 | *enumerate(t: _Transaction): Iterable { 31 | for (const raw of this.selection.enumerate(t)) { 32 | const cond = this.getter.get(raw, t); 33 | if (cond) { 34 | yield raw; 35 | } 36 | } 37 | } 38 | 39 | explain(e: _Explainer): _SelectExplanation { 40 | return { 41 | id: e.idFor(this), 42 | _: 'seqFilter', 43 | filtered: this.selection.explain(e), 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /.deno/transforms/startswith-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _IIndex, _ITable, getId, _Transaction, _Explainer, _SelectExplanation, Stats, Row } from '../interfaces-private.ts'; 2 | import { FilterBase } from './transform-base.ts'; 3 | import { nullIsh } from '../utils.ts'; 4 | 5 | export class StartsWithFilter extends FilterBase { 6 | 7 | get index() { 8 | return null; 9 | } 10 | 11 | entropy(t: _Transaction) { 12 | return this.onValue.index!.entropy({ 13 | type: 'ge', 14 | key: [this.startWith], 15 | t, 16 | }); 17 | } 18 | 19 | hasItem(item: Row, t: _Transaction) { 20 | const get = this.onValue.get(item, t); 21 | return typeof get === 'string' 22 | && get.startsWith(this.startWith); 23 | } 24 | 25 | constructor(private onValue: IValue 26 | , private startWith: string) { 27 | super(onValue.origin!); 28 | if (onValue.index!.expressions[0].hash !== this.onValue.hash) { 29 | throw new Error('Startwith must be the first component of the index'); 30 | } 31 | } 32 | 33 | 34 | stats(t: _Transaction): Stats | null { 35 | return null; 36 | } 37 | 38 | *enumerate(t: _Transaction): Iterable { 39 | const index = this.onValue.index!; 40 | for (const item of index.enumerate({ 41 | type: 'ge', 42 | key: [this.startWith], 43 | t, 44 | })) { 45 | const got: string = this.onValue.get(item, t); 46 | if (nullIsh(got) || !got.startsWith(this.startWith)) { 47 | break; 48 | } 49 | yield item; 50 | } 51 | } 52 | 53 | explain(e: _Explainer): _SelectExplanation { 54 | return { 55 | id: e.idFor(this), 56 | _: 'ineq', 57 | entropy: this.entropy(e.transaction), 58 | on: this.onValue.index!.explain(e), 59 | }; 60 | } 61 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # set this to 'true' to disable AST full utilization monitorying. 2 | # ... this will not check that the full query is used anymore 3 | # but it is easyier to debug 4 | # ( ⚠ set it back to 'false' once you're done) 5 | NOCHECKFULLQUERYUSAGE=false 6 | 7 | DEBUG_PG_SERVER=false -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: oguimbal 4 | ko_fi: oguimbal 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: A problem with pg-mem ? 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Describe the bug 11 | 12 | (Describe your issue here). 13 | 14 | ``` 15 | (pg-mem tries to throw nice error messages when possible... so paste it here if you have one !) 16 | ``` 17 | 18 | # To Reproduce 19 | 20 | ```sql 21 | 22 | "If possible, paste here (including create table statements) a query that fails on https://oguimbal.github.io/pg-mem-playground/, but suceeds when ran on an actual PG database" 23 | 24 | ``` 25 | 26 | 27 | # pg-mem version 28 | 29 | (paste your pg-mem version here) 30 | 31 | (nb: the version in your package.json version is often not precise enough... please run "cat ./node_modules/pg-mem/package.json | grep version" to tell which minor version is actually installed) 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | What's on your mind ? :) 11 | -------------------------------------------------------------------------------- /.github/pg_mem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oguimbal/pg-mem/d593c231507056c324919ea7ab9c58955c55d350/.github/pg_mem.png -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: oven-sh/setup-bun@v1 16 | - run: bun install 17 | - run: bun typecheck 18 | - run: bun test 19 | env: 20 | CI: true 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | output 3 | .nyc_output 4 | coverage 5 | dist 6 | lib 7 | lib-types -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "bun", 6 | "request": "attach", 7 | "name": "Debug --only UT", 8 | 9 | // The URL of the WebSocket inspector to attach to. 10 | // This value can be retrieved by using `bun --inspect`. 11 | // bun test --inspect-wait=0.0.0.0:6499 12 | "url": "ws://0.0.0.0:6499/mapi-ut", 13 | "preLaunchTask": "launch-only-ut" 14 | }, 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "node_modules": true, 9 | "output": true, 10 | ".nyc_output": true, 11 | "coverage": true, 12 | "dist": true, 13 | "lib": true, 14 | ".deno": true 15 | }, 16 | "editor.formatOnSave": true, 17 | "typescript.tsdk": "node_modules/typescript/lib", 18 | "[typescript]": { 19 | "editor.defaultFormatter": "vscode.typescript-language-features" 20 | }, 21 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "launch-only-ut", 6 | "command": "bun", // Could be any other shell command 7 | "args": ["test", "--only", "--timeout", "0", "--inspect-wait=ws://0.0.0.0:6499/mapi-ut"], 8 | "type": "shell", 9 | "isBackground": true, 10 | "runOptions": { 11 | "instanceLimit": 1 12 | }, 13 | // this ensures that the config in launch.json will not wait for this task to finish 14 | "problemMatcher": { 15 | "owner": "custom", 16 | "pattern": { 17 | "regexp": "_____" 18 | }, 19 | "background": { 20 | "activeOnStart": true, 21 | "beginsPattern": "^.*Listening.*$", 22 | "endsPattern": "^.*Inspect.*$" 23 | } 24 | }, 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Olivier Guimbal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oguimbal/pg-mem/d593c231507056c324919ea7ab9c58955c55d350/bun.lockb -------------------------------------------------------------------------------- /playground/error.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const ErrorDisplay = ({ error }: { error: Error }) => { 4 | return (
{error.message}
); 5 | } -------------------------------------------------------------------------------- /playground/grid.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ValueDisplay } from './value'; 3 | 4 | export const DataGrid = ({ data, inline }: { data: any[]; inline?: boolean }) => { 5 | const columns = Object.keys(data[0]); 6 | return ( 7 | 8 | 9 | {columns.map(k => )} 10 | 11 | 12 | 13 | { 14 | data.map((x, i) => ( 15 | { 16 | columns.map(k => ) 19 | } 20 | )) 21 | } 22 | 23 |
{k}
17 | 18 |
); 24 | } 25 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pg-mem playground 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "types": [], 9 | "skipLibCheck": true, 10 | "moduleResolution": "node", 11 | "baseUrl": ".", 12 | "jsx": "react", 13 | "declaration": true, 14 | "declarationMap": true, 15 | "declarationDir": "lib-types", 16 | "outDir": "lib-types" 17 | }, 18 | "include": [ 19 | "**/**.ts", 20 | "**/**.tsx", 21 | ] 22 | } -------------------------------------------------------------------------------- /playground/value.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { nullIsh } from '../src/utils'; 3 | import moment from 'moment'; 4 | 5 | export const ValueDisplay = ({ value, singleLine }: { value: any, singleLine?: boolean }) => { 6 | if (nullIsh(value)) { 7 | return (NULL) 8 | } 9 | switch (typeof value) { 10 | case 'number': 11 | return ({value}); 12 | case 'string': 13 | return ({value}); 14 | case 'boolean': 15 | return ({value ? 'true' : 'false'}); 16 | case 'object': 17 | if (value instanceof Date) { 18 | const val = moment(value); 19 | const repr = Math.abs(moment(val).startOf('day').diff(val)) < 10 20 | ? val.format('YYYY-MM-DD') 21 | : val.format('YYYY-MM-DD HH:mm:ss'); 22 | return ({repr}); 23 | } 24 | return singleLine 25 | ? ({JSON.stringify(value)}) 26 | : (
{JSON.stringify(value, null, '    ')}
); 27 | default: 28 | return ({JSON.stringify(value)}); 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /samples/knex/knex.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | import { newDb } from '../../src/db'; 3 | import type { Knex } from "knex"; 4 | 5 | export async function knexSample() { 6 | 7 | // ========= CONNECT ========== 8 | 9 | // Create a new DB instance 10 | const mem = newDb(); 11 | 12 | // create a Knex instance bound to this db 13 | // => This replaces require('knex')({ ... }) 14 | const knex = mem.adapters.createKnex() as Knex; 15 | 16 | 17 | // ========= USE AS USUAL ========== 18 | 19 | // Create a table 20 | await knex.schema 21 | .createTable('users', table => { 22 | table.increments('id'); 23 | table.string('user_name'); 24 | }) 25 | // ...and another 26 | .createTable('accounts', table => { 27 | table.increments('id'); 28 | table.string('account_name'); 29 | table 30 | .integer('user_id') 31 | .unsigned() 32 | .references('users.id'); 33 | }) 34 | 35 | // Then query user table... 36 | await knex('users').insert({ user_name: 'Tim' }); 37 | 38 | 39 | // ... and check 40 | expect(mem.public.many('select * from users')) 41 | .toEqual([{ 42 | id: 1, 43 | user_name: 'Tim', 44 | }]); 45 | 46 | // Then insert into account table... 47 | await knex('accounts').insert({ account_name: 'knex', user_id: 1 }) 48 | 49 | 50 | // ... and check 51 | expect(mem.public.many('select * from accounts')) 52 | .toEqual([{ 53 | id: 1, 54 | account_name: 'knex', 55 | user_id: 1, 56 | }]); 57 | 58 | 59 | // Try to run a join 60 | const selectedRows = await knex('users') 61 | .join('accounts', 'users.id', 'accounts.user_id') 62 | .select('users.user_name as user', 'accounts.account_name as account') 63 | 64 | expect(selectedRows) 65 | .toEqual([ 66 | { user: 'Tim', account: 'knex' }, 67 | ]) 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /samples/mikro-orm/simple.ts: -------------------------------------------------------------------------------- 1 | import { Collection, Entity, ManyToOne, MikroORM, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; 2 | 3 | import { newDb } from '../../src/db'; 4 | 5 | @Entity() 6 | export class Book { 7 | 8 | @PrimaryKey({ type: 'text' }) 9 | id!: string; 10 | 11 | @Property({ type: 'text' }) 12 | title!: string; 13 | 14 | @ManyToOne(() => Author) 15 | author!: Author; 16 | 17 | } 18 | 19 | @Entity() 20 | export class Author { 21 | 22 | @PrimaryKey({ type: 'text' }) 23 | id!: string; 24 | 25 | @Property({ type: 'text' }) 26 | name!: string; 27 | 28 | @OneToMany(() => Book, book => book.author) 29 | books = new Collection(this); 30 | 31 | constructor(name: string) { 32 | this.name = name; 33 | } 34 | 35 | } 36 | 37 | export async function mikroOrmSample() { 38 | 39 | // create an instance of pg-mem 40 | const db = newDb(); 41 | 42 | // bind an instance of mikro-orm to our pg-mem instance 43 | const orm: MikroORM = await db.adapters.createMikroOrm({ 44 | entities: [Author, Book], 45 | }); 46 | 47 | // create schema 48 | await orm.getSchemaGenerator().createSchema(); 49 | 50 | // MikroORM started enforcing forking EM at some point 51 | // normally it's done in some kind of middleware etc. 52 | const forkedEm = orm.em.fork(); 53 | 54 | // do things 55 | const books = forkedEm.getRepository(Book); 56 | const authors = forkedEm.getRepository(Author); 57 | 58 | const hugo = authors.create({ 59 | id: 'hugo', 60 | name: 'Victor Hugo', 61 | }); 62 | const miserables = books.create({ 63 | id: 'miserables', 64 | author: hugo, 65 | title: 'Les Misérables', 66 | }); 67 | 68 | await books.getEntityManager().persistAndFlush([hugo, miserables]); 69 | 70 | return db; 71 | } 72 | -------------------------------------------------------------------------------- /samples/sequelize/sequelize.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | import { newDb } from '../../src/db'; 3 | import { Sequelize, DataTypes } from 'sequelize'; 4 | 5 | export async function sequelizeSample(force?: boolean) { 6 | 7 | // ========= CONNECT ========== 8 | 9 | // Create a new DB instance 10 | const mem = newDb(); 11 | 12 | const sequelize = new Sequelize({ 13 | dialect: 'postgres', 14 | dialectModule: mem.adapters.createPg(), 15 | }); 16 | 17 | // await seq.authenticate(); 18 | const User = sequelize.define('User', { 19 | // Model attributes are defined here 20 | firstName: { 21 | type: DataTypes.STRING, 22 | allowNull: false 23 | }, 24 | lastName: { 25 | type: DataTypes.STRING 26 | // allowNull defaults to true 27 | } 28 | }, { 29 | // Other model options go here 30 | }); 31 | 32 | await sequelize.sync({ force }); 33 | } -------------------------------------------------------------------------------- /src/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export * from './adapters'; 2 | -------------------------------------------------------------------------------- /src/constraints/index-cst.ts: -------------------------------------------------------------------------------- 1 | import { _IConstraint, _IIndex, _ITable, _Transaction } from '../interfaces-private'; 2 | 3 | export class IndexConstraint implements _IConstraint { 4 | 5 | constructor(readonly name: string, readonly index: _IIndex, private table: _ITable) { 6 | } 7 | 8 | uninstall(t: _Transaction): void { 9 | this.table.dropIndex(t, this.name); 10 | } 11 | } -------------------------------------------------------------------------------- /src/constraints/subscription.ts: -------------------------------------------------------------------------------- 1 | import { _IConstraint, _Transaction } from '../interfaces-private'; 2 | 3 | export class SubscriptionConstraint implements _IConstraint { 4 | constructor(readonly name: string, readonly uninstall: (t: _Transaction) => void) { 5 | } 6 | } -------------------------------------------------------------------------------- /src/constraints/wrapped.ts: -------------------------------------------------------------------------------- 1 | import { _IConstraint, _Transaction } from '../interfaces-private'; 2 | 3 | export class ConstraintWrapper implements _IConstraint { 4 | constructor(private refs: Map, private inner: _IConstraint) { 5 | if (inner.name) { 6 | refs.set(inner.name, this); 7 | } 8 | } 9 | get name() { 10 | return this.inner.name; 11 | } 12 | uninstall(t: _Transaction): void { 13 | this.inner.uninstall(t); 14 | if (this.name) { 15 | this.refs.delete(this.name); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/datatypes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './datatypes'; 2 | -------------------------------------------------------------------------------- /src/datatypes/t-custom-enum.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from '../evaluator'; 2 | import { TypeBase } from './datatype-base'; 3 | import { DataType, nil, QueryError } from '../interfaces'; 4 | import { _IRelation, _ISchema, _IType, _Transaction } from '../interfaces-private'; 5 | 6 | export function asEnum(o: _IRelation | null): CustomEnumType { 7 | if (o && o.type === 'type' && o instanceof CustomEnumType) { 8 | return o; 9 | } 10 | throw new QueryError(`"${o?.name}" is not a enum`); 11 | } 12 | export class CustomEnumType extends TypeBase { 13 | 14 | get primary(): DataType { 15 | return this.name as any; 16 | } 17 | 18 | get name(): string { 19 | return this._name; 20 | } 21 | 22 | constructor(readonly schema: _ISchema 23 | , private readonly _name: string 24 | , readonly values: string[]) { 25 | super(null); 26 | } 27 | 28 | install() { 29 | this.schema._registerType(this); 30 | } 31 | 32 | doCanCast(to: _IType) { 33 | return to.primary === DataType.text; 34 | } 35 | 36 | doCast(value: Evaluator, to: _IType): Evaluator | nil { 37 | return value; 38 | } 39 | 40 | prefer(type: _IType): _IType | nil { 41 | return this; 42 | } 43 | 44 | doCanBuildFrom(from: _IType): boolean | nil { 45 | return from.primary === DataType.text; 46 | } 47 | 48 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 49 | return value 50 | .setConversion((raw: string) => { 51 | if (!this.values.includes(raw)) { 52 | throw new QueryError(`invalid input value for enum ${this.name}: "${raw}"`); 53 | } 54 | return raw; 55 | } 56 | , conv => ({ conv, toCenum: this.name })) 57 | } 58 | 59 | 60 | drop(t: _Transaction): void { 61 | this.schema._unregisterType(this); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/datatypes/t-equivalent.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from '../evaluator'; 2 | import { TypeBase } from './datatype-base'; 3 | import { CastError, DataType, IEquivalentType, IType, nil, QueryError, typeDefToStr } from '../interfaces'; 4 | import { _ISchema, _IType } from '../interfaces-private'; 5 | import { Types } from './datatypes'; 6 | 7 | export class EquivalentType extends TypeBase { 8 | 9 | private equiv: _IType; 10 | 11 | constructor(private def: IEquivalentType) { 12 | super(null); 13 | if (typeof def.equivalentTo === 'string') { 14 | let eq = (Types as any)[def.equivalentTo]; 15 | if (typeof eq === 'function') { 16 | eq = eq(); 17 | } 18 | this.equiv = eq; 19 | } else { 20 | this.equiv = def.equivalentTo as _IType; 21 | } 22 | 23 | if (!this.equiv) { 24 | throw new Error(`Invalid equilvalent type`); 25 | } 26 | } 27 | 28 | get primary(): DataType { 29 | return this.equiv.primary; 30 | } 31 | 32 | get primaryName(): string { 33 | return this.def.name; 34 | } 35 | 36 | get name(): string { 37 | return this.def.name; 38 | } 39 | 40 | doCanCast(to: _IType) { 41 | return to.primary === this.equiv.primary; 42 | } 43 | 44 | doCast(value: Evaluator, to: _IType): Evaluator | nil { 45 | return value; 46 | } 47 | 48 | prefer(type: _IType): _IType | nil { 49 | return this; 50 | } 51 | 52 | doCanBuildFrom(from: _IType): boolean | nil { 53 | // return from.canCast(this.equiv); 54 | return this.equiv.canCast(from); 55 | } 56 | 57 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 58 | return value 59 | .setConversion(x => { 60 | if (!this.def.isValid(x)) { 61 | throw new QueryError(`invalid input syntax for type ${typeDefToStr(this)}: ${x}`); 62 | } 63 | return x; 64 | }, val => ({ val, to: this.equiv.primary })); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/datatypes/t-inet.ts: -------------------------------------------------------------------------------- 1 | import { Evaluator } from '../evaluator'; 2 | import { TypeBase } from './datatype-base'; 3 | import { DataType, nil, QueryError } from '../interfaces'; 4 | import { _ISchema, _IType } from '../interfaces-private'; 5 | 6 | // https://www.postgresql.org/docs/13/datatype-net-types.html#DATATYPE-INET 7 | 8 | export class INetType extends TypeBase { 9 | 10 | get primary(): DataType { 11 | return DataType.inet 12 | } 13 | 14 | doCanCast(to: _IType) { 15 | return to.primary === DataType.text; 16 | } 17 | 18 | doCast(value: Evaluator, to: _IType): Evaluator | nil { 19 | return value; 20 | } 21 | 22 | prefer(type: _IType): _IType | nil { 23 | return this; 24 | } 25 | 26 | doCanBuildFrom(from: _IType): boolean | nil { 27 | return from.primary === DataType.text; 28 | } 29 | 30 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 31 | return value 32 | .setConversion(x => { 33 | const [_, a, b, c, d, __, m] = /^(\d+)\.(\d+)\.(\d+)\.(\d+)(\/(\d+))?$/.exec(x) ?? [] 34 | if ([a, b, c, d].some(notByte) || notMask(m)) { 35 | throw new QueryError(`invalid input syntax for type inet: ${x}`); 36 | } 37 | return x; 38 | }, toInet => ({ toInet })); 39 | } 40 | } 41 | 42 | function notByte(b: string) { 43 | return !b 44 | || b.length > 1 && b[0] === '0' 45 | || parseInt(b, 10) > 255; 46 | } 47 | 48 | function notMask(b: string) { 49 | return b 50 | && (b.length > 1 && b[0] === '0' 51 | || parseInt(b, 10) > 32); 52 | } -------------------------------------------------------------------------------- /src/datatypes/t-interval.ts: -------------------------------------------------------------------------------- 1 | import { DataType, nil, _IType } from '../interfaces-private'; 2 | import { Interval, normalizeInterval, parseIntervalLiteral } from 'pgsql-ast-parser'; 3 | import { TypeBase } from './datatype-base'; 4 | import { Evaluator } from '../evaluator'; 5 | import { intervalToSec } from '../utils'; 6 | 7 | export class IntervalType extends TypeBase { 8 | 9 | get primary(): DataType { 10 | return DataType.interval; 11 | } 12 | 13 | doCanBuildFrom(from: _IType) { 14 | switch (from.primary) { 15 | case DataType.text: 16 | return true; 17 | } 18 | return false; 19 | } 20 | 21 | doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { 22 | switch (from.primary) { 23 | case DataType.text: 24 | return value 25 | .setConversion(str => { 26 | const conv = normalizeInterval(parseIntervalLiteral(str)); 27 | return conv; 28 | } 29 | , toInterval => ({ toInterval })); 30 | } 31 | return null; 32 | } 33 | 34 | doEquals(a: Interval, b: Interval): boolean { 35 | return intervalToSec(a) === intervalToSec(b); 36 | } 37 | doGt(a: Interval, b: Interval): boolean { 38 | return intervalToSec(a) > intervalToSec(b); 39 | } 40 | doLt(a: Interval, b: Interval): boolean { 41 | return intervalToSec(a) < intervalToSec(b); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/execution/exec-utils.ts: -------------------------------------------------------------------------------- 1 | import { QName, Statement, NodeLocation, toSql } from 'pgsql-ast-parser'; 2 | import { _ISchema, QueryError, _Transaction, _IDb } from '../interfaces-private'; 3 | 4 | export function checkExistence(schema: _ISchema, name: QName, ifNotExists: boolean | undefined, act: () => void): boolean { 5 | // check if object exists 6 | const exists = schema.getObject(name, { 7 | skipSearch: true, 8 | nullIfNotFound: true 9 | }); 10 | if (exists) { 11 | if (ifNotExists) { 12 | return false; 13 | } 14 | throw new QueryError(`relation "${name.name}" already exists`); 15 | } 16 | 17 | // else, perform operation 18 | act(); 19 | return true; 20 | } 21 | 22 | 23 | 24 | export function locOf(p: Statement): NodeLocation { 25 | return p._location ?? { start: 0, end: 0 }; 26 | } 27 | 28 | export abstract class ExecHelper { 29 | constructor(private statement: Statement) { 30 | } 31 | 32 | protected noData(t: _Transaction, name?: string) { 33 | return { 34 | result: { 35 | command: name ?? this.statement.type.toUpperCase(), 36 | fields: [], 37 | rowCount: 0, 38 | rows: [], 39 | location: locOf(this.statement), 40 | }, 41 | state: t, 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/execution/records-mutations/deletion.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _Transaction, IValue, _Explainer, _ISchema, asTable, _ISelection, _IIndex, _IStatement } from '../../interfaces-private'; 2 | import { DeleteStatement } from 'pgsql-ast-parser'; 3 | import { MutationDataSourceBase } from './mutation-base'; 4 | import { buildCtx } from '../../parser/context'; 5 | 6 | export class Deletion extends MutationDataSourceBase { 7 | 8 | 9 | constructor(ast: DeleteStatement) { 10 | const { schema } = buildCtx(); 11 | const table = asTable(schema.getObject(ast.from)); 12 | const mutatedSel = table 13 | .selection 14 | .filter(ast.where); 15 | 16 | super(table, mutatedSel, ast); 17 | } 18 | 19 | protected performMutation(t: _Transaction): any[] { 20 | // perform deletion 21 | const rows = []; 22 | for (const item of this.mutatedSel.enumerate(t)) { 23 | this.table.delete(t, item); 24 | rows.push(item); 25 | } 26 | return rows; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/execution/records-mutations/truncate-table.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, SchemaField, NotSupported, _ITable, _IStatementExecutor, asTable, StatementResult, _IStatement, TruncateOpts } from '../../interfaces-private'; 2 | import { TruncateTableStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { buildCtx } from '../../parser/context'; 5 | 6 | export class TruncateTable extends ExecHelper implements _IStatementExecutor { 7 | private table: _ITable; 8 | private opts: TruncateOpts; 9 | 10 | constructor(statement: TruncateTableStatement) { 11 | super(statement); 12 | if (statement.tables.length !== 1) { 13 | throw new NotSupported('Multiple truncations'); 14 | } 15 | this.opts = { 16 | cascade: statement.cascade === 'cascade', 17 | restartIdentity: statement.identity === 'restart', 18 | }; 19 | const { schema } = buildCtx(); 20 | this.table = asTable(schema.getObject(statement.tables[0])); 21 | } 22 | 23 | execute(t: _Transaction): StatementResult { 24 | this.table.truncate(t, this.opts); 25 | return this.noData(t, 'TRUNCATE'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/execution/schema-amends/alter-enum.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _IStatementExecutor, _IStatement } from '../../interfaces-private'; 2 | import { AlterEnumType } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore } from '../../utils'; 5 | import { asEnum, CustomEnumType } from "../../datatypes/t-custom-enum"; 6 | 7 | export class AlterEnum extends ExecHelper implements _IStatementExecutor { 8 | private onSchema: _ISchema; 9 | private originalEnum: CustomEnumType; 10 | constructor({ schema }: _IStatement, private p: AlterEnumType) { 11 | super(p); 12 | this.onSchema = schema.getThisOrSiblingFor(p.name); 13 | this.originalEnum = asEnum(schema.getObject(p.name)) 14 | if (!this.onSchema) { 15 | ignore(this.p) 16 | } 17 | } 18 | 19 | execute(t: _Transaction) { 20 | // commit pending data before making changes 21 | // (because the index sequence creation does support further rollbacks) 22 | t = t.fullCommit(); 23 | const enumValues = this.originalEnum.values 24 | 25 | switch (this.p.change.type) { 26 | case 'add value': 27 | enumValues.push(this.p.change.add.value) 28 | break; 29 | case 'rename': 30 | this.originalEnum.drop(t) 31 | this.onSchema.registerEnum(this.p.change.to.name, enumValues) 32 | break; 33 | } 34 | 35 | // new implicit transaction 36 | t = t.fork(); 37 | 38 | return this.noData(t, 'ALTER'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/execution/schema-amends/alter-sequence.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq } from '../../interfaces-private'; 2 | import { AlterSequenceStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore } from '../../utils'; 5 | 6 | export class AlterSequence extends ExecHelper implements _IStatementExecutor { 7 | private seq: _ISequence | null; 8 | 9 | 10 | constructor({ schema }: _IStatement, private p: AlterSequenceStatement) { 11 | super(p); 12 | 13 | this.seq = asSeq(schema.getObject(p.name, { 14 | nullIfNotFound: p.ifExists, 15 | })); 16 | if (!this.seq) { 17 | ignore(this.p); 18 | } 19 | } 20 | 21 | execute(t: _Transaction) { 22 | // commit pending data before making changes 23 | // (because the index sequence creation does support further rollbacks) 24 | t = t.fullCommit(); 25 | 26 | // alter the sequence 27 | this.seq?.alter(t, this.p.change); 28 | 29 | // new implicit transaction 30 | t = t.fork(); 31 | 32 | return this.noData(t, 'ALTER'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/execution/schema-amends/create-enum.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, _ISchema, NotSupported, CreateIndexColDef, _ITable, CreateIndexDef, _IStatement, _IStatementExecutor } from '../../interfaces-private'; 2 | import { CreateEnumType } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | 5 | export class CreateEnum extends ExecHelper implements _IStatementExecutor { 6 | private onSchema: _ISchema; 7 | private values: string[]; 8 | private name: string; 9 | 10 | constructor({ schema }: _IStatement, st: CreateEnumType) { 11 | super(st); 12 | this.onSchema = schema.getThisOrSiblingFor(st.name); 13 | this.values = st.values.map(x => x.value); 14 | this.name = st.name.name; 15 | } 16 | 17 | execute(t: _Transaction) { 18 | // commit pending data before making changes 19 | // (because does not support further rollbacks) 20 | t = t.fullCommit(); 21 | 22 | // register enum 23 | this.onSchema 24 | .registerEnum(this.name, this.values); 25 | 26 | // new implicit transaction 27 | t = t.fork(); 28 | return this.noData(t, 'CREATE'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/execution/schema-amends/create-materialized-view.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, asTable, _ISchema, NotSupported, CreateIndexColDef, _ITable, CreateIndexDef, _IStatement, _IStatementExecutor, asView, _IView, QueryError } from '../../interfaces-private'; 2 | import { CreateMaterializedViewStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { View } from '../../schema/view'; 5 | import { buildSelect } from '../select'; 6 | 7 | export class CreateMaterializedView extends ExecHelper implements _IStatementExecutor { 8 | private schema: _ISchema; 9 | private toRegister?: View; 10 | 11 | 12 | constructor(st: _IStatement, p: CreateMaterializedViewStatement) { 13 | super(p); 14 | this.schema = st.schema.getThisOrSiblingFor(p.name); 15 | // check existence 16 | const existing = this.schema.getObject(p.name, { nullIfNotFound: true }); 17 | if (existing) { 18 | if (p.ifNotExists) { 19 | return; 20 | } 21 | throw new QueryError(`Name already exists: ${p.name.name}`); 22 | } 23 | 24 | const view = buildSelect(p.query); 25 | 26 | // hack: materialized views are implemented as simple views :/ (todo ?) 27 | this.toRegister = new View(this.schema, p.name.name, view); 28 | } 29 | 30 | 31 | execute(t: _Transaction) { 32 | if (!this.toRegister) { 33 | return this.noData(t, 'CREATE'); 34 | } 35 | 36 | // commit pending data before making changes 37 | // (because does not support further rollbacks) 38 | t = t.fullCommit(); 39 | 40 | // view creation 41 | this.toRegister.register(); 42 | 43 | // new implicit transaction 44 | t = t.fork(); 45 | return this.noData(t, 'CREATE'); 46 | } 47 | } -------------------------------------------------------------------------------- /src/execution/schema-amends/create-schema.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, asTable, _ISchema, NotSupported, CreateIndexColDef, _ITable, CreateIndexDef, _IStatement, _IStatementExecutor, QueryError } from '../../interfaces-private'; 2 | import { CreateSchemaStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore } from '../../utils'; 5 | 6 | export class CreateSchema extends ExecHelper implements _IStatementExecutor { 7 | private toCreate?: string; 8 | 9 | constructor(private st: _IStatement, p: CreateSchemaStatement) { 10 | super(p); 11 | const sch = this.st.schema.db.getSchema(p.name.name, true); 12 | if (!p.ifNotExists && sch) { 13 | throw new QueryError('schema already exists! ' + p.name); 14 | } 15 | if (sch) { 16 | ignore(p); 17 | } else { 18 | this.toCreate = p.name.name; 19 | } 20 | } 21 | 22 | execute(t: _Transaction) { 23 | // commit pending data before making changes 24 | // (because does not support further rollbacks) 25 | t = t.fullCommit(); 26 | 27 | // create schema 28 | if (this.toCreate) { 29 | this.st.schema.db.createSchema(this.toCreate); 30 | } 31 | 32 | // new implicit transaction 33 | t = t.fork(); 34 | return this.noData(t, 'CREATE'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/execution/schema-amends/create-sequence.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, NotSupported, _ISequence, _IStatementExecutor } from '../../interfaces-private'; 2 | import { QName, CreateSequenceStatement } from 'pgsql-ast-parser'; 3 | import { Sequence } from '../../schema/sequence'; 4 | import { checkExistence, ExecHelper } from '../exec-utils'; 5 | 6 | export class ExecuteCreateSequence extends ExecHelper implements _IStatementExecutor { 7 | schema: _ISchema; 8 | constructor(inSchema: _ISchema, private p: CreateSequenceStatement, private acceptTempSequences: boolean) { 9 | super(p); 10 | const name: QName = p.name; 11 | this.schema = inSchema.getThisOrSiblingFor(name); 12 | } 13 | 14 | execute(t: _Transaction) { 15 | // commit pending data before making changes 16 | // (because the index sequence creation does support further rollbacks) 17 | t = t.fullCommit(); 18 | 19 | // create the sequence 20 | this.createSeq(t); 21 | 22 | // new implicit transaction 23 | t = t.fork(); 24 | return this.noData(t, 'CREATE'); 25 | } 26 | 27 | createSeq(t: _Transaction) { 28 | const p = this.p; 29 | const name: QName = p.name; 30 | // const ret = this.simple('CREATE', p); 31 | 32 | let ret: _ISequence | null = null; 33 | checkExistence(this.schema, name, p.ifNotExists, () => { 34 | if (p.temp && !this.acceptTempSequences) { 35 | throw new NotSupported('temp sequences'); 36 | } 37 | ret = new Sequence(name.name, this.schema) 38 | .alter(t, p.options); 39 | this.schema.db.onSchemaChange(); 40 | }); 41 | return ret; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/execution/schema-amends/do.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult, _IStatement, CompiledFunction } from '../../interfaces-private'; 2 | import { DoStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../../execution/exec-utils'; 4 | 5 | export class DoStatementExec extends ExecHelper implements _IStatementExecutor { 6 | private compiled: CompiledFunction; 7 | 8 | constructor({ schema }: _IStatement, st: DoStatement) { 9 | super(st); 10 | const lang = schema.db.getLanguage(st.language?.name ?? 'plpgsql'); 11 | this.compiled = lang({ 12 | args: [], 13 | code: st.code, 14 | schema: schema, 15 | }); 16 | } 17 | 18 | execute(t: _Transaction): StatementResult { 19 | this.compiled(); 20 | return this.noData(t, 'DO'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/execution/schema-amends/drop-index.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq, asIndex, _INamedIndex } from '../../interfaces-private'; 2 | import { DropStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore, notNil } from '../../utils'; 5 | 6 | export class DropIndex extends ExecHelper implements _IStatementExecutor { 7 | private idx: _INamedIndex[]; 8 | 9 | 10 | constructor({ schema }: _IStatement, statement: DropStatement) { 11 | super(statement); 12 | 13 | this.idx = notNil(statement.names.map(x => asIndex(schema.getObject(x, { 14 | nullIfNotFound: statement.ifExists, 15 | })))); 16 | 17 | if (this.idx.length) { 18 | ignore(statement.concurrently); 19 | } else { 20 | ignore(statement); 21 | } 22 | } 23 | 24 | execute(t: _Transaction) { 25 | // commit pending data before making changes 26 | // (because the index sequence creation does support further rollbacks) 27 | t = t.fullCommit(); 28 | 29 | // alter the sequence 30 | for (const idx of this.idx) { 31 | idx.onTable.dropIndex(t, idx.name); 32 | } 33 | 34 | // new implicit transaction 35 | t = t.fork(); 36 | 37 | return this.noData(t, 'DROP'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/execution/schema-amends/drop-sequence.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq } from '../../interfaces-private'; 2 | import { DropStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore, notNil } from '../../utils'; 5 | 6 | export class DropSequence extends ExecHelper implements _IStatementExecutor { 7 | private seqs: _ISequence[]; 8 | 9 | constructor({ schema }: _IStatement, statement: DropStatement) { 10 | super(statement); 11 | 12 | this.seqs = notNil(statement.names.map(x => asSeq(schema.getObject(x, { 13 | nullIfNotFound: statement.ifExists, 14 | })))); 15 | if (!this.seqs.length) { 16 | ignore(statement); 17 | } 18 | } 19 | 20 | execute(t: _Transaction) { 21 | // commit pending data before making changes 22 | // (because the index sequence creation does support further rollbacks) 23 | t = t.fullCommit(); 24 | 25 | // drop the sequence 26 | for (const seq of this.seqs) { 27 | seq.drop(t); 28 | } 29 | 30 | // new implicit transaction 31 | t = t.fork(); 32 | 33 | return this.noData(t, 'DROP'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/execution/schema-amends/drop-table.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq, asIndex, _INamedIndex, _ITable, asTable } from '../../interfaces-private'; 2 | import { DropStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore, notNil } from '../../utils'; 5 | 6 | export class DropTable extends ExecHelper implements _IStatementExecutor { 7 | private tables: _ITable[]; 8 | private cascade: boolean; 9 | 10 | 11 | constructor({ schema }: _IStatement, statement: DropStatement) { 12 | super(statement); 13 | 14 | this.tables = notNil(statement.names.map(x => asTable(schema.getObject(x, { 15 | nullIfNotFound: statement.ifExists, 16 | })))); 17 | 18 | this.cascade = statement.cascade === 'cascade'; 19 | 20 | if (!this.tables.length) { 21 | ignore(statement); 22 | } 23 | } 24 | 25 | execute(t: _Transaction) { 26 | // commit pending data before making changes 27 | // (because it does not support further rollbacks) 28 | t = t.fullCommit(); 29 | 30 | // drop table 31 | for (const table of this.tables) { 32 | table.drop(t, this.cascade); 33 | } 34 | 35 | // new implicit transaction 36 | t = t.fork(); 37 | 38 | return this.noData(t, 'DROP'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/execution/schema-amends/drop-type.ts: -------------------------------------------------------------------------------- 1 | import { _ISchema, _Transaction, _ISequence, _IStatementExecutor, _IStatement, asSeq, asType, _IType } from '../../interfaces-private'; 2 | import { DropStatement } from 'pgsql-ast-parser'; 3 | import { ExecHelper } from '../exec-utils'; 4 | import { ignore, notNil } from '../../utils'; 5 | 6 | export class DropType extends ExecHelper implements _IStatementExecutor { 7 | private types: _IType[]; 8 | 9 | constructor({ schema }: _IStatement, statement: DropStatement) { 10 | super(statement); 11 | 12 | this.types = notNil(statement.names.map(x => asType(schema.getObject(x, { 13 | nullIfNotFound: statement.ifExists, 14 | })))); 15 | if (!this.types.length) { 16 | ignore(statement); 17 | } 18 | } 19 | 20 | execute(t: _Transaction) { 21 | // commit pending data before making changes 22 | // (because the index sequence creation does support further rollbacks) 23 | t = t.fullCommit(); 24 | 25 | // drop the sequence 26 | for (const seq of this.types) { 27 | seq.drop(t); 28 | } 29 | 30 | // new implicit transaction 31 | t = t.fork(); 32 | 33 | return this.noData(t, 'DROP'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/execution/set.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult, GLOBAL_VARS, QueryError } from '../interfaces-private'; 2 | import { SetGlobalStatement, SetTimezone, SetNames } from 'pgsql-ast-parser'; 3 | import { ignore } from '../utils'; 4 | import { ExecHelper } from './exec-utils'; 5 | 6 | export class SetExecutor extends ExecHelper implements _IStatementExecutor { 7 | 8 | constructor(private p: SetGlobalStatement | SetTimezone | SetNames) { 9 | super(p); 10 | // todo handle set statements timezone ? 11 | // They are just ignored as of today (in order to handle pg_dump exports) 12 | ignore(p); 13 | } 14 | 15 | execute(t: _Transaction): StatementResult { 16 | const p = this.p; 17 | if (p.type === 'set' && p.set.type === 'value') { 18 | t.set(GLOBAL_VARS, t.getMap(GLOBAL_VARS) 19 | .set(p.variable.name, p.set.value)); 20 | } 21 | return this.noData(t, 'SET'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/execution/show.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult, GLOBAL_VARS, QueryError } from '../interfaces-private'; 2 | import { ShowStatement } from 'pgsql-ast-parser'; 3 | import { locOf } from './exec-utils'; 4 | 5 | export class ShowExecutor implements _IStatementExecutor { 6 | constructor(private statement: ShowStatement) { } 7 | 8 | execute(t: _Transaction): StatementResult { 9 | const p = this.statement; 10 | const got = t.getMap(GLOBAL_VARS); 11 | if (!got.has(p.variable.name)) { 12 | throw new QueryError(`unrecognized configuration parameter "${p.variable.name}"`); 13 | } 14 | return { 15 | state: t, 16 | result: { 17 | rows: [{ [p.variable.name]: got.get(p.variable.name) }], 18 | rowCount: 1, 19 | command: 'SHOW', 20 | fields: [], 21 | location: locOf(p), 22 | }, 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/execution/transaction-statements.ts: -------------------------------------------------------------------------------- 1 | import { _IStatementExecutor, _Transaction, StatementResult } from '../interfaces-private'; 2 | import { ExecHelper } from './exec-utils'; 3 | import { CommitStatement, RollbackStatement, StartTransactionStatement, BeginStatement } from 'pgsql-ast-parser'; 4 | import { ignore } from '../utils'; 5 | 6 | export class CommitExecutor extends ExecHelper implements _IStatementExecutor { 7 | 8 | constructor(statement: CommitStatement) { 9 | super(statement) 10 | } 11 | 12 | execute(t: _Transaction): StatementResult { 13 | t = t.commit(); 14 | // recreate an implicit transaction if we're at root 15 | // (I can see how its usfull, but this is dubious...) 16 | if (!t.isChild) { 17 | t = t.fork(); 18 | } 19 | return this.noData(t, 'COMMIT'); 20 | } 21 | 22 | } 23 | 24 | export class RollbackExecutor extends ExecHelper implements _IStatementExecutor { 25 | constructor(statement: RollbackStatement) { 26 | super(statement); 27 | ignore(statement); 28 | } 29 | 30 | execute(t: _Transaction): StatementResult { 31 | t = t.rollback(); 32 | return this.noData(t, 'ROLLBACK'); 33 | } 34 | } 35 | 36 | 37 | export class BeginStatementExec extends ExecHelper implements _IStatementExecutor { 38 | constructor(statement: BeginStatement | StartTransactionStatement) { 39 | super(statement); 40 | ignore(statement); 41 | } 42 | 43 | execute(t: _Transaction): StatementResult { 44 | t = t.fork(); 45 | return this.noData(t, 'BEGIN'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/functions/date.ts: -------------------------------------------------------------------------------- 1 | import { FunctionDefinition } from '../interfaces'; 2 | import moment from 'moment'; 3 | import { DataType, QueryError } from '../interfaces-private'; 4 | import { nullIsh } from '../utils'; 5 | 6 | 7 | export const dateFunctions: FunctionDefinition[] = [ 8 | { 9 | name: 'to_date', 10 | args: [DataType.text, DataType.text], 11 | returns: DataType.date, 12 | implementation: (data, format) => { 13 | if (nullIsh(data) || nullIsh(format)) { 14 | return null; // if one argument is null => null 15 | } 16 | const ret = moment.utc(data, format); 17 | if (!ret.isValid()) { 18 | throw new QueryError(`The text '${data}' does not match the date format ${format}`); 19 | } 20 | return ret.toDate(); 21 | } 22 | }, 23 | { 24 | name: 'now', 25 | returns: DataType.timestamptz, 26 | impure: true, 27 | implementation: () => new Date(), 28 | }, 29 | ]; -------------------------------------------------------------------------------- /src/functions/index.ts: -------------------------------------------------------------------------------- 1 | import { stringFunctions } from './string'; 2 | import { dateFunctions } from './date'; 3 | import { systemFunctions } from './system'; 4 | import { sequenceFunctions } from './sequence-fns'; 5 | import { numberFunctions } from './numbers'; 6 | import { subqueryFunctions } from './subquery'; 7 | 8 | 9 | export const allFunctions = [ 10 | ...stringFunctions 11 | , ... dateFunctions 12 | , ... systemFunctions 13 | , ... sequenceFunctions 14 | , ... numberFunctions 15 | , ... subqueryFunctions 16 | ] 17 | -------------------------------------------------------------------------------- /src/functions/numbers.ts: -------------------------------------------------------------------------------- 1 | import { DataType, FunctionDefinition } from "../interfaces"; 2 | 3 | export const numberFunctions: FunctionDefinition[] = [ 4 | { 5 | name: 'greatest', 6 | args: [DataType.integer], 7 | argsVariadic: DataType.integer, 8 | returns: DataType.integer, 9 | implementation: (...args: number[]) => Math.max(...args), 10 | }, 11 | { 12 | name: 'least', 13 | args: [DataType.integer], 14 | argsVariadic: DataType.integer, 15 | returns: DataType.integer, 16 | implementation: (...args: number[]) => Math.min(...args), 17 | }, 18 | ] -------------------------------------------------------------------------------- /src/functions/string.ts: -------------------------------------------------------------------------------- 1 | import { DataType, FunctionDefinition } from '../interfaces-private'; 2 | 3 | export const stringFunctions: FunctionDefinition[] = [ 4 | { 5 | name: 'lower', 6 | args: [DataType.text], 7 | returns: DataType.text, 8 | implementation: (x: string) => x?.toLowerCase(), 9 | }, 10 | { 11 | name: 'upper', 12 | args: [DataType.text], 13 | returns: DataType.text, 14 | implementation: (x: string) => x?.toUpperCase(), 15 | }, 16 | { 17 | name: 'concat', 18 | args: [DataType.text], 19 | argsVariadic: DataType.text, 20 | returns: DataType.text, 21 | allowNullArguments: true, 22 | implementation: (...x: string[]) => x?.join(''), 23 | }, 24 | { 25 | name: 'concat_ws', 26 | args: [DataType.text], 27 | argsVariadic: DataType.text, 28 | returns: DataType.text, 29 | allowNullArguments: true, 30 | implementation: (separator: string, ...x: string[]) => x?.join(separator), 31 | }, 32 | ] 33 | -------------------------------------------------------------------------------- /src/functions/subquery.ts: -------------------------------------------------------------------------------- 1 | import { FunctionDefinition } from '../interfaces'; 2 | import { DataType } from '../interfaces-private'; 3 | 4 | export const subqueryFunctions: FunctionDefinition[] = [ 5 | { 6 | name: 'exists', 7 | args: [DataType.integer], 8 | argsVariadic: DataType.integer, 9 | returns: DataType.bool, 10 | allowNullArguments: true, 11 | impure: true, 12 | implementation: (...items: number[]) => items?.some?.(Boolean) ?? false, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/functions/system.ts: -------------------------------------------------------------------------------- 1 | import { Types } from '../datatypes'; 2 | import { FunctionDefinition } from '../interfaces'; 3 | 4 | export const systemFunctions: FunctionDefinition[] = [ 5 | { 6 | // ugly hack... 7 | name: 'current_schema', 8 | returns: Types.text(), 9 | implementation: () => 'public', 10 | }, 11 | { 12 | name: 'obj_description', 13 | args: [Types.regclass, Types.text()], 14 | returns: Types.null, 15 | implementation: () => null 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { newDb } from './db'; 2 | export { enableStatementLocationTracking } from './parser/parse-cache'; 3 | export { replaceQueryArgs$ } from './adapters'; 4 | export * from './interfaces'; 5 | -------------------------------------------------------------------------------- /src/migrate/migrate-interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface MigrationParams { 2 | /** 3 | * If true, will force the migration API to rollback and re-apply the latest migration over 4 | * again each time when Node.js app launches. 5 | */ 6 | force?: boolean 7 | /** 8 | * Migrations table name. Default is 'migrations' 9 | */ 10 | table?: string 11 | /** 12 | * Path to the migrations folder. Default is `path.join(process.cwd(), 'migrations')` 13 | */ 14 | migrationsPath?: string 15 | /** 16 | * Migration data read from migrations folder. `migrationsPath` will be ignored if this is 17 | * provided. 18 | */ 19 | migrations?: readonly MigrationData[] 20 | } 21 | 22 | export interface MigrationFile { 23 | id: number 24 | name: string 25 | filename: string 26 | } 27 | 28 | export interface MigrationData { 29 | id: number 30 | name: string 31 | up: string 32 | down: string 33 | } 34 | -------------------------------------------------------------------------------- /src/migrate/readme.md: -------------------------------------------------------------------------------- 1 | This behaviour has been adapted from the [node-sqlite](https://github.com/kriasoft/node-sqlite/blob/6c0dc7f40f4342680df8cc381d4040bfd64410d4/src/utils/migrate.ts) migration algorithm -------------------------------------------------------------------------------- /src/misc/buffer-deno.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* 5 | ⛔⛔⛔ WARN ⛔⛔⛔ 6 | 7 | This file is built for Deno. 8 | Dont be surprised if it yields errors in Node, it's not meant to be used there. 9 | 10 | (transpilation replaces buffer-node.ts by buffer-node.ts when building deno version) 11 | 12 | */ 13 | 14 | export type TBuffer = Uint8Array; 15 | 16 | 17 | export function bufToString(buf: TBuffer): string { 18 | // @ts-ignore 19 | const decoder = new TextDecoder() 20 | return decoder.decode(buf); 21 | } 22 | 23 | export function bufCompare(a: TBuffer, b: TBuffer) { 24 | if (a === b) { 25 | return 0; 26 | } 27 | if (a.length > b.length) { 28 | return 1; 29 | } 30 | for (let i = 0; i < a.length; i++) { 31 | const d = a[i] - b[i]; 32 | if (d === 0) { 33 | continue; 34 | } 35 | return d < 0 ? -1 : 1; 36 | } 37 | return 0; 38 | } 39 | 40 | export function bufFromString(str: string) { 41 | // @ts-ignore 42 | const encoder = new TextEncoder() 43 | const buffer = encoder.encode(str); 44 | return buffer; 45 | } 46 | 47 | export function isBuf(v: any): v is TBuffer { 48 | return v instanceof Uint8Array; 49 | } 50 | 51 | export function bufClone(buf: TBuffer): TBuffer { 52 | return new Uint8Array(buf); 53 | } 54 | -------------------------------------------------------------------------------- /src/misc/buffer-node.ts: -------------------------------------------------------------------------------- 1 | export type TBuffer = Buffer; 2 | 3 | 4 | export function bufToString(buf: TBuffer): string { 5 | return buf?.toString('utf-8'); 6 | } 7 | 8 | export function bufCompare(a: TBuffer, b: TBuffer) { 9 | return Buffer.compare(a, b); 10 | } 11 | 12 | export function bufFromString(str: string) { 13 | return Buffer.from(str); 14 | } 15 | 16 | export function isBuf(v: any): v is TBuffer { 17 | return Buffer.isBuffer(v); 18 | } 19 | 20 | export function bufClone(buf: TBuffer): TBuffer { 21 | const bufcopy = Buffer.alloc(buf.length); 22 | buf.copy(bufcopy); 23 | return bufcopy; 24 | } -------------------------------------------------------------------------------- /src/misc/pg-escape.ts: -------------------------------------------------------------------------------- 1 | // stolen from https://github.com/segmentio/pg-escape/blob/master/index.js 2 | 3 | export function literal(val: any) { 4 | if (null == val) return 'NULL'; 5 | if (Array.isArray(val)) { 6 | var vals: any[] = val.map(literal) 7 | return "(" + vals.join(", ") + ")" 8 | } 9 | var backslash = ~val.indexOf('\\'); 10 | var prefix = backslash ? 'E' : ''; 11 | val = val.replace(/'/g, "''"); 12 | val = val.replace(/\\/g, '\\\\'); 13 | return prefix + "'" + val + "'"; 14 | }; 15 | -------------------------------------------------------------------------------- /src/parser/parse-cache.ts: -------------------------------------------------------------------------------- 1 | 2 | import { QueryError } from '../interfaces'; 3 | import LRUCache from 'lru-cache'; 4 | import hash from 'object-hash'; 5 | import { Expr, parse, Statement } from 'pgsql-ast-parser'; 6 | import { errorMessage } from '../utils'; 7 | 8 | 9 | const astCache: LRUCache = new LRUCache({ 10 | max: 1000, 11 | }); 12 | 13 | let locationTracking = false; 14 | export function enableStatementLocationTracking() { 15 | locationTracking = true; 16 | astCache.reset(); 17 | } 18 | 19 | 20 | /** Parse an AST from SQL */ 21 | export function parseSql(sql: string): Statement[]; 22 | export function parseSql(sql: string, entry: 'expr'): Expr; 23 | export function parseSql(sql: string, entry?: string): any { 24 | // when 'entry' is not specified, lets cache parsings 25 | // => better perf on repetitive requests 26 | const key = !entry && hash(sql); 27 | if (!entry) { 28 | const cached = astCache.get(key); 29 | if (cached) { 30 | return cached; 31 | } 32 | } 33 | 34 | try { 35 | 36 | let ret = parse(sql, { 37 | entry, 38 | locationTracking, 39 | }); 40 | 41 | // cache result 42 | if (!entry) { 43 | astCache.set(key, ret); 44 | } 45 | return ret; 46 | 47 | } catch (e) { 48 | const msg = errorMessage(e); 49 | if (!/Syntax error/.test(msg)) { 50 | throw e; 51 | } 52 | 53 | 54 | // throw a nice parsing error. 55 | throw new QueryError(`💔 Your query failed to parse. 56 | This is most likely due to a SQL syntax error. However, you might also have hit a bug, or an unimplemented feature of pg-mem. 57 | If this is the case, please file an issue at https://github.com/oguimbal/pg-mem along with a query that reproduces this syntax error. 58 | 59 | 👉 Failed query: 60 | 61 | ${sql} 62 | 63 | 💀 ${msg}`); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/schema/consts.ts: -------------------------------------------------------------------------------- 1 | export const SCHEMA_NAMESPACE = 11; 2 | export const MAIN_NAMESPACE = 2200; 3 | 4 | type OidType = 'table' | 'index'; 5 | export function makeOid(type: OidType, id: string) { 6 | return `oid:${type}:${id}`; 7 | } 8 | 9 | export function parseOid(oid: string): { type: OidType; id: string } { 10 | const [_, type, id] = /^oid:([^:]+):([^:]+)$/.exec(oid) ?? []; 11 | return { 12 | type: type as OidType, 13 | id 14 | } 15 | } -------------------------------------------------------------------------------- /src/schema/function-call-table.ts: -------------------------------------------------------------------------------- 1 | import { _Transaction, IValue, _Explainer, _IIndex, _SelectExplanation, Stats } from '../interfaces-private'; 2 | import { RecordCol } from '../datatypes'; 3 | import { buildCtx } from '../parser/context'; 4 | import { DataSourceBase } from '../transforms/transform-base'; 5 | import { columnEvaluator } from '../transforms/selection'; 6 | import { colByName, fromEntries } from '../utils'; 7 | 8 | export class FunctionCallTable extends DataSourceBase { 9 | readonly columns: readonly IValue[]; 10 | private readonly colsByName: Map; 11 | private symbol = Symbol(); 12 | 13 | get isExecutionWithNoResult(): boolean { 14 | return false; 15 | } 16 | 17 | constructor(cols: readonly RecordCol[], private evaluator: IValue) { 18 | super(buildCtx().schema); 19 | this.columns = cols.map(c => columnEvaluator(this, c.name, c.type).setOrigin(this)); 20 | this.colsByName = fromEntries(this.columns.map(c => [c.id!, c])); 21 | } 22 | 23 | entropy(t: _Transaction): number { 24 | return 0; 25 | } 26 | 27 | enumerate(t: _Transaction): Iterable { 28 | const results = this.evaluator.get(null, t); 29 | for (const result of results ?? []) { 30 | result[this.symbol] = true; 31 | } 32 | return results; 33 | } 34 | 35 | hasItem(value: any, t: _Transaction): boolean { 36 | return !!(value as any)[this.symbol]; 37 | } 38 | 39 | getColumn(column: string, nullIfNotFound?: boolean | undefined): IValue { 40 | return colByName(this.colsByName, column, nullIfNotFound)!; 41 | } 42 | 43 | getIndex(forValue: IValue): _IIndex | null | undefined { 44 | return null; 45 | } 46 | 47 | isOriginOf(value: IValue): boolean { 48 | return value.origin === this; 49 | } 50 | 51 | 52 | explain(e: _Explainer): _SelectExplanation { 53 | throw new Error('Method not implemented.'); 54 | } 55 | 56 | stats(t: _Transaction): Stats | null { 57 | throw new Error('Method not implemented.'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/schema/information-schema/constraint-column-usage.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | export class ConstraintColumnUsage extends ReadOnlyTable implements _ITable { 7 | 8 | 9 | _schema: Schema = { 10 | name: 'constraint_column_usage', 11 | fields: [ 12 | { name: 'constraint_catalog', type: Types.text() } 13 | , { name: 'constraint_schema', type: Types.text() } 14 | , { name: 'constraint_name', type: Types.text() } 15 | 16 | , { name: 'table_catalog', type: Types.text() } 17 | , { name: 'table_schema', type: Types.text() } 18 | , { name: 'table_name', type: Types.text() } 19 | 20 | , { name: 'column_name', type: Types.text() } 21 | ] 22 | }; 23 | 24 | 25 | entropy(): number { 26 | return 0; 27 | } 28 | 29 | *enumerate() { 30 | } 31 | 32 | 33 | hasItem(value: any): boolean { 34 | return false; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/schema/information-schema/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { _IDb, _ISchema } from '../../interfaces-private'; 3 | import { ColumnsListSchema } from './columns-list'; 4 | import { TablesSchema } from './table-list'; 5 | import { TableConstraints } from './table-constraints'; 6 | import { KeyColumnUsage } from './key-column-usage'; 7 | import { ConstraintColumnUsage } from './constraint-column-usage'; 8 | 9 | export function setupInformationSchema(db: _IDb) { 10 | const schema: _ISchema = db.createSchema('information_schema'); 11 | 12 | // SELECT * FROM "information_schema"."tables" WHERE ("table_schema" = 'public' AND "table_name" = 'user') 13 | new TablesSchema(schema).register(); 14 | new ColumnsListSchema(schema).register(); 15 | new TableConstraints(schema).register(); 16 | new KeyColumnUsage(schema).register(); 17 | new ConstraintColumnUsage(schema).register(); 18 | 19 | schema.setReadonly(); 20 | } -------------------------------------------------------------------------------- /src/schema/information-schema/key-column-usage.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | 7 | export class KeyColumnUsage extends ReadOnlyTable implements _ITable { 8 | 9 | 10 | _schema: Schema = { 11 | name: 'key_column_usage', 12 | fields: [ 13 | { name: 'constraint_catalog', type: Types.text() } 14 | , { name: 'constraint_schema', type: Types.text() } 15 | , { name: 'constraint_name', type: Types.text() } 16 | , { name: 'table_catalog', type: Types.text() } 17 | , { name: 'table_schema', type: Types.text() } 18 | , { name: 'table_name', type: Types.text() } 19 | , { name: 'column_name', type: Types.text() } 20 | , { name: 'ordinal_position', type: Types.integer } 21 | , { name: 'position_in_unique_constraint', type: Types.integer } 22 | ] 23 | }; 24 | 25 | 26 | entropy(): number { 27 | return 0; 28 | } 29 | 30 | *enumerate() { 31 | } 32 | 33 | 34 | hasItem(value: any): boolean { 35 | return false; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/schema/information-schema/table-constraints.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | // https://www.postgresql.org/docs/13/catalog-pg-range.html 7 | export class TableConstraints extends ReadOnlyTable implements _ITable { 8 | 9 | 10 | _schema: Schema = { 11 | name: 'table_constraints', 12 | fields: [ 13 | { name: 'constraint_catalog', type: Types.text() } 14 | , { name: 'constraint_schema', type: Types.text() } 15 | , { name: 'constraint_name', type: Types.text() } 16 | , { name: 'table_catalog', type: Types.text() } 17 | , { name: 'table_schema', type: Types.text() } 18 | , { name: 'table_name', type: Types.text() } 19 | , { name: 'constraint_type', type: Types.text() } 20 | , { name: 'is_deferrable', type: Types.bool } 21 | , { name: 'initially_deferred', type: Types.bool } 22 | , { name: 'enforced', type: Types.bool } 23 | ] 24 | }; 25 | 26 | 27 | entropy(): number { 28 | return 0; 29 | } 30 | 31 | *enumerate() { 32 | } 33 | 34 | 35 | hasItem(value: any): boolean { 36 | return false; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-database.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema, _Transaction, setId } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | // https://www.postgresql.org/docs/12/catalog-pg-class.html 7 | 8 | const IS_SCHEMA = Symbol('_is_pg_database'); 9 | export class PgDatabaseTable extends ReadOnlyTable implements _ITable { 10 | 11 | get ownSymbol() { 12 | return IS_SCHEMA; 13 | } 14 | 15 | 16 | _schema: Schema = { 17 | name: 'pg_database', 18 | fields: [ 19 | { name: 'oid', type: Types.integer } // hidden oid column 20 | , { name: 'datname', type: Types.text() } 21 | , { name: 'datdba', type: Types.integer } 22 | , { name: 'encoding', type: Types.integer } 23 | , { name: 'datcollate', type: Types.text() } 24 | , { name: 'datctype', type: Types.text() } 25 | , { name: 'datistemplate', type: Types.bool } 26 | , { name: 'datlowconn', type: Types.bool } 27 | , { name: 'datconlimit', type: Types.integer } 28 | , { name: 'datlastsysoid', type: Types.integer } 29 | , { name: 'datfrozenxid', type: Types.integer } 30 | , { name: 'datminmxid', type: Types.integer } 31 | , { name: 'dattablespace', type: Types.integer } 32 | , { name: 'datacl', type: Types.jsonb } 33 | ] 34 | }; 35 | 36 | entropy(t: _Transaction): number { 37 | return this.db.listSchemas().length; 38 | } 39 | 40 | *enumerate() { 41 | // this is 💩, whaterver... 42 | let i = 48593; 43 | for (const t of this.db.listSchemas()) { 44 | const ret = { 45 | oid: ++i, 46 | datname: t.name, 47 | [IS_SCHEMA]: true, 48 | }; 49 | yield setId(ret, '/schema/pg_database/' + t.name); 50 | } 51 | } 52 | 53 | 54 | hasItem(value: any): boolean { 55 | return !!value?.[IS_SCHEMA]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-enum-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | export class PgEnumTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_enum', 10 | fields: [ 11 | { name: 'oid', type: Types.integer } 12 | , { name: 'enumtypid', type: Types.integer } 13 | , { name: 'enumsortorder', type: Types.integer } 14 | , { name: 'enumlabel', type: Types.text() } 15 | ] 16 | }; 17 | 18 | entropy(): number { 19 | return 0; 20 | } 21 | 22 | *enumerate() { 23 | } 24 | 25 | hasItem(value: any): boolean { 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-index-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | export class PgIndexTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_index', 10 | fields: [ 11 | { name: 'indexrelid', type: Types.integer } // oid 12 | , { name: 'indrelid', type: Types.integer } // oid 13 | , { name: 'indnatts', type: Types.integer } 14 | , { name: 'indnkyatts', type: Types.integer } 15 | , { name: 'indisunique', type: Types.bool } 16 | , { name: 'indisprimary', type: Types.bool } 17 | , { name: 'indisxclusion', type: Types.bool } 18 | , { name: 'indimmediate', type: Types.bool } 19 | , { name: 'indisclustered', type: Types.bool } 20 | , { name: 'indisvalid', type: Types.bool } 21 | , { name: 'indcheckxmin', type: Types.bool } 22 | , { name: 'indisready', type: Types.bool } 23 | , { name: 'indisliv', type: Types.bool } 24 | , { name: 'indisreplident', type: Types.bool } 25 | , { name: 'indkey', type: Types.integer.asArray() } // int2vector 26 | , { name: 'indcollation', type: Types.integer.asArray() } // oidvector 27 | , { name: 'indclass', type: Types.integer.asArray() } // oidvector 28 | , { name: 'indoption', type: Types.integer.asArray() } // int2vector 29 | , { name: 'indeexprs', type: Types.jsonb } // pg_node_tree 30 | , { name: 'indpred', type: Types.jsonb } // pg_node_tree 31 | ] 32 | }; 33 | 34 | entropy(): number { 35 | return 0; 36 | } 37 | 38 | *enumerate() { 39 | } 40 | 41 | 42 | 43 | hasItem(value: any): boolean { 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-namespace-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | export class PgNamespaceTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_namespace', 10 | fields: [ 11 | { name: 'oid', type: Types.integer } // hidden oid column 12 | , { name: 'nspname', type: Types.text() } 13 | , { name: 'nspowner', type: Types.integer } // oid 14 | , { name: 'nspacl', type: Types.jsonb } // aclitem[] 15 | ] 16 | }; 17 | 18 | 19 | entropy(): number { 20 | return 0; 21 | } 22 | 23 | *enumerate() { 24 | 25 | // yield { 26 | // oid: MAIN_NAMESPACE, 27 | // nspname: 'public', 28 | // nspowner: null, 29 | // nspacl: null, 30 | // }; 31 | // yield { 32 | // oid: MAIN_NAMESPACE, 33 | // nspname: 'public', 34 | // nspowner: null, 35 | // nspacl: null, 36 | // }; 37 | } 38 | 39 | 40 | 41 | hasItem(value: any): boolean { 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-proc.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | // https://www.postgresql.org/docs/13/catalog-pg-range.html 7 | export class PgProc extends ReadOnlyTable implements _ITable { 8 | 9 | 10 | _schema: Schema = { 11 | name: 'pg_range', 12 | fields: [ 13 | { name: 'rngtypid', type: Types.integer } // oid 14 | , { name: 'rngsubtype', type: Types.integer } // oid 15 | , { name: 'rngcollation', type: Types.integer } // oid 16 | , { name: 'rngsubopc', type: Types.integer } // oid 17 | , { name: 'rngcanonical', type: Types.integer } // oid 18 | , { name: 'rngsubdiff', type: Types.integer } // oid 19 | ] 20 | }; 21 | 22 | 23 | entropy(): number { 24 | return 0; 25 | } 26 | 27 | *enumerate() { 28 | } 29 | 30 | 31 | 32 | hasItem(value: any): boolean { 33 | return false; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-sequences-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | export class PgSequencesTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_sequences', 10 | fields: [ 11 | { name: 'schemaname', type: Types.text() } 12 | , { name: 'sequencename', type: Types.text() } 13 | , { name: 'sequenceowner', type: Types.integer } 14 | , { name: 'data_type', type: Types.text() } 15 | , { name: 'start_value', type: Types.integer } 16 | , { name: 'min_value', type: Types.integer } 17 | , { name: 'max_value', type: Types.integer } 18 | , { name: 'increment_by', type: Types.integer } 19 | , { name: 'cycle', type: Types.bool } 20 | , { name: 'cache_size', type: Types.integer } 21 | , { name: 'last_value', type: Types.integer } 22 | ] 23 | }; 24 | 25 | entropy(): number { 26 | return 0; 27 | } 28 | 29 | *enumerate() { 30 | } 31 | 32 | hasItem(value: any): boolean { 33 | return false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg-user-list.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | export class PgUserTable extends ReadOnlyTable implements _ITable { 7 | 8 | _schema: Schema = { 9 | name: 'pg_user', 10 | fields: [ 11 | { name: 'usename', type: Types.text() } 12 | , { name: 'usesysid', type: Types.integer } 13 | , { name: 'usecreatedb', type: Types.bool } 14 | , { name: 'usesuper', type: Types.bool } 15 | , { name: 'usecatupd', type: Types.bool } 16 | , { name: 'userepl', type: Types.bool } 17 | , { name: 'usebypassrls', type: Types.bool } 18 | , { name: 'passwd', type: Types.text() } 19 | , { name: 'valuntil', type: Types.timestamptz() } 20 | , { name: 'useconfig', type: Types.jsonb } 21 | ] 22 | }; 23 | 24 | entropy(): number { 25 | return 0; 26 | } 27 | 28 | *enumerate() { 29 | } 30 | 31 | hasItem(value: any): boolean { 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/schema/pg-catalog/pg_statio_user_tables.ts: -------------------------------------------------------------------------------- 1 | import { _ITable, _ISelection, IValue, _IIndex, _IDb, IndexKey, setId, _ISchema } from '../../interfaces-private'; 2 | import { Schema } from '../../interfaces'; 3 | import { Types } from '../../datatypes'; 4 | import { ReadOnlyTable } from '../readonly-table'; 5 | 6 | // https://www.postgresql.org/docs/13/catalog-pg-range.html 7 | const IS_SCHEMA = Symbol('_is_pg_statio_user_tables'); 8 | export class PgStatioUserTables extends ReadOnlyTable implements _ITable { 9 | 10 | 11 | _schema: Schema = { 12 | name: 'pg_statio_user_tables', 13 | fields: [ 14 | { name: 'relid', type: Types.integer } // oid 15 | , { name: 'schemaname', type: Types.text() } 16 | , { name: 'relname', type: Types.text() } 17 | , { name: 'heap_blks_read', type: Types.integer } 18 | , { name: 'heap_blks_hit', type: Types.integer } 19 | , { name: 'idx_blks_read', type: Types.integer } 20 | , { name: 'idx_blks_hit', type: Types.integer } 21 | , { name: 'toast_blks_read', type: Types.integer } 22 | , { name: 'toast_blks_hit', type: Types.integer } 23 | , { name: 'tidx_blks_read', type: Types.integer } 24 | , { name: 'tidx_blks_hit', type: Types.integer } 25 | 26 | ] 27 | }; 28 | 29 | 30 | entropy(): number { 31 | return 0; 32 | } 33 | 34 | *enumerate() { 35 | for (const t of this.db.public.listTables()) { 36 | yield { 37 | relid: t.reg.typeId, 38 | schemaname: 'public', 39 | relname: t.name, 40 | heap_blks_read: 0, 41 | heap_blks_hit: 0, 42 | idx_blks_read: 0, 43 | idx_blks_hit: 0, 44 | toast_blks_read: 0, 45 | toast_blks_hit: 0, 46 | tidx_blks_read: 0, 47 | tidx_blks_hit: 0, 48 | [IS_SCHEMA]: true, 49 | }; 50 | } 51 | } 52 | 53 | 54 | 55 | hasItem(value: any): boolean { 56 | return value[IS_SCHEMA]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/schema/view.ts: -------------------------------------------------------------------------------- 1 | import { QueryError, Reg, _Explainer, _ISchema, _ISelection, _IView, _Transaction } from '../interfaces-private'; 2 | import { DataSourceBase, FilterBase } from '../transforms/transform-base'; 3 | 4 | export class View extends FilterBase implements _IView { 5 | get type(): 'view' { 6 | return 'view'; 7 | } 8 | 9 | private _reg?: Reg; 10 | get reg(): Reg { 11 | if (!this._reg) { 12 | throw new QueryError(`relation "${this.name}" does not exist`); 13 | } 14 | return this._reg; 15 | } 16 | 17 | constructor(readonly ownerSchema: _ISchema, readonly name: string, readonly selection: _ISelection) { 18 | super(selection); 19 | } 20 | 21 | 22 | enumerate(t: _Transaction): Iterable { 23 | return this.selection.enumerate(t); 24 | } 25 | 26 | hasItem(value: any, t: _Transaction): boolean { 27 | return this.selection.hasItem(value, t); 28 | } 29 | 30 | explain(e: _Explainer) { 31 | return this.selection.explain(e); 32 | } 33 | 34 | stats(t: _Transaction) { 35 | return this.selection.stats(t); 36 | } 37 | 38 | 39 | register() { 40 | // once fields registered, 41 | // then register the table 42 | // (column registrations need it not to be registered yet) 43 | this._reg = this.ownerSchema._reg_register(this); 44 | return this; 45 | } 46 | 47 | drop(t: _Transaction): void { 48 | throw new Error('Method not implemented.'); 49 | } 50 | } -------------------------------------------------------------------------------- /src/tests/drizzle-requests.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { _IDb } from '../interfaces-private'; 6 | 7 | describe('drizzle - requests', () => { 8 | 9 | let db: _IDb; 10 | let many: (str: string) => any[]; 11 | let none: (str: string) => void; 12 | beforeEach(() => { 13 | db = newDb({ 14 | autoCreateForeignKeyIndices: true, 15 | }) as _IDb; 16 | many = db.public.many.bind(db.public); 17 | none = db.public.none.bind(db.public); 18 | }); 19 | 20 | function simpleDb() { 21 | db.public.none('create table data(id text primary key, data jsonb, num integer, var varchar(10))'); 22 | } 23 | 24 | it('can select pg_user', () => { 25 | simpleDb(); 26 | many(`select s.nspname as table_schema 27 | from pg_catalog.pg_namespace s 28 | join pg_catalog.pg_user u on u.usesysid = s.nspowner 29 | where nspname not in ('information_schema', 'pg_catalog', 'public') 30 | and nspname not like 'pg_toast%' 31 | and nspname not like 'pg_temp_%' 32 | order by table_schema`); 33 | }); 34 | 35 | it('can select pg_sequences', () => { 36 | simpleDb(); 37 | many(`select schemaname, sequencename, start_value, min_value, max_value, increment_by, cycle, cache_size from pg_sequences as seq WHERE schemaname = 'public'`); 38 | }); 39 | 40 | it('can select pg_enum', () => { 41 | simpleDb(); 42 | many(`select n.nspname as enum_schema, 43 | t.typname as enum_name, 44 | e.enumlabel as enum_value, 45 | e.enumsortorder as sort_order 46 | from pg_type t 47 | join pg_enum e on t.oid = e.enumtypid 48 | join pg_catalog.pg_namespace n ON n.oid = t.typnamespace 49 | where n.nspname = 'public' 50 | order by enum_schema, enum_name, sort_order`); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/tests/extensions.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { _IDb } from '../interfaces-private'; 6 | import { DataType } from '../interfaces'; 7 | import { expectQueryError } from './test-utils'; 8 | 9 | describe('Extensions', () => { 10 | 11 | let db: _IDb; 12 | let many: (str: string) => any[]; 13 | beforeEach(() => { 14 | db = newDb() as _IDb; 15 | many = db.public.many.bind(db.public); 16 | }); 17 | 18 | it('can call function declared in extension', () => { 19 | db.registerExtension('ext', s => s.registerFunction({ 20 | name: 'say_hello', 21 | args: [DataType.text], 22 | returns: DataType.text, 23 | implementation: x => 'hello ' + x, 24 | })); 25 | 26 | expect(many(`create extension ext; 27 | select say_hello('world') as msg`)) 28 | .toEqual([{ 29 | msg: 'hello world', 30 | }]) 31 | }) 32 | 33 | 34 | it('can call function declared in another schema', () => { 35 | expect(many(`select pg_catalog.col_description(1,2) as msg`)) 36 | .toEqual([{ 37 | msg: 'Fake description provided by pg-mem', 38 | }]) 39 | }); 40 | 41 | 42 | it('cannot create extension twice', () => { 43 | db.registerExtension('ext', s => { }); 44 | 45 | many('create extension "ext"'); 46 | expectQueryError(() => many('create extension "ext"')); 47 | }); 48 | 49 | it('can recreate extension twice with "if not exists"', () => { 50 | db.registerExtension('ext', s => { }); 51 | 52 | many('create extension if not exists "ext"'); 53 | many('create extension if not exists "ext"'); 54 | }); 55 | }); -------------------------------------------------------------------------------- /src/tests/fork.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { trimNullish } from '../utils'; 6 | import { Types } from '../datatypes'; 7 | import { preventSeqScan } from './test-utils'; 8 | import { IMemoryDb } from '../interfaces'; 9 | 10 | describe('DB restore points', () => { 11 | 12 | let db: IMemoryDb; 13 | let many: (str: string) => any[]; 14 | let none: (str: string) => void; 15 | beforeEach(() => { 16 | db = newDb(); 17 | many = db.public.many.bind(db.public); 18 | none = db.public.none.bind(db.public); 19 | }); 20 | 21 | function simpleDb() { 22 | db.public.declareTable({ 23 | name: 'data', 24 | fields: [{ 25 | name: 'id', 26 | type: Types.text(), 27 | constraints: [{ type: 'primary key' }], 28 | }, { 29 | name: 'str', 30 | type: Types.text(), 31 | }, { 32 | name: 'otherstr', 33 | type: Types.text(), 34 | }], 35 | }); 36 | return db; 37 | } 38 | 39 | 40 | it('can backup & resotre db', () => { 41 | simpleDb(); 42 | none(`insert into data(id) values ('value')`); 43 | const bck = db.backup(); 44 | none(`update data set id='updatd';insert into data(id) values ('other')`); 45 | bck.restore(); 46 | expect(trimNullish(many(`select * from data`))).toEqual([{ id: 'value' }]); 47 | }); 48 | 49 | 50 | it('can backup & resotre db multiple times', () => { 51 | simpleDb(); 52 | none(`insert into data(id) values ('value')`); 53 | const bck = db.backup(); 54 | none(`update data set id='updatd';insert into data(id) values ('other')`); 55 | bck.restore(); 56 | expect(trimNullish(many(`select * from data`))).toEqual([{ id: 'value' }]); 57 | none(`update data set id='updatd';insert into data(id) values ('other')`); 58 | bck.restore(); 59 | expect(trimNullish(many(`select * from data`))).toEqual([{ id: 'value' }]); 60 | }); 61 | 62 | }); -------------------------------------------------------------------------------- /src/tests/invalid-syntaxes.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { IMemoryDb } from '../interfaces'; 6 | import { expectQueryError } from './test-utils'; 7 | 8 | describe('Invalid syntaxes', () => { 9 | 10 | let db: IMemoryDb; 11 | let many: (str: string) => any[]; 12 | let none: (str: string) => void; 13 | function all(table = 'data') { 14 | return many(`select * from ${table}`); 15 | } 16 | beforeEach(() => { 17 | db = newDb(); 18 | many = db.public.many.bind(db.public); 19 | none = db.public.none.bind(db.public); 20 | }); 21 | 22 | 23 | 24 | it('checks this is an invalid syntax', () => { 25 | expectQueryError(() => none(`create table test(val integer); 26 | create index on test(val); 27 | insert into test values (1), (2), (3), (4) 28 | select * from test where val >= 2;`)); // ^ missing a ";" ... but was not throwing. 29 | }) 30 | }); -------------------------------------------------------------------------------- /src/tests/irl-tests/cbadger85.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { 4 | BaseEntity, 5 | DriverException, 6 | EntitySchema, 7 | MikroORM, 8 | } from '@mikro-orm/core'; 9 | import { newDb } from '../..'; 10 | import { errorMessage } from '../../utils'; 11 | 12 | export class Book extends BaseEntity { 13 | id!: number; 14 | title!: string; 15 | author!: string; 16 | } 17 | 18 | export const bookSchema = new EntitySchema>({ 19 | class: Book, 20 | extends: 'BaseEntity', 21 | properties: { 22 | id: { type: Number, primary: true }, 23 | title: { type: 'string' }, 24 | author: { type: 'string' }, 25 | }, 26 | }); 27 | 28 | describe('IRL tests', () => { 29 | 30 | 31 | let orm: MikroORM; 32 | 33 | beforeEach(async () => { 34 | const db = newDb(); 35 | orm = await db.adapters.createMikroOrm({ 36 | entities: [bookSchema], 37 | debug: true, 38 | }); 39 | 40 | await orm.getSchemaGenerator().createSchema(); 41 | }); 42 | 43 | it('cbadger85 x mikro-orm', async () => { 44 | const em = orm.em.fork() 45 | try { 46 | await em.getRepository(bookSchema).findOneOrFail({ id: 1 }); 47 | } catch (e) { 48 | expect(e).not.toBeInstanceOf(DriverException); 49 | expect(errorMessage(e)).toMatch(/Book not found/); 50 | return; 51 | } 52 | expect('Should have thrown').toBe(''); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/tests/kysely-real.spec.ts: -------------------------------------------------------------------------------- 1 | import { newDb } from '../db'; 2 | import { kyselySample } from '../../samples/kysely/kysely'; 3 | import { describe, it, beforeEach, expect } from 'bun:test'; 4 | import type { Kysely } from "kysely"; 5 | 6 | describe('Kysely', () => { 7 | it('can perform sample', async () => { 8 | await kyselySample(); 9 | }); 10 | 11 | it('should use kysely config from parameter', async () => { 12 | const mem = newDb(); 13 | const camelCasePlugin = new (await import('kysely')).CamelCasePlugin(); 14 | const kysely = mem.adapters.createKysely( 15 | undefined, 16 | { 17 | plugins: [camelCasePlugin] 18 | } 19 | ) as Kysely; 20 | const executor = kysely.getExecutor(); 21 | expect(executor.plugins).toEqual([camelCasePlugin]); 22 | }); 23 | 24 | it('should ignore dialect prop in kysely config', async () => { 25 | const mem = newDb(); 26 | const kysely = mem.adapters.createKysely( 27 | undefined, 28 | { 29 | dialect: new (await import('kysely')).MysqlDialect({ 30 | pool: {} as any, 31 | }) 32 | } 33 | ) as Kysely; 34 | const executor = kysely.getExecutor(); 35 | const ctorName = Object.getPrototypeOf(executor.adapter).constructor.name; 36 | expect(ctorName).toBe('PostgresAdapter'); 37 | // expect(executor.adapter).toBeInstanceOf((await import('kysely')).PostgresAdapter); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/tests/limit.queries.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { _IDb } from '../interfaces-private'; 6 | import { preventSeqScan } from './test-utils'; 7 | 8 | describe('Limits', () => { 9 | 10 | let db: _IDb; 11 | let many: (str: string) => any[]; 12 | let none: (str: string) => void; 13 | let one: (str: string) => any; 14 | beforeEach(() => { 15 | db = newDb() as _IDb; 16 | many = db.public.many.bind(db.public); 17 | none = db.public.none.bind(db.public); 18 | one = db.public.one.bind(db.public); 19 | }); 20 | 21 | 22 | it('simple limit', () => { 23 | expect(many(`create table test(val text); 24 | insert into test values ('a'), ('b'), ('c'); 25 | select val from test limit 2`)) 26 | .toEqual([ 27 | { val: 'a' } 28 | , { val: 'b' } 29 | ]); 30 | }); 31 | 32 | it('offset + limit', () => { 33 | expect(many(`create table test(val text); 34 | insert into test values ('a'), ('b'), ('c'); 35 | select val from test limit 1 offset 1`)) 36 | .toEqual([ 37 | { val: 'b' } 38 | ]); 39 | }); 40 | }); -------------------------------------------------------------------------------- /src/tests/migrate/001-initial.sql: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- 3 | -- Up 4 | -------------------------------------------------------------------------------- 5 | 6 | CREATE TABLE Category ( 7 | id INTEGER PRIMARY KEY, 8 | name TEXT NOT NULL 9 | ); 10 | 11 | CREATE TABLE Post ( 12 | id INTEGER PRIMARY KEY, 13 | categoryId INTEGER NOT NULL, 14 | title TEXT NOT NULL, 15 | isPublished NUMERIC NOT NULL DEFAULT 0, 16 | CONSTRAINT Post_fk_categoryId FOREIGN KEY (categoryId) 17 | REFERENCES Category (id) ON UPDATE CASCADE ON DELETE CASCADE, 18 | CONSTRAINT Post_ck_isPublished CHECK (isPublished IN (0, 1)) 19 | ); 20 | 21 | CREATE INDEX Post_ix_categoryId ON Post (categoryId); 22 | 23 | INSERT INTO Category (id, name) VALUES (1, 'Test'); 24 | 25 | -------------------------------------------------------------------------------- 26 | -- Down 27 | -------------------------------------------------------------------------------- 28 | 29 | DROP INDEX Post_ix_categoryId; 30 | DROP TABLE Post; 31 | DROP TABLE Category; -------------------------------------------------------------------------------- /src/tests/migrate/002-some-feature.sql: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- 3 | -- Up 4 | -------------------------------------------------------------------------------- 5 | 6 | CREATE TABLE Test ( 7 | id INTEGER PRIMARY KEY, 8 | name TEXT NOT NULL 9 | ); 10 | 11 | -------------------------------------------------------------------------------- 12 | -- Down 13 | -------------------------------------------------------------------------------- 14 | 15 | DROP TABLE Test; -------------------------------------------------------------------------------- /src/tests/migrate/003-test-cert.sql: -------------------------------------------------------------------------------- 1 | 2 | -- Up 3 | CREATE TABLE whatever ( certificate TEXT ); 4 | INSERT INTO whatever ( certificate ) VALUES ( 5 | '-----BEGIN CERTIFICATE----- 6 | some contents 7 | -----END CERTIFICATE-----'); 8 | 9 | -- Down 10 | DROP TABLE whatever; -------------------------------------------------------------------------------- /src/tests/migrate/004-no-down.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS downless ( value TEXT ); 2 | INSERT INTO downless ( value ) VALUES ('down migration is optional'); -------------------------------------------------------------------------------- /src/tests/migrate/migrate.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../../db'; 4 | 5 | import { _IDb } from '../../interfaces-private'; 6 | import path from 'path'; 7 | 8 | describe('Migrate', () => { 9 | 10 | let db: _IDb; 11 | let many: (str: string) => any[]; 12 | let none: (str: string) => void; 13 | beforeEach(() => { 14 | db = newDb() as _IDb; 15 | many = db.public.many.bind(db.public); 16 | none = db.public.none.bind(db.public); 17 | }); 18 | 19 | 20 | it('can run migrations', async () => { 21 | await db.public.migrate({ 22 | migrationsPath: path.resolve('./src/tests/migrate'), 23 | }); 24 | 25 | expect(many(`select id, name from migrations`)) 26 | .toEqual([ 27 | { id: 1, name: 'initial' } 28 | , { id: 2, name: 'some-feature' } 29 | , { id: 3, name: 'test-cert' } 30 | , { id: 4, name: 'no-down' } 31 | ]) 32 | }) 33 | 34 | }); -------------------------------------------------------------------------------- /src/tests/mikro-orm-real.spec.ts: -------------------------------------------------------------------------------- 1 | import { newDb } from '../db'; 2 | import { mikroOrmSample } from '../../samples/mikro-orm/simple'; 3 | import { describe, it, beforeEach, expect } from 'bun:test'; 4 | 5 | describe('Mikro ORM', () => { 6 | 7 | it('can perform sample', async () => { 8 | const db = await mikroOrmSample(); 9 | 10 | expect(db.public.many(`select * from book`)) 11 | .toEqual([{ 12 | id: 'miserables', 13 | author_id: 'hugo', 14 | title: 'Les Misérables', 15 | }]); 16 | 17 | expect(db.public.many(`select * from author`)) 18 | .toEqual([{ 19 | id: 'hugo', 20 | name: 'Victor Hugo', 21 | }]); 22 | }) 23 | }); 24 | -------------------------------------------------------------------------------- /src/tests/pg-promise.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { _IDb } from '../interfaces-private'; 6 | 7 | describe('pg-promise', () => { 8 | 9 | let db: _IDb; 10 | let many: (str: string) => any[]; 11 | let none: (str: string) => void; 12 | beforeEach(() => { 13 | db = newDb() as _IDb; 14 | many = db.public.many.bind(db.public); 15 | none = db.public.none.bind(db.public); 16 | }); 17 | 18 | function simpleDb() { 19 | db.public.none(`create table data(id text primary key, data jsonb, num integer, var varchar(10)); 20 | insert into data values ('str', '{"data": true}', 42, 'varchar')`); 21 | } 22 | 23 | 24 | it('can open connection', async () => { 25 | simpleDb(); 26 | const pgp = db.adapters.createPgPromise(); 27 | const got = await pgp.any('select * from data'); 28 | expect(got).toEqual([{ 29 | id: 'str', 30 | data: { data: true }, 31 | num: 42, 32 | var: 'varchar', 33 | }]); 34 | }); 35 | 36 | it('can execute begin', async () => { 37 | simpleDb(); 38 | const pgp = db.adapters.createPgPromise(); 39 | await pgp.any('begin'); 40 | }); 41 | }); -------------------------------------------------------------------------------- /src/tests/sequelize-real.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | import { sequelizeSample } from '../../samples/sequelize/sequelize'; 3 | 4 | describe('Sequelize', () => { 5 | 6 | it('can perform sample without force sync', async () => { 7 | await sequelizeSample(); 8 | }) 9 | 10 | it('can perform sample with force sync', async () => { 11 | await sequelizeSample(true); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/tests/test-utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | import { watchUse, queryJson } from '../utils'; 3 | 4 | describe('Test utils', () => { 5 | it('checkUse() checks everything', () => { 6 | const old = globalThis.process.env['NOCHECKFULLQUERYUSAGE']; 7 | delete globalThis.process.env['NOCHECKFULLQUERYUSAGE']; 8 | try { 9 | { 10 | const { checked, check } = watchUse({ a: 1, b: [{ c: 1 }] }); 11 | checked.a; 12 | checked.b[0].c; 13 | expect(check!()).toBeFalsy(); 14 | } 15 | { 16 | const { checked, check } = watchUse({ a: 1, b: [{ c: 1 }] }); 17 | checked.a; 18 | expect(check!()).toBeTruthy(); 19 | } 20 | { 21 | const { checked, check } = watchUse({ a: 1, b: [{ c: 1 }, { d: 1 }] }); 22 | checked.a; 23 | checked.b[1].c; 24 | expect(check!()).toBeTruthy(); 25 | } 26 | { 27 | const { checked, check } = watchUse({ a: 1, b: [{ c: 1 }, 5] }); 28 | checked.a; 29 | (checked.b[0] as any).c; 30 | expect(check!()).toBeFalsy(); 31 | } 32 | } finally { 33 | globalThis.process.env['NOCHECKFULLQUERYUSAGE'] = old; 34 | } 35 | }); 36 | 37 | 38 | it('queryJson() works', () => { 39 | expect(queryJson({ a: 1 }, { a: 1, b: 2 })).toBeTrue(); 40 | expect(queryJson([{ a: 1 }], { a: 1, b: 2 })).toBeFalse(); 41 | expect(queryJson([{ a: 1 }], [{ a: 1, b: 2 }])).toBeTrue(); 42 | expect(queryJson({ a: 1 }, [{ a: 1, b: 2 }])).toBeFalse(); 43 | expect(queryJson({ a: [1] }, { a: [1, 2, 3] })).toBeTrue(); 44 | expect(queryJson({ a: [{ b: 'test' }] }, { a: [{ b: 'test', c: 42 }] })).toBeTrue(); 45 | expect(queryJson({ a: [{ b: 'test' }, { c: 12 }] }, { a: [{ c: 12 }, { b: 'test' }] })).toBeTrue(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/tests/transactions.queries.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, beforeEach, expect } from 'bun:test'; 2 | 3 | import { newDb } from '../db'; 4 | 5 | import { IMemoryDb } from '../interfaces'; 6 | import { Types } from '../datatypes'; 7 | 8 | describe('Transactions', () => { 9 | 10 | let db: IMemoryDb; 11 | let many: (str: string) => any[]; 12 | let none: (str: string) => void; 13 | function all(table = 'test') { 14 | return many(`select * from ${table}`); 15 | } 16 | beforeEach(() => { 17 | db = newDb(); 18 | many = db.public.many.bind(db.public); 19 | none = db.public.none.bind(db.public); 20 | }); 21 | 22 | 23 | function simpleDb() { 24 | db.public.declareTable({ 25 | name: 'data', 26 | fields: [{ 27 | name: 'id', 28 | type: Types.text(), 29 | constraints: [{ type: 'primary key' }], 30 | }, { 31 | name: 'str', 32 | type: Types.text(), 33 | }, { 34 | name: 'otherstr', 35 | type: Types.text(), 36 | }], 37 | }); 38 | return db; 39 | } 40 | 41 | it('can rollback an update', () => { 42 | simpleDb(); 43 | none(`insert into data(id, str) values ('some id', 'some str')`); 44 | expect(many(`update data set str='to rollback'; 45 | rollback; 46 | select str from data;`)) 47 | .toEqual([{ str: 'some str' }]); 48 | }); 49 | 50 | 51 | }); -------------------------------------------------------------------------------- /src/transforms/aggregations/array_agg.ts: -------------------------------------------------------------------------------- 1 | import { AggregationComputer, AggregationGroupComputer, IValue, nil, QueryError, _ISelection, _IType, _Transaction } from '../../interfaces-private'; 2 | import { ExprCall } from 'pgsql-ast-parser'; 3 | import { buildValue } from '../../parser/expression-builder'; 4 | import { Types } from '../../datatypes'; 5 | import { withSelection } from '../../parser/context'; 6 | 7 | 8 | class ArrayAggExpr implements AggregationComputer { 9 | 10 | constructor(private exp: IValue) { 11 | } 12 | 13 | get type(): _IType { 14 | return Types.integer.asArray(); 15 | } 16 | 17 | createGroup(t: _Transaction): AggregationGroupComputer { 18 | let val: any[] = []; 19 | return { 20 | feedItem: (item) => { 21 | const value = this.exp.get(item, t); 22 | val = [...val, value]; 23 | }, 24 | finish: () => val, 25 | } 26 | } 27 | } 28 | 29 | export function buildArrayAgg(this: void, base: _ISelection, call: ExprCall) { 30 | return withSelection(base, () => { 31 | const args = call.args; 32 | if (args.length !== 1) { 33 | throw new QueryError('ARRAY_AGG expects one argument, given ' + args.length); 34 | } 35 | 36 | const what = buildValue(args[0]); 37 | return new ArrayAggExpr(what); 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /src/transforms/aggregations/avg.ts: -------------------------------------------------------------------------------- 1 | import { AggregationComputer, AggregationGroupComputer, IValue, nil, QueryError, _ISelection, _IType, _Transaction } from '../../interfaces-private'; 2 | import { ExprCall } from 'pgsql-ast-parser'; 3 | import { buildValue } from '../../parser/expression-builder'; 4 | import { Types } from '../../datatypes'; 5 | import { nullIsh, sum } from '../../utils'; 6 | import { withSelection } from '../../parser/context'; 7 | 8 | 9 | class AvgExpr implements AggregationComputer { 10 | 11 | constructor(private exp: IValue) { 12 | } 13 | 14 | get type(): _IType { 15 | return Types.bigint; 16 | } 17 | 18 | createGroup(t: _Transaction): AggregationGroupComputer { 19 | let full: number[] = []; 20 | return { 21 | feedItem: (item) => { 22 | const value = this.exp.get(item, t); 23 | if (!nullIsh(value)) { 24 | full.push(value); 25 | } 26 | }, 27 | finish: () => full.length === 0 ? null : sum(full) / full.length, 28 | } 29 | } 30 | } 31 | 32 | 33 | export function buildAvg(this: void, base: _ISelection, call: ExprCall) { 34 | return withSelection(base, () => { 35 | const args = call.args; 36 | if (args.length !== 1) { 37 | throw new QueryError('AVG expects one argument, given ' + args.length); 38 | } 39 | 40 | const what = buildValue(args[0]); 41 | return new AvgExpr(what); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/transforms/aggregations/bool-aggregs.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, QueryError, AggregationComputer, IValue, _IType, _Transaction, AggregationGroupComputer } from '../../interfaces-private'; 2 | import { ExprCall } from 'pgsql-ast-parser'; 3 | import { withSelection } from '../../parser/context'; 4 | import { buildValue } from '../../parser/expression-builder'; 5 | import { nullIsh } from '../../utils'; 6 | import { Types } from '../../datatypes'; 7 | 8 | 9 | class BoolAgg implements AggregationComputer { 10 | 11 | constructor(private exp: IValue, private isOr: boolean) { 12 | } 13 | 14 | get type() { 15 | return Types.bool; 16 | } 17 | 18 | createGroup(t: _Transaction): AggregationGroupComputer { 19 | let result: boolean | null = null; 20 | return { 21 | feedItem: (item) => { 22 | if (result === this.isOr) { 23 | // no need to compute it further 24 | return; 25 | } 26 | const value = this.exp.get(item, t); 27 | if (nullIsh(value)) { 28 | return; 29 | } 30 | result = !!value; 31 | }, 32 | finish: () => result, 33 | } 34 | } 35 | } 36 | 37 | 38 | 39 | export function buildBoolAgg(this: void, base: _ISelection, call: ExprCall, fn: 'bool_and' | 'bool_or') { 40 | return withSelection(base, () => { 41 | const args = call.args; 42 | if (args.length !== 1) { 43 | throw new QueryError(fn + ' expects one argument, given ' + args.length); 44 | } 45 | const what = buildValue(args[0]).cast(Types.bool); 46 | return new BoolAgg(what, fn === 'bool_or'); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /src/transforms/aggregations/json_aggs.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, QueryError, AggregationComputer, IValue, _IType, _Transaction, AggregationGroupComputer } from '../../interfaces-private'; 2 | import { ExprCall } from 'pgsql-ast-parser'; 3 | import { withSelection } from '../../parser/context'; 4 | import { buildValue } from '../../parser/expression-builder'; 5 | import { nullIsh } from '../../utils'; 6 | import { Types } from '../../datatypes'; 7 | 8 | 9 | class JsonAggExpr implements AggregationComputer { 10 | 11 | constructor(private exp: IValue, readonly type: _IType) { 12 | } 13 | 14 | createGroup(t: _Transaction): AggregationGroupComputer { 15 | let full: any[][] = []; 16 | return { 17 | feedItem: (item) => { 18 | const value = this.exp.get(item, t); 19 | if (!nullIsh(value)) { 20 | full.push(value); 21 | } 22 | }, 23 | finish: () => full.length === 0 ? null : full, 24 | } 25 | } 26 | } 27 | 28 | 29 | export function buildJsonAgg(this: void, base: _ISelection, call: ExprCall, fn: 'json_agg' | 'jsonb_agg') { 30 | return withSelection(base, () => { 31 | const args = call.args; 32 | if (args.length !== 1) { 33 | throw new QueryError(fn + ' expects one argument, given ' + args.length); 34 | } 35 | const type = fn === 'json_agg' ? Types.json : Types.jsonb; 36 | const what = buildValue(args[0]); 37 | return new JsonAggExpr(what, type); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/transforms/aggregations/sum.ts: -------------------------------------------------------------------------------- 1 | import { AggregationComputer, AggregationGroupComputer, IValue, nil, QueryError, _ISelection, _IType, _Transaction } from '../../interfaces-private'; 2 | import { ExprCall } from 'pgsql-ast-parser'; 3 | import { buildValue } from '../../parser/expression-builder'; 4 | import { Types } from '../../datatypes'; 5 | import { nullIsh } from '../../utils'; 6 | import { withSelection } from '../../parser/context'; 7 | 8 | class SumExpr implements AggregationComputer { 9 | 10 | constructor(private exp: IValue) { 11 | } 12 | 13 | get type(): _IType { 14 | return Types.bigint; 15 | } 16 | 17 | createGroup(t: _Transaction): AggregationGroupComputer { 18 | let val: number | nil = null; 19 | return { 20 | feedItem: (item) => { 21 | const value = this.exp.get(item, t); 22 | if (!nullIsh(value)) { 23 | val = nullIsh(val) ? value : val + value; 24 | } 25 | }, 26 | finish: () => val, 27 | } 28 | } 29 | } 30 | 31 | export function buildSum(this: void, base: _ISelection, call: ExprCall) { 32 | return withSelection(base, () => { 33 | const args = call.args; 34 | if (args.length !== 1) { 35 | throw new QueryError('SUM expects one argument, given ' + args.length); 36 | } 37 | 38 | const what = buildValue(args[0]); 39 | return new SumExpr(what); 40 | 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /src/transforms/array-filter.ts: -------------------------------------------------------------------------------- 1 | import { FilterBase } from './transform-base'; 2 | import { _ISelection, _Explainer, _SelectExplanation, _Transaction, Stats, Row } from '../interfaces-private'; 3 | 4 | export class ArrayFilter extends FilterBase { 5 | 6 | get index() { 7 | return null; 8 | } 9 | 10 | entropy() { 11 | return this.rows.length; 12 | } 13 | 14 | hasItem(raw: Row): boolean { 15 | return this.rows.includes(raw); 16 | } 17 | 18 | getIndex() { 19 | return null; 20 | } 21 | 22 | constructor(fromTable: _ISelection, public rows: Row[]) { 23 | super(fromTable); 24 | } 25 | 26 | enumerate(): Iterable { 27 | return this.rows; 28 | } 29 | 30 | stats(t: _Transaction): Stats | null { 31 | return { 32 | count: this.rows.length, 33 | }; 34 | } 35 | 36 | explain(e: _Explainer): _SelectExplanation { 37 | return { 38 | id: e.idFor(this), 39 | _: 'constantSet', 40 | rawArrayLen: this.rows.length, 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/transforms/between-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _IIndex, _ITable, _Transaction, _Explainer, _SelectExplanation, IndexOp, Stats, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | import { nullIsh } from '../utils'; 4 | 5 | export class BetweenFilter extends FilterBase { 6 | 7 | private opDef: IndexOp; 8 | 9 | 10 | entropy(t: _Transaction) { 11 | return this.onValue.index!.entropy({ ...this.opDef, t }); 12 | } 13 | 14 | constructor(private onValue: IValue 15 | , private lo: any 16 | , private hi: any 17 | , private op: 'inside' | 'outside') { 18 | super(onValue.origin!); 19 | if (onValue.index!.expressions[0]?.hash !== onValue.hash) { 20 | throw new Error('Between index misuse'); 21 | } 22 | this.opDef = { 23 | type: op, 24 | hi: [hi], 25 | lo: [lo], 26 | t: null as any, 27 | } 28 | } 29 | 30 | hasItem(value: Row, t: _Transaction): boolean { 31 | const v = this.onValue.get(value, t); 32 | if (nullIsh(v)) { 33 | return false; 34 | } 35 | if (this.op === 'inside') { 36 | return !!this.onValue.type.ge(v, this.lo) 37 | && !!this.onValue.type.le(v, this.hi); 38 | } 39 | return !!this.onValue.type.lt(v, this.lo) 40 | || !!this.onValue.type.gt(v, this.lo); 41 | } 42 | 43 | enumerate(t: _Transaction): Iterable { 44 | return this.onValue.index!.enumerate({ ...this.opDef, t }); 45 | } 46 | 47 | 48 | stats(t: _Transaction): Stats | null { 49 | return null; 50 | } 51 | 52 | explain(e: _Explainer): _SelectExplanation { 53 | return { 54 | id: e.idFor(this), 55 | _: this.op, 56 | entropy: this.entropy(e.transaction), 57 | on: this.onValue.index!.explain(e), 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/transforms/distinct.ts: -------------------------------------------------------------------------------- 1 | import { Expr } from 'pgsql-ast-parser'; 2 | import { buildValue } from '../parser/expression-builder'; 3 | import { IValue, Row, Stats, _Explainer, _ISelection, _SelectExplanation, _Transaction } from '../interfaces-private'; 4 | import { FilterBase } from './transform-base'; 5 | import objectHash from 'object-hash'; 6 | import { withSelection } from '../parser/context'; 7 | 8 | export function buildDistinct(on: _ISelection, exprs?: Expr[]) { 9 | return withSelection(on, () => { 10 | const vals = exprs && exprs.length > 0 11 | ? exprs.map(v => buildValue(v)) 12 | : on.columns 13 | return new Distinct(on, vals); 14 | }); 15 | } 16 | 17 | 18 | // todo: use indices to optimize this (avoid iterating everything) 19 | 20 | class Distinct extends FilterBase { 21 | 22 | get index() { 23 | return null; 24 | } 25 | 26 | entropy(t: _Transaction) { 27 | // cant foresight how many items will be filtered 28 | // => just asumme nothing will be. 29 | return this.base.entropy(t); 30 | } 31 | 32 | hasItem(raw: Row, t: _Transaction): boolean { 33 | return this.base.hasItem(raw, t); 34 | } 35 | 36 | constructor(selection: _ISelection, private exprs: ReadonlyArray) { 37 | super(selection); 38 | } 39 | 40 | stats(t: _Transaction): Stats | null { 41 | return this.base.stats(t); 42 | } 43 | 44 | *enumerate(t: _Transaction): Iterable { 45 | const got = new Set(); 46 | for (const i of this.base.enumerate(t)) { 47 | const vals = this.exprs.map(v => v.type.hash(v.get(i, t))); 48 | const hash = vals.length === 1 ? vals[0] : objectHash(vals); 49 | if (got.has(hash)) { 50 | continue; 51 | } 52 | got.add(hash); 53 | yield i; 54 | } 55 | } 56 | 57 | explain(e: _Explainer): _SelectExplanation { 58 | return { 59 | id: e.idFor(this), 60 | _: 'distinct', 61 | of: this.base.explain(e), 62 | }; 63 | } 64 | } -------------------------------------------------------------------------------- /src/transforms/false-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _Explainer, _SelectExplanation, _Transaction, Stats, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | 4 | export class FalseFilter extends FilterBase { 5 | 6 | get index() { 7 | return null; 8 | } 9 | 10 | entropy() { 11 | return 0; 12 | } 13 | 14 | hasItem() { 15 | return false; 16 | } 17 | 18 | enumerate(): Iterable { 19 | return []; 20 | } 21 | 22 | stats(t: _Transaction): Stats | null { 23 | return { 24 | count: 0, 25 | } 26 | } 27 | 28 | explain(e: _Explainer): _SelectExplanation { 29 | return { 30 | id: e.idFor(this), 31 | _: 'empty', 32 | }; 33 | } 34 | } -------------------------------------------------------------------------------- /src/transforms/ineq-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _IIndex, _ITable, _Transaction, _Explainer, _SelectExplanation, IndexOp, Stats, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | import { nullIsh } from '../utils'; 4 | 5 | export class IneqFilter extends FilterBase { 6 | 7 | private index: _IIndex; 8 | private opDef: IndexOp; 9 | 10 | entropy(t: _Transaction) { 11 | return this.onValue.index!.entropy({ ...this.opDef, t }); 12 | } 13 | 14 | hasItem(item: Row, t: _Transaction) { 15 | const val = this.onValue.get(item, t); 16 | if (nullIsh(val)) { 17 | return false; 18 | } 19 | return !!this.onValue.type[this.op](val, this.than); 20 | } 21 | 22 | constructor(private onValue: IValue 23 | , private op: 'gt' | 'ge' | 'lt' | 'le' 24 | , private than: any) { 25 | super(onValue.origin!); 26 | 27 | this.index = this.onValue.index!; 28 | this.opDef = { 29 | type: op, 30 | key: [than], 31 | t: null as any, 32 | } 33 | } 34 | 35 | 36 | stats(t: _Transaction): Stats | null { 37 | return null; 38 | } 39 | 40 | *enumerate(t: _Transaction): Iterable { 41 | for (const item of this.index.enumerate({ ...this.opDef, t })) { 42 | if (!this.hasItem(item, t)) { 43 | break; 44 | } 45 | yield item; 46 | } 47 | } 48 | 49 | 50 | explain(e: _Explainer): _SelectExplanation { 51 | return { 52 | id: e.idFor(this), 53 | _: 'ineq', 54 | entropy: this.entropy(e.transaction), 55 | on: this.onValue.index!.explain(e), 56 | }; 57 | } 58 | } -------------------------------------------------------------------------------- /src/transforms/limit.ts: -------------------------------------------------------------------------------- 1 | import { IValue, _ISelection, _Transaction, _Explainer, _SelectExplanation, Stats, nil, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | import { LimitStatement } from 'pgsql-ast-parser'; 4 | import { buildValue } from '../parser/expression-builder'; 5 | import { withSelection } from '../parser/context'; 6 | 7 | export function buildLimit(on: _ISelection, limit: LimitStatement) { 8 | return withSelection(on, () => { 9 | const l = limit.limit && buildValue(limit.limit); 10 | const o = limit.offset && buildValue(limit.offset); 11 | return new LimitFilter(on, l, o); 12 | }); 13 | } 14 | 15 | class LimitFilter extends FilterBase { 16 | 17 | get index() { 18 | return null; 19 | } 20 | 21 | entropy(t: _Transaction) { 22 | return this.selection.entropy(t); 23 | } 24 | 25 | hasItem(raw: Row, t: _Transaction): boolean { 26 | return this.base.hasItem(raw, t); 27 | } 28 | 29 | constructor(private selection: _ISelection, private take: IValue | nil, private skip: IValue | nil) { 30 | super(selection); 31 | } 32 | 33 | 34 | stats(t: _Transaction): Stats | null { 35 | return null; 36 | } 37 | 38 | *enumerate(t: _Transaction): Iterable { 39 | let skip = this.skip?.get(null, t) ?? 0; 40 | let take = this.take?.get(null, t) ?? Number.MAX_SAFE_INTEGER; 41 | if (take <= 0) { 42 | return; 43 | } 44 | for (const raw of this.selection.enumerate(t)) { 45 | if (skip > 0) { 46 | skip--; 47 | continue; 48 | } 49 | yield raw; 50 | take--; 51 | if (!take) { 52 | return; 53 | } 54 | } 55 | } 56 | 57 | 58 | 59 | explain(e: _Explainer): _SelectExplanation { 60 | return { 61 | id: e.idFor(this), 62 | _: 'limit', 63 | take: this.take?.explain(e), 64 | skip: this.skip?.explain(e), 65 | on: this.selection.explain(e), 66 | }; 67 | } 68 | } -------------------------------------------------------------------------------- /src/transforms/or-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, _IIndex, _ITable, getId, _Transaction, _Explainer, _SelectExplanation, Stats, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | 4 | 5 | export class OrFilter extends FilterBase { 6 | 7 | entropy(t: _Transaction) { 8 | return this.left.entropy(t) + this.right.entropy(t); 9 | } 10 | 11 | hasItem(value: Row, t: _Transaction): boolean { 12 | return this.left.hasItem(value, t) || this.right.hasItem(value, t); 13 | } 14 | 15 | constructor(private left: _ISelection, private right: _ISelection) { 16 | super(left); 17 | if (left.columns !== right.columns) { // istanbul ignore next 18 | throw new Error('Column set mismatch'); 19 | } 20 | } 21 | 22 | stats(t: _Transaction): Stats | null { 23 | return null; 24 | } 25 | 26 | *enumerate(t: _Transaction): Iterable { 27 | const yielded = new Set(); 28 | for (const item of this.left.enumerate(t)) { 29 | yield item; 30 | yielded.add(getId(item)); 31 | } 32 | for (const item of this.right.enumerate(t)) { 33 | const id = getId(item); 34 | if (!yielded.has(id)) { 35 | yield item; 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | explain(e: _Explainer): _SelectExplanation { 43 | return { 44 | id: e.idFor(this), 45 | _: 'union', 46 | union: [ 47 | this.left.explain(e), 48 | this.right.explain(e), 49 | ], 50 | }; 51 | } 52 | } -------------------------------------------------------------------------------- /src/transforms/restrictive-index.ts: -------------------------------------------------------------------------------- 1 | import { _IIndex, IValue, IndexExpression, _Transaction, IndexKey, _Explainer, _IndexExplanation, IndexOp, _ISelection, Stats, Row } from '../interfaces-private'; 2 | 3 | export class RestrictiveIndex implements _IIndex { 4 | constructor(private base: _IIndex, readonly filter: _ISelection) { 5 | } 6 | 7 | private match(raw: Row, t: _Transaction) { 8 | return this.filter.hasItem(raw, t); 9 | } 10 | 11 | get expressions(): IndexExpression[] { 12 | return this.base.expressions; 13 | } 14 | 15 | stats(t: _Transaction, key?: IndexKey): Stats | null { 16 | // cannot comput without iterating 17 | return null; 18 | } 19 | 20 | iterateKeys() { 21 | // cannot comput without iterating 22 | // (we know underlying keys, but we dont know which have items that match our filter) 23 | return null; 24 | } 25 | 26 | eqFirst(rawKey: IndexKey, t: _Transaction) { 27 | for (const i of this.base.enumerate({ 28 | key: rawKey, 29 | t: t, 30 | type: 'eq', 31 | })) { 32 | if (this.match(i, t)) { 33 | return i; 34 | } 35 | } 36 | return null; 37 | } 38 | 39 | 40 | entropy(t: IndexOp): number { 41 | return this.base.entropy(t); 42 | } 43 | 44 | *enumerate(op: IndexOp): Iterable { 45 | for (const i of this.base.enumerate(op)) { 46 | if (this.match(i, op.t)) { 47 | yield i; 48 | } 49 | } 50 | } 51 | 52 | explain(e: _Explainer): _IndexExplanation { 53 | return { 54 | _: 'indexRestriction', 55 | lookup: this.base.explain(e), 56 | for: this.filter.explain(e), 57 | // criteria: this.restrict.explain(e), 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/transforms/seq-scan.ts: -------------------------------------------------------------------------------- 1 | import { IValue, _ISelection, _Transaction, _Explainer, _SelectExplanation, Stats, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | import { Types } from '../datatypes'; 4 | 5 | export class SeqScanFilter extends FilterBase { 6 | 7 | get index() { 8 | return null; 9 | } 10 | 11 | entropy(t: _Transaction) { 12 | // boost source entropy (in case an index has the same items count) 13 | return this.selection.entropy(t) * 1.5; 14 | } 15 | 16 | hasItem(raw: Row, t: _Transaction): boolean { 17 | return !!this.getter.get(raw, t); 18 | } 19 | 20 | constructor(private selection: _ISelection, private getter: IValue) { 21 | super(selection); 22 | this.getter = getter.cast(Types.bool); 23 | } 24 | 25 | 26 | stats(t: _Transaction): Stats | null { 27 | return null; 28 | } 29 | 30 | *enumerate(t: _Transaction): Iterable { 31 | for (const raw of this.selection.enumerate(t)) { 32 | const cond = this.getter.get(raw, t); 33 | if (cond) { 34 | yield raw; 35 | } 36 | } 37 | } 38 | 39 | explain(e: _Explainer): _SelectExplanation { 40 | return { 41 | id: e.idFor(this), 42 | _: 'seqFilter', 43 | filtered: this.selection.explain(e), 44 | }; 45 | } 46 | } -------------------------------------------------------------------------------- /src/transforms/startswith-filter.ts: -------------------------------------------------------------------------------- 1 | import { _ISelection, IValue, _IIndex, _ITable, getId, _Transaction, _Explainer, _SelectExplanation, Stats, Row } from '../interfaces-private'; 2 | import { FilterBase } from './transform-base'; 3 | import { nullIsh } from '../utils'; 4 | 5 | export class StartsWithFilter extends FilterBase { 6 | 7 | get index() { 8 | return null; 9 | } 10 | 11 | entropy(t: _Transaction) { 12 | return this.onValue.index!.entropy({ 13 | type: 'ge', 14 | key: [this.startWith], 15 | t, 16 | }); 17 | } 18 | 19 | hasItem(item: Row, t: _Transaction) { 20 | const get = this.onValue.get(item, t); 21 | return typeof get === 'string' 22 | && get.startsWith(this.startWith); 23 | } 24 | 25 | constructor(private onValue: IValue 26 | , private startWith: string) { 27 | super(onValue.origin!); 28 | if (onValue.index!.expressions[0].hash !== this.onValue.hash) { 29 | throw new Error('Startwith must be the first component of the index'); 30 | } 31 | } 32 | 33 | 34 | stats(t: _Transaction): Stats | null { 35 | return null; 36 | } 37 | 38 | *enumerate(t: _Transaction): Iterable { 39 | const index = this.onValue.index!; 40 | for (const item of index.enumerate({ 41 | type: 'ge', 42 | key: [this.startWith], 43 | t, 44 | })) { 45 | const got: string = this.onValue.get(item, t); 46 | if (nullIsh(got) || !got.startsWith(this.startWith)) { 47 | break; 48 | } 49 | yield item; 50 | } 51 | } 52 | 53 | explain(e: _Explainer): _SelectExplanation { 54 | return { 55 | id: e.idFor(this), 56 | _: 'ineq', 57 | entropy: this.entropy(e.transaction), 58 | on: this.onValue.index!.explain(e), 59 | }; 60 | } 61 | } -------------------------------------------------------------------------------- /tools/deno-test.ts: -------------------------------------------------------------------------------- 1 | import { newDb } from '../.deno/mod.ts'; 2 | 3 | const db = newDb(); 4 | 5 | db.public.none(`create table test(id text); 6 | insert into test values ('value');`); 7 | // create a restore point & mess with data 8 | const backup = db.backup(); 9 | db.public.none(`update test set id='new value';`) 10 | // restore it ! 11 | backup.restore(); 12 | console.log(db.public.many(`select * from test`)) // => {test: 'value'} -------------------------------------------------------------------------------- /tsconfig.deno.json: -------------------------------------------------------------------------------- 1 | { 2 | // === THIS IS THE DEFAULT DENO CONFIGURATION : https://deno.land/manual/getting_started/typescript 3 | "compilerOptions": { 4 | "allowJs": false, 5 | "allowUmdGlobalAccess": false, 6 | "allowUnreachableCode": false, 7 | "allowUnusedLabels": false, 8 | "alwaysStrict": true, 9 | "assumeChangesOnlyAffectDirectDependencies": false, 10 | "checkJs": false, 11 | "disableSizeLimit": false, 12 | "generateCpuProfile": "profile.cpuprofile", 13 | "jsx": "react", 14 | "jsxFactory": "React.createElement", 15 | "lib": [], 16 | "noFallthroughCasesInSwitch": false, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": true, 20 | "noImplicitUseStrict": false, 21 | "noStrictGenericChecks": false, 22 | "noUnusedLocals": false, 23 | "noUnusedParameters": false, 24 | "preserveConstEnums": true, 25 | "removeComments": false, 26 | "resolveJsonModule": true, 27 | "strict": true, 28 | "strictBindCallApply": true, 29 | "strictFunctionTypes": true, 30 | "strictNullChecks": true, 31 | "strictPropertyInitialization": true, 32 | "suppressExcessPropertyErrors": false, 33 | "suppressImplicitAnyIndexErrors": false, 34 | "useDefineForClassFields": false 35 | } 36 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.deno.json", 3 | "compilerOptions": { 4 | "target": "ES2019", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "types": ["node", "bun-types"], 10 | "lib": ["es2022"], 11 | "skipLibCheck": true, 12 | "moduleResolution": "node", 13 | "isolatedModules": false, 14 | "jsx": "react", 15 | "declaration": true, 16 | "declarationMap": true, 17 | "declarationDir": "lib-types", 18 | "outDir": "lib-types", 19 | }, 20 | "include": [ 21 | "src/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------