Skip to content

Pre-release documentation

This describes Beacon 2.0.0-rc3, a release candidate. Behavior documented here may still change before 2.0.0 ships, and some of it is not in any released build yet. For the current stable release, see the 1.8.0 documentation.

JSON Query DSL

The JSON DSL gives a query as a typed object. You build no SQL string. Use this interface in a client program and in a query builder.

http
POST /api/query
Content-Type: application/json

TIP

Find the available columns before you write a query:

  • Default table: GET /api/table-schema?table_name=default
  • From a file glob: GET /api/dataset-schema?file=argo/**/*.nc

Request shape

FieldRequiredDescription
selectYesColumns (and expressions) to return
fromNoData source, table name or inline file source
filtersNoRow filters, combined with AND by default
sort_byNoSort expressions
limitNoMaximum rows to return
offsetNoRows to skip
distinctNoDISTINCT ON expression
outputNoOutput format (default: Arrow IPC stream)

Select columns

Plain column

json
{ "select": ["time", "latitude", "longitude"] }

Column with alias

json
{
  "select": [
    { "column": "sea_surface_temperature", "alias": "sst" }
  ]
}

Function call

json
{
  "select": [
    { "function": "round", "args": ["temperature", { "value": 2 }], "alias": "temperature_rounded" }
  ]
}

An args entry is a column name string, or a literal object { "value": … }.

Choosing the data source (from)

Query a registered table

http
POST /api/query
Content-Type: application/json

{
  "from": "default",
  "select": ["time", "temperature"],
  "limit": 100,
  "output": { "format": "csv" }
}

Use GET /api/tables to list the table names. Without a from field, Beacon uses the default table.

Query files directly

Give a format key with a paths array. Beacon resolves a path against its dataset root. A path also takes a glob pattern.

NetCDF:

http
POST /api/query
Content-Type: application/json

{
  "from": { "netcdf": { "paths": ["argo/**/*.nc"] } },
  "select": ["time", "latitude", "longitude", "temperature"],
  "limit": 100,
  "output": { "format": "csv" }
}

Zarr:

http
POST /api/query
Content-Type: application/json

{
  "from": { "zarr": { "paths": ["sst/*/zarr.json"] } },
  "select": ["time", "sst"],
  "limit": 100,
  "output": { "format": "csv" }
}

Predicate pushdown is automatic on a large Zarr store. Beacon prunes the chunks and slices the coordinate dimensions. It uses your filters. You configure no extra option:

http
POST /api/query
Content-Type: application/json

{
  "from": {
    "zarr": {
      "paths": ["sst/*/zarr.json"]
    }
  },
  "select": ["time", "latitude", "longitude", "sst"],
  "filters": [{ "column": "time", "min": "2025-01-01" }],
  "limit": 1000,
  "output": { "format": "csv" }
}

Parquet:

http
POST /api/query
Content-Type: application/json

{
  "from": { "parquet": { "paths": ["obs/**/*.parquet"] } },
  "select": ["time", "latitude", "longitude"],
  "limit": 100,
  "output": { "format": "csv" }
}

The other format keys are csv, arrow, odv, tiff and bbf.

Filters

filters is an array of filter objects. Beacon combines the entries with AND. A filter works on any column of the schema.

Range (min / max)

json
{ "filters": [{ "column": "temperature", "min": 2, "max": 10 }] }

Omit min or max for a range with one limit.

Equality

json
{ "filters": [{ "column": "platform", "eq": "SHIP" }] }

AND (multiple filters)

http
POST /api/query
Content-Type: application/json

{
  "select": ["time", "latitude", "longitude", "temperature"],
  "filters": [
    { "column": "temperature", "min": 2, "max": 10 },
    { "column": "latitude", "min": -10, "max": 10 }
  ],
  "limit": 10000,
  "output": { "format": "csv" }
}

OR

Put the OR branches in one or filter object:

http
POST /api/query
Content-Type: application/json

{
  "select": ["time", "platform", "temperature"],
  "filters": [
    {
      "or": [
        { "column": "platform", "eq": "SHIP" },
        { "column": "platform", "eq": "BUOY" }
      ]
    }
  ],
  "limit": 1000,
  "output": { "format": "csv" }
}

GeoJSON spatial filter

Tests if a point lies inside a GeoJSON geometry. The point comes from a longitude column and a latitude column:

http
POST /api/query
Content-Type: application/json

{
  "select": ["time", "longitude", "latitude", "temperature"],
  "filters": [
    {
      "longitude_column": "longitude",
      "latitude_column": "latitude",
      "geometry": {
        "type": "Polygon",
        "coordinates": [[[4.0, 52.0], [6.0, 52.0], [6.0, 54.0], [4.0, 54.0], [4.0, 52.0]]]
      }
    }
  ],
  "limit": 10000,
  "output": { "format": "csv" }
}

Filter operator reference

Every leaf filter names a column with column. The alias is for_query_parameter. Each filter also takes one operator key. This is the full set:

OperatorKey(s)Example
Equaleq{ "column": "platform", "eq": "SHIP" }
Not equalneq (aliases not_eq, not_equal){ "column": "platform", "neq": "BUOY" }
Greater thangt{ "column": "depth", "gt": 0 }
Greater or equalgt_eq (alias min){ "column": "depth", "gt_eq": 0 }
Less thanlt{ "column": "depth", "lt": 100 }
Less or equallt_eq (alias max){ "column": "depth", "lt_eq": 100 }
Range (between)gt_eq + lt_eq (aliases min/low and max/high){ "column": "temp", "min": 2, "max": 10 }
Is nullis_null{ "is_null": { "column": "qc_flag" } }
Is not nullis_not_null (aliases skip_fill_values, skip_missing){ "is_not_null": { "column": "qc_flag" } }
All ofand{ "and": [ … ] }
Any ofor{ "or": [ … ] }
Point-in-geometrylongitude_column + latitude_column + geometrysee GeoJSON spatial filter

The min and max keys on this page are aliases of gt_eq and lt_eq.

Sort and paginate

FieldDescription
sort_byArray of {"Asc": "col"} or {"Desc": "col"} objects
limitMaximum number of rows
offsetNumber of rows to skip

WARNING

The sort_by enum keys use exact case. Write "Asc" and "Desc". Do not write "asc" or "desc".

http
POST /api/query
Content-Type: application/json

{
  "select": ["time", "temperature"],
  "sort_by": [{ "Desc": "time" }],
  "offset": 100,
  "limit": 50,
  "output": { "format": "csv" }
}

DISTINCT ON

Returns one row for each unique combination of the on columns:

http
POST /api/query
Content-Type: application/json

{
  "distinct": {
    "on": ["platform"],
    "select": ["platform", "time", "temperature"]
  },
  "sort_by": [{ "Desc": "time" }],
  "limit": 100,
  "output": { "format": "csv" }
}

Output formats

See Querying, Output formats for the full list. The output field is the same for a JSON DSL query and a SQL query.

Released under the AGPL-3.0 License.