
io.questdb.cairo.CairoEngine Maven / Gradle / Ivy
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2023 QuestDB
*
* Licensed 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 io.questdb.cairo;
import io.questdb.MessageBus;
import io.questdb.MessageBusImpl;
import io.questdb.Metrics;
import io.questdb.Telemetry;
import io.questdb.cairo.mig.EngineMigration;
import io.questdb.cairo.pool.*;
import io.questdb.cairo.sql.*;
import io.questdb.cairo.vm.api.MemoryMARW;
import io.questdb.cairo.wal.*;
import io.questdb.cairo.wal.seq.TableSequencerAPI;
import io.questdb.cutlass.text.CopyContext;
import io.questdb.griffin.*;
import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.mp.*;
import io.questdb.std.*;
import io.questdb.std.datetime.microtime.MicrosecondClock;
import io.questdb.std.datetime.microtime.Timestamps;
import io.questdb.std.str.Path;
import io.questdb.std.str.StringSink;
import io.questdb.tasks.TelemetryTask;
import io.questdb.tasks.TelemetryWalTask;
import io.questdb.tasks.WalTxnNotificationTask;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.Closeable;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import static io.questdb.cairo.sql.OperationFuture.QUERY_COMPLETE;
import static io.questdb.griffin.CompiledQuery.*;
public class CairoEngine implements Closeable, WriterSource {
public static final String BUSY_READER = "busyReader";
public static final Predicate EMPTY_RESOLVER = (tableName) -> false;
private static final Log LOG = LogFactory.getLog(CairoEngine.class);
private final AtomicLong asyncCommandCorrelationId = new AtomicLong();
private final CairoConfiguration configuration;
private final CopyContext copyContext;
private final EngineMaintenanceJob engineMaintenanceJob;
private final FunctionFactoryCache ffCache;
private final MessageBusImpl messageBus;
private final MetadataPool metadataPool;
private final Metrics metrics;
private final Predicate protectedTableResolver;
private final ReaderPool readerPool;
private final DatabaseSnapshotAgent snapshotAgent;
private final SqlCompilerPool sqlCompilerPool;
private final IDGenerator tableIdGenerator;
private final TableNameRegistry tableNameRegistry;
private final TableSequencerAPI tableSequencerAPI;
private final Telemetry telemetry;
private final Telemetry telemetryWal;
// initial value of unpublishedWalTxnCount is 1 because we want to scan for non-applied WAL transactions on startup
private final AtomicLong unpublishedWalTxnCount = new AtomicLong(1);
private final WalWriterPool walWriterPool;
private final WriterPool writerPool;
private @NotNull DdlListener ddlListener = DefaultDdlListener.INSTANCE;
private @NotNull WalInitializer walInitializer = DefaultWalInitializer.INSTANCE;
private @NotNull WalListener walListener = DefaultWalListener.INSTANCE;
// Kept for embedded API purposes. The second constructor (the one with metrics)
// should be preferred for internal use.
public CairoEngine(CairoConfiguration configuration) {
this(configuration, Metrics.disabled());
}
public CairoEngine(CairoConfiguration configuration, Metrics metrics) {
ffCache = new FunctionFactoryCache(
configuration,
ServiceLoader.load(FunctionFactory.class, FunctionFactory.class.getClassLoader())
);
this.protectedTableResolver = newProtectedTableResolver(configuration);
this.configuration = configuration;
this.copyContext = new CopyContext(configuration);
this.tableSequencerAPI = new TableSequencerAPI(this, configuration);
this.messageBus = new MessageBusImpl(configuration);
this.metrics = metrics;
// Message bus and metrics must be initialized before the pools.
this.writerPool = new WriterPool(configuration, this);
this.readerPool = new ReaderPool(configuration, messageBus);
this.metadataPool = new MetadataPool(configuration, this);
this.walWriterPool = new WalWriterPool(configuration, this);
this.engineMaintenanceJob = new EngineMaintenanceJob(configuration);
this.telemetry = new Telemetry<>(TelemetryTask.TELEMETRY, configuration);
this.telemetryWal = new Telemetry<>(TelemetryWalTask.WAL_TELEMETRY, configuration);
this.tableIdGenerator = new IDGenerator(configuration, TableUtils.TAB_INDEX_FILE_NAME);
this.snapshotAgent = new DatabaseSnapshotAgent(this);
try {
this.tableIdGenerator.open();
} catch (Throwable e) {
close();
throw e;
}
// Recover snapshot, if necessary.
try {
snapshotAgent.recoverSnapshot();
} catch (Throwable e) {
close();
throw e;
}
// Migrate database files.
try {
EngineMigration.migrateEngineTo(this, ColumnType.VERSION, ColumnType.MIGRATION_VERSION, false);
} catch (Throwable e) {
close();
throw e;
}
try {
tableNameRegistry = configuration.isReadOnlyInstance() ?
new TableNameRegistryRO(configuration, protectedTableResolver) : new TableNameRegistryRW(configuration, protectedTableResolver);
tableNameRegistry.reloadTableNameCache();
} catch (Throwable e) {
close();
throw e;
}
this.sqlCompilerPool = new SqlCompilerPool(this);
}
public static void compile(SqlCompiler compiler, CharSequence sql, SqlExecutionContext sqlExecutionContext) throws SqlException {
CompiledQuery cq = compiler.compile(sql, sqlExecutionContext);
switch (cq.getType()) {
default:
try (OperationFuture future = cq.execute(null)) {
future.await();
}
break;
case INSERT:
case INSERT_AS_SELECT:
final InsertOperation insertOperation = cq.getInsertOperation();
if (insertOperation != null) {
// for insert as select the operation is null
try (InsertMethod insertMethod = insertOperation.createMethod(sqlExecutionContext)) {
insertMethod.execute();
insertMethod.commit();
}
}
break;
case DROP:
drop0(null, cq);
break;
case SELECT:
throw SqlException.$(0, "use select()");
}
}
public static void ddl(
SqlCompiler compiler,
CharSequence ddl,
SqlExecutionContext sqlExecutionContext,
@Nullable SCSequence eventSubSeq
) throws SqlException {
CompiledQuery cc = compiler.compile(ddl, sqlExecutionContext);
switch (cc.getType()) {
default:
try (OperationFuture future = cc.execute(eventSubSeq)) {
future.await();
}
break;
case INSERT:
throw SqlException.$(0, "use insert()");
case DROP:
throw SqlException.$(0, "use drop()");
case SELECT:
throw SqlException.$(0, "use select()");
}
}
public static void insert(SqlCompiler compiler, CharSequence insertSql, SqlExecutionContext sqlExecutionContext) throws SqlException {
CompiledQuery cq = compiler.compile(insertSql, sqlExecutionContext);
switch (cq.getType()) {
case INSERT:
case INSERT_AS_SELECT:
final InsertOperation insertOperation = cq.getInsertOperation();
if (insertOperation != null) {
// for insert as select the operation is null
try (InsertMethod insertMethod = insertOperation.createMethod(sqlExecutionContext)) {
insertMethod.execute();
insertMethod.commit();
}
}
break;
case SELECT:
throw SqlException.$(0, "use select()");
case DROP:
throw SqlException.$(0, "use drop()");
default:
throw SqlException.$(0, "use ddl()");
}
}
public static RecordCursorFactory select(SqlCompiler compiler, CharSequence selectSql, SqlExecutionContext sqlExecutionContext) throws SqlException {
return compiler.compile(selectSql, sqlExecutionContext).getRecordCursorFactory();
}
public void applyTableRename(TableToken token, TableToken updatedTableToken) {
tableNameRegistry.rename(token.getTableName(), updatedTableToken.getTableName(), token);
}
@TestOnly
public boolean clear() {
snapshotAgent.clear();
messageBus.reset();
boolean b1 = readerPool.releaseAll();
boolean b2 = writerPool.releaseAll();
boolean b3 = tableSequencerAPI.releaseAll();
boolean b4 = metadataPool.releaseAll();
boolean b5 = walWriterPool.releaseAll();
return b1 & b2 & b3 & b4 & b5;
}
@Override
public void close() {
Misc.free(sqlCompilerPool);
Misc.free(writerPool);
Misc.free(readerPool);
Misc.free(metadataPool);
Misc.free(walWriterPool);
Misc.free(tableIdGenerator);
Misc.free(messageBus);
Misc.free(tableSequencerAPI);
Misc.free(telemetry);
Misc.free(telemetryWal);
Misc.free(tableNameRegistry);
Misc.free(snapshotAgent);
}
@TestOnly
public void closeNameRegistry() {
tableNameRegistry.close();
}
public void compile(CharSequence sql, SqlExecutionContext sqlExecutionContext) throws SqlException {
try (SqlCompiler compiler = getSqlCompiler()) {
compile(compiler, sql, sqlExecutionContext);
}
}
public void completeSnapshot() throws SqlException {
snapshotAgent.completeSnapshot();
}
public @NotNull TableToken createTable(
SecurityContext securityContext,
MemoryMARW mem,
Path path,
boolean ifNotExists,
TableStructure struct,
boolean keepLock
) {
securityContext.authorizeTableCreate();
return createTableInsecure(securityContext, mem, path, ifNotExists, struct, keepLock, false);
}
public @NotNull TableToken createTableInVolume(
SecurityContext securityContext,
MemoryMARW mem,
Path path,
boolean ifNotExists,
TableStructure struct,
boolean keepLock
) {
securityContext.authorizeTableCreate();
return createTableInsecure(securityContext, mem, path, ifNotExists, struct, keepLock, true);
}
public @NotNull TableToken createTableInsecure(
SecurityContext securityContext,
MemoryMARW mem,
Path path,
boolean ifNotExists,
TableStructure struct,
boolean keepLock,
boolean inVolume
) {
assert !struct.isWalEnabled() || PartitionBy.isPartitioned(struct.getPartitionBy()) : "WAL is only supported for partitioned tables";
final CharSequence tableName = struct.getTableName();
validNameOrThrow(tableName);
int tableId = (int) tableIdGenerator.getNextId();
TableToken tableToken = lockTableName(tableName, tableId, struct.isWalEnabled());
if (tableToken == null) {
if (ifNotExists) {
return getTableTokenIfExists(tableName);
}
throw EntryUnavailableException.instance("table exists");
}
try {
String lockedReason = lock(tableToken, "createTable");
if (lockedReason == null) {
boolean tableCreated = false;
try {
if (inVolume) {
createTableInVolumeUnsafe(mem, path, struct, tableToken);
} else {
createTableUnsafe(mem, path, struct, tableToken);
}
if (struct.isWalEnabled()) {
tableSequencerAPI.registerTable(tableToken.getTableId(), struct, tableToken);
}
tableCreated = true;
} finally {
if (!keepLock) {
unlockTableUnsafe(tableToken, null, tableCreated);
LOG.info().$("unlocked [table=`").$(tableToken).$("`]").$();
}
}
tableNameRegistry.registerName(tableToken);
} else {
if (!ifNotExists) {
throw EntryUnavailableException.instance(lockedReason);
}
}
} catch (Throwable th) {
if (struct.isWalEnabled()) {
// tableToken.getLoggingName() === tableName, table cannot be renamed while creation hasn't finished
tableSequencerAPI.dropTable(tableToken, true);
}
throw th;
} finally {
tableNameRegistry.unlockTableName(tableToken);
}
getDdlListener(tableToken).onTableCreated(securityContext, tableToken);
return tableToken;
}
public void ddl(CharSequence ddl, SqlExecutionContext executionContext) throws SqlException {
ddl(ddl, executionContext, null);
}
public void ddl(CharSequence ddl, SqlExecutionContext sqlExecutionContext, @Nullable SCSequence eventSubSeq) throws SqlException {
try (SqlCompiler compiler = getSqlCompiler()) {
ddl(compiler, ddl, sqlExecutionContext, eventSubSeq);
}
}
public void drop(Path path, TableToken tableToken) {
verifyTableToken(tableToken);
if (tableToken.isWal()) {
if (tableNameRegistry.dropTable(tableToken)) {
tableSequencerAPI.dropTable(tableToken, false);
} else {
LOG.info().$("table is already dropped [table=").$(tableToken)
.$(", dirName=").$(tableToken.getDirName()).I$();
}
} else {
CharSequence lockedReason = lock(tableToken, "removeTable");
if (lockedReason == null) {
try {
path.of(configuration.getRoot()).concat(tableToken).$();
if (!configuration.getFilesFacade().unlinkOrRemove(path, LOG)) {
throw CairoException.critical(configuration.getFilesFacade().errno()).put("could not remove table [name=").put(tableToken)
.put(", dirName=").put(tableToken.getDirName()).put(']');
}
} finally {
unlockTableUnsafe(tableToken, null, false);
}
tableNameRegistry.dropTable(tableToken);
return;
}
throw CairoException.nonCritical().put("could not lock '").put(tableToken).put("' [reason='").put(lockedReason).put("']");
}
}
public void drop(CharSequence dropSql, SqlExecutionContext sqlExecutionContext, @Nullable SCSequence eventSubSeq) throws SqlException {
try (SqlCompiler compiler = getSqlCompiler()) {
final CompiledQuery cq = compiler.compile(dropSql, sqlExecutionContext);
switch (cq.getType()) {
case UPDATE:
case DROP:
drop0(eventSubSeq, cq);
break;
case INSERT:
case INSERT_AS_SELECT:
throw SqlException.$(0, "use insert()");
case SELECT:
throw SqlException.$(0, "use select()");
default:
throw SqlException.$(0, "use ddl()");
}
} catch (SqlException | CairoException ex) {
if (!Chars.contains(ex.getFlyweightMessage(), "table does not exist")) {
throw ex;
}
} catch (TableReferenceOutOfDateException e) {
// ignore
}
}
public TableWriter getBackupWriter(TableToken tableToken, CharSequence backupDirName) {
verifyTableToken(tableToken);
// There is no point in pooling/caching these writers since they are only used once, backups are not incremental
return new TableWriter(
configuration,
tableToken,
messageBus,
null,
true,
DefaultLifecycleManager.INSTANCE,
backupDirName,
getDdlListener(tableToken),
Metrics.disabled()
);
}
@TestOnly
public int getBusyReaderCount() {
return readerPool.getBusyCount();
}
@TestOnly
public int getBusyWriterCount() {
return writerPool.getBusyCount();
}
public long getCommandCorrelationId() {
return asyncCommandCorrelationId.incrementAndGet();
}
public CairoConfiguration getConfiguration() {
return configuration;
}
public CopyContext getCopyContext() {
return copyContext;
}
public @NotNull DdlListener getDdlListener(TableToken tableToken) {
return isSysTable(tableToken) ? DefaultDdlListener.INSTANCE : ddlListener;
}
public Job getEngineMaintenanceJob() {
return engineMaintenanceJob;
}
public FunctionFactoryCache getFunctionFactoryCache() {
return ffCache;
}
public MessageBus getMessageBus() {
return messageBus;
}
public TableMetadata getMetadata(TableToken tableToken) {
verifyTableToken(tableToken);
try {
return metadataPool.get(tableToken);
} catch (CairoException e) {
tryRepairTable(tableToken, e);
}
return metadataPool.get(tableToken);
}
public TableRecordMetadata getMetadata(TableToken tableToken, long metadataVersion) {
verifyTableToken(tableToken);
try {
final TableRecordMetadata metadata = metadataPool.get(tableToken);
if (metadataVersion != TableUtils.ANY_TABLE_VERSION && metadata.getMetadataVersion() != metadataVersion) {
final TableReferenceOutOfDateException ex = TableReferenceOutOfDateException.of(
tableToken,
metadata.getTableId(),
metadata.getTableId(),
metadataVersion,
metadata.getMetadataVersion()
);
metadata.close();
throw ex;
}
return metadata;
} catch (CairoException e) {
tryRepairTable(tableToken, e);
}
return metadataPool.get(tableToken);
}
public Metrics getMetrics() {
return metrics;
}
@TestOnly
public PoolListener getPoolListener() {
return this.writerPool.getPoolListener();
}
public Predicate getProtectedTableResolver() {
return protectedTableResolver;
}
public TableReader getReader(CharSequence tableName) {
return getReader(verifyTableNameForRead(tableName));
}
public TableReader getReader(TableToken tableToken) {
verifyTableToken(tableToken);
return readerPool.get(tableToken);
}
public TableReader getReader(TableToken tableToken, long metadataVersion) {
verifyTableToken(tableToken);
final int tableId = tableToken.getTableId();
TableReader reader = readerPool.get(tableToken);
if ((metadataVersion > -1 && reader.getMetadataVersion() != metadataVersion)
|| tableId > -1 && reader.getMetadata().getTableId() != tableId) {
TableReferenceOutOfDateException ex = TableReferenceOutOfDateException.of(
tableToken,
tableId,
reader.getMetadata().getTableId(),
metadataVersion,
reader.getMetadataVersion()
);
reader.close();
throw ex;
}
return reader;
}
public Map> getReaderPoolEntries() {
return readerPool.entries();
}
public Map getWriterPoolEntries() {
return writerPool.entries();
}
public TableReader getReaderWithRepair(TableToken tableToken) {
// todo: untested verification
verifyTableToken(tableToken);
try {
return getReader(tableToken);
} catch (CairoException e) {
// Cannot open reader on existing table is pretty bad.
// In some messed states, for example after _meta file swap failure Reader cannot be opened
// but writer can be. Opening writer fixes the table mess.
tryRepairTable(tableToken, e);
}
try {
return getReader(tableToken);
} catch (CairoException e) {
LOG.critical()
.$("could not open reader [table=").$(tableToken)
.$(", errno=").$(e.getErrno())
.$(", error=").$(e.getMessage()).I$();
throw e;
}
}
public SqlCompiler getSqlCompiler() {
return sqlCompilerPool.get();
}
public SqlCompilerFactory getSqlCompilerFactory() {
return SqlCompilerFactoryImpl.INSTANCE;
}
public IDGenerator getTableIdGenerator() {
return tableIdGenerator;
}
public TableSequencerAPI getTableSequencerAPI() {
return tableSequencerAPI;
}
public int getTableStatus(Path path, TableToken tableToken) {
if (tableToken == TableNameRegistry.LOCKED_TOKEN) {
return TableUtils.TABLE_RESERVED;
}
if (tableToken == null || !tableToken.equals(tableNameRegistry.getTableToken(tableToken.getTableName()))) {
return TableUtils.TABLE_DOES_NOT_EXIST;
}
return TableUtils.exists(configuration.getFilesFacade(), path, configuration.getRoot(), tableToken.getDirName());
}
public int getTableStatus(CharSequence tableName) {
TableToken tableToken = getTableTokenIfExists(tableName);
if (tableToken == null) {
return TableUtils.TABLE_DOES_NOT_EXIST;
}
return getTableStatus(Path.getThreadLocal(configuration.getRoot()), tableToken);
}
public TableToken getTableTokenByDirName(String dirName) {
return tableNameRegistry.getTableTokenByDirName(dirName);
}
public int getTableTokenCount(boolean includeDropped) {
return tableNameRegistry.getTableTokenCount(includeDropped);
}
public TableToken getTableTokenIfExists(CharSequence tableName) {
return tableNameRegistry.getTableToken(tableName);
}
public TableToken getTableTokenIfExists(CharSequence tableName, int lo, int hi) {
StringSink sink = Misc.getThreadLocalBuilder();
sink.put(tableName, lo, hi);
return tableNameRegistry.getTableToken(sink);
}
public void getTableTokens(ObjHashSet bucket, boolean includeDropped) {
tableNameRegistry.getTableTokens(bucket, includeDropped);
}
@Override
public TableWriterAPI getTableWriterAPI(TableToken tableToken, @Nullable String lockReason) {
verifyTableToken(tableToken);
if (!tableToken.isWal()) {
return writerPool.get(tableToken, lockReason);
}
return walWriterPool.get(tableToken);
}
@Override
public TableWriterAPI getTableWriterAPI(CharSequence tableName, String lockReason) {
return getTableWriterAPI(verifyTableNameForRead(tableName), lockReason);
}
public Telemetry getTelemetry() {
return telemetry;
}
public Telemetry getTelemetryWal() {
return telemetryWal;
}
public long getUnpublishedWalTxnCount() {
return unpublishedWalTxnCount.get();
}
public TableToken getUpdatedTableToken(TableToken tableToken) {
return tableNameRegistry.getTokenByDirName(tableToken.getDirName());
}
public @NotNull WalInitializer getWalInitializer() {
return walInitializer;
}
public @NotNull WalListener getWalListener() {
return walListener;
}
// For testing only
@TestOnly
public WalReader getWalReader(
@SuppressWarnings("unused") SecurityContext securityContext,
TableToken tableToken,
CharSequence walName,
int segmentId,
long walRowCount
) {
if (tableToken.isWal()) {
return new WalReader(configuration, tableToken, walName, segmentId, walRowCount);
}
throw CairoException.nonCritical().put("WAL reader is not supported for table ").put(tableToken);
}
public @NotNull WalWriter getWalWriter(TableToken tableToken) {
verifyTableToken(tableToken);
return walWriterPool.get(tableToken);
}
public TableWriter getWriter(TableToken tableToken, String lockReason) {
verifyTableToken(tableToken);
return writerPool.get(tableToken, lockReason);
}
public TableWriter getWriterOrPublishCommand(TableToken tableToken, @NotNull AsyncWriterCommand asyncWriterCommand) {
verifyTableToken(tableToken);
return writerPool.getWriterOrPublishCommand(tableToken, asyncWriterCommand.getCommandName(), asyncWriterCommand);
}
public TableWriter getWriterUnsafe(TableToken tableToken, String lockReason) {
return writerPool.get(tableToken, lockReason);
}
public void initialized() {
}
public void insert(CharSequence insertSql, SqlExecutionContext sqlExecutionContext) throws SqlException {
try (SqlCompiler compiler = getSqlCompiler()) {
insert(compiler, insertSql, sqlExecutionContext);
}
}
public boolean isSysTable(TableToken tableToken) {
return Chars.startsWith(tableToken.getTableName(), configuration.getSystemTableNamePrefix());
}
public boolean isTableDropped(TableToken tableToken) {
return tableNameRegistry.isTableDropped(tableToken);
}
public boolean isWalTable(TableToken tableToken) {
return tableToken.isWal();
}
public void load() {
// Convert tables to WAL/non-WAL, if necessary.
final ObjList convertedTables = TableConverter.convertTables(configuration, tableSequencerAPI, protectedTableResolver);
tableNameRegistry.reloadTableNameCache(convertedTables);
}
public String lock(TableToken tableToken, String lockReason) {
assert null != lockReason;
// busy metadata is same as busy reader from user perspective
String lockedReason = BUSY_READER;
if (metadataPool.lock(tableToken)) {
lockedReason = writerPool.lock(tableToken, lockReason);
if (lockedReason == null) {
// not locked
if (readerPool.lock(tableToken)) {
LOG.info().$("locked [table=`").utf8(tableToken.getDirName()).$("`, thread=").$(Thread.currentThread().getId()).I$();
return null;
}
writerPool.unlock(tableToken);
lockedReason = BUSY_READER;
}
metadataPool.unlock(tableToken);
}
return lockedReason;
}
public boolean lockReaders(TableToken tableToken) {
verifyTableToken(tableToken);
return readerPool.lock(tableToken);
}
public boolean lockReadersByTableToken(TableToken tableToken) {
return readerPool.lock(tableToken);
}
public TableToken lockTableName(CharSequence tableName, boolean isWal) {
validNameOrThrow(tableName);
int tableId = (int) getTableIdGenerator().getNextId();
return lockTableName(tableName, tableId, isWal);
}
@Nullable
public TableToken lockTableName(CharSequence tableName, int tableId, boolean isWal) {
String tableNameStr = Chars.toString(tableName);
final String dirName = TableUtils.getTableDir(configuration.mangleTableDirNames(), tableNameStr, tableId, isWal);
return tableNameRegistry.lockTableName(tableNameStr, dirName, tableId, isWal);
}
@Nullable
public TableToken lockTableName(CharSequence tableName, String dirName, int tableId, boolean isWal) {
String tableNameStr = Chars.toString(tableName);
return tableNameRegistry.lockTableName(tableNameStr, dirName, tableId, isWal);
}
public void notifyDropped(TableToken tableToken) {
tableNameRegistry.dropTable(tableToken);
}
public void notifyWalTxnCommitted(@NotNull TableToken tableToken) {
final Sequence pubSeq = messageBus.getWalTxnNotificationPubSequence();
while (true) {
long cursor = pubSeq.next();
if (cursor > -1L) {
WalTxnNotificationTask task = messageBus.getWalTxnNotificationQueue().get(cursor);
task.of(tableToken);
pubSeq.done(cursor);
return;
} else if (cursor == -1L) {
LOG.info().$("cannot publish WAL notifications, queue is full [current=")
.$(pubSeq.current()).$(", table=").utf8(tableToken.getDirName())
.I$();
// queue overflow, throw away notification and notify a job to rescan all tables
notifyWalTxnRepublisher(tableToken);
return;
}
}
}
public void notifyWalTxnRepublisher(TableToken tableToken) {
tableSequencerAPI.notifyCommitReadable(tableToken, -1);
unpublishedWalTxnCount.incrementAndGet();
}
public void prepareSnapshot(SqlExecutionContext executionContext) throws SqlException {
snapshotAgent.prepareSnapshot(executionContext);
}
public void recoverSnapshot() {
this.snapshotAgent.recoverSnapshot();
}
public void registerTableToken(TableToken tableToken) {
tableNameRegistry.registerName(tableToken);
}
@TestOnly
public boolean releaseAllReaders() {
boolean b1 = metadataPool.releaseAll();
return readerPool.releaseAll() & b1;
}
@TestOnly
public void releaseAllWalWriters() {
walWriterPool.releaseAll();
}
@TestOnly
public void releaseAllWriters() {
writerPool.releaseAll();
}
public boolean releaseInactive() {
boolean useful = writerPool.releaseInactive();
useful |= readerPool.releaseInactive();
useful |= tableSequencerAPI.releaseInactive();
useful |= metadataPool.releaseInactive();
useful |= walWriterPool.releaseInactive();
return useful;
}
@TestOnly
public void releaseInactiveTableSequencers() {
walWriterPool.releaseInactive();
tableSequencerAPI.releaseInactive();
}
public void releaseReadersByTableToken(TableToken tableToken) {
readerPool.unlock(tableToken);
}
@TestOnly
public void reloadTableNames() {
reloadTableNames(null);
}
@TestOnly
public void reloadTableNames(ObjList convertedTables) {
tableNameRegistry.reloadTableNameCache(convertedTables);
}
public void removeTableToken(TableToken tableToken) {
tableNameRegistry.purgeToken(tableToken);
tableSequencerAPI.purgeTxnTracker(tableToken.getDirName());
PoolListener listener = getPoolListener();
if (listener != null) {
listener.onEvent(
PoolListener.SRC_TABLE_REGISTRY,
Thread.currentThread().getId(),
tableToken,
PoolListener.EV_REMOVE_TOKEN,
(short) 0,
(short) 0
);
}
}
public TableToken rename(
SecurityContext securityContext,
Path fromPath,
MemoryMARW memory,
CharSequence fromTableName,
Path toPath,
CharSequence toTableName
) {
validNameOrThrow(fromTableName);
validNameOrThrow(toTableName);
final TableToken fromTableToken = verifyTableName(fromTableName);
if (Chars.equalsIgnoreCaseNc(fromTableName, toTableName)) {
return fromTableToken;
}
securityContext.authorizeTableRename(fromTableToken);
final TableToken toTableToken;
if (fromTableToken != null) {
if (fromTableToken.isWal()) {
String toTableNameStr = Chars.toString(toTableName);
toTableToken = tableNameRegistry.addTableAlias(toTableNameStr, fromTableToken);
if (toTableToken != null) {
boolean renamed = false;
try {
try (WalWriter walWriter = getWalWriter(fromTableToken)) {
long seqTxn = walWriter.renameTable(fromTableName, toTableNameStr);
LOG.info().$("renamed table [from='").utf8(fromTableName)
.$("', to='").utf8(toTableName)
.$("', wal=").$(walWriter.getWalId())
.$("', seqTxn=").$(seqTxn)
.I$();
renamed = true;
}
TableUtils.overwriteTableNameFile(
fromPath.of(configuration.getRoot()).concat(toTableToken),
memory, configuration.getFilesFacade(),
toTableToken.getTableName()
);
} finally {
if (renamed) {
tableNameRegistry.replaceAlias(fromTableToken, toTableToken);
} else {
LOG.info()
.$("failed to rename table [from=").utf8(fromTableName)
.$(", to=").utf8(toTableName)
.I$();
tableNameRegistry.removeAlias(toTableToken);
}
}
} else {
throw CairoException.nonCritical()
.put("cannot rename table, new name is already in use [table=").put(fromTableName)
.put(", toTableName=").put(toTableName)
.put(']');
}
} else {
String lockedReason = lock(fromTableToken, "renameTable");
if (lockedReason == null) {
try {
toTableToken = rename0(fromPath, fromTableToken, toPath, toTableName);
TableUtils.overwriteTableNameFile(
fromPath.of(configuration.getRoot()).concat(toTableToken),
memory,
configuration.getFilesFacade(),
toTableToken.getTableName()
);
} finally {
unlock(securityContext, fromTableToken, null, false);
}
tableNameRegistry.dropTable(fromTableToken);
} else {
LOG.error()
.$("could not lock and rename [from=").utf8(fromTableName)
.$("', to=").utf8(toTableName)
.$("', reason=").$(lockedReason)
.I$();
throw EntryUnavailableException.instance(lockedReason);
}
}
getDdlListener(fromTableToken).onTableRenamed(securityContext, fromTableToken, toTableToken);
return toTableToken;
} else {
LOG.error().$("cannot rename, table does not exist [table=").utf8(fromTableName).I$();
throw CairoException.nonCritical().put("cannot rename, table does not exist [table=").put(fromTableName).put(']');
}
}
@TestOnly
public void resetNameRegistryMemory() {
tableNameRegistry.resetMemory();
}
public RecordCursorFactory select(CharSequence selectSql, SqlExecutionContext sqlExecutionContext) throws SqlException {
try (SqlCompiler compiler = getSqlCompiler()) {
return select(compiler, selectSql, sqlExecutionContext);
}
}
public void setDdlListener(@NotNull DdlListener ddlListener) {
this.ddlListener = ddlListener;
}
@TestOnly
public void setPoolListener(PoolListener poolListener) {
this.metadataPool.setPoolListener(poolListener);
this.writerPool.setPoolListener(poolListener);
this.readerPool.setPoolListener(poolListener);
this.walWriterPool.setPoolListener(poolListener);
}
@TestOnly
public void setReaderListener(ReaderPool.ReaderListener readerListener) {
readerPool.setTableReaderListener(readerListener);
}
@TestOnly
public void setUp() {
}
public void setWalInitializer(@NotNull WalInitializer walInitializer) {
this.walInitializer = walInitializer;
}
public void setWalListener(@NotNull WalListener walListener) {
this.walListener = walListener;
}
public void setWalPurgeJobRunLock(@Nullable SimpleWaitingLock walPurgeJobRunLock) {
this.snapshotAgent.setWalPurgeJobRunLock(walPurgeJobRunLock);
}
public void unlock(
@SuppressWarnings("unused") SecurityContext securityContext,
TableToken tableToken,
@Nullable TableWriter writer,
boolean newTable
) {
verifyTableToken(tableToken);
unlockTableUnsafe(tableToken, writer, newTable);
LOG.info().$("unlocked [table=`").$(tableToken).$("`]").$();
}
public void unlockReaders(TableToken tableToken) {
verifyTableToken(tableToken);
readerPool.unlock(tableToken);
}
public void unlockTableName(TableToken tableToken) {
tableNameRegistry.unlockTableName(tableToken);
}
public TableToken verifyTableName(final CharSequence tableName) {
TableToken tableToken = tableNameRegistry.getTableToken(tableName);
if (tableToken == null) {
throw CairoException.tableDoesNotExist(tableName);
}
if (tableToken == TableNameRegistry.LOCKED_TOKEN) {
throw CairoException.nonCritical().put("table name is reserved [table=").put(tableName).put("]");
}
return tableToken;
}
public TableToken verifyTableName(final CharSequence tableName, int lo, int hi) {
StringSink sink = Misc.getThreadLocalBuilder();
sink.put(tableName, lo, hi);
return verifyTableName(sink);
}
public void verifyTableToken(TableToken tableToken) {
TableToken tt = tableNameRegistry.getTableToken(tableToken.getTableName());
if (tt == null) {
throw CairoException.tableDoesNotExist(tableToken.getTableName());
}
if (!tt.equals(tableToken)) {
throw TableReferenceOutOfDateException.of(tableToken, tableToken.getTableId(), tt.getTableId(), tt.getTableId(), -1);
}
}
private static void drop0(@Nullable SCSequence eventSubSeq, CompiledQuery cq) throws SqlException {
try (OperationFuture fut = cq.execute(eventSubSeq)) {
if (fut.await(30 * Timestamps.SECOND_MILLIS) != QUERY_COMPLETE) {
throw SqlException.$(0, "drop table timeout");
}
}
}
// caller has to acquire the lock before this method is called and release the lock after the call
private void createTableInVolumeUnsafe(MemoryMARW mem, Path path, TableStructure struct, TableToken tableToken) {
if (TableUtils.TABLE_DOES_NOT_EXIST != TableUtils.existsInVolume(configuration.getFilesFacade(), path, tableToken.getDirName())) {
throw CairoException.nonCritical().put("name is reserved [table=").put(tableToken.getTableName()).put(']');
}
// only create the table after it has been registered
TableUtils.createTableInVolume(
configuration.getFilesFacade(),
configuration.getRoot(),
configuration.getMkDirMode(),
mem,
path,
tableToken.getDirName(),
struct,
ColumnType.VERSION,
tableToken.getTableId()
);
}
// caller has to acquire the lock before this method is called and release the lock after the call
private void createTableUnsafe(MemoryMARW mem, Path path, TableStructure struct, TableToken tableToken) {
if (TableUtils.TABLE_DOES_NOT_EXIST != TableUtils.exists(configuration.getFilesFacade(), path, configuration.getRoot(), tableToken.getDirName())) {
throw CairoException.nonCritical().put("name is reserved [table=").put(tableToken.getTableName()).put(']');
}
// only create the table after it has been registered
TableUtils.createTable(
configuration.getFilesFacade(),
configuration.getRoot(),
configuration.getMkDirMode(),
mem,
path,
tableToken.getDirName(),
struct,
ColumnType.VERSION,
tableToken.getTableId()
);
}
private TableToken rename0(Path fromPath, TableToken fromTableToken, Path toPath, CharSequence toTableName) {
// !!! we do not care what is inside the path1 & path2, we will reset them anyway
final FilesFacade ff = configuration.getFilesFacade();
final CharSequence root = configuration.getRoot();
fromPath.of(root).concat(fromTableToken).$();
TableToken toTableToken = lockTableName(toTableName, fromTableToken.getTableId(), false);
if (toTableToken == null) {
LOG.error()
.$("rename target exists [from='").utf8(fromTableToken.getTableName())
.$("', to='").utf8(toTableName)
.I$();
throw CairoException.nonCritical().put("Rename target exists");
}
if (ff.exists(toPath.of(root).concat(toTableToken).$())) {
tableNameRegistry.unlockTableName(toTableToken);
}
try {
if (ff.rename(fromPath, toPath) != Files.FILES_RENAME_OK) {
final int error = ff.errno();
LOG.error()
.$("could not rename [from='").utf8(fromPath)
.$("', to='").utf8(toPath)
.$("', error=").$(error)
.I$();
throw CairoException.critical(error)
.put("could not rename [from='").put(fromPath)
.put("', to='").put(toPath)
.put(']');
}
tableNameRegistry.registerName(toTableToken);
return toTableToken;
} finally {
tableNameRegistry.unlockTableName(toTableToken);
}
}
private void tryRepairTable(TableToken tableToken, RuntimeException rethrow) {
try {
writerPool.get(tableToken, "repair").close();
} catch (EntryUnavailableException e) {
// This is fine, writer is busy. Throw back origin error.
throw rethrow;
} catch (Throwable th) {
LOG.critical()
.$("could not repair before reading [dirName=").utf8(tableToken.getDirName())
.$(" ,error=").$(th.getMessage()).I$();
throw rethrow;
}
}
private void unlockTableUnsafe(TableToken tableToken, TableWriter writer, boolean newTable) {
readerPool.unlock(tableToken);
writerPool.unlock(tableToken, writer, newTable);
metadataPool.unlock(tableToken);
}
private void validNameOrThrow(CharSequence tableName) {
if (!TableUtils.isValidTableName(tableName, configuration.getMaxFileNameLength())) {
throw CairoException.nonCritical()
.put("invalid table name [table=").putAsPrintable(tableName)
.put(']');
}
}
@NotNull
private TableToken verifyTableNameForRead(CharSequence tableName) {
TableToken token = getTableTokenIfExists(tableName);
if (token == null || token == TableNameRegistry.LOCKED_TOKEN) {
throw CairoException.tableDoesNotExist(tableName);
}
return token;
}
protected Predicate newProtectedTableResolver(CairoConfiguration configuration) {
return EMPTY_RESOLVER;
}
private class EngineMaintenanceJob extends SynchronizedJob {
private final long checkInterval;
private final MicrosecondClock clock;
private long last = 0;
public EngineMaintenanceJob(CairoConfiguration configuration) {
this.clock = configuration.getMicrosecondClock();
this.checkInterval = configuration.getIdleCheckInterval() * 1000;
}
@Override
protected boolean runSerially() {
long t = clock.getTicks();
if (last + checkInterval < t) {
last = t;
return releaseInactive();
}
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy