@backstage/plugin-search-backend-module-pg 0.4.3-next.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @backstage/plugin-search-backend-module-pg
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e48fc1f1ae: Added the option to pass a logger to `PgSearchEngine` during instantiation. You may do so as follows:
8
+
9
+ ```diff
10
+ const searchEngine = await PgSearchEngine.fromConfig(env.config, {
11
+ database: env.database,
12
+ + logger: env.logger,
13
+ });
14
+ ```
15
+
16
+ - dff9843718: The search engine now better handles the case when it receives 0 documents at index-time. Prior to this change, the indexer would replace any existing index with an empty index, effectively deleting it. Now instead, a warning is logged, and any existing index is left alone (preserving the index from the last successful indexing attempt).
17
+
18
+ ### Patch Changes
19
+
20
+ - c507aee8a2: Ensured typescript type checks in migration files.
21
+ - Updated dependencies
22
+ - @backstage/plugin-search-backend-node@1.1.0
23
+ - @backstage/backend-common@0.17.0
24
+ - @backstage/plugin-search-common@1.2.0
25
+ - @backstage/config@1.0.5
26
+
3
27
  ## 0.4.3-next.3
4
28
 
5
29
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -129,8 +129,10 @@ class DatabaseDocumentStore {
129
129
  class PgSearchEngineIndexer extends pluginSearchBackendNode.BatchSearchEngineIndexer {
130
130
  constructor(options) {
131
131
  super({ batchSize: options.batchSize });
132
+ this.numRecords = 0;
132
133
  this.store = options.databaseStore;
133
134
  this.type = options.type;
135
+ this.logger = options.logger || backendCommon.getVoidLogger();
134
136
  }
135
137
  async initialize() {
136
138
  this.tx = await this.store.getTransaction();
@@ -142,6 +144,7 @@ class PgSearchEngineIndexer extends pluginSearchBackendNode.BatchSearchEngineInd
142
144
  }
143
145
  }
144
146
  async index(documents) {
147
+ this.numRecords += documents.length;
145
148
  try {
146
149
  await this.store.insertDocuments(this.tx, this.type, documents);
147
150
  } catch (e) {
@@ -150,6 +153,13 @@ class PgSearchEngineIndexer extends pluginSearchBackendNode.BatchSearchEngineInd
150
153
  }
151
154
  }
152
155
  async finalize() {
156
+ if (this.numRecords === 0) {
157
+ this.logger.warn(
158
+ `Index for ${this.type} was not replaced: indexer received 0 documents`
159
+ );
160
+ this.tx.rollback();
161
+ return;
162
+ }
153
163
  try {
154
164
  await this.store.completeInsert(this.tx, this.type);
155
165
  this.tx.commit();
@@ -161,7 +171,7 @@ class PgSearchEngineIndexer extends pluginSearchBackendNode.BatchSearchEngineInd
161
171
  }
162
172
 
163
173
  class PgSearchEngine {
164
- constructor(databaseStore, config) {
174
+ constructor(databaseStore, config, logger) {
165
175
  this.databaseStore = databaseStore;
166
176
  var _a, _b, _c, _d, _e, _f, _g;
167
177
  const uuidTag = uuid.v4();
@@ -180,17 +190,20 @@ class PgSearchEngine {
180
190
  fragmentDelimiter: (_g = highlightConfig == null ? void 0 : highlightConfig.getOptionalString("fragmentDelimiter")) != null ? _g : " ... "
181
191
  };
182
192
  this.highlightOptions = highlightOptions;
193
+ this.logger = logger;
183
194
  }
184
195
  static async from(options) {
185
196
  return new PgSearchEngine(
186
197
  await DatabaseDocumentStore.create(options.database),
187
- options.config
198
+ options.config,
199
+ options.logger
188
200
  );
189
201
  }
190
202
  static async fromConfig(config, options) {
191
203
  return new PgSearchEngine(
192
204
  await DatabaseDocumentStore.create(options.database),
193
- config
205
+ config,
206
+ options.logger
194
207
  );
195
208
  }
196
209
  static async supported(database) {
@@ -217,10 +230,12 @@ class PgSearchEngine {
217
230
  this.translator = translator;
218
231
  }
219
232
  async getIndexer(type) {
233
+ var _a;
220
234
  return new PgSearchEngineIndexer({
221
235
  batchSize: 1e3,
222
236
  type,
223
- databaseStore: this.databaseStore
237
+ databaseStore: this.databaseStore,
238
+ logger: (_a = this.logger) == null ? void 0 : _a.child({ documentType: type })
224
239
  });
225
240
  }
226
241
  async query(query) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/database/util.ts","../src/database/DatabaseDocumentStore.ts","../src/PgSearchEngine/PgSearchEngineIndexer.ts","../src/PgSearchEngine/PgSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Knex } from 'knex';\n\nexport async function queryPostgresMajorVersion(knex: Knex): Promise<number> {\n if (knex.client.config.client !== 'pg') {\n throw new Error(\"Can't resolve version, not a postgres database\");\n }\n\n const { rows } = await knex.raw('SHOW server_version_num');\n const [result] = rows;\n const version = +result.server_version_num;\n const majorVersion = Math.floor(version / 10000);\n return majorVersion;\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n PluginDatabaseManager,\n resolvePackagePath,\n} from '@backstage/backend-common';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\nimport { Knex } from 'knex';\nimport {\n DatabaseStore,\n DocumentResultRow,\n PgSearchQuery,\n RawDocumentRow,\n} from './types';\nimport { queryPostgresMajorVersion } from './util';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-search-backend-module-pg',\n 'migrations',\n);\n\n/** @public */\nexport class DatabaseDocumentStore implements DatabaseStore {\n static async create(\n database: PluginDatabaseManager,\n ): Promise<DatabaseDocumentStore> {\n const knex = await database.getClient();\n try {\n const majorVersion = await queryPostgresMajorVersion(knex);\n\n if (majorVersion < 12) {\n // We are using some features (like generated columns) that aren't\n // available in older postgres versions.\n throw new Error(\n `The PgSearchEngine requires at least postgres version 12 (but is running on ${majorVersion})`,\n );\n }\n } catch {\n // Actually both mysql and sqlite have a full text search, too. We could\n // implement them separately or add them here.\n throw new Error(\n 'The PgSearchEngine is only supported when using a postgres database (>=12.x)',\n );\n }\n\n if (!database.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new DatabaseDocumentStore(knex);\n }\n\n static async supported(knex: Knex): Promise<boolean> {\n try {\n const majorVersion = await queryPostgresMajorVersion(knex);\n\n return majorVersion >= 12;\n } catch {\n return false;\n }\n }\n\n constructor(private readonly db: Knex) {}\n\n async transaction<T>(fn: (tx: Knex.Transaction) => Promise<T>): Promise<T> {\n return await this.db.transaction(fn);\n }\n\n async getTransaction(): Promise<Knex.Transaction> {\n return this.db.transaction();\n }\n\n async prepareInsert(tx: Knex.Transaction): Promise<void> {\n // We create a temporary table to collect the hashes of the documents that\n // we expect to be in the documents table at the end. The table is deleted\n // at the end of the transaction.\n // The hash makes sure that we generate a new row for every change.\n await tx.raw(\n 'CREATE TEMP TABLE documents_to_insert (' +\n 'type text NOT NULL, ' +\n 'document jsonb NOT NULL, ' +\n // Generating the hash requires a trick, as the text to bytea\n // conversation runs into errors in case the text contains a backslash.\n // Therefore we have to escape them.\n \"hash bytea NOT NULL GENERATED ALWAYS AS (sha256(replace(document::text || type, '\\\\', '\\\\\\\\')::bytea)) STORED\" +\n ') ON COMMIT DROP',\n );\n }\n\n async completeInsert(tx: Knex.Transaction, type: string): Promise<void> {\n // Copy all new rows into the documents table\n await tx\n .insert(\n tx<RawDocumentRow>('documents_to_insert').select(\n 'type',\n 'document',\n 'hash',\n ),\n )\n .into(tx.raw('documents (type, document, hash)'))\n .onConflict('hash')\n .ignore();\n\n // Delete all documents that we don't expect (deleted and changed)\n await tx<RawDocumentRow>('documents')\n .where({ type })\n .whereNotIn(\n 'hash',\n tx<RawDocumentRow>('documents_to_insert').select('hash'),\n )\n .delete();\n }\n\n async insertDocuments(\n tx: Knex.Transaction,\n type: string,\n documents: IndexableDocument[],\n ): Promise<void> {\n // Insert all documents into the temporary table to process them later\n await tx<DocumentResultRow>('documents_to_insert').insert(\n documents.map(document => ({\n type,\n document,\n })),\n );\n }\n\n async query(\n tx: Knex.Transaction,\n searchQuery: PgSearchQuery,\n ): Promise<DocumentResultRow[]> {\n const { types, pgTerm, fields, offset, limit, options } = searchQuery;\n // TODO(awanlin): We should make the language a parameter so that we can support more then just english\n // Builds a query like:\n // SELECT ts_rank_cd(body, query) AS rank, type, document,\n // ts_headline('english', document, query) AS highlight\n // FROM documents, to_tsquery('english', 'consent') query\n // WHERE query @@ body AND (document @> '{\"kind\": \"API\"}')\n // ORDER BY rank DESC\n // LIMIT 10;\n const query = tx<DocumentResultRow>('documents');\n\n if (pgTerm) {\n query\n .from(tx.raw(\"documents, to_tsquery('english', ?) query\", pgTerm))\n .whereRaw('query @@ body');\n } else {\n query.from('documents');\n }\n\n if (types) {\n query.whereIn('type', types);\n }\n\n if (fields) {\n Object.keys(fields).forEach(key => {\n const value = fields[key];\n const valueArray = Array.isArray(value) ? value : [value];\n const valueCompare = valueArray\n .map(v => ({ [key]: v }))\n .map(v => JSON.stringify(v));\n query.whereRaw(\n `(${valueCompare.map(() => 'document @> ?').join(' OR ')})`,\n valueCompare,\n );\n });\n }\n\n query.select('type', 'document');\n\n if (pgTerm && options.useHighlight) {\n const headlineOptions = `MaxWords=${options.maxWords}, MinWords=${options.minWords}, ShortWord=${options.shortWord}, HighlightAll=${options.highlightAll}, MaxFragments=${options.maxFragments}, FragmentDelimiter=${options.fragmentDelimiter}, StartSel=${options.preTag}, StopSel=${options.postTag}`;\n query\n .select(tx.raw('ts_rank_cd(body, query) AS \"rank\"'))\n .select(\n tx.raw(\n `ts_headline(\\'english\\', document, query, '${headlineOptions}') as \"highlight\"`,\n ),\n )\n .orderBy('rank', 'desc');\n } else if (pgTerm && !options.useHighlight) {\n query\n .select(tx.raw('ts_rank_cd(body, query) AS \"rank\"'))\n .orderBy('rank', 'desc');\n } else {\n query.select(tx.raw('1 as rank'));\n }\n\n return await query.offset(offset).limit(limit);\n }\n}\n","/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\nimport { Knex } from 'knex';\nimport { DatabaseStore } from '../database';\n\n/** @public */\nexport type PgSearchEngineIndexerOptions = {\n batchSize: number;\n type: string;\n databaseStore: DatabaseStore;\n};\n\n/** @public */\nexport class PgSearchEngineIndexer extends BatchSearchEngineIndexer {\n private store: DatabaseStore;\n private type: string;\n private tx: Knex.Transaction | undefined;\n\n constructor(options: PgSearchEngineIndexerOptions) {\n super({ batchSize: options.batchSize });\n this.store = options.databaseStore;\n this.type = options.type;\n }\n\n async initialize(): Promise<void> {\n this.tx = await this.store.getTransaction();\n try {\n await this.store.prepareInsert(this.tx);\n } catch (e) {\n // In case of error, rollback the transaction and re-throw the error so\n // that the stream can be closed and destroyed properly.\n this.tx.rollback(e);\n throw e;\n }\n }\n\n async index(documents: IndexableDocument[]): Promise<void> {\n try {\n await this.store.insertDocuments(this.tx!, this.type, documents);\n } catch (e) {\n // In case of error, rollback the transaction and re-throw the error so\n // that the stream can be closed and destroyed properly.\n this.tx!.rollback(e);\n throw e;\n }\n }\n\n async finalize(): Promise<void> {\n // Attempt to complete and commit the transaction.\n try {\n await this.store.completeInsert(this.tx!, this.type);\n this.tx!.commit();\n } catch (e) {\n // Otherwise, rollback the transaction and re-throw the error so that the\n // stream can be closed and destroyed properly.\n this.tx!.rollback!(e);\n throw e;\n }\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PluginDatabaseManager } from '@backstage/backend-common';\nimport { SearchEngine } from '@backstage/plugin-search-common';\nimport {\n SearchQuery,\n IndexableResultSet,\n IndexableResult,\n} from '@backstage/plugin-search-common';\nimport { PgSearchEngineIndexer } from './PgSearchEngineIndexer';\nimport {\n DatabaseDocumentStore,\n DatabaseStore,\n PgSearchQuery,\n} from '../database';\nimport { v4 as uuid } from 'uuid';\nimport { Config } from '@backstage/config';\n\n/**\n * Search query that the Postgres search engine understands.\n * @public\n */\nexport type ConcretePgSearchQuery = {\n pgQuery: PgSearchQuery;\n pageSize: number;\n};\n\n/**\n * Options available for the Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslatorOptions = {\n highlightOptions: PgSearchHighlightOptions;\n};\n\n/**\n * Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslator = (\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n) => ConcretePgSearchQuery;\n\n/**\n * Options to instantiate PgSearchEngine\n * @public\n */\nexport type PgSearchOptions = {\n database: PluginDatabaseManager;\n};\n\n/**\n * Options for highlighting search terms\n * @public\n */\nexport type PgSearchHighlightOptions = {\n useHighlight?: boolean;\n maxWords?: number;\n minWords?: number;\n shortWord?: number;\n highlightAll?: boolean;\n maxFragments?: number;\n fragmentDelimiter?: string;\n preTag: string;\n postTag: string;\n};\n\n/** @public */\nexport class PgSearchEngine implements SearchEngine {\n private readonly highlightOptions: PgSearchHighlightOptions;\n\n /**\n * @deprecated This will be marked as private in a future release, please us fromConfig instead\n */\n constructor(private readonly databaseStore: DatabaseStore, config: Config) {\n const uuidTag = uuid();\n const highlightConfig = config.getOptionalConfig(\n 'search.pg.highlightOptions',\n );\n\n const highlightOptions: PgSearchHighlightOptions = {\n preTag: `<${uuidTag}>`,\n postTag: `</${uuidTag}>`,\n useHighlight: highlightConfig?.getOptionalBoolean('useHighlight') ?? true,\n maxWords: highlightConfig?.getOptionalNumber('maxWords') ?? 35,\n minWords: highlightConfig?.getOptionalNumber('minWords') ?? 15,\n shortWord: highlightConfig?.getOptionalNumber('shortWord') ?? 3,\n highlightAll:\n highlightConfig?.getOptionalBoolean('highlightAll') ?? false,\n maxFragments: highlightConfig?.getOptionalNumber('maxFragments') ?? 0,\n fragmentDelimiter:\n highlightConfig?.getOptionalString('fragmentDelimiter') ?? ' ... ',\n };\n this.highlightOptions = highlightOptions;\n }\n\n /**\n * @deprecated This will be removed in a future release, please us fromConfig instead\n */\n static async from(options: {\n database: PluginDatabaseManager;\n config: Config;\n }): Promise<PgSearchEngine> {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n options.config,\n );\n }\n\n static async fromConfig(config: Config, options: PgSearchOptions) {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n config,\n );\n }\n\n static async supported(database: PluginDatabaseManager): Promise<boolean> {\n return await DatabaseDocumentStore.supported(await database.getClient());\n }\n\n translator(\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n ): ConcretePgSearchQuery {\n const pageSize = query.pageLimit || 25;\n const { page } = decodePageCursor(query.pageCursor);\n const offset = page * pageSize;\n // We request more result to know whether there is another page\n const limit = pageSize + 1;\n\n return {\n pgQuery: {\n pgTerm: query.term\n .split(/\\s/)\n .map(p => p.replace(/[\\0()|&:*!]/g, '').trim())\n .filter(p => p !== '')\n .map(p => `(${JSON.stringify(p)} | ${JSON.stringify(p)}:*)`)\n .join('&'),\n fields: query.filters as Record<string, string | string[]>,\n types: query.types,\n offset,\n limit,\n options: options.highlightOptions,\n },\n pageSize,\n };\n }\n\n setTranslator(translator: PgSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async getIndexer(type: string) {\n return new PgSearchEngineIndexer({\n batchSize: 1000,\n type,\n databaseStore: this.databaseStore,\n });\n }\n\n async query(query: SearchQuery): Promise<IndexableResultSet> {\n const { pgQuery, pageSize } = this.translator(query, {\n highlightOptions: this.highlightOptions,\n });\n\n const rows = await this.databaseStore.transaction(async tx =>\n this.databaseStore.query(tx, pgQuery),\n );\n\n // We requested one result more than the page size to know whether there is\n // another page.\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = rows.length > pageSize;\n const hasPreviousPage = page > 0;\n const pageRows = rows.slice(0, pageSize);\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n const results = pageRows.map(\n ({ type, document, highlight }, index): IndexableResult => ({\n type,\n document,\n rank: page * pageSize + index + 1,\n highlight: {\n preTag: pgQuery.options.preTag,\n postTag: pgQuery.options.postTag,\n fields: highlight\n ? {\n text: highlight.text,\n title: highlight.title,\n location: highlight.location,\n path: '',\n }\n : {},\n },\n }),\n );\n\n return { results, nextPageCursor, previousPageCursor };\n }\n}\n\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["resolvePackagePath","BatchSearchEngineIndexer","uuid"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/database/util.ts","../src/database/DatabaseDocumentStore.ts","../src/PgSearchEngine/PgSearchEngineIndexer.ts","../src/PgSearchEngine/PgSearchEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Knex } from 'knex';\n\nexport async function queryPostgresMajorVersion(knex: Knex): Promise<number> {\n if (knex.client.config.client !== 'pg') {\n throw new Error(\"Can't resolve version, not a postgres database\");\n }\n\n const { rows } = await knex.raw('SHOW server_version_num');\n const [result] = rows;\n const version = +result.server_version_num;\n const majorVersion = Math.floor(version / 10000);\n return majorVersion;\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n PluginDatabaseManager,\n resolvePackagePath,\n} from '@backstage/backend-common';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\nimport { Knex } from 'knex';\nimport {\n DatabaseStore,\n DocumentResultRow,\n PgSearchQuery,\n RawDocumentRow,\n} from './types';\nimport { queryPostgresMajorVersion } from './util';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage/plugin-search-backend-module-pg',\n 'migrations',\n);\n\n/** @public */\nexport class DatabaseDocumentStore implements DatabaseStore {\n static async create(\n database: PluginDatabaseManager,\n ): Promise<DatabaseDocumentStore> {\n const knex = await database.getClient();\n try {\n const majorVersion = await queryPostgresMajorVersion(knex);\n\n if (majorVersion < 12) {\n // We are using some features (like generated columns) that aren't\n // available in older postgres versions.\n throw new Error(\n `The PgSearchEngine requires at least postgres version 12 (but is running on ${majorVersion})`,\n );\n }\n } catch {\n // Actually both mysql and sqlite have a full text search, too. We could\n // implement them separately or add them here.\n throw new Error(\n 'The PgSearchEngine is only supported when using a postgres database (>=12.x)',\n );\n }\n\n if (!database.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new DatabaseDocumentStore(knex);\n }\n\n static async supported(knex: Knex): Promise<boolean> {\n try {\n const majorVersion = await queryPostgresMajorVersion(knex);\n\n return majorVersion >= 12;\n } catch {\n return false;\n }\n }\n\n constructor(private readonly db: Knex) {}\n\n async transaction<T>(fn: (tx: Knex.Transaction) => Promise<T>): Promise<T> {\n return await this.db.transaction(fn);\n }\n\n async getTransaction(): Promise<Knex.Transaction> {\n return this.db.transaction();\n }\n\n async prepareInsert(tx: Knex.Transaction): Promise<void> {\n // We create a temporary table to collect the hashes of the documents that\n // we expect to be in the documents table at the end. The table is deleted\n // at the end of the transaction.\n // The hash makes sure that we generate a new row for every change.\n await tx.raw(\n 'CREATE TEMP TABLE documents_to_insert (' +\n 'type text NOT NULL, ' +\n 'document jsonb NOT NULL, ' +\n // Generating the hash requires a trick, as the text to bytea\n // conversation runs into errors in case the text contains a backslash.\n // Therefore we have to escape them.\n \"hash bytea NOT NULL GENERATED ALWAYS AS (sha256(replace(document::text || type, '\\\\', '\\\\\\\\')::bytea)) STORED\" +\n ') ON COMMIT DROP',\n );\n }\n\n async completeInsert(tx: Knex.Transaction, type: string): Promise<void> {\n // Copy all new rows into the documents table\n await tx\n .insert(\n tx<RawDocumentRow>('documents_to_insert').select(\n 'type',\n 'document',\n 'hash',\n ),\n )\n .into(tx.raw('documents (type, document, hash)'))\n .onConflict('hash')\n .ignore();\n\n // Delete all documents that we don't expect (deleted and changed)\n await tx<RawDocumentRow>('documents')\n .where({ type })\n .whereNotIn(\n 'hash',\n tx<RawDocumentRow>('documents_to_insert').select('hash'),\n )\n .delete();\n }\n\n async insertDocuments(\n tx: Knex.Transaction,\n type: string,\n documents: IndexableDocument[],\n ): Promise<void> {\n // Insert all documents into the temporary table to process them later\n await tx<DocumentResultRow>('documents_to_insert').insert(\n documents.map(document => ({\n type,\n document,\n })),\n );\n }\n\n async query(\n tx: Knex.Transaction,\n searchQuery: PgSearchQuery,\n ): Promise<DocumentResultRow[]> {\n const { types, pgTerm, fields, offset, limit, options } = searchQuery;\n // TODO(awanlin): We should make the language a parameter so that we can support more then just english\n // Builds a query like:\n // SELECT ts_rank_cd(body, query) AS rank, type, document,\n // ts_headline('english', document, query) AS highlight\n // FROM documents, to_tsquery('english', 'consent') query\n // WHERE query @@ body AND (document @> '{\"kind\": \"API\"}')\n // ORDER BY rank DESC\n // LIMIT 10;\n const query = tx<DocumentResultRow>('documents');\n\n if (pgTerm) {\n query\n .from(tx.raw(\"documents, to_tsquery('english', ?) query\", pgTerm))\n .whereRaw('query @@ body');\n } else {\n query.from('documents');\n }\n\n if (types) {\n query.whereIn('type', types);\n }\n\n if (fields) {\n Object.keys(fields).forEach(key => {\n const value = fields[key];\n const valueArray = Array.isArray(value) ? value : [value];\n const valueCompare = valueArray\n .map(v => ({ [key]: v }))\n .map(v => JSON.stringify(v));\n query.whereRaw(\n `(${valueCompare.map(() => 'document @> ?').join(' OR ')})`,\n valueCompare,\n );\n });\n }\n\n query.select('type', 'document');\n\n if (pgTerm && options.useHighlight) {\n const headlineOptions = `MaxWords=${options.maxWords}, MinWords=${options.minWords}, ShortWord=${options.shortWord}, HighlightAll=${options.highlightAll}, MaxFragments=${options.maxFragments}, FragmentDelimiter=${options.fragmentDelimiter}, StartSel=${options.preTag}, StopSel=${options.postTag}`;\n query\n .select(tx.raw('ts_rank_cd(body, query) AS \"rank\"'))\n .select(\n tx.raw(\n `ts_headline(\\'english\\', document, query, '${headlineOptions}') as \"highlight\"`,\n ),\n )\n .orderBy('rank', 'desc');\n } else if (pgTerm && !options.useHighlight) {\n query\n .select(tx.raw('ts_rank_cd(body, query) AS \"rank\"'))\n .orderBy('rank', 'desc');\n } else {\n query.select(tx.raw('1 as rank'));\n }\n\n return await query.offset(offset).limit(limit);\n }\n}\n","/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { getVoidLogger } from '@backstage/backend-common';\nimport { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';\nimport { IndexableDocument } from '@backstage/plugin-search-common';\nimport { Knex } from 'knex';\nimport { Logger } from 'winston';\nimport { DatabaseStore } from '../database';\n\n/** @public */\nexport type PgSearchEngineIndexerOptions = {\n batchSize: number;\n type: string;\n databaseStore: DatabaseStore;\n logger?: Logger;\n};\n\n/** @public */\nexport class PgSearchEngineIndexer extends BatchSearchEngineIndexer {\n private logger: Logger;\n private store: DatabaseStore;\n private type: string;\n private tx: Knex.Transaction | undefined;\n private numRecords = 0;\n\n constructor(options: PgSearchEngineIndexerOptions) {\n super({ batchSize: options.batchSize });\n this.store = options.databaseStore;\n this.type = options.type;\n this.logger = options.logger || getVoidLogger();\n }\n\n async initialize(): Promise<void> {\n this.tx = await this.store.getTransaction();\n try {\n await this.store.prepareInsert(this.tx);\n } catch (e) {\n // In case of error, rollback the transaction and re-throw the error so\n // that the stream can be closed and destroyed properly.\n this.tx.rollback(e);\n throw e;\n }\n }\n\n async index(documents: IndexableDocument[]): Promise<void> {\n this.numRecords += documents.length;\n\n try {\n await this.store.insertDocuments(this.tx!, this.type, documents);\n } catch (e) {\n // In case of error, rollback the transaction and re-throw the error so\n // that the stream can be closed and destroyed properly.\n this.tx!.rollback(e);\n throw e;\n }\n }\n\n async finalize(): Promise<void> {\n // If no documents were indexed, rollback the transaction, log a warning,\n // and do not continue. This ensures that collators that return empty sets\n // of documents do not cause the index to be deleted.\n if (this.numRecords === 0) {\n this.logger.warn(\n `Index for ${this.type} was not replaced: indexer received 0 documents`,\n );\n this.tx!.rollback!();\n return;\n }\n\n // Attempt to complete and commit the transaction.\n try {\n await this.store.completeInsert(this.tx!, this.type);\n this.tx!.commit();\n } catch (e) {\n // Otherwise, rollback the transaction and re-throw the error so that the\n // stream can be closed and destroyed properly.\n this.tx!.rollback!(e);\n throw e;\n }\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { PluginDatabaseManager } from '@backstage/backend-common';\nimport { SearchEngine } from '@backstage/plugin-search-common';\nimport {\n SearchQuery,\n IndexableResultSet,\n IndexableResult,\n} from '@backstage/plugin-search-common';\nimport { PgSearchEngineIndexer } from './PgSearchEngineIndexer';\nimport {\n DatabaseDocumentStore,\n DatabaseStore,\n PgSearchQuery,\n} from '../database';\nimport { v4 as uuid } from 'uuid';\nimport { Logger } from 'winston';\nimport { Config } from '@backstage/config';\n\n/**\n * Search query that the Postgres search engine understands.\n * @public\n */\nexport type ConcretePgSearchQuery = {\n pgQuery: PgSearchQuery;\n pageSize: number;\n};\n\n/**\n * Options available for the Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslatorOptions = {\n highlightOptions: PgSearchHighlightOptions;\n};\n\n/**\n * Postgres specific query translator.\n * @public\n */\nexport type PgSearchQueryTranslator = (\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n) => ConcretePgSearchQuery;\n\n/**\n * Options to instantiate PgSearchEngine\n * @public\n */\nexport type PgSearchOptions = {\n database: PluginDatabaseManager;\n logger?: Logger;\n};\n\n/**\n * Options for highlighting search terms\n * @public\n */\nexport type PgSearchHighlightOptions = {\n useHighlight?: boolean;\n maxWords?: number;\n minWords?: number;\n shortWord?: number;\n highlightAll?: boolean;\n maxFragments?: number;\n fragmentDelimiter?: string;\n preTag: string;\n postTag: string;\n};\n\n/** @public */\nexport class PgSearchEngine implements SearchEngine {\n private readonly logger?: Logger;\n private readonly highlightOptions: PgSearchHighlightOptions;\n\n /**\n * @deprecated This will be marked as private in a future release, please us fromConfig instead\n */\n constructor(\n private readonly databaseStore: DatabaseStore,\n config: Config,\n logger?: Logger,\n ) {\n const uuidTag = uuid();\n const highlightConfig = config.getOptionalConfig(\n 'search.pg.highlightOptions',\n );\n\n const highlightOptions: PgSearchHighlightOptions = {\n preTag: `<${uuidTag}>`,\n postTag: `</${uuidTag}>`,\n useHighlight: highlightConfig?.getOptionalBoolean('useHighlight') ?? true,\n maxWords: highlightConfig?.getOptionalNumber('maxWords') ?? 35,\n minWords: highlightConfig?.getOptionalNumber('minWords') ?? 15,\n shortWord: highlightConfig?.getOptionalNumber('shortWord') ?? 3,\n highlightAll:\n highlightConfig?.getOptionalBoolean('highlightAll') ?? false,\n maxFragments: highlightConfig?.getOptionalNumber('maxFragments') ?? 0,\n fragmentDelimiter:\n highlightConfig?.getOptionalString('fragmentDelimiter') ?? ' ... ',\n };\n this.highlightOptions = highlightOptions;\n this.logger = logger;\n }\n\n /**\n * @deprecated This will be removed in a future release, please us fromConfig instead\n */\n static async from(options: {\n database: PluginDatabaseManager;\n config: Config;\n logger?: Logger;\n }): Promise<PgSearchEngine> {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n options.config,\n options.logger,\n );\n }\n\n static async fromConfig(config: Config, options: PgSearchOptions) {\n return new PgSearchEngine(\n await DatabaseDocumentStore.create(options.database),\n config,\n options.logger,\n );\n }\n\n static async supported(database: PluginDatabaseManager): Promise<boolean> {\n return await DatabaseDocumentStore.supported(await database.getClient());\n }\n\n translator(\n query: SearchQuery,\n options: PgSearchQueryTranslatorOptions,\n ): ConcretePgSearchQuery {\n const pageSize = query.pageLimit || 25;\n const { page } = decodePageCursor(query.pageCursor);\n const offset = page * pageSize;\n // We request more result to know whether there is another page\n const limit = pageSize + 1;\n\n return {\n pgQuery: {\n pgTerm: query.term\n .split(/\\s/)\n .map(p => p.replace(/[\\0()|&:*!]/g, '').trim())\n .filter(p => p !== '')\n .map(p => `(${JSON.stringify(p)} | ${JSON.stringify(p)}:*)`)\n .join('&'),\n fields: query.filters as Record<string, string | string[]>,\n types: query.types,\n offset,\n limit,\n options: options.highlightOptions,\n },\n pageSize,\n };\n }\n\n setTranslator(translator: PgSearchQueryTranslator) {\n this.translator = translator;\n }\n\n async getIndexer(type: string) {\n return new PgSearchEngineIndexer({\n batchSize: 1000,\n type,\n databaseStore: this.databaseStore,\n logger: this.logger?.child({ documentType: type }),\n });\n }\n\n async query(query: SearchQuery): Promise<IndexableResultSet> {\n const { pgQuery, pageSize } = this.translator(query, {\n highlightOptions: this.highlightOptions,\n });\n\n const rows = await this.databaseStore.transaction(async tx =>\n this.databaseStore.query(tx, pgQuery),\n );\n\n // We requested one result more than the page size to know whether there is\n // another page.\n const { page } = decodePageCursor(query.pageCursor);\n const hasNextPage = rows.length > pageSize;\n const hasPreviousPage = page > 0;\n const pageRows = rows.slice(0, pageSize);\n const nextPageCursor = hasNextPage\n ? encodePageCursor({ page: page + 1 })\n : undefined;\n const previousPageCursor = hasPreviousPage\n ? encodePageCursor({ page: page - 1 })\n : undefined;\n\n const results = pageRows.map(\n ({ type, document, highlight }, index): IndexableResult => ({\n type,\n document,\n rank: page * pageSize + index + 1,\n highlight: {\n preTag: pgQuery.options.preTag,\n postTag: pgQuery.options.postTag,\n fields: highlight\n ? {\n text: highlight.text,\n title: highlight.title,\n location: highlight.location,\n path: '',\n }\n : {},\n },\n }),\n );\n\n return { results, nextPageCursor, previousPageCursor };\n }\n}\n\nexport function decodePageCursor(pageCursor?: string): { page: number } {\n if (!pageCursor) {\n return { page: 0 };\n }\n\n return {\n page: Number(Buffer.from(pageCursor, 'base64').toString('utf-8')),\n };\n}\n\nexport function encodePageCursor({ page }: { page: number }): string {\n return Buffer.from(`${page}`, 'utf-8').toString('base64');\n}\n"],"names":["resolvePackagePath","BatchSearchEngineIndexer","getVoidLogger","uuid"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { PluginDatabaseManager } from '@backstage/backend-common';
2
2
  import { IndexableDocument, SearchQuery, SearchEngine, IndexableResultSet } from '@backstage/plugin-search-common';
3
3
  import { Knex } from 'knex';
4
4
  import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node';
5
+ import { Logger } from 'winston';
5
6
  import { Config } from '@backstage/config';
6
7
 
7
8
  /** @public */
@@ -9,12 +10,15 @@ declare type PgSearchEngineIndexerOptions = {
9
10
  batchSize: number;
10
11
  type: string;
11
12
  databaseStore: DatabaseStore;
13
+ logger?: Logger;
12
14
  };
13
15
  /** @public */
14
16
  declare class PgSearchEngineIndexer extends BatchSearchEngineIndexer {
17
+ private logger;
15
18
  private store;
16
19
  private type;
17
20
  private tx;
21
+ private numRecords;
18
22
  constructor(options: PgSearchEngineIndexerOptions);
19
23
  initialize(): Promise<void>;
20
24
  index(documents: IndexableDocument[]): Promise<void>;
@@ -47,6 +51,7 @@ declare type PgSearchQueryTranslator = (query: SearchQuery, options: PgSearchQue
47
51
  */
48
52
  declare type PgSearchOptions = {
49
53
  database: PluginDatabaseManager;
54
+ logger?: Logger;
50
55
  };
51
56
  /**
52
57
  * Options for highlighting search terms
@@ -66,17 +71,19 @@ declare type PgSearchHighlightOptions = {
66
71
  /** @public */
67
72
  declare class PgSearchEngine implements SearchEngine {
68
73
  private readonly databaseStore;
74
+ private readonly logger?;
69
75
  private readonly highlightOptions;
70
76
  /**
71
77
  * @deprecated This will be marked as private in a future release, please us fromConfig instead
72
78
  */
73
- constructor(databaseStore: DatabaseStore, config: Config);
79
+ constructor(databaseStore: DatabaseStore, config: Config, logger?: Logger);
74
80
  /**
75
81
  * @deprecated This will be removed in a future release, please us fromConfig instead
76
82
  */
77
83
  static from(options: {
78
84
  database: PluginDatabaseManager;
79
85
  config: Config;
86
+ logger?: Logger;
80
87
  }): Promise<PgSearchEngine>;
81
88
  static fromConfig(config: Config, options: PgSearchOptions): Promise<PgSearchEngine>;
82
89
  static supported(database: PluginDatabaseManager): Promise<boolean>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/plugin-search-backend-module-pg",
3
3
  "description": "A module for the search backend that implements search using PostgreSQL",
4
- "version": "0.4.3-next.3",
4
+ "version": "0.5.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "Apache-2.0",
@@ -23,17 +23,18 @@
23
23
  "clean": "backstage-cli package clean"
24
24
  },
25
25
  "dependencies": {
26
- "@backstage/backend-common": "^0.17.0-next.3",
27
- "@backstage/config": "^1.0.5-next.1",
28
- "@backstage/plugin-search-backend-node": "^1.1.0-next.3",
29
- "@backstage/plugin-search-common": "^1.2.0-next.3",
26
+ "@backstage/backend-common": "^0.17.0",
27
+ "@backstage/config": "^1.0.5",
28
+ "@backstage/plugin-search-backend-node": "^1.1.0",
29
+ "@backstage/plugin-search-common": "^1.2.0",
30
30
  "knex": "^2.0.0",
31
31
  "lodash": "^4.17.21",
32
- "uuid": "^8.3.2"
32
+ "uuid": "^8.3.2",
33
+ "winston": "^3.2.1"
33
34
  },
34
35
  "devDependencies": {
35
- "@backstage/backend-test-utils": "^0.1.31-next.4",
36
- "@backstage/cli": "^0.22.0-next.4"
36
+ "@backstage/backend-test-utils": "^0.1.31",
37
+ "@backstage/cli": "^0.22.0"
37
38
  },
38
39
  "files": [
39
40
  "dist",