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
35 changes: 35 additions & 0 deletions src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,25 @@ export class PGJsonb {
}
}

/**
* @typedef PGOid
* @see Spanner.pgOid
*/
export class PGOid extends WrappedNumber {
value: string;
constructor(value: string) {
super();
this.value = value.toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need toString() as value type is already string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this would work but it actually makes the behaviour different. I'm not sure I understand why. But this logic is the same as how we represent the Int type on line 144.

}
valueOf(): number {
const num = Number(this.value);
if (num > Number.MAX_SAFE_INTEGER) {
throw new GoogleError(`PG.OID ${this.value} is out of bounds.`);
}
return num;
}
}

/**
* @typedef JSONOptions
* @property {boolean} [wrapNumbers=false] Indicates if the numbers should be
Expand Down Expand Up @@ -364,6 +383,14 @@ function decode(value: Value, type: spannerClient.spanner.v1.Type): Value {
break;
case spannerClient.spanner.v1.TypeCode.INT64:
case 'INT64':
if (
type.typeAnnotation ===
spannerClient.spanner.v1.TypeAnnotationCode.PG_OID ||
type.typeAnnotation === 'PG_OID'
) {
decoded = new PGOid(decoded);
break;
}
decoded = new Int(decoded);
break;
case spannerClient.spanner.v1.TypeCode.NUMERIC:
Expand Down Expand Up @@ -503,6 +530,7 @@ const TypeCode: {
unspecified: 'TYPE_CODE_UNSPECIFIED',
bool: 'BOOL',
int64: 'INT64',
pgOid: 'INT64',
float64: 'FLOAT64',
numeric: 'NUMERIC',
pgNumeric: 'NUMERIC',
Expand Down Expand Up @@ -593,6 +621,10 @@ function getType(value: Value): Type {
return {type: 'pgJsonb'};
}

if (value instanceof PGOid) {
return {type: 'pgOid'};
}

if (is.boolean(value)) {
return {type: 'bool'};
}
Expand Down Expand Up @@ -733,6 +765,8 @@ function createTypeObject(
spannerClient.spanner.v1.TypeAnnotationCode.PG_NUMERIC;
} else if (friendlyType.type === 'jsonb') {
type.typeAnnotation = spannerClient.spanner.v1.TypeAnnotationCode.PG_JSONB;
} else if (friendlyType.type === 'pgOid') {
type.typeAnnotation = spannerClient.spanner.v1.TypeAnnotationCode.PG_OID;
}
return type;
}
Expand All @@ -748,6 +782,7 @@ export const codec = {
Numeric,
PGNumeric,
PGJsonb,
PGOid,
convertFieldsToJson,
decode,
encode,
Expand Down
78 changes: 78 additions & 0 deletions system-test/spanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,38 @@ describe('Spanner', () => {
});
});

describe('oids', () => {
it('POSTGRESQL should read non-null pgOid values', function (done) {
if (IS_EMULATOR_ENABLED) {
this.skip();
}
PG_DATABASE.run('SELECT 123::oid', (err, rows) => {
assert.ifError(err);
let queriedValue = rows[0][0].value;
if (rows[0][0].value) {
queriedValue = rows[0][0].value.value;
}
assert.strictEqual(queriedValue, '123');
done();
});
});

it('POSTGRESQL should read null pgOid values', function (done) {
if (IS_EMULATOR_ENABLED) {
this.skip();
}
PG_DATABASE.run('SELECT null::oid', (err, rows) => {
assert.ifError(err);
let queriedValue = rows[0][0].value;
if (rows[0][0].value) {
queriedValue = rows[0][0].value.value;
}
assert.strictEqual(queriedValue, null);
done();
});
});
});

describe('float64s', () => {
const float64Insert = (done, dialect, value) => {
insert({FloatValue: value}, dialect, (err, row) => {
Expand Down Expand Up @@ -4951,6 +4983,52 @@ describe('Spanner', () => {
});
});

describe('pgOid', () => {
const oidQuery = (done, database, query, value) => {
database.run(query, (err, rows) => {
assert.ifError(err);
let queriedValue = rows[0][0].value;
if (rows[0][0].value) {
queriedValue = rows[0][0].value.value;
}
assert.strictEqual(queriedValue, value);
done();
});
};

it('POSTGRESQL should bind the value', function (done) {
if (IS_EMULATOR_ENABLED) {
this.skip();
}
const query = {
sql: 'SELECT $1',
params: {
p1: 1234,
},
types: {
v: 'pgOid',
},
};
oidQuery(done, PG_DATABASE, query, '1234');
});

it('POSTGRESQL should allow for null values', function (done) {
if (IS_EMULATOR_ENABLED) {
this.skip();
}
const query = {
sql: 'SELECT $1',
params: {
p1: null,
},
types: {
p1: 'pgOid',
},
};
oidQuery(done, PG_DATABASE, query, null);
});
});

describe('float64', () => {
const float64Query = (done, database, query, value) => {
database.run(query, (err, rows) => {
Expand Down
73 changes: 73 additions & 0 deletions test/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,35 @@ describe('codec', () => {
});
});

describe('PGOid', () => {
it('should stringify the value', () => {
const value = 8;
const oid = new codec.PGOid(value);

assert.strictEqual(oid.value, '8');
});

it('should return as a number', () => {
const value = 8;
const oid = new codec.PGOid(value);

assert.strictEqual(oid.valueOf(), 8);
assert.strictEqual(oid + 2, 10);
});

it('should throw if number is out of bounds', () => {
const value = '9223372036854775807';
const oid = new codec.PGOid(value);

assert.throws(
() => {
oid.valueOf();
},
new RegExp('PG.OID ' + value + ' is out of bounds.')
);
});
});

describe('Numeric', () => {
it('should store value as a string', () => {
const value = '8.01911';
Expand Down Expand Up @@ -601,6 +630,18 @@ describe('codec', () => {
assert.deepStrictEqual(decoded.toString(), expected);
});

it('should decode PG OID', () => {
const value = '64';

const decoded = codec.decode(value, {
code: google.spanner.v1.TypeCode.INT64,
typeAnnotation: google.spanner.v1.TypeAnnotationCode.PG_OID,
});

assert(decoded instanceof codec.PGOid);
assert.strictEqual(decoded.value, value);
});

it('should decode TIMESTAMP', () => {
const value = new Date();
const expected = new PreciseDate(value.getTime());
Expand Down Expand Up @@ -821,6 +862,14 @@ describe('codec', () => {
assert.strictEqual(encoded, '10');
});

it('should encode PG OID', () => {
const value = new codec.PGOid(10);

const encoded = codec.encode(value);

assert.strictEqual(encoded, '10');
});

it('should encode FLOAT64', () => {
const value = new codec.Float(10);

Expand Down Expand Up @@ -986,6 +1035,12 @@ describe('codec', () => {
type: 'pgNumeric',
});
});

it('should determine if the value is a PGOid', () => {
assert.deepStrictEqual(codec.getType(new codec.PGOid(5678)), {
type: 'pgOid',
});
});
});

describe('convertToListValue', () => {
Expand Down Expand Up @@ -1211,5 +1266,23 @@ describe('codec', () => {
typeAnnotation: google.spanner.v1.TypeAnnotationCode.PG_NUMERIC,
});
});

it('should set code and typeAnnotation for pgOid string', () => {
const type = codec.createTypeObject('pgOid');

assert.deepStrictEqual(type, {
code: google.spanner.v1.TypeCode[google.spanner.v1.TypeCode.INT64],
typeAnnotation: google.spanner.v1.TypeAnnotationCode.PG_OID,
});
});

it('should set code and typeAnnotation for pgOid friendlyType object', () => {
const type = codec.createTypeObject({type: 'pgOid'});

assert.deepStrictEqual(type, {
code: google.spanner.v1.TypeCode[google.spanner.v1.TypeCode.INT64],
typeAnnotation: google.spanner.v1.TypeAnnotationCode.PG_OID,
});
});
});
});