@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.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -3
- package/dist/lib/granule.d.ts +4 -33
- package/dist/lib/granule.js +10 -61
- package/dist/models/granule.js +2 -2
- package/dist/s3search/AsyncOperationS3Search.d.ts +20 -0
- package/dist/s3search/AsyncOperationS3Search.js +29 -0
- package/dist/s3search/CollectionS3Search.d.ts +39 -0
- package/dist/s3search/CollectionS3Search.js +113 -0
- package/dist/s3search/DuckDBSearchExecutor.d.ts +36 -0
- package/dist/s3search/DuckDBSearchExecutor.js +57 -0
- package/dist/s3search/ExecutionS3Search.d.ts +20 -0
- package/dist/s3search/ExecutionS3Search.js +29 -0
- package/dist/s3search/GranuleS3Search.d.ts +31 -0
- package/dist/s3search/GranuleS3Search.js +100 -0
- package/dist/s3search/PdrS3Search.d.ts +20 -0
- package/dist/s3search/PdrS3Search.js +29 -0
- package/dist/s3search/ProviderS3Search.d.ts +20 -0
- package/dist/s3search/ProviderS3Search.js +29 -0
- package/dist/s3search/ReconciliationReportS3Search.d.ts +20 -0
- package/dist/s3search/ReconciliationReportS3Search.js +29 -0
- package/dist/s3search/RuleS3Search.d.ts +20 -0
- package/dist/s3search/RuleS3Search.js +29 -0
- package/dist/s3search/StatsS3Search.d.ts +25 -0
- package/dist/s3search/StatsS3Search.js +51 -0
- package/dist/s3search/duckdbHelpers.d.ts +43 -0
- package/dist/s3search/duckdbHelpers.js +83 -0
- package/dist/s3search/s3TableSchemas.d.ts +11 -0
- package/dist/s3search/s3TableSchemas.js +272 -0
- package/dist/search/BaseSearch.d.ts +46 -2
- package/dist/search/BaseSearch.js +84 -22
- package/dist/search/CollectionSearch.d.ts +6 -4
- package/dist/search/CollectionSearch.js +2 -3
- package/dist/search/ExecutionSearch.d.ts +1 -1
- package/dist/search/ExecutionSearch.js +3 -3
- package/dist/search/GranuleSearch.d.ts +2 -3
- package/dist/search/GranuleSearch.js +3 -3
- package/dist/search/PdrSearch.js +1 -1
- package/dist/search/ReconciliationReportSearch.js +1 -1
- package/dist/search/RuleSearch.js +4 -4
- package/dist/search/StatsSearch.d.ts +15 -4
- package/dist/search/StatsSearch.js +12 -6
- package/dist/search/field-mapping.d.ts +1 -3
- package/dist/search/field-mapping.js +40 -19
- package/dist/test-duckdb-utils.d.ts +31 -0
- package/dist/test-duckdb-utils.js +125 -0
- package/dist/test-utils.js +6 -0
- package/dist/translate/async_operations.js +7 -3
- package/dist/translate/collections.js +6 -6
- package/dist/translate/executions.js +7 -7
- package/dist/translate/granules.js +16 -11
- package/dist/translate/pdr.js +4 -4
- package/dist/translate/providers.js +2 -2
- package/dist/translate/reconciliation_reports.js +5 -4
- package/dist/translate/rules.d.ts +1 -1
- package/dist/translate/rules.js +6 -6
- package/dist/types/file.d.ts +2 -0
- package/dist/types/granule.d.ts +1 -1
- 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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|