Moose provides type-safe SQL querying for your OlapTable and MaterializedView instances. Use cases include:
Use MooseClient to query data from existing tables and materialized views.
import { MooseClient, sql } from "@514labs/moose-lib";import { UserTable } from "./tables/UserTable"; const client = new MooseClient(); // Query existing tableconst query = sql` SELECT id, name, email FROM ${UserTable} WHERE status = 'active' LIMIT 10`; const result = await client.query.execute(query);const data = await result.json();import { MooseClient, sql } from "@514labs/moose-lib"; const client = new MooseClient(); // Query existing materialized viewconst query = sql` SELECT user_id, total_orders, average_order_value FROM user_stats_view WHERE total_orders > 10 ORDER BY average_order_value DESC`; const result = await client.query.execute(query);import { sql } from "@514labs/moose-lib";import { UserTable, OrderTable } from "./tables"; // Reference table columns with type safetyconst cols = UserTable.columns;const query = sql` SELECT ${cols.id}, ${cols.name}, ${cols.email} FROM ${UserTable} WHERE ${cols.status} = 'active'`; // Multiple table referencesconst joinQuery = sql` SELECT ${UserTable.columns.id}, ${UserTable.columns.name}, ${OrderTable.columns.order_value} FROM ${UserTable} JOIN ${OrderTable} ON ${UserTable.columns.id} = ${OrderTable.columns.user_id}`;When you query a materialized view, you reference the MaterializedView.targetTable to get the columns of the target table.
import { sql } from "@514labs/moose-lib";import { ExampleMaterializedView } from "./materialized-views"; const query = sql` SELECT ${ExampleMaterializedView.targetTable.columns.id}, ${ExampleMaterializedView.targetTable.columns.name}, ${ExampleMaterializedView.targetTable.columns.email} FROM ${ExampleMaterializedView.targetTable}`;In ClickHouse, when you query a Materialized View that has columns of type AggregateFunction in the result set, ordinarily you would need to run:
SELECT sumMerge(amount) FROM {ExampleMaterializedView}When querying this with Moose, you can just reference the column name in the sql template literal. The interpolation will be replaced with the correct ClickHouse function:
import { sql } from "@514labs/moose-lib";import { ExampleMaterializedView } from "./materialized-views"; const query = sql` SELECT ${ExampleMaterializedView.targetTable.columns.amount} FROM ${ExampleMaterializedView.targetTable}`; // This will be replaced with:// SELECT sumMerge(amount) FROM {ExampleMaterializedView}import { sql } from "@514labs/moose-lib"; // Multiple WHERE conditionsconst filterQuery = sql` SELECT ${UserTable.columns.id}, ${UserTable.columns.name} FROM ${UserTable} WHERE ${UserTable.columns.status} = 'active' AND ${UserTable.columns.created_at} > '2024-01-01' AND ${UserTable.columns.email} ILIKE ${'%' + searchTerm + '%'}`; // Using IN clausesconst inQuery = sql` SELECT * FROM ${UserTable} WHERE ${UserTable.columns.id} IN (${userIds})`; // Using BETWEENconst rangeQuery = sql` SELECT * FROM ${UserTable} WHERE ${UserTable.columns.age} BETWEEN ${minAge} AND ${maxAge}`;Use the sql template literal to build safe queries:
import { sql } from "@514labs/moose-lib"; // Safe interpolation with sql template literalconst status = 'active';const limit = 10; const query = sql` SELECT id, name, email FROM ${UserTable} WHERE ${UserTable.columns.status} = ${status} LIMIT ${limit}`; // Conditional WHERE clausesinterface FilterParams { minAge?: number; status?: "active" | "inactive"; searchText?: string;} const buildConditionalQuery = (filters: FilterParams) => { let conditions = []; if (filters.minAge !== undefined) { conditions.push(sql`age >= ${filters.minAge}`); } if (filters.status) { conditions.push(sql`status = ${filters.status}`); } if (filters.searchText) { conditions.push(sql`(name ILIKE ${'%' + filters.searchText + '%'} OR email ILIKE ${'%' + filters.searchText + '%'})`); } let query = sql`SELECT * FROM ${UserTable}`; if (conditions.length > 0) { query = sql`${query} WHERE ${conditions.join(' AND ')}`; } return sql`${query} ORDER BY created_at DESC`;};To build REST APIs that expose your data, see the Bring Your Own API Framework documentation for comprehensive examples and patterns using Express, Koa, Fastify, or FastAPI.
UserTable.columns.columnName for autocompletesql not regular stringsclient.query.execute()If your query is slower than expected, there are a few things you can check:
orderByFields of the table