diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java index 7ad5172fca41..7915d64a6808 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java @@ -19,6 +19,7 @@ package org.apache.iotdb.relational.it.schema; +import org.apache.iotdb.commons.utils.WindowsOSUtils; import org.apache.iotdb.db.it.utils.TestUtils; import org.apache.iotdb.isession.ITableSession; import org.apache.iotdb.it.env.EnvFactory; @@ -31,6 +32,7 @@ import org.apache.tsfile.enums.ColumnCategory; import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.external.commons.lang3.SystemUtils; import org.apache.tsfile.write.record.Tablet; import org.apache.tsfile.write.schema.IMeasurementSchema; import org.apache.tsfile.write.schema.MeasurementSchema; @@ -784,6 +786,11 @@ public void testConcurrentAutoCreateAndDropColumn() throws Exception { @Test public void testTableObjectCheck() throws Exception { final Set illegal = new HashSet<>(Arrays.asList("./", ".", "..", ".\\", "../hack")); + if (SystemUtils.IS_OS_WINDOWS) { + illegal.add("C."); + illegal.add("a:b<|"); + illegal.add("COM1"); + } for (final String single : illegal) { testObject4SingleIllegalPath(single); } @@ -796,21 +803,9 @@ private void testObject4SingleIllegalPath(final String illegal) throws Exception final ITableSession session = EnvFactory.getEnv().getTableSessionConnection()) { statement.execute("create database if not exists db2"); statement.execute("use db2"); - statement.execute(String.format("create table \"%s\" ()", illegal)); - - try { - statement.execute(String.format("alter table \"%s\" add column a object", illegal)); - fail(); - } catch (final SQLException e) { - Assert.assertEquals( - String.format( - "701: When there are object fields, the tableName %s shall not be '.', '..' or contain './', '.\\'", - illegal), - e.getMessage()); - } - // Test auto-create - String testObject = + // Test auto-create table + final String testObject = System.getProperty("user.dir") + File.separator + "target" @@ -819,7 +814,7 @@ private void testObject4SingleIllegalPath(final String illegal) throws Exception + File.separator + "object-example.pt"; - List schemaList = new ArrayList<>(); + final List schemaList = new ArrayList<>(); schemaList.add(new MeasurementSchema("a", TSDataType.STRING)); schemaList.add(new MeasurementSchema("b", TSDataType.STRING)); schemaList.add(new MeasurementSchema("c", TSDataType.INT32)); @@ -843,26 +838,46 @@ private void testObject4SingleIllegalPath(final String illegal) throws Exception tablet.addValue(schemaList.get(2).getMeasurementName(), 0, 0); tablet.addValue(0, 3, true, 0, Files.readAllBytes(Paths.get(testObject))); + final String expectedTableError = + String.format( + "701: When there are object fields, the tableName %s shall not be '.', '..' or contain './', '.\\'." + + (SystemUtils.IS_OS_WINDOWS ? " " + WindowsOSUtils.OS_SEGMENT_ERROR : ""), + illegal.toLowerCase()); + final String expectedObjectError = + String.format( + "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'." + + (SystemUtils.IS_OS_WINDOWS ? " " + WindowsOSUtils.OS_SEGMENT_ERROR : ""), + illegal.toLowerCase()); + try { session.executeNonQueryStatement("use db2"); session.insert(tablet); } catch (final Exception e) { - Assert.assertEquals( - String.format( - "701: When there are object fields, the tableName %s shall not be '.', '..' or contain './', '.\\'", - illegal), - e.getMessage()); + Assert.assertEquals(expectedTableError, e.getMessage()); + } + + statement.execute(String.format("create table \"%s\" ()", illegal)); + + try { + statement.execute(String.format("alter table \"%s\" add column a object", illegal)); + fail(); + } catch (final SQLException e) { + Assert.assertEquals(expectedTableError, e.getMessage()); + } + + // Test auto-create column + try { + session.executeNonQueryStatement("use db2"); + session.insert(tablet); + } catch (final Exception e) { + Assert.assertEquals(expectedTableError, e.getMessage()); } try { statement.execute(String.format("create table test (\"%s\" object)", illegal)); fail(); } catch (final SQLException e) { - Assert.assertEquals( - String.format( - "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'", - illegal), - e.getMessage()); + Assert.assertEquals(expectedObjectError, e.getMessage()); } statement.execute("create table test (a tag, b attribute, c int32, d object)"); @@ -873,11 +888,7 @@ private void testObject4SingleIllegalPath(final String illegal) throws Exception session.executeNonQueryStatement("use db2"); session.insert(tablet); } catch (final Exception e) { - Assert.assertEquals( - String.format( - "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'", - illegal), - e.getMessage()); + Assert.assertEquals(expectedObjectError, e.getMessage()); } // It's OK if you don't write object @@ -891,7 +902,8 @@ private void testObject4SingleIllegalPath(final String illegal) throws Exception } catch (final SQLException e) { Assert.assertEquals( String.format( - "507: When there are object fields, the deviceId [test, %s] shall not be '.', '..' or contain './', '.\\'", + "507: When there are object fields, the deviceId [test, %s] shall not be '.', '..' or contain './', '.\\'." + + (SystemUtils.IS_OS_WINDOWS ? " " + WindowsOSUtils.OS_SEGMENT_ERROR : ""), illegal), e.getMessage()); } @@ -900,11 +912,7 @@ private void testObject4SingleIllegalPath(final String illegal) throws Exception statement.execute(String.format("alter table test add column \"%s\" object", illegal)); fail(); } catch (final SQLException e) { - Assert.assertEquals( - String.format( - "701: When there are object fields, the objectName %s shall not be '.', '..' or contain './', '.\\'", - illegal), - e.getMessage()); + Assert.assertEquals(expectedObjectError, e.getMessage()); } statement.execute("drop database db2"); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java index 0f6c4bdb538e..4b609aa87eb7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java @@ -580,7 +580,7 @@ private Pair parseTable4CreateTableOrView( final TsTableColumnCategory category = columnDefinition.getColumnCategory(); final String columnName = columnDefinition.getName().getValue(); final TSDataType dataType = getDataType(columnDefinition.getType()); - hasObject |= dataType.equals(TSDataType.OBJECT); + hasObject |= dataType == TSDataType.OBJECT; final String comment = columnDefinition.getComment(); if (checkTimeColumnIdempotent(category, columnName, dataType, comment, table) && !hasTimeColumn) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java index daea9a9e9da6..99da3d005f72 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/TableHeaderSchemaValidator.java @@ -20,6 +20,8 @@ package org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher; import org.apache.iotdb.commons.exception.IoTDBException; +import org.apache.iotdb.commons.exception.IoTDBRuntimeException; +import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.schema.table.InsertNodeMeasurementInfo; import org.apache.iotdb.commons.schema.table.TreeViewSchema; import org.apache.iotdb.commons.schema.table.TsTable; @@ -629,6 +631,7 @@ public TsTable toTsTable(InsertNodeMeasurementInfo measurementInfo) { return tsTable; } + boolean hasObject = false; for (int i = 0; i < measurements.length; i++) { if (measurements[i] == null) { continue; @@ -657,9 +660,17 @@ public TsTable toTsTable(InsertNodeMeasurementInfo measurementInfo) { throw new ColumnCreationFailException( "Cannot create column " + columnName + " datatype is not provided"); } - + hasObject |= dataType == TSDataType.OBJECT; tsTable.addColumnSchema(generateColumnSchema(category, columnName, dataType, null, null)); } + if (hasObject) { + try { + tsTable.checkTableNameAndObjectNames4Object(); + } catch (final MetadataException e) { + throw new IoTDBRuntimeException( + e.getMessage(), TSStatusCode.SEMANTIC_ERROR.getStatusCode()); + } + } return tsTable; } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java index 8f5f5f44f605..0a28cae2ea02 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java @@ -30,10 +30,12 @@ import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchemaUtil; import org.apache.iotdb.commons.utils.CommonDateTimeUtils; +import org.apache.iotdb.commons.utils.WindowsOSUtils; import org.apache.iotdb.rpc.TSStatusCode; import com.google.common.collect.ImmutableList; import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.external.commons.lang3.SystemUtils; import org.apache.tsfile.utils.Pair; import org.apache.tsfile.utils.ReadWriteIOUtils; @@ -71,7 +73,7 @@ public class TsTable { public static final String TTL_PROPERTY = "ttl"; public static final Set TABLE_ALLOWED_PROPERTIES = Collections.singleton(TTL_PROPERTY); private static final String OBJECT_STRING_ERROR = - "When there are object fields, the %s %s shall not be '.', '..' or contain './', '.\\'"; + "When there are object fields, the %s %s shall not be '.', '..' or contain './', '.\\'."; protected String tableName; private final Map columnSchemaMap = new LinkedHashMap<>(); @@ -435,15 +437,21 @@ && isInvalid4ObjectType(schema.getColumnName())) { } } - public static boolean isInvalid4ObjectType(final String column) { - return column.equals(".") - || column.equals("..") - || column.contains("./") - || column.contains(".\\"); + public static boolean isInvalid4ObjectType(final String path) { + return path.equals(".") + || path.equals("..") + || path.contains("./") + || path.contains(".\\") + || !WindowsOSUtils.isLegalPathSegment4Windows(path); } public static String getObjectStringError(final String columnType, final String columnName) { - return String.format(OBJECT_STRING_ERROR, columnType, columnName); + return String.format( + SystemUtils.IS_OS_WINDOWS + ? OBJECT_STRING_ERROR + " " + WindowsOSUtils.OS_SEGMENT_ERROR + : OBJECT_STRING_ERROR, + columnType, + columnName); } @Override diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/WindowsOSUtils.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/WindowsOSUtils.java new file mode 100644 index 000000000000..3e8155ed759b --- /dev/null +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/WindowsOSUtils.java @@ -0,0 +1,64 @@ +/* + * 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.iotdb.commons.utils; + +import org.apache.tsfile.external.commons.lang3.SystemUtils; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class WindowsOSUtils { + private static final String ILLEGAL_WINDOWS_CHARS = "\\/:*?\"<>|"; + private static final Set ILLEGAL_WINDOWS_NAMES = + new HashSet<>(Arrays.asList("CON", "PRN", "AUX", "NUL", "COM1-COM9, LPT1-LPT9")); + + static { + for (int i = 0; i < 10; ++i) { + ILLEGAL_WINDOWS_NAMES.add("COM" + i); + ILLEGAL_WINDOWS_NAMES.add("LPT" + i); + } + } + + public static final String OS_SEGMENT_ERROR = + String.format( + "In Windows System, the path shall not contains %s, equals one of %s, or ends with '.' or ' '.", + ILLEGAL_WINDOWS_CHARS, ILLEGAL_WINDOWS_NAMES); + + public static boolean isLegalPathSegment4Windows(final String pathSegment) { + if (!SystemUtils.IS_OS_WINDOWS) { + return true; + } + for (final char illegalChar : ILLEGAL_WINDOWS_CHARS.toCharArray()) { + if (pathSegment.indexOf(illegalChar) != -1) { + return false; + } + } + if (pathSegment.endsWith(".") || pathSegment.endsWith(" ")) { + return false; + } + for (final String illegalName : ILLEGAL_WINDOWS_NAMES) { + if (pathSegment.equalsIgnoreCase(illegalName)) { + return false; + } + } + return true; + } +} diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/WindowsOSUtilsTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/WindowsOSUtilsTest.java new file mode 100644 index 000000000000..bbe847b6f8ec --- /dev/null +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/WindowsOSUtilsTest.java @@ -0,0 +1,41 @@ +/* + * 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.iotdb.commons.utils; + +import org.apache.tsfile.external.commons.lang3.SystemUtils; +import org.junit.Assert; +import org.junit.Test; + +import static org.apache.iotdb.commons.utils.WindowsOSUtils.isLegalPathSegment4Windows; + +public class WindowsOSUtilsTest { + @Test + public void testIllegalDetection() { + if (!SystemUtils.IS_OS_WINDOWS) { + return; + } + Assert.assertTrue(isLegalPathSegment4Windows("abc")); + Assert.assertTrue(isLegalPathSegment4Windows(".A!")); + + Assert.assertFalse(isLegalPathSegment4Windows("C.")); + Assert.assertFalse(isLegalPathSegment4Windows("a:b<|")); + Assert.assertFalse(isLegalPathSegment4Windows("COM1")); + } +}