diff --git a/render/Dockerfile b/render/Dockerfile index 7f662f8b..a968086f 100644 --- a/render/Dockerfile +++ b/render/Dockerfile @@ -5,7 +5,7 @@ # # An argument so an unreleased build can be tried locally: # docker build --build-arg SQLFLOW_IMAGE=turbolytics/sql-flow: render -ARG SQLFLOW_IMAGE=turbolytics/sql-flow:v2026.09.18.1 +ARG SQLFLOW_IMAGE=turbolytics/sql-flow:v2026.09.19.1 FROM ${SQLFLOW_IMAGE} # psql and pg_isready apply the migrations. curl sends the install events. diff --git a/render/Makefile b/render/Makefile index 7bdbedfb..2358f78c 100644 --- a/render/Makefile +++ b/render/Makefile @@ -1,5 +1,5 @@ # Must match the Dockerfile and docker-compose.yml. -SQLFLOW_IMAGE ?= turbolytics/sql-flow:v2026.09.18.1 +SQLFLOW_IMAGE ?= turbolytics/sql-flow:v2026.09.19.1 .PHONY: validate rollups up test psql clean diff --git a/render/README.md b/render/README.md index d0b04a9e..c949c99d 100644 --- a/render/README.md +++ b/render/README.md @@ -47,13 +47,13 @@ of both: ```json {"name": "install.deployed", "type": "count", "value": 1, "dimensions": {"install_id": "3f0c1b7e-…", "source": "render", - "template": "render-metrics", "sqlflow_version": "v2026.09.18.1"}} + "template": "render-metrics", "sqlflow_version": "v2026.09.19.1"}} ``` ```json {"name": "install.first_request", "type": "count", "value": 1, "dimensions": {"install_id": "3f0c1b7e-…", "source": "render", - "template": "render-metrics", "sqlflow_version": "v2026.09.18.1"}} + "template": "render-metrics", "sqlflow_version": "v2026.09.19.1"}} ``` | Field | What it is | diff --git a/render/bin/telemetry.sh b/render/bin/telemetry.sh index dc757f91..80dfb36f 100755 --- a/render/bin/telemetry.sh +++ b/render/bin/telemetry.sh @@ -31,7 +31,7 @@ TEMPLATE=render-metrics sql() { psql "$SQLFLOW_POSTGRES_URI" -X -Atq -v ON_ERROR_STOP=1 -c "$1" 2>/dev/null; } -# The first line of `sqlflow --version` is "sqlflow v2026.09.18.1". +# The first line of `sqlflow --version` is "sqlflow v2026.09.19.1". VERSION="$(sqlflow --version 2>/dev/null | awk 'NR==1 {print $2}')" case "$VERSION" in ''|*[!A-Za-z0-9._-]*) VERSION=unknown ;; esac diff --git a/render/docker-compose.yml b/render/docker-compose.yml index 5af60d45..feb326b7 100644 --- a/render/docker-compose.yml +++ b/render/docker-compose.yml @@ -38,7 +38,7 @@ services: build: context: . args: - SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.18.1} + SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.19.1} depends_on: postgres: condition: service_healthy @@ -71,7 +71,7 @@ services: build: context: . args: - SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.18.1} + SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.19.1} depends_on: postgres: condition: service_healthy @@ -93,7 +93,7 @@ services: build: context: . args: - SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.18.1} + SQLFLOW_IMAGE: ${SQLFLOW_IMAGE:-turbolytics/sql-flow:v2026.09.19.1} entrypoint: /app/bin/serve.sh depends_on: postgres: diff --git a/render/test/e2e.sh b/render/test/e2e.sh index 9df8159a..2964f36e 100755 --- a/render/test/e2e.sh +++ b/render/test/e2e.sh @@ -10,7 +10,7 @@ API="http://127.0.0.1:${API_HOST_PORT:-8080}" SECRET=local-secret # The tag the image is built from, which an install reports as its version. # make test passes it. Must match the Dockerfile's default. -: "${SQLFLOW_IMAGE:=turbolytics/sql-flow:v2026.09.18.1}" +: "${SQLFLOW_IMAGE:=turbolytics/sql-flow:v2026.09.19.1}" export SQLFLOW_IMAGE GRAINS="1m 5m 15m 1h 6h 1d" @@ -209,9 +209,29 @@ for g in $GRAINS; do '[.rows[] | [.value_sum, .value_count, .value_min, .value_max]] == [[9,4,1,5]]' "$total" done +# A grain coarser than the window answers rather than erroring. Whether the +# answer is empty depends on the clock, which is why this no longer asserts +# that it is: the default window is one hour, and a 1d bucket falls inside it +# only when a day begins during that hour. For the hour after UTC midnight one +# does, the range covers that day, and the fixtures written seconds earlier +# match. The old assertion -- since == until, no rows -- was a claim about the +# clock wearing a claim about the API, and it failed on main as readily as +# here. +# +# The invariant that holds at every hour: the grain asked for is the grain +# answered, the range is never inverted, and a row is returned only for a +# bucket inside it. Checked against the midnight response and against a +# zero-width one, and it rejects a bucket outside the range either way. narrow="$(get metric name=checkout grain=1d)" -expect "a pinned 1d grain with the default hour is an empty range, not an error" \ - '(.range.since == .range.until) and (.rows | type) == "array" and (.rows | length) == 0' "$narrow" +expect "a pinned 1d grain answers within its own range, whatever the hour" \ + '. as $r + | ($r.grain == "1d") + and (($r.rows | type) == "array") + and ($r.range.since <= $r.range.until) + and (if $r.range.since == $r.range.until + then ($r.rows | length) == 0 + else all($r.rows[]; .bucket >= $r.range.since and .bucket < $r.range.until) + end)' "$narrow" auto="$(get metric name=checkout)" expect "without a grain the API picks the finest that covers the range" '.grain == "1m" and (.rows | length) == 3' "$auto"