@cumulus/db 21.3.1 → 21.3.2-testlerna.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.
Files changed (59) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +3 -3
  3. package/dist/lib/granule.d.ts +4 -33
  4. package/dist/lib/granule.js +10 -61
  5. package/dist/models/granule.js +2 -2
  6. package/dist/s3search/AsyncOperationS3Search.d.ts +20 -0
  7. package/dist/s3search/AsyncOperationS3Search.js +29 -0
  8. package/dist/s3search/CollectionS3Search.d.ts +39 -0
  9. package/dist/s3search/CollectionS3Search.js +113 -0
  10. package/dist/s3search/DuckDBSearchExecutor.d.ts +36 -0
  11. package/dist/s3search/DuckDBSearchExecutor.js +57 -0
  12. package/dist/s3search/ExecutionS3Search.d.ts +20 -0
  13. package/dist/s3search/ExecutionS3Search.js +29 -0
  14. package/dist/s3search/GranuleS3Search.d.ts +31 -0
  15. package/dist/s3search/GranuleS3Search.js +100 -0
  16. package/dist/s3search/PdrS3Search.d.ts +20 -0
  17. package/dist/s3search/PdrS3Search.js +29 -0
  18. package/dist/s3search/ProviderS3Search.d.ts +20 -0
  19. package/dist/s3search/ProviderS3Search.js +29 -0
  20. package/dist/s3search/ReconciliationReportS3Search.d.ts +20 -0
  21. package/dist/s3search/ReconciliationReportS3Search.js +29 -0
  22. package/dist/s3search/RuleS3Search.d.ts +20 -0
  23. package/dist/s3search/RuleS3Search.js +29 -0
  24. package/dist/s3search/StatsS3Search.d.ts +25 -0
  25. package/dist/s3search/StatsS3Search.js +51 -0
  26. package/dist/s3search/duckdbHelpers.d.ts +43 -0
  27. package/dist/s3search/duckdbHelpers.js +83 -0
  28. package/dist/s3search/s3TableSchemas.d.ts +11 -0
  29. package/dist/s3search/s3TableSchemas.js +272 -0
  30. package/dist/search/BaseSearch.d.ts +46 -2
  31. package/dist/search/BaseSearch.js +84 -22
  32. package/dist/search/CollectionSearch.d.ts +6 -4
  33. package/dist/search/CollectionSearch.js +2 -3
  34. package/dist/search/ExecutionSearch.d.ts +1 -1
  35. package/dist/search/ExecutionSearch.js +3 -3
  36. package/dist/search/GranuleSearch.d.ts +2 -3
  37. package/dist/search/GranuleSearch.js +3 -3
  38. package/dist/search/PdrSearch.js +1 -1
  39. package/dist/search/ReconciliationReportSearch.js +1 -1
  40. package/dist/search/RuleSearch.js +4 -4
  41. package/dist/search/StatsSearch.d.ts +15 -4
  42. package/dist/search/StatsSearch.js +12 -6
  43. package/dist/search/field-mapping.d.ts +1 -3
  44. package/dist/search/field-mapping.js +40 -19
  45. package/dist/test-duckdb-utils.d.ts +31 -0
  46. package/dist/test-duckdb-utils.js +125 -0
  47. package/dist/test-utils.js +6 -0
  48. package/dist/translate/async_operations.js +7 -3
  49. package/dist/translate/collections.js +6 -6
  50. package/dist/translate/executions.js +7 -7
  51. package/dist/translate/granules.js +16 -11
  52. package/dist/translate/pdr.js +4 -4
  53. package/dist/translate/providers.js +2 -2
  54. package/dist/translate/reconciliation_reports.js +5 -4
  55. package/dist/translate/rules.d.ts +1 -1
  56. package/dist/translate/rules.js +6 -6
  57. package/dist/types/file.d.ts +2 -0
  58. package/dist/types/granule.d.ts +1 -1
  59. package/package.json +12 -11
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rulesS3TableSql = exports.reconciliationReportsS3TableSql = exports.providersS3TableSql = exports.pdrsS3TableSql = exports.granulesExecutionsS3TableSql = exports.granulesS3TableSql = exports.filesS3TableSql = exports.executionsS3TableSql = exports.collectionsS3TableSql = exports.asyncOperationsS3TableSql = void 0;
4
+ const asyncOperationsS3TableSql = (tableName = 'async_operations') => `
5
+ CREATE TABLE IF NOT EXISTS ${tableName} (
6
+ cumulus_id INTEGER PRIMARY KEY,
7
+ id UUID NOT NULL,
8
+ description TEXT NOT NULL,
9
+ operation_type TEXT NOT NULL,
10
+ output JSON,
11
+ status TEXT NOT NULL,
12
+ task_arn TEXT,
13
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
14
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
15
+ CONSTRAINT async_operations_id_unique UNIQUE (id),
16
+ CONSTRAINT async_operations_status_check
17
+ CHECK (status IN (
18
+ 'RUNNING',
19
+ 'SUCCEEDED',
20
+ 'RUNNER_FAILED',
21
+ 'TASK_FAILED'
22
+ )),
23
+ CONSTRAINT async_operations_operation_type_check
24
+ CHECK (operation_type IN (
25
+ 'Bulk Execution Archive',
26
+ 'Bulk Execution Delete',
27
+ 'Bulk Granules',
28
+ 'Bulk Granule Archive',
29
+ 'Bulk Granule Delete',
30
+ 'Bulk Granule Reingest',
31
+ 'Data Migration',
32
+ 'Dead-Letter Processing',
33
+ 'DLA Migration',
34
+ 'ES Index',
35
+ 'Kinesis Replay',
36
+ 'Migration Count Report',
37
+ 'Reconciliation Report',
38
+ 'SQS Replay'
39
+ ))
40
+ );`;
41
+ exports.asyncOperationsS3TableSql = asyncOperationsS3TableSql;
42
+ const collectionsS3TableSql = (tableName = 'collections') => `
43
+ CREATE TABLE IF NOT EXISTS ${tableName} (
44
+ cumulus_id INTEGER PRIMARY KEY,
45
+ name TEXT NOT NULL,
46
+ version TEXT NOT NULL,
47
+ sample_file_name TEXT NOT NULL,
48
+ granule_id_validation_regex TEXT NOT NULL,
49
+ granule_id_extraction_regex TEXT NOT NULL,
50
+ files JSON NOT NULL,
51
+ process TEXT,
52
+ url_path TEXT,
53
+ duplicate_handling TEXT,
54
+ report_to_ems BOOLEAN,
55
+ ignore_files_config_for_discovery BOOLEAN,
56
+ meta JSON,
57
+ tags JSON,
58
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
59
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
60
+ UNIQUE (name, version),
61
+ CHECK (duplicate_handling IN ('error', 'replace', 'skip', 'version'))
62
+ );`;
63
+ exports.collectionsS3TableSql = collectionsS3TableSql;
64
+ const executionsS3TableSql = (tableName = 'executions') => `
65
+ CREATE TABLE IF NOT EXISTS ${tableName} (
66
+ cumulus_id BIGINT PRIMARY KEY,
67
+ arn TEXT NOT NULL,
68
+ async_operation_cumulus_id INTEGER,
69
+ collection_cumulus_id INTEGER,
70
+ parent_cumulus_id BIGINT,
71
+ cumulus_version TEXT,
72
+ url TEXT,
73
+ status TEXT NOT NULL,
74
+ tasks JSON,
75
+ error JSON,
76
+ workflow_name TEXT,
77
+ duration REAL,
78
+ original_payload JSON,
79
+ final_payload JSON,
80
+ "timestamp" TIMESTAMPTZ,
81
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
82
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
83
+ archived BOOLEAN NOT NULL DEFAULT FALSE,
84
+ CONSTRAINT executions_arn_unique UNIQUE (arn),
85
+ CONSTRAINT executions_url_unique UNIQUE (url),
86
+ CONSTRAINT executions_async_operation_cumulus_id_foreign
87
+ FOREIGN KEY (async_operation_cumulus_id)
88
+ REFERENCES async_operations (cumulus_id),
89
+ CONSTRAINT executions_collection_cumulus_id_foreign
90
+ FOREIGN KEY (collection_cumulus_id)
91
+ REFERENCES collections (cumulus_id),
92
+ CONSTRAINT executions_parent_cumulus_id_foreign
93
+ FOREIGN KEY (parent_cumulus_id)
94
+ REFERENCES ${tableName} (cumulus_id),
95
+ CONSTRAINT executions_status_check
96
+ CHECK (status IN ('running', 'completed', 'failed', 'unknown'))
97
+ );`;
98
+ exports.executionsS3TableSql = executionsS3TableSql;
99
+ const filesS3TableSql = (tableName = 'files') => `
100
+ CREATE TABLE IF NOT EXISTS ${tableName} (
101
+ cumulus_id BIGINT PRIMARY KEY,
102
+ granule_cumulus_id BIGINT NOT NULL,
103
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
104
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
105
+ file_size BIGINT,
106
+ bucket TEXT NOT NULL,
107
+ checksum_type TEXT,
108
+ checksum_value TEXT,
109
+ file_name TEXT,
110
+ key TEXT NOT NULL,
111
+ path TEXT,
112
+ source TEXT,
113
+ type TEXT,
114
+ CONSTRAINT files_bucket_key_unique UNIQUE (bucket, key),
115
+ CONSTRAINT files_granule_cumulus_id_foreign
116
+ FOREIGN KEY (granule_cumulus_id)
117
+ REFERENCES granules (cumulus_id)
118
+ );`;
119
+ exports.filesS3TableSql = filesS3TableSql;
120
+ const granulesS3TableSql = (tableName = 'granules') => `
121
+ CREATE TABLE IF NOT EXISTS ${tableName} (
122
+ cumulus_id BIGINT PRIMARY KEY,
123
+ granule_id TEXT NOT NULL,
124
+ status TEXT NOT NULL,
125
+ collection_cumulus_id INTEGER NOT NULL,
126
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
127
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
128
+ published BOOLEAN,
129
+ duration REAL,
130
+ time_to_archive REAL,
131
+ time_to_process REAL,
132
+ product_volume BIGINT,
133
+ error JSON,
134
+ cmr_link TEXT,
135
+ pdr_cumulus_id INTEGER,
136
+ provider_cumulus_id INTEGER,
137
+ beginning_date_time TIMESTAMPTZ,
138
+ ending_date_time TIMESTAMPTZ,
139
+ last_update_date_time TIMESTAMPTZ,
140
+ processing_end_date_time TIMESTAMPTZ,
141
+ processing_start_date_time TIMESTAMPTZ,
142
+ production_date_time TIMESTAMPTZ,
143
+ query_fields JSON,
144
+ "timestamp" TIMESTAMPTZ,
145
+ producer_granule_id TEXT NOT NULL,
146
+ archived BOOLEAN NOT NULL DEFAULT FALSE,
147
+ UNIQUE (collection_cumulus_id, granule_id),
148
+ CHECK (status IN ('running', 'completed', 'failed', 'queued'))
149
+ );`;
150
+ exports.granulesS3TableSql = granulesS3TableSql;
151
+ const granulesExecutionsS3TableSql = (tableName = 'granules_executions') => `
152
+ CREATE TABLE IF NOT EXISTS ${tableName} (
153
+ granule_cumulus_id BIGINT NOT NULL,
154
+ execution_cumulus_id BIGINT NOT NULL,
155
+ CONSTRAINT granules_executions_granule_execution_unique
156
+ UNIQUE (granule_cumulus_id, execution_cumulus_id),
157
+ CONSTRAINT granules_executions_execution_cumulus_id_foreign
158
+ FOREIGN KEY (execution_cumulus_id)
159
+ REFERENCES executions (cumulus_id),
160
+ CONSTRAINT granules_executions_granule_cumulus_id_foreign
161
+ FOREIGN KEY (granule_cumulus_id)
162
+ REFERENCES granules (cumulus_id)
163
+ );`;
164
+ exports.granulesExecutionsS3TableSql = granulesExecutionsS3TableSql;
165
+ const pdrsS3TableSql = (tableName = 'pdrs') => `
166
+ CREATE TABLE IF NOT EXISTS ${tableName} (
167
+ cumulus_id INTEGER PRIMARY KEY,
168
+ collection_cumulus_id INTEGER NOT NULL,
169
+ provider_cumulus_id INTEGER NOT NULL,
170
+ execution_cumulus_id BIGINT,
171
+ status TEXT NOT NULL,
172
+ name TEXT NOT NULL,
173
+ progress REAL,
174
+ pan_sent BOOLEAN,
175
+ pan_message TEXT,
176
+ stats JSON,
177
+ address TEXT,
178
+ original_url TEXT,
179
+ duration REAL,
180
+ "timestamp" TIMESTAMPTZ,
181
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
182
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
183
+ CONSTRAINT pdrs_name_unique UNIQUE (name),
184
+ CONSTRAINT pdrs_collection_cumulus_id_foreign
185
+ FOREIGN KEY (collection_cumulus_id)
186
+ REFERENCES collections (cumulus_id),
187
+ CONSTRAINT pdrs_execution_cumulus_id_foreign
188
+ FOREIGN KEY (execution_cumulus_id)
189
+ REFERENCES executions (cumulus_id),
190
+ CONSTRAINT pdrs_provider_cumulus_id_foreign
191
+ FOREIGN KEY (provider_cumulus_id)
192
+ REFERENCES providers (cumulus_id),
193
+ CONSTRAINT pdrs_status_check
194
+ CHECK (status IN ('running', 'failed', 'completed'))
195
+ );`;
196
+ exports.pdrsS3TableSql = pdrsS3TableSql;
197
+ const providersS3TableSql = (tableName = 'providers') => `
198
+ CREATE TABLE IF NOT EXISTS ${tableName} (
199
+ cumulus_id INTEGER PRIMARY KEY,
200
+ name TEXT NOT NULL,
201
+ protocol TEXT NOT NULL DEFAULT 'http',
202
+ host TEXT NOT NULL,
203
+ port INTEGER,
204
+ username TEXT,
205
+ password TEXT,
206
+ global_connection_limit INTEGER,
207
+ private_key TEXT,
208
+ cm_key_id TEXT,
209
+ certificate_uri TEXT,
210
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
211
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
212
+ allowed_redirects TEXT[],
213
+ max_download_time INTEGER,
214
+ CONSTRAINT providers_name_unique UNIQUE (name),
215
+ CONSTRAINT providers_protocol_check
216
+ CHECK (protocol IN ('http', 'https', 'ftp', 'sftp', 's3'))
217
+ );`;
218
+ exports.providersS3TableSql = providersS3TableSql;
219
+ const reconciliationReportsS3TableSql = (tableName = 'reconciliation_reports') => `
220
+ CREATE TABLE IF NOT EXISTS ${tableName} (
221
+ cumulus_id INTEGER PRIMARY KEY,
222
+ name TEXT NOT NULL,
223
+ type TEXT NOT NULL,
224
+ status TEXT NOT NULL,
225
+ location TEXT,
226
+ error JSON,
227
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
228
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
229
+ CONSTRAINT reconciliation_reports_name_unique UNIQUE (name),
230
+ CONSTRAINT reconciliation_reports_type_check
231
+ CHECK (type IN (
232
+ 'Granule Inventory',
233
+ 'Granule Not Found',
234
+ 'Internal',
235
+ 'Inventory',
236
+ 'ORCA Backup'
237
+ )),
238
+ CONSTRAINT reconciliation_reports_status_check
239
+ CHECK (status IN ('Generated', 'Pending', 'Failed'))
240
+ );`;
241
+ exports.reconciliationReportsS3TableSql = reconciliationReportsS3TableSql;
242
+ const rulesS3TableSql = (tableName = 'rules') => `
243
+ CREATE TABLE IF NOT EXISTS ${tableName} (
244
+ cumulus_id INTEGER PRIMARY KEY,
245
+ name TEXT NOT NULL,
246
+ workflow TEXT NOT NULL,
247
+ collection_cumulus_id INTEGER,
248
+ provider_cumulus_id INTEGER,
249
+ type TEXT NOT NULL,
250
+ enabled BOOLEAN NOT NULL,
251
+ value TEXT,
252
+ arn TEXT,
253
+ log_event_arn TEXT,
254
+ execution_name_prefix TEXT,
255
+ payload JSON,
256
+ meta JSON,
257
+ tags JSON,
258
+ queue_url TEXT,
259
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
260
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
261
+ CONSTRAINT rules_name_unique UNIQUE (name),
262
+ CONSTRAINT rules_collection_cumulus_id_foreign
263
+ FOREIGN KEY (collection_cumulus_id)
264
+ REFERENCES collections (cumulus_id),
265
+ CONSTRAINT rules_provider_cumulus_id_foreign
266
+ FOREIGN KEY (provider_cumulus_id)
267
+ REFERENCES providers (cumulus_id),
268
+ CONSTRAINT rules_type_check
269
+ CHECK (type IN ('onetime', 'scheduled', 'sns', 'kinesis', 'sqs'))
270
+ );`;
271
+ exports.rulesS3TableSql = rulesS3TableSql;
272
+ //# sourceMappingURL=s3TableSchemas.js.map
@@ -1,11 +1,44 @@
1
1
  import { Knex } from 'knex';
2
2
  import { BaseRecord } from '../types/base';
3
3
  import { DbQueryParameters, QueryEvent, QueryStringParameters } from '../types/search';
4
+ export declare type Meta = {
5
+ name: string;
6
+ stack?: string;
7
+ table?: string;
8
+ limit?: number;
9
+ page?: number;
10
+ count?: number;
11
+ };
4
12
  export declare const typeToTable: {
5
13
  [key: string]: string;
6
14
  };
7
15
  /**
8
- * Class to build and execute db search query
16
+ * BaseSearch
17
+ *
18
+ * Abstract base class for building and executing database search queries.
19
+ *
20
+ * Responsibilities:
21
+ * - Parse and normalize incoming query string parameters.
22
+ * - Build database queries using Knex.
23
+ * - Execute queries against PostgreSQL by default.
24
+ * - Return standardized search API response format including metadata.
25
+ *
26
+ * Default Behavior:
27
+ * - The `query()` method executes against PostgreSQL using a Knex client.
28
+ *
29
+ * DuckDB Support:
30
+ * - Subclasses that query DuckDB (e.g., *S3Search classes) must override
31
+ * the `query()` and related methods
32
+ * - DuckDB subclasses are responsible for:
33
+ * - Executing queries using a DuckDB connection.
34
+ * - Handling sequential execution (to avoid prepared statement conflicts).
35
+ * - Translating DuckDB result types (e.g., string dates/JSON) into proper API types.
36
+ *
37
+ * Design Notes:
38
+ * - Query construction logic (e.g., `buildSearch`) is shared across Postgres
39
+ * and DuckDB implementations.
40
+ * - Execution strategy is delegated to subclasses when a different database
41
+ * engine is required.
9
42
  */
10
43
  declare abstract class BaseSearch {
11
44
  readonly type: string;
@@ -44,6 +77,17 @@ declare abstract class BaseSearch {
44
77
  * @returns whether an estimated row count should be returned
45
78
  */
46
79
  protected shouldEstimateRowcount(countSql: string): boolean;
80
+ /**
81
+ * Build a JSON query expression string for nested fields.
82
+ *
83
+ *
84
+ * @param fullFieldName - Dot-separated JSON path, e.g., 'query_fields.cnm.receivedTime'
85
+ * @returns The JSON query path string
86
+ * @example
87
+ * buildJsonQueryExpression('query_fields.cnm.receivedTime')
88
+ * // returns: query_fields -> 'cnm' ->> 'receivedTime'
89
+ */
90
+ protected buildJsonQueryExpression: (fullFieldName: string) => string;
47
91
  /**
48
92
  * Build the search query
49
93
  *
@@ -59,7 +103,7 @@ declare abstract class BaseSearch {
59
103
  *
60
104
  * @returns metadata template
61
105
  */
62
- private _metaTemplate;
106
+ protected _metaTemplate(): Meta;
63
107
  /**
64
108
  * Build basic query
65
109
  *
@@ -22,12 +22,55 @@ exports.typeToTable = {
22
22
  reconciliationReport: tables_1.TableNames.reconciliationReports,
23
23
  };
24
24
  /**
25
- * Class to build and execute db search query
25
+ * BaseSearch
26
+ *
27
+ * Abstract base class for building and executing database search queries.
28
+ *
29
+ * Responsibilities:
30
+ * - Parse and normalize incoming query string parameters.
31
+ * - Build database queries using Knex.
32
+ * - Execute queries against PostgreSQL by default.
33
+ * - Return standardized search API response format including metadata.
34
+ *
35
+ * Default Behavior:
36
+ * - The `query()` method executes against PostgreSQL using a Knex client.
37
+ *
38
+ * DuckDB Support:
39
+ * - Subclasses that query DuckDB (e.g., *S3Search classes) must override
40
+ * the `query()` and related methods
41
+ * - DuckDB subclasses are responsible for:
42
+ * - Executing queries using a DuckDB connection.
43
+ * - Handling sequential execution (to avoid prepared statement conflicts).
44
+ * - Translating DuckDB result types (e.g., string dates/JSON) into proper API types.
45
+ *
46
+ * Design Notes:
47
+ * - Query construction logic (e.g., `buildSearch`) is shared across Postgres
48
+ * and DuckDB implementations.
49
+ * - Execution strategy is delegated to subclasses when a different database
50
+ * engine is required.
26
51
  */
27
52
  class BaseSearch {
28
53
  constructor(event, type) {
29
54
  // parsed from queryStringParameters for query build
30
55
  this.dbQueryParameters = {};
56
+ /**
57
+ * Build a JSON query expression string for nested fields.
58
+ *
59
+ *
60
+ * @param fullFieldName - Dot-separated JSON path, e.g., 'query_fields.cnm.receivedTime'
61
+ * @returns The JSON query path string
62
+ * @example
63
+ * buildJsonQueryExpression('query_fields.cnm.receivedTime')
64
+ * // returns: query_fields -> 'cnm' ->> 'receivedTime'
65
+ */
66
+ this.buildJsonQueryExpression = (fullFieldName) => {
67
+ const normalizedFieldName = fullFieldName === 'error.Error.keyword'
68
+ ? 'error.Error' : fullFieldName;
69
+ const [column, ...pathParts] = normalizedFieldName.split('.');
70
+ return `${column}${pathParts
71
+ .map((p, i) => (i === pathParts.length - 1 ? ` ->> '${p}'` : ` -> '${p}'`))
72
+ .join('')}`;
73
+ };
31
74
  this.type = type;
32
75
  this.tableName = exports.typeToTable[this.type];
33
76
  this.queryStringParameters = event?.queryStringParameters ?? {};
@@ -81,7 +124,7 @@ class BaseSearch {
81
124
  * @returns whether an estimated row count should be returned
82
125
  */
83
126
  shouldEstimateRowcount(countSql) {
84
- const isBasicQuery = (countSql === `select count(*) from "${this.tableName}"`);
127
+ const isBasicQuery = (countSql === `select count(* as count) from "${this.tableName}"`);
85
128
  return this.dbQueryParameters.estimateTableRowCount === true && isBasicQuery;
86
129
  }
87
130
  /**
@@ -127,7 +170,7 @@ class BaseSearch {
127
170
  */
128
171
  buildBasicQuery(knex) {
129
172
  const countQuery = knex(this.tableName)
130
- .count('*');
173
+ .count('* as count');
131
174
  const searchQuery = knex(this.tableName)
132
175
  .select(`${this.tableName}.*`);
133
176
  return { countQuery, searchQuery };
@@ -158,6 +201,10 @@ class BaseSearch {
158
201
  Object.entries(exists).forEach(([name, value]) => {
159
202
  const queryMethod = value ? 'whereNotNull' : 'whereNull';
160
203
  const checkNull = value ? 'not null' : 'null';
204
+ if (name.includes('.')) {
205
+ [countQuery, searchQuery].forEach((query) => query?.whereRaw(`(${this.tableName}.${this.buildJsonQueryExpression(name)}) is ${checkNull}`));
206
+ return;
207
+ }
161
208
  switch (name) {
162
209
  case 'collectionName':
163
210
  case 'collectionVersion':
@@ -175,10 +222,6 @@ class BaseSearch {
175
222
  case 'asyncOperationId':
176
223
  [countQuery, searchQuery].forEach((query) => query?.[queryMethod](`${this.tableName}.async_operation_cumulus_id`));
177
224
  break;
178
- case 'error':
179
- case 'error.Error':
180
- [countQuery, searchQuery].forEach((query) => query?.whereRaw(`${this.tableName}.error ->> 'Error' is ${checkNull}`));
181
- break;
182
225
  case 'parentArn':
183
226
  [countQuery, searchQuery].forEach((query) => query?.[queryMethod](`${this.tableName}.parent_cumulus_id`));
184
227
  break;
@@ -200,8 +243,21 @@ class BaseSearch {
200
243
  buildRangeQuery(params) {
201
244
  const { countQuery, searchQuery, dbQueryParameters } = params;
202
245
  const { range = {} } = dbQueryParameters ?? this.dbQueryParameters;
246
+ const queries = [countQuery, searchQuery];
203
247
  Object.entries(range).forEach(([name, rangeValues]) => {
204
- const { gte, lte } = rangeValues;
248
+ const { gte, lte } = rangeValues ?? {};
249
+ if (!gte && !lte)
250
+ return;
251
+ if (name.includes('.')) {
252
+ const jsonExpr = `(${this.tableName}.${this.buildJsonQueryExpression(name)})`;
253
+ if (gte) {
254
+ queries.forEach((query) => query?.whereRaw(`${jsonExpr} >= ?`, [gte]));
255
+ }
256
+ if (lte) {
257
+ queries.forEach((query) => query?.whereRaw(`${jsonExpr} <= ?`, [lte]));
258
+ }
259
+ return;
260
+ }
205
261
  if (gte) {
206
262
  [countQuery, searchQuery].forEach((query) => query?.where(`${this.tableName}.${name}`, '>=', gte));
207
263
  }
@@ -223,6 +279,10 @@ class BaseSearch {
223
279
  const { countQuery, searchQuery, dbQueryParameters } = params;
224
280
  const { term = {} } = dbQueryParameters ?? this.dbQueryParameters;
225
281
  Object.entries(term).forEach(([name, value]) => {
282
+ if (name.includes('.')) {
283
+ [countQuery, searchQuery].forEach((query) => query?.whereRaw(`(${this.tableName}.${this.buildJsonQueryExpression(name)}) = ?`, value));
284
+ return;
285
+ }
226
286
  switch (name) {
227
287
  case 'collectionName':
228
288
  [countQuery, searchQuery].forEach((query) => query?.where(`${collectionsTable}.name`, value));
@@ -239,10 +299,6 @@ class BaseSearch {
239
299
  case 'pdrName':
240
300
  [countQuery, searchQuery].forEach((query) => query?.where(`${pdrsTable}.name`, value));
241
301
  break;
242
- case 'error.Error':
243
- [countQuery, searchQuery]
244
- .forEach((query) => value && query?.whereRaw(`${this.tableName}.error->>'Error' = ?`, value));
245
- break;
246
302
  case 'asyncOperationId':
247
303
  [countQuery, searchQuery].forEach((query) => query?.where(`${asyncOperationsTable}.id`, value));
248
304
  break;
@@ -282,6 +338,10 @@ class BaseSearch {
282
338
  .forEach((query) => query?.whereIn([`${collectionsTable}.name`, `${collectionsTable}.version`], collectionPair));
283
339
  }
284
340
  Object.entries((0, omit_1.default)(terms, ['collectionName', 'collectionVersion'])).forEach(([name, value]) => {
341
+ if (name.includes('.')) {
342
+ [countQuery, searchQuery].forEach((query) => query?.whereRaw(`(${this.tableName}.${this.buildJsonQueryExpression(name)}) in (${value.map(() => '?').join(',')})`, [...value]));
343
+ return;
344
+ }
285
345
  switch (name) {
286
346
  case 'executionArn':
287
347
  [countQuery, searchQuery].forEach((query) => query?.whereIn(`${executionsTable}.arn`, value));
@@ -292,10 +352,6 @@ class BaseSearch {
292
352
  case 'pdrName':
293
353
  [countQuery, searchQuery].forEach((query) => query?.whereIn(`${pdrsTable}.name`, value));
294
354
  break;
295
- case 'error.Error':
296
- [countQuery, searchQuery]
297
- .forEach((query) => query?.whereRaw(`${this.tableName}.error->>'Error' in (${value.map(() => '?').join(',')})`, [...value]));
298
- break;
299
355
  case 'asyncOperationId':
300
356
  [countQuery, searchQuery].forEach((query) => query?.whereIn(`${asyncOperationsTable}.id`, value));
301
357
  break;
@@ -328,6 +384,10 @@ class BaseSearch {
328
384
  }));
329
385
  }
330
386
  Object.entries((0, omit_1.default)(term, ['collectionName', 'collectionVersion'])).forEach(([name, value]) => {
387
+ if (name.includes('.')) {
388
+ [countQuery, searchQuery].forEach((query) => query?.whereRaw(`(${this.tableName}.${this.buildJsonQueryExpression(name)}) != ?`, value));
389
+ return;
390
+ }
331
391
  switch (name) {
332
392
  case 'executionArn':
333
393
  [countQuery, searchQuery].forEach((query) => query?.whereNot(`${executionsTable}.arn`, value));
@@ -344,9 +404,6 @@ class BaseSearch {
344
404
  case 'parentArn':
345
405
  [countQuery, searchQuery].forEach((query) => query?.whereNot(`${executionsTable}_parent.arn`, value));
346
406
  break;
347
- case 'error.Error':
348
- [countQuery, searchQuery].forEach((query) => value && query?.whereRaw(`${this.tableName}.error->>'Error' != ?`, value));
349
- break;
350
407
  default:
351
408
  [countQuery, searchQuery].forEach((query) => query?.whereNot(`${this.tableName}.${name}`, value));
352
409
  break;
@@ -361,18 +418,23 @@ class BaseSearch {
361
418
  * @param [params.dbQueryParameters] - db query parameters
362
419
  */
363
420
  buildSortQuery(params) {
421
+ const customColumns = ['collectionName', 'collectionVersion', 'executionArn', 'providerName', 'pdrName', 'asyncOperationId', 'parentArn'];
364
422
  const { searchQuery, dbQueryParameters } = params;
365
423
  const { sort } = dbQueryParameters || this.dbQueryParameters;
366
424
  sort?.forEach((key) => {
367
- if (key.column.startsWith('error')) {
368
- searchQuery.orderByRaw(`${this.tableName}.error ->> 'Error' ${key.order}`);
425
+ const prefixedColumn = `${this.tableName}.${key.column}`;
426
+ if (key.column.includes('.')) {
427
+ searchQuery.orderByRaw(`(${this.tableName}.${this.buildJsonQueryExpression(key.column)}) ${key.order}`);
369
428
  }
370
429
  else if (dbQueryParameters?.collate) {
371
430
  searchQuery.orderByRaw(`${key} collate \"${dbQueryParameters.collate}\"`);
372
431
  }
373
- else {
432
+ else if (customColumns.includes(key.column)) {
374
433
  searchQuery.orderBy([key]);
375
434
  }
435
+ else {
436
+ searchQuery.orderBy(prefixedColumn, key.order);
437
+ }
376
438
  });
377
439
  }
378
440
  /**
@@ -3,14 +3,17 @@ import { CollectionRecord } from '@cumulus/types/api/collections';
3
3
  import { BaseSearch } from './BaseSearch';
4
4
  import { DbQueryParameters, QueryEvent } from '../types/search';
5
5
  import { PostgresCollectionRecord } from '../types/collection';
6
- declare type Statuses = {
6
+ export declare type Statuses = {
7
7
  queued: number;
8
8
  completed: number;
9
9
  failed: number;
10
10
  running: number;
11
11
  total: number;
12
12
  };
13
- interface CollectionRecordApi extends CollectionRecord {
13
+ export declare type StatsRecords = {
14
+ [key: number]: Statuses;
15
+ };
16
+ export interface CollectionRecordApi extends CollectionRecord {
14
17
  stats?: Statuses;
15
18
  }
16
19
  /**
@@ -58,7 +61,7 @@ export declare class CollectionSearch extends BaseSearch {
58
61
  * @param knex - knex for the stats query
59
62
  * @returns the collection's granules status' aggregation
60
63
  */
61
- private retrieveGranuleStats;
64
+ protected retrieveGranuleStats(collectionCumulusIds: number[], knex: Knex): Promise<StatsRecords>;
62
65
  /**
63
66
  * Translate postgres records to api records
64
67
  *
@@ -68,5 +71,4 @@ export declare class CollectionSearch extends BaseSearch {
68
71
  */
69
72
  protected translatePostgresRecordsToApiRecords(pgRecords: PostgresCollectionRecord[], knex: Knex): Promise<Partial<CollectionRecordApi>[]>;
70
73
  }
71
- export {};
72
74
  //# sourceMappingURL=CollectionSearch.d.ts.map
@@ -61,8 +61,7 @@ class CollectionSearch extends BaseSearch_1.BaseSearch {
61
61
  subQuery
62
62
  .clear('select')
63
63
  .select(1)
64
- .where(`${granulesTable}.collection_cumulus_id`, knex.raw(`${this.tableName}.cumulus_id`))
65
- .limit(1);
64
+ .where(`${granulesTable}.collection_cumulus_id`, knex.raw(`${this.tableName}.cumulus_id`));
66
65
  return subQuery;
67
66
  }
68
67
  /**
@@ -101,7 +100,7 @@ class CollectionSearch extends BaseSearch_1.BaseSearch {
101
100
  }
102
101
  statsQuery
103
102
  .select(`${granulesTable}.collection_cumulus_id`, `${granulesTable}.status`)
104
- .count('*')
103
+ .count('* as count')
105
104
  .groupBy(`${granulesTable}.collection_cumulus_id`, `${granulesTable}.status`)
106
105
  .whereIn(`${granulesTable}.collection_cumulus_id`, collectionCumulusIds);
107
106
  log.debug(`retrieveGranuleStats statsQuery: ${statsQuery?.toSQL().sql}`);
@@ -14,7 +14,7 @@ interface ExecutionRecord extends BaseRecord, PostgresExecutionRecord {
14
14
  * Class to build and execute db search query for executions
15
15
  */
16
16
  export declare class ExecutionSearch extends BaseSearch {
17
- constructor(event: QueryEvent);
17
+ constructor(event: QueryEvent, enableEstimate?: boolean);
18
18
  /**
19
19
  * check if joined async_operations table search is needed
20
20
  *
@@ -16,9 +16,9 @@ const log = new logger_1.default({ sender: '@cumulus/db/ExecutionSearch' });
16
16
  * Class to build and execute db search query for executions
17
17
  */
18
18
  class ExecutionSearch extends BaseSearch_1.BaseSearch {
19
- constructor(event) {
19
+ constructor(event, enableEstimate = true) {
20
20
  // estimate the table rowcount by default
21
- if (event?.queryStringParameters?.estimateTableRowCount !== 'false') {
21
+ if (enableEstimate && event?.queryStringParameters?.estimateTableRowCount !== 'false') {
22
22
  (0, set_1.default)(event, 'queryStringParameters.estimateTableRowCount', 'true');
23
23
  }
24
24
  super(event, 'execution');
@@ -62,7 +62,7 @@ class ExecutionSearch extends BaseSearch_1.BaseSearch {
62
62
  searchQuery.select({ parentArn: `${executionsTable}_parent.arn` });
63
63
  }
64
64
  const countQuery = knex(this.tableName)
65
- .count('*');
65
+ .count('* as count');
66
66
  if (this.searchCollection()) {
67
67
  countQuery.innerJoin(collectionsTable, `${this.tableName}.collection_cumulus_id`, `${collectionsTable}.cumulus_id`);
68
68
  searchQuery.innerJoin(collectionsTable, `${this.tableName}.collection_cumulus_id`, `${collectionsTable}.cumulus_id`);
@@ -4,7 +4,7 @@ import { BaseRecord } from '../types/base';
4
4
  import { BaseSearch } from './BaseSearch';
5
5
  import { DbQueryParameters, QueryEvent } from '../types/search';
6
6
  import { PostgresGranuleRecord } from '../types/granule';
7
- interface GranuleRecord extends BaseRecord, PostgresGranuleRecord {
7
+ export interface GranuleRecord extends BaseRecord, PostgresGranuleRecord {
8
8
  collectionName: string;
9
9
  collectionVersion: string;
10
10
  pdrName?: string;
@@ -14,7 +14,7 @@ interface GranuleRecord extends BaseRecord, PostgresGranuleRecord {
14
14
  * Class to build and execute db search query for granules
15
15
  */
16
16
  export declare class GranuleSearch extends BaseSearch {
17
- constructor(event: QueryEvent);
17
+ constructor(event: QueryEvent, enableEstimate?: boolean);
18
18
  /**
19
19
  * Build basic query
20
20
  *
@@ -59,5 +59,4 @@ export declare class GranuleSearch extends BaseSearch {
59
59
  */
60
60
  protected translatePostgresRecordsToApiRecords(pgRecords: GranuleRecord[], knex: Knex): Promise<Partial<ApiGranuleRecord>[]>;
61
61
  }
62
- export {};
63
62
  //# sourceMappingURL=GranuleSearch.d.ts.map