Skip to content

Commit

Permalink
Merge pull request #202 from gridaco/database/filter
Browse files Browse the repository at this point in the history
Easy filter templates (extended predicate templates)
  • Loading branch information
softmarshmallow authored Oct 11, 2024
2 parents cef7563 + 87e362a commit f5b8345
Show file tree
Hide file tree
Showing 13 changed files with 713 additions and 323 deletions.
609 changes: 531 additions & 78 deletions apps/forms/lib/data/index.ts

Large diffs are not rendered by default.

55 changes: 18 additions & 37 deletions apps/forms/lib/supabase-postgrest/builder.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,19 @@
import type { SQLOrderBy, SQLPredicate } from "@/types";
import {
PostgrestQueryBuilder,
type PostgrestFilterBuilder,
type PostgrestSingleResponse,
type PostgrestTransformBuilder,
} from "@supabase/postgrest-js";
import type { SupabaseClient } from "@supabase/supabase-js";
import { Data } from "../data";
import { Data } from "@/lib/data";

export namespace XPostgrestQuery {
export namespace PredicateOperator {
export type SQLPredicateOperatorKeyword =
| "eq" // Equals
| "neq" // Not Equals
| "gt" // Greater Than
| "gte" // Greater Than or Equal
| "lt" // Less Than
| "lte" // Less Than or Equal
| "like" // Case-Sensitive Pattern Match
| "ilike" // Case-Insensitive Pattern Match
| "is" // Is (NULL check, etc.)
| "in" // In (list of values)
| "cs" // Contains (JSON/Array containment)
| "cd" // Contained By (JSON/Array containment)
| "sl" // Strictly Left of (range operation)
| "sr" // Strictly Right of (range operation)
| "nxl" // Not Extends to the Left of (range operation)
| "nxr" // Not Extends to the Right of (range operation)
| "adj" // Adjacent to (range operation)
| "ov" // Overlaps (range operation)
| "fts" // Full-Text Search
| "plfts" // Phrase Full-Text Search
| "phfts" // Plain Full-Text Search
| "wfts"; // Web Full-Text Search

export type PostgRESTSQLPredicateNegateOperatorKeyword =
`not.${SQLPredicateOperatorKeyword}`;
`not.${Data.Query.Predicate.PredicateOperatorKeyword}`;

export type PostgRESTSQLPredicateOperators =
| SQLPredicateOperatorKeyword
| Data.Query.Predicate.PredicateOperatorKeyword
| PostgRESTSQLPredicateNegateOperatorKeyword;

/**
Expand Down Expand Up @@ -76,17 +51,20 @@ export namespace XPostgrestQuery {
* ```
*/
export function analyzeOperator(operator: PostgRESTSQLPredicateOperators): {
keyword: SQLPredicateOperatorKeyword;
keyword: Data.Query.Predicate.PredicateOperatorKeyword;
negate: boolean;
} {
if (operator.startsWith("not.")) {
return {
keyword: operator.replace("not.", "") as SQLPredicateOperatorKeyword,
keyword: operator.replace(
"not.",
""
) as Data.Query.Predicate.PredicateOperatorKeyword,
negate: true,
};
}
return {
keyword: operator as SQLPredicateOperatorKeyword,
keyword: operator as Data.Query.Predicate.PredicateOperatorKeyword,
negate: false,
};
}
Expand All @@ -109,7 +87,7 @@ export namespace XPostgrestQuery {
values: ReadonlyArray<unknown>;
}

export type OrderBy = { [col: string]: SQLOrderBy };
export type OrderBy = { [col: string]: Data.Query.OrderBy.SQLOrderBy };

/**
* modular, partial postgrest query string builder / parser
Expand All @@ -130,8 +108,8 @@ export namespace XPostgrestQuery {
columns?: string;
limit?: number;
range?: { from: number; to: number };
order?: { [col: string]: SQLOrderBy };
filters?: ReadonlyArray<SQLPredicate>;
order?: { [col: string]: Data.Query.OrderBy.SQLOrderBy };
filters?: ReadonlyArray<Data.Query.Predicate.SQLPredicate>;
textSearch?: Data.Query.Predicate.TextSearchQuery;
}) {
const pq = new PostgrestQueryBuilder(new URL("noop:noop"), {});
Expand Down Expand Up @@ -181,6 +159,10 @@ export namespace XPostgrestQuery {
}

export function fromQueryState(query: Partial<Data.Relation.QueryState>) {
const sql_predicates = query.q_predicates
?.map(Data.Query.Predicate.Extension.encode)
?.filter(Data.Query.Predicate.is_predicate_fulfilled);

const search = XPostgrestQuery.QS.select({
limit: query.q_page_limit,
order: query.q_orderby,
Expand All @@ -191,8 +173,7 @@ export namespace XPostgrestQuery {
to: (query.q_page_index + 1) * query.q_page_limit - 1,
}
: undefined,
// only pass predicates with value set
filters: query.q_predicates?.filter((p) => p.value ?? false),
filters: sql_predicates,
textSearch: query.q_text_search ?? undefined,
});

Expand All @@ -211,7 +192,7 @@ export namespace XPostgrestQuery {
const column = parts[0];
if (!column) return; // Skip empty column names

const orderBy: SQLOrderBy = { column };
const orderBy: Data.Query.OrderBy.SQLOrderBy = { column };

// Default order is ascending
orderBy.ascending = parts.includes("asc");
Expand Down
126 changes: 0 additions & 126 deletions apps/forms/lib/x-supabase/typemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,132 +98,6 @@ export namespace PostgresTypeTools {
return undefined;
}

export function getPredicateOperators({
format,
}: {
format: SupabasePostgRESTOpenApi.PostgRESTOpenAPIDefinitionPropertyFormatType;
}): XPostgrestQuery.PredicateOperator.SQLPredicateOperatorKeyword[] {
const _get_for_non_array = (
format: PGSupportedColumnType
): XPostgrestQuery.PredicateOperator.SQLPredicateOperatorKeyword[] => {
switch (format) {
case "int":
case "int2":
case "int4":
case "int8":
case "smallint":
case "integer":
case "bigint":
case "decimal":
case "numeric":
case "real":
case "float":
case "float4":
case "float8":
case "double precision":
case "money":
return ["eq", "neq", "gt", "gte", "lt", "lte", "is", "in"];

case "character varying":
case "varchar":
case "character":
case "char":
case "text":
case "citext":
return [
"eq",
"neq",
"like",
"ilike",
"is",
"in",
"fts",
"plfts",
"phfts",
"wfts",
];

case "bool":
case "boolean":
return ["eq", "neq", "is"];

case "json":
case "jsonb":
case "hstore":
return ["eq", "neq", "is", "cs", "cd", "ov"];

case "tsvector":
case "tsquery":
return ["eq", "neq", "fts", "plfts", "phfts", "wfts"];

case "uuid":
case "xml":
case "inet":
case "cidr":
case "macaddr":
return ["eq", "neq", "is", "in"];

case "date":
case "timestamp":
case "timestamptz":
case "timestamp without time zone":
case "timestamp with time zone":
case "time":
case "time without time zone":
case "time with time zone":
case "timetz":
case "interval":
return ["eq", "neq", "gt", "gte", "lt", "lte", "is", "in"];

case "point":
case "line":
case "lseg":
case "box":
case "path":
case "polygon":
case "circle":
return ["eq", "neq", "is", "sl", "sr", "nxl", "nxr", "adj", "ov"];

case "int4range":
case "int8range":
case "numrange":
case "tsrange":
case "tstzrange":
case "daterange":
case "int4multirange":
case "int8multirange":
case "nummultirange":
case "tsmultirange":
case "tstzmultirange":
case "datemultirange":
return [
"eq",
"neq",
"is",
"sl",
"sr",
"nxl",
"nxr",
"adj",
"ov",
"cs",
"cd",
];

default:
return ["eq", "neq", "is"];
}
};

if (format.includes("[]")) {
const baseFormat = format.replace("[]", "") as PGSupportedColumnType;
// For array types, the operators "cs" (contains) and "cd" (contained by) are typically used
return [..._get_for_non_array(baseFormat), "cs", "cd"];
} else {
return _get_for_non_array(format as PGSupportedColumnType);
}
}

export type SQLLiteralInputConfig =
| { type: "text" }
| { type: "boolean" }
Expand Down
8 changes: 4 additions & 4 deletions apps/forms/scaffolds/data-query/data-query.action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SQLOrderBy, SQLPredicate } from "@/types";
import type { Data } from "@/lib/data";

export type DataQueryAction =
| DataQueryRefreshAction
Expand Down Expand Up @@ -36,7 +36,7 @@ export interface DataQueryPaginateAction {
export interface DataQueryOrderByUpsertAction {
type: "data/query/orderby";
column_id: string;
data: Omit<SQLOrderBy, "column">;
data: Omit<Data.Query.OrderBy.SQLOrderBy, "column">;
}

export interface DataQueryOrderByRemoveAction {
Expand All @@ -50,13 +50,13 @@ export interface DataQueryOrderByClearAction {

export interface DataQueryPredicatesAddAction {
type: "data/query/predicates/add";
predicate: SQLPredicate;
predicate: Data.Query.Predicate.ExtendedPredicate;
}

export interface DataQueryPredicatesUpdateAction {
type: "data/query/predicates/update";
index: number;
predicate: Partial<SQLPredicate>;
predicate: Partial<Data.Query.Predicate.ExtendedPredicate>;
}

export interface DataQueryPredicatesRemoveAction {
Expand Down
18 changes: 13 additions & 5 deletions apps/forms/scaffolds/data-query/data-query.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React, {
} from "react";
import { Data } from "@/lib/data";
import type { DataQueryAction } from "./data-query.action";
import type { SQLOrderBy, SQLPredicate } from "@/types";
import type {
DataQueryOrderbyAddDispatcher,
DataQueryOrderbyRemoveAllDispatcher,
Expand Down Expand Up @@ -158,7 +157,10 @@ export function useStandaloneSchemaDataQueryConsumer(
const orderbyUsedKeys = Object.keys(q_orderby);

const onOrderbyAdd: DataQueryOrderbyAddDispatcher = useCallback(
(column_id: string, initial?: Partial<Omit<SQLOrderBy, "column">>) => {
(
column_id: string,
initial?: Partial<Omit<Data.Query.OrderBy.SQLOrderBy, "column">>
) => {
dispatch({
type: "data/query/orderby",
column_id: column_id,
Expand All @@ -169,7 +171,10 @@ export function useStandaloneSchemaDataQueryConsumer(
);

const onOrderbyUpdate: DataQueryOrderbyUpdateDispatcher = useCallback(
(column_id: string, data: Partial<Omit<SQLOrderBy, "column">>) => {
(
column_id: string,
data: Partial<Omit<Data.Query.OrderBy.SQLOrderBy, "column">>
) => {
dispatch({
type: "data/query/orderby",
column_id: column_id,
Expand Down Expand Up @@ -201,7 +206,7 @@ export function useStandaloneSchemaDataQueryConsumer(
const isPredicatesSet = q_predicates.length > 0;

const onPredicatesAdd: DataQueryPredicateAddDispatcher = useCallback(
(predicate: SQLPredicate) => {
(predicate: Data.Query.Predicate.ExtendedPredicate) => {
dispatch({
type: "data/query/predicates/add",
predicate: predicate,
Expand All @@ -211,7 +216,10 @@ export function useStandaloneSchemaDataQueryConsumer(
);

const onPredicatesUpdate: DataQueryPredicateUpdateDispatcher = useCallback(
(index: number, predicate: Partial<SQLPredicate>) => {
(
index: number,
predicate: Partial<Data.Query.Predicate.ExtendedPredicate>
) => {
dispatch({
type: "data/query/predicates/update",
index: index,
Expand Down
Loading

0 comments on commit f5b8345

Please sign in to comment.