Skip to content

Commit

Permalink
fix cast to varchar (regression #412)
Browse files Browse the repository at this point in the history
  • Loading branch information
oguimbal committed Oct 11, 2024
1 parent 3461bd4 commit 8df81cd
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 26 deletions.
53 changes: 32 additions & 21 deletions src/datatypes/datatypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,27 @@ class TextType extends TypeBase<string> {
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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ export interface _IType<TRaw = any> extends IType, _RelationBase {
canCast(to: _IType<TRaw>): boolean | nil;
cast<T = any>(value: IValue<TRaw>, to: _IType<T>): IValue;
convertImplicit<T = any>(value: IValue<TRaw>, to: _IType<T>): IValue;
prefer(type: _IType<any>): _IType | nil;
prefer(type: _IType<any>, stricterType?: boolean): _IType | nil;

/** Build an array type for this type */
asArray(): _IType<TRaw[]>;
Expand Down
12 changes: 12 additions & 0 deletions src/tests/select.queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,18 @@ describe('Selections', () => {
.toEqual([{ a: 'a', b: 'a' }])
})

it('can have a string in condition that is too long for datatype on =', () => {
// this was throwing, see https://github.com/oguimbal/pg-mem/issues/412
none(`create table repro (id varchar(1));
select * from repro where id = 'longerstring';`);
})

it('can have a string in condition that is too long for datatype on IN', () => {
// this was throwing, see https://github.com/oguimbal/pg-mem/issues/412
none(`create table repro (id varchar(1));
select * from repro where id in ('longerstring');`);
})

it('can select from function with alias', () => {
expect(many(`select * from concat('a') as a join concat('a') as b on a.a=b.b`))
.toEqual([{ a: 'a', b: 'a' }])
Expand Down
8 changes: 5 additions & 3 deletions src/transforms/build-filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { _ISelection, CastError, DataType, NotSupported, IValue } from '../interfaces-private';
import { buildValue } from '../parser/expression-builder';
import { Types, ArrayType } from '../datatypes';
import { Types, ArrayType, reconciliateTypes } from '../datatypes';
import { EqFilter } from './eq-filter';
import { Value } from '../evaluator';
import { FalseFilter } from './false-filter';
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/transforms/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '<unknown col>'}) and ${r.type.name} (${r.id ?? '<unknown col>'}) cannot be matched`);
}
Expand Down

0 comments on commit 8df81cd

Please sign in to comment.