Skip to content

Commit

Permalink
Implement proper array-like SQL record/row compatible with 6b1
Browse files Browse the repository at this point in the history
  • Loading branch information
1st1 committed Dec 14, 2024
1 parent d153164 commit 6d482a2
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 7 deletions.
3 changes: 2 additions & 1 deletion packages/driver/src/codecs/ifaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export type CodecKind =
| "scalar"
| "sparse_object"
| "range"
| "multirange";
| "multirange"
| "record";

export interface ICodec {
readonly tid: uuid;
Expand Down
3 changes: 2 additions & 1 deletion packages/driver/src/codecs/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export class ObjectCodec extends Codec implements ICodec {
}

encodeArgs(args: any): Uint8Array {
if (this.fields[0].name === "0") {
// EdgeQL query parameters start at 0, SQL start at 1.
if (this.fields[0].name === "0" || this.fields[0].name === "1") {
return this._encodePositionalArgs(args);
}
return this._encodeNamedArgs(args);
Expand Down
84 changes: 84 additions & 0 deletions packages/driver/src/codecs/record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*!
* This source file is part of the EdgeDB open source project.
*
* Copyright 2019-present MagicStack Inc. and the EdgeDB authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { ICodec, uuid, CodecKind } from "./ifaces";
import { Codec } from "./ifaces";
import { ReadBuffer, WriteBuffer } from "../primitives/buffer";

Check failure on line 21 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Imports "WriteBuffer" are only used as type

Check failure on line 21 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Imports "WriteBuffer" are only used as type

Check failure on line 21 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (22, ubuntu-latest, stable)

Imports "WriteBuffer" are only used as type

Check failure on line 21 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, nightly)

Imports "WriteBuffer" are only used as type

Check failure on line 21 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, 3)

Imports "WriteBuffer" are only used as type

Check failure on line 21 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, 2)

Imports "WriteBuffer" are only used as type
import {
InvalidArgumentError,
ProtocolError,
} from "../errors";

export class RecordCodec extends Codec implements ICodec {
private subCodecs: ICodec[];
private names: string[];

constructor(
tid: uuid,
codecs: ICodec[],
names: string[],
) {
super(tid);
this.subCodecs = codecs;
this.names = names;
}

encode(buf: WriteBuffer, object: any): void {

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

'buf' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

'object' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

'buf' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

'object' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (22, ubuntu-latest, stable)

'buf' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (22, ubuntu-latest, stable)

'object' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, nightly)

'buf' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, nightly)

'object' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, 3)

'buf' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, 3)

'object' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, 2)

'buf' is defined but never used. Allowed unused args must match /^_/u

Check failure on line 41 in packages/driver/src/codecs/record.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 20, 2)

'object' is defined but never used. Allowed unused args must match /^_/u
throw new InvalidArgumentError(
"SQL records cannot be passed as arguments");
}

decode(buf: ReadBuffer): any {
const els = buf.readUInt32();
const subCodecs = this.subCodecs;
if (els !== subCodecs.length) {
throw new ProtocolError(
`cannot decode Record: expected ` +
`${subCodecs.length} elements, got ${els}`,
);
}

const elemBuf = ReadBuffer.alloc();
const result: any[] = new Array(els);
for (let i = 0; i < els; i++) {
buf.discard(4); // reserved
const elemLen = buf.readInt32();
let val = null;
if (elemLen !== -1) {
buf.sliceInto(elemBuf, elemLen);
val = subCodecs[i].decode(elemBuf);
elemBuf.finish();
}
result[i] = val;
}

return result;
}

getSubcodecs(): ICodec[] {
return Array.from(this.subCodecs);
}

getNames(): string[] {
return Array.from(this.names);
}

getKind(): CodecKind {
return "record";
}
}
30 changes: 30 additions & 0 deletions packages/driver/src/codecs/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { NamedTupleCodec } from "./namedtuple";
import { EnumCodec } from "./enum";
import { ObjectCodec } from "./object";
import { SetCodec } from "./set";
import { RecordCodec } from "./record";
import { MultiRangeCodec, RangeCodec } from "./range";
import type { ProtocolVersion } from "../ifaces";
import { versionGreaterThanOrEqual } from "../utils";
Expand All @@ -52,6 +53,7 @@ const CTYPE_RANGE = 9;
const CTYPE_OBJECT = 10;
const CTYPE_COMPOUND = 11;
const CTYPE_MULTIRANGE = 12;
const CTYPE_RECORD = 13;

export interface CustomCodecSpec {
int64_bigint?: boolean;
Expand Down Expand Up @@ -261,6 +263,15 @@ export class CodecsRegistry {
break;
}

case CTYPE_RECORD: {
const els = frb.readUInt16();
for (let i = 0; i < els; i++) {
const elm_length = frb.readUInt32();
frb.discard(elm_length + 2);
}
break;
}

case CTYPE_ARRAY: {
frb.discard(2);
const els = frb.readUInt16();
Expand Down Expand Up @@ -567,6 +578,25 @@ export class CodecsRegistry {
break;
}

case CTYPE_RECORD: {
const els = frb.readUInt16();
const codecs = new Array(els);
const names = new Array(els);
for (let i = 0; i < els; i++) {
names[i] = frb.readString();
const pos = frb.readUInt16();
const subCodec = cl[pos];
if (subCodec == null) {
throw new ProtocolError(
"could not build record codec: missing subcodec",
);
}
codecs[i] = subCodec;
}
res = new RecordCodec(tid, codecs, names);
break;
}

case CTYPE_ENUM: {
let typeName: string | null = null;
if (isProtoV2) {
Expand Down
10 changes: 5 additions & 5 deletions packages/driver/test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2366,13 +2366,13 @@ if (getEdgeDBVersion().major >= 6) {

try {
let res = await client.querySQL("select 1");
expect(JSON.stringify(res)).toEqual('[{"col~1":1}]');
expect(JSON.stringify(res)).toEqual('[[1]]');

res = await client.querySQL("select 1 AS foo, 2 AS bar");
expect(JSON.stringify(res)).toEqual('[{"foo":1,"bar":2}]');
expect(JSON.stringify(res)).toEqual('[[1,2]]');

res = await client.querySQL("select 1 + $1::int8", [41]);
expect(JSON.stringify(res)).toEqual('[{"col~1":42}]');
expect(JSON.stringify(res)).toEqual('[[42]]');
} finally {
await client.close();
}
Expand Down Expand Up @@ -2439,11 +2439,11 @@ if (getEdgeDBVersion().major >= 6) {

try {
for (const [typename, val] of pgTypes) {
const res = await client.querySQL<{ val: any }>(
const res = await client.querySQL<any>(
`select $1::${typename} as "val"`,
[val],
);
expect(JSON.stringify(res[0].val)).toEqual(JSON.stringify(val));
expect(JSON.stringify(res[0][0])).toEqual(JSON.stringify(val));
}
} finally {
await client.close();
Expand Down

0 comments on commit 6d482a2

Please sign in to comment.