Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow writing table name as a string literal #52635

Merged
8 changes: 7 additions & 1 deletion docs/en/operations/utilities/clickhouse-local.md
Expand Up @@ -34,7 +34,13 @@ The binary you just downloaded can run all sorts of ClickHouse tools and utiliti

A common use of `clickhouse-local` is to run ad-hoc queries on files: where you don't have to insert the data into a table. `clickhouse-local` can stream the data from a file into a temporary table and execute your SQL.

If the file is sitting on the same machine as `clickhouse-local`, use the `file` table engine. The following `reviews.tsv` file contains a sampling of Amazon product reviews:
If the file is sitting on the same machine as `clickhouse-local`, you can simple specify the file to load. The following `reviews.tsv` file contains a sampling of Amazon product reviews:

```bash
./clickhouse local -q "SELECT * FROM 'reviews.tsv'"
```

This command is a shortcut of:

```bash
./clickhouse local -q "SELECT * FROM file('reviews.tsv')"
Expand Down
32 changes: 32 additions & 0 deletions src/Parsers/ExpressionElementParsers.cpp
Expand Up @@ -243,6 +243,38 @@ bool ParserIdentifier::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
}


bool ParserTableAsStringLiteralIdentifier::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
if (pos->type != TokenType::StringLiteral)
return false;

ReadBufferFromMemory in(pos->begin, pos->size());
String s;

if (!tryReadQuotedStringInto(s, in))
{
expected.add(pos, "string literal");
return false;
}

if (in.count() != pos->size())
{
expected.add(pos, "string literal");
return false;
}

if (s.empty())
{
expected.add(pos, "non-empty string literal");
return false;
}

node = std::make_shared<ASTTableIdentifier>(s);
++pos;
return true;
}


bool ParserCompoundIdentifier::parseImpl(Pos & pos, ASTPtr & node, Expected & expected)
{
ASTPtr id_list;
Expand Down
13 changes: 13 additions & 0 deletions src/Parsers/ExpressionElementParsers.h
Expand Up @@ -34,6 +34,19 @@ class ParserIdentifier : public IParserBase
};


/** An identifier for tables written as string literal, for example, 'mytable.avro'
*/
class ParserTableAsStringLiteralIdentifier : public IParserBase
{
public:
explicit ParserTableAsStringLiteralIdentifier() {}

protected:
const char * getName() const override { return "string literal table identifier"; }
bool parseImpl(Pos & pos, ASTPtr & node, Expected & expected) override;
};


/** An identifier, possibly containing a dot, for example, x_yz123 or `something special` or Hits.EventTime,
* possibly with UUID clause like `db name`.`table name` UUID 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Parsers/ParserTablesInSelectQuery.cpp
Expand Up @@ -24,6 +24,8 @@ bool ParserTableExpression::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (!ParserWithOptionalAlias(std::make_unique<ParserSubquery>(), allow_alias_without_as_keyword).parse(pos, res->subquery, expected)
&& !ParserWithOptionalAlias(std::make_unique<ParserFunction>(false, true), allow_alias_without_as_keyword).parse(pos, res->table_function, expected)
&& !ParserWithOptionalAlias(std::make_unique<ParserCompoundIdentifier>(true, true), allow_alias_without_as_keyword)
.parse(pos, res->database_and_table_name, expected)
&& !ParserWithOptionalAlias(std::make_unique<ParserTableAsStringLiteralIdentifier>(), allow_alias_without_as_keyword)
.parse(pos, res->database_and_table_name, expected))
return false;

Expand Down
@@ -0,0 +1,27 @@
Test 1: check double quotes
1 abc 123 abacaba
2 def 456 bacabaa
3 story 78912 acabaab
4 history 21321321 cabaaba
Test 1a: check double quotes no parsing overflow
1
Test 1b: check double quotes empty
1
Test 2: check back quotes
1 abc 123 abacaba
2 def 456 bacabaa
3 story 78912 acabaab
4 history 21321321 cabaaba
Test 2a: check back quotes no parsing overflow
1
Test 2b: check back quotes empty
1
Test 3: check literal
1 abc 123 abacaba
2 def 456 bacabaa
3 story 78912 acabaab
4 history 21321321 cabaaba
Test 3a: check literal no parsing overflow
1
Test 3b: check literal empty
1
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh

dir=${CLICKHOUSE_TEST_UNIQUE_NAME}
[[ -d $dir ]] && rm -rd $dir
mkdir $dir

# Create temporary csv file for tests
echo '"id","str","int","text"' > $dir/tmp.csv
echo '1,"abc",123,"abacaba"' >> $dir/tmp.csv
echo '2,"def",456,"bacabaa"' >> $dir/tmp.csv
echo '3,"story",78912,"acabaab"' >> $dir/tmp.csv
echo '4,"history",21321321,"cabaaba"' >> $dir/tmp.csv

#################
echo "Test 1: check double quotes"

$CLICKHOUSE_LOCAL -q "SELECT * FROM \"${dir}/tmp.csv\""
#################
echo "Test 1a: check double quotes no parsing overflow"

$CLICKHOUSE_LOCAL -q "SELECT * FROM \"${dir}/tmp.csv\"\"bad\"" 2>&1 | grep -c "UNKNOWN_TABLE"
#################
echo "Test 1b: check double quotes empty"

$CLICKHOUSE_LOCAL -q "SELECT * FROM \"\"" 2>&1 | grep -c "SYNTAX_ERROR"
#################
echo "Test 2: check back quotes"

$CLICKHOUSE_LOCAL -q "SELECT * FROM \`${dir}/tmp.csv\`"
#################
echo "Test 2a: check back quotes no parsing overflow"

$CLICKHOUSE_LOCAL -q "SELECT * FROM \`${dir}/tmp.csv\`\`bad\`" 2>&1 | grep -c "UNKNOWN_TABLE"
#################
echo "Test 2b: check back quotes empty"

$CLICKHOUSE_LOCAL -q "SELECT * FROM \`\`" 2>&1 | grep -c "SYNTAX_ERROR"
#################
echo "Test 3: check literal"

$CLICKHOUSE_LOCAL -q "SELECT * FROM '${dir}/tmp.csv'"
#################
echo "Test 3a: check literal no parsing overflow"

$CLICKHOUSE_LOCAL -q "SELECT * FROM '${dir}/tmp.csv''bad'" 2>&1 | grep -c "SYNTAX_ERROR"
#################
echo "Test 3b: check literal empty"

$CLICKHOUSE_LOCAL -q "SELECT * FROM ''" 2>&1 | grep -c "SYNTAX_ERROR"

# Remove temporary dir with files
rm -rd $dir