logo

qbjs

Core Concepts

QueryAST

Understanding the Abstract Syntax Tree structure in qbjs

QueryAST

The QueryAST is the central data structure in qbjs. It represents a parsed query in a normalized, type-safe format that serves as the contract between the parser and compilers.

Structure Overview

interface QueryAST {
  fields: string[] | null;
  pagination: Pagination;
  sort: SortSpec[];
  filter: FilterNode | null;
}

Fields

The fields property specifies which columns to select from the database.

// Select specific fields
fields: ["id", "name", "email"]

// Select all fields (null means no restriction)
fields: null

When fields is null, the compiler will select all columns from the table. When it's an array, only those specific columns are selected.

Pagination

The pagination property controls result set size and offset using offset-based pagination.

interface Pagination {
  offset: number;  // Number of items to skip
  limit: number;   // Maximum items to return
}

Example values:

// Page 1 with 10 items per page
pagination: { offset: 0, limit: 10 }

// Page 3 with 20 items per page
pagination: { offset: 40, limit: 20 }

The parser automatically converts page numbers to offsets:

  • page=1, limit=10offset: 0, limit: 10
  • page=2, limit=10offset: 10, limit: 10
  • page=3, limit=20offset: 40, limit: 20

Sort

The sort property is an array of sort specifications, allowing multi-column sorting.

interface SortSpec {
  field: string;
  direction: "asc" | "desc";
}

Example values:

// Sort by createdAt descending
sort: [{ field: "createdAt", direction: "desc" }]

// Sort by status ascending, then by createdAt descending
sort: [
  { field: "status", direction: "asc" },
  { field: "createdAt", direction: "desc" }
]

// No sorting
sort: []

Filter

The filter property is a tree structure that can represent simple conditions or complex logical expressions.

FilterNode Types

A FilterNode is a discriminated union of two types:

type FilterNode = FieldFilter | LogicalFilter;

FieldFilter

A field filter represents a condition on a single field:

interface FieldFilter {
  type: "field";
  field: string;
  operator: FilterOperator;
  value: unknown;
}

Example:

// status equals "active"
{
  type: "field",
  field: "status",
  operator: "eq",
  value: "active"
}

// age greater than 18
{
  type: "field",
  field: "age",
  operator: "gt",
  value: 18
}

LogicalFilter

A logical filter combines multiple conditions:

interface LogicalFilter {
  type: "logical";
  operator: "and" | "or" | "not";
  conditions: FilterNode[];
}

Example:

// status = "active" AND role = "admin"
{
  type: "logical",
  operator: "and",
  conditions: [
    { type: "field", field: "status", operator: "eq", value: "active" },
    { type: "field", field: "role", operator: "eq", value: "admin" }
  ]
}

Filter Operators

qbjs supports these filter operators:

OperatorDescriptionExample Value
eqEquals"active"
eqiEquals (case-insensitive)"Active"
neNot equals"deleted"
neiNot equals (case-insensitive)"Deleted"
ltLess than100
lteLess than or equal100
gtGreater than0
gteGreater than or equal18
inIn array["a", "b", "c"]
notInNot in array["x", "y"]
containsContains substring"search"
containsiContains (case-insensitive)"Search"
notContainsDoes not contain"spam"
notContainsiDoes not contain (case-insensitive)"Spam"
startsWithStarts with"prefix"
endsWithEnds with"suffix"
nullIs nulltrue
notNullIs not nulltrue
betweenBetween two values[10, 20]

Complete AST Example

Here's a complete QueryAST representing a complex query:

const ast: QueryAST = {
  // Select only these fields
  fields: ["id", "title", "status", "createdAt"],
  
  // Page 2 with 20 items per page
  pagination: {
    offset: 20,
    limit: 20
  },
  
  // Sort by status ascending, then createdAt descending
  sort: [
    { field: "status", direction: "asc" },
    { field: "createdAt", direction: "desc" }
  ],
  
  // Complex filter: (status = "published" OR status = "draft") AND title contains "typescript"
  filter: {
    type: "logical",
    operator: "and",
    conditions: [
      {
        type: "logical",
        operator: "or",
        conditions: [
          { type: "field", field: "status", operator: "eq", value: "published" },
          { type: "field", field: "status", operator: "eq", value: "draft" }
        ]
      },
      {
        type: "field",
        field: "title",
        operator: "containsi",
        value: "typescript"
      }
    ]
  }
};

Type Guards

qbjs provides type guards for working with filter nodes:

import { isFieldFilter, isLogicalFilter } from "@qbjs/core";

function processFilter(node: FilterNode) {
  if (isFieldFilter(node)) {
    // TypeScript knows node is FieldFilter
    console.log(`Field: ${node.field}, Operator: ${node.operator}`);
  }
  
  if (isLogicalFilter(node)) {
    // TypeScript knows node is LogicalFilter
    console.log(`Logical: ${node.operator}, Conditions: ${node.conditions.length}`);
  }
}

Next Steps

On this page