diff --git a/CHANGELOG.md b/CHANGELOG.md index 097069bb5..417d11c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.14.0 [unreleased] +### Features + +1. [#338](https://github.com/influxdata/influxdb-client-js/pull/338): Use nanosecond precision by default in `Point.toLineProtocol()`. + ## 1.13.0 [2021-04-30] ### Features diff --git a/packages/core/src/Point.ts b/packages/core/src/Point.ts index e605a86a0..e145681d2 100644 --- a/packages/core/src/Point.ts +++ b/packages/core/src/Point.ts @@ -1,3 +1,4 @@ +import {convertTimeToNanos} from './util/currentTime' import {escape} from './util/escape' /** @@ -5,7 +6,9 @@ import {escape} from './util/escape' * to a protocol line. */ export interface PointSettings { + /** default tags to add to every point */ defaultTags?: {[key: string]: string} + /** convertTime serializes Point's timestamp to a line protocol value */ convertTime?: ( value: string | number | Date | undefined ) => string | undefined @@ -126,10 +129,13 @@ export class Point { * Sets point timestamp. Timestamp can be specified as a Date (preferred), number, string * or an undefined value. An undefined value instructs to assign a local timestamp using * the client's clock. An empty string can be used to let the server assign - * the timestamp. A number value represents time as a count of time units since epoch. - * The current time in nanoseconds can't precisely fit into a JS number, which - * can hold at most 2^53 integer number. Nanosecond precision numbers are thus supplied as - * a (base-10) string. An application can use ES2020 BigInt to represent nanoseconds, + * the timestamp. A number value represents time as a count of time units since epoch, the + * exact time unit then depends on the {@link InfluxDB.getWriteApi | precision} of the API + * that writes the point. + * + * Beware that the current time in nanoseconds can't precisely fit into a JS number, + * which can hold at most 2^53 integer number. Nanosecond precision numbers are thus supplied as + * a (base-10) string. An application can also use ES2020 BigInt to represent nanoseconds, * BigInt's `toString()` returns the required high-precision string. * * Note that InfluxDB requires the timestamp to fit into int64 data type. @@ -144,10 +150,11 @@ export class Point { /** * Creates an InfluxDB protocol line out of this instance. - * @param settings - settings define the exact representation of point time and can also add default tags + * @param settings - settings control serialization of a point timestamp and can also add default tags, + * nanosecond timestamp precision is used when no `settings` or no `settings.convertTime` is supplied. * @returns an InfluxDB protocol line out of this instance */ - public toLineProtocol(settings?: PointSettings): string | undefined { + public toLineProtocol(settings?: Partial): string | undefined { if (!this.name) return undefined let fieldsLine = '' Object.keys(this.fields) @@ -179,6 +186,8 @@ export class Point { let time = this.time if (settings && settings.convertTime) { time = settings.convertTime(time) + } else { + time = convertTimeToNanos(time) } return `${escape.measurement(this.name)}${tagsLine} ${fieldsLine}${ diff --git a/packages/core/src/util/currentTime.ts b/packages/core/src/util/currentTime.ts index b21e0c66f..1254c8d82 100644 --- a/packages/core/src/util/currentTime.ts +++ b/packages/core/src/util/currentTime.ts @@ -94,3 +94,24 @@ export const dateToProtocolTimestamp = { us: (d: Date): string => `${d.getTime()}000`, ns: (d: Date): string => `${d.getTime()}000000`, } + +/** + * convertTimeToNanos converts of Point's timestamp to a string + * @param value - supported timestamp value + * @returns line protocol value + */ +export function convertTimeToNanos( + value: string | number | Date | undefined +): string | undefined { + if (value === undefined) { + return nanos() + } else if (typeof value === 'string') { + return value.length > 0 ? value : undefined + } else if (value instanceof Date) { + return `${value.getTime()}000000` + } else if (typeof value === 'number') { + return String(Math.floor(value)) + } else { + return String(value) + } +} diff --git a/packages/core/test/unit/Point.test.ts b/packages/core/test/unit/Point.test.ts index e8c9fce6e..9040b9db8 100644 --- a/packages/core/test/unit/Point.test.ts +++ b/packages/core/test/unit/Point.test.ts @@ -51,6 +51,8 @@ function createPoint(test: PointTest): Point { }) if (test.time) { point.timestamp(test.time) + } else { + point.timestamp('') } return point } @@ -98,10 +100,48 @@ describe('Point', () => { it('creates line with JSON double encoded field #241', () => { const fieldValue = JSON.stringify({prop: JSON.stringify({str: 'test'})}) const point = new Point('tst') - point.stringField('a', fieldValue) + point.stringField('a', fieldValue).timestamp('') expect(point.toLineProtocol()).equals( 'tst a="{X"propX":X"{XXX"strXXX":XXX"testXXX"}X"}"'.replace(/X/g, '\\') ) }) + it('serializes Point with current time nanosecond OOTB', () => { + const point = new Point('tst').floatField('a', 1) + const lpParts = point.toLineProtocol()?.split(' ') as string[] + expect(lpParts).has.length(3) + expect(lpParts[0]).equals('tst') + expect(lpParts[1]).equals('a=1') + // expect current time in nanoseconds + const nowMillisStr = String(Date.now()) + expect(lpParts[2]).has.length(nowMillisStr.length + 6) + expect( + Number.parseInt(lpParts[2].substring(0, nowMillisStr.length)) - 1 + ).lte(Date.now()) + }) + it("serializes Point's Date timestamp with nanosecond precision OOTB", () => { + const point = new Point('tst').floatField('a', 1).timestamp(new Date()) + const lpParts = point.toLineProtocol()?.split(' ') as string[] + expect(lpParts).has.length(3) + expect(lpParts[0]).equals('tst') + expect(lpParts[1]).equals('a=1') + // expect current time in nanoseconds + const nowMillisStr = String(Date.now()) + expect(lpParts[2]).has.length(nowMillisStr.length + 6) + expect( + Number.parseInt(lpParts[2].substring(0, nowMillisStr.length)) - 1 + ).lte(Date.now()) + }) + it("serializes Point's number timestamp as-is OOTB", () => { + const point = new Point('tst').floatField('a', 1).timestamp(1) + expect(point.toLineProtocol()).equals('tst a=1 1') + }) + it("serializes Point's unknown timestamp as-is OOTB", () => { + const point = new Point('tst').floatField('a', 1).timestamp(({ + toString() { + return 'any' + }, + } as unknown) as undefined) + expect(point.toLineProtocol()).equals('tst a=1 any') + }) }) })