Skip to content

Commit

Permalink
feature(griffin) - support rename column. fixed #223 (#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaugsburger committed Jun 22, 2020
1 parent b2554a5 commit d74a57d
Show file tree
Hide file tree
Showing 8 changed files with 1,258 additions and 4 deletions.
6 changes: 5 additions & 1 deletion core/src/main/java/io/questdb/cairo/TableColumnMetadata.java
Expand Up @@ -25,9 +25,9 @@
package io.questdb.cairo;

public class TableColumnMetadata {
private final String name;
private final int type;
private final boolean symbolTableStatic;
private String name;
private int indexValueBlockCapacity;
private boolean indexed;

Expand Down Expand Up @@ -72,6 +72,10 @@ public boolean isIndexed() {
return indexed;
}

public void setName(String name) {
this.name = name;
}

public void setIndexed(boolean value) {
indexed = value;
}
Expand Down
118 changes: 118 additions & 0 deletions core/src/main/java/io/questdb/cairo/TableWriter.java
Expand Up @@ -705,6 +705,83 @@ public void removeColumn(CharSequence name) {
LOG.info().$("REMOVED column '").utf8(name).$("' from ").$(path).$();
}


public void renameColumn(CharSequence currentName, CharSequence newName) {

checkDistressed();

final int index = getColumnIndex(currentName);
final int type = metadata.getColumnType(index);

LOG.info().$("renaming column '").utf8(currentName).$("' to '").utf8(newName).$("' from ").$(path).$();

commit();

this.metaSwapIndex = renameColumnFromMeta(index, newName);

// close _meta so we can rename it
metaMem.close();

// rename _meta to _meta.prev
renameMetaToMetaPrev(currentName);

// after we moved _meta to _meta.prev
// we have to have _todo to restore _meta should anything go wrong
writeRestoreMetaTodo(currentName);

// rename _meta.swp to _meta
renameSwapMetaToMeta(currentName);

try {
// open _meta file
openMetaFile();

// remove _todo
removeTodoFile();

// rename column files has to be done after _todo is removed
renameColumnFiles(currentName, newName, type);
} catch (CairoException err) {
throwDistressException(err);
}

bumpStructureVersion();

metadata.renameColumn(currentName, newName);

LOG.info().$("RENAMED column '").utf8(currentName).$("' to '").utf8(newName).$("' from ").$(path).$();
}

private void renameColumnFiles(CharSequence columnName, CharSequence newName, int columnType) {
try {
ff.iterateDir(path.$(), (file, type) -> {
nativeLPSZ.of(file);
if (type == Files.DT_DIR && IGNORED_FILES.excludes(nativeLPSZ)) {
path.trimTo(rootLen);
path.concat(nativeLPSZ);
other.trimTo(rootLen);
other.concat(nativeLPSZ);
int plen = path.length();
renameFileOrLog(ff, dFile(path.trimTo(plen), columnName), dFile(other.trimTo(plen), newName));
renameFileOrLog(ff, iFile(path.trimTo(plen), columnName), iFile(other.trimTo(plen), newName));
renameFileOrLog(ff, topFile(path.trimTo(plen), columnName), topFile(other.trimTo(plen), newName));
renameFileOrLog(ff, BitmapIndexUtils.keyFileName(path.trimTo(plen), columnName), BitmapIndexUtils.keyFileName(other.trimTo(plen), newName));
renameFileOrLog(ff, BitmapIndexUtils.valueFileName(path.trimTo(plen), columnName), BitmapIndexUtils.valueFileName(other.trimTo(plen), newName));
}
});

if (columnType == ColumnType.SYMBOL) {
renameFileOrLog(ff, SymbolMapWriter.offsetFileName(path.trimTo(rootLen), columnName), SymbolMapWriter.offsetFileName(other.trimTo(rootLen), newName));
renameFileOrLog(ff, SymbolMapWriter.charFileName(path.trimTo(rootLen), columnName), SymbolMapWriter.charFileName(other.trimTo(rootLen), newName));
renameFileOrLog(ff, BitmapIndexUtils.keyFileName(path.trimTo(rootLen), columnName), BitmapIndexUtils.keyFileName(other.trimTo(rootLen), newName));
renameFileOrLog(ff, BitmapIndexUtils.valueFileName(path.trimTo(rootLen), columnName), BitmapIndexUtils.valueFileName(other.trimTo(rootLen), newName));
}
} finally {
path.trimTo(rootLen);
other.trimTo(rootLen);
}
}

public boolean removePartition(long timestamp) {

if (partitionBy == PartitionBy.NONE || timestamp < timestampFloorMethod.floor(minTimestamp) || timestamp > maxTimestamp) {
Expand Down Expand Up @@ -1013,6 +1090,16 @@ private static void removeFileAndOrLog(FilesFacade ff, LPSZ name) {
}
}

private static void renameFileOrLog(FilesFacade ff, LPSZ name, LPSZ to) {
if (ff.exists(name)) {
if (ff.rename(name, to)) {
LOG.info().$("renamed: ").$(name).$();
} else {
LOG.error().$("cannot rename: ").utf8(name).$(" [errno=").$(ff.errno()).$(']').$();
}
}
}

static void indexAndCountDown(ColumnIndexer indexer, long lo, long hi, SOCountDownLatch latch) {
try {
indexer.refreshSourceAndIndex(lo, hi);
Expand Down Expand Up @@ -1973,6 +2060,37 @@ private int removeColumnFromMeta(int index) {
}
}

private int renameColumnFromMeta(int index, CharSequence newName) {
try {
int metaSwapIndex = openMetaSwapFile(ff, ddlMem, path, rootLen, fileOperationRetryCount);
int timestampIndex = metaMem.getInt(META_OFFSET_TIMESTAMP_INDEX);
ddlMem.putInt(columnCount);
ddlMem.putInt(partitionBy);
ddlMem.putInt(timestampIndex);
ddlMem.putInt(ColumnType.VERSION);
ddlMem.jumpTo(META_OFFSET_COLUMN_TYPES);

for (int i = 0; i < columnCount; i++) {
writeColumnEntry(i);
}

long nameOffset = getColumnNameOffset(columnCount);
for (int i = 0; i < columnCount; i++) {
CharSequence columnName = metaMem.getStr(nameOffset);
nameOffset += VirtualMemory.getStorageLength(columnName);

if (i == index) {
columnName = newName;
}
ddlMem.putStr(columnName);
}

return metaSwapIndex;
} finally {
ddlMem.close();
}
}

private void removeIndexFiles(CharSequence columnName) {
try {
ff.iterateDir(path.$(), (file, type) -> {
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/io/questdb/cairo/TableWriterMetadata.java
Expand Up @@ -102,6 +102,16 @@ void removeColumn(CharSequence name) {
}
}

void renameColumn(CharSequence name, CharSequence newName) {
int index = columnNameIndexMap.keyIndex(name);
int columnIndex = columnNameIndexMap.valueAt(index);
columnNameIndexMap.removeAt(index);
columnNameIndexMap.putAt(columnNameIndexMap.keyIndex(newName), newName, columnIndex);
//
TableColumnMetadata oldColumnMetadata = columnMetadata.get(columnIndex);
oldColumnMetadata.setName(Chars.toString(newName));
}

void setTimestampIndex(int index) {
this.timestampIndex = index;
}
Expand Down
62 changes: 59 additions & 3 deletions core/src/main/java/io/questdb/griffin/SqlCompiler.java
Expand Up @@ -700,6 +700,13 @@ private CompiledQuery alterTable(SqlExecutionContext executionContext) throws Sq
} else {
throw SqlException.$(lexer.lastTokenPosition(), "'column' or 'partition' expected");
}
} else if (SqlKeywords.isRenameKeyword(tok)) {
tok = expectToken(lexer, "'column'");
if (SqlKeywords.isColumnKeyword(tok)) {
alterTableRenameColumn(tableNamePosition, writer);
} else {
throw SqlException.$(lexer.lastTokenPosition(), "'column' expected");
}
} else if (SqlKeywords.isAlterKeyword(tok)) {
tok = expectToken(lexer, "'column'");
if (SqlKeywords.isColumnKeyword(tok)) {
Expand All @@ -724,7 +731,7 @@ private CompiledQuery alterTable(SqlExecutionContext executionContext) throws Sq
}

} else {
throw SqlException.$(lexer.lastTokenPosition(), "'add' or 'drop' expected");
throw SqlException.$(lexer.lastTokenPosition(), "'add' or 'drop' or 'rename' expected");
}
} catch (CairoException e) {
LOG.info().$("failed to alter table: ").$((Sinkable) e).$();
Expand Down Expand Up @@ -898,8 +905,57 @@ private void alterTableDropColumn(int tableNamePosition, TableWriter writer) thr
try {
writer.removeColumn(tok);
} catch (CairoException e) {
LOG.error().$("Cannot drop column '").$(writer.getName()).$('.').$(tok).$("'. Exception: ").$((Sinkable) e).$();
throw SqlException.$(tableNamePosition, "Cannot add column. Try again later.");
LOG.error().$("cannot drop column '").$(writer.getName()).$('.').$(tok).$("'. Exception: ").$((Sinkable) e).$();
throw SqlException.$(tableNamePosition, "cannot drop column. Try again later.");
}

tok = SqlUtil.fetchNext(lexer);

if (tok == null) {
break;
}

if (!Chars.equals(tok, ',')) {
throw SqlException.$(lexer.lastTokenPosition(), "',' expected");
}
} while (true);
}


private void alterTableRenameColumn(int tableNamePosition, TableWriter writer) throws SqlException {
RecordMetadata metadata = writer.getMetadata();

do {
CharSequence tok = expectToken(lexer, "current column name");
if (metadata.getColumnIndexQuiet(tok) == -1) {
throw SqlException.invalidColumn(lexer.lastTokenPosition(), tok);
}
CharSequence existingName = GenericLexer.immutableOf(tok);

tok = expectToken(lexer, "'to' expected");
if (!SqlKeywords.isToKeyboard(tok)) {
throw SqlException.$(lexer.lastTokenPosition(), "'to' expected'");
}

tok = expectToken(lexer, "new column name");
if (Chars.equals(existingName, tok)) {
throw SqlException.$(lexer.lastTokenPosition(), "new column name is identical to existing name");
}

if (metadata.getColumnIndexQuiet(tok) > -1) {
throw SqlException.$(lexer.lastTokenPosition(), " column already exists");
}

if (!SqlKeywords.isValidColumnName(tok)) {
throw SqlException.$(lexer.lastTokenPosition(), " new column name contains invalid characters");
}

CharSequence newName = GenericLexer.immutableOf(tok);
try {
writer.renameColumn(existingName, newName);
} catch (CairoException e) {
LOG.error().$("cannot rename column '").$(writer.getName()).$('.').$(tok).$("'. Exception: ").$((Sinkable) e).$();
throw SqlException.$(tableNamePosition, "cannot rename column. Try again later.");
}

tok = SqlUtil.fetchNext(lexer);
Expand Down
40 changes: 40 additions & 0 deletions core/src/main/java/io/questdb/griffin/SqlKeywords.java
Expand Up @@ -263,6 +263,16 @@ public static boolean isTrueKeyword(CharSequence tok) {
&& (tok.charAt(i) | 32) == 'e';
}

public static boolean isToKeyboard(CharSequence tok) {
if (tok.length() != 2) {
return false;
}

int i = 0;
return (tok.charAt(i++) | 32) == 't'
&& (tok.charAt(i) | 32) == 'o';
}

public static boolean isCacheKeyword(CharSequence tok) {
if (tok.length() != 5) {
return false;
Expand Down Expand Up @@ -691,4 +701,34 @@ public static boolean isColumnsKeyword(CharSequence tok) {
&& (tok.charAt(i) | 32) == 's';
// @formatter:on
}

public static boolean isValidColumnName(CharSequence seq) {
for (int i = 0, l = seq.length(); i < l; i++) {
char c = seq.charAt(i);
switch (c) {
case ' ':
case '?':
case '.':
case ',':
case '\'':
case '\"':
case '\\':
case '/':
case '\0':
case ':':
case ')':
case '(':
case '+':
case '-':
case '*':
case '%':
case '~':
case 0xfeff: // UTF-8 BOM (Byte Order Mark) can appear at the beginning of a character stream
return false;
default:

}
}
return true;
}
}

0 comments on commit d74a57d

Please sign in to comment.