Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Notable changes to `@tinbase/pg-mem`, the tinbase fork of pg-mem.

Released from `main`, which carries the scoped package name. Upstream is tracked through the `upstream` remote (`oguimbal/pg-mem`) rather than a branch; the leftover `master` is vestigial.

## 4.0.1

- Comparisons across type categories fail with Postgres' own error, `operator does not exist: uuid = text` (was `cannot cast type text to uuid`), types spelled as Postgres spells them. Also for `x IN (select col ...)`. The SQL that is rejected does not change.

## 4.0.0

Postgres parity for validating real Supabase migrations: pg-mem now rejects what Postgres rejects, rolls back schema changes, and enforces row-level security on every read and write path. Found by running 250 real RapidNative projects' migrations and seeds through pg-mem and PGlite side by side (`tools/corpus-diff`); all 5 sets of 50 now match. Requires `@tinbase/pgsql-ast-parser` 12.2.0.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tinbase/pg-mem",
"version": "4.0.0",
"version": "4.0.1",
"description": "Fork of pg-mem with extended Postgres conformance (PL/pgSQL, triggers, RLS, correlated subqueries, MERGE, ranges, full-text, partitioning, ...). Tracks oguimbal/pg-mem; pending upstream PR #476.",
"main": "index.js",
"publishConfig": {
Expand Down
22 changes: 21 additions & 1 deletion src/parser/expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ function buildIn(left: Expr, array: Expr, inclusive: boolean): IValue {
const col = (rightValue.type as ArrayType).of;
const leftUnknown = leftValue.isConstantLiteral && leftValue.type.primary === DataType.text;
if (col && !leftUnknown && crossesTypeCategory(leftValue.type, col)) {
throw new QueryError(`operator does not exist: ${leftValue.type.name} = ${col.name}`, '42883');
throw new QueryError(`operator does not exist: ${pgTypeName(leftValue.type)} = ${pgTypeName(col)}`, '42883');
}
}
if (array.type !== 'list' && rightValue.type.primary !== DataType.list && rightValue.type.primary !== DataType.array) {
Expand All @@ -405,6 +405,19 @@ function buildIn(left: Expr, array: Expr, inclusive: boolean): IValue {
}


/** a type as postgres spells it in error messages (format_type) */
function pgTypeName(t: _IType): string {
switch (t.primary) {
case DataType.bool: return 'boolean';
case DataType.float: return 'double precision';
case DataType.decimal: return 'numeric';
case DataType.timestamptz: return 'timestamp with time zone';
case DataType.timestamp: return 'timestamp without time zone';
case DataType.time: return 'time without time zone';
}
return t.name;
}

function buildBinary(val: ExprBinary): IValue {
let leftValue = _buildValue(val.left);
let rightValue = _buildValue(val.right);
Expand Down Expand Up @@ -437,6 +450,13 @@ export function buildBinaryValue(leftValue: IValue, op: BinaryOperator, rightVal
rightValue = doMap(rightValue);
}
}
// two typed operands from different type categories: postgres has no such operator, and
// says so ("operator does not exist: uuid = text") - which is also what tells the author
// which side to cast
const unknown = (v: IValue) => v.isConstantLiteral && v.type.primary === DataType.text;
if (!unknown(leftValue) && !unknown(rightValue) && crossesTypeCategory(leftValue.type, rightValue.type)) {
throw new QueryError(`operator does not exist: ${pgTypeName(leftValue.type)} ${op} ${pgTypeName(rightValue.type)}`, '42883');
}
const type: _IType = reconciliateTypes([leftValue, rightValue]);
leftValue = leftValue.cast(type);
rightValue = rightValue.cast(type);
Expand Down
7 changes: 7 additions & 0 deletions src/tests/corpus-parity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ describe('corpus parity', () => {
}
});

it('says which operator does not exist, as postgres does', () => {
expectQueryError(() => many(`select 1 from t where u = s`), /operator does not exist: uuid = text/);
expectQueryError(() => many(`select 1 from t where b = s`), /operator does not exist: boolean = text/);
expectQueryError(() => many(`select 1 from t where s = i`), /operator does not exist: text = integer/);
expectQueryError(() => many(`select 1 from t where u in (select s from t)`), /operator does not exist: uuid = text/);
});

it('still coerces untyped literals and widens within a category', () => {
for (const e of [`u = '00000000-0000-0000-0000-000000000001'`, `i = '1'`, 'i = bi', 'bi = i', 'i = n', 'd = ts', 'ts > d',
'd = now()', 's = v', 'u::text = s', `s || i = 'x1'`, `j ? s`, `concat(u, 'x') = s`]) {
Expand Down
Loading