
io.questdb.cairo.CairoEngine Maven / Gradle / Ivy
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 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.cairo.pool.PoolListener;
import io.questdb.cairo.pool.ReaderPool;
import io.questdb.cairo.pool.WriterPool;
import io.questdb.cairo.sql.ReaderOutOfDateException;
import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.mp.SynchronizedJob;
import io.questdb.std.Files;
import io.questdb.std.FilesFacade;
import io.questdb.std.Misc;
import io.questdb.std.Transient;
import io.questdb.std.microtime.MicrosecondClock;
import io.questdb.std.str.Path;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
public class CairoEngine implements Closeable {
private static final Log LOG = LogFactory.getLog(CairoEngine.class);
private final WriterPool writerPool;
private final ReaderPool readerPool;
private final CairoConfiguration configuration;
public CairoEngine(CairoConfiguration configuration) {
this(configuration, null);
}
public CairoEngine(CairoConfiguration configuration, CairoWorkScheduler workScheduler) {
this.configuration = configuration;
this.writerPool = new WriterPool(configuration, workScheduler);
this.readerPool = new ReaderPool(configuration);
if (workScheduler != null) {
workScheduler.addJob(new WriterMaintenanceJob(configuration));
workScheduler.addJob(new ColumnIndexerJob(workScheduler));
}
}
@Override
public void close() {
Misc.free(writerPool);
Misc.free(readerPool);
}
public void creatTable(
CairoSecurityContext securityContext,
AppendMemory mem,
Path path,
TableStructure struct
) {
TableUtils.createTable(
configuration.getFilesFacade(),
mem,
path,
configuration.getRoot(),
struct,
configuration.getMkDirMode()
);
}
public int getBusyReaderCount() {
return readerPool.getBusyCount();
}
public int getBusyWriterCount() {
return writerPool.getBusyCount();
}
public void releaseInactive() {
writerPool.releaseInactive();
readerPool.releaseInactive();
}
public CairoConfiguration getConfiguration() {
return configuration;
}
public PoolListener getPoolListener() {
return this.writerPool.getPoolListener();
}
public void setPoolListener(PoolListener poolListener) {
this.writerPool.setPoolListener(poolListener);
this.readerPool.setPoolListener(poolListener);
}
public TableReader getReader(
CairoSecurityContext securityContext,
CharSequence tableName
) {
return getReader(securityContext, tableName, TableUtils.ANY_TABLE_VERSION);
}
public TableReader getReader(
CairoSecurityContext securityContext,
CharSequence tableName,
long version
) {
TableReader reader = readerPool.get(tableName);
if (version > -1 && reader.getVersion() != version) {
reader.close();
throw ReaderOutOfDateException.INSTANCE;
}
return reader;
}
public int getStatus(
CairoSecurityContext securityContext,
Path path,
CharSequence tableName,
int lo,
int hi
) {
return TableUtils.exists(configuration.getFilesFacade(), path, configuration.getRoot(), tableName, lo, hi);
}
public int getStatus(
CairoSecurityContext securityContext,
Path path,
CharSequence tableName
) {
return getStatus(securityContext, path, tableName, 0, tableName.length());
}
public TableWriter getWriter(
CairoSecurityContext securityContext,
CharSequence tableName
) {
return writerPool.get(tableName);
}
public boolean lock(
CairoSecurityContext securityContext,
CharSequence tableName
) {
if (writerPool.lock(tableName)) {
boolean locked = readerPool.lock(tableName);
if (locked) {
return true;
}
writerPool.unlock(tableName);
}
return false;
}
public boolean releaseAllReaders() {
return readerPool.releaseAll();
}
public boolean lockReaders(CharSequence tableName) {
return readerPool.lock(tableName);
}
public void unlockReaders(CharSequence tableName) {
readerPool.unlock(tableName);
}
public boolean releaseAllWriters() {
return writerPool.releaseAll();
}
public void remove(
CairoSecurityContext securityContext,
Path path,
CharSequence tableName
) {
if (lock(securityContext, tableName)) {
try {
path.of(configuration.getRoot()).concat(tableName).$();
if (!configuration.getFilesFacade().rmdir(path)) {
int error = configuration.getFilesFacade().errno();
LOG.error().$("remove failed [tableName='").utf8(tableName).$("', error=").$(error).$(']').$();
throw CairoException.instance(error).put("Table remove failed");
}
return;
} finally {
unlock(securityContext, tableName, null);
}
}
throw CairoException.instance(configuration.getFilesFacade().errno()).put("Could not lock '").put(tableName).put('\'');
}
public boolean removeDirectory(@Transient Path path, CharSequence dir) {
path.of(configuration.getRoot()).concat(dir);
final FilesFacade ff = configuration.getFilesFacade();
return ff.rmdir(path.put(Files.SEPARATOR).$());
}
public void rename(
CairoSecurityContext securityContext,
Path path,
CharSequence tableName,
Path otherPath,
CharSequence newName
) {
if (lock(securityContext, tableName)) {
try {
rename0(path, tableName, otherPath, newName);
} finally {
unlock(securityContext, tableName, null);
}
} else {
LOG.error().$("cannot lock and rename [from='").$(tableName).$("', to='").$(newName).$("']").$();
throw CairoException.instance(0).put("Cannot lock [table=").put(tableName).put(']');
}
}
public void unlock(
CairoSecurityContext securityContext,
CharSequence tableName,
@Nullable TableWriter writer
) {
readerPool.unlock(tableName);
writerPool.unlock(tableName, writer);
}
private void rename0(Path path, CharSequence tableName, Path otherPath, CharSequence to) {
final FilesFacade ff = configuration.getFilesFacade();
final CharSequence root = configuration.getRoot();
if (TableUtils.exists(ff, path, root, tableName) != TableUtils.TABLE_EXISTS) {
LOG.error().$('\'').utf8(tableName).$("' does not exist. Rename failed.").$();
throw CairoException.instance(0).put("Rename failed. Table '").put(tableName).put("' does not exist");
}
path.of(root).concat(tableName).$();
otherPath.of(root).concat(to).$();
if (ff.exists(otherPath)) {
LOG.error().$("rename target exists [from='").$(tableName).$("', to='").$(otherPath).$("']").$();
throw CairoException.instance(0).put("Rename target exists");
}
if (!ff.rename(path, otherPath)) {
int error = ff.errno();
LOG.error().$("rename failed [from='").$(path).$("', to='").$(otherPath).$("', error=").$(error).$(']').$();
throw CairoException.instance(error).put("Rename failed");
}
}
private class WriterMaintenanceJob extends SynchronizedJob {
private final MicrosecondClock clock;
private final long checkInterval;
private long last = 0;
public WriterMaintenanceJob(CairoConfiguration configuration) {
this.clock = configuration.getMicrosecondClock();
this.checkInterval = configuration.getIdleCheckInterval() * 1000;
}
protected boolean doRun() {
boolean w = writerPool.releaseInactive();
boolean r = readerPool.releaseInactive();
return w || r;
}
@Override
protected boolean runSerially() {
long t = clock.getTicks();
if (last + checkInterval < t) {
last = t;
return doRun();
}
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy