Skip to content

Commit 022d4f5

Browse files
committed
Merge branch 'tickets/DM-52125'
2 parents fa1f3fd + 5daf520 commit 022d4f5

44 files changed

Lines changed: 462 additions & 228 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/ingest/api/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
.. note::
33

4-
Information in this guide corresponds to the version **39** of the Qserv REST API. Keep in mind
4+
Information in this guide corresponds to the version **49** of the Qserv REST API. Keep in mind
55
that each implementation of the API has a specific version. The version number will change
66
if any changes to the implementation or the API that might affect users will be made.
77
The current document will be kept updated to reflect the latest version of the API.

doc/ingest/api/reference/rest/controller/db-table-management.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ that has the following schema (of which only the database and database family-re
4646
"latitude_key" : "coord_dec",
4747
"create_time" : 1662774817703,
4848
"unique_primary_key" : 1,
49+
"charset_name" : "latin1",
50+
"collation_name" : "latin1_swedish_ci",
4951
"flag" : "",
5052
"name" : "Source",
5153
"director_database_name" : "",
@@ -221,7 +223,9 @@ the **partitioned** tables is presented below:
221223
"longitude_key" : <string>,
222224
"flag" : <string>,
223225
"ang_sep" : <double>,
224-
"unique_primary_key" : <number>
226+
"unique_primary_key" : <number>,
227+
"charset_name" : <string>,
228+
"collation_name" : <string>
225229
}
226230
227231
A description of the *regular* tables has a fewer number of attributes (attributes that which are specific to the *partitioned*
@@ -317,6 +321,13 @@ Where the attributes are:
317321
- ``0``: The primary key is not unique.
318322
- ``1``: The primary key is unique.
319323

324+
``charset_name`` : *part* = ``latin1``
325+
The optional parameter that affects the interpretation of the data in the CSV file when ingesting the contribution.
326+
The name will be also used for setting the ``CHARSET`` attribute of the relevant MySQL tables created by the service.
327+
328+
``collation_name`` : *part* = ``latin1_swedish_ci``
329+
The optional parameter is used for setting the ``COLLATE`` attribute of the relevant MySQL tables created by the service.
330+
320331
.. warning::
321332

322333
- The table schema does not include definitions of indexes. Those are managed separately after the table is published.

doc/user/http-frontend-ingest.rst

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,8 @@ Most services described in this document require user database and table names.
7979
This prefix is reserved for naming internal tables that Qserv places into user databases.
8080

8181
Depending on the version of the Qserv AOI, there are additional restrictions on the names of databases and tables.
82-
Before version number **46** of the API, database and table names could only container alphanumeric characters and underscores.
83-
This restriction was imposed to avoid issues with the MySQL server. This restriction was relaxed in version **46** of the API
84-
to allow the following special characters:
82+
Before version number **46** of the API, database and table names could only contain alphanumeric characters and underscores.
83+
This restriction is relaxed as of version **46** of the API to allow the following special characters:
8584

8685
- (white space)
8786
- ``-`` (hyphen)
@@ -140,6 +139,8 @@ to specify the operation to be performed. The object follows this schema:
140139
{ "database" : <string>,
141140
"table" : <string>,
142141
"binary_encoding" : <string>,
142+
"charset_name" : <string>,
143+
"collation_name" : <string>,
143144
"timeout" : <number>,
144145
"schema" : <array>,
145146
"indexes" : <array>,
@@ -159,6 +160,13 @@ Where:
159160

160161
- :ref:`ingest-general-binary-encoding` (REST)
161162

163+
``charset_name`` : *part* = ``latin1``
164+
The optional parameter that affects the interpretation of the data in the CSV file when ingesting the contribution.
165+
The name will be used for setting the ``CHARSET`` attribute of the relevant MySQL tables created by the service.
166+
167+
``collation_name`` : *part* = ``latin1_swedish_ci``
168+
The optional parameter is used for setting the ``COLLATE`` attribute of the relevant MySQL tables created by the service.
169+
162170
``schema`` : *array*
163171
The required schema definition. The schema must be a JSON array, where each entry represents a column specification.
164172
More information on the schema specification requirements can be found in the dedicated section of the document:
@@ -328,11 +336,11 @@ and files:
328336
The default value assumes the newline character.
329337

330338
``charset_name`` : *part* = ``latin1``
331-
The optional parameters specify the desired character set name to be assumed when ingesting
332-
the contribution. The default value may be also affected by the ingest services configuration.
333-
See the following document for more details:
339+
The optional parameter that affects the interpretation of the data in the CSV file when ingesting the contribution.
340+
The name will be also used for setting the ``CHARSET`` attribute of the relevant MySQL tables created by the service.
334341

335-
- :ref:`ingest-api-advanced-charset` (ADVANCED)
342+
``collation_name`` : *part* = ``latin1_swedish_ci``
343+
The optional parameter is used for setting the ``COLLATE`` attribute of the relevant MySQL tables created by the service.
336344

337345
``timeout`` : *part* = ``300``
338346
The optional timeout (in seconds) that limits the duration of the internal operations initiated by the service.
@@ -398,6 +406,8 @@ The request could be pushed to the service using:
398406
-F 'table=employee' \
399407
-F 'fields_terminated_by=,' \
400408
-F 'timeout=300' \
409+
-F 'charset_name=utf8mb4' \
410+
-F 'collation_name=utf8mb4_uca1400_ai_ci' \
401411
-F 'schema=@/path/to/schema.json' \
402412
-F 'indexes=@/path/to/indexes.json' \
403413
-F 'rows=@/path/to/employee.csv'

doc/user/http-frontend.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
.. note::
33

4-
- This guide corresponds to version **47** of the Qserv REST API. Note that each API implementation has a specific version.
4+
- This guide corresponds to version **49** of the Qserv REST API. Note that each API implementation has a specific version.
55
The version number will change if any modifications to the implementation or API that might affect users are made.
66
This document will be updated to reflect the latest API version.
77
- All communications with the service are over SSL/TLS encrypted connections.

src/admin/python/lsst/qserv/admin/itest.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,8 +1111,12 @@ def run_http_ingest(
11111111
# this scenario..
11121112
database = "user_test-db"
11131113
table_json = "json-table"
1114+
table_json_utf8 = "json-table-utf8"
11141115
table_csv = "csv$table"
1116+
table_csv_utf8 = "csv$table-utf8"
11151117
timeout = 30
1118+
charset = "utf8mb4"
1119+
collation = "utf8mb4_uca1400_ai_ci"
11161120

11171121
_log.debug("Testing user database: %s", database)
11181122

@@ -1138,6 +1142,19 @@ def run_http_ingest(
11381142
_log.error("Failed to query table: %s of user database: %s, error: ", table_json, database, e)
11391143
return False
11401144

1145+
# Create the table and ingest data using the JSON option. Then query the table.
1146+
try:
1147+
_http_ingest_data_json(http_frontend_uri, user, password, database, table_json_utf8, schema, indexes, rows, charset, collation)
1148+
except Exception as e:
1149+
_log.error("Failed to ingest data into table: %s of user database: %s, error: %s", table_json_utf8, database, e)
1150+
return False
1151+
try:
1152+
_http_query_table(http_frontend_uri, user, password, database, table_json_utf8, rows)
1153+
except Exception as e:
1154+
_log.error("Failed to query table: %s of user database: %s, error: ", table_json_utf8, database, e)
1155+
return False
1156+
1157+
11411158
# Create the table and ingest data using the CSV option. Then query the table.
11421159
try:
11431160
_http_ingest_data_csv(http_frontend_uri, user, password, database, table_csv, schema, indexes, rows, timeout)
@@ -1149,10 +1166,21 @@ def run_http_ingest(
11491166
except Exception as e:
11501167
_log.error("Failed to query table: %s of user database: %s, error: ", table_csv, database, e)
11511168

1169+
# Create the table and ingest data using the CSV option. Then query the table.
1170+
try:
1171+
_http_ingest_data_csv(http_frontend_uri, user, password, database, table_csv_utf8, schema, indexes, rows, timeout, charset, collation)
1172+
except Exception as e:
1173+
_log.error("Failed to ingest data into table: %s of user database: %s, error: %s", table_csv_utf8, database, e)
1174+
return False
1175+
try:
1176+
_http_query_table(http_frontend_uri, user, password, database, table_csv_utf8, rows)
1177+
except Exception as e:
1178+
_log.error("Failed to query table: %s of user database: %s, error: ", table_csv_utf8, database, e)
1179+
11521180
# Cleanup the tables and the database in two separate steps unless the user
11531181
# requested to keep the results.
11541182
if not keep_results:
1155-
for table in [table_json, table_csv]:
1183+
for table in [table_json, table_json_utf8, table_csv, table_csv_utf8]:
11561184
try:
11571185
_http_delete_table(http_frontend_uri, user, password, database, table)
11581186
except Exception as e:
@@ -1234,6 +1262,8 @@ def _http_ingest_data_json(
12341262
schema: List[Dict[str, str]],
12351263
indexes: List[Dict[str, Sequence[Collection[str]]]],
12361264
rows: List[List[Any]],
1265+
charset: Optional[str] = None,
1266+
collation: Optional[str] = None,
12371267
) -> None:
12381268
"""Ingest data into an existing table of the user database.
12391269
@@ -1253,6 +1283,14 @@ def _http_ingest_data_json(
12531283
The schema of the table to be created.
12541284
indexes : `list` [`dict` [`str`, `list` [`list` [`str`]]]]
12551285
The indexes of the table to be created.
1286+
rows : `list` [`list` [`Any`]]
1287+
The rows of data to be ingested into the table.
1288+
charset : `str`, optional
1289+
The character set to use for the table. If not provided, the default
1290+
character set will be used.
1291+
collation : `str`, optional
1292+
The collation to use for the table. If not provided, the default
1293+
collation will be used.
12561294
"""
12571295
_log.debug("Ingesting JSON data into table: %s of user database: %s", table, database)
12581296
data = {
@@ -1262,6 +1300,11 @@ def _http_ingest_data_json(
12621300
"indexes": indexes,
12631301
"rows": rows,
12641302
}
1303+
if charset is not None:
1304+
data["charset_name"] = charset
1305+
if collation is not None:
1306+
data["collation_name"] = collation
1307+
12651308
url = str(urljoin(http_frontend_uri, f"/ingest/data?version={repl_api_version}"))
12661309
req = requests.post(url, json=data, verify=False, auth=(requests.auth.HTTPBasicAuth(user, password)))
12671310
req.raise_for_status()
@@ -1279,7 +1322,9 @@ def _http_ingest_data_csv(
12791322
schema: List[Dict[str, str]],
12801323
indexes: List[Dict[str, Sequence[Collection[str]]]],
12811324
rows: List[List[Any]],
1282-
timeout: int
1325+
timeout: int,
1326+
charset: Optional[str] = None,
1327+
collation: Optional[str] = None,
12831328
) -> None:
12841329
"""Create the table and ingest the data into the table.
12851330
@@ -1299,7 +1344,16 @@ def _http_ingest_data_csv(
12991344
The schema of the table to be created.
13001345
indexes : `list` [`dict` [`str`, `list` [`list` [`str`]]]]
13011346
The indexes of the table to be created.
1347+
rows : `list` [`list` [`Any`]]
1348+
The rows of data to be ingested into the table.
13021349
timeout : `int`
1350+
The timeout for the ingestion operation in seconds.
1351+
charset : `str`, optional
1352+
The character set to use for the table. If not provided, the default
1353+
character set will be used.
1354+
collation : `str`, optional
1355+
The collation to use for the table. If not provided, the default
1356+
collation will be used.
13031357
"""
13041358
_log.debug("Ingesting CSV data into table: %s of user database: %s", table, database)
13051359
base_dir = "/tmp"
@@ -1319,17 +1373,21 @@ def _http_ingest_data_csv(
13191373
for row in rows:
13201374
csv_writer.writerow(row)
13211375

1322-
encoder = MultipartEncoder(
1323-
fields = {
1324-
"database" : (None, database),
1325-
"table": (None, table),
1326-
"fields_terminated_by": (None, ","),
1327-
"timeout": (None, str(timeout)),
1328-
"schema": (schema_file, open(schema_file_path, "rb"), "application/json"),
1329-
"indexes": (indexes_file, open(indexes_file_path, "rb"), "application/json"),
1330-
"rows": (rows_file, open(rows_file_path, "rb"), "text/csv"),
1331-
}
1332-
)
1376+
fields = {
1377+
"database" : (None, database),
1378+
"table": (None, table),
1379+
"fields_terminated_by": (None, ","),
1380+
"timeout": (None, str(timeout)),
1381+
"schema": (schema_file, open(schema_file_path, "rb"), "application/json"),
1382+
"indexes": (indexes_file, open(indexes_file_path, "rb"), "application/json"),
1383+
"rows": (rows_file, open(rows_file_path, "rb"), "text/csv"),
1384+
}
1385+
if charset is not None:
1386+
fields["charset_name"] = (None, charset)
1387+
if collation is not None:
1388+
fields["collation_name"] = (None, collation)
1389+
1390+
encoder = MultipartEncoder(fields=fields)
13331391
url = str(urljoin(http_frontend_uri, f"/ingest/csv?version={repl_api_version}"))
13341392
req = requests.post(url, data=encoder, headers={'Content-Type': encoder.content_type}, verify=False,
13351393
auth=(requests.auth.HTTPBasicAuth(user, password)))

src/admin/python/lsst/qserv/admin/replicationInterface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
chunk_info_file = "chunk_info.json"
4343

44-
repl_api_version = 48
44+
repl_api_version = 49
4545

4646
_log = logging.getLogger(__name__)
4747

src/czar/HttpCzarIngestCsvModule.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,12 @@ void HttpCzarIngestCsvModule::onEndOfFile() {
154154

155155
json HttpCzarIngestCsvModule::onEndOfBody() {
156156
debug(__func__);
157-
checkApiVersion(__func__, 39);
157+
checkApiVersion(__func__, 49);
158158

159159
_databaseName = body().required<string>("database");
160160
_tableName = body().required<string>("table");
161161
_charsetName = body().optional<string>("charset_name", "latin1");
162+
_collationName = body().optional<string>("collation_name", "latin1_swedish_ci");
162163
_fieldsTerminatedBy = body().optional<string>("fields_terminated_by", R"(\t)");
163164
_fieldsEnclosedBy = body().optional<string>("fields_enclosed_by", R"(\0)");
164165
_fieldsEscapedBy = body().optional<string>("fields_escaped_by", R"(\\)");
@@ -178,6 +179,7 @@ json HttpCzarIngestCsvModule::onEndOfBody() {
178179
debug(__func__, "database: '" + _databaseName + "'");
179180
debug(__func__, "table: '" + _tableName + "'");
180181
debug(__func__, "charsetName: '" + _charsetName + "'");
182+
debug(__func__, "collationName: '" + _collationName + "'");
181183
debug(__func__, "fields_terminated_by: '" + _fieldsTerminatedBy + "'");
182184
debug(__func__, "fields_enclosed_by: '" + _fieldsEnclosedBy + "'");
183185
debug(__func__, "fields_escaped_by: '" + _fieldsEscapedBy + "'");
@@ -216,7 +218,7 @@ json HttpCzarIngestCsvModule::onEndOfBody() {
216218

217219
// Make changes to the persistent state of Qserv and the Replicaton/Ingest system.
218220
list<pair<string, string>> const warnings = ingestData(
219-
_databaseName, _tableName, schema, indexes,
221+
_databaseName, _tableName, _charsetName, _collationName, schema, indexes,
220222
[&](uint32_t transactionId) -> map<string, string> { return _pushDataToWorkers(transactionId); });
221223
for (auto const& warning : warnings) {
222224
warn(warning.first, warning.second);

src/czar/HttpCzarIngestCsvModule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class HttpCzarIngestCsvModule : public http::FileUploadModule, public HttpCzarIn
119119
std::string _databaseName;
120120
std::string _tableName;
121121
std::string _charsetName;
122+
std::string _collationName;
122123
std::string _fieldsTerminatedBy;
123124
std::string _fieldsEnclosedBy;
124125
std::string _fieldsEscapedBy;

src/czar/HttpCzarIngestModule.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,12 @@ json HttpCzarIngestModule::executeImpl(string const& subModuleName) {
7474

7575
json HttpCzarIngestModule::_ingestData() {
7676
debug(__func__);
77-
checkApiVersion(__func__, 35);
77+
checkApiVersion(__func__, 49);
7878

7979
auto const databaseName = body().required<string>("database");
8080
auto const tableName = body().required<string>("table");
81+
auto const charsetName = body().optional<string>("charset_name", "latin1");
82+
auto const collationName = body().optional<string>("collation_name", "latin1_swedish_ci");
8183
setTimeoutSec(max(1U, body().optional<unsigned int>("timeout", timeoutSec())));
8284

8385
// This is needed for decoding values of the binary columns should they be present
@@ -129,13 +131,15 @@ json HttpCzarIngestModule::_ingestData() {
129131
// Make changes to the persistent state of Qserv and the Replicaton/Ingest system.
130132
// Post warnings if any reported by the method.
131133
list<pair<string, string>> const warnings = ingestData(
132-
databaseName, tableName, schema, indexes, [&](uint32_t transactionId) -> map<string, string> {
134+
databaseName, tableName, charsetName, collationName, schema, indexes,
135+
[&](uint32_t transactionId) -> map<string, string> {
133136
// Send table data to all eligible workers and wait for the responses.
134137
// Note that requests are sent in parallel, and the duration of each such request
135138
// is limited by the timeout parameter.
136139
json dataJson =
137140
json::object({{"transaction_id", transactionId},
138141
{"table", tableName},
142+
{"charset_name", charsetName},
139143
{"chunk", 0},
140144
{"overlap", 0},
141145
{"rows", rows},

src/czar/HttpCzarIngestModuleBase.cc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ HttpCzarIngestModuleBase::HttpCzarIngestModuleBase(asio::io_service& io_service)
8282
to_string(cconfig::CzarConfig::instance()->replicationRegistryPort())) {}
8383

8484
list<pair<string, string>> HttpCzarIngestModuleBase::ingestData(
85-
string const& databaseName, string const& tableName, json const& schema, json const& indexes,
85+
string const& databaseName, string const& tableName, string const& charsetName,
86+
string const& collationName, json const& schema, json const& indexes,
8687
function<map<string, string>(uint32_t)> const& submitRequestsToWorkers) {
8788
_unpublishOrCreateDatabase(databaseName);
88-
_createTable(databaseName, tableName, schema);
89+
_createTable(databaseName, tableName, charsetName, collationName, schema);
8990

9091
uint32_t transactionId = 0;
9192
try {
@@ -192,9 +193,14 @@ void HttpCzarIngestModuleBase::_publishDatabase(string const& databaseName) {
192193
}
193194

194195
void HttpCzarIngestModuleBase::_createTable(string const& databaseName, string const& tableName,
196+
string const& charsetName, string const& collationName,
195197
json const& schema) {
196-
json data = json::object(
197-
{{"database", databaseName}, {"table", tableName}, {"is_partitioned", 0}, {"schema", schema}});
198+
json data = json::object({{"database", databaseName},
199+
{"table", tableName},
200+
{"is_partitioned", 0},
201+
{"charset_name", charsetName},
202+
{"collation_name", collationName},
203+
{"schema", schema}});
198204
_requestController(http::Method::POST, "/ingest/table/", data);
199205
}
200206

0 commit comments

Comments
 (0)