diff --git a/CHANGELOG.md b/CHANGELOG.md index 54698827c..cd79565e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ 1. [#237](https://github.com/influxdata/influxdb-client-js/pull/237): Fixed line splitter of query results that might have produced wrong results for query responses with quoted data. 1. [#242](https://github.com/influxdata/influxdb-client-js/pull/242): Repair escaping of backslash in line protocol string field. 1. [#246](https://github.com/influxdata/influxdb-client-js/pull/246): Throw error on attempt to write points using a closed WriteApi instance. +1. [#252](https://github.com/influxdata/influxdb-client-js/pull/252): Repair nesting of flux expressions. ## 1.6.0 [2020-08-14] diff --git a/packages/core/src/query/flux.ts b/packages/core/src/query/flux.ts index fbe274268..ac5dca3fd 100644 --- a/packages/core/src/query/flux.ts +++ b/packages/core/src/query/flux.ts @@ -28,6 +28,15 @@ class FluxParameter implements FluxParameterLike, ParameterizedQuery { } } +/** + * Checks if the supplied object is FluxParameterLike + * @param value - any value + * @returns true if it is + */ +function isFluxParameterLike(value: any): boolean { + return typeof value === 'object' && typeof value[FLUX_VALUE] === 'function' +} + /** * Escapes content of the supplied string so it can be wrapped into double qoutes * to become a [flux string literal](https://docs.influxdata.com/flux/v0.65/language/lexical-elements/#string-literals). @@ -222,7 +231,9 @@ export function flux( strings: TemplateStringsArray, ...values: any ): ParameterizedQuery { - if (strings.length == 1 && (!values || values.length === 0)) return strings[0] // the simplest case + if (strings.length == 1 && (!values || values.length === 0)) { + return fluxExpression(strings[0]) // the simplest case + } const parts = new Array(strings.length + values.length) let partIndex = 0 for (let i = 0; i < strings.length; i++) { @@ -241,9 +252,12 @@ export function flux( } else { sanitized = toFluxValue(val) if (sanitized === '') { - throw new Error( - `Unsupported parameter literal '${val}' at index: ${i}, type: ${typeof val}` - ) + // do not allow to insert empty strings, unless it is FluxParameterLike + if (!isFluxParameterLike(val)) { + throw new Error( + `Unsupported parameter literal '${val}' at index: ${i}, type: ${typeof val}` + ) + } } } parts[partIndex++] = sanitized diff --git a/packages/core/test/unit/query/flux.test.ts b/packages/core/test/unit/query/flux.test.ts index 2b29ea9a9..c0c975a10 100644 --- a/packages/core/test/unit/query/flux.test.ts +++ b/packages/core/test/unit/query/flux.test.ts @@ -24,12 +24,7 @@ describe('Flux Values', () => { const subject = fluxInteger(123) expect(subject.toString()).equals('123') expect((subject as any)[FLUX_VALUE]()).equals('123') - try { - fluxInteger('123a') - expect.fail() - } catch (_e) { - // OK, this must happen - } + expect(() => fluxInteger('123a')).to.throw() }) it('creates fluxBool', () => { expect(fluxBool('true').toString()).equals('true') @@ -49,18 +44,8 @@ describe('Flux Values', () => { const subject = fluxFloat(123.456) expect(subject.toString()).equals('123.456') expect((subject as any)[FLUX_VALUE]()).equals('123.456') - try { - fluxFloat('123..') - expect.fail() - } catch (_e) { - // OK, this must happen - } - try { - fluxFloat('123.a') - expect.fail() - } catch (_e) { - // OK, this must happen - } + expect(() => fluxFloat('123..')).to.throw() + expect(() => fluxFloat('123.a')).to.throw() }) it('creates fluxDuration', () => { const subject = fluxDuration('1ms') @@ -127,37 +112,68 @@ describe('Flux Values', () => { }) describe('Flux Tagged Template', () => { - expect( - flux`from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")`.toString(), - 'from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")' - ) - expect( - flux`from(bucket:"my-bucket") |> range(start: ${0}) |> filter(fn: (r) => r._measurement == ${'temperature'})`.toString(), - 'from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")' - ) - expect( - flux`from(bucket:"my-bucket") |> range(start: ${0}) |> filter(fn: (r) => r._measurement == "${'temperature'}")`.toString(), - 'from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")' - ) - - try { - flux`${undefined}` - expect.fail() - } catch (_e) { - // ok expected, undefined is not supported - } - - try { - flux((['1', '2'] as any) as TemplateStringsArray) - expect.fail() - } catch (_e) { - // ok expected, too few arguments supplied to a tagged template - } + it('creates a string from a simple string', () => { + expect( + flux`from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")`.toString() + ).equals( + 'from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")' + ) + }) + it('interpolates a number', () => { + expect( + flux`from(bucket:"my-bucket") |> range(start: ${0}) |> filter(fn: (r) => r._measurement == ${'temperature'})`.toString() + ).equals( + 'from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")' + ) + }) + it('interpolates a string', () => { + expect( + flux`from(bucket:${'my-bucket'}) |> range(start: 0) |> filter(fn: (r) => r._measurement == ${'temperature'})`.toString() + ).equals( + 'from(bucket:"my-bucket") |> range(start: 0) |> filter(fn: (r) => r._measurement == "temperature")' + ) + }) + it('interpolates a wrapped string', () => { + expect(flux`from(bucket:"${'my-bucket'}")`.toString()).equals( + 'from(bucket:"my-bucket")' + ) + }) + it('fails on undefined', () => { + expect(() => flux`${undefined}`).to.throw() + }) + it('converts object with empty toString to ""', () => { + const x = { + toString(): string { + return '' + }, + } + expect(flux`${x}`.toString()).equals('""') + }) + it('fails on wrong usage of template', () => { + try { + flux((['1', '2'] as any) as TemplateStringsArray) + expect.fail() + } catch (_e) { + // ok expected, too few arguments supplied to a tagged template + } + }) - // nested flux templates - const flux1 = flux`from(bucket:"my-bucket")` - expect( - flux`${flux1} |> range(start: ${0})")`.toString(), - 'from(bucket:"my-bucket") |> range(start: 0)")' - ) + it('processes a simple nested flux template', () => { + const flux1 = flux`from(bucket:"my-bucket")` + expect(flux`${flux1} |> range(start: ${0})")`.toString()).equals( + 'from(bucket:"my-bucket") |> range(start: 0)")' + ) + }) + it('processes a parameterized nested flux template', () => { + const flux1 = flux`from(bucket:${'my-bucket'})` + expect(flux`${flux1} |> range(start: ${0})")`.toString()).equals( + 'from(bucket:"my-bucket") |> range(start: 0)")' + ) + }) + it('processes an empty nested flux template', () => { + const empty = flux`` + expect(flux`from(bucket:"my-bucket")${empty}`.toString()).equals( + 'from(bucket:"my-bucket")' + ) + }) })