Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .agents/skills/cli-auth/references/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ csdk user list --where.searchTsv "search query" --select title,tsvRank
csdk user list --where.trgmDisplayName.value "approximate query" --where.trgmDisplayName.threshold 0.3 --select title,displayNameTrgmSimilarity
```

### Composite search (fullTextSearch dispatches to all text adapters)
### Composite search (unifiedSearch dispatches to all text adapters)

```bash
csdk user list --where.fullTextSearch "search query" --select title,tsvRank,displayNameTrgmSimilarity,searchScore
csdk user list --where.unifiedSearch "search query" --select title,tsvRank,displayNameTrgmSimilarity,searchScore
```

### Search with pagination and field projection

```bash
csdk user list --where.fullTextSearch "query" --limit 10 --select id,title,searchScore
csdk user list --where.unifiedSearch "query" --limit 10 --select id,title,searchScore
csdk user search "query" --limit 10 --select id,title,searchScore
```

Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/cli-public/references/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ csdk user list --where.searchTsv "search query" --select title,tsvRank
csdk user list --where.trgmDisplayName.value "approximate query" --where.trgmDisplayName.threshold 0.3 --select title,displayNameTrgmSimilarity
```

### Composite search (fullTextSearch dispatches to all text adapters)
### Composite search (unifiedSearch dispatches to all text adapters)

```bash
csdk user list --where.fullTextSearch "search query" --select title,tsvRank,displayNameTrgmSimilarity,searchScore
csdk user list --where.unifiedSearch "search query" --select title,tsvRank,displayNameTrgmSimilarity,searchScore
```

### Search with pagination and field projection

```bash
csdk user list --where.fullTextSearch "query" --limit 10 --select id,title,searchScore
csdk user list --where.unifiedSearch "query" --limit 10 --select id,title,searchScore
csdk user search "query" --limit 10 --select id,title,searchScore
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('@searchConfig integration tests', () => {
createPgvectorAdapter(),
],
enableSearchScore: true,
enableFullTextSearch: true,
enableUnifiedSearch: true,
});

// Inject @searchConfig on the articles table with custom weights
Expand Down Expand Up @@ -289,7 +289,7 @@ describe('@searchConfig with sigmoid normalization', () => {
createPgvectorAdapter(),
],
enableSearchScore: true,
enableFullTextSearch: true,
enableUnifiedSearch: true,
});

// Inject @searchConfig with sigmoid normalization
Expand Down
54 changes: 27 additions & 27 deletions graphile/graphile-search/src/__tests__/unified-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('graphile-search (unified search plugin)', () => {
createPgvectorAdapter(),
],
enableSearchScore: true,
enableFullTextSearch: true,
enableUnifiedSearch: true,
});

const testPreset = {
Expand Down Expand Up @@ -585,16 +585,16 @@ describe('graphile-search (unified search plugin)', () => {
expect(limitNodes[1].rowId).toBe(allNodes[1].rowId);
});

it('fullTextSearch + per-adapter orderBy: LIMIT returns top results', async () => {
// fullTextSearch dispatches to all text-compatible adapters.
it('unifiedSearch + per-adapter orderBy: LIMIT returns top results', async () => {
// unifiedSearch dispatches to all text-compatible adapters.
// Per-adapter orderBy (e.g. BM25 score) still works correctly with LIMIT
// because the adapter score is a SQL-level expression.
// (Note: SEARCH_SCORE is a JS-computed composite and does not produce
// SQL-level ORDER BY, so it should not be relied on with LIMIT.)
const allResult = await query<AllDocumentsResult>(`
query {
allDocuments(
where: { fullTextSearch: "machine learning" }
where: { unifiedSearch: "machine learning" }
orderBy: BODY_BM25_SCORE_ASC
) {
nodes { rowId title bodyBm25Score }
Expand All @@ -609,7 +609,7 @@ describe('graphile-search (unified search plugin)', () => {
const limitResult = await query<AllDocumentsResult>(`
query {
allDocuments(
where: { fullTextSearch: "machine learning" }
where: { unifiedSearch: "machine learning" }
orderBy: BODY_BM25_SCORE_ASC
first: 1
) {
Expand Down Expand Up @@ -732,18 +732,18 @@ describe('graphile-search (unified search plugin)', () => {
}
});

it('mega query v2: fullTextSearch + searchScore with composite ordering', async () => {
// Mega Query v2 — New-style: uses the unified `fullTextSearch` composite
it('mega query v2: unifiedSearch + searchScore with composite ordering', async () => {
// Mega Query v2 — New-style: uses the unified `unifiedSearch` composite
// filter that fans out to all text-compatible algorithms (tsvector, BM25, trgm)
// with a single string, plus a manual pgvector filter for semantic search.
// Orders by composite searchScore (highest overall relevance first).
const result = await query<AllDocumentsResult>(`
query MegaQueryV2_UnifiedSearch {
allDocuments(
where: {
# fullTextSearch: single string fans out to tsvector + BM25 + trgm
# unifiedSearch: single string fans out to tsvector + BM25 + trgm
# automatically — no need to specify each algorithm separately
fullTextSearch: "machine learning"
unifiedSearch: "machine learning"

# pgvector still needs its own filter (vectors aren't text)
vectorEmbedding: { vector: [1, 0, 0], metric: COSINE }
Expand All @@ -757,7 +757,7 @@ describe('graphile-search (unified search plugin)', () => {
title
body

# Per-adapter scores — populated by fullTextSearch for text algorithms
# Per-adapter scores — populated by unifiedSearch for text algorithms
tsvRank
bodyBm25Score
titleTrgmSimilarity
Expand Down Expand Up @@ -788,14 +788,14 @@ describe('graphile-search (unified search plugin)', () => {
});
});

// ─── fullTextSearch composite filter ────────────────────────────────────
// ─── unifiedSearch composite filter ────────────────────────────────────

describe('fullTextSearch composite filter', () => {
it('fullTextSearch field exists on the filter type', async () => {
describe('unifiedSearch composite filter', () => {
it('unifiedSearch field exists on the filter type', async () => {
const result = await query<AllDocumentsResult>(`
query {
allDocuments(where: {
fullTextSearch: "learning"
unifiedSearch: "learning"
}) {
nodes {
title
Expand All @@ -813,7 +813,7 @@ describe('graphile-search (unified search plugin)', () => {
const result = await query<AllDocumentsResult>(`
query {
allDocuments(where: {
fullTextSearch: "machine learning"
unifiedSearch: "machine learning"
}) {
nodes {
title
Expand Down Expand Up @@ -843,7 +843,7 @@ describe('graphile-search (unified search plugin)', () => {
const result = await query<AllDocumentsResult>(`
query {
allDocuments(where: {
fullTextSearch: "learning"
unifiedSearch: "learning"
tsvTsv: "machine"
}) {
nodes {
Expand All @@ -856,15 +856,15 @@ describe('graphile-search (unified search plugin)', () => {

expect(result.errors).toBeUndefined();
const nodes = result.data?.allDocuments?.nodes ?? [];
// The algorithm-specific filter (tsvTsv) narrows further within the fullTextSearch results
// The algorithm-specific filter (tsvTsv) narrows further within the unifiedSearch results
expect(nodes).toBeDefined();
});

it('returns empty results for nonsense query', async () => {
const result = await query<AllDocumentsResult>(`
query {
allDocuments(where: {
fullTextSearch: "xyzzy_nonexistent_term_12345"
unifiedSearch: "xyzzy_nonexistent_term_12345"
}) {
nodes {
title
Expand Down Expand Up @@ -908,22 +908,22 @@ describe('graphile-search (unified search plugin)', () => {
});
});

// ─── fullTextSearch disabled (separate suite — needs its own DB connection) ───
// ─── unifiedSearch disabled (separate suite — needs its own DB connection) ───

describe('fullTextSearch can be disabled', () => {
describe('unifiedSearch can be disabled', () => {
let disabledQuery: QueryFn;
let disabledTeardown: (() => Promise<void>) | undefined;

beforeAll(async () => {
// Build a plugin with fullTextSearch DISABLED
// Build a plugin with unifiedSearch DISABLED
const disabledPlugin = createUnifiedSearchPlugin({
adapters: [
createTsvectorAdapter(),
createBm25Adapter(),
createTrgmAdapter({ defaultThreshold: 0.1 }),
],
enableSearchScore: true,
enableFullTextSearch: false,
enableUnifiedSearch: false,
});

const disabledPreset = {
Expand Down Expand Up @@ -956,8 +956,8 @@ describe('fullTextSearch can be disabled', () => {
}
});

it('fullTextSearch field does NOT exist when disabled', async () => {
// Introspect the filter type to verify fullTextSearch is NOT present.
it('unifiedSearch field does NOT exist when disabled', async () => {
// Introspect the filter type to verify unifiedSearch is NOT present.
// NOTE: Grafast silently ignores unknown input fields rather than
// returning a GraphQL validation error, so we verify via introspection.
const introspection = await disabledQuery<any>(`
Expand All @@ -973,10 +973,10 @@ describe('fullTextSearch can be disabled', () => {
expect(introspection.errors).toBeUndefined();
const filterFields = introspection.data?.__type?.inputFields?.map((f: any) => f.name) ?? [];

// fullTextSearch should NOT be in the filter fields
expect(filterFields).not.toContain('fullTextSearch');
// unifiedSearch should NOT be in the filter fields
expect(filterFields).not.toContain('unifiedSearch');

// The per-algorithm fields should still exist (only fullTextSearch is disabled)
// The per-algorithm fields should still exist (only unifiedSearch is disabled)
expect(filterFields).toContain('tsvTsv');
expect(filterFields).toContain('bm25Body');
expect(filterFields).toContain('trgmTitle');
Expand Down
14 changes: 7 additions & 7 deletions graphile/graphile-search/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ interface AdapterColumnCache {
export function createUnifiedSearchPlugin(
options: UnifiedSearchOptions
): GraphileConfig.Plugin {
const { adapters, enableSearchScore = true, enableFullTextSearch = true } = options;
const { adapters, enableSearchScore = true, enableUnifiedSearch = true } = options;

// Per-codec cache of discovered columns, keyed by codec name
const codecCache = new Map<string, AdapterColumnCache[]>();
Expand Down Expand Up @@ -857,18 +857,18 @@ export function createUnifiedSearchPlugin(
}
}

// ── fullTextSearch composite filter ──
// Adds a single `fullTextSearch: String` field that fans out the same
// ── unifiedSearch composite filter ──
// Adds a single `unifiedSearch: String` field that fans out the same
// text query to all adapters where supportsTextSearch is true.
// WHERE clauses are combined with OR (match ANY algorithm).
if (enableFullTextSearch) {
if (enableUnifiedSearch) {
// Collect text-compatible adapters and their columns for this codec
const textAdapterColumns = adapterColumns.filter(
(ac) => ac.adapter.supportsTextSearch && ac.adapter.buildTextSearchInput
);

if (textAdapterColumns.length > 0) {
const fieldName = 'fullTextSearch';
const fieldName = 'unifiedSearch';

newFields = build.extend(
newFields,
Expand All @@ -880,7 +880,7 @@ export function createUnifiedSearchPlugin(
} as any,
{
description: build.wrapDescription(
'Composite full-text search. Provide a search string and it will be dispatched ' +
'Composite unified search. Provide a search string and it will be dispatched ' +
'to all text-compatible search algorithms (tsvector, BM25, pg_trgm) simultaneously. ' +
'Rows matching ANY algorithm are returned. All matching score fields are populated.',
'field'
Expand Down Expand Up @@ -956,7 +956,7 @@ export function createUnifiedSearchPlugin(
}
),
},
`UnifiedSearchPlugin adding fullTextSearch composite filter on '${codec.name}'`
`UnifiedSearchPlugin adding unifiedSearch composite filter on '${codec.name}'`
);
}
}
Expand Down
8 changes: 4 additions & 4 deletions graphile/graphile-search/src/preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ export interface UnifiedSearchPresetOptions {
enableSearchScore?: boolean;

/**
* Whether to expose the composite `fullTextSearch` filter field.
* Whether to expose the composite `unifiedSearch` filter field.
* @default true
*/
enableFullTextSearch?: boolean;
enableUnifiedSearch?: boolean;

/**
* Custom weights for the composite searchScore.
Expand Down Expand Up @@ -105,7 +105,7 @@ export function UnifiedSearchPreset(
trgm = true,
pgvector = true,
enableSearchScore = true,
enableFullTextSearch = true,
enableUnifiedSearch = true,
searchScoreWeights,
fullTextScalarName = 'FullText',
tsConfig = 'english',
Expand Down Expand Up @@ -136,7 +136,7 @@ export function UnifiedSearchPreset(
const pluginOptions: UnifiedSearchOptions = {
adapters,
enableSearchScore,
enableFullTextSearch,
enableUnifiedSearch,
searchScoreWeights,
};

Expand Down
10 changes: 5 additions & 5 deletions graphile/graphile-search/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export interface SearchAdapter {
/**
* Whether this adapter supports plain text search queries.
* If true, the adapter's columns will be included in the automatic
* `fullTextSearch` composite filter that fans out the same text query
* `unifiedSearch` composite filter that fans out the same text query
* to all text-compatible adapters simultaneously.
*
* Adapters that require non-text input (e.g. pgvector needs a vector array)
Expand All @@ -125,7 +125,7 @@ export interface SearchAdapter {
supportsTextSearch?: boolean;

/**
* Build the filter value for a text search query dispatched by fullTextSearch.
* Build the filter value for a text search query dispatched by unifiedSearch.
* Only called when supportsTextSearch is true.
* Converts a plain text string into the adapter-specific filter input format.
*
Expand Down Expand Up @@ -209,14 +209,14 @@ export interface UnifiedSearchOptions {
enableSearchScore?: boolean;

/**
* Whether to expose the `fullTextSearch` composite filter field.
* Whether to expose the `unifiedSearch` composite filter field.
* When enabled, every table with at least one text-compatible adapter gets a
* `fullTextSearch: String` field on its filter type. Providing a value fans
* `unifiedSearch: String` field on its filter type. Providing a value fans
* out the same text query to all adapters where `supportsTextSearch: true`,
* combining their WHERE clauses with OR (match any algorithm).
* @default true
*/
enableFullTextSearch?: boolean;
enableUnifiedSearch?: boolean;

/**
* Custom weights for the composite searchScore. Keys are adapter names,
Expand Down
8 changes: 4 additions & 4 deletions graphql/codegen/src/core/codegen/docs-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export function buildSearchExamples(
}
}

// Composite fullTextSearch example (dispatches to all text adapters)
// Composite unifiedSearch example (dispatches to all text adapters)
const hasTextSearch = specialGroups.some(
(g) => g.category === 'search' && g.fields.some(
(f) => f.type.gqlType === 'FullText' || /TrgmSimilarity$/.test(f.name) || /Bm25Score$/.test(f.name),
Expand All @@ -404,9 +404,9 @@ export function buildSearchExamples(
? `title,${[...new Set(scoreFields)].join(',')}`
: 'title';
examples.push({
description: 'Composite search (fullTextSearch dispatches to all text adapters)',
description: 'Composite search (unifiedSearch dispatches to all text adapters)',
code: [
`${toolName} ${cmd} list --where.fullTextSearch "search query" --select ${fieldsArg}`,
`${toolName} ${cmd} list --where.unifiedSearch "search query" --select ${fieldsArg}`,
],
});
}
Expand All @@ -416,7 +416,7 @@ export function buildSearchExamples(
examples.push({
description: 'Search with pagination and field projection',
code: [
`${toolName} ${cmd} list --where.fullTextSearch "query" --limit 10 --select id,title,searchScore`,
`${toolName} ${cmd} list --where.unifiedSearch "query" --limit 10 --select id,title,searchScore`,
`${toolName} ${cmd} search "query" --limit 10 --select id,title,searchScore`,
],
});
Expand Down
Loading
Loading