blob: 31a6f52a2d39b9a4928689af10ad67f87104fdbd [file]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Timezone.hh"
#include "orc/Exceptions.hh"
#include "orc/OrcFile.hh"
#include <getopt.h>
#include <sys/time.h>
#include <time.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <list>
#include <memory>
#include <string>
static char gDelimiter = ',';
// extract one column raw text from one line
std::string extractColumn(std::string s, uint64_t colIndex) {
uint64_t col = 0;
size_t start = 0;
size_t end = s.find(gDelimiter);
while (col < colIndex && end != std::string::npos) {
start = end + 1;
end = s.find(gDelimiter, start);
++col;
}
return col == colIndex ? s.substr(start, end - start) : "";
}
static const char* GetDate(void) {
static char buf[200];
time_t t = time(nullptr);
struct tm* p = localtime(&t);
strftime(buf, sizeof(buf), "[%Y-%m-%d %H:%M:%S]", p);
return buf;
}
void fillLongValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex) {
orc::LongVectorBatch* longBatch = dynamic_cast<orc::LongVectorBatch*>(batch);
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
batch->notNull[i] = 1;
longBatch->data[i] = atoll(col.c_str());
}
}
longBatch->hasNulls = hasNull;
longBatch->numElements = numValues;
}
void fillStringValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex, orc::DataBuffer<char>& buffer) {
uint64_t offset = 0;
orc::StringVectorBatch* stringBatch = dynamic_cast<orc::StringVectorBatch*>(batch);
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
batch->notNull[i] = 1;
char* oldBufferAddress = buffer.data();
// Resize the buffer in case buffer does not have remaining space to store the next string.
while (buffer.size() - offset < col.size()) {
buffer.resize(buffer.size() * 2);
}
char* newBufferAddress = buffer.data();
// Refill stringBatch->data with the new addresses, if buffer's address has changed.
if (newBufferAddress != oldBufferAddress) {
for (uint64_t refillIndex = 0; refillIndex < i; ++refillIndex) {
stringBatch->data[refillIndex] =
stringBatch->data[refillIndex] - oldBufferAddress + newBufferAddress;
}
}
memcpy(buffer.data() + offset, col.c_str(), col.size());
stringBatch->data[i] = buffer.data() + offset;
stringBatch->length[i] = static_cast<int64_t>(col.size());
offset += col.size();
}
}
stringBatch->hasNulls = hasNull;
stringBatch->numElements = numValues;
}
void fillDoubleValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex) {
orc::DoubleVectorBatch* dblBatch = dynamic_cast<orc::DoubleVectorBatch*>(batch);
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
batch->notNull[i] = 1;
dblBatch->data[i] = atof(col.c_str());
}
}
dblBatch->hasNulls = hasNull;
dblBatch->numElements = numValues;
}
// parse fixed point decimal numbers
void fillDecimalValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex, size_t scale, size_t precision) {
orc::Decimal128VectorBatch* d128Batch = nullptr;
orc::Decimal64VectorBatch* d64Batch = nullptr;
if (precision <= 18) {
d64Batch = dynamic_cast<orc::Decimal64VectorBatch*>(batch);
d64Batch->scale = static_cast<int32_t>(scale);
} else {
d128Batch = dynamic_cast<orc::Decimal128VectorBatch*>(batch);
d128Batch->scale = static_cast<int32_t>(scale);
}
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
batch->notNull[i] = 1;
size_t ptPos = col.find('.');
size_t curScale = 0;
std::string num = col;
if (ptPos != std::string::npos) {
curScale = col.length() - ptPos - 1;
num = col.substr(0, ptPos) + col.substr(ptPos + 1);
}
orc::Int128 decimal(num);
while (curScale != scale) {
curScale++;
decimal *= 10;
}
if (precision <= 18) {
d64Batch->values[i] = decimal.toLong();
} else {
d128Batch->values[i] = decimal;
}
}
}
batch->hasNulls = hasNull;
batch->numElements = numValues;
}
void fillBoolValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex) {
orc::LongVectorBatch* boolBatch = dynamic_cast<orc::LongVectorBatch*>(batch);
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
batch->notNull[i] = 1;
std::transform(col.begin(), col.end(), col.begin(), ::tolower);
if (col == "true" || col == "t") {
boolBatch->data[i] = true;
} else {
boolBatch->data[i] = false;
}
}
}
boolBatch->hasNulls = hasNull;
boolBatch->numElements = numValues;
}
// parse date string from format YYYY-mm-dd
void fillDateValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex) {
orc::LongVectorBatch* longBatch = dynamic_cast<orc::LongVectorBatch*>(batch);
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
batch->notNull[i] = 1;
struct tm tm;
memset(&tm, 0, sizeof(struct tm));
strptime(col.c_str(), "%Y-%m-%d", &tm);
time_t t = mktime(&tm);
time_t t1970 = 0;
double seconds = difftime(t, t1970);
int64_t days = static_cast<int64_t>(seconds / (60 * 60 * 24));
longBatch->data[i] = days;
}
}
longBatch->hasNulls = hasNull;
longBatch->numElements = numValues;
}
// parse timestamp values in seconds
void fillTimestampValues(const std::vector<std::string>& data, orc::ColumnVectorBatch* batch,
uint64_t numValues, uint64_t colIndex) {
struct tm timeStruct;
orc::TimestampVectorBatch* tsBatch = dynamic_cast<orc::TimestampVectorBatch*>(batch);
bool hasNull = false;
for (uint64_t i = 0; i < numValues; ++i) {
std::string col = extractColumn(data[i], colIndex);
if (col.empty()) {
batch->notNull[i] = 0;
hasNull = true;
} else {
memset(&timeStruct, 0, sizeof(timeStruct));
char* left = strptime(col.c_str(), "%Y-%m-%d %H:%M:%S", &timeStruct);
if (left == nullptr) {
batch->notNull[i] = 0;
} else {
batch->notNull[i] = 1;
tsBatch->data[i] = timegm(&timeStruct);
char* tail;
double d = strtod(left, &tail);
if (tail != left) {
tsBatch->nanoseconds[i] = static_cast<long>(d * 1000000000.0);
} else {
tsBatch->nanoseconds[i] = 0;
}
}
}
}
tsBatch->hasNulls = hasNull;
tsBatch->numElements = numValues;
}
void usage() {
std::cout << "Usage: csv-import [-h] [--help]\n"
<< " [-m] [--metrics]\n"
<< " [-d <character>] [--delimiter=<character>]\n"
<< " [-s <size>] [--stripe=<size>]\n"
<< " [-c <size>] [--block=<size>]\n"
<< " [-b <size>] [--batch=<size>]\n"
<< " [-t <string>] [--timezone=<string>]\n"
<< " <schema> <input> <output>\n"
<< "Import CSV file into an Orc file using the specified schema.\n"
<< "The timezone is writer timezone of timestamp types.\n"
<< "Compound types are not yet supported.\n";
}
int main(int argc, char* argv[]) {
std::string input;
std::string output;
std::string schema;
std::string timezoneName = "GMT";
uint64_t stripeSize = (128 << 20); // 128M
uint64_t blockSize = 64 << 10; // 64K
uint64_t batchSize = 1024;
orc::CompressionKind compression = orc::CompressionKind_ZLIB;
static struct option longOptions[] = {{"help", no_argument, nullptr, 'h'},
{"metrics", no_argument, nullptr, 'm'},
{"delimiter", required_argument, nullptr, 'd'},
{"stripe", required_argument, nullptr, 's'},
{"block", required_argument, nullptr, 'c'},
{"batch", required_argument, nullptr, 'b'},
{"timezone", required_argument, nullptr, 't'},
{nullptr, 0, nullptr, 0}};
bool helpFlag = false;
bool showMetrics = false;
int opt;
char* tail;
do {
opt = getopt_long(argc, argv, "d:s:c:b:t:mh", longOptions, nullptr);
switch (opt) {
case 'h':
helpFlag = true;
opt = -1;
break;
case 'm':
showMetrics = true;
break;
case 'd':
gDelimiter = optarg[0];
break;
case 's':
stripeSize = strtoul(optarg, &tail, 10);
if (*tail != '\0') {
fprintf(stderr, "The --stripe parameter requires an integer option.\n");
return 1;
}
break;
case 'c':
blockSize = strtoul(optarg, &tail, 10);
if (*tail != '\0') {
fprintf(stderr, "The --block parameter requires an integer option.\n");
return 1;
}
break;
case 'b':
batchSize = strtoul(optarg, &tail, 10);
if (*tail != '\0') {
fprintf(stderr, "The --batch parameter requires an integer option.\n");
return 1;
}
break;
case 't':
timezoneName = std::string(optarg);
break;
}
} while (opt != -1);
argc -= optind;
argv += optind;
if (argc != 3 || helpFlag) {
usage();
return 1;
}
schema = argv[0];
input = argv[1];
output = argv[2];
std::cout << GetDate() << " Start importing Orc file..." << std::endl;
std::unique_ptr<orc::Type> fileType = orc::Type::buildTypeFromString(schema);
double totalElapsedTime = 0.0;
clock_t totalCPUTime = 0;
typedef std::list<orc::DataBuffer<char>> DataBufferList;
DataBufferList bufferList;
orc::WriterOptions options;
orc::WriterMetrics metrics;
options.setStripeSize(stripeSize);
options.setCompressionBlockSize(blockSize);
options.setCompression(compression);
options.setTimezoneName(timezoneName);
options.setWriterMetrics(showMetrics ? &metrics : nullptr);
std::unique_ptr<orc::OutputStream> outStream = orc::writeLocalFile(output);
std::unique_ptr<orc::Writer> writer = orc::createWriter(*fileType, outStream.get(), options);
std::unique_ptr<orc::ColumnVectorBatch> rowBatch = writer->createRowBatch(batchSize);
bool eof = false;
std::string line;
std::vector<std::string> data; // buffer that holds a batch of rows in raw text
std::ifstream finput(input.c_str());
while (!eof) {
uint64_t numValues = 0; // num of lines read in a batch
data.clear();
memset(rowBatch->notNull.data(), 1, batchSize);
// read a batch of lines from the input file
for (uint64_t i = 0; i < batchSize; ++i) {
if (!std::getline(finput, line)) {
eof = true;
break;
}
data.push_back(line);
++numValues;
}
if (numValues != 0) {
orc::StructVectorBatch* structBatch = dynamic_cast<orc::StructVectorBatch*>(rowBatch.get());
structBatch->numElements = numValues;
for (uint64_t i = 0; i < structBatch->fields.size(); ++i) {
const orc::Type* subType = fileType->getSubtype(i);
switch (subType->getKind()) {
case orc::BYTE:
case orc::INT:
case orc::SHORT:
case orc::LONG:
fillLongValues(data, structBatch->fields[i], numValues, i);
break;
case orc::STRING:
case orc::CHAR:
case orc::VARCHAR:
case orc::BINARY:
bufferList.emplace_back(*orc::getDefaultPool(), 1 * 1024 * 1024);
fillStringValues(data, structBatch->fields[i], numValues, i, bufferList.back());
break;
case orc::FLOAT:
case orc::DOUBLE:
fillDoubleValues(data, structBatch->fields[i], numValues, i);
break;
case orc::DECIMAL:
fillDecimalValues(data, structBatch->fields[i], numValues, i, subType->getScale(),
subType->getPrecision());
break;
case orc::BOOLEAN:
fillBoolValues(data, structBatch->fields[i], numValues, i);
break;
case orc::DATE:
fillDateValues(data, structBatch->fields[i], numValues, i);
break;
case orc::TIMESTAMP:
case orc::TIMESTAMP_INSTANT:
fillTimestampValues(data, structBatch->fields[i], numValues, i);
break;
case orc::STRUCT:
case orc::LIST:
case orc::MAP:
case orc::UNION:
case orc::GEOMETRY:
case orc::GEOGRAPHY:
throw std::runtime_error(subType->toString() + " is not supported yet.");
}
}
struct timeval t_start, t_end;
gettimeofday(&t_start, nullptr);
clock_t c_start = clock();
writer->add(*rowBatch);
totalCPUTime += (clock() - c_start);
gettimeofday(&t_end, nullptr);
totalElapsedTime += (static_cast<double>(t_end.tv_sec - t_start.tv_sec) * 1000000.0 +
static_cast<double>(t_end.tv_usec - t_start.tv_usec)) /
1000000.0;
}
}
struct timeval t_start, t_end;
gettimeofday(&t_start, nullptr);
clock_t c_start = clock();
writer->close();
totalCPUTime += (clock() - c_start);
gettimeofday(&t_end, nullptr);
totalElapsedTime += (static_cast<double>(t_end.tv_sec - t_start.tv_sec) * 1000000.0 +
static_cast<double>(t_end.tv_usec - t_start.tv_usec)) /
1000000.0;
std::cout << GetDate() << " Finish importing Orc file." << std::endl;
std::cout << GetDate() << " Total writer elasped time: " << totalElapsedTime << "s." << std::endl;
std::cout << GetDate()
<< " Total writer CPU time: " << static_cast<double>(totalCPUTime) / CLOCKS_PER_SEC
<< "s." << std::endl;
if (showMetrics) {
std::cout << GetDate() << " IO block lantency: "
<< static_cast<double>(metrics.IOBlockingLatencyUs) / 1000000.0 << "s." << std::endl;
std::cout << GetDate() << " IO count: " << metrics.IOCount << std::endl;
}
return 0;
}