Niki123456123456/quickql

See the code

See what people are saying

SourceMessageScoreDate

QuickQL, a VS Code extension for SQL-like queries over JSON APIs (r/coolgithubprojects)

Hey everyone, During my apprenticeship, I worked with a large SQL database. Whenever I wanted to explore some data, I could just write a query and see what was there. Now I work with lots of microservices, and I missed that convenience. The data is still there, but it’s spread across different…

1

Oct 4, 2026

README

QuickQL (quick query language)

A lightweight pipeline query language for transforming JSON, CSV, and HTTP data sources. QuickQL queries are plain text files (.ql) that describe a sequence of data transformation steps executed top to bottom.

Quick Example

SOURCE OPEN('orders.json')
FILTER EQ(status, 'shipped')
MAP customer_id, total, shipped_date = GETDATE(shipped_at)
GROUP_BY customer_id MAP total = SUM(total), last_ship = MAXDATE(shipped_date)
SORT_BY last_ship DESC
LIMIT 1024

Statements

Each line in a .ql file is one pipeline step. Steps are separated by newlines; comments start with --.

StatementDescription
SOURCELoad data from a file, URL, or inline literal
MAPSelect, rename, or compute columns
FILTERKeep only rows matching a condition
MAP_MANYFlatten an array field into individual rows
GROUP_BYGroup rows by keys and aggregate
SORT_BYSort rows by one or more columns
LIMITKeep at most the first number of rows

Equivalents in other query APIs

The following examples assume a collection named rows. They show the closest conceptual equivalent; loading data and accessing dynamically typed fields depend on the database, serialization library, and data model in use.

QuickQL statementSQL.NET LINQ (C#)JavaScriptJava Stream APIRust iterators
SOURCE OPEN('data.json')SELECT * FROM sourceLoadRows("data.json")await loadRows("data.json")loadRows("data.json").stream()load_rows("data.json")?.into_iter()
MAP id, full_name = nameSELECT id, name AS full_namerows.Select(r => new { r.Id, FullName = r.Name })rows.map(({ id, name }) => ({ id, full_name: name }))rows.stream().map(r -> new Result(r.id(), r.name()))rows.into_iter().map(|r| Result { id: r.id, full_name: r.name })
FILTER EQ(status, 'active')WHERE status = 'active'rows.Where(r => r.Status == "active")rows.filter(r => r.status === "active")rows.stream().filter(r -> r.status().equals("active"))rows.into_iter().filter(|r| r.status == "active")
MAP_MANY linesCROSS JOIN UNNEST(lines) AS linerows.SelectMany(r => r.Lines)rows.flatMap(r => r.lines)rows.stream().flatMap(r -> r.lines().stream())rows.into_iter().flat_map(|r| r.lines)
GROUP_BY region MAP revenue = SUM(amount)SELECT region, SUM(amount) AS revenue FROM source GROUP BY regionrows.GroupBy(r => r.Region).Select(g => new { Region = g.Key, Revenue = g.Sum(r => r.Amount) })Map.groupBy(rows, r => r.region) + aggregaterows.stream().collect(groupingBy(Row::region, summingDouble(Row::amount)))rows.into_iter().into_group_map_by(|r| r.region.clone()) + aggregate
SORT_BY price DESC, name ASCORDER BY price DESC, name ASCrows.OrderByDescending(r => r.Price).ThenBy(r => r.Name)rows.toSorted((a, b) => b.price - a.price || a.name.localeCompare(b.name))rows.stream().sorted(comparing(Row::price).reversed().thenComparing(Row::name))rows.sort_by(|a, b| b.price.total_cmp(&a.price).then_with(|| a.name.cmp(&b.name)))
LIMIT 1024LIMIT 1024rows.Take(1024)rows.slice(0, 1024)rows.stream().limit(1024)rows.into_iter().take(1024)

Data Sources

QuickQL can read:

  • JSON files — array of objects or a single object/array
  • CSV files — automatically detected by .csv extension
  • HTTP endpoints — GET, POST, or PUT with optional headers, body, and pagination
  • Other .ql files — compose queries by referencing them as sources
-- JSON file
SOURCE OPEN('data/users.json')

-- CSV file
SOURCE OPEN('reports/sales.csv')

-- HTTP API
SOURCE GET('https://api.example.com/users')

-- Another query
SOURCE OPEN('other_query.ql')

-- Multiple sources merged
SOURCE OPEN('users.json'), OPEN('admins.json')

Transforming Data

Select and rename columns

SOURCE OPEN('users.json')
MAP id, name, email
SOURCE OPEN('users.json')
MAP id, full_name = name, contact = email

Compute new fields

SOURCE OPEN('orders.json')
MAP *, total_with_tax = SUM(total, tax)

Filter rows

SOURCE OPEN('users.json')
FILTER active
SOURCE OPEN('orders.json')
FILTER AND(EQ(status, 'pending'), total)

Flatten nested arrays

SOURCE OPEN('invoices.json')  -- each invoice has a "lines" array
MAP_MANY lines
MAP product_id, quantity, price

Group and aggregate

SOURCE OPEN('sales.json')
GROUP_BY region MAP revenue = SUM(amount), orders = COUNT(amount)

Sort

SOURCE OPEN('products.json')
SORT_BY price DESC, name ASC

Limit

SOURCE OPEN('products.json')
SORT_BY price DESC
LIMIT 1024

Functions

FunctionDescription
SUM(field)Sum of numeric values (works on grouped arrays)
COUNT(field)Count of values
ARRAY(a, b, ...)Collect values into an array
UNZIPROWS(rows)Convert row objects into column arrays
JOINROWS({a, b}, key)Inner-join two object arrays on a shared key
JOINROWSINDEX({a, b}, key)Join the first array's key to the second array's index
CONCAT(a, b, ...)Concatenate strings
INDEXOF(array, value)Zero-based index of a value in an array, or -1
CONTAINS(array, value)true if the array contains the value
EQ(a, b)true if a equals b
AND(a, b, ...)true if all arguments are truthy
OR(a, b, ...)true if any argument is truthy
GETDATE(field)Extract the date part from an ISO datetime string
ISODATE(field)Convert a date like 24.03.2026 to 2026-03-24
MINDATE(field)Earliest date in a set
MAXDATE(field)Latest date in a set
BASE64(value)Base64-encode a value
COLOR(index)Deterministic RGB color for a zero-based index
OPTICS(matrix, config)Run OPTICS cluster analysis over a numeric matrix
OPEN(src) / GET(src)Load a file or URL (HTTP GET)
POST(src)HTTP POST
PUT(src)HTTP PUT

See docs/functions.md for full details and examples.

Values and Expressions

  • Field reference: name, address.city (dot-notation for nested fields)
  • Secret reference: @API_TOKEN (resolved at runtime)
  • String: 'hello' or "hello"
  • Number: 42, -3.14
  • Boolean: true, false
  • Inline object: {key: value, other: 'text'}
  • Inline array: [1, 2, 3]
  • Function call: SUM(amount)

See docs/values-and-expressions.md for full details.

When a query is run from the VS Code extension, secret references are loaded from the current process environment or a .env file in the query's workspace folder. Process environment values take precedence over .env values.

Development

Install locally

code --install-extension quickql-0.0.3.vsix

Build package

npm run check:binaries
vsce package

The universal VSIX bundles Windows x64, macOS Intel, and macOS Apple Silicon executables for both the query engine and language server. Build each target on its matching operating system, then collect the generated bin/<platform>-<arch> folders before packaging. The GitHub Actions workflow .github/workflows/package-universal.yml does this on tag pushes and manual runs. Package on macOS or Linux: check:binaries restores executable permissions for the macOS binaries after artifact downloads, before they are added to the VSIX. The extension also repairs missing owner execute permissions on bundled binaries before starting the query engine or language server.

Detailed Documentation

Niki123456123456/quickql

See the code

See what people are saying

SourceMessageScoreDate

QuickQL, a VS Code extension for SQL-like queries over JSON APIs (r/coolgithubprojects)

Hey everyone, During my apprenticeship, I worked with a large SQL database. Whenever I wanted to explore some data, I could just write a query and see what was there. Now I work with lots of microservices, and I missed that convenience. The data is still there, but it’s spread across different…

1

Oct 4, 2026

README

QuickQL (quick query language)

A lightweight pipeline query language for transforming JSON, CSV, and HTTP data sources. QuickQL queries are plain text files (.ql) that describe a sequence of data transformation steps executed top to bottom.

Quick Example

SOURCE OPEN('orders.json')
FILTER EQ(status, 'shipped')
MAP customer_id, total, shipped_date = GETDATE(shipped_at)
GROUP_BY customer_id MAP total = SUM(total), last_ship = MAXDATE(shipped_date)
SORT_BY last_ship DESC
LIMIT 1024

Statements

Each line in a .ql file is one pipeline step. Steps are separated by newlines; comments start with --.

StatementDescription
SOURCELoad data from a file, URL, or inline literal
MAPSelect, rename, or compute columns
FILTERKeep only rows matching a condition
MAP_MANYFlatten an array field into individual rows
GROUP_BYGroup rows by keys and aggregate
SORT_BYSort rows by one or more columns
LIMITKeep at most the first number of rows

Equivalents in other query APIs

The following examples assume a collection named rows. They show the closest conceptual equivalent; loading data and accessing dynamically typed fields depend on the database, serialization library, and data model in use.

QuickQL statementSQL.NET LINQ (C#)JavaScriptJava Stream APIRust iterators
SOURCE OPEN('data.json')SELECT * FROM sourceLoadRows("data.json")await loadRows("data.json")loadRows("data.json").stream()load_rows("data.json")?.into_iter()
MAP id, full_name = nameSELECT id, name AS full_namerows.Select(r => new { r.Id, FullName = r.Name })rows.map(({ id, name }) => ({ id, full_name: name }))rows.stream().map(r -> new Result(r.id(), r.name()))rows.into_iter().map(|r| Result { id: r.id, full_name: r.name })
FILTER EQ(status, 'active')WHERE status = 'active'rows.Where(r => r.Status == "active")rows.filter(r => r.status === "active")rows.stream().filter(r -> r.status().equals("active"))rows.into_iter().filter(|r| r.status == "active")
MAP_MANY linesCROSS JOIN UNNEST(lines) AS linerows.SelectMany(r => r.Lines)rows.flatMap(r => r.lines)rows.stream().flatMap(r -> r.lines().stream())rows.into_iter().flat_map(|r| r.lines)
GROUP_BY region MAP revenue = SUM(amount)SELECT region, SUM(amount) AS revenue FROM source GROUP BY regionrows.GroupBy(r => r.Region).Select(g => new { Region = g.Key, Revenue = g.Sum(r => r.Amount) })Map.groupBy(rows, r => r.region) + aggregaterows.stream().collect(groupingBy(Row::region, summingDouble(Row::amount)))rows.into_iter().into_group_map_by(|r| r.region.clone()) + aggregate
SORT_BY price DESC, name ASCORDER BY price DESC, name ASCrows.OrderByDescending(r => r.Price).ThenBy(r => r.Name)rows.toSorted((a, b) => b.price - a.price || a.name.localeCompare(b.name))rows.stream().sorted(comparing(Row::price).reversed().thenComparing(Row::name))rows.sort_by(|a, b| b.price.total_cmp(&a.price).then_with(|| a.name.cmp(&b.name)))
LIMIT 1024LIMIT 1024rows.Take(1024)rows.slice(0, 1024)rows.stream().limit(1024)rows.into_iter().take(1024)

Data Sources

QuickQL can read:

  • JSON files — array of objects or a single object/array
  • CSV files — automatically detected by .csv extension
  • HTTP endpoints — GET, POST, or PUT with optional headers, body, and pagination
  • Other .ql files — compose queries by referencing them as sources
-- JSON file
SOURCE OPEN('data/users.json')

-- CSV file
SOURCE OPEN('reports/sales.csv')

-- HTTP API
SOURCE GET('https://api.example.com/users')

-- Another query
SOURCE OPEN('other_query.ql')

-- Multiple sources merged
SOURCE OPEN('users.json'), OPEN('admins.json')

Transforming Data

Select and rename columns

SOURCE OPEN('users.json')
MAP id, name, email
SOURCE OPEN('users.json')
MAP id, full_name = name, contact = email

Compute new fields

SOURCE OPEN('orders.json')
MAP *, total_with_tax = SUM(total, tax)

Filter rows

SOURCE OPEN('users.json')
FILTER active
SOURCE OPEN('orders.json')
FILTER AND(EQ(status, 'pending'), total)

Flatten nested arrays

SOURCE OPEN('invoices.json')  -- each invoice has a "lines" array
MAP_MANY lines
MAP product_id, quantity, price

Group and aggregate

SOURCE OPEN('sales.json')
GROUP_BY region MAP revenue = SUM(amount), orders = COUNT(amount)

Sort

SOURCE OPEN('products.json')
SORT_BY price DESC, name ASC

Limit

SOURCE OPEN('products.json')
SORT_BY price DESC
LIMIT 1024

Functions

FunctionDescription
SUM(field)Sum of numeric values (works on grouped arrays)
COUNT(field)Count of values
ARRAY(a, b, ...)Collect values into an array
UNZIPROWS(rows)Convert row objects into column arrays
JOINROWS({a, b}, key)Inner-join two object arrays on a shared key
JOINROWSINDEX({a, b}, key)Join the first array's key to the second array's index
CONCAT(a, b, ...)Concatenate strings
INDEXOF(array, value)Zero-based index of a value in an array, or -1
CONTAINS(array, value)true if the array contains the value
EQ(a, b)true if a equals b
AND(a, b, ...)true if all arguments are truthy
OR(a, b, ...)true if any argument is truthy
GETDATE(field)Extract the date part from an ISO datetime string
ISODATE(field)Convert a date like 24.03.2026 to 2026-03-24
MINDATE(field)Earliest date in a set
MAXDATE(field)Latest date in a set
BASE64(value)Base64-encode a value
COLOR(index)Deterministic RGB color for a zero-based index
OPTICS(matrix, config)Run OPTICS cluster analysis over a numeric matrix
OPEN(src) / GET(src)Load a file or URL (HTTP GET)
POST(src)HTTP POST
PUT(src)HTTP PUT

See docs/functions.md for full details and examples.

Values and Expressions

  • Field reference: name, address.city (dot-notation for nested fields)
  • Secret reference: @API_TOKEN (resolved at runtime)
  • String: 'hello' or "hello"
  • Number: 42, -3.14
  • Boolean: true, false
  • Inline object: {key: value, other: 'text'}
  • Inline array: [1, 2, 3]
  • Function call: SUM(amount)

See docs/values-and-expressions.md for full details.

When a query is run from the VS Code extension, secret references are loaded from the current process environment or a .env file in the query's workspace folder. Process environment values take precedence over .env values.

Development

Install locally

code --install-extension quickql-0.0.3.vsix

Build package

npm run check:binaries
vsce package

The universal VSIX bundles Windows x64, macOS Intel, and macOS Apple Silicon executables for both the query engine and language server. Build each target on its matching operating system, then collect the generated bin/<platform>-<arch> folders before packaging. The GitHub Actions workflow .github/workflows/package-universal.yml does this on tag pushes and manual runs. Package on macOS or Linux: check:binaries restores executable permissions for the macOS binaries after artifact downloads, before they are added to the VSIX. The extension also repairs missing owner execute permissions on bundled binaries before starting the query engine or language server.

Detailed Documentation

Languages

Rust

83.1%

TypeScript

14.5%

JavaScript

2.1%