Skip to content

Commit 42f52a9

Browse files
authored
feat(nodejs): nanos precision for timestamps (#55)
* feat(nodejs): nanos precision for timestamps * small doc fix * only accept BigInt with nanos precision * use values in timestamp tests which reveal conversion and/or timestamp truncation issues
1 parent 75da0cd commit 42f52a9

File tree

10 files changed

+244
-187
lines changed

10 files changed

+244
-187
lines changed

src/buffer/base.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@ import {
99
DEFAULT_BUFFER_SIZE,
1010
DEFAULT_MAX_BUFFER_SIZE,
1111
} from "./index";
12-
import {
13-
isInteger,
14-
timestampToMicros,
15-
timestampToNanos,
16-
TimestampUnit,
17-
} from "../utils";
12+
import { isInteger, TimestampUnit } from "../utils";
1813

1914
// Default maximum length for table and column names.
2015
const DEFAULT_MAX_NAME_LENGTH = 127;
@@ -269,6 +264,12 @@ abstract class SenderBufferBase implements SenderBuffer {
269264
return this;
270265
}
271266

267+
protected abstract writeTimestamp(
268+
timestamp: number | bigint,
269+
unit: TimestampUnit,
270+
designated: boolean,
271+
): void;
272+
272273
/**
273274
* Writes a timestamp column with its value into the buffer. <br>
274275
* Use it to insert into TIMESTAMP columns.
@@ -284,15 +285,18 @@ abstract class SenderBufferBase implements SenderBuffer {
284285
unit: TimestampUnit = "us",
285286
): SenderBuffer {
286287
if (typeof value !== "bigint" && !Number.isInteger(value)) {
287-
throw new Error(`Value must be an integer or BigInt, received ${value}`);
288+
throw new Error(
289+
`Timestamp value must be an integer or BigInt, received ${value}`,
290+
);
288291
}
289-
this.writeColumn(name, value, () => {
290-
const valueMicros = timestampToMicros(BigInt(value), unit);
291-
const valueStr = valueMicros.toString();
292-
this.checkCapacity([valueStr], 1);
293-
this.write(valueStr);
294-
this.write("t");
295-
});
292+
if (unit == "ns" && typeof value !== "bigint") {
293+
throw new Error(
294+
`Timestamp value must be a BigInt if it is set in nanoseconds`,
295+
);
296+
}
297+
this.writeColumn(name, value, () =>
298+
this.writeTimestamp(value, unit, false),
299+
);
296300
return this;
297301
}
298302

@@ -313,11 +317,14 @@ abstract class SenderBufferBase implements SenderBuffer {
313317
`Designated timestamp must be an integer or BigInt, received ${timestamp}`,
314318
);
315319
}
316-
const timestampNanos = timestampToNanos(BigInt(timestamp), unit);
317-
const timestampStr = timestampNanos.toString();
318-
this.checkCapacity([timestampStr], 2);
320+
if (unit == "ns" && typeof timestamp !== "bigint") {
321+
throw new Error(
322+
`Designated timestamp must be a BigInt if it is set in nanoseconds`,
323+
);
324+
}
325+
this.checkCapacity([], 1);
319326
this.write(" ");
320-
this.write(timestampStr);
327+
this.writeTimestamp(timestamp, unit, true);
321328
this.write("\n");
322329
this.startNewRow();
323330
}

src/buffer/bufferv1.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { SenderOptions } from "../options";
33
import { SenderBuffer } from "./index";
44
import { SenderBufferBase } from "./base";
5+
import { timestampToMicros, timestampToNanos, TimestampUnit } from "../utils";
56

67
/**
78
* Buffer implementation for protocol version 1. <br>
@@ -39,6 +40,23 @@ class SenderBufferV1 extends SenderBufferBase {
3940
return this;
4041
}
4142

43+
protected writeTimestamp(
44+
timestamp: number | bigint,
45+
unit: TimestampUnit = "us",
46+
designated: boolean,
47+
): void {
48+
const biValue = BigInt(timestamp);
49+
const timestampValue = designated
50+
? timestampToNanos(biValue, unit)
51+
: timestampToMicros(biValue, unit);
52+
const timestampStr = timestampValue.toString();
53+
this.checkCapacity([timestampStr], 2);
54+
this.write(timestampStr);
55+
if (!designated) {
56+
this.write("t");
57+
}
58+
}
59+
4260
/**
4361
* Array columns are not supported in protocol v1.
4462
*

src/buffer/bufferv2.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
import { SenderOptions } from "../options";
33
import { SenderBuffer } from "./index";
44
import { SenderBufferBase } from "./base";
5-
import { ArrayPrimitive, getDimensions, validateArray } from "../utils";
5+
import {
6+
ArrayPrimitive,
7+
getDimensions,
8+
timestampToMicros,
9+
TimestampUnit,
10+
validateArray,
11+
} from "../utils";
612

713
// Column type constants for protocol v2.
814
const COLUMN_TYPE_DOUBLE: number = 10;
@@ -53,6 +59,28 @@ class SenderBufferV2 extends SenderBufferBase {
5359
return this;
5460
}
5561

62+
protected writeTimestamp(
63+
value: number | bigint,
64+
unit: TimestampUnit = "us",
65+
): void {
66+
let biValue: bigint;
67+
let suffix: string;
68+
switch (unit) {
69+
case "ns":
70+
biValue = BigInt(value);
71+
suffix = "n";
72+
break;
73+
default:
74+
biValue = timestampToMicros(BigInt(value), unit);
75+
suffix = "t";
76+
}
77+
78+
const timestampStr = biValue.toString();
79+
this.checkCapacity([timestampStr], 2);
80+
this.write(timestampStr);
81+
this.write(suffix);
82+
}
83+
5684
/**
5785
* Write an array column with its values into the buffer using v2 format.
5886
*

src/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ class SenderOptions {
226226
* If TCP transport is used, the protocol version will default to 1.
227227
* In case of HTTP transport the <i>/settings</i> endpoint of the database is used to find the protocol versions
228228
* supported by the server, and the highest will be selected.
229-
* When calling the <i>/settings</i> endpoint the timeout and TLs options are used from the <i>options</i> object.
229+
* When calling the <i>/settings</i> endpoint the timeout and TLS options are used from the <i>options</i> object.
230230
* @param {SenderOptions} options SenderOptions instance needs resolving protocol version
231231
*/
232232
static async resolveAuto(options: SenderOptions) {

test/logging.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
vi,
1010
} from "vitest";
1111

12-
import { Logger } from "../src/logging";
12+
import { Logger } from "../src";
1313

1414
describe("Default logging suite", function () {
1515
const error = vi.spyOn(console, "error").mockImplementation(() => {});

test/options.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { describe, it, expect, beforeAll, afterAll } from "vitest";
33
import { Agent } from "undici";
44

5-
import { SenderOptions } from "../src/options";
5+
import { SenderOptions } from "../src";
66
import { MockHttp } from "./util/mockhttp";
77
import { readFileSync } from "fs";
88

0 commit comments

Comments
 (0)