Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ public class TsTable {
"When there are object fields, the %s %s shall not be '.', '..' or contain './', '.\\'.";
protected String tableName;

private final Map<String, TsTableColumnSchema> columnSchemaMap = new LinkedHashMap<>();
private final Map<String, Integer> tagColumnIndexMap = new HashMap<>();
private final Map<String, Integer> idColumnIndexMap = new HashMap<>();
// Copy-on-Write maps for thread-safe access without read locks
private volatile Map<String, TsTableColumnSchema> columnSchemaMap = new LinkedHashMap<>();
private volatile Map<String, Integer> tagColumnIndexMap = new HashMap<>();
private volatile Map<String, Integer> idColumnIndexMap = new HashMap<>();

private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

Expand Down Expand Up @@ -149,23 +150,77 @@ public TsTableColumnSchema getColumnSchema(final String columnName) {
}

/**
* Execute a write operation with optimistic lock support. This method handles the write flag and
* version increment automatically.
* Execute a write operation with Copy-on-Write support. This method creates new copies of the
* maps before modification to ensure thread-safe reads without locks.
*
* @param writeOperation the write operation to execute
* @param writeOperation the write operation to execute on the new map copies
*/
private void executeWrite(Runnable writeOperation) {
private void executeWrite(WriteOperation writeOperation) {
readWriteLock.writeLock().lock();
isNotWrite.set(false);
try {
writeOperation.run();
// Copy-on-Write: create local copies first
Map<String, TsTableColumnSchema> newColumnSchemaMap = new LinkedHashMap<>(columnSchemaMap);
Map<String, Integer> newTagColumnIndexMap = new HashMap<>(tagColumnIndexMap);
Map<String, Integer> newIdColumnIndexMap = new HashMap<>(idColumnIndexMap);

// Execute write operation on local copies
writeOperation.execute(newColumnSchemaMap, newTagColumnIndexMap, newIdColumnIndexMap);

// After write completes, atomically update the class fields
columnSchemaMap = newColumnSchemaMap;
tagColumnIndexMap = newTagColumnIndexMap;
idColumnIndexMap = newIdColumnIndexMap;
Comment on lines +170 to +173
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may still introduce inconsistency

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, may only retain tagColumnIndexMap since id is a deprecated name

} finally {
instanceVersion.incrementAndGet();
isNotWrite.set(true);
readWriteLock.writeLock().unlock();
}
}

/**
* Execute a write operation with a custom columnSchemaMap transformer. This allows transforming
* the map during copy (e.g., for rename operations) in a single pass.
*
* @param columnSchemaMapTransformer transforms columnSchemaMap entries during copy
*/
private void executeWriteWithTransform(ColumnSchemaMapTransformer columnSchemaMapTransformer) {
readWriteLock.writeLock().lock();
isNotWrite.set(false);
try {
// Copy-on-Write with transformation: transform columnSchemaMap in single pass
Map<String, TsTableColumnSchema> newColumnSchemaMap = new LinkedHashMap<>();
for (Map.Entry<String, TsTableColumnSchema> entry : columnSchemaMap.entrySet()) {
columnSchemaMapTransformer.transform(entry.getKey(), entry.getValue(), newColumnSchemaMap);
}
Map<String, Integer> newTagColumnIndexMap = new HashMap<>(tagColumnIndexMap);
Map<String, Integer> newIdColumnIndexMap = new HashMap<>(idColumnIndexMap);

// After write completes, atomically update the class fields
columnSchemaMap = newColumnSchemaMap;
tagColumnIndexMap = newTagColumnIndexMap;
idColumnIndexMap = newIdColumnIndexMap;
} finally {
instanceVersion.incrementAndGet();
isNotWrite.set(true);
readWriteLock.writeLock().unlock();
}
}

@FunctionalInterface
private interface WriteOperation {
void execute(
Map<String, TsTableColumnSchema> columnSchemaMap,
Map<String, Integer> tagColumnIndexMap,
Map<String, Integer> idColumnIndexMap);
}

@FunctionalInterface
private interface ColumnSchemaMapTransformer {
void transform(
String key, TsTableColumnSchema value, Map<String, TsTableColumnSchema> targetMap);
}

public int getTagColumnOrdinal(final String columnName) {
readWriteLock.readLock().lock();
try {
Expand Down Expand Up @@ -201,67 +256,70 @@ public List<TsTableColumnSchema> getTagColumnSchemaList() {

// Currently only supports device view
public void renameTable(final String newName) {
executeWrite(() -> tableName = newName);
executeWrite((colMap, tagMap, idMap) -> tableName = newName);
}

public void addColumnSchema(final TsTableColumnSchema columnSchema) {
executeWrite(
() -> {
columnSchemaMap.put(columnSchema.getColumnName(), columnSchema);
(colMap, tagMap, idMap) -> {
colMap.put(columnSchema.getColumnName(), columnSchema);
if (columnSchema.getColumnCategory().equals(TsTableColumnCategory.TAG)) {
tagNums++;
tagColumnIndexMap.put(columnSchema.getColumnName(), tagNums - 1);
tagMap.put(columnSchema.getColumnName(), tagNums - 1);
} else if (columnSchema.getColumnCategory().equals(TsTableColumnCategory.FIELD)) {
fieldNum++;
}
});
}

public void renameColumnSchema(final String oldName, final String newName) {
executeWrite(
() -> {
// Ensures idempotency
if (columnSchemaMap.containsKey(oldName)) {
final TsTableColumnSchema schema = columnSchemaMap.remove(oldName);
// Transform during copy: rename column while preserving insertion order in single pass
executeWriteWithTransform(
(key, schema, targetMap) -> {
if (key.equals(oldName)) {
// Rename this column while preserving its position
final Map<String, String> oldProps = schema.getProps();
oldProps.computeIfAbsent(TreeViewSchema.ORIGINAL_NAME, k -> schema.getColumnName());

TsTableColumnSchema renamedSchema;
switch (schema.getColumnCategory()) {
case TAG:
columnSchemaMap.put(
newName, new TagColumnSchema(newName, schema.getDataType(), oldProps));
renamedSchema = new TagColumnSchema(newName, schema.getDataType(), oldProps);
break;
case FIELD:
columnSchemaMap.put(
newName,
renamedSchema =
new FieldColumnSchema(
newName,
schema.getDataType(),
((FieldColumnSchema) schema).getEncoding(),
((FieldColumnSchema) schema).getCompressor(),
oldProps));
oldProps);
break;
case ATTRIBUTE:
columnSchemaMap.put(
newName, new AttributeColumnSchema(newName, schema.getDataType(), oldProps));
renamedSchema = new AttributeColumnSchema(newName, schema.getDataType(), oldProps);
break;
case TIME:
default:
// Do nothing
columnSchemaMap.put(oldName, schema);
// Do nothing for TIME column
targetMap.put(key, schema);
return;
}
targetMap.put(newName, renamedSchema);
} else {
targetMap.put(key, schema);
}
});
}

public void removeColumnSchema(final String columnName) {
executeWrite(
() -> {
final TsTableColumnSchema columnSchema = columnSchemaMap.get(columnName);
(colMap, tagMap, idMap) -> {
final TsTableColumnSchema columnSchema = colMap.get(columnName);
if (columnSchema != null
&& columnSchema.getColumnCategory().equals(TsTableColumnCategory.TAG)) {
throw new SchemaExecutionException("Cannot remove an tag column: " + columnName);
} else if (columnSchema != null) {
columnSchemaMap.remove(columnName);
colMap.remove(columnName);
if (columnSchema.getColumnCategory().equals(TsTableColumnCategory.FIELD)) {
fieldNum--;
}
Expand Down Expand Up @@ -354,7 +412,7 @@ public Optional<String> getPropValue(final String propKey) {

public void addProp(final String key, final String value) {
executeWrite(
() -> {
(colMap, tagMap, idMap) -> {
if (props == null) {
props = new HashMap<>();
}
Expand All @@ -364,7 +422,7 @@ public void addProp(final String key, final String value) {

public void removeProp(final String key) {
executeWrite(
() -> {
(colMap, tagMap, idMap) -> {
if (props == null) {
return;
}
Expand Down Expand Up @@ -410,7 +468,7 @@ public static TsTable deserialize(final ByteBuffer buffer) {
}

public void setProps(Map<String, String> props) {
executeWrite(() -> this.props = props);
executeWrite((colMap, tagMap, idMap) -> this.props = props);
}

public void checkTableNameAndObjectNames4Object() throws MetadataException {
Expand Down
Loading