diff --git a/.deno/column.ts b/.deno/column.ts index 24da54f..552409a 100644 --- a/.deno/column.ts +++ b/.deno/column.ts @@ -128,48 +128,50 @@ export class ColRef implements _Column { } alter(alter: AlterColumn, t: _Transaction): this { - switch (alter.type) { - case 'drop default': - this.default = null; - break; - case 'set default': - if (alter.default.type === 'null') { + withSelection(this.table.selection, () => { + switch (alter.type) { + case 'drop default': this.default = null; break; - } - const df = withSelection(this.table.selection, () => buildValue(alter.default)); - if (!df.isConstant) { - throw new QueryError('cannot use column references in default expression'); - } - if (alter.updateExisting) { - const defVal = df.get(); - this.table.remapData(t, x => x[this.expression.id!] = defVal); - } - this.default = df; - break; - case 'set not null': - this.addNotNullConstraint(t); - break; - case 'drop not null': - this.notNull = false; - break; - case 'set type': - const newType = this.table.ownerSchema.getType(alter.dataType); - const conv = this.expression.cast(newType); - const eid = this.expression.id; - - this.table.remapData(t, x => x[this.expression.id!] = conv.get(x, t)); - - // once converted, do nasty things to change expression - this.replaceExpression(eid!, newType); - break; - case 'add generated': - new GeneratedIdentityConstraint(alter.constraintName?.name, this) - .install(t, alter); - break; - default: - throw NotSupported.never(alter, 'alter column type'); - } + case 'set default': + if (alter.default.type === 'null') { + this.default = null; + break; + } + const df = buildValue(alter.default); + if (!df.isConstant) { + throw new QueryError('cannot use column references in default expression'); + } + if (alter.updateExisting) { + const defVal = df.get(); + this.table.remapData(t, x => x[this.expression.id!] = defVal); + } + this.default = df; + break; + case 'set not null': + this.addNotNullConstraint(t); + break; + case 'drop not null': + this.notNull = false; + break; + case 'set type': + const newType = this.table.ownerSchema.getType(alter.dataType); + const conv = this.expression.cast(newType); + const eid = this.expression.id; + + this.table.remapData(t, x => x[this.expression.id!] = conv.get(x, t)); + + // once converted, do nasty things to change expression + this.replaceExpression(eid!, newType); + break; + case 'add generated': + new GeneratedIdentityConstraint(alter.constraintName?.name, this) + .install(t, alter); + break; + default: + throw NotSupported.never(alter, 'alter column type'); + } + }); this.table.db.onSchemaChange(); this.table.selection.rebuild(); return this; diff --git a/.deno/datatypes/datatype-base.ts b/.deno/datatypes/datatype-base.ts index 965c25f..a7cded6 100644 --- a/.deno/datatypes/datatype-base.ts +++ b/.deno/datatypes/datatype-base.ts @@ -46,6 +46,9 @@ export abstract class TypeBase implements _IType, _RelationBas get name(): string { return this.primary; } + get primaryName(): string { + return this.primary; + } /** Compute a custom unicty hash for a non null value */ diff --git a/.deno/datatypes/datatypes.ts b/.deno/datatypes/datatypes.ts index ede306b..0df3095 100644 --- a/.deno/datatypes/datatypes.ts +++ b/.deno/datatypes/datatypes.ts @@ -254,16 +254,27 @@ class TextType extends TypeBase { super(25); } - doPrefer(to: _IType) { + doPrefer(to: _IType, stricterType?: boolean) { if (to instanceof TextType) { - // returns the broader type - if (!to.len) { - return to; - } - if (!this.len) { - return this; + if (stricterType) { + // returns the stricter type + if (!to.len) { + return to; + } + if (!this.len) { + return this; + } + return to.len > this.len ? to : this; + } else { + // returns the broader type + if (!to.len) { + return to; + } + if (!this.len) { + return this; + } + return to.len < this.len ? to : this; } - return to.len > this.len ? to : this; } if (this.canCast(to)) { return to; @@ -744,10 +755,10 @@ export const typeSynonyms: { [key: string]: DataType | { type: DataType; ignoreC /** Finds a common type by implicit conversion */ -export function reconciliateTypes(values: IValue[], nullIfNoMatch?: false): _IType; -export function reconciliateTypes(values: IValue[], nullIfNoMatch: true): _IType | nil; -export function reconciliateTypes(values: IValue[], nullIfNoMatch?: boolean): _IType | nil -export function reconciliateTypes(values: IValue[], nullIfNoMatch?: boolean): _IType | nil { +export function reconciliateTypes(values: IValue[], nullIfNoMatch?: false, stricterType?: boolean): _IType; +export function reconciliateTypes(values: IValue[], nullIfNoMatch: true, stricterType?: boolean): _IType | nil; +export function reconciliateTypes(values: IValue[], nullIfNoMatch?: boolean, stricterType?: boolean): _IType | nil +export function reconciliateTypes(values: IValue[], nullIfNoMatch?: boolean, stricterType?: boolean): _IType | nil { // FROM https://www.postgresql.org/docs/current/typeconv-union-case.html const nonNull = values @@ -761,27 +772,27 @@ export function reconciliateTypes(values: IValue[], nullIfNoMatch?: boolean): _I // If all inputs are of the same type, and it is not unknown, resolve as that type. const single = new Set(nonNull .map(v => v.type.reg.typeId)); - if (single.size === 1) { - return nonNull[0].type; - } + // if (single.size === 1) { + // return nonNull[0].type; + // } - return reconciliateTypesRaw(nonNull, nullIfNoMatch); + return reconciliateTypesRaw(nonNull, nullIfNoMatch, stricterType); } /** Finds a common type by implicit conversion */ -function reconciliateTypesRaw(values: IValue[], nullIfNoMatch?: false): _IType; -function reconciliateTypesRaw(values: IValue[], nullIfNoMatch: true): _IType | nil; -function reconciliateTypesRaw(values: IValue[], nullIfNoMatch?: boolean): _IType | nil -function reconciliateTypesRaw(values: IValue[], nullIfNoMatch?: boolean): _IType | nil { +function reconciliateTypesRaw(values: IValue[], nullIfNoMatch?: false, stricterType?: boolean): _IType; +function reconciliateTypesRaw(values: IValue[], nullIfNoMatch: true, stricterType?: boolean): _IType | nil; +function reconciliateTypesRaw(values: IValue[], nullIfNoMatch?: boolean, stricterType?: boolean): _IType | nil +function reconciliateTypesRaw(values: IValue[], nullIfNoMatch?: boolean, stricterType?: boolean): _IType | nil { // find the matching type among non constants const foundType = values .reduce((final, c) => { if (c.type === Types.null) { return final; } - const pref = final.prefer(c.type); + const pref = final.prefer(c.type, stricterType); if (!pref) { throw new CastError(c.type.primary, final.primary, c.id ?? undefined); } diff --git a/.deno/datatypes/t-equivalent.ts b/.deno/datatypes/t-equivalent.ts index 23f2682..d33fd06 100644 --- a/.deno/datatypes/t-equivalent.ts +++ b/.deno/datatypes/t-equivalent.ts @@ -6,7 +6,7 @@ import { Types } from './datatypes.ts'; export class EquivalentType extends TypeBase { - private equiv: IType; + private equiv: _IType; constructor(private def: IEquivalentType) { super(null); @@ -17,7 +17,7 @@ export class EquivalentType extends TypeBase { } this.equiv = eq; } else { - this.equiv = def.equivalentTo; + this.equiv = def.equivalentTo as _IType; } if (!this.equiv) { @@ -26,7 +26,15 @@ export class EquivalentType extends TypeBase { } get primary(): DataType { - return this.def.name as any; + return this.equiv.primary; + } + + get primaryName(): string { + return this.def.name; + } + + get name(): string { + return this.def.name; } doCanCast(to: _IType) { @@ -42,7 +50,8 @@ export class EquivalentType extends TypeBase { } doCanBuildFrom(from: _IType): boolean | nil { - return from.primary === this.equiv.primary; + // return from.canCast(this.equiv); + return this.equiv.canCast(from); } doBuildFrom(value: Evaluator, from: _IType): Evaluator | nil { diff --git a/.deno/functions/index.ts b/.deno/functions/index.ts index 2646794..7e051c8 100644 --- a/.deno/functions/index.ts +++ b/.deno/functions/index.ts @@ -3,6 +3,7 @@ import { dateFunctions } from './date.ts'; import { systemFunctions } from './system.ts'; import { sequenceFunctions } from './sequence-fns.ts'; import { numberFunctions } from './numbers.ts'; +import { subqueryFunctions } from './subquery.ts'; export const allFunctions = [ @@ -11,4 +12,5 @@ export const allFunctions = [ , ... systemFunctions , ... sequenceFunctions , ... numberFunctions -] \ No newline at end of file + , ... subqueryFunctions +] diff --git a/.deno/functions/subquery.ts b/.deno/functions/subquery.ts new file mode 100644 index 0000000..d81abd2 --- /dev/null +++ b/.deno/functions/subquery.ts @@ -0,0 +1,14 @@ +import { FunctionDefinition } from '../interfaces.ts'; +import { DataType } from '../interfaces-private.ts'; + +export const subqueryFunctions: FunctionDefinition[] = [ + { + name: 'exists', + args: [DataType.integer], + argsVariadic: DataType.integer, + returns: DataType.bool, + allowNullArguments: true, + impure: true, + implementation: (...items: number[]) => items?.some?.(Boolean) ?? false, + }, +]; diff --git a/.deno/functions/system.ts b/.deno/functions/system.ts index c62d8b2..8f5429d 100644 --- a/.deno/functions/system.ts +++ b/.deno/functions/system.ts @@ -8,4 +8,10 @@ export const systemFunctions: FunctionDefinition[] = [ returns: Types.text(), implementation: () => 'public', }, + { + name: 'obj_description', + args: [Types.regclass, Types.text()], + returns: Types.null, + implementation: () => null + }, ] diff --git a/.deno/interfaces-private.ts b/.deno/interfaces-private.ts index d29e209..74b3356 100644 --- a/.deno/interfaces-private.ts +++ b/.deno/interfaces-private.ts @@ -502,6 +502,7 @@ export interface _IType extends IType, _RelationBase { readonly type: 'type'; /** Data type */ readonly primary: DataType; + readonly primaryName: string; /** Reg type name */ readonly name: string; // | null; readonly reg: Reg; @@ -516,7 +517,7 @@ export interface _IType extends IType, _RelationBase { canCast(to: _IType): boolean | nil; cast(value: IValue, to: _IType): IValue; convertImplicit(value: IValue, to: _IType): IValue; - prefer(type: _IType): _IType | nil; + prefer(type: _IType, stricterType?: boolean): _IType | nil; /** Build an array type for this type */ asArray(): _IType; diff --git a/.deno/interfaces.ts b/.deno/interfaces.ts index c028e4b..dd088ce 100644 --- a/.deno/interfaces.ts +++ b/.deno/interfaces.ts @@ -342,6 +342,9 @@ export interface ISchema { /** Register a simple type, which is equivalent to another */ registerEquivalentType(type: IEquivalentType): IType; + /** Register a simple type, which is equivalent to another */ + registerEquivalentSizableType(type: IEquivalentType): IType; + /** Get an existing type */ getType(name: DataType): IType; diff --git a/.deno/schema/pg-catalog/index.ts b/.deno/schema/pg-catalog/index.ts index c0eab0c..9b6409b 100644 --- a/.deno/schema/pg-catalog/index.ts +++ b/.deno/schema/pg-catalog/index.ts @@ -3,9 +3,12 @@ import { DataType, FunctionDefinition, _IDb, _ISchema } from '../../interfaces-p import { PgAttributeTable } from './pg-attribute-list.ts'; import { PgClassListTable } from './pg-class.ts'; import { PgConstraintTable } from './pg-constraints-list.ts'; +import { PgEnumTable } from './pg-enum-list.ts'; import { PgIndexTable } from './pg-index-list.ts'; import { PgNamespaceTable } from './pg-namespace-list.ts'; +import { PgSequencesTable } from './pg-sequences-list.ts'; import { PgTypeTable } from './pg-type-list.ts'; +import { PgUserTable } from './pg-user-list.ts'; import { allFunctions } from '../../functions/index.ts'; import { PgRange } from './pg-range.ts'; import { sqlSubstring } from '../../parser/expression-builder.ts'; @@ -58,6 +61,9 @@ export function setupPgCatalog(db: _IDb) { new PgProc(catalog).register(); new PgDatabaseTable(catalog).register(); new PgStatioUserTables(catalog).register(); + new PgEnumTable(catalog).register(); + new PgSequencesTable(catalog).register(); + new PgUserTable(catalog).register(); // this is an ugly hack... diff --git a/.deno/schema/pg-catalog/pg-enum-list.ts b/.deno/schema/pg-catalog/pg-enum-list.ts new file mode 100644 index 0000000..07ba76e --- /dev/null +++ b/.deno/schema/pg-catalog/pg-enum-list.ts @@ -0,0 +1,28 @@ +import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private.ts'; +import { Schema } from '../../interfaces.ts'; +import { Types } from '../../datatypes/index.ts'; +import { ReadOnlyTable } from '../readonly-table.ts'; + +export class PgEnumTable extends ReadOnlyTable implements _ITable { + + _schema: Schema = { + name: 'pg_enum', + fields: [ + { name: 'oid', type: Types.integer } + , { name: 'enumtypid', type: Types.integer } + , { name: 'enumsortorder', type: Types.integer } + , { name: 'enumlabel', type: Types.text() } + ] + }; + + entropy(): number { + return 0; + } + + *enumerate() { + } + + hasItem(value: any): boolean { + return false; + } +} diff --git a/.deno/schema/pg-catalog/pg-sequences-list.ts b/.deno/schema/pg-catalog/pg-sequences-list.ts new file mode 100644 index 0000000..64ac095 --- /dev/null +++ b/.deno/schema/pg-catalog/pg-sequences-list.ts @@ -0,0 +1,35 @@ +import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private.ts'; +import { Schema } from '../../interfaces.ts'; +import { Types } from '../../datatypes/index.ts'; +import { ReadOnlyTable } from '../readonly-table.ts'; + +export class PgSequencesTable extends ReadOnlyTable implements _ITable { + + _schema: Schema = { + name: 'pg_sequences', + fields: [ + { name: 'schemaname', type: Types.text() } + , { name: 'sequencename', type: Types.text() } + , { name: 'sequenceowner', type: Types.integer } + , { name: 'data_type', type: Types.text() } + , { name: 'start_value', type: Types.integer } + , { name: 'min_value', type: Types.integer } + , { name: 'max_value', type: Types.integer } + , { name: 'increment_by', type: Types.integer } + , { name: 'cycle', type: Types.bool } + , { name: 'cache_size', type: Types.integer } + , { name: 'last_value', type: Types.integer } + ] + }; + + entropy(): number { + return 0; + } + + *enumerate() { + } + + hasItem(value: any): boolean { + return false; + } +} diff --git a/.deno/schema/pg-catalog/pg-user-list.ts b/.deno/schema/pg-catalog/pg-user-list.ts new file mode 100644 index 0000000..b9390d9 --- /dev/null +++ b/.deno/schema/pg-catalog/pg-user-list.ts @@ -0,0 +1,34 @@ +import { _ITable, _ISelection, _IIndex, _IDb, _ISchema } from '../../interfaces-private.ts'; +import { Schema } from '../../interfaces.ts'; +import { Types } from '../../datatypes/index.ts'; +import { ReadOnlyTable } from '../readonly-table.ts'; + +export class PgUserTable extends ReadOnlyTable implements _ITable { + + _schema: Schema = { + name: 'pg_user', + fields: [ + { name: 'usename', type: Types.text() } + , { name: 'usesysid', type: Types.integer } + , { name: 'usecreatedb', type: Types.bool } + , { name: 'usesuper', type: Types.bool } + , { name: 'usecatupd', type: Types.bool } + , { name: 'userepl', type: Types.bool } + , { name: 'usebypassrls', type: Types.bool } + , { name: 'passwd', type: Types.text() } + , { name: 'valuntil', type: Types.timestamptz() } + , { name: 'useconfig', type: Types.jsonb } + ] + }; + + entropy(): number { + return 0; + } + + *enumerate() { + } + + hasItem(value: any): boolean { + return false; + } +} diff --git a/.deno/schema/schema.ts b/.deno/schema/schema.ts index 29d1b92..4b104be 100644 --- a/.deno/schema/schema.ts +++ b/.deno/schema/schema.ts @@ -394,6 +394,14 @@ export class DbSchema implements _ISchema, ISchema { registerEquivalentType(type: IEquivalentType): IType { const ret = new EquivalentType(type); this._registerType(ret); + + return ret; + } + + registerEquivalentSizableType(type: IEquivalentType): IType { + const ret = new EquivalentType(type); + this._registerTypeSizeable(ret.primaryName, (_) => ret); + return ret; } @@ -409,10 +417,10 @@ export class DbSchema implements _ISchema, ISchema { } _registerType(type: _IType): this { - if (this.simpleTypes[type.primary] || this.sizeableTypes[type.primary] || this.getOwnObject(type.primary)) { - throw new QueryError(`type "${type.primary}" already exists`); + if (this.simpleTypes[type.primaryName] || this.sizeableTypes[type.primaryName] || this.getOwnObject(type.primaryName)) { + throw new QueryError(`type "${type.primaryName}" already exists`); } - this.simpleTypes[type.primary] = type; + this.simpleTypes[type.primaryName] = type; this._reg_register(type); return this; } diff --git a/.deno/transforms/build-filter.ts b/.deno/transforms/build-filter.ts index 4bf2348..56d5ca8 100644 --- a/.deno/transforms/build-filter.ts +++ b/.deno/transforms/build-filter.ts @@ -1,6 +1,6 @@ import { _ISelection, CastError, DataType, NotSupported, IValue } from '../interfaces-private.ts'; import { buildValue } from '../parser/expression-builder.ts'; -import { Types, ArrayType } from '../datatypes/index.ts'; +import { Types, ArrayType, reconciliateTypes } from '../datatypes/index.ts'; import { EqFilter } from './eq-filter.ts'; import { Value } from '../evaluator.ts'; import { FalseFilter } from './false-filter.ts'; @@ -174,9 +174,11 @@ function buildComparison(this: void, on: _ISelection, filter: ExprBinary): _ISel } if (rightValue.isConstant) { - rightValue = rightValue.cast(leftValue.type); + const reconcilied = reconciliateTypes([leftValue, rightValue]); + rightValue = rightValue.cast(reconcilied); } else if (leftValue.isConstant) { - leftValue = leftValue.cast(rightValue.type); + const reconcilied = reconciliateTypes([leftValue, rightValue]); + leftValue = leftValue.cast(reconcilied); } switch (op) { diff --git a/.deno/transforms/union.ts b/.deno/transforms/union.ts index 3dd3037..b0570cd 100644 --- a/.deno/transforms/union.ts +++ b/.deno/transforms/union.ts @@ -16,7 +16,7 @@ export function buildUnion(left: _ISelection, right: _ISelection) { const l = left.columns[i]; const r = right.columns[i]; - const type = reconciliateTypes([l, r], true); + const type = reconciliateTypes([l, r], true, true); if (!type) { throw new QueryError(`UNION types ${l.type.name} (${l.id ?? ''}) and ${r.type.name} (${r.id ?? ''}) cannot be matched`); }