blob: 54021bde93d5fa2923309d63cd5124be2bbee181 [file] [log] [blame]
/*
* 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.
*/
package org.apache.ignite.internal.sql.command;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.ignite.internal.processors.bulkload.BulkLoadAckClientParameters;
import org.apache.ignite.internal.processors.bulkload.BulkLoadCsvFormat;
import org.apache.ignite.internal.processors.bulkload.BulkLoadFormat;
import org.apache.ignite.internal.sql.SqlKeyword;
import org.apache.ignite.internal.sql.SqlLexer;
import org.apache.ignite.internal.sql.SqlLexerTokenType;
import org.apache.ignite.internal.util.typedef.internal.S;
import static org.apache.ignite.internal.sql.SqlParserUtils.error;
import static org.apache.ignite.internal.sql.SqlParserUtils.errorUnexpectedToken;
import static org.apache.ignite.internal.sql.SqlParserUtils.parseBoolean;
import static org.apache.ignite.internal.sql.SqlParserUtils.parseIdentifier;
import static org.apache.ignite.internal.sql.SqlParserUtils.parseInt;
import static org.apache.ignite.internal.sql.SqlParserUtils.parseQualifiedIdentifier;
import static org.apache.ignite.internal.sql.SqlParserUtils.parseString;
import static org.apache.ignite.internal.sql.SqlParserUtils.skipCommaOrRightParenthesis;
import static org.apache.ignite.internal.sql.SqlParserUtils.skipIfMatches;
import static org.apache.ignite.internal.sql.SqlParserUtils.skipIfMatchesKeyword;
/**
* A parser for a COPY command (called 'bulk load' in the code, since word 'copy' is too generic).
*/
public class SqlBulkLoadCommand implements SqlCommand {
/** Local file name to send from client to server. */
private String locFileName;
/** Schema name + table name. */
private SqlQualifiedName tblQName;
/** User-specified list of columns. */
private List<String> cols;
/** File format. */
private BulkLoadFormat inputFormat;
/** Packet size (size of portion of a file sent in each sub-request). */
private Integer packetSize;
/**
* Parses the command.
*
* @param lex The lexer.
* @return The parsed command object.
*/
@Override public SqlCommand parse(SqlLexer lex) {
skipIfMatchesKeyword(lex, SqlKeyword.FROM); // COPY keyword is already parsed
parseFileName(lex);
parseTableName(lex);
parseColumns(lex);
parseFormat(lex);
parseParameters(lex);
return this;
}
/**
* Parses the file name.
*
* @param lex The lexer.
*/
private void parseFileName(SqlLexer lex) {
if (lex.lookAhead().tokenType() != SqlLexerTokenType.STRING)
throw errorUnexpectedToken(lex.lookAhead(), "[file name: string]");
lex.shift();
locFileName = lex.token();
}
/**
* Parses the schema and table names.
*
* @param lex The lexer.
*/
private void parseTableName(SqlLexer lex) {
skipIfMatchesKeyword(lex, SqlKeyword.INTO);
tblQName = parseQualifiedIdentifier(lex);
}
/**
* Parses the list of columns.
*
* @param lex The lexer.
*/
private void parseColumns(SqlLexer lex) {
skipIfMatches(lex, SqlLexerTokenType.PARENTHESIS_LEFT);
cols = new ArrayList<>();
do {
cols.add(parseColumn(lex));
}
while (!skipCommaOrRightParenthesis(lex));
}
/**
* Parses column clause.
*
* @param lex The lexer.
* @return The column name.
*/
private String parseColumn(SqlLexer lex) {
return parseIdentifier(lex);
}
/**
* Parses the format clause.
*
* @param lex The lexer.
*/
private void parseFormat(SqlLexer lex) {
skipIfMatchesKeyword(lex, SqlKeyword.FORMAT);
String name = parseIdentifier(lex);
switch (name.toUpperCase()) {
case BulkLoadCsvFormat.NAME:
BulkLoadCsvFormat fmt = new BulkLoadCsvFormat();
// IGNITE-7537 will introduce user-defined values
fmt.lineSeparator(BulkLoadCsvFormat.DEFAULT_LINE_SEPARATOR);
fmt.fieldSeparator(BulkLoadCsvFormat.DEFAULT_FIELD_SEPARATOR);
fmt.quoteChars(BulkLoadCsvFormat.DEFAULT_QUOTE_CHARS);
fmt.commentChars(BulkLoadCsvFormat.DEFAULT_COMMENT_CHARS);
fmt.escapeChars(BulkLoadCsvFormat.DEFAULT_ESCAPE_CHARS);
fmt.nullString(BulkLoadCsvFormat.DEFAULT_NULL_STRING);
fmt.trim(BulkLoadCsvFormat.DEFAULT_TRIM_SPACES);
parseCsvOptions(lex, fmt);
validateCsvParserFormat(lex, fmt);
inputFormat = fmt;
break;
default:
throw error(lex, "Unknown format name: " + name +
". Currently supported format is " + BulkLoadCsvFormat.NAME);
}
}
/**
* Parses CSV format options.
*
* @param lex The lexer.
* @param format CSV format object to configure.
*/
private void parseCsvOptions(SqlLexer lex, BulkLoadCsvFormat format) {
while (lex.lookAhead().tokenType() == SqlLexerTokenType.DEFAULT) {
switch (lex.lookAhead().token()) {
case SqlKeyword.CHARSET: {
lex.shift();
String charsetName = parseString(lex);
format.inputCharsetName(charsetName);
break;
}
case SqlKeyword.DELIMITER: {
lex.shift();
String delimiter = parseString(lex);
format.fieldSeparator(Pattern.compile(delimiter));
break;
}
case SqlKeyword.TRIM: {
lex.shift();
Boolean trim = parseBoolean(lex);
format.trim(trim);
break;
}
case SqlKeyword.NULLSTRING: {
lex.shift();
String nullString = parseString(lex);
format.nullString(nullString);
break;
}
default:
return;
}
}
}
/**
* Parses the optional parameters.
*
* @param lex The lexer.
*/
private void parseParameters(SqlLexer lex) {
while (lex.lookAhead().tokenType() == SqlLexerTokenType.DEFAULT) {
switch (lex.lookAhead().token()) {
case SqlKeyword.PACKET_SIZE:
lex.shift();
int size = parseInt(lex);
if (!BulkLoadAckClientParameters.isValidPacketSize(size))
throw error(lex, BulkLoadAckClientParameters.packetSizeErrorMesssage(size));
packetSize = size;
break;
default:
return;
}
}
}
/**
* Parses the optional parameters.
*
* @param lex The lexer.
* @param format CSV format object to validate.
*/
private void validateCsvParserFormat(SqlLexer lex, BulkLoadCsvFormat format) {
String delimiter = format.fieldSeparator().toString();
String quoteChars = format.quoteChars();
if (delimiter.length() > 1 || quoteChars.length() > 1)
throw error(lex, "Delimiter or quote chars must consist of single character: delim is '" + delimiter
+ "', quote char is '" + quoteChars + "'");
if (delimiter.equals(quoteChars))
throw error(lex, "Invalid delimiter or quote chars: delim is '" + delimiter
+ "', quote char is '" + quoteChars + "'");
}
/**
* Returns the schemaName.
*
* @return schemaName.
*/
@Override public String schemaName() {
return tblQName.schemaName();
}
/** {@inheritDoc} */
@Override public void schemaName(String schemaName) {
tblQName.schemaName(schemaName);
}
/**
* Returns the table name.
*
* @return The table name
*/
public String tableName() {
return tblQName.name();
}
/**
* Sets the table name
*
* @param tblName The table name.
*/
public void tableName(String tblName) {
tblQName.name(tblName);
}
/**
* Returns the local file name.
*
* @return The local file name.
*/
public String localFileName() {
return locFileName;
}
/**
* Sets the local file name.
*
* @param locFileName The local file name.
*/
public void localFileName(String locFileName) {
this.locFileName = locFileName;
}
/**
* Returns the list of columns.
*
* @return The list of columns.
*/
public List<String> columns() {
return cols;
}
/**
* Returns the input file format.
*
* @return The input file format.
*/
public BulkLoadFormat inputFormat() {
return inputFormat;
}
/**
* Returns the packet size.
*
* @return The packet size.
*/
public Integer packetSize() {
return packetSize;
}
/**
* Sets the packet size.
*
* @param packetSize The packet size.
*/
public void packetSize(int packetSize) {
this.packetSize = packetSize;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(SqlBulkLoadCommand.class, this);
}
}