Commit eb289261 authored by Donald H. (Donnie) Pinkston, III's avatar Donald H. (Donnie) Pinkston, III
Browse files

Initial commit of NanoDB Wi-2019

parents
No related merge requests found
Showing with 1851 additions and 0 deletions
+1851 -0
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.expressions.Expression;
import edu.caltech.nanodb.expressions.TupleLiteral;
import edu.caltech.nanodb.queryeval.Planner;
import edu.caltech.nanodb.queryeval.TupleProcessor;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.TableInfo;
import edu.caltech.nanodb.relations.Tuple;
import edu.caltech.nanodb.server.EventDispatcher;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.TableManager;
import edu.caltech.nanodb.storage.TupleFile;
import edu.caltech.nanodb.storage.StorageManager;
/**
* <p>
* This command object represents a top-level <tt>DELETE</tt> command issued
* against the database. <tt>DELETE</tt> commands are pretty simple, having a
* single form: <tt>DELETE FROM ... [WHERE ...]</tt>.
* </p>
* <p>
* Execution of this command is relatively straightforward; the only nuance is
* that we can treat it as a "select for deletion", and possibly perform the
* deletion in an optimized manner (particularly if the <tt>WHERE</tt> clause
* contains subqueries). Internally, we treat it as a <tt>SELECT</tt>, and
* each row returned is deleted from the specified table.
* </p>
*/
public class DeleteCommand extends QueryCommand {
/**
* An implementation of the tuple processor interface used by the
* {@link DeleteCommand} to delete each tuple.
*/
private static class TupleRemover implements TupleProcessor {
/** The table whose tuples will be deleted. */
private TableInfo tableInfo;
private TupleFile tupleFile;
/** The event dispatcher for firing row-delete events. */
private EventDispatcher eventDispatcher;
/**
* Initialize the tuple-remover object with the details it needs to
* delete tuples from the specified table.
*
* @param tableInfo details of the table that will be modified
*/
TupleRemover(EventDispatcher eventDispatcher, TableInfo tableInfo) {
this.tableInfo = tableInfo;
this.tupleFile = tableInfo.getTupleFile();
this.eventDispatcher = eventDispatcher;
}
/** This tuple-processor implementation doesn't care about the schema. */
public void setSchema(Schema schema) {
// Ignore.
}
/** This implementation simply deletes each tuple it is handed. */
public void process(Tuple tuple) {
// Make a copy of this, because once we delete the tuple, we can't
// use the "tuple" variable anymore!
TupleLiteral oldTuple = TupleLiteral.fromTuple(tuple);
eventDispatcher.fireBeforeRowDeleted(tableInfo, tuple);
tupleFile.deleteTuple(tuple);
eventDispatcher.fireAfterRowDeleted(tableInfo, oldTuple);
}
public void finish() {
// Not used
}
}
/** The name of the table that the data will be deleted from. */
private String tableName;
/**
* If a <tt>WHERE</tt> expression is specified, this field will refer to
* the expression to be evaluated.
*/
private Expression whereExpr;
TableInfo tableInfo;
/**
* Constructs a new delete command.
*
* @param tableName the name of the table from which we will be deleting
* tuples
*
* @param whereExpr the predicate governing which rows will be deleted
*/
public DeleteCommand(String tableName, Expression whereExpr) {
super(QueryCommand.Type.DELETE);
if (tableName == null)
throw new NullPointerException("tableName cannot be null");
this.tableName = tableName;
this.whereExpr = whereExpr;
}
/**
* Returns the name of the table that this delete command operates on.
* This should never be {@code null}.
*
* @return the name of the table that this delete command operates on.
*/
public String getTableName() {
return tableName;
}
/**
* Returns the <tt>WHERE</tt> predicate for this delete command, if
* specified, or {@code null} if there is no <tt>WHERE</tt> predicate.
*
* @return the <tt>WHERE</tt> predicate for this delete command, if
* specified, or {@code null} if there is no <tt>WHERE</tt>
* predicate.
*/
public Expression getWhereExpr() {
return whereExpr;
}
@Override
protected void prepareQueryPlan(NanoDBServer server) {
// Open the table and save the TableInfo so that the
// getTupleProcessor() method can use it.
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
tableInfo = tableManager.openTable(tableName);
// Create a plan for executing the SQL query.
Planner planner = server.getQueryPlanner();
plan = planner.makeSimpleSelect(tableName, whereExpr, null);
plan.prepare();
}
@Override
protected TupleProcessor getTupleProcessor(EventDispatcher eventDispatcher) {
return new TupleRemover(eventDispatcher, tableInfo);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("DeleteCommand[table=");
sb.append(tableName);
if (whereExpr != null) {
sb.append(", whereExpr=\"");
sb.append(whereExpr);
sb.append("\"");
}
sb.append(']');
return sb.toString();
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.indexes.IndexManager;
import edu.caltech.nanodb.relations.TableInfo;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.StorageManager;
import edu.caltech.nanodb.storage.TableManager;
/**
* This command-class represents the <tt>DROP INDEX</tt> DDL command.
*/
public class DropIndexCommand extends Command {
/** The name of the index to drop. */
private String indexName;
/** The name of the table that the index is built against. */
private String tableName;
/**
* This flag controls whether the drop-index command will fail if the
* index already doesn't exist when the removal is attempted.
*/
private boolean ifExists;
public DropIndexCommand(String indexName, String tableName,
boolean ifExists) {
super(Type.DDL);
if (tableName == null)
throw new IllegalArgumentException("tableName cannot be null");
this.indexName = indexName;
this.tableName = tableName;
this.ifExists = ifExists;
}
/**
* Get the name of the table containing the index to be dropped.
*
* @return the name of the table containing the index to drop
*/
public String getTableName() {
return tableName;
}
/**
* Get the name of the index to be dropped.
*
* @return the name of the index to drop
*/
public String getIndexName() {
return indexName;
}
/**
* Returns the value of the "if exists" flag; true indicates that it is
* not an error if the index doesn't exist when this command is issued.
*
* @return the value of the "if exists" flag
*/
public boolean getIfExists() {
return ifExists;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
IndexManager indexManager = storageManager.getIndexManager();
// Open the table, then attempt to drop the index. If it works,
// save the table's schema back to the table file.
TableInfo tableInfo = tableManager.openTable(tableName);
indexManager.dropIndex(tableInfo, indexName);
tableManager.saveTableInfo(tableInfo);
out.printf("Dropped index %s on table %s.%n", indexName, tableName);
}
}
package edu.caltech.nanodb.commands;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.TableManager;
import edu.caltech.nanodb.storage.StorageManager;
/** This Command class represents the <tt>DROP TABLE</tt> SQL command. */
public class DropTableCommand extends Command {
/** A logging object for reporting anything interesting that happens. **/
private static Logger logger = LogManager.getLogger(DropTableCommand.class);
/** The name of the table to drop from the database. */
private String tableName;
/**
* This flag controls whether the drop-table command will fail if the
* table already doesn't exist when the removal is attempted.
*/
private boolean ifExists;
/**
* Construct a drop-table command for the named table.
*
* @param tableName the name of the table to drop.
* @param ifExists a flag controlling whether the command should complain if
* the table already doesn't exist when the removal is attempted.
*/
public DropTableCommand(String tableName, boolean ifExists) {
super(Command.Type.DDL);
this.tableName = tableName;
this.ifExists = ifExists;
}
/**
* Get the name of the table to be dropped.
*
* @return the name of the table to drop
*/
public String getTableName() {
return tableName;
}
/**
* Returns the value of the "if exists" flag; true indicates that it is
* not an error if the table doesn't exist when this command is issued.
*
* @return the value of the "if exists" flag
*/
public boolean getIfExists() {
return ifExists;
}
/**
* This method executes the <tt>DROP TABLE</tt> command by calling the
* {@link TableManager#dropTable} method with the specified table name.
*
* @throws ExecutionException if the table doesn't actually exist, or if
* the table cannot be deleted for some reason.
*/
@Override
public void execute(NanoDBServer server) throws ExecutionException {
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
// See if the table already doesn't exist.
if (ifExists && !tableManager.tableExists(tableName)) {
// Table already doesn't exist! Skip the operation.
out.printf("Table %s already doesn't exist; skipping drop-table.%n",
tableName);
return;
}
tableManager.dropTable(tableName);
}
@Override
public String toString() {
return "DropTable[" + tableName + "]";
}
}
package edu.caltech.nanodb.commands;
import java.io.PrintStream;
import edu.caltech.nanodb.plannodes.PlanNode;
import edu.caltech.nanodb.queryeval.EvalStats;
import edu.caltech.nanodb.queryeval.QueryEvaluator;
import edu.caltech.nanodb.queryeval.TupleProcessor;
import edu.caltech.nanodb.relations.ColumnInfo;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.Tuple;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* An abstract base-class that holds the common implementation of the various
* kinds of dump commands.
*/
public abstract class DumpCommand extends Command {
/** A tuple processor implementation used to dump each tuple. */
protected static class TupleExporter implements TupleProcessor {
private PrintStream dumpOut;
/**
* Initialize the tuple-exporter object with the details it needs to
* print out tuples from the specified table.
*/
public TupleExporter(PrintStream dumpOut) {
this.dumpOut = dumpOut;
}
/** The exporter can output the schema to the dump file. */
public void setSchema(Schema schema) {
dumpOut.print("{");
boolean first = true;
for (ColumnInfo colInfo : schema) {
if (first)
first = false;
else
dumpOut.print(",");
String colName = colInfo.getName();
String tblName = colInfo.getTableName();
// TODO: To only print out table-names when the column-name
// is ambiguous by itself, uncomment the first part and
// then comment out the next part.
// Only print out the table name if there are multiple columns
// with this column name.
// if (schema.numColumnsWithName(colName) > 1 && tblName != null)
// out.print(tblName + '.');
// If table name is specified, always print it out.
if (tblName != null)
dumpOut.print(tblName + '.');
dumpOut.print(colName);
dumpOut.print(":");
dumpOut.print(colInfo.getType());
}
dumpOut.println("}");
}
/** This implementation simply prints out each tuple it is handed. */
public void process(Tuple tuple) {
dumpOut.print("[");
boolean first = true;
for (int i = 0; i < tuple.getColumnCount(); i++) {
if (first)
first = false;
else
dumpOut.print(", ");
Object val = tuple.getColumnValue(i);
if (val instanceof String)
dumpOut.printf("\"%s\"", val);
else
dumpOut.print(val);
}
dumpOut.println("]");
}
public void finish() {
// Not used
}
}
/** The path and filename to dump the table data to, if desired. */
protected String fileName;
/** The data format to use when dumping the table data. */
protected String format;
protected DumpCommand(String fileName, String format) {
super(Command.Type.UTILITY);
this.fileName = fileName;
this.format = format;
}
public String getFilename() {
return fileName;
}
public String getFormat() {
return format;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
try {
// Figure out where the dumped data should go.
PrintStream dumpOut = out;
if (fileName != null)
dumpOut = new PrintStream(fileName);
// Dump the table.
PlanNode dumpPlan = prepareDumpPlan(server);
TupleExporter exporter = new TupleExporter(dumpOut);
EvalStats stats = QueryEvaluator.executePlan(dumpPlan, exporter);
if (fileName != null)
dumpOut.close();
// Print out the evaluation statistics.
out.printf("Dumped %d rows in %f sec.%n",
stats.getRowsProduced(), stats.getElapsedTimeSecs());
}
catch (ExecutionException e) {
throw e;
}
catch (Exception e) {
throw new ExecutionException(e);
}
}
protected abstract PlanNode prepareDumpPlan(NanoDBServer server);
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.plannodes.PlanNode;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* <p>
* This command object represents a <tt>DUMP INDEX</tt> command issued against
* the database. <tt>DUMP INDEX</tt> commands are pretty simple, having a
* single form: <tt>DUMP INDEX ... ON TABLE ... [TO FILE ...] [FORMAT ...]</tt>.
* </p>
*/
public class DumpIndexCommand extends DumpCommand {
/** The name of the index to dump. */
private String indexName;
/** The name of the table containing the index to dump. */
private String tableName;
public DumpIndexCommand(String indexName, String tableName,
String fileName, String format) {
super(fileName, format);
if (indexName == null)
throw new IllegalArgumentException("indexName cannot be null");
if (tableName == null)
throw new IllegalArgumentException("tableName cannot be null");
this.indexName = indexName;
this.tableName = tableName;
}
/**
* Get the name of the table containing the index to be dumped.
*
* @return the name of the table containing the index to dump
*/
public String getTableName() {
return tableName;
}
/**
* Get the name of the index to be dumped.
*
* @return the name of the index to dump
*/
public String getIndexName() {
return indexName;
}
@Override
protected PlanNode prepareDumpPlan(NanoDBServer server) {
// TODO: Index scan!
return null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("DumpTableCommand[table=");
sb.append(tableName);
if (fileName != null) {
sb.append(", filename=\"");
sb.append(fileName);
sb.append("\"");
}
if (format != null) {
sb.append(", format=");
sb.append(format);
}
sb.append(']');
return sb.toString();
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.plannodes.PlanNode;
import edu.caltech.nanodb.queryeval.Planner;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* <p>
* This command object represents a <tt>DUMP TABLE</tt> command issued against
* the database. <tt>DUMP TABLE</tt> commands are pretty simple, having a
* single form: <tt>DUMP TABLE ... [TO FILE ...] [FORMAT ...]</tt>.
* </p>
* <p>
* This command is effectively identical to <tt>SELECT * FROM tbl;</tt> with
* the results being output to console or a data file for analysis. The
* command is particularly helpful when the planner only implements
* {@link Planner#makeSimpleSelect}; no planning is needed for this command
* to work.
* </p>
*/
public class DumpTableCommand extends DumpCommand {
/** The name of the table to dump. */
private String tableName;
/**
* Constructs a new dump-table command.
*
* @param tableName the name of the table to dump
*
* @param fileName the path and file to dump the data to. The console
* will be used if this is @code{null}.
*
* @param format the format to dump the data in
*
* @throws IllegalArgumentException if tableName is null.
*/
public DumpTableCommand(String tableName, String fileName, String format) {
super(fileName, format);
if (tableName == null)
throw new IllegalArgumentException("tableName cannot be null");
this.tableName = tableName;
}
/**
* Get the name of the table to be dumped.
*
* @return the name of the table to dump
*/
public String getTableName() {
return tableName;
}
@Override
protected PlanNode prepareDumpPlan(NanoDBServer server) {
// Create a simple plan for scanning the table.
Planner planner = server.getQueryPlanner();
PlanNode plan = planner.makeSimpleSelect(tableName, null, null);
plan.prepare();
return plan;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("DumpTableCommand[table=");
sb.append(tableName);
if (fileName != null) {
sb.append(", filename=\"");
sb.append(fileName);
sb.append("\"");
}
if (format != null) {
sb.append(", format=");
sb.append(format);
}
sb.append(']');
return sb.toString();
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBException;
/**
* This exception is thrown when a fatal error occurs during command
* execution.
*/
public class ExecutionException extends NanoDBException {
public ExecutionException() {
super();
}
public ExecutionException(String msg) {
super(msg);
}
public ExecutionException(Throwable cause) {
super(cause);
}
public ExecutionException(String msg, Throwable cause) {
super(msg, cause);
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.StorageManager;
/**
* This Command class represents the <tt>EXIT</tt> or <tt>QUIT</tt> SQL
* commands. These commands aren't standard SQL of course, but are the
* way that we tell the database to stop.
*/
public class ExitCommand extends Command {
/** Construct an exit command. */
public ExitCommand() {
super(Command.Type.UTILITY);
}
/**
* This method really doesn't do anything, and it isn't intended to be
* called. The server looks for this command when commands are being
* executed, and handles it separately.
*
* @review (Donnie) We could actually have this command operate on the
* server now that we pass it in... Need to think about this.
*/
@Override
public void execute(NanoDBServer server) throws ExecutionException {
// Do nothing.
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* This Command class represents the <tt>EXPLAIN</tt> SQL command, which prints
* out details of how SQL DML statements will be evaluated.
*/
public class ExplainCommand extends Command {
/** The command to explain! */
private QueryCommand cmdToExplain;
/**
* Construct an explain command.
*
* @param cmdToExplain the command that should be explained.
*/
public ExplainCommand(QueryCommand cmdToExplain) {
super(Command.Type.UTILITY);
this.cmdToExplain = cmdToExplain;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
cmdToExplain.setExplain(true);
cmdToExplain.execute(server);
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* This command flushes all unwritten data from the buffer manager to disk.
* A sync is not performed.
*/
public class FlushCommand extends Command {
/** Construct a new <tt>FLUSH</tt> command. */
public FlushCommand() {
super(Command.Type.UTILITY);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
out.println("Flushing all unwritten data to disk.");
server.getStorageManager().flushAllData();
}
/**
* Prints a simple representation of the flush command.
*
* @return a string representing this flush command
*/
@Override
public String toString() {
return "Flush";
}
}
package edu.caltech.nanodb.commands;
import java.util.Collections;
import java.util.List;
import edu.caltech.nanodb.expressions.Expression;
import edu.caltech.nanodb.expressions.ExpressionException;
import edu.caltech.nanodb.expressions.TupleLiteral;
import edu.caltech.nanodb.queryast.SelectClause;
import edu.caltech.nanodb.queryeval.Planner;
import edu.caltech.nanodb.queryeval.TupleProcessor;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.TableInfo;
import edu.caltech.nanodb.relations.Tuple;
import edu.caltech.nanodb.relations.TupleUtils;
import edu.caltech.nanodb.server.EventDispatcher;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.TableManager;
import edu.caltech.nanodb.storage.TupleFile;
import edu.caltech.nanodb.storage.StorageManager;
/**
* This command object represents a top-level <tt>INSERT</tt> command issued
* against the database. <tt>INSERT</tt> commands have two forms. The first
* form is <tt>INSERT ... VALUES</tt>, in which case a literal tuple-value is
* specified and stored. The second form is <tt>INSERT</tt> ... <tt>SELECT</tt>,
* in which case a select-clause object is evaluated, and its results are stored
* into the specified table.
*
* @see edu.caltech.nanodb.expressions.TupleLiteral
* @see SelectClause
*/
public class InsertCommand extends QueryCommand {
/**
* An implementation of the tuple processor interface used by the
* {@link InsertCommand} to insert tuples into a table, when the command is
* of the form <tt>INSERT</tt> ... <tt>SELECT</tt>.
*/
private static class TupleInserter implements TupleProcessor {
/** The table into which the new tuples will be inserted. */
private TableInfo tableInfo;
private TupleFile tupleFile;
/**
* The event-dispatcher for reporting insert events to other
* components.
*/
EventDispatcher eventDispatcher;
/**
* Initialize the tuple-inserter object with the details it needs to
* insert tuples into the specified table.
*
* @param tableInfo details of the table that will be modified
*/
TupleInserter(EventDispatcher eventDispatcher, TableInfo tableInfo) {
this.tableInfo = tableInfo;
this.tupleFile = tableInfo.getTupleFile();
this.eventDispatcher = eventDispatcher;
}
/**
* This implementation ignores the schema of the results, since we
* just don't care.
*/
public void setSchema(Schema schema) {
// Ignore.
}
/** This implementation simply inserts each tuple it is handed. */
public void process(Tuple tuple) {
Tuple coerced =
TupleUtils.coerceToSchema(tuple, tableInfo.getSchema());
eventDispatcher.fireBeforeRowInserted(tableInfo, coerced);
Tuple newTuple = tupleFile.addTuple(coerced);
eventDispatcher.fireAfterRowInserted(tableInfo, newTuple);
newTuple.unpin();
}
public void finish() {
// Not used
}
}
/** The name of the table that the data will be inserted into. */
private String tableName;
/**
* An optional list of column-names that can be specified in the INSERT
* command. If the INSERT command didn't specify a list of column-names
* then this will be <code>null</code>.
*/
private List<String> colNames;
/**
* When the insert command is of the form <code>INSERT ... VALUES</code>,
* the literal values are stored in this variable. Otherwise this will be
* set to <code>null</code>.
*/
private List<Expression> values;
/**
* When the insert command is of the form <code>INSERT ... SELECT</code>,
* the details of the select-clause are stored in this variable.
* Otherwise this will be set to <code>null</code>.
*/
private SelectClause selClause;
/**
* The table that the data will be inserted into, once it has been opened.
*/
private TableInfo tableInfo;
/**
* Constructs a new insert command for <tt>INSERT</tt> ... <tt>VALUES</tt>
* statements.
*/
public InsertCommand(String tableName, List<String> colNames,
List<Expression> values) {
super(QueryCommand.Type.INSERT);
if (tableName == null)
throw new NullPointerException("tableName cannot be null");
if (values == null)
throw new NullPointerException("values cannot be null");
this.tableName = tableName;
this.colNames = colNames;
this.values = values;
}
/**
* Constructs a new insert command for <tt>INSERT</tt> ... <tt>SELECT</tt>
* statements.
*/
public InsertCommand(String tableName, List<String> colNames,
SelectClause selClause) {
super(QueryCommand.Type.INSERT);
if (tableName == null)
throw new NullPointerException("tableName cannot be null");
if (selClause == null)
throw new NullPointerException("selClause cannot be null");
this.tableName = tableName;
this.colNames = colNames;
this.selClause = selClause;
}
public String getTableName() {
return tableName;
}
public List<String> getColNames() {
if (colNames == null)
return null;
return Collections.unmodifiableList(colNames);
}
public List<Expression> getValues() {
if (values == null)
return null;
return Collections.unmodifiableList(values);
}
public SelectClause getSelectClause() {
return selClause;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
if (values != null) {
// Inserting a single row.
if (!explain)
insertSingleRow(server);
else
out.println("Nothing to explain about INSERT ... VALUES");
}
else {
// Inserting the results of a SELECT query.
super.execute(server);
}
}
/** This method is used when inserting only a single row of data. */
private void insertSingleRow(NanoDBServer server)
throws ExecutionException {
// Try to open the table with the specified name.
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
tableInfo = tableManager.openTable(tableName);
if (tableInfo == null)
throw new ExecutionException("No table named " + tableName);
// Build up a tuple-literal from the values we have.
TupleLiteral tuple = new TupleLiteral();
for (Expression expr : values) {
if (expr.hasSymbols()) {
throw new ExecutionException(
"INSERT values cannot contain symbols!");
}
try {
tuple.addValue(expr.evaluate());
}
catch (ExpressionException e) {
// This should be rare, but is still possible -- users
// can type anything...
throw new ExecutionException("Couldn't evaluate an INSERT value.", e);
}
}
EventDispatcher eventDispatcher = server.getEventDispatcher();
TupleFile tupleFile = tableInfo.getTupleFile();
Tuple coerced =
TupleUtils.coerceToSchema(tuple, tableInfo.getSchema());
eventDispatcher.fireBeforeRowInserted(tableInfo, coerced);
Tuple newTuple = tupleFile.addTuple(coerced);
eventDispatcher.fireAfterRowInserted(tableInfo, newTuple);
newTuple.unpin();
}
@Override
protected void prepareQueryPlan(NanoDBServer server) {
// Open the table and save the TableInfo so that the
// getTupleProcessor() method can use it.
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
tableInfo = tableManager.openTable(tableName);
//Schema resultSchema = selClause.computeSchema();
// Create a plan for executing the SQL query.
Planner planner = server.getQueryPlanner();
plan = planner.makePlan(selClause, null);
}
@Override
protected TupleProcessor getTupleProcessor(EventDispatcher eventDispatcher) {
return new TupleInserter(eventDispatcher, tableInfo);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("InsertCommand[");
if (colNames != null) {
sb.append("cols=(");
boolean first = true;
for (String colName : colNames) {
if (first)
first = false;
else
sb.append(',');
sb.append(colName);
}
sb.append("), ");
}
if (values != null) {
sb.append("values=(");
boolean first = true;
for (Expression e : values) {
if (first)
first = false;
else
sb.append(',');
sb.append(e);
}
sb.append(')');
}
else {
sb.append("select=");
sb.append(selClause);
}
sb.append(']');
return sb.toString();
}
}
package edu.caltech.nanodb.commands;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* This Command class represents the <tt>OPTIMIZE</tt> SQL command, which
* optimizes a table's representation (along with any indexes) to improve access
* performance and space utilization. This is not a standard SQL command.
*/
public class OptimizeCommand extends Command {
/**
* Table names are kept in a set so that we don't need to worry about a
* particular table being specified multiple times.
*/
private LinkedHashSet<String> tableNames;
/**
* Construct a new <tt>OPTIMIZE</tt> command with an empty table list.
* Tables can be added to the internal list using the {@link #addTable}
* method.
*/
public OptimizeCommand() {
super(Command.Type.UTILITY);
tableNames = new LinkedHashSet<>();
}
/**
* Construct a new <tt>OPTIMIZE</tt> command to optimize the specified
* table.
*
* @param tableName the name of the table to optimize.
*/
public OptimizeCommand(String tableName) {
this();
addTable(tableName);
}
/**
* Add a table to the list of tables to optimize.
*
* @param tableName the name of the table to optimize.
*/
public void addTable(String tableName) {
if (tableName == null)
throw new NullPointerException("tableName cannot be null");
tableNames.add(tableName);
}
/**
* Returns the set of tables to optimize in an unmodifiable set.
*
* @return the set of tables to optimize in an unmodifiable set.
*/
public Set<String> getTableNames() {
return Collections.unmodifiableSet(tableNames);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
throw new ExecutionException("Not yet implemented!");
}
/**
* Prints a simple representation of the optimize command, including the
* names of the tables to be optimized.
*
* @return a string representing this optimize command
*/
@Override
public String toString() {
return "Optimize[" + tableNames + "]";
}
}
package edu.caltech.nanodb.commands;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.plannodes.PlanNode;
import edu.caltech.nanodb.queryeval.EvalStats;
import edu.caltech.nanodb.queryeval.PlanCost;
import edu.caltech.nanodb.queryeval.QueryEvaluator;
import edu.caltech.nanodb.queryeval.TupleProcessor;
import edu.caltech.nanodb.server.EventDispatcher;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.StorageManager;
/**
* This class represents all SQL query commands, including <tt>SELECT</tt>,
* <tt>INSERT</tt>, <tt>UPDATE</tt>, and <tt>DELETE</tt>. The main difference
* between these commands is simply what happens with the tuples that are
* retrieved from the database.
*/
public abstract class QueryCommand extends Command {
/** Typesafe enumeration of query-command types. */
public enum Type {
/** A SELECT query, which simply retrieves rows of data. */
SELECT,
/** An INSERT query, which adds new rows of data to a table. */
INSERT,
/**
* An UPDATE query, which retrieves and then modifies rows of data in
* a table.
*/
UPDATE,
/**
* A DELETE query, which retrieves and then deletes rows of data in
* a table.
*/
DELETE
}
/** A logging object for reporting anything interesting that happens. */
private static Logger logger = LogManager.getLogger(QueryCommand.class);
/** The type of this query command, from the {@link Type} enum. */
private QueryCommand.Type queryType;
protected PlanNode plan;
/**
* If this flag is true then the command is to be explained only. Otherwise
* the command is to be executed normally.
*/
protected boolean explain = false;
/**
* Initializes a new query-command object.
*
* @param queryType the kind of query command that is being executed
*/
protected QueryCommand(QueryCommand.Type queryType) {
super(Command.Type.DML);
this.queryType = queryType;
}
public void setExplain(boolean f) {
explain = f;
}
@Override
public void execute(NanoDBServer server) {
prepareQueryPlan(server);
if (!explain) {
// Debug: print out the plan and its costing details.
logger.debug("Generated execution plan:\n" +
PlanNode.printNodeTreeToString(plan, true));
PlanCost cost = plan.getCost();
if (cost != null) {
logger.debug(String.format(
"Estimated %f tuples with average size %f bytes",
cost.numTuples, cost.tupleSize));
logger.debug("Estimated number of block IOs: " +
cost.numBlockIOs);
logger.debug("Estimated CPU cost: " + cost.cpuCost);
}
// Execute the query plan, then print out the evaluation stats.
TupleProcessor processor =
getTupleProcessor(server.getEventDispatcher());
EvalStats stats = QueryEvaluator.executePlan(plan, processor);
// Print out the evaluation statistics.
out.printf("%s took %f sec to evaluate.%n", queryType,
stats.getElapsedTimeSecs());
String desc;
switch (queryType) {
case SELECT:
desc = "Selected ";
break;
case INSERT:
desc = "Inserted ";
break;
case UPDATE:
desc = "Updated ";
break;
case DELETE:
desc = "Deleted ";
break;
default:
desc = "(UNKNOWN) ";
}
out.println(desc + stats.getRowsProduced() + " rows.");
}
else {
out.println("Explain Plan:");
plan.printNodeTree(out, true, " ");
out.println();
PlanCost cost = plan.getCost();
if (cost != null) {
out.printf("Estimated %f tuples with average size %f%n",
cost.numTuples, cost.tupleSize);
out.println("Estimated number of block IOs: " +
cost.numBlockIOs);
logger.debug("Estimated CPU cost: " + cost.cpuCost);
}
else {
out.println("Plan cost is not available.");
}
}
}
/**
* Prepares an execution plan for generating the tuples that this query
* command will operate on. Since the specific plan to generate depends
* on the operation being performed, this is an abstract method to be
* implemented by subclasses. For example, <tt>SELECT</tt>s support very
* sophisticated operations and thus require complex plans, but a
* <tt>DELETE</tt> operation simply requires a scan over a tuple file,
* perhaps with a predicate applied.
*
* @param server the server to use for planning, fetching table schemas,
* statistics, and other details relevant for planning
*/
protected abstract void prepareQueryPlan(NanoDBServer server);
/**
* Creates a tuple-processor responsible for dealing with the tuples that
* are generated by the query command. Depending on the operation being
* performed, different tuple-processors are appropriate. For example,
* the <tt>SELECT</tt> processor sends the tuples to the console or to a
* remote client; the <tt>DELETE</tt> processor deletes the tuples from
* the referenced table.
*
* @param eventDispatcher used for notifying other components in the
* database when rows are inserted/updated/deleted
*
* @return the tuple processor to use in processing tuples generated by
* the query
*/
protected abstract TupleProcessor getTupleProcessor(
EventDispatcher eventDispatcher);
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.StorageManager;
import edu.caltech.nanodb.transactions.TransactionException;
/**
* This class represents a command that rolls back a transaction, such as
* <tt>ROLLBACK</tt> or <tt>ROLLBACK WORK</tt>.
*/
public class RollbackTransactionCommand extends Command {
public RollbackTransactionCommand() {
super(Type.UTILITY);
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
// Roll back the transaction.
try {
StorageManager storageManager = server.getStorageManager();
storageManager.getTransactionManager().rollbackTransaction();
}
catch (TransactionException e) {
throw new ExecutionException(e);
}
}
}
package edu.caltech.nanodb.commands;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.server.SessionState;
import edu.caltech.nanodb.queryast.SelectClause;
import edu.caltech.nanodb.queryeval.Planner;
import edu.caltech.nanodb.queryeval.PrettyTuplePrinter;
import edu.caltech.nanodb.queryeval.TupleProcessor;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.server.EventDispatcher;
import edu.caltech.nanodb.storage.TableManager;
import edu.caltech.nanodb.storage.StorageManager;
/**
* This command object represents a top-level <tt>SELECT</tt> command issued
* against the database. The query itself is represented by the
* {@link SelectClause} class, particularly because a <tt>SELECT</tt> statement
* can itself contain other <tt>SELECT</tt> statements.
*
* @see SelectClause
*/
public class SelectCommand extends QueryCommand {
/** A logging object for reporting anything interesting that happens. */
private static Logger logger = LogManager.getLogger(SelectCommand.class);
/**
* This object contains all the details of the top-level select clause,
* including any subqueries, that is going to be evaluated.
*/
private SelectClause selClause;
private TupleProcessor tupleProcessor;
public SelectCommand(SelectClause selClause) {
super(QueryCommand.Type.SELECT);
if (selClause == null)
throw new NullPointerException("selClause cannot be null");
this.selClause = selClause;
}
/**
* Returns the root select-clause for this select command.
*
* @return the root select-clause for this select command
*/
public SelectClause getSelectClause() {
return selClause;
}
/**
* Prepares the <tt>SELECT</tt> statement for evaluation by analyzing the
* schema details of the statement, and then preparing an execution plan
* for the statement.
*/
@Override
protected void prepareQueryPlan(NanoDBServer server) {
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
Schema resultSchema = selClause.computeSchema(tableManager, null);
logger.debug("Prepared SelectClause:\n" + selClause);
logger.debug("Result schema: " + resultSchema);
// Create a plan for executing the SQL query.
Planner planner = server.getQueryPlanner();
plan = planner.makePlan(selClause, null);
}
public void setTupleProcessor(TupleProcessor tupleProcessor) {
this.tupleProcessor = tupleProcessor;
}
@Override
protected TupleProcessor getTupleProcessor(EventDispatcher eventDispatcher) {
if (tupleProcessor == null) {
logger.info("No tuple processor; using a PrettyTuplePrinter");
tupleProcessor =
new PrettyTuplePrinter(SessionState.get().getOutputStream());
//tupleProcessor = new TuplePrinter(SessionState.get().getOutputStream());
}
return tupleProcessor;
}
@Override
public String toString() {
return "SelectCommand[" + selClause + "]";
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.expressions.Expression;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.server.properties.PropertyRegistry;
/** Implements the "SET VARIABLE ..." command. */
public class SetPropertyCommand extends Command {
/** The name of the property to set. */
private String propertyName;
/** The value to set the property to. */
private Expression valueExpr;
public SetPropertyCommand(String propertyName, Expression valueExpr) {
super(Command.Type.UTILITY);
this.propertyName = propertyName;
this.valueExpr = valueExpr;
}
@Override
public void execute(NanoDBServer server) {
PropertyRegistry propReg = server.getPropertyRegistry();
Object value = valueExpr.evaluate();
propReg.setPropertyValue(propertyName, value);
out.printf("Set property \"%s\" to value %s%n", propertyName, value);
}
}
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.server.properties.PropertyRegistry;
/**
* Implements the "SHOW VARIABLES" command.
*/
public class ShowPropertiesCommand extends Command {
private String filter = null;
public ShowPropertiesCommand() {
super(Command.Type.UTILITY);
}
public void setFilter(String filter) {
this.filter = filter;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
PropertyRegistry propReg = server.getPropertyRegistry();
ArrayList<String> propertyNames =
new ArrayList<>(propReg.getAllPropertyNames());
Collections.sort(propertyNames);
ArrayList<String> values = new ArrayList<>();
int maxNameLength = 0;
int maxValueLength = 0;
for (String name : propertyNames) {
Object value = propReg.getPropertyValue(name);
String valueStr = (value != null ? value.toString() : null);
values.add(valueStr);
if (name.length() > maxNameLength)
maxNameLength = name.length();
if (valueStr == null) {
if (maxValueLength < 4)
maxValueLength = 4;
}
else if (valueStr.length() > maxValueLength) {
maxValueLength = valueStr.length();
}
}
String formatStr = String.format("| %%-%ds | %%%ds |%%n",
maxNameLength, maxValueLength);
char[] lines = new char[maxNameLength + maxValueLength + 7];
Arrays.fill(lines, '-');
lines[0] = '+';
lines[lines.length - 1] = '+';
lines[maxNameLength + 3] = '+';
String lineStr = new String(lines);
out.println(lineStr);
out.printf(formatStr, "PROPERTY NAME", "VALUE");
out.println(lineStr);
for (int i = 0; i < propertyNames.size(); i++) {
String name = propertyNames.get(i);
String valueStr = values.get(i);
out.printf(formatStr, name, valueStr);
}
out.println(lineStr);
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.server.performance.PerformanceCounters;
/**
* Implements the "SHOW [system] STATS" command.
*/
public class ShowSystemStatsCommand extends Command {
public static final String STORAGE_SYSTEM = "storage";
/** The subsystem that we are displaying statistics for. */
private String systemName;
/**
* These are the performance counters corresponding to various subsystems.
*/
private static final String[][] PERF_COUNTERS = {
{ STORAGE_SYSTEM, PerformanceCounters.STORAGE_PAGES_READ },
{ STORAGE_SYSTEM, PerformanceCounters.STORAGE_PAGES_WRITTEN },
{ STORAGE_SYSTEM, PerformanceCounters.STORAGE_FILE_CHANGES },
{ STORAGE_SYSTEM, PerformanceCounters.STORAGE_FILE_DISTANCE_TRAVELED }
};
public ShowSystemStatsCommand(String systemName) {
super(Command.Type.UTILITY);
if (systemName == null)
throw new IllegalArgumentException("systemName cannot be null");
this.systemName = systemName.trim().toLowerCase();
// Make sure the actual system-name is recognized!
if (!this.systemName.equals(STORAGE_SYSTEM)) {
throw new IllegalArgumentException(
"Unrecognized system-stats argument: " + this.systemName);
}
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
for (String[] pair : PERF_COUNTERS) {
if (pair[0].equals(systemName)) {
String name = pair[1];
long value = PerformanceCounters.get(name);
out.printf("%s = %d%n", name, value);
}
}
}
}
package edu.caltech.nanodb.commands;
import edu.caltech.nanodb.expressions.ColumnName;
import edu.caltech.nanodb.queryeval.ColumnStats;
import edu.caltech.nanodb.queryeval.TableStats;
import edu.caltech.nanodb.relations.Schema;
import edu.caltech.nanodb.relations.TableInfo;
import edu.caltech.nanodb.server.NanoDBServer;
import edu.caltech.nanodb.storage.StorageManager;
import edu.caltech.nanodb.storage.TableManager;
/**
* Implements the "SHOW TABLE t STATS" command.
*/
public class ShowTableStatsCommand extends Command {
/** The name of the table whose statistics to print out. */
private String tableName;
public ShowTableStatsCommand(String tableName) {
super(Command.Type.UTILITY);
this.tableName = tableName;
}
@Override
public void execute(NanoDBServer server) throws ExecutionException {
StorageManager storageManager = server.getStorageManager();
TableManager tableManager = storageManager.getTableManager();
TableInfo tableInfo = tableManager.openTable(tableName);
Schema schema = tableInfo.getSchema();
TableStats stats = tableInfo.getStats();
out.printf("Statistics for table %s:%n", tableName);
out.printf("\t%d tuples, %d data pages, avg tuple size is %.1f bytes%n",
stats.numTuples, stats.numDataPages, stats.avgTupleSize);
int numCols = schema.numColumns();
for (int i = 0; i < numCols; i++) {
ColumnName colName = schema.getColumnInfo(i).getColumnName();
ColumnStats colStat = stats.getColumnStats(i);
out.printf("\tColumn %s: %d unique values, %d null " +
"values, min = %s, max = %s%n", colName.getColumnName(),
colStat.getNumUniqueValues(), colStat.getNumNullValues(),
colStat.getMinValue(), colStat.getMaxValue());
}
}
}
package edu.caltech.nanodb.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import edu.caltech.nanodb.storage.StorageManager;
import edu.caltech.nanodb.server.NanoDBServer;
/**
* Implements the "SHOW TABLES" command.
*/
public class ShowTablesCommand extends Command {
public ShowTablesCommand() {
super(Command.Type.UTILITY);
}
@Override
public void execute(NanoDBServer server)
throws ExecutionException {
StorageManager storageManager = server.getStorageManager();
ArrayList<String> tableNames = new ArrayList<>(
storageManager.getTableManager().getTables());
Collections.sort(tableNames);
// Determine max name length. Start at 10 because "TABLE NAME" is 10
// chars long, and so we'd be at least that wide anyway.
int maxNameLength = 10;
for (String name : tableNames) {
if (name.length() > maxNameLength)
maxNameLength = name.length();
}
// Create formatting
String formatStr = String.format("| %%-%ds |%%n",
maxNameLength);
char[] lines = new char[maxNameLength + 4];
Arrays.fill(lines, '-');
lines[0] = '+';
lines[lines.length - 1] = '+';
String lineStr = new String(lines);
// Print out table information with headers
out.println(lineStr);
out.printf(formatStr, "TABLE NAME");
out.println(lineStr);
for (String name : tableNames)
out.printf(formatStr, name);
out.println(lineStr);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment